Skip to content

Commit 980cdc1

Browse files
committed
test(plugin-auth): the lean-embedding pin runs on a nullable sqlite column, 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>
1 parent 433c946 commit 980cdc1

3 files changed

Lines changed: 39 additions & 40 deletions

File tree

‎packages/plugins/plugin-auth/package.json‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@
4646
"jose": "^6.2.12"
4747
},
4848
"devDependencies": {
49-
"@objectstack/driver-memory": "workspace:*",
5049
"@objectstack/driver-sql": "workspace:*",
5150
"@objectstack/formula": "workspace:*",
5251
"@objectstack/objectql": "workspace:*",

‎packages/plugins/plugin-auth/src/last-admin-guard.anchor-parity.test.ts‎

Lines changed: 39 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,19 @@
1515
* let a write take the deployment's last platform administrator — the
1616
* fail-open this file pins shut.
1717
*
18-
* Every case runs a REAL {@link ObjectQL} engine over a real driver
19-
* (better-sqlite3 `:memory:`, or driver-memory), registers the real guard, and
20-
* asks the real resolver. Three things are pinned:
18+
* Every case runs a REAL {@link ObjectQL} engine over better-sqlite3
19+
* `:memory:`, registers the real guard, and asks the real resolver. Three
20+
* things are pinned:
2121
*
2222
* 1. **Where the row can exist.** Through the engine, an organization-less
2323
* grant is derived or refused wherever the organization object is
2424
* registered (ADR-0131 D9), and refused by the NOT NULL column a fresh SQL
25-
* table carries (D1) where it is not. Two compositions can still hold one:
26-
* a lean embedding on driver-memory, and a store built before 18.x.
27-
* 2. **The negative pin** — in both, with a verified `OS_PLATFORM_OWNER_EMAIL`
25+
* table carries (D1) where it is not. What can still hold one is a column
26+
* that is NOT NULL nowhere: a store built before 18.x, or a driver that
27+
* enforces no NOT NULL (driver-memory, measured on #24883), in a lean
28+
* embedding that registers no organization object or beneath the engine.
29+
* The nullable column stands in for both here.
30+
* 2. **The negative pin** — in both shapes, with a verified `OS_PLATFORM_OWNER_EMAIL`
2831
* account as the resolver's only platform administrator and an
2932
* organization-less grant holder beside it, banning that administrator is
3033
* REFUSED.
@@ -36,7 +39,6 @@
3639
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
3740
import { ObjectQL } from '@objectstack/objectql';
3841
import { SqlDriver } from '@objectstack/driver-sql';
39-
import { InMemoryDriver } from '@objectstack/driver-memory';
4042
import { resetPlatformAdminEmailMemo, resolveAuthzContext } from '@objectstack/core';
4143
import { ADMIN_FULL_ACCESS } from '@objectstack/spec/identity';
4244

@@ -93,7 +95,6 @@ const sysUserPosition = {
9395
fields: { id: { ...text('id'), primaryKey: true }, user_id: text('user_id'), position: text('position'), organization_id: text('organization_id') },
9496
};
9597

96-
type DriverKind = 'sqlite' | 'memory';
9798
interface Rig {
9899
engine: ObjectQL;
99100
/** The driver itself, for the rows only a store built before 18.x holds. */
@@ -122,19 +123,19 @@ afterEach(async () => {
122123
}
123124
});
124125

125-
async function boot(kind: DriverKind, opts: { organizationObject: boolean; legacyStore?: boolean }): Promise<Rig> {
126+
async function boot(opts: { organizationObject: boolean; nullableTenantColumn?: boolean }): Promise<Rig> {
126127
const engine = new ObjectQL();
127128
engines.push(engine);
128-
const driver = kind === 'sqlite'
129-
? new SqlDriver({ client: 'better-sqlite3', connection: { filename: ':memory:' }, useNullAsDefault: true })
130-
: new InMemoryDriver();
131-
engine.registerDriver(driver as never, true);
129+
const driver = new SqlDriver({ client: 'better-sqlite3', connection: { filename: ':memory:' }, useNullAsDefault: true });
130+
engine.registerDriver(driver, true);
132131
await engine.init();
133132
const objects = [sysUser, sysMember, sysUserPermissionSet, sysUserPosition, ...(opts.organizationObject ? [sysOrganization] : [])];
134133
for (const o of objects) engine.registry.registerObject(o as never);
135134
await engine.syncSchemas();
136-
// A store built before 18.x: the tenant column nullable (ADR-0131 D1).
137-
if (opts.legacyStore) await relaxEveryRegisteredTenantColumn(engine);
135+
// A tenant column that is NOT NULL nowhere: a store built before 18.x has one
136+
// (ADR-0131 D1 makes a fresh one NOT NULL), and so does a driver that
137+
// enforces no NOT NULL.
138+
if (opts.nullableTenantColumn) await relaxEveryRegisteredTenantColumn(engine);
138139
bindTestSecurityCatalog(engine, { permissions: [{ name: ADMIN_FULL_ACCESS }] });
139140
registerLastAdminGuard(engine as unknown as LastAdminGuardEngine, { packageId: 'test.last-admin-guard-parity' });
140141
return { engine, driver: driver as unknown as Rig['driver'] };
@@ -173,22 +174,29 @@ async function refusalOf(write: Promise<unknown>): Promise<{ code?: unknown; sta
173174

174175
describe('[ADR-0131 D1/D9] where an organization-less admin_full_access grant can still be written', () => {
175176
type Expect = 'derived' | 'refused-d9' | 'refused-not-null' | 'stored-null';
176-
const cases: Array<{ kind: DriverKind; organizations: number | 'no-object'; posture: 'single' | 'isolated'; expected: Expect }> = [
177-
{ kind: 'sqlite', organizations: 'no-object', posture: 'single', expected: 'refused-not-null' },
178-
{ kind: 'sqlite', organizations: 0, posture: 'single', expected: 'refused-d9' },
179-
{ kind: 'sqlite', organizations: 1, posture: 'single', expected: 'derived' },
180-
{ kind: 'sqlite', organizations: 2, posture: 'single', expected: 'refused-d9' },
181-
{ kind: 'sqlite', organizations: 0, posture: 'isolated', expected: 'refused-d9' },
182-
{ kind: 'sqlite', organizations: 1, posture: 'isolated', expected: 'refused-d9' },
183-
{ kind: 'sqlite', organizations: 2, posture: 'isolated', expected: 'refused-d9' },
184-
{ kind: 'memory', organizations: 'no-object', posture: 'single', expected: 'stored-null' },
185-
{ kind: 'memory', organizations: 0, posture: 'single', expected: 'refused-d9' },
177+
type Column = 'fresh' | 'nullable';
178+
const cases: Array<{ column: Column; organizations: number | 'no-object'; posture: 'single' | 'isolated'; expected: Expect }> = [
179+
{ column: 'fresh', organizations: 'no-object', posture: 'single', expected: 'refused-not-null' },
180+
{ column: 'fresh', organizations: 0, posture: 'single', expected: 'refused-d9' },
181+
{ column: 'fresh', organizations: 1, posture: 'single', expected: 'derived' },
182+
{ column: 'fresh', organizations: 2, posture: 'single', expected: 'refused-d9' },
183+
{ column: 'fresh', organizations: 0, posture: 'isolated', expected: 'refused-d9' },
184+
{ column: 'fresh', organizations: 1, posture: 'isolated', expected: 'refused-d9' },
185+
{ column: 'fresh', organizations: 2, posture: 'isolated', expected: 'refused-d9' },
186+
// The engine's no-organization-object exit (ADR-0131 D9) leaves the row
187+
// organization-less; only the column could refuse it.
188+
{ column: 'nullable', organizations: 'no-object', posture: 'single', expected: 'stored-null' },
189+
{ column: 'nullable', organizations: 1, posture: 'single', expected: 'derived' },
190+
{ column: 'nullable', organizations: 1, posture: 'isolated', expected: 'refused-d9' },
186191
];
187192

188193
for (const c of cases) {
189-
it(`${c.kind}, ${c.organizations === 'no-object' ? 'no organization object' : `${c.organizations} organization(s)`}, ${c.posture}: ${c.expected} — and the guard agrees with the resolver about its holder`, async () => {
194+
it(`${c.column} tenant column, ${c.organizations === 'no-object' ? 'no organization object' : `${c.organizations} organization(s)`}, ${c.posture}: ${c.expected} — and the guard agrees with the resolver about its holder`, async () => {
190195
process.env.OS_TENANCY_POSTURE = c.posture;
191-
const { engine, driver } = await boot(c.kind, { organizationObject: c.organizations !== 'no-object' });
196+
const { engine, driver } = await boot({
197+
organizationObject: c.organizations !== 'no-object',
198+
nullableTenantColumn: c.column === 'nullable',
199+
});
192200
const organizations = typeof c.organizations === 'number' ? c.organizations : 0;
193201
// Written beneath the engine, before anything has asked it about
194202
// organizations: its probe reads the table at the grant insert below.
@@ -221,11 +229,6 @@ describe('[ADR-0131 D1/D9] where an organization-less admin_full_access grant ca
221229
expect(banRefusal?.code).toBe(isAdmin ? 'PERMISSION_DENIED' : undefined);
222230
});
223231
}
224-
225-
it('driver-memory refuses a walled posture outright — no walled composition holds the row there', async () => {
226-
process.env.OS_TENANCY_POSTURE = 'isolated';
227-
await expect(boot('memory', { organizationObject: true })).rejects.toMatchObject({ code: 'MEMORY_MULTI_TENANT_UNSUPPORTED' });
228-
});
229232
});
230233

231234
describe('[ADR-0131 D5] the negative pin: an organization-less holder is no survivor', () => {
@@ -244,9 +247,9 @@ describe('[ADR-0131 D5] the negative pin: an organization-less holder is no surv
244247
expect(await resolverStanding(rig.engine, 'usr_legacy'), 'the holder is no administrator to the resolver').toBe(false);
245248
}
246249

247-
it('⭐ a lean embedding on driver-memory (no sys_organization): banning the only platform administrator is REFUSED', async () => {
250+
it('⭐ a lean embedding (no sys_organization) whose tenant column is NOT NULL nowhere: banning the only platform administrator is REFUSED', async () => {
248251
process.env.OS_TENANCY_POSTURE = 'single';
249-
const rig = await boot('memory', { organizationObject: false });
252+
const rig = await boot({ organizationObject: false, nullableTenantColumn: true });
250253
await seedTwoCandidates(rig, false);
251254

252255
await expect(ban(rig.engine, 'usr_owner')).rejects.toMatchObject({ code: 'PERMISSION_DENIED', status: 403 });
@@ -256,7 +259,7 @@ describe('[ADR-0131 D5] the negative pin: an organization-less holder is no surv
256259

257260
it('⭐ a store built before 18.x (sys_organization registered, the row beneath the engine): the same ban is REFUSED', async () => {
258261
process.env.OS_TENANCY_POSTURE = 'single';
259-
const rig = await boot('sqlite', { organizationObject: true, legacyStore: true });
262+
const rig = await boot({ organizationObject: true, nullableTenantColumn: true });
260263
await rig.driver.create('sys_organization', { id: DEFAULT_ORG, name: 'Default', slug: 'default' });
261264
await seedTwoCandidates(rig, true);
262265

@@ -271,7 +274,7 @@ describe('[ADR-0131 D5] the positive control: the Default Organization\'s grant
271274
process.env.OS_TENANCY_POSTURE = 'single';
272275
process.env.OS_PLATFORM_OWNER_EMAIL = OWNER;
273276
resetPlatformAdminEmailMemo();
274-
const { engine } = await boot('sqlite', { organizationObject: true });
277+
const { engine } = await boot({ organizationObject: true });
275278
await engine.insert('sys_organization', { id: DEFAULT_ORG, name: 'Default Organization', slug: 'default' }, SYSTEM);
276279
await seedUser(engine, 'usr_promoted', 'promoted@corp.example');
277280
await engine.insert(

‎pnpm-lock.yaml‎

Lines changed: 0 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)