Every permissions system is correct for the requirements it was designed against. The interesting failures happen when a genuinely reasonable new requirement does not fit the shape at all.
The original design
Three roles — admin, manager, staff — each with a fixed set of permissions. It matched the client's organisation chart exactly and took an afternoon to build. For a year it needed no changes, which in hindsight should have made us suspicious rather than comfortable.
The request that broke it
A manager wanted to grant one specific staff member access to payroll reports, without promoting them to manager and without giving every staff member that access. Nothing about "admin, manager, staff" has room for an exception scoped to one person and one resource.
Why we did not rebuild immediately
The temptation was to redesign for full generality — arbitrary permissions on arbitrary resources for arbitrary users — the day the first exception appeared. We waited for a second and third request instead, because a permissions model designed around one anecdote tends to be wrong in a different, more expensive way.
What we built once the pattern was clear
- Roles stayed as the default — most users need nothing more specific and should not have to think about it.
- Resource-level grants were added as an explicit override, visible in the interface as a specific exception, not hidden in a config table.
- Every override expires or is reviewed — a grant with no review date is a permission nobody remembers to revoke.
The part that took the longest
Not the schema — the audit trail. Once permissions could be layered, "why does this person have access to this" had to be answerable without reading code, because the client's compliance obligations required it. We built a plain-language log of every grant, who made it, and why, before we shipped the feature that made grants possible.
The expensive part of a flexible permissions model is rarely the flexibility. It is being able to explain, months later, why any given person can see what they can see.
What we would tell ourselves a year earlier
The original three-role system was the right call for the requirements at the time. The mistake would have been building the flexible version pre-emptively, against imagined future requirements rather than the one that actually arrived.
