Filed as a finding (recording only, unassigned, ungraded) out of the review of PR #8713 / #8613. Not fixed there deliberately — it is the structural gap that card exposed, not the card's own work.
The observation
packages/plugins/plugin-auth/src/last-admin-guard.ts decides whether to pay for a full "who is still an administrator" enumeration by testing the pending write against three standing-key lists, one of which is:
constPERMISSION_SET_STANDING_KEYS=['name','active']asconst;// was ['name'] before #8613
That list is not an independent design artifact. It is a cache of which columns resolveAuthzContext reads when deriving platform_admin. Its correctness is entirely derivative: if the resolver starts reading a column the list omits, the guard silently stops firing on the one write class that can empty the administrator population.
Nothing mechanical enforces that correspondence. It is maintained by a prose comment and by whoever next edits the resolver happening to remember the guard exists in another package.
Why this is worth a card rather than a comment
It has already failed once, and the failure mode was the dangerous direction — a false assertion left standing in the code:
An authoritative, recently-dated, wrong comment in a security guard is the specific hazard here — it reads as a checked fact to the next author, human or agent.
Shape of a fix (sketch, not a prescription)
The gate wants to answer: does the set of columns the platform-admin derivation reads equal the set the guard treats as standing-bearing? Candidate approaches, in rough order of cost:
- A check script that extracts the column literals read along
resolveAuthzContext's platform-admin path (packages/core/src/security/resolve-authz-context.ts) and the standing-key lists (last-admin-guard.ts), and fails on asymmetry — the same family as the existing check:authz-resolver. - A shared, single-source declaration both sides import, so the two cannot spell it differently.
- Failing either, a test that asserts the correspondence explicitly, so the pin lives somewhere a resolver change will break.
Whichever shape, the property to hold is that a resolver change that starts reading a new column cannot land green while the guard's list omits it.
Note the same reasoning applies to the sibling lists in that file (GRANT_STANDING_KEYS and the membership/identity-table equivalents), which have the same derivative relationship to their readers.
Not in scope
Not asserting the current lists are wrong — after #8613 they are believed correct. This is about the absence of anything that would tell us if they stopped being correct.
Filed as a
finding(recording only, unassigned, ungraded) out of the review of PR #8713 / #8613. Not fixed there deliberately — it is the structural gap that card exposed, not the card's own work.The observation
packages/plugins/plugin-auth/src/last-admin-guard.tsdecides whether to pay for a full "who is still an administrator" enumeration by testing the pending write against three standing-key lists, one of which is:That list is not an independent design artifact. It is a cache of which columns
resolveAuthzContextreads when derivingplatform_admin. Its correctness is entirely derivative: if the resolver starts reading a column the list omits, the guard silently stops firing on the one write class that can empty the administrator population.Nothing mechanical enforces that correspondence. It is maintained by a prose comment and by whoever next edits the resolver happening to remember the guard exists in another package.
Why this is worth a card rather than a comment
It has already failed once, and the failure mode was the dangerous direction — a false assertion left standing in the code:
admin_full_access那条sys_permission_set行,一次废掉所有 platform admin #6084 wrote, in the comment beside the list, that everything a permission-set write touches other thanname— explicitly namingactive— is invisible to "who is an administrator".sys_permission_set.activeandsys_position.activeare unenforced too — both Deactivate dialogs promise access stops, and it does not #8613 madeactivea resolution-time predicate, which made the sentence false and opened a one-click, in-product-unrecoverable path to an installation-wide administrator lockout.An authoritative, recently-dated, wrong comment in a security guard is the specific hazard here — it reads as a checked fact to the next author, human or agent.
Shape of a fix (sketch, not a prescription)
The gate wants to answer: does the set of columns the platform-admin derivation reads equal the set the guard treats as standing-bearing? Candidate approaches, in rough order of cost:
resolveAuthzContext's platform-admin path (packages/core/src/security/resolve-authz-context.ts) and the standing-key lists (last-admin-guard.ts), and fails on asymmetry — the same family as the existingcheck:authz-resolver.Whichever shape, the property to hold is that a resolver change that starts reading a new column cannot land green while the guard's list omits it.
Note the same reasoning applies to the sibling lists in that file (
GRANT_STANDING_KEYSand the membership/identity-table equivalents), which have the same derivative relationship to their readers.Not in scope
Not asserting the current lists are wrong — after #8613 they are believed correct. This is about the absence of anything that would tell us if they stopped being correct.