Skip to content

Commit a9339b0

Browse files
committed
test(plugin-security): keep the platform-admin seeding suites until the code they pin is deleted (#15204 stage 6b-2a)
bootstrap-platform-admin.test.ts and bootstrap-platform-admin-seeded-provenance.test.ts pin bootstrapPlatformAdmin's seeding, which 6b-2 deletes; no pin of a behaviour 6b-2 deletes leaves main before it. Restored byte-identical to main with their ledger entries; their deletion moves to the T1 PR, which lands directly before 6b-2. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013XKtmoR5QCUSTMjiQ3os49
1 parent 675d1bc commit a9339b0

4 files changed

Lines changed: 523 additions & 0 deletions

File tree

Lines changed: 369 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,369 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* [commit 712e185db] What provenance a REAL `bootstrapPlatformAdmin` run leaves on the
5+
* platform default permission sets, and what `os meta resync` then does with it.
6+
*
7+
* ## Why this file exists at all
8+
*
9+
* `bootstrap-platform-admin.test.ts` covers the resync branch three ways —
10+
* `managed_by` hand-seeded to `null`, to `'user'`, to `'package'` — and every
11+
* one of them hand-builds the pre-image. That is correct unit coverage of the
12+
* BRANCH, and it is deliberately left untouched here. What none of them can
13+
* answer is which pre-image the seeder itself produces, because a fake `ql`
14+
* stores exactly the columns it is handed and a declared field `defaultValue`
15+
* never runs. So the one row shape the platform actually creates was the one
16+
* row shape nothing exercised, and the drift was invisible in both directions.
17+
*
18+
* This file closes that by seeding through the REAL shipped
19+
* `defaultPermissionSets` against a real `ObjectQL` engine over a real
20+
* better-sqlite3 `SqlDriver` — the same wiring `security-plugin.ts` and
21+
* `os meta resync` use, which hand the bare engine straight to the seeder.
22+
*
23+
* ## The ruling these assertions now pin (2026-08-15, Option A)
24+
*
25+
* The original version of this file recorded a MEASUREMENT and explicitly
26+
* refused to endorse it: a seeded row stored `'admin'` (the declaration's
27+
* `defaultValue`), so `os meta resync` returned `resynced 0 / resyncSkipped 8`
28+
* and skipped every platform default set — the inverse of what #2705 built the
29+
* flag for. It left the product question open:
30+
*
31+
* > are the platform default permission sets meant to be platform-owned
32+
* > (and therefore resyncable), or env-authored (and therefore deliberately
33+
* > left alone)?
34+
*
35+
* ⛔ That question is CLOSED. The maintainer ruled **Option A** on 2026-08-15:
36+
* the platform default sets are platform-owned and resyncable, and the seeder
37+
* stamps `managed_by: 'platform'` on its insert. This file no longer records an
38+
* undecided state — it pins the ruling, on BOTH sides of the line the ruling
39+
* drew:
40+
*
41+
* 1. **Forward (section 1)** — a fresh install stores `'platform'` and a real
42+
* resync reconciles every default set.
43+
* 2. **Legacy (section 2)** — a row from a PRE-ruling install still carries
44+
* `'admin'` and is still SKIPPED, forever and on purpose. The ruling
45+
* forbids a migration: a stored `'admin'` cannot be told apart from a
46+
* genuine Setup takeover, so restamping could silently overwrite a real
47+
* admin's edits on the next resync. Report, don't rewrite.
48+
*
49+
* Section 2 is the half that is easy to lose. Deleting it would leave the
50+
* upgrade path — the one every existing deployment is actually on — with no
51+
* coverage at all, and a later "cleanup" that restamps legacy rows would then
52+
* go green.
53+
*/
54+
55+
import { describe, it, expect, afterEach } from 'vitest';
56+
import { ObjectQL } from '@objectstack/objectql';
57+
import { SqlDriver } from '@objectstack/driver-sql';
58+
// `sys_user` is what the promotion half enumerates. Registered as the REAL
59+
// declaration so its verdict ("no human users yet") is a genuine reading of an
60+
// empty table rather than a table-missing error `tryFind` swallowed into `[]` —
61+
// the two are indistinguishable from the outside and only one of them is the
62+
// first-boot ordering this file means to reproduce.
63+
import { SysUser } from '@objectstack/platform-objects/identity';
64+
// The REAL shipped seeder and the REAL shipped declarations. A local
65+
// re-implementation of either would make this a test of the copy, which is
66+
// precisely the gap the file exists to close.
67+
import { bootstrapPlatformAdmin } from './bootstrap-platform-admin.js';
68+
import { SysPermissionSet } from './objects/sys-permission-set.object.js';
69+
import { SysUserPermissionSet } from './objects/sys-user-permission-set.object.js';
70+
import { defaultPermissionSets } from './objects/default-permission-sets.js';
71+
72+
const SYSTEM_CTX = { isSystem: true };
73+
74+
const engines: ObjectQL[] = [];
75+
76+
afterEach(async () => {
77+
while (engines.length) {
78+
try {
79+
await engines.pop()?.destroy();
80+
} catch {
81+
/* noop */
82+
}
83+
}
84+
});
85+
86+
/**
87+
* A fresh engine on its own `:memory:` database, carrying the permission-set
88+
* declaration passed in. `declaration` exists for section 3's counterfactual;
89+
* every case in sections 1 and 2 boots the shipped declaration unmodified.
90+
*/
91+
async function boot(declaration: any = SysPermissionSet): Promise<ObjectQL> {
92+
const engine = new ObjectQL();
93+
engine.registerDriver(
94+
new SqlDriver({
95+
client: 'better-sqlite3',
96+
connection: { filename: ':memory:' },
97+
useNullAsDefault: true,
98+
}),
99+
true,
100+
);
101+
await engine.init();
102+
engine.registerApp({
103+
id: 'com.objectstack.security-objects',
104+
name: 'Security Objects',
105+
version: '1.0.0',
106+
type: 'plugin',
107+
scope: 'system',
108+
objects: [declaration, SysUserPermissionSet, SysUser],
109+
} as any);
110+
await engine.syncSchemas();
111+
engines.push(engine);
112+
return engine;
113+
}
114+
115+
/**
116+
* The row as it is physically STORED, read at the driver past every engine-side
117+
* projection. The resync branch reads the engine's view, so both are asserted
118+
* where it matters — a default applied only on read and a value written on
119+
* insert are different facts about the database, and only one of them survives
120+
* a restart.
121+
*/
122+
async function storedRow(engine: ObjectQL, name: string): Promise<any> {
123+
const driver: any = (engine as any).getDriver('sys_permission_set');
124+
const rows: any[] = await driver.knex('sys_permission_set').select('*').where({ name });
125+
return rows[0];
126+
}
127+
128+
/** The row as the seeder's own `tryFind` sees it — the value resync branches on. */
129+
async function rowViaEngine(engine: ObjectQL, name: string): Promise<any> {
130+
const rows: any[] = await (engine as any).find(
131+
'sys_permission_set',
132+
{ where: { name }, limit: 1 },
133+
{ context: SYSTEM_CTX },
134+
);
135+
return rows[0];
136+
}
137+
138+
/**
139+
* A row exactly as an install before commit 712e185db holds it — written the way the old
140+
* seeder wrote it, which is to say WITHOUT `managed_by`, so the value comes
141+
* from the declaration's `defaultValue: 'admin'` by the very mechanism that
142+
* produced it on every install created before the ruling.
143+
*
144+
* Reproducing the legacy pre-image through its original mechanism rather than
145+
* hand-writing `'admin'` is deliberate: it keeps the case anchored to the real
146+
* declaration, and each case asserts the resulting value before relying on it,
147+
* so a moved default shows up as a failure here instead of quietly turning the
148+
* legacy pin into a test of something else.
149+
*/
150+
async function seedLegacyRow(engine: ObjectQL, ps: any): Promise<void> {
151+
await (engine as any).insert(
152+
'sys_permission_set',
153+
{
154+
id: `ps_legacy_${ps.name}`,
155+
name: ps.name,
156+
label: ps.label ?? ps.name,
157+
// A deliberately STALE payload: the resync would rewrite these if it ever
158+
// touched the row, so section 2 can prove "left untouched" by content and
159+
// not merely by a counter.
160+
object_permissions: '{}',
161+
field_permissions: '{}',
162+
system_permissions: '[]',
163+
row_level_security: '[]',
164+
tab_permissions: '{}',
165+
active: true,
166+
},
167+
{ context: SYSTEM_CTX },
168+
);
169+
}
170+
171+
function collectingLogger() {
172+
const info: string[] = [];
173+
const warn: string[] = [];
174+
return {
175+
info,
176+
warn,
177+
logger: {
178+
info: (m: string) => info.push(m),
179+
warn: (m: string) => warn.push(m),
180+
},
181+
};
182+
}
183+
184+
describe('#8692 — provenance of a seeder-created default permission set (measured, real engine)', () => {
185+
// ───────────────────────────────────────────────────────────────────────────
186+
// 1. FORWARD — a fresh install after the ruling
187+
// ───────────────────────────────────────────────────────────────────────────
188+
189+
it('ANTI-VACUITY: the shipped defaults are non-empty and really get seeded', async () => {
190+
// Every count below is relative to this set. If it were empty, "resync
191+
// reconciled all of them" would be trivially true of nothing — the shape in
192+
// which this whole file could pass while measuring air.
193+
expect(defaultPermissionSets.length).toBeGreaterThan(0);
194+
195+
const engine = await boot();
196+
const out = await bootstrapPlatformAdmin(engine as any, defaultPermissionSets);
197+
expect(out.seeded).toBe(defaultPermissionSets.length);
198+
});
199+
200+
it('a seeder-created row is stored `managed_by: "platform"` — stamped, not defaulted', async () => {
201+
// THE RULING, pinned (Option A, 2026-08-15). Before it, this row stored
202+
// `'admin'` — the declaration's `defaultValue` — because the insert omitted
203+
// `managed_by` entirely. The seeder now stamps provenance explicitly, in
204+
// line with `bootstrap-builtin-positions.ts`, which always has.
205+
const engine = await boot();
206+
await bootstrapPlatformAdmin(engine as any, defaultPermissionSets);
207+
208+
// Physically stored, not merely projected on read: the value survives a
209+
// restart, so every later boot reads it too.
210+
expect((await storedRow(engine, 'admin_full_access')).managed_by).toBe('platform');
211+
// …and the same value is what the seeder's own read sees, which is the one
212+
// the resync condition branches on.
213+
expect((await rowViaEngine(engine, 'admin_full_access')).managed_by).toBe('platform');
214+
});
215+
216+
it('so `os meta resync` now reconciles EVERY platform default set', async () => {
217+
// The behaviour #2705 built the flag for, finally reachable: resynced == the
218+
// whole shipped set, resyncSkipped 0. Asserted against the live length so
219+
// the pin stays true if a default set is added or removed.
220+
const engine = await boot();
221+
await bootstrapPlatformAdmin(engine as any, defaultPermissionSets);
222+
223+
const out = await bootstrapPlatformAdmin(engine as any, defaultPermissionSets, { resync: true });
224+
225+
expect(out.resynced).toBe(defaultPermissionSets.length);
226+
expect(out.resyncSkipped).toBe(0);
227+
});
228+
229+
it('a resync of a fresh install warns about nothing', async () => {
230+
// The counterpart to the old measurement, which emitted one bogus
231+
// "intentional override" warn per shipped set. Nothing is skipped now, so
232+
// nothing is warned about.
233+
const engine = await boot();
234+
await bootstrapPlatformAdmin(engine as any, defaultPermissionSets);
235+
236+
const { warn, logger } = collectingLogger();
237+
await bootstrapPlatformAdmin(engine as any, defaultPermissionSets, { resync: true, logger });
238+
239+
expect(warn).toEqual([]);
240+
});
241+
242+
it('resync reconciles a STALE platform-owned row to the shipped declaration', async () => {
243+
// Counts alone cannot show the reconcile actually landed. Blank the payload
244+
// on a seeded row, resync, and read the declaration back out of storage.
245+
const engine = await boot();
246+
await bootstrapPlatformAdmin(engine as any, defaultPermissionSets);
247+
248+
const driver: any = (engine as any).getDriver('sys_permission_set');
249+
await driver.knex('sys_permission_set')
250+
.where({ name: 'admin_full_access' })
251+
.update({ system_permissions: '[]' });
252+
expect((await storedRow(engine, 'admin_full_access')).system_permissions).toBe('[]');
253+
254+
await bootstrapPlatformAdmin(engine as any, defaultPermissionSets, { resync: true });
255+
256+
const shipped = defaultPermissionSets.find((p) => p.name === 'admin_full_access')!;
257+
expect(JSON.parse((await storedRow(engine, 'admin_full_access')).system_permissions))
258+
.toEqual(shipped.systemPermissions ?? []);
259+
// …and the reconcile did NOT touch provenance: `platformOwnedFields` carries
260+
// no `managed_by`, which is what keeps a resync from restamping any row.
261+
expect((await storedRow(engine, 'admin_full_access')).managed_by).toBe('platform');
262+
});
263+
264+
// ───────────────────────────────────────────────────────────────────────────
265+
// 2. LEGACY — a pre-ruling install, which keeps the skip forever
266+
// ───────────────────────────────────────────────────────────────────────────
267+
268+
it('LEGACY: a pre-ruling `admin`-stamped row is still skipped, and its content survives', async () => {
269+
// ⛔ The ruling's load-bearing half: forward-stamp only, NO migration. A
270+
// stored `'admin'` is indistinguishable between "the old seeder's field
271+
// default" and "an administrator took this set over in Setup", so it must
272+
// never be reconciled on the strength of a guess.
273+
const engine = await boot();
274+
const legacy = defaultPermissionSets[0];
275+
await seedLegacyRow(engine, legacy);
276+
277+
// The pre-image really is what a pre-ruling install holds — asserted, not
278+
// assumed, so this case cannot pass by testing some other row shape.
279+
expect((await storedRow(engine, legacy.name)).managed_by).toBe('admin');
280+
281+
// An upgraded install: the seeder runs, finds the legacy row and leaves it,
282+
// and inserts the remaining sets freshly stamped `'platform'`.
283+
await bootstrapPlatformAdmin(engine as any, defaultPermissionSets);
284+
expect((await storedRow(engine, legacy.name)).managed_by).toBe('admin');
285+
286+
const out = await bootstrapPlatformAdmin(engine as any, defaultPermissionSets, { resync: true });
287+
288+
// The mixed shape an upgrade produces: everything the platform stamped is
289+
// reconciled, the one legacy row is not.
290+
expect(out.resyncSkipped).toBe(1);
291+
expect(out.resynced).toBe(defaultPermissionSets.length - 1);
292+
293+
// "Left untouched" proven by CONTENT, not just by a counter: the stale
294+
// payload seeded above is still stale, and provenance was not restamped.
295+
const row = await storedRow(engine, legacy.name);
296+
expect(row.system_permissions).toBe('[]');
297+
expect(row.object_permissions).toBe('{}');
298+
expect(row.managed_by).toBe('admin');
299+
});
300+
301+
it('LEGACY: the skip is logged neutrally — no claim of intent', async () => {
302+
// The warn used to end "(intentional override)", which is false for exactly
303+
// these rows: on a pre-ruling install the only writer may have been the
304+
// seeder itself one call earlier. The reworded line states the provenance
305+
// and the action and asserts nothing about anybody's intent.
306+
const engine = await boot();
307+
const legacy = defaultPermissionSets[0];
308+
await seedLegacyRow(engine, legacy);
309+
await bootstrapPlatformAdmin(engine as any, defaultPermissionSets);
310+
311+
const { warn, logger } = collectingLogger();
312+
await bootstrapPlatformAdmin(engine as any, defaultPermissionSets, { resync: true, logger });
313+
314+
expect(warn).toEqual([
315+
`[security] resync left ${legacy.name} untouched — row is admin-owned`,
316+
]);
317+
// Pinned as a substring too, so a future reword cannot quietly reintroduce
318+
// the intent claim in some other sentence shape.
319+
expect(warn.join('\n')).not.toContain('intentional override');
320+
});
321+
322+
// ───────────────────────────────────────────────────────────────────────────
323+
// 3. ATTRIBUTION — which link actually supplies the value
324+
// ───────────────────────────────────────────────────────────────────────────
325+
326+
/**
327+
* A counterfactual, and note it runs the OPPOSITE way round from the one this
328+
* file carried before the ruling. Back then the seeder wrote no `managed_by`,
329+
* so respelling the declared default flipped the stored value — which is what
330+
* proved the default was the source. Now the seeder stamps explicitly, so the
331+
* declared default must no longer be able to move the value at all.
332+
*
333+
* Predicted direction, written down before running: boot a CLONE of the
334+
* declaration whose `managed_by` default is respelled to `'package'`, seed
335+
* with the same unmodified seeder, and the row must STILL store `'platform'`.
336+
*
337+
* That one reading establishes what section 1 cannot on its own: the value
338+
* comes from the seeder's stamp and not from a declaration that merely
339+
* happens to agree with it. Without this case, respelling
340+
* `defaultValue: 'admin'` to `'platform'` in the object file would make every
341+
* assertion in section 1 pass with the stamp deleted.
342+
*/
343+
it('ATTRIBUTION: the seed stamp beats the declared default, so the default cannot move it', async () => {
344+
const respelled = {
345+
...SysPermissionSet,
346+
fields: {
347+
...(SysPermissionSet as any).fields,
348+
managed_by: { ...(SysPermissionSet as any).fields.managed_by, defaultValue: 'package' },
349+
},
350+
};
351+
// Guard: the clone is a copy, and the shipped declaration is untouched by
352+
// building it. Without this the case could be measuring a mutation it made.
353+
// The shipped default stays `'admin'` deliberately — it is the right default
354+
// for a set an admin creates in Setup, and the ruling changed the SEEDER,
355+
// not the declaration.
356+
expect((SysPermissionSet as any).fields.managed_by.defaultValue).toBe('admin');
357+
358+
const engine = await boot(respelled);
359+
await bootstrapPlatformAdmin(engine as any, defaultPermissionSets);
360+
361+
expect((await storedRow(engine, 'admin_full_access')).managed_by).toBe('platform');
362+
363+
// And the consequence still holds under a default that would otherwise have
364+
// caused a skip: resync reconciles everything.
365+
const out = await bootstrapPlatformAdmin(engine as any, defaultPermissionSets, { resync: true });
366+
expect(out.resynced).toBe(defaultPermissionSets.length);
367+
expect(out.resyncSkipped).toBe(0);
368+
});
369+
});

0 commit comments

Comments
 (0)