Repository navigation
fix(plugin-auth)!: the last-admin guard and the default-org bootstrap count only the Default Organization's admin_full_access grant - #25141
Conversation
… read only the Default Organization's admin_full_access grant An organization-less grant anchors nothing since 18.x step 7a-2 (ADR-0131 D1/D5); the guard counted one, so it failed open where such a row exists. Claude-Session: https://claude.ai/code/session_01GvYt2WyJja4rEjcy6pdD4p Co-authored-by: Claude <noreply@anthropic.com>
… the Default Organization Claude-Session: https://claude.ai/code/session_01GvYt2WyJja4rEjcy6pdD4p Co-authored-by: Claude <noreply@anthropic.com>
…the Default Organization Claude-Session: https://claude.ai/code/session_01GvYt2WyJja4rEjcy6pdD4p Co-authored-by: Claude <noreply@anthropic.com>
…he lean-embedding fail-open shut Claude-Session: https://claude.ai/code/session_01GvYt2WyJja4rEjcy6pdD4p Co-authored-by: Claude <noreply@anthropic.com>
…changeset Claude-Session: https://claude.ai/code/session_01GvYt2WyJja4rEjcy6pdD4p Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 10 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 18 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 7d3af2df0ca548a596f44aedf49856958be6468d && git checkout 7d3af2df0ca548a596f44aedf49856958be6468d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 60b42cd0b79d99252a003b5374b604e36157de79 bc359a7c731f390b322a3958ffd0d2a259308dec && git checkout -B drift-repro 60b42cd0b79d99252a003b5374b604e36157de79 && git merge --no-ff bc359a7c731f390b322a3958ffd0d2a259308dec
node scripts/docs-audit/affected-docs.mjs --json 60b42cd0b79d99252a003b5374b604e36157de79
|
…olumn, not driver-memory The driver-memory census refuses a new consumer without a maintainer ruling. Claude-Session: https://claude.ai/code/session_01GvYt2WyJja4rEjcy6pdD4p Co-authored-by: Claude <noreply@anthropic.com>
…anization's; an organization-less grant anchors nothing Claude-Session: https://claude.ai/code/session_01GvYt2WyJja4rEjcy6pdD4p Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Read-only. ① Derived judgments1. Parity with the resolver — yes, in every composition. The resolver's predicate (
2. The failure direction — the fail-open is closed; no legitimate fail-closed is opened.
3. The bootstrap — the right admin is still found and bound.
4. The tests — none weakened; the parity suite is real.
6. Docs. 7. #25016. ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
|
Seat ACCEPT · epic PM Seat check
|
Fixes #24883
Clause-②: yes (narrowing)
Metadata-protocol: none
Seat decision 6324268472 (option A). The measurement behind it is 6324239568.
Why
Since 18.x step 7a-2,
resolveAuthzContextgives anadmin_full_accessgrant platform standing only undersingle, and only when the Default Organization owns it (grantAnchorsPlatformStanding). An organization-less grant anchors nothing in any posture (ADR-0131 D1/D5).Two plugin-auth readers still counted such a grant:
anchorOwnerTest;Two compositions can still hold that row:
registerLastAdminGuardwith nosys_organization, over a tenant column that accepts NULL (driver-memory enforces no NOT NULL; a pre-7b-0 SQL table is nullable);os serve, on a database built before 18.x.In both, the guard believed an administrator remained where the resolver saw none. It permitted banning the deployment's only platform administrator, the verified
OS_PLATFORM_OWNER_EMAILaccount. This PR closes that fail-open.What changed
last-admin-guard.tsanchorOwnerTestreads the resolver's rule: a grant anchors only when the Default Organization owns it. Under a wall, or with no nameable Default Organization, none anchors.ensure-default-organization.ts: the bootstrap reads only the Default Organization's grant, and no grant when none is nameable. The organization-less read is gone.sys_organizationwith adefaultrow, and the platform-admin grant is owned by it;relaxEveryRegisteredTenantColumndropped), except where a case deliberately models a pre-18 row;sqliteEnginePoolgainskeepBootOrganizations, which keeps the boot's organization rows and refuses a case that changed them.singleeither: the new direction;single.last-admin-guard.anchor-parity.test.ts(13 cases) runs a real engine with the real resolver:singleandisolated. Each case asserts what the engine does with an organization-less grant, then that the guard counts its holder exactly when the resolver does;check:driver-memory-censusrefuses a new driver-memory consumer without a maintainer ruling, and the guard takes the same path on both drivers. No dependency is added.content/docs/permissions/authorization.mdx: the platform-admin anchor wording, and the embedder's remedy.patchchangeset with the BREAKING banner, the FROM → TO, and the embedder's remedy (D5's config anchor plus membership).Verification (local, before CI)
@objectstack/plugin-auth: 3448 tests, 3438 passed, 0 failed, 10 skipped.pnpm typecheckexit 0, after a full DTS build of the plugin-auth closure.@objectstack/organizations: 233/233.anchorOwnerTestturns 6 cases red: the matrix's no-organization-object row, both negative pins, and three config-anchor pins;check-governed-merges --branch: NOT governed, 0 of 15 paths hit the register (14 at980cdc13c, pluspermission-sets.mdxinbc359a7c).Acceptance notes
judgeOrganizationDelete(fix(objectql, plugin-auth)!: organization delete refuses while records remain, removes its own auth structure as one unit, and never orphans a tenant column #25016) asks the same count, so it inherits the fix. Its pins are re-run with the dogfood battery.package.jsonor lockfile change.🤖 Generated with Claude Code
https://claude.ai/code/session_01GvYt2WyJja4rEjcy6pdD4p
Generated by Claude Code