') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); 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 by os-steve · Pull Request #13685 · objectstack-ai/objectstack · GitHub
Skip to content

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

Merged
huangyiirene merged 4 commits into
mainfrom
claude/issue-11973-l3-plugin-auth-repoint
Aug 31, 2026
Merged

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
huangyiirene merged 4 commits into
mainfrom
claude/issue-11973-l3-plugin-auth-repoint

Conversation

@os-steve

Copy link
Copy Markdown
Collaborator

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 first OS_PLATFORM_OWNER_EMAIL entry, in operator order, that a stored sys_user row holds verified. The membership + verified predicate is matchesConfiguredPlatformAdmin imported from @objectstack/core — the derivation site's own (resolve-authz-context.ts 6b-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's resolvePlatformAdminStanding, so the bound account is the account the audit surface reports as holding standing). The legacy grant anchor (oldest unscoped admin_full_access grant) stays as the fallback — it is what anchors single posture 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 predicate isDefaultOrganizationBootstrapTrigger: sys_user insert/create, sys_user update touching email/email_verified (the #11343 trigger set — recovered verbatim from the pre-L4 shouldReplayBootstrapFor update arm), plus the legacy sys_user_permission_set insert 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 --fix re-anchored two line citations in content/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 --fix output, 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.ts is the output of the one resolveAdminUserIds enumeration, 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):

Refusal familyNew priceRationalePin
shape (4): DELETE admin_full_accesssys_permission_set rowobsolete where a config-anchored admin stands — PERMITTED (was: refused whenever it emptied the population)config standing reads only sys_user + env; the row's removal cannot empty a population containing a config admin"DELETING the admin_full_access row is permitted, and lands"
shape (4): RENAME the rowsamesame"RENAMING it is permitted, and lands"
shape (4): DEACTIVATE the row (ADR-0049 spelling)samesame"DEACTIVATING it is permitted, and lands"
grant-delete of the last legacy sys_user_permission_set rowsamethe legacy anchor is no longer load-bearing where config standing exists — this is the migration's intended end state"deleting the LAST legacy grant row is permitted, and lands"
ALL of the above, grant-anchored environment (no declared admins — the Choice 4A single shape; also P5's legacy window)KEPT — still REFUSEDthe row/grant is still the only anchor there; 4A rules single unchangedfour "still refused" counter-pins, plus "a DECLARED-but-unverified account does not re-price anything"
sys_user ban / delete of the last unbanned adminKEPT, price unchangedconfig admins are IN the population, so banning/deleting the last of them still refuses; a grant admin's ban is permitted when a config admin remains (set arithmetic)existing suites + config-anchor suite
sys_member downgrade / deleteKEPT, price unchangedself-relaxes exactly when another anchor survivesexisting suites
shape (5): sys_useremail/email_verified writes (the "newly necessary" class of migration step 5)landed in L2, VERIFIED here as present and pinnedthe write class that revokes config standing with no grant table touchedlast-admin-guard.config-anchor.test.ts (pre-existing)
zero-population tri-state (refuseIfEmptiedRatherThanFresh: dangling grants / deactivated set)KEPT unchangedreachable only when NO anchor stands; the config anchor makes it rarer, never wronger — and recovery (register + verify a declared address) runs through writes this guard does not judgeunchanged existing pins

Prose (not refusals, enumerated for completeness): the two warning surfaces L4 flagged to this card were trued up — walled-owner-verification-path.ts no longer names the retired walled_owner_not_verified elevation 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), and walled-owner-operator-stamp.ts no longer claims the retired shouldReplayBootstrapFor create-arm coupling (it now points at isDefaultOrganizationBootstrapTrigger). Message pins moved with the wording and assert the retired token is gone.

The interim window L4 opened (this card's job to close)

  • In-repo: closed.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").
  • Out-of-repo: shrunk, plus a coordination item — not guessed at. The enterprise organizations package reuses ensureDefaultOrganization with 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, their kernel:ready pass 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 exported isDefaultOrganizationBootstrapTrigger (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)

  • Config UNSET: a verified sys_user row 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 the single branch.
  • The four "still refused" guard counter-pins above are the re-pricing side of the same leak detector.

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).
  • Gates re-derived from this worktree's actual diff at the merged head: 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 ratchets check: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, and check:nul-bytes ("no raw ASCII control bytes"). The exception: scripts/check-test-completeness.mjs exits 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-census went 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, narrowed with evidence instead of the repo-wide CI-owned sweep: population is the one eslint.config.mjs behind pnpm lint; the 11 changed paths were passed explicitly and --format json counts 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/spec moved on main's side of the pre-PR merge, so pnpm --filter @objectstack/spec check:generated was 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)

  1. The walled-rig end-to-end acceptance. The criterion "walled bootstrap end-to-end per the design section 2 flow passes on a walled rig" needs a live walled rig with a real registration + verification round trip; this repo's unit/integration harness cannot stand that up. Verified here: every step of the section 2 flow that is unit-pinnable (population, trigger set, guard prices, single-posture invariance). Needs an operator (or the L6 census rig) for the end-to-end pass — the green suites here are NOT claimed as that pass.
  2. The out-of-repo trigger adoption (coordination item above).

