Three questions, answered by one table.
Ask an application whether a person may open a record and it will usually answer with a single lookup. That lookup is quietly conflating three different questions: who is this person, what has their organisation bought, and what are they permitted to do with it.
The first is authentication and belongs to an identity service. The second is entitlement: a commercial fact about a subscription, a plan and a seat. The third is authorisation: a policy decision about a subject, an action and a resource. They change on completely different clocks. Identity changes when somebody joins or leaves. Entitlement changes when a contract is signed or a seat reassigned. Authorisation changes when a manager moves a person between teams.
Collapsed into one roles table inside one application, all three are correct on the day it ships. The trouble starts with the second application.
The second-application problem.
The second product needs the same three answers, so the model is copied. There are now two implementations of one intent and nothing that keeps them equal. They drift in three predictable ways.
- Vocabulary. One product's administrator includes billing and the other's does not, and nobody wrote either definition down.
- Timing. A seat is removed in the billing system; one product notices at next login, the second caches for a day, the third never re-reads at all.
- Coverage. A permission is added to one product because a customer asked for it. It has no counterpart anywhere else, and it is now permanent.
By the fifth product these have stopped being bugs anybody can fix individually. The only repair is to move the decision, and moving it later costs considerably more than starting there.
What the platform actually holds.
In CEDX OS the entitlement is a record in its own right: a subject, an organisation, a product, a plan, a seat and a scope, with a validity window. Gate holds identity and issues the session. The individual product holds only what is genuinely its own: that a draft invoice may be edited by its author until it is posted is Ledger's rule and should never leave Ledger.
If two products would have to agree on a rule, it is not the product's rule. Everything else is.
Decisions are evaluated at one point, deny by default, and are recorded with their inputs: subject, resource, action, the grants that applied and the version of the policy in force. Recording the inputs rather than only the outcome is what makes the log worth keeping. Six months later, “denied” tells you nothing. “Denied, because the seat expired on the ninth” tells you whether the fault was in the software or in the contract.
The latency objection, and the honest answer.
The standard objection is that a central authoriser adds a network hop to every check, and that a list view performs thousands of them. The objection is correct, and pretending otherwise is how these projects fail six months in.
The answer is not to make the hop faster. It is to make most checks not need it. The session carries a signed grant set with a short validity (minutes rather than hours) which resolves the overwhelming majority of decisions locally. Anything that changes a grant publishes a revocation that products subscribe to, so the window between a change and its effect is bounded by the revocation channel rather than by the lifetime of the token.
This is a staleness trade, and it should be made deliberately and written down. A few seconds of stale read access to a document is usually acceptable. A few seconds of stale permission to approve a payment is not, so payment approval takes the hop. Deciding that per action, in one place, is another thing you cannot do once the model has been copied into every application.
Offboarding is the real test.
The honest measure of an entitlement model is not how quickly it grants. It is how completely it revokes. Ask an organisation running five separate permission tables how long a leaver keeps access to the fifth one; the answer is usually that nobody knows, because there is nowhere to ask.
Centralised, revocation is one write and one event. The more interesting consequence is that the question becomes answerable at all. “Who could have opened this record in March” is a query over versioned grants rather than an archaeological dig across five databases and a backup tape.
Getting there from here.
Run it in shadow before you enforce it.
Do not begin by removing the per-application checks. Put the central service behind them and run it in shadow: every local decision is also evaluated centrally, and disagreements are logged rather than enforced. Expect disagreements in both directions. The shadow period is where the undocumented rules finally surface, and there are always more of them than the team believes.
Only when a product's disagreement rate has sat at zero for a while do you invert it and let the central decision win, leaving the local check in place as a second opinion for one more release. It is slower than a rewrite. It is also the only version of this exercise that does not produce an incident.


