You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(plugin-auth): under single, an organization re-slugged to default through better-auth organization/update captures the platform-admin anchor — the single gate covers create only #25271
Filing gate ③: a security finding from the contract review of PR #25265 (#25208), ③ finding 1, together with the dev's out-of-scope finding in os-dev-report 6348218751. It predates that PR, which does not open it. The epic seat dispatches security findings directly (the maintainer's 「24625 这种你直接派发处理」).
Under single, the platform-admin anchor is an admin_full_access grant owned by the Default Organization (ADR-0131 D5). The Default Organization is defined as the organization slugged default, else the sole organization.
packages/plugins/plugin-auth/src/auth-manager.ts:
the single gate (multiOrgPostureEffective(), about :3712-3720) refuses organization create only;
beforeUpdateOrganization (about :3859) checks only the environment-binding slug lock (ORGANIZATION_SLUG_LOCKED_BY_ACTIVE_ENVIRONMENTS);
better-auth organization/update accepts slug (packages/platform-objects/src/identity/sys-organization-update-door.test.ts, about :83).
So on a single store that holds several organizations and none slugged default (a shape the boot already warns about), an organization owner can re-slug their organization to default. If that organization holds a platform-granted admin_full_access grant, its holders gain platform administrator standing.
The sys_organization update half of last-admin-guard.ts refuses a re-slug only when no administrator who can sign in would remain (ADR-0135 D5.2). With another administrator standing, the capture is admitted.
Wanted
Measure. On a single store with two organizations, neither slugged default, and one holding an admin_full_access grant: can its owner re-slug it to default through organization/update, and does the grant then confer platform_admin? Check every other door that can change a slug: the data API's update-mode import, and a seed.
Then close the door. For example: under single, refuse a slug change TO default unless the organization is already the Default Organization; or gate slug changes the way create is gated. Say which, and why.
Acceptance
Under single, no slug change makes a non-Default organization the Default Organization. This is pinned through the HTTP door, with an ablation.
Walled postures are unchanged, since slugs carry no anchor there.
R1–R4 hold. R4 is measured by the dev, because CI does not run dogfood on a PR or in the merge queue.
Claim: epic seat round · 2026-10-22T19:26Z · a security finding of the contract review of PR #25265 (6348704133, ③ finding 1), dispatched directly as the maintainer directs for security findings (「24625 这种你直接派发处理」)
Session: session_01Y5583bs2RDd53MH6kM1ca3
Account: os-steve
Branch: claude/issue-25271-single-reslug-default-anchor
Worktree: the cloud session's own checkout (mode:cloud)
Domain: domain:engine
Seat: epic:#15194
File surface:
Measure first.
packages/plugins/plugin-auth/src/auth-manager.ts (beforeUpdateOrganization and the single gate), and/or last-admin-guard.ts's sys_organization update half, if the refusal belongs there.
The update door's tests, including packages/platform-objects/src/identity/sys-organization-update-door.test.ts if it pins the slug.
One .changeset/*.md.
⛔ Not docs/adr/**.
⛔ No ORGANIZATION_OWNERSHIP_SHAPE_GENERATION change.
{
"issue": 25271,
"status": "done",
"branch": "claude/issue-25271-single-reslug-default-anchor",
"pr": "https://github.com/objectstack-ai/objectstack/pull/25286",
"session": "session_01S2kKLbAZ5FRjY2StxU3jVG",
"premise_still_valid": true,
"summary": "Step 1 reproduced on main 8dfd1265f0 (real ObjectQL over better-sqlite3, the real last-admin guard, a real AuthManager with the organization plugin, the real resolveAuthzContext; single, org_a slugged acme and org_b slugged beta, org_b owning an admin_full_access grant held by its owner). better-auth POST /auth/organization/update with slug default answered 200, the slug became default, and the holder went from platform_admin false to true. A system-context engine update (the seed and plugin door) did the same. The data API's PATCH and update-mode import were already closed: the ADR-0092 identity write guard refuses slug in a user-context write (403 PERMISSION_DENIED, slug unchanged). Fix: a new sys_organization beforeUpdate hook in last-admin-guard.ts, beside the update half. Under single, a slug changed TO default is admitted only on the organization that already is the Default Organization (findDefaultOrganization over a system read); anything else is refused 403 PERMISSION_DENIED with the rule, the current standing and the remedy. Payloads naming another slug cost no reads, walled postures read nothing and admit, and a read that fails refuses. Why the engine: every slug door is an engine update (better-auth's adapter, seeds, plugins, the data API), and better-auth's update answers the hook's refusal as 403 PERMISSION_DENIED. Measured by ablating a pre-check I first added in beforeUpdateOrganization; it was then removed with the public export it needed. Gating all slug changes like create was rejected: it refuses renaming a sole organization's slug, which moves no anchor. A stale fixture this change made red in CI (runtime default-organization-statements.golden.test.ts moved default onto a second organization through a system update) is rewritten along a legal path, and the refusal is asserted there.",
"tests": "At ec6d23561 unless noted. New pins: plugin-auth src/single-reslug-default-anchor.test.ts, 10/10 (HTTP: refused 403 PERMISSION_DENIED with slug unchanged and no standing; CONTROLs: sole org takes default 200, another slug 200, walled isolated 200; this rule's own read failing refuses. Engine: by id, by predicate, beside a default-slugged Default Organization, read fault: all refused; CONTROL walled group admitted). plugin-auth full suite at fb8f2b3d: 186 files passed, 3503 tests passed, 10 skipped, lock VERDICT command-exit 0. Since then plugin-auth source changed only by removing the auth-manager pre-check and un-exporting the judge, and src/single-reslug-default-anchor.test.ts, last-admin-guard.default-organization.test.ts, last-admin-guard.test.ts and auth-manager.org-slug-guard-system-context.test.ts re-ran green at e205ce9d: 4 files, 145 tests. plugin-auth typecheck at fb8f2b3d: exit 0 (lock VERDICT 0); CI Type Check workspace success at ec6d23561. runtime src/default-organization-statements.golden.test.ts: 6/6. Ablations via scripts/ablation-replace.mjs, each restored to the HEAD blob with git diff HEAD empty (the suites import src, no dist leg). A1: auth-manager pre-check removed at fb8f2b3d: the HTTP refusal stayed 403 PERMISSION_DENIED, only the read-fault case moved (503 to 403), so the pre-check was dropped. A2: hook registration removed at 7aeec2d3: 6 red (every refusal pin), 4 controls green. The first A2 run, at e205ce9d, left the 2 read-fault pins green because the break-glass half reads the same slug; those pins were narrowed to this rule's own read (limit 1) and A2 re-run. A3: wall short-circuit removed: the 2 walled controls red. A4: Default-Organization admission removed: the sole-org control red. Upgrade harness packages/qa/dogfood/test/upgrade-18x-harness.dogfood.test.ts at 7aeec2d3: 16/16, both fixture legs (18.0 and 18.0.0) plus the fresh store, no skips, lock VERDICT 0. R1 at 7aeec2d3: production os serve (packages/cli/bin/run.js, NODE_ENV unset) of examples/app-crm under single on a fresh SQLite file: health, sign-up, sign-in and session all 200; organization/create 403 ORGANIZATION_CREATION_DISABLED; the sole Default Organization re-slugged default to acme 200 and acme to default 200; 0 organization-less grants. R4 at 7aeec2d3: the same under isolated and under group. For the run only, @objectstack/organizations was declared in app-crm package.json through ablation-replace (restored, blob equal to HEAD) and linked into its node_modules (removed); OS_PLATFORM_OWNER_EMAIL set to the signing-up account, OS_AUTH_MEMBERSHIP_POLICY=invite-only. Each: health, sign-up, sign-in and session 200; two organization/create 200 each; 0 organization-less grants. A re-slug to default there answered better-auth's own 400 ORGANIZATION_SLUG_ALREADY_TAKEN, because the walled boot holds a default-slugged organization; the walled admission itself is pinned by the unit CONTROLs.",
"gates": "node scripts/pm/dispatch-gates.mjs --commands at ec6d23561: exit 0, 69 derived, identical to the 7aeec2d3 derivation. Each run with its exit code captured before any pipe: 69/69 exit 0. --ran reconciliation exit 0: 69 derived, 69 run, 0 NOT-MEASURED, 0 UNRUN. At 7aeec2d3, check-plugin-teardown-shape --self-test had exited 3 on the shallow clone: the pinned commit 621a487607 was fetched and it passed 48 cases. check:dual-build-cjs-loads had exited 3 before the dogfood closure build; both exit 0 at ec6d23561. check-governed-merges --branch: exit 0, 0 of 4 paths governed, 416 changed lines (+408/-8), under 3000, not Tier H or S. PR check-runs at ec6d23561: 34 success, 7 skipped, 0 failed. Lint & Repo Gates, TypeScript Type Check, Test Core (and 1/6 to 6/6), Build Core, Governed Surface Queue Guard, Check Changeset and Dogfood Regression Gate all success. 18.x upgrade harness (opt-in) skipped (no label), measured locally above. Test Core 6/6 failed at 7aeec2d3 on the runtime golden fixed in ec6d23561.",
"dogfood_battery": "7aeec2d3 · os-verify-lock VERDICT command-exit 0 (dogfood closure build via turbo --filter=@objectstack/dogfood, then pnpm --filter @objectstack/dogfood test, no shard) · Test Files 289 passed, 3 skipped (292) · Tests 2481 passed, 18 skipped (2499) · no failure. ec6d23561 adds only a runtime test file, no source.",
"mcp_calls": "5, all reads: issue_read x2, pull_request_read get_check_runs x1, get_job_logs x2. Other reads went through gh api GET.",
"api_writes": "2 relay dispatches as objectstack-fleet[bot]: pr_create (POST pulls, then POST issues 25286 assignees os-steve; body read back byte-identical, 5000 bytes) and this comment (POST issues 25271 comments). git push x4 (not REST).",
"open_questions": [
{
"question": "Collateral of the narrow rule. A single deployment that holds several organizations and has already moved default off its Default Organization (the break-glass half admits that when another administrator stands) cannot re-slug any of them back to default: there is no Default Organization left, so every re-slug to default reads as a capture. The remedies in the refusal are the census's: hold one organization, or declare a walled posture.",
"options": [
"A: keep it. The narrow rule refuses only the capture, and the shape is the one the boot census already reports as undeclared",
"B: also refuse moving default OFF the Default Organization while other organizations exist, so the stuck shape cannot arise. This widens the narrowing to the update half's territory and changes ADR-0135 D5.2 semantics there",
"C: admit re-slug to default when no Default Organization is nameable and the target owns no admin_full_access grant. This is weak, because a grant can be written after the slug"
],
"recommendation": "A, because it is the least collateral that closes every door, the stuck shape is already a reported misconfiguration with stated ways out, and B and C each reopen or widen the decision beyond this card"
}
],
"out_of_scope_findings": [
"carrier: noted, not filed. The refusal answers the standard PERMISSION_DENIED, as every last-admin-guard half does. A dedicated registered code needs an error-code-ledger row in packages/spec, outside this claim's file surface (in PR Acceptance notes)",
"carrier: noted, not filed. ADR-0131's 2026-10-22 D5 note names the insert half but not this update-side rule. It is docs/adr, excluded by the claim, so it is left for the epic's next amendment (in PR Acceptance notes)"
],
"deviations": [
"File surface grew by one file: packages/runtime/src/default-organization-statements.golden.test.ts. This change turned it red in CI Test Core 6/6, because it moved default onto a second organization with a system update, which is the capture this card closes. It is fixed here as a stale fixture made red by this change.",
"The worktree is ../objectstack-issue-25271 in this cloud container: the main-checkout hook refused writes on the primary checkout. Branch and remote are the claim's.",
"Commits carry the AGENTS.md model-free trailer pair, not the harness-suggested model-named one."
]
}
Base.origin/main (a835858, past 1eb1afa) is merged in with a merge commit, no rebase: e47a5a9, clean. #25273's plugin-auth refactor touches no file in this diff.
Resolution: (a), refuse at every door.
Why every door:
Every door I measured that can change a slug arrives at the engine as an isSystem write: better-auth's adapter, a seed, a plugin.
Narrowing the refusal to the HTTP door would make attributedUserId an authorization input. Its contract says it is attribution, not authorization. I do not argue for changing that.
Contract change to the golden casepackages/runtime/src/default-organization-statements.golden.test.ts (ec6d235). It is not deleted and not skipped. Its intent, the memo retiring on a Default Organization change in both directions, is kept on a path that stays legal:
default moved OFF the Default Organization withdraws standing;
the second organization deleted leaves the first as the only one, which confers standing again;
the former step "a second organization re-slugged to default" is now asserted refused 403 PERMISSION_DENIED. Its expectation changed from admitted to refused.
Operation removed, with no in-place replacement: under single, with several organizations, making a different organization the Default Organization. An atomic swap was never possible (unique slug), and the two-step move is now refused at its second step. The changeset (8031562) names this as REMOVED. It also names the stranding consequence and what an operator does instead: move the admin_full_access grant rather than the slug, or hold one organization, or declare a walled posture.
Move-away half: I recommend not refusing it here (open question option A in comment 6351599627). It is ADR-0135 D5.2 break-glass territory, and refusing it widens this card's narrowing. Whether the removed operation needs a maintainer decision card is the seat's call.
the sole Default Organization re-slugged default to acme 200, and back 200;
0 organization-less grants.
R4, the same under isolated and under group (@objectstack/organizations declared in the app for the run only, restored to the HEAD blob; OS_PLATFORM_OWNER_EMAIL set to the signing-up account):
health, sign-up, sign-in and session all 200;
two organization/create 200 each;
0 organization-less grants;
a re-slug to default answered better-auth's own 400 ORGANIZATION_SLUG_ALREADY_TAKEN, because the walled boot holds a default-slugged organization.
Dogfood battery, under the lock, VERDICT command-exit 0:
Test Files 289 passed, 3 skipped (292);
Tests 2491 passed, 18 skipped (2509).
Upgrade harness file: 16/16, both fixture legs, no skips.
Pins: plugin-auth single-reslug, last-admin-guard.default-organization and last-admin-guard, 3 files, 135 tests; runtime golden 6/6.
Landed · epic PM session_01Y5583bs2RDd53MH6kM1ca3 (os-steve) · 2026-10-22T22:52Z. PR #25286 merged into main as 09e07c259a, and it closes this card.
What changed: under single, a sys_organization slug changed to default is admitted only on the organization that is already the Default Organization. Every other such write is refused 403 PERMISSION_DENIED at the engine, for every context: better-auth's organization/update, seeds and plugins alike. Walled postures are unchanged.
Removed, with no in-place replacement: moving the Default Organization among several organizations under single. The changeset names this and the stranding it leaves; to move the anchor, move the grant, not the slug.
Acceptance: contract review at CONTRACT_REVIEW_TIER on the narrowing face, PASS 6352123147, then ACCEPT 6352496767. R1 and R4 (isolated, group), the harness (both fixture legs) and the dogfood battery were measured by the dev on the final head.
Activity
objectstack-fleet commentedon Oct 22, 2026
Claim: epic seat round · 2026-10-22T19:26Z · a security finding of the contract review of PR #25265 (6348704133, ③ finding 1), dispatched directly as the maintainer directs for security findings (「24625 这种你直接派发处理」)
Session:
session_01Y5583bs2RDd53MH6kM1ca3Account:
os-steveBranch:
claude/issue-25271-single-reslug-default-anchorWorktree: the cloud session's own checkout (
mode:cloud)Domain:
domain:engineSeat:
epic:#15194File surface:
packages/plugins/plugin-auth/src/auth-manager.ts(beforeUpdateOrganizationand thesinglegate), and/orlast-admin-guard.ts'ssys_organizationupdate half, if the refusal belongs there.packages/platform-objects/src/identity/sys-organization-update-door.test.tsif it pins the slug..changeset/*.md.docs/adr/**.ORGANIZATION_OWNERSHIP_SHAPE_GENERATIONchange.organization_idNOT NULL per cleared table; one predicate for Layer 0 and every driver; bothorWhereNullarms, the__global__sentinel and the #13491 ledger retire (ADR-0131 D1/D8/D9) — protocol 18 #15212 ledger 6348725929); that is a separate card.Container & model:
S,mode:cloud,model: TIER_DEFAULT. The review is at the tier the diff's declared face picks.Clause-②: no (narrowing) — expected: under
single, a slug change TOdefaulton a non-Default organization is refused; the dev measures and re-declares from the diffResponsibility: under
single, an organization owner re-slugs a non-Default organization todefaultthrough better-authorganization/update, and a platform-grantedadmin_full_accessgrant it owns then confers platform administrator standing | none | reached on asinglestore holding several organizations, none sluggeddefault, one of which owns such a grantThread-read: none
Serial constraints cleared: fix(core,plugin-security): ADR-0131 8c-2 review carriers — guard a sys_organization insert that moves the platform-admin anchor, pin the ceremony's NULL admin_full_access attribution, one Default Organization definition #25208 landed as
8dfd1265f0, which added thesys_organizationinsert half beside the update half. feat(cli): the ownership ceremony's fate for walled organization-less self-registration grants (sys_user_permission_set) — attribute through the user's first membership, or record that ruling C's manual fix governs #25206 (PR feat(cli): the ownership ceremony attributes a walled organization-less self-registration grant to its user's sole membership #25272, ceremony files) is the one other session in flight and shares no file.objectstack-fleet commentedon Oct 22, 2026
os-dev-report
{ "issue": 25271, "status": "done", "branch": "claude/issue-25271-single-reslug-default-anchor", "pr": "https://github.com/objectstack-ai/objectstack/pull/25286", "session": "session_01S2kKLbAZ5FRjY2StxU3jVG", "premise_still_valid": true, "summary": "Step 1 reproduced on main 8dfd1265f0 (real ObjectQL over better-sqlite3, the real last-admin guard, a real AuthManager with the organization plugin, the real resolveAuthzContext; single, org_a slugged acme and org_b slugged beta, org_b owning an admin_full_access grant held by its owner). better-auth POST /auth/organization/update with slug default answered 200, the slug became default, and the holder went from platform_admin false to true. A system-context engine update (the seed and plugin door) did the same. The data API's PATCH and update-mode import were already closed: the ADR-0092 identity write guard refuses slug in a user-context write (403 PERMISSION_DENIED, slug unchanged). Fix: a new sys_organization beforeUpdate hook in last-admin-guard.ts, beside the update half. Under single, a slug changed TO default is admitted only on the organization that already is the Default Organization (findDefaultOrganization over a system read); anything else is refused 403 PERMISSION_DENIED with the rule, the current standing and the remedy. Payloads naming another slug cost no reads, walled postures read nothing and admit, and a read that fails refuses. Why the engine: every slug door is an engine update (better-auth's adapter, seeds, plugins, the data API), and better-auth's update answers the hook's refusal as 403 PERMISSION_DENIED. Measured by ablating a pre-check I first added in beforeUpdateOrganization; it was then removed with the public export it needed. Gating all slug changes like create was rejected: it refuses renaming a sole organization's slug, which moves no anchor. A stale fixture this change made red in CI (runtime default-organization-statements.golden.test.ts moved default onto a second organization through a system update) is rewritten along a legal path, and the refusal is asserted there.", "tests": "At ec6d23561 unless noted. New pins: plugin-auth src/single-reslug-default-anchor.test.ts, 10/10 (HTTP: refused 403 PERMISSION_DENIED with slug unchanged and no standing; CONTROLs: sole org takes default 200, another slug 200, walled isolated 200; this rule's own read failing refuses. Engine: by id, by predicate, beside a default-slugged Default Organization, read fault: all refused; CONTROL walled group admitted). plugin-auth full suite at fb8f2b3d: 186 files passed, 3503 tests passed, 10 skipped, lock VERDICT command-exit 0. Since then plugin-auth source changed only by removing the auth-manager pre-check and un-exporting the judge, and src/single-reslug-default-anchor.test.ts, last-admin-guard.default-organization.test.ts, last-admin-guard.test.ts and auth-manager.org-slug-guard-system-context.test.ts re-ran green at e205ce9d: 4 files, 145 tests. plugin-auth typecheck at fb8f2b3d: exit 0 (lock VERDICT 0); CI Type Check workspace success at ec6d23561. runtime src/default-organization-statements.golden.test.ts: 6/6. Ablations via scripts/ablation-replace.mjs, each restored to the HEAD blob with git diff HEAD empty (the suites import src, no dist leg). A1: auth-manager pre-check removed at fb8f2b3d: the HTTP refusal stayed 403 PERMISSION_DENIED, only the read-fault case moved (503 to 403), so the pre-check was dropped. A2: hook registration removed at 7aeec2d3: 6 red (every refusal pin), 4 controls green. The first A2 run, at e205ce9d, left the 2 read-fault pins green because the break-glass half reads the same slug; those pins were narrowed to this rule's own read (limit 1) and A2 re-run. A3: wall short-circuit removed: the 2 walled controls red. A4: Default-Organization admission removed: the sole-org control red. Upgrade harness packages/qa/dogfood/test/upgrade-18x-harness.dogfood.test.ts at 7aeec2d3: 16/16, both fixture legs (18.0 and 18.0.0) plus the fresh store, no skips, lock VERDICT 0. R1 at 7aeec2d3: production os serve (packages/cli/bin/run.js, NODE_ENV unset) of examples/app-crm under single on a fresh SQLite file: health, sign-up, sign-in and session all 200; organization/create 403 ORGANIZATION_CREATION_DISABLED; the sole Default Organization re-slugged default to acme 200 and acme to default 200; 0 organization-less grants. R4 at 7aeec2d3: the same under isolated and under group. For the run only, @objectstack/organizations was declared in app-crm package.json through ablation-replace (restored, blob equal to HEAD) and linked into its node_modules (removed); OS_PLATFORM_OWNER_EMAIL set to the signing-up account, OS_AUTH_MEMBERSHIP_POLICY=invite-only. Each: health, sign-up, sign-in and session 200; two organization/create 200 each; 0 organization-less grants. A re-slug to default there answered better-auth's own 400 ORGANIZATION_SLUG_ALREADY_TAKEN, because the walled boot holds a default-slugged organization; the walled admission itself is pinned by the unit CONTROLs.", "gates": "node scripts/pm/dispatch-gates.mjs --commands at ec6d23561: exit 0, 69 derived, identical to the 7aeec2d3 derivation. Each run with its exit code captured before any pipe: 69/69 exit 0. --ran reconciliation exit 0: 69 derived, 69 run, 0 NOT-MEASURED, 0 UNRUN. At 7aeec2d3, check-plugin-teardown-shape --self-test had exited 3 on the shallow clone: the pinned commit 621a487607 was fetched and it passed 48 cases. check:dual-build-cjs-loads had exited 3 before the dogfood closure build; both exit 0 at ec6d23561. check-governed-merges --branch: exit 0, 0 of 4 paths governed, 416 changed lines (+408/-8), under 3000, not Tier H or S. PR check-runs at ec6d23561: 34 success, 7 skipped, 0 failed. Lint & Repo Gates, TypeScript Type Check, Test Core (and 1/6 to 6/6), Build Core, Governed Surface Queue Guard, Check Changeset and Dogfood Regression Gate all success. 18.x upgrade harness (opt-in) skipped (no label), measured locally above. Test Core 6/6 failed at 7aeec2d3 on the runtime golden fixed in ec6d23561.", "dogfood_battery": "7aeec2d3 · os-verify-lock VERDICT command-exit 0 (dogfood closure build via turbo --filter=@objectstack/dogfood, then pnpm --filter @objectstack/dogfood test, no shard) · Test Files 289 passed, 3 skipped (292) · Tests 2481 passed, 18 skipped (2499) · no failure. ec6d23561 adds only a runtime test file, no source.", "mcp_calls": "5, all reads: issue_read x2, pull_request_read get_check_runs x1, get_job_logs x2. Other reads went through gh api GET.", "api_writes": "2 relay dispatches as objectstack-fleet[bot]: pr_create (POST pulls, then POST issues 25286 assignees os-steve; body read back byte-identical, 5000 bytes) and this comment (POST issues 25271 comments). git push x4 (not REST).", "open_questions": [ { "question": "Collateral of the narrow rule. A single deployment that holds several organizations and has already moved default off its Default Organization (the break-glass half admits that when another administrator stands) cannot re-slug any of them back to default: there is no Default Organization left, so every re-slug to default reads as a capture. The remedies in the refusal are the census's: hold one organization, or declare a walled posture.", "options": [ "A: keep it. The narrow rule refuses only the capture, and the shape is the one the boot census already reports as undeclared", "B: also refuse moving default OFF the Default Organization while other organizations exist, so the stuck shape cannot arise. This widens the narrowing to the update half's territory and changes ADR-0135 D5.2 semantics there", "C: admit re-slug to default when no Default Organization is nameable and the target owns no admin_full_access grant. This is weak, because a grant can be written after the slug" ], "recommendation": "A, because it is the least collateral that closes every door, the stuck shape is already a reported misconfiguration with stated ways out, and B and C each reopen or widen the decision beyond this card" } ], "out_of_scope_findings": [ "carrier: noted, not filed. The refusal answers the standard PERMISSION_DENIED, as every last-admin-guard half does. A dedicated registered code needs an error-code-ledger row in packages/spec, outside this claim's file surface (in PR Acceptance notes)", "carrier: noted, not filed. ADR-0131's 2026-10-22 D5 note names the insert half but not this update-side rule. It is docs/adr, excluded by the claim, so it is left for the epic's next amendment (in PR Acceptance notes)" ], "deviations": [ "File surface grew by one file: packages/runtime/src/default-organization-statements.golden.test.ts. This change turned it red in CI Test Core 6/6, because it moved default onto a second organization with a system update, which is the capture this card closes. It is fixed here as a stale fixture made red by this change.", "The worktree is ../objectstack-issue-25271 in this cloud container: the main-checkout hook refused writes on the primary checkout. Branch and remote are the claim's.", "Commits carry the AGENTS.md model-free trailer pair, not the harness-suggested model-named one." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
os-dev-report (follow-up round, final head 8031562)
Answering the seat's two messages on PR #25286.
Base.
origin/main(a835858, past 1eb1afa) is merged in with a merge commit, no rebase: e47a5a9, clean. #25273's plugin-auth refactor touches no file in this diff.Resolution: (a), refuse at every door.
isSystemwrite: better-auth's adapter, a seed, a plugin.attributedUserIdan authorization input. Its contract says it is attribution, not authorization. I do not argue for changing that.packages/runtime/src/default-organization-statements.golden.test.ts(ec6d235). It is not deleted and not skipped. Its intent, the memo retiring on a Default Organization change in both directions, is kept on a path that stays legal:defaultmoved OFF the Default Organization withdraws standing;default" is now asserted refused 403PERMISSION_DENIED. Its expectation changed from admitted to refused.single, with several organizations, making a different organization the Default Organization. An atomic swap was never possible (unique slug), and the two-step move is now refused at its second step. The changeset (8031562) names this as REMOVED. It also names the stranding consequence and what an operator does instead: move theadmin_full_accessgrant rather than the slug, or hold one organization, or declare a walled posture.Measured on 8031562:
os serveofexamples/app-crmundersingleon a fresh SQLite file:organization/create403ORGANIZATION_CREATION_DISABLED;defaulttoacme200, and back 200;isolatedand undergroup(@objectstack/organizationsdeclared in the app for the run only, restored to the HEAD blob;OS_PLATFORM_OWNER_EMAILset to the signing-up account):organization/create200 each;defaultanswered better-auth's own 400ORGANIZATION_SLUG_ALREADY_TAKEN, because the walled boot holds adefault-slugged organization.dispatch-gates --commandsexit 0, 69 derived; 68 exit 0;pnpm check:dual-build-cjs-loadsexit 3, NOT MEASURED: it needs a full build this fresh worktree lacks, and CI Build Core covers it;--ranexit 0: 68 run, 1 NOT-MEASURED, 0 UNRUN;check-governed-merges --branchexit 0: 0 of 4 paths governed, 418 lines.Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
Landed · epic PM
session_01Y5583bs2RDd53MH6kM1ca3(os-steve) · 2026-10-22T22:52Z. PR #25286 merged intomainas09e07c259a, and it closes this card.single, asys_organizationslug changed todefaultis admitted only on the organization that is already the Default Organization. Every other such write is refused 403PERMISSION_DENIEDat the engine, for every context: better-auth'sorganization/update, seeds and plugins alike. Walled postures are unchanged.single. The changeset names this and the stranding it leaves; to move the anchor, move the grant, not the slug.CONTRACT_REVIEW_TIERon the narrowing face, PASS 6352123147, then ACCEPT 6352496767. R1 and R4 (isolated,group), the harness (both fixture legs) and the dogfood battery were measured by the dev on the final head.organization_idNOT NULL per cleared table; one predicate for Layer 0 and every driver; bothorWhereNullarms, the__global__sentinel and the #13491 ledger retire (ADR-0131 D1/D8/D9) — protocol 18 #15212 ledger (6352526831).organization_idNOT NULL per cleared table; one predicate for Layer 0 and every driver; bothorWhereNullarms, the__global__sentinel and the #13491 ledger retire (ADR-0131 D1/D8/D9) — protocol 18 #15212's open children are now chore: ADR-0131 small carriers — resolveDeclaredRuleScopes checks the raw page length, the semantic-entry and ceremony wording, and the help texts and docblocks that still say NULL organization means global #25203, fix(core): resolveLocalizationContext's direct sys_setting fallback reads tenant rows with no organization filter, taking the first row per key from any organization (ADR-0131 D8) #25248 and fix(service-settings): a host that binds SettingsService without an organization source reads as the lean composition (NULL rows) on a store that registers sys_organization — derive from the bound engine, or refuse #25249.