Generated by Claude Code

…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
… 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
@github-actionsgithub-actionsBot added size/l documentation Improvements or additions to documentation tests tooling labels Aug 31, 2026
@os-steveos-steve added needs:contract-review and removed documentation Improvements or additions to documentation tests tooling labels Aug 31, 2026 — with Claude
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/plugin-auth, touching 13 documentable anchor(s). ⚠️1 changed file(s) yielded no anchor (packages/plugins/plugin-auth/src/walled-owner-operator-stamp.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

12 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/error-catalog.mdx(via sys_permission_set (literal))
  • content/docs/automation/approvals.mdx(via admin_full_access (literal))
  • content/docs/data-modeling/field-type-decision-tree.mdx(via email_verified (literal))
  • content/docs/data-modeling/objects.mdx(via sys_user_permission_set (literal))
  • content/docs/deployment/environment-variables.mdx(via sys_permission_set (literal), sys_user_permission_set (literal))
  • content/docs/permissions/authentication.mdx(via email_verified (literal))
  • content/docs/permissions/authorization.mdx(via admin_full_access (literal), sys_permission_set (literal), sys_user_permission_set (literal))
  • content/docs/permissions/delegated-administration.mdx(via sys_permission_set (literal), sys_user_permission_set (literal))
  • content/docs/permissions/permission-sets.mdx(via admin_full_access (literal), sys_permission_set (literal), sys_user_permission_set (literal))
  • content/docs/permissions/permissions-matrix.mdx(via admin_full_access (literal))
  • content/docs/permissions/sharing-rules.mdx(via admin_full_access (literal))
  • content/docs/ui/audience-based-interfaces.mdx(via admin_full_access (literal))

7 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/implementation-status.mdx(via sys_user_permission_set (literal))
  • content/docs/releases/v12.mdx(via sys_permission_set (literal))
  • content/docs/releases/v13.mdx(via sys_permission_set (literal), sys_user_permission_set (literal))
  • content/docs/releases/v14.mdx(via admin_full_access (literal), sys_user_permission_set (literal))
  • content/docs/releases/v15.mdx(via sys_permission_set (literal))
  • content/docs/releases/v16.mdx(via admin_full_access (literal), sys_user_permission_set (literal))
  • content/docs/releases/v17.mdx(via admin_full_access (literal), sys_permission_set (literal), sys_user_permission_set (literal))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/plugins/plugin-auth/src/walled-owner-operator-stamp.ts) — pages documenting those are invisible to this run
  • 1 anchor(s) matched too much of the corpus to be a work list: sys_user (literal, 29 pages)
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 11 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 713f83f5d88e20d3024722c1aa71dc1e369886a9packageMentionDocs.

Which tree this was computed on

This run read content/docs from 74a5cbe322c7f5cb783b7bd37d19fd23d119f146 — the merge of head df17ff0a721914cc253e1e3e8faae2c37cc505f7 into base 713f83f5d88e20d3024722c1aa71dc1e369886a9, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 713f83f5d88e20d3024722c1aa71dc1e369886a9 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-steveClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM read (domain:services seat, #6021) — delivery ACCEPTED; one red stood down; parked for the contract-review chain.

1. The red is not this PR's — stood down, card filed

Temporal Conformance (live PG + MySQL) failed. It is not this PR's:

Test Files 1 failed | 155 passed (156)
FAIL packages/drivers/driver-sql/src/sql-driver-13056-orphan-shadow-column-cleanup.test.ts
> drops the orphaned generated column, and keeps the one still carrying a constraint
> converges — a second detect finds neither the index nor an orphan column
Error: Test timed out in 5000ms.

Both are timeouts, not assertion failures, in @objectstack/driver-sql — a package this diff does not touch (the fence, measured below, is plugin-auth/** + one changeset + one docs page).

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 new SqlDriver(...)initObjects(...)disconnect() cycles against the live MySQL container, declare no per-test timeout, and packages/drivers/driver-sql/vitest.config.ts sets no global testTimeout — so vitest's default 5000ms applies. Every other multi-round-trip live test in that same package carries an explicit budget (sql-driver-12380-json-roundtrip.test.ts 60s/60s/120s, sql-driver-connect-bound.test.ts 40s, sql-driver-diagnostic-value-probe.test.ts 60s, sql-driver-value-roundtrip-conformance.test.ts 60s). This file shipped with #13056's fix without one, so it reddens whichever PR is unlucky with runner load.

Filed as #13688 (domain:engine, bug, pm:queue) with the proposed patch. ⛔ Not ported into this PR: it is another lane's surface and porting it here would widen a needs:contract-review diff for an unrelated package. ⛔ Not skipped, not quarantined, no global timeout — the card says so in terms.

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.

load-bearing pointmeasured
the two steps are genuinely two commitsbe6e37144 (re-point) and de9db6593 (re-pricing) are distinct commits, in that order
the re-pointing commit touched no guard filebe6e37144 touches no *guard* path at all
zero refusals added or deleted in codestronger than claimed: every one of the 41 changed lines in last-admin-guard.ts is a comment or blank — filtering the diff to non-comment lines returns empty. throw-statement census across all five changed source files: auth-plugin.ts 4→4, ensure-default-organization.ts 0→0, last-admin-guard.ts 6→6, walled-owner-operator-stamp.ts 0→0, walled-owner-verification-path.ts 0→0. No statement-level throw was added or removed anywhere in the diff
the retired walled_owner_not_verified framing removed no live refusal✅ on the merge base, all four occurrences were inside notice prose strings in walled-owner-verification-path.ts — the same four this PR rewrote. It was already dead as an emitted code; the only surviving occurrence in packages/ is a comment in core/src/security/platform-admin.ts:289
both directions are pinned, not just the relaxationlast-admin-guard.re-pricing.test.ts carries 4 "permitted, and lands" cases and 5 "still refused" counter-pins, including the over-relaxation control "a DECLARED-but-unverified account does not re-price anything"
the config-anchor oracle really is core's ownresolvePlatformAdminEmails and matchesConfiguredPlatformAdmin are both in the export { … } from './platform-admin.js' block at packages/core/src/security/index.ts:196–209; plugin-auth already imports core security symbols through the root barrel (auth-manager.ts:29), so the import path is the established one
fence✅ nothing outside packages/plugins/plugin-auth/**, .changeset/, content/docs/permissions/system-context.mdx

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 stops

Per the seat's protocol the dispatching seat's job ends at recording the finding and parking the carrier: ⛔ 禁止自查放行. This PR carries needs:contract-review (clause ② content limb: isDefaultOrganizationBootstrapTrigger is newly published on @objectstack/plugin-auth's barrel via export * from './ensure-default-organization.js', and the guard's accept/reject prices move), and it stays a draft for the director seat (/pm-dispatch director) to run the contract-review chain. I am not clearing it myself.

#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

@huangyiirene
huangyiirene marked this pull request as ready for review August 31, 2026 07:42
@huangyiirene
huangyiirene added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit 4f65837Aug 31, 2026
66 of 67 checks passed
@huangyiirene
huangyiirene deleted the claude/issue-11973-l3-plugin-auth-repoint branch August 31, 2026 08:12
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@os-steve@huangyiirene@claude