Restricting a role does nothing to a route that has no permission on it
- Symptom
The stated goal was “restrict this area to the bid team”. The role existed, most of the top-level bid routes already carried a permission, and the admin tab could grant it. It looked like a configuration task.
- Diagnosis
The questionnaire tree, which is where the actual content lives, had no resource in the permission catalogue whatsoever. Forty-four routes, authentication only. A route that asks only are you signed in has the entire signed-in population as its blast radius, and that population was three orders of magnitude larger than the intended audience.
Enumerating what was reachable is what made the ticket urgent rather than tidy. A bulk delete of the shared answer corpus. A silent rewrite of an approved answer that gets quoted verbatim into a customer-facing document, with no approval step between the edit and the quote. An unmetered full-document model pass per call. And a read of any tender by id, because no by-ID route checked ownership, so a link pasted into a chat was a working grant.
- Decision
Fold all of it under the existing bid resource rather than mint a questionnaire resource of its own. The team that may see a tender is the same team that may see its questionnaires; a second resource would have meant a second permission to seed, a second thing to grant, and a new way for the two to disagree. Fewer moving parts, and one less seeding step, which matters because seeding is where the lockout risk lives.
That ordering is the part worth stating. The middleware fails closed and the permission lookup is cached, so enforcement and provisioning have to ship in the same deployment, with the cache invalidated. Ship the guards first and the first thing the change does is lock out the team it was written for.
Inside the role there are deliberately no silos: every holder can see and edit every bid. That is what the team wanted, since they hand tenders between each other constantly, but it also means mutual delete rights, and a wrong click is not recoverable through the interface. This is written down as an accepted consequence of the design rather than left to be discovered later as a bug report. The honest framing is that the boundary is around the team, not within it.