|
2919 | 2919 | "title": "OS_PLATFORM_OWNER_EMAIL platform-admin anchor: only a VERIFIED stored-email match confers PLATFORM_ADMIN — unset, unverified match, non-match and malformed lists each confer nothing, fail closed", |
2920 | 2920 | "since": "v17", |
2921 | 2921 | "status": "active", |
2922 | | - "revision": 7, |
| 2922 | + "revision": 8, |
2923 | 2923 | "priority": "P1", |
2924 | 2924 | "surface": "api", |
2925 | 2925 | "personas": [ |
2926 | | - "admin (seeded dev admin — its address is seed-stamped verified per #11343. Under single, the posture this item boots, it is grant-anchored via the unscoped admin_full_access row that first-registrant promotion writes, the ADR-0068 D2 contrast; under a walled posture no such row is written and an existing one confers no platform-admin standing (#11663 L5), so it would stand only if OS_PLATFORM_OWNER_EMAIL named its address)", |
| 2926 | + "admin (seeded dev admin — its address is seed-stamped verified per #11343. Under single, the posture this item boots, it is grant-anchored via the admin_full_access grant that first-registrant promotion writes, owned by the Default Organization (ADR-0131 D5), the ADR-0068 D2 contrast; an organization-less admin_full_access row anchors nothing on any posture; under a walled posture no such grant is written and an existing one confers no platform-admin standing (#11663 L5), so it would stand only if OS_PLATFORM_OWNER_EMAIL named its address)", |
2927 | 2927 | "fresh member M (the config-anchor subject; a fresh sign-up's sys_user row is UNVERIFIED, which is exactly the unverified-match fixture)" |
2928 | 2928 | ], |
2929 | 2929 | "fixtures": { |
|
2933 | 2933 | "a manage_platform_settings-floor probe route: GET /api/v1/datasources answers 401/403 BEFORE any service resolution (packages/services/service-datasource/src/admin-routes.ts; DATASOURCE_ADMIN_CAPABILITY), and admin_full_access carries that capability (packages/spec/src/identity/eval-user.zod.ts) — so the probe discriminates platform-admin standing cleanly" |
2934 | 2934 | ], |
2935 | 2935 | "knownGaps": [ |
2936 | | - "the ENTITLED live leg needs a VERIFIED second account, and no stock HTTP path verifies one: email_verified is statically readonly at the user-context API (stripped — the write-path-guards class), a fresh sign-up reads unverified, and the seeded dev admin — whose address IS stamped verified at seed time (#11343, auth-plugin.ts) — also holds the unscoped admin_full_access grant under single (the posture this item boots), so its 200s can never attribute to the config anchor. Verify M's address via a verification-enabled boot with mail capture (identity-auth.email-verification-loop's fixture, itself a knownGap there) or a system-context stamp through the same isSystem doorway the seed itself uses; without either, clause 5's live leg scores blocked(fixture) and the entitled side rests on the unit pin. Measured on main c91af8ed6: POST /api/v1/auth/admin/update-user {userId, data: {emailVerified: true}} answers the seeded dev admin 403 YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS, so the HTTP lane has no verifying door; the run stamped M's email_verified directly on its own scratch database with the server stopped (a system-level write, never on a shared database) and records that it did" |
| 2936 | + "the ENTITLED live leg needs a VERIFIED second account, and no stock HTTP path verifies one: email_verified is statically readonly at the user-context API (stripped — the write-path-guards class), a fresh sign-up reads unverified, and the seeded dev admin — whose address IS stamped verified at seed time (#11343, auth-plugin.ts) — also holds the Default Organization's admin_full_access grant under single (the posture this item boots; ADR-0131 D5), so its 200s can never attribute to the config anchor. Verify M's address via a verification-enabled boot with mail capture (identity-auth.email-verification-loop's fixture, itself a knownGap there) or a system-context stamp through the same isSystem doorway the seed itself uses; without either, clause 5's live leg scores blocked(fixture) and the entitled side rests on the unit pin. Measured on main c91af8ed6: POST /api/v1/auth/admin/update-user {userId, data: {emailVerified: true}} answers the seeded dev admin 403 YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS, so the HTTP lane has no verifying door; the run stamped M's email_verified directly on its own scratch database with the server stopped (a system-level write, never on a shared database) and records that it did" |
2937 | 2937 | ] |
2938 | 2938 | }, |
2939 | 2939 | "steps": [ |
|
2946 | 2946 | ], |
2947 | 2947 | "acceptance": [ |
2948 | 2948 | { |
2949 | | - "clause": "unset = ZERO config-derived administrators, and under single the ADR-0068 D2 unscoped-grant anchor stays the only door: M's datasource probe is refused server-side (401/403 per identity, never 200) and M's systemPermissions lacks manage_platform_settings, while the seeded admin's identical probe answers 200 (both sides of the gate)", |
| 2949 | + "clause": "unset = ZERO config-derived administrators, and under single the ADR-0068 D2 stored-grant anchor — the Default Organization's admin_full_access grant (ADR-0131 D5) — stays the only door: M's datasource probe is refused server-side (401/403 per identity, never 200) and M's systemPermissions lacks manage_platform_settings, while the seeded admin's identical probe answers 200 (both sides of the gate)", |
2950 | 2950 | "oracle": "api", |
2951 | 2951 | "verify": "M's GET /api/v1/datasources >=400 with the declared code; M's /auth/me/permissions systemPermissions array excludes manage_platform_settings; admin's same GET is 200", |
2952 | 2952 | "evidence": "both personas' traces" |
|
2976 | 2976 | "evidence": "test output (and, when the fixture exists, M's entitled traces)" |
2977 | 2977 | }, |
2978 | 2978 | { |
2979 | | - "clause": "the legacy grant is an anchor under single ONLY, and silently so (#11663 L5): under single, standing resting on the unscoped admin_full_access grant alone still resolves PLATFORM_ADMIN (clauses 1 and 3 observe it live); under a walled posture — postureEnforcesWall, i.e. every posture except single, read from the REQUESTED posture — the same row confers no PLATFORM_ADMIN standing (the held set still grants its own capabilities; only the rung and the built-in platform_admin position go), while the config anchor keeps conferring on that same walled rig; and the request path logs nothing about the row on any posture", |
| 2979 | + "clause": "the legacy grant is an anchor under single ONLY, and silently so (#11663 L5): under single, standing resting on the admin_full_access grant first-registrant promotion writes — owned by the Default Organization (ADR-0131 D5) — alone still resolves PLATFORM_ADMIN (clauses 1 and 3 observe it live as the seeded admin's 200); an organization-less admin_full_access row anchors nothing on any posture; under a walled posture — postureEnforcesWall, i.e. every posture except single, read from the REQUESTED posture — the same grant confers no PLATFORM_ADMIN standing (the held set still grants its own capabilities in its own organization; only the rung and the built-in platform_admin position go), while the config anchor keeps conferring on that same walled rig; and the request path logs nothing about the row on any posture", |
2980 | 2980 | "oracle": "test", |
2981 | | - "verify": "packages/core/src/security/resolve-authz-context.platform-admin-config.test.ts — the '[#11663 L5] the legacy grant row is an anchor under `single`, and NOWHERE else', '[#11663 L5] the legacy anchor confers only on the rigs Choice 4A keeps it for' and '[#11663 L5] standing across postures' describes; packages/core/src/security/platform-admin.test.ts — '[#11663 L5] the legacy-grant deprecation pointer is retired' (both pointer symbols absent from the module and from the @objectstack/core security entry, with positive controls). The single half's own fate is #11979's (Choice 4B): if that lands, this clause is re-authored, not read as a regression", |
| 2981 | + "verify": "packages/core/src/security/resolve-authz-context.platform-admin-config.test.ts — the '[#11663 L5] the legacy grant row is an anchor under `single`, and NOWHERE else', '[#11663 L5] the legacy anchor confers only on the rigs Choice 4A keeps it for' and '[#11663 L5] standing across postures' describes, and the '[ADR-0131 D5] under `single` the Default Organization's admin_full_access grant is the anchor' describe (the Default Organization's grant confers with no config; another organization's grant, a walled rig and the organization-less row a pre-D5 promotion wrote anchor nothing); packages/core/src/security/platform-admin.test.ts — '[#11663 L5] the legacy-grant deprecation pointer is retired' (both pointer symbols absent from the module and from the @objectstack/core security entry, with positive controls). The single half's fate was #11979's (Choice 4B): it landed (PR #24373), and this clause was re-authored for it at revision 8 rather than read as a regression", |
2982 | 2982 | "evidence": "test output" |
2983 | 2983 | } |
2984 | 2984 | ], |
|
2995 | 2995 | "non-match (nothing)", |
2996 | 2996 | "comma-separated list — duplicates collapsed, trim+lowercase normalization (platform-admin.ts)", |
2997 | 2997 | "malformed entry (whole-variable refusal, loud once)", |
2998 | | - "walled posture — the unscoped admin_full_access grant alone confers no PLATFORM_ADMIN standing (unit pin, clause 6)" |
| 2998 | + "walled posture — the Default Organization's admin_full_access grant alone confers no PLATFORM_ADMIN standing, and an organization-less row anchors nothing on any posture (unit pins, clause 6)" |
2999 | 2999 | ], |
3000 | 3000 | "traps": [ |
3001 | 3001 | "wrong-persona", |
3002 | 3002 | "auth-state-leak" |
3003 | 3003 | ], |
3004 | 3004 | "automated": { |
3005 | 3005 | "kind": "unit", |
3006 | | - "ref": "packages/core/src/security/resolve-authz-context.platform-admin-config.test.ts (the acceptance criterion, all four fail-closed arms, the ⭐ stored-row-not-seed pin, the #11663 L5 posture split of the legacy grant anchor) + packages/core/src/security/platform-admin.test.ts (parse/refusal/memo) + packages/core/src/security/admin-standing-surface.test.ts. STILL MANUAL: the live-HTTP arms over a real boot (clauses 1-4; clause 6 is test-only) — the pins run the derivation in-process; this item's live legs prove the same verdicts through the mounted server" |
| 3006 | + "ref": "packages/core/src/security/resolve-authz-context.platform-admin-config.test.ts (the acceptance criterion, all four fail-closed arms, the ⭐ stored-row-not-seed pin, the #11663 L5 posture split of the legacy grant anchor, the '[ADR-0131 D5] under `single` the Default Organization's admin_full_access grant is the anchor' describe) + packages/core/src/security/platform-admin.test.ts (parse/refusal/memo) + packages/core/src/security/admin-standing-surface.test.ts. STILL MANUAL: the live-HTTP arms over a real boot (clauses 1-4; clause 6 is test-only) — the pins run the derivation in-process; this item's live legs prove the same verdicts through the mounted server" |
3007 | 3007 | }, |
3008 | 3008 | "source": [ |
3009 | 3009 | "packages/core/src/security/resolve-authz-context.ts#hasPlatformAdminStanding (§6b-config — the config anchor inside the ONE derivation site; additive, never subtractive) + (hasPlatformAdminStanding, the id-shaped projection)", |
3010 | 3010 | "packages/core/src/security/platform-admin.ts#parsePlatformAdminEmails (parsePlatformAdminEmails whole-variable refusal, resolvePlatformAdminEmails loud-once resolve, matchesConfiguredPlatformAdmin verified-only)", |
3011 | | - "packages/core/src/security/resolve-authz-context.ts#grantAnchorsPlatformStanding (#11663 L5 — the unscoped admin_full_access row derives PLATFORM_ADMIN only when the requested posture enforces no wall)", |
| 3011 | + "packages/core/src/security/resolve-authz-context.ts#grantAnchorsPlatformStanding (ADR-0131 D5 + #11663 L5 — the Default Organization's admin_full_access grant derives PLATFORM_ADMIN only when the requested posture enforces no wall; an organization-less row anchors nothing)", |
| 3012 | + "packages/plugins/plugin-security/src/bootstrap-platform-admin.ts#readPromotionGrantOwner (ADR-0131 D5 — the single first-user promotion writes its admin_full_access grant owned by the Default Organization, never with a NULL organization)", |
3012 | 3013 | "packages/spec/src/security/tenancy-posture.ts#postureEnforcesWall (walled = every posture except single)", |
3013 | 3014 | "packages/core/src/security/admin-standing-surface.ts (the env declared a non-table derivation input — no break-glass write can reach it)", |
3014 | 3015 | "packages/types/src/env.ts#PLATFORM_OWNER_EMAIL_ENV + packages/types/src/env.ts#resolvePlatformOwnerEmail (the variable's name, and its live per-call read)", |
|
3058 | 3059 | "date": "2026-10-21", |
3059 | 3060 | "change": "Respelled — fixtures.knownGaps[0]: 'its own isolated scratch database' → 'its own scratch database'. PR #25003's out-of-scope finding (round 17 of wave anchor #24579; ACCEPT 6304389370, landing record 6304641541): 22 non-history texts in 17 items named the own-port boot or its DB with 'isolated' in other words, the phrasing round 12 found a runner reading as the `isolated` tenancy posture (OS_TENANCY_POSTURE=isolated). The new spelling names the boot or the DB by what it is, the run's own port and DB (RUNNER.md's trap-table counter: own port/DB, skill §0), and names no posture. Read at base 93e6c1a5f8. steps[0]'s 'a walled posture (group / isolated)' names the posture and is left as written. No clause changed", |
3060 | 3061 | "ref": "#25018" |
| 3062 | + }, |
| 3063 | + { |
| 3064 | + "revision": 8, |
| 3065 | + "date": "2026-10-23", |
| 3066 | + "change": "Re-authored for the Default Organization grant — clause 6's own re-author trigger fired. #11979 (Choice 4B) landed by PR #24373 (b2e78701f7): the single first-user promotion writes its admin_full_access grant owned by the Default Organization (ADR-0131 D5, bootstrap-platform-admin.ts). PR #24872 (94842bad2a) then retired the organization-less row's single-posture anchor: grantAnchorsPlatformStanding answers yes for the Default Organization's grant only. Six texts still named the organization-less row as the live door — personas[0], fixtures.knownGaps[0], acceptance[0], acceptance[5], variants[6], source[2]; each now names the Default Organization's grant, and personas[0], acceptance[5], variants[6] and source[2] add that an organization-less row anchors nothing on any posture. Clause 6's verify and automated.ref cite the '[ADR-0131 D5] under `single` the Default Organization's admin_full_access grant is the anchor' describe; source gains bootstrap-platform-admin.ts#readPromotionGrantOwner. PR #24463 had re-pointed source[2]'s anchor at grantAnchorsPlatformStanding and left the revision at 3. Read at base 9dc1f42d50. No clause verdict changed: M is refused, the seeded admin answers 200, the walled grant confers no standing, the config anchor confers", |
| 3067 | + "ref": "#24465" |
3061 | 3068 | } |
3062 | 3069 | ] |
3063 | 3070 | }, |
|
0 commit comments