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
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)
* feat(plugin-auth): re-point ensureDefaultOrganization at the config anchor; 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
* docs(plugin-auth): re-price last-admin-guard under the config anchor; 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
* docs(permissions): re-anchor system-context census lines moved by the 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
---------
Co-authored-by: Claude <noreply@anthropic.com>
Re-point the default-organization bootstrap at the platform-admin config anchor (#11973, #11663 leg L3). `ensureDefaultOrganization` now resolves "which user is the platform admin" from `OS_PLATFORM_OWNER_EMAIL` first — the first declared entry with a stored, email-verified `sys_user` account, matched through `@objectstack/core`'s own `matchesConfiguredPlatformAdmin`, the same oracle the authorization derivation reads — and falls back to the legacy unscoped `admin_full_access` grant row (which still anchors `single`-posture deployments and the honoured migration window). Its re-run trigger widens from `sys_user_permission_set` inserts to the new exported predicate `isDefaultOrganizationBootstrapTrigger`: `sys_user` inserts and `email`/`email_verified` updates (how a config-anchored administrator comes into standing — on fresh walled rigs no grant insert ever fires any more), plus the legacy grant-insert arm unchanged. `single`-posture behaviour is unchanged: with the variable unset, the config half costs no read and the grant anchor decides exactly as before.
Copy file name to clipboardExpand all lines: content/docs/permissions/system-context.mdx
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -97,7 +97,7 @@ that silently does not happen.
97
97
| 8 |`explain()` may target a principal other than the caller | plugin-security | Get: no `manage_users` / delegated-admin check |`security-plugin.ts:3827`|
98
98
| 9 | Anonymous-deny treats the caller as authenticated | core | Get: passes the 401 seam with no `userId`|`anonymous-deny.ts:154`|
| 12 | Per-request performance timings disclosed | observability | Get: timing headers a normal caller cannot pull |`perf-timing.ts:474`|
102
102
| 13 | Permission-set **overlay discard** skips the tenant-admin assertion | plugin-security | Get: an overlay can be discarded with no authenticated tenant administrator |`permission-set-overlay-discard.ts:142`|
103
103
| 14 | MCP stdio bridge skips the object API-exposure gate | mcp | Get: the bridge reaches objects whose `apiEnabled` / `apiMethods` would refuse an external caller |`stdio-data-bridge.ts:246`|
@@ -198,7 +198,7 @@ assuming `isSystem` covers it is a documented source of bugs.
198
198
| "It skips validation rules" |**No.** Field shape, `format`, `script` and the rest still run. The `readonly` strip runs *before* validation precisely so a discarded value is not judged |`objectql/src/engine.ts:9492`–`9509`|
199
199
| "It preserves a supplied `updated_at` / `updated_by`" |**No.** That is `preserveAudit`, a separate opt-in — and an UPDATE-path exemption only |`field.zod.ts:1451` (#3493 / #6640) |
200
200
| "It stamps `created_by`" |**No.** Audit stamping reads `userId` from the context. A user-less system write stamps nothing — that is today's behaviour, not an error |`runtime-identity.ts:280`–`281`|
201
-
| "It bypasses every guard" |**No.** The last-admin guard applies to **every** context, `isSystem` included — the deprovision path that actually locks an org out is the system one |`last-admin-guard.ts:273`|
201
+
| "It bypasses every guard" |**No.** The last-admin guard applies to **every** context, `isSystem` included — the deprovision path that actually locks an org out is the system one |`last-admin-guard.ts:286`|
202
202
| "A client can request it" |**No.** Never settable from inbound HTTP or from an action body |`rest-server.ts:1240`, `:1269`; `domains/actions.ts:404`|
0 commit comments