Uh oh!
There was an error while loading. Please reload this page.
feat(plugin-auth): platform-admin re-anchor L3 — re-point ensureDefaultOrganization to the config anchor; re-price last-admin-guard as its own reviewed step - #13685
Conversation
…nchor; move its trigger to the sys_user trigger set (L3) Design #11663 §2 step 5 / H4, ruled bundle 4A. The population question reads the config anchor first (matchesConfiguredPlatformAdmin from @objectstack/core — the derivation site's own predicate, no second derivation site) and falls back to the legacy unscoped admin_full_access grant (Choice 4A single-posture anchor + P5 honoured window, removed with migration step 6). The re-run trigger is the exported isDefaultOrganizationBootstrapTrigger: sys_user insert/create, sys_user update touching email/email_verified (the #11343 trigger set), plus the legacy grant-insert arm unchanged. No guard refusal is added or deleted in this commit — the last-admin-guard re-pricing is its own reviewed step (migration step 5), landing separately. Part of #11973 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZC5rNQj3WEet5HAmmAkMs
… retire the elevation framing from the walled-owner prose surfaces (L3 step 5) Migration step 5 of #11663 — its OWN reviewed change, separate from the re-pointing commit as the card requires. No refusal is added or deleted in code: every refusal is the output of the one resolveAdminUserIds enumeration, which has counted config-anchored administrators since L2, so pricing the population re-priced the refusals mechanically. This commit makes that a measured, pinned verdict: - OBSOLETE where the config anchor stands (pinned PERMITTED): delete / rename / deactivate of the admin_full_access sys_permission_set row, and deletion of the last legacy grant row, while a declared VERIFIED administrator stands. - KEPT where the grant anchor is load-bearing (pinned REFUSED): the identical four writes with no declared administrators (Choice 4A single-posture shape), and with a declared-but-UNVERIFIED account. - UNCHANGED: the zero-population tri-state (refuseIfEmptiedRatherThanFresh) — reachable only when no anchor stands, made rarer, never wronger; and the L2 fifth write shape (the newly-necessary refusals), verified as landed. Prose true-ups the L4 landing flagged to this card: the retired walled_owner_not_verified / elevation framing in walled-owner-verification-path.ts (semantic unchanged — an unverified declared address resolves non-admin, now at the derivation site) and the retired shouldReplayBootstrapFor create-arm coupling in walled-owner-operator-stamp.ts. Part of #11973 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZC5rNQj3WEet5HAmmAkMs
…-plugin-auth-repoint
… L3 diff Mechanical: node scripts/check-system-context-census.mjs --fix rewrote two line anchors (auth-plugin.ts:1288 -> 1296, last-admin-guard.ts:273 -> 286) that my comment-only insertions above them had rotted. Gate green after. Part of #11973 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZC5rNQj3WEet5HAmmAkMs
📓 Docs Drift CheckThis PR changes 1 package(s): 12 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 7 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 11 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 74a5cbe322c7f5cb783b7bd37d19fd23d119f146 && git checkout 74a5cbe322c7f5cb783b7bd37d19fd23d119f146
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 713f83f5d88e20d3024722c1aa71dc1e369886a9 df17ff0a721914cc253e1e3e8faae2c37cc505f7 && git checkout -B drift-repro 713f83f5d88e20d3024722c1aa71dc1e369886a9 && git merge --no-ff df17ff0a721914cc253e1e3e8faae2c37cc505f7
node scripts/docs-audit/affected-docs.mjs --json 713f83f5d88e20d3024722c1aa71dc1e369886a9
|
os-steve
commented
Aug 31, 2026
PM read ( 1. The red is not this PR's — stood down, card filed
Both are timeouts, not assertion failures, in I did not spend the permitted re-run, because a re-run would have been the weaker evidence. The root cause is readable in the tree: those two tests each drive 3–4 full Filed as #13688 ( 2. Independent verification of the card's hardest fence#11973 says, in terms: "⛔ Do not delete refusals as a side effect of the re-pointing commit — separate, reviewed change." I re-derived this from the commits rather than reading it off the PR body.
The acceptance criterion "every deleted or added guard refusal is enumerated in the PR body with its re-pricing rationale" is satisfied, and satisfied in the strongest form available: the enumeration is of an empty set in code, with the price table pinning what the set arithmetic re-priced. 3. Where this PR stopsPer the seat's protocol the dispatching seat's job ends at recording the finding and parking the carrier: ⛔ 禁止自查放行. This PR carries #13688 is not a blocker for that chain — it is another lane's flake-shaped defect and the re-run of this job on the next push will most likely pass. Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Part of #11973 — leg L3 of the #11663 platform-admin re-anchor (design comment 5394453215, section 2 step 5 / H4 and migration steps 4–5; maintainer acceptance comment 5404675670, 2026-08-25, bundle 1A/2B/3A/4A/5A/6A/7A). #11973 stays open on purpose: two halves of its acceptance are outside this repo's reach and are listed under "What this PR does not close". #11973 remains open.
Parks as a draft carrying
needs:contract-review(authorization-adjacent guard semantics, clause-② yes) — the same chain L4's PR #13514 cleared.The two reviewed steps, as distinct commits (the card requires this in terms)
Commit be6e371 — the re-point (design H4).
ensureDefaultOrganization's population question ("which user is the platform admin?") now reads the config anchor first: the firstOS_PLATFORM_OWNER_EMAILentry, in operator order, that a storedsys_userrow holds verified. The membership + verified predicate ismatchesConfiguredPlatformAdminimported from@objectstack/core— the derivation site's own (resolve-authz-context.ts6b-config) — so nothing of the parse, normalization, or fail-closed verified read is re-implemented (no second derivation site; the lookup shape mirrors plugin-security'sresolvePlatformAdminStanding, so the bound account is the account the audit surface reports as holding standing). The legacy grant anchor (oldest unscopedadmin_full_accessgrant) stays as the fallback — it is what anchorssingleposture under Choice 4A and P5's honoured window, and it is removed with the legacy-grant removal leg (design section 5 step 6), not here. The re-run trigger is the new exported predicateisDefaultOrganizationBootstrapTrigger:sys_userinsert/create,sys_userupdate touchingemail/email_verified(the #11343 trigger set — recovered verbatim from the pre-L4shouldReplayBootstrapForupdate arm), plus the legacysys_user_permission_setinsert arm unchanged. This commit adds or deletes NO guard refusal.Commit de9db65 — the re-pricing (migration step 5), arguable on its own. See the enumeration below. No refusal was added or deleted in code; the commit turns "the set arithmetic already re-prices them" from an assumption into a pinned, reviewed verdict, and trues up the two prose surfaces the L4 landing flagged to this card.
Commit df17ff0 — mechanical.
node scripts/check-system-context-census.mjs --fixre-anchored two line citations incontent/docs/permissions/system-context.mdx(auth-plugin.ts 1288 to 1296; last-admin-guard.ts 273 to 286) that comment-only insertions in this diff had rotted. That file is outside the claimed surface but was explicitly released by L4's merge; the edit is the gate's own--fixoutput, nothing hand-written; gate green after. Declared here per the bounded in-place-fix rule.Guard refusals touched — the acceptance-criterion enumeration
Zero refusals deleted, zero added, in code. Every refusal in
last-admin-guard.tsis the output of the oneresolveAdminUserIdsenumeration, which has counted config-anchored administrators since L2 — so pricing the population re-priced every refusal mechanically. This PR makes each price a measured, pinned verdict (last-admin-guard.re-pricing.test.ts, real ObjectQL over sqlite:memory:, same method as the config-anchor suite):admin_full_accesssys_permission_setrowsys_user+ env; the row's removal cannot empty a population containing a config adminsys_user_permission_setrowsingleshape; also P5's legacy window)singleunchangedsys_userban / delete of the last unbanned adminsys_memberdowngrade / deletesys_useremail/email_verifiedwrites (the "newly necessary" class of migration step 5)last-admin-guard.config-anchor.test.ts(pre-existing)refuseIfEmptiedRatherThanFresh: dangling grants / deactivated set)Prose (not refusals, enumerated for completeness): the two warning surfaces L4 flagged to this card were trued up —
walled-owner-verification-path.tsno longer names the retiredwalled_owner_not_verifiedelevation refusal (the semantic survives: an unverified declared address resolves non-admin at the derivation site, and the warning now says so in the derivation's vocabulary), andwalled-owner-operator-stamp.tsno longer claims the retiredshouldReplayBootstrapForcreate-arm coupling (it now points atisDefaultOrganizationBootstrapTrigger). Message pins moved with the wording and assert the retired token is gone.The interim window L4 opened (this card's job to close)
plugin-auth's own wiring (single/no-wall postures) fires on the new trigger set, and the function's population works from config with no grant row anywhere (pinned: "finds a declared, VERIFIED administrator with NO grant row anywhere").ensureDefaultOrganizationwith its own wiring on grant inserts. This PR changes the function's signature not at all, so their calls keep working; with the re-pointed population, theirkernel:readypass now resolves a config-anchored admin — which shrinks the fresh-walled-rig window from "forever" to "until the next process start". Fully closing it needs their wiring to adopt the exportedisDefaultOrganizationBootstrapTrigger(one import; the predicate exists precisely so their trigger cannot drift from ours). That is a coordination item for the enterprise package, named here rather than assumed.Choice 4A pins (PM mechanism assumption 2)
sys_userrow is NOT a population candidate; the grant anchor decides, and the config half costs zero reads (asserted on the fake engine's call log). This goes red if the re-point leaks into thesinglebranch.Verification (all at head df17ff0, the final commit; clean tree)
pnpm --filter @objectstack/plugin-auth test— vitest prints "Test Files 87 passed (87) · Tests 1783 passed (1783)".pnpm --filter @objectstack/plugin-auth typecheck(both tsc legs, examples included) — exit 0 after building the package. NOT MEASURED by it: the package tsconfig excludes**/*.test.ts, so test files are exercised by vitest only (package status quo — no test-typecheck ledger entry exists for plugin-auth).node scripts/pm/dispatch-gates.mjs(both output sections read whole — the path-derived block and the whole-tree kind-gates section). All 37 harvested families run at df17ff0: 36 exit 0 — including the ratchetscheck:type-check-debt("29 ledger entries re-measured, none above its recorded number"),check:type-check-coverage,check:engine-double-contract,check:query-options-erasure,check:where-matcher("none new"),check:test-source-alias, andcheck:nul-bytes("no raw ASCII control bytes"). The exception:scripts/check-test-completeness.mjsexits 3 and prints "the local reading for this gate is NOT MEASURED... It is not a red" — its local branch needs a saved turbo test log; recorded as NOT MEASURED per its own text.check:system-context-censuswent red once mid-run on the line rot this diff caused, was repaired by its own--fix(commit df17ff0), and prints "OK — 109 elevation read sites in 20 packages across 45 files, all anchored" after.eslint.config.mjsbehindpnpm lint; the 11 changed paths were passed explicitly and--format jsoncounts 9 TS files judged, 0 errors, 0 warnings (the 2 reported messages are "File ignored" notices for the changeset .md and the .mdx, which sit outside the config's population); invariance for untouched files: the config "never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file" (its own measured comment, eslint.config.mjs lines 327–329), so this diff cannot move any untouched file's verdict.packages/specmoved on main's side of the pre-PR merge, sopnpm --filter @objectstack/spec check:generatedwas run per AGENTS.md section 10: "All 14 generated artifacts are up to date."What this PR does not close (why
Part of, not a closing keyword)Generated by Claude Code