Skip to content

Commit 052aaf7

Browse files
committed
wip(auth,core): the last-admin guard and the standing surface read the Default Organization's grant
Claude-Session: https://claude.ai/code/session_01A3vH4wwSVnJvvx5BcfUpPf Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4af7d39 commit 052aaf7

4 files changed

Lines changed: 267 additions & 26 deletions

File tree

‎packages/core/src/security/admin-standing-surface.test.ts‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,27 @@ const VARIANTS: Record<
268268
},
269269
},
270270

271+
// [ADR-0131 D5] The `single` promotion's grant, owned by the Default
272+
// Organization: the only variant in which that organization's row is read,
273+
// so the union needs it — without it `sys_organization.id` would be absent
274+
// from the declaration and the guard would stop judging the delete that
275+
// moves the anchor off this grant.
276+
'default-organization-owned grant': {
277+
org: 'org_1',
278+
tables: {
279+
sys_user: [{ id: 'usr_1', email: 'ada@example.com', ai_access: 0 }],
280+
sys_organization: [{ id: 'org_1', slug: 'default' }],
281+
sys_member: [],
282+
sys_user_position: [],
283+
sys_position: [],
284+
sys_position_permission_set: [],
285+
sys_user_permission_set: [
286+
{ id: 'ups_1', user_id: 'usr_1', permission_set: 'admin_full_access', organization_id: 'org_1' },
287+
],
288+
sys_permission_set: [],
289+
},
290+
},
291+
271292
// [#11663 L2] The CONFIG anchor. No grant row anywhere: standing comes from
272293
// the declared administrator list matched against this row's own `email`,
273294
// gated on `email_verified`. This is the only variant in which those two

‎packages/core/src/security/admin-standing-surface.ts‎

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,9 @@ export interface AdminStandingTable {
9898
* ADMISSION path (`resolveApiKeyAdmission`) is outside it on purpose: it
9999
* authenticates a principal and seeds `permissions` with the key's scopes, and
100100
* confers no administrator standing of its own — `hasPlatformAdminGrant` is set
101-
* from the catalog's `admin_full_access` set reached through an UNSCOPED
102-
* `sys_user_permission_set` grant (§6b, on a NON-WALLED posture only since
103-
* #11663 L5) or from the deployment config matched against the caller's own
101+
* from the catalog's `admin_full_access` set reached through the anchoring
102+
* `sys_user_permission_set` grant (§6b — the Default Organization's or an
103+
* organization-less one, on a NON-WALLED posture only since #11663 L5) or from the deployment config matched against the caller's own
104104
* STORED `sys_user` row (§6b-config, on every posture), never from a scope
105105
* string and never from the caller-seedable `grants.email`.
106106
*/
@@ -126,14 +126,17 @@ export const ADMIN_STANDING_SURFACE: Readonly<Record<string, AdminStandingTable>
126126
sys_user_permission_set: {
127127
role: 'derives',
128128
reason:
129-
'The grant that makes a user a platform admin UNDER A NON-WALLED POSTURE: an UNSCOPED, '
130-
+ 'in-window (ADR-0091) grant of `admin_full_access` (§6b). Re-pointing it, scoping it to an '
131-
+ 'organization or moving it out of its window revokes the standing while leaving the row in '
132-
+ 'place. ⚠️ Under a WALLED posture (`group`/`isolated`) it confers NOTHING: the walled half '
129+
'The grant that makes a user a platform admin UNDER A NON-WALLED POSTURE: an in-window '
130+
+ '(ADR-0091) grant of `admin_full_access` owned by the Default Organization (ADR-0131 D5, the '
131+
+ 'row the first-user promotion writes) or by no organization (the row it wrote before) — §6, '
132+
+ '`grantAnchorsPlatformStanding`. Re-pointing it, scoping it to another organization or moving '
133+
+ 'it out of its window revokes the standing while leaving the row in place. ⚠️ Under a '
134+
+ 'WALLED posture (`group`/`isolated`) it confers NOTHING: the walled half '
133135
+ 'of this legacy anchor is retired, so standing there comes from `OS_PLATFORM_OWNER_EMAIL` '
134136
+ '(§6b-config) and from nothing else, and this row is read only for the permission set it '
135-
+ 'names. Under `single` — the default — the row is unchanged and still the anchor that '
136-
+ "rig's zero-config first-user promotion mints. [ADR-0131 D4] The grant holds its set BY "
137+
+ 'names. Under `single` — the default — the row is still the anchor that rig\'s '
138+
+ 'zero-config first-user promotion mints, owned by the Default Organization (see '
139+
+ '`sys_organization`). [ADR-0131 D4] The grant holds its set BY '
137140
+ 'NAME (`permission_set`): a grant that names nothing confers nothing, and its '
138141
+ '`permission_set_id` is not read here.',
139142
columns: [
@@ -148,6 +151,21 @@ export const ADMIN_STANDING_SURFACE: Readonly<Record<string, AdminStandingTable>
148151
],
149152
},
150153

154+
sys_organization: {
155+
role: 'derives',
156+
reason:
157+
'[ADR-0131 D5] Names the Default Organization — `slug = \'default\'`, else the only '
158+
+ 'organization (`readDefaultOrganizationId`) — whose `admin_full_access` grant anchors '
159+
+ 'platform standing under `single` (§6). Read only for a principal holding an '
160+
+ 'organization-owned grant of that set, and never under a wall. Re-slugging the Default '
161+
+ 'Organization while several organizations exist, or deleting it, moves the anchor off its '
162+
+ 'grant and revokes that standing with no grant row touched.',
163+
columns: [
164+
'id',
165+
'slug',
166+
],
167+
},
168+
151169
sys_member: {
152170
role: 'derives',
153171
reason:

‎packages/core/src/security/resolve-authz-context.platform-admin-config.test.ts‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,3 +655,69 @@ describe('[#11663 L5] standing across postures — three arms invariant, the leg
655655
expect(seen[3]).toBe('MEMBER|');
656656
});
657657
});
658+
659+
/**
660+
* [ADR-0131 D5] The `single` promotion's grant row is OWNED BY THE DEFAULT
661+
* ORGANIZATION (#11979, maintainer ruling A on #18707), so the anchor reads that
662+
* row — one derivation site, the posture its parameter. Before this, the anchor
663+
* read the organization-less row only, so a promoted rig whose grant D10's
664+
* ceremony (or the promotion itself) had given the Default Organization resolved
665+
* a zero-admin deployment.
666+
*/
667+
describe('[ADR-0131 D5] under `single` the Default Organization\'s admin_full_access grant is the anchor', () => {
668+
const ownedBy = (organizationId: string, organizations: Array<Record<string, unknown>>) => ({
669+
...legacyTables(),
670+
sys_organization: organizations,
671+
sys_user_permission_set: [
672+
{ id: 'ups_1', user_id: 'usr_1', permission_set: ADMIN_FULL_ACCESS, organization_id: organizationId },
673+
],
674+
});
675+
const DEFAULT_ONLY = [{ id: 'org_default', slug: 'default' }];
676+
677+
it('⭐ confers PLATFORM_ADMIN with no config at all — in the Default Organization and with no active organization', async () => {
678+
for (const tenantId of ['org_default', undefined]) {
679+
const grants = await resolveUserAuthzGrants(makeQl(ownedBy('org_default', DEFAULT_ONLY)), 'usr_1', { nowMs: NOW, tenantId });
680+
expect(grants.posture, String(tenantId)).toBe('PLATFORM_ADMIN');
681+
expect(grants.positions[0]).toBe('platform_admin');
682+
// The anchoring grant applies wherever the principal acts, as the
683+
// organization-less row it replaces did: the set comes with the standing.
684+
expect(grants.permissions).toContain(ADMIN_FULL_ACCESS);
685+
}
686+
});
687+
688+
it('the sole organization is the Default Organization whatever its slug — the boot invariant\'s definition', async () => {
689+
const grants = await resolveUserAuthzGrants(
690+
makeQl(ownedBy('org_only', [{ id: 'org_only', slug: 'acme' }])), 'usr_1', { nowMs: NOW, tenantId: 'org_only' },
691+
);
692+
expect(grants.posture).toBe('PLATFORM_ADMIN');
693+
});
694+
695+
it('⛔ another organization\'s grant of the set confers the set in that organization, never standing', async () => {
696+
const orgs = [{ id: 'org_default', slug: 'default' }, { id: 'org_b', slug: 'b' }];
697+
const grants = await resolveUserAuthzGrants(makeQl(ownedBy('org_b', orgs)), 'usr_1', { nowMs: NOW, tenantId: 'org_b' });
698+
expect(grants.permissions).toContain(ADMIN_FULL_ACCESS);
699+
expect(grants.posture).toBe('MEMBER');
700+
expect(grants.positions).not.toContain('platform_admin');
701+
});
702+
703+
it('⛔ several organizations and none of them `default` name no Default Organization, so nothing anchors', async () => {
704+
const orgs = [{ id: 'org_a', slug: 'a' }, { id: 'org_b', slug: 'b' }];
705+
const grants = await resolveUserAuthzGrants(makeQl(ownedBy('org_a', orgs)), 'usr_1', { nowMs: NOW, tenantId: 'org_a' });
706+
expect(grants.posture).toBe('MEMBER');
707+
});
708+
709+
it('⛔ a WALLED rig derives nothing from the Default Organization\'s grant either, and does not ask which one it is', async () => {
710+
requestPosture('isolated');
711+
const ql = makeQl(ownedBy('org_default', DEFAULT_ONLY));
712+
const grants = await resolveUserAuthzGrants(ql, 'usr_1', { nowMs: NOW, tenantId: 'org_default' });
713+
expect(grants.posture).toBe('MEMBER');
714+
expect(ql.calls.map((c) => c.object)).not.toContain('sys_organization');
715+
});
716+
717+
it('the organization-less row a pre-D5 promotion wrote still anchors, and asks no organization read', async () => {
718+
const ql = makeQl({ ...legacyTables(), sys_organization: DEFAULT_ONLY });
719+
const grants = await resolveUserAuthzGrants(ql, 'usr_1', { nowMs: NOW });
720+
expect(grants.posture).toBe('PLATFORM_ADMIN');
721+
expect(ql.calls.map((c) => c.object)).not.toContain('sys_organization');
722+
});
723+
});

0 commit comments

Comments
 (0)