Repository navigation
sys_permission_set.active and sys_position.active are unenforced too — both Deactivate dialogs promise access stops, and it does not #8613
Description
Activity
Triage:
needs-user-decision+domain:identity+security; native type Bug (both dialogs declare behavior the runtime does not deliver — declared ≠ enforced). Triage seat Routine, 2026-08-14 ~02:15Z.Routing note: unlike sibling #8535 (
domain:metadata), every surface this card names lives inpackages/plugins/plugin-security/— the object definitions (src/objects/sys-permission-set.object.ts,sys-position.object.ts), the four generated locale bundles, and the plausible enforcement point (security-plugin.tsDB loader). Anchoring rule ⇒ identity lane.Why the inbox, not a rider on the #8535 ruling: the 2026-08-13 sys_capability ruling (option B — withdraw the claim) was argued from "don't put the catalogue registry on the authorization hot path". Neither ground carries over:
sys_permission_setrows are already read on the hot path (the DB loader the card cites), and a permission set / position is the grant itself, not a catalogue entry. Enforcement here changes runtime access-resolution semantics ⇒ human floor, correctly filed as its own decision.Four-prism block (required):
- Platform long-term coherence — split. Withdrawing makes
activea uniform never-enforced catalogue flag across all three RBAC objects (consistent withsys_capability.activeis read by nothing — the Deactivate action tells the admin grants stop resolving, and they do not #8535). Enforcing givesactivereal semantics exactly where the object is the grant — but leaves the column meaning two different things across the family unless capability's ruling is ever revisited. - Measured business pull — favors enforce. "Switch a grant off without deleting it" (compromised or over-broad set) is a real admin operation; the promise already ships in four locales; the card's measured hazard is that an admin who trusts the dialog will not take the action that would actually work.
- AI-agent error-resistance — enforce fully or withdraw loudly, never the middle. A partial enforce (DB-loader predicate only) leaves the metadata-by-name and bootstrap-by-name resolution sources unfiltered — a wall that looks enforced and is not, which is worse than today's honest absence. Either full-coverage enforcement across all three sources, or withdrawal; no silent middle.
- Startup scope discipline — favors withdraw. Withdraw is a patch-sized text change; enforce adds a permanent hot-path obligation plus a behavior flip for existing
active:falserows on deployed instances.
Recommendation: enforce, for both objects, at the resolution seam (DB loader predicate + carry
activethrough the mappedPermissionSetshape + the same filter on the metadata and bootstrap sources; forsys_position, the named row-read seams). The behavior flip on existingactive:falserows lands on the side of what the admin was told when they clicked Deactivate — but it is a behavior change on deployed data and needs the breaking/release-notes judgment made explicitly. Fallback if scope discipline wins: withdraw both dialogs now (as #8535 option B), and file enforcement as a Feature card. Partial enforcement is rejected under prism 3 either way.Serial constraints (for the eventual dispatch): the withdraw path touches plugin-security's generated locale bundles — same file family as in-flight PR #8599 (#8554) and queued #8601; hard serial after those land. No file overlap with #8535's PR (
sys_capabilityonly), but adjudicate with that ruling in view.
Generated by Claude Code
- Platform long-term coherence — split. Withdrawing makes
Maintainer ruling (2026-08-14, verbatim: 「同意你的建议」, approving item 4 of the eight-item decision-box list: 「#8613:enforce 全覆盖,target:v17 进席 A?」): enforce
activefor bothsys_permission_setandsys_position, at every resolution source — DB loader, metadata/bootstrap sources, positional lookups — never partial coverage, per the triage analysis already on this card. Deactivation is an incident-response control; it must actually stop access.needs-user-decision→pm:queue,target:v17; Seat A (#8667). Serial note: the enforce path as scoped does not touch the plugin-security locale bundles; the serial-after-#8599/#8601 constraint applies only if any dialog copy changes end up in scope.
Generated by Claude Code
- added 6 commits that reference this issue
on Aug 17, 2026 - added a commit that references this issue
on Aug 17, 2026 - added a commit that references this issue
on Aug 23, 2026 - added a commit that references this issue
on Oct 7, 2026
Found while implementing #8535 (which withdraws the identical false claim on
sys_capabilityonly). Filed unassigned — out of scope for #8535's PR, which deliberately touches nothing butsys_capability.#8535 established that
sys_capability.activeis read by nothing while its Deactivate dialog claims grants stop resolving. The same shape exists on the other two RBAC catalogue objects, and on those it is worse: a permission set and a position are the grant itself, not a catalogue entry.The claims
sys-permission-set.object.ts,deactivate_permission_set:sys-position.object.ts,deactivate_position:Both ship translated into all four locales.
What actually happens (measured on
b45c71e85a)Nothing filters on
activeanywhere in the resolution chain:SecurityPlugin.resolvePermissionSetsForContext(security-plugin.ts:3626) buildsrequestedfromcontext.positions+context.permissionsplus the additive baseline. Noactivecheck.PermissionEvaluator.resolvePermissionSets(permission-evaluator.ts:398) matches metadata sets byname, then bootstrap sets byname, then falls through to the DB loader. Noactivecheck in any of the three sources.security-plugin.ts:812) queriessys_permission_setwithwhere: { name: { $in: names } }— noactivepredicate — and the row is mapped to aPermissionSetthat does not carryactiveat all, so nothing downstream could filter on it even if it wanted to.sys_positionrow read (delegated-admin-gate.ts,security-plugin.ts, the bootstrap seeders) looks up bynameorid. None readsactive.So
active: falseon a permission set or a position changes a column value and a badge in Setup. The assignments keep granting.Why this is worth its own card
Same direction of falsehood as #8535, higher blast radius. An admin revoking a compromised or over-broad permission set is told in a confirmation dialog that access stopped; it did not. The admin's likely next action is to not do the thing that would actually work (delete the set, or remove the assignments).
Note
sys_positionwas partially touched by #8556/#8601, but that was different text — thedeactivate_positiondialog above is still onmainmaking the claim.Not a recommendation
The direction is the same contract decision the maintainer already ruled on for
sys_capability(2026-08-13, ADR-0049 enforce-or-remove, option B: withdraw the claim rather than put the registry on the authorization hot path). Whether that ruling extends to these two is not something this finding assumes: unlike a capability catalogue, a permission set'sactiveflag has a plausible real enforcement point (the DB loader'swhere, one predicate) and a plausible real business pull (switching a grant off without deleting it). Both options are live and the trade-off is different from #8535's. Needs triage, not a rider.