Repository navigation
test(core): pin that the legacy platform-admin deprecation latch dies with the process (L5) - #13666
Conversation
… with the process Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZC5rNQj3WEet5HAmmAkMs
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
|
PM read ( CI35 checks at Fence
The pin does what the body claimsRead directly rather than taken from the report. Three
The The reverse verification is the real deliverableThe dev did not assert coverage — it measured it, with on-disk proof at both ends:
That is the ablation pattern done to standard. An "I added a test" claim with no ablation would not have shown that the 25 siblings all stay green under the mutation — which is the whole justification for the card. Discipline readings 1–3, and one correction I am adoptingThe three claims #11975 owed were measured against
That is the ruled shape (design §5 step 3 — config standing is added, nothing is revoked in this step). Exclusion is the window's exit, and it is #13515's, not this card's. Anyone reading "derivation prefers env" as "the row stops working" would mis-scope #13515. No changeset — correct, not an omissionThe diff is one Two follow-ons, both already where they belong
#11975 stays open on its own acceptance criterion: the exit on Generated by Claude Code |
Part of #11975 — leg L5 of the #11663 platform-admin re-anchor.
This card is a verification-and-disposition slice, and its main output is the
measurement below, not the code. The entry half of the migration window was found
already delivered by L2 and L4; the one discipline the card names that nothing
pinned is now pinned. One test file, no behaviour change.
What the card owed, and what was found
#11975 carries the migration window's discipline: derivation prefers env · a
detected legacy grant logs its deprecation pointer every boot · never a silent
dual-track. All three were measured against
origin/main, not assumed.1. Derivation prefers env — SATISFIED (precedence, not exclusion)
packages/core/src/security/resolve-authz-context.ts§6b-config isif (configConfersPlatformAdmin) { ... } else if (hasPlatformAdminGrant) { report }.The config arm is evaluated first, confers standing, and pushes the declared
capability envelope from
ADMIN_FULL_ACCESS_CAPABILITIES; the legacy arm isreachable only when config does not confer.
administrators declared, a caller who does not match still has their legacy row
honoured. That is the ruled shape — design §5 step 3, "Config-derived standing is
added; the legacy grant read is still honoured. Nothing is revoked in this
step." Exclusion is the window's EXIT, and it belongs to #13515.
2. Every boot vs once per process — MEASURED, and it satisfies the card
The latch is a module-scope
let legacyGrantPointerSaidinpackages/core/src/security/platform-admin.ts. Measured over three fresh moduleinstances (the closest in-process stand-in for a boot): 1 line each, and two
calls inside one instance still produce 1. So "once per process" and "every boot"
coincide — nothing persists the suppression past the process, and a restarted
deployment is told again.
Two ways it differs from a literal per-detection line, both ruled by design P5
("logged once per process (not per request) naming the row, its holder and the
exact config line") and both disclosed in the message text itself:
plugin-security/src/bootstrap-platform-admin-walled-owner.test.ts, "EXACTLY ONEline ... even across repeated bootstraps";
per process; further holders are not listed." Enumerating holders is the census's
job (design §5 step 7), not this pointer's.
3. Never a silent dual-track — NONE FOUND
Two detectors share one latch and cover both entry points: boot-side
(
plugin-security/src/bootstrap-platform-admin.ts, walled) and request-side(
resolve-authz-context.ts, posture-independent).hasPlatformAdminGrantiswritten in exactly two places and consumed once by
derivePosture; a repo-widegrep finds no third route to
PLATFORM_ADMIN. The cross-request grants cache isin-process and default-OFF, so it cannot carry standing across a boot without a
derivation having run. The only path where a legacy row confers with no line is
the same caller also matching config, where standing no longer rests on the row
— already pinned in
resolve-authz-context.platform-admin-config.test.ts.The gap this PR fills
Every existing pin asserts suppression. None asserts that the suppression
ends. A latch moved onto
globalThis, or persisted to quiet the noise, turns"once per process" into "once ever" and leaves all of them green — while a
deployment restarted onto the old anchor is never told again. That is the silent
dual-track the window exists to prevent, and it is precisely the half the card names
by name ("every boot").
So this PR adds one pin to
packages/core/src/security/platform-admin.test.ts:three fresh module instances must each emit exactly one line — not zero (a latch
that outlived the process) and not two (a pointer gone per-call).
Reverse verification. The pin was committed first, then
platform-admin.ts's latch was mutated to aglobalThis-keyed one. Mutationconfirmed on disk before measuring — HEAD blob
c2abfe57, mutated blob8e9478c3,injected marker present 3x, original declaration count 0. Result: the new pin
failed (
expected [ +0, +0, +0 ] to deeply equal [ 1, 1, 1 ]) and the other 25tests in the file stayed green — confirming this is the only pin covering the
every-boot half. Restored and verified by state: worktree blob back to
c2abfe57,marker count 0,
git diff HEADempty. No rebuild was needed on either leg — thetest imports
./platform-admin.js, a package-relative specifier vitest resolves tosource rather than through
exports/dist.The minor boundary #13515 must honour
Established here because #13515 is blocked on this card and needs exactly this
number:
@objectstack/plugin-security@17.2.0(and@objectstack/core@17.2.0) is thelatest released tag, cut at
e7d2cc67fon 2026-08-23.b9972720fon 2026-08-31 — after that tag, so it is not in17.2.0.
.changeset/walled-bootstrap-stops-granting.mdis still unconsumedon
mainand declares"@objectstack/plugin-security": minor. No pendingchangeset anywhere in
.changeset/declares amajor.⇒ L4 ships in 17.3.0, and the exit may land no earlier than 17.4.0 — it must
not ride the 17.3.0 release train. Recorded on #13515.
Disposition
The window's entry is delivered and correctly disciplined. #11975's own acceptance
criterion also requires the exit on
origin/main, which is #13515 and cannot landbefore 17.4.0 — so this card's only live residue is that card. #13515 is not
addressed here.
Verification
Run at
d0c912847, the final commit.pnpm --filter @objectstack/core exec vitest run --maxWorkers=2 src/security/platform-admin.test.ts src/security/resolve-authz-context.platform-admin-config.test.ts src/security/admin-standing-surface.test.ts—Test Files 3 passed (3) · Tests 53 passed (53).node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, both sections: 22 path-derived + 6 convention-triggered (change kind "adds or edits a test file"). 23 ran green.check-test-completeness.mjs(needs a saved test-run log; the family names it withno argument) and
check:dual-build-cjs-loads("PREREQUISITE NOT MET ... this isNOT a pass: nothing was measured" — needs a whole-workspace build).
pnpm lint(eslint . --no-inline-config, whole repo, no narrowing) — exit 0.check:type-check-coveragegreen. Its--re-measurehalf (check:type-check-debt)needs the whole-workspace build and was not run locally; what was measured
instead is
packages/core's owntsc --noEmit --listFiles: the new test file is inthe program (525 files, 1 match —
packages/coresetsexclude: []) andcontributes 0 of the package's 98 pre-existing errors, so this diff cannot move
the ratchet up. CI runs the re-measure.
check:nul-bytesgreen;grep -naPcontrol-character self-scan over the editedfile finds nothing.
No changeset: the diff is one
*.test.ts, andpackages/corepublishes only["dist","README.md","CHANGELOG.md"], so nothing is released. Labelledskip-changeset.Out-of-scope finding, filed separately
Measured while checking discipline 3, and not touched here: on a
single-posturerig — the DEFAULT posture — the request-side pointer fires for the first-user
promotion row and tells the operator their anchor "is removed in a later release",
which under ruled Choice 4A is not true for that rig. Filed unassigned; it bears on
#13515's
singlecarve-out clause.Generated by Claude Code
Generated by Claude Code