All writing
Architecture·3 min read·Mar 2, 2026

Permissions Are Architecture, Not a Feature

Every system that gets permissions late pays for it twice. Notes from nine years maintaining a four-layer permissions model.

ArchitecturePermissionsSystems

Permissions get scheduled like a feature. They sit in a backlog next to "export to CSV" and "dark mode," they get estimated in days, and they get built once someone complains that the wrong person saw the wrong record.

By then it is the most expensive thing on the board.

Why retrofitting costs so much more

A permissions model is not a check you add at the top of a handler. It is a claim about what every query in the system is allowed to return.

Add it on day one and it is one line in a query builder that everything already routes through. Add it in year three and you have four hundred query sites, each written by someone who assumed they were allowed to see everything, and no reliable way to enumerate them. Miss one and you have not shipped a bug — you have shipped a disclosure.

The same is true of audit logging, and for the same structural reason. Both are cross-cutting claims about every data access in the system. Cross-cutting concerns are cheap when they are a property of the foundation and ruinous when they are a patch.

Four layers, and what each one is for

The model I maintain operates at four levels:

  • Profile — what this kind of user can do. Broad, static, easy to reason about.
  • User — deviations for a specific person. Necessary, and the layer that quietly accumulates entropy if you let it.
  • Criteria — access driven by the data itself: this programme, this site, this status. Where most real-world rules actually live.
  • Field — visibility of individual fields on records the user can otherwise see.

Most teams build the first and discover they needed the third. Criteria-level rules are the ones the business actually describes when you ask them who should see what, and they are the ones you cannot express with roles alone.

The hard part

Field-level permissions over user-configurable fields is where this stops being a solved problem.

If your users can define new fields — and on a research platform they must, because you cannot ship an engineer every time a study needs a new measurement — then your permission model has to stay coherent against a schema that changes without you. A permission that references a field is a foreign key into something mutable. Deleting a field, changing its type, or duplicating it are all now permission events.

We handled it by treating field definitions as first-class records with their own lifecycle, so a permission can reference a stable identity rather than a name. That sounds obvious written down. It was not obvious in year two, and getting it wrong would have meant permissions that silently stopped applying when someone renamed a column.

The rule I'd give a team starting today

Decide who can see what before you decide what you are storing. Not the full model — you won't know it yet — but the shape: are rules going to be role-based, or data-based? If there is any chance of the second, put the query layer in place now, while there is one query site to change instead of four hundred.

It is the cheapest architectural insurance in software, and it is almost never bought.