28 August 2026

Don't flatten permissions

The easiest design is also the fastest way to leak.

When you index a source system, the tempting move is to compute a single visibility bit per object per user and store it alongside the content. Queries become trivial: filter on the bit. We did not do this, and the reason is that the bit is a lossy projection of a richer rule, and lossy projections leak.

Three shapes that do not collapse

Any of these turns a cached bit into a stale bit, and a stale bit that says "visible" is an incident.

We keep the rule, not the answer. Evaluation happens at query time, against the permission state as it exists right then.

What it costs

Query latency. We evaluate a predicate tree per candidate object rather than reading a column. On a workspace with a hundred thousand objects and a deep group graph, that is the difference between twelve milliseconds and ninety.

We accept it. Ninety milliseconds is still interactive, and the alternative is a class of bug that no amount of testing fully closes.

The rule we hold ourselves to

If a user cannot see an object in the source system, they must not be able to see it here — including through citations. When an answer's supporting object turns out to be invisible to the requester, we drop the citation and mark the answer as partially supported rather than showing a redacted stub. A redacted stub is itself a disclosure.