← Back to everything else

Case study · production

How an assistant reads payroll
without leaking it

ApproveHR runs HR and payroll for Nigerian companies. Putting an assistant inside it means every answer is a potential disclosure, and the model is the last thing you should trust to decide who may see what.

Role  built the assistant's query engine, backend and frontend Stack  TypeScript · Prisma · React · Gemini · SSE Status  in production at Schulltech

01

The obvious design is the dangerous one

The straightforward build gives the model broad database access and asks it, in the prompt, not to reveal things it shouldn't. That works until someone phrases a question the prompt didn't anticipate. Prompt instructions are a request, not a boundary. In a payroll system the failure is one employee reading another's salary.

The assistant should not be trusted with the rule. It should be unable to break it.

02

The catalogue is built from permissions

Rather than filtering results after the model asks, the catalogue declares every queryable entity and field along with the permission it requires and the rows a caller may see. The model gets one read-only query tool, and the schema behind it is built per request from the asking user's own permissions. A field the caller can't see arrives redacted: the model learns only whether a value exists, never what it is.

The server enforces the same catalogue when the query runs. A model that guessed the name of a hidden field still gets nothing back.

question auth who is asking catalogue query schema fields gated by permission rows scoped to the caller model picks from what exists read streamed answer every turn: tokens metered, trace written at chosen verbosity

Permissions shape the tools before the model sees them. Filtering after the fact leaves a window where the data has already been read.

03

Streamed, with a ceiling

Answers stream over server-sent events as they're written, so nobody stares at a spinner. A turn can make at most four calls, and if the person closes the tab, the turn stops instead of spending tokens on nobody.

04

What it costs, and what it saw

Token spend is metered per organisation across a daily and a monthly window, so a runaway conversation is visible before it's an invoice. Every turn writes a trace, and because those traces carry real pay figures and leave reasons, verbosity is a setting rather than a constant: full detail while debugging, timings only in normal running, off entirely when it should be.