Alex HerreraSoftware developer · Cybersecurity focus
ES
← Back to projects

Professional

Kaizen AI | Business intelligence

Companies query their data in plain language, and the server limits every answer to their company and their role.

  • AI
  • Security
Role
Development
Period
2025-09 – 2026-07

Stack

  • Next.js
  • TypeScript
  • Vercel AI SDK
  • Upstash Vector
  • Cohere rerank
  • Drizzle ORM
  • MySQL
  • Google Cloud Run

Context

Kaizen licenses an HR ERP to other companies. Kaizen AI is the tool those companies use to query their own data. They write the question in plain language, without going through a report or through the support team.

The project came out of an operational need. The support team answered every client’s questions one by one, and that work grew at the same rate as the client base.

Problem

Every client question was answered by someone on the support team. When demand for help grew, the team saturated and the client waited.

A large share of those questions repeated: how to do something, what an HR or occupational-health concept means, or what labour law says. Those were hours of a specialist team spent on questions that already had an answer.

Technical decisions

Several client companies use the tool on the same installation, so access control is the central decision of the project. A model querying production data raises a question a prompt cannot settle: who may read what. If the restriction depended on the model following instructions, rephrasing the question would be enough to get around it.

The question is translated into SQL and the server rewrites that query before running it. A rewriter over the syntax tree injects three filters: the client’s database, the session role and the scope of the action.

A query asking for another company’s data does not depend on a validation catching it: it runs already narrowed and returns only what is allowed.

None of the three restrictions is asked of the model. The server always injects them, so the reach of an answer does not depend on how the question is worded.

Architecture

The model does not connect to the database. It proposes SQL, and that text passes through the rewriter before it runs.

QuestionModelProposed SQLAST rewriterExecuted SQLPermitted data

Every query is scoped to the database of the client asking. A user at one company cannot reach another company's data, even if the generated SQL asked for it explicitly.

Developer, administrator and user see different sets. The filter is injected from the authenticated role, not from whatever the model proposes.

The specific action narrows the result once more. What the model returns is an intent; the effective scope is set by the server.

The SQL that ran is recorded, together with the role and the user. The audit trail covers the final query, not the one the model proposed.

The rewriter sits between the SQL the model proposes and the SQL that reaches the database.

The rewriter is the only point on the path that sets how much comes back, so the permission tests are written against it rather than against the conversation.

Result

The tool is in production for the companies that license the ERP. Each company queries its own data, each role sees its own set, sensitive fields are masked and every executed query is logged.

The questions that used to occupy the support team are now answered by the client, and the team handles what needs a person.

Retrieval requires a minimum relevance. A question with no backing in the data returns that there is no information, instead of text assembled from loosely related fragments.

What I learned

A visibility control can be written in the wording of the instruction or in the code. Only the second can be executed: a test passes a question through the rewriter and checks that the resulting SQL carries all three filters.

There is nothing equivalent to run against a prompt. So a guarantee that has to hold up in front of a client goes where a test can check it.