Skip to content

Commit 3937ada

Browse files
committed
test(plugin-security): lock bootstrap-declared-positions seed semantics (#2909 T2)
sys_position is record-authoritative — the declared seeder refreshes only label/description and must never touch bindings, active, is_default, delegatable, or managed_by. That behavior predates these tests but was never locked; regressions would silently clobber admin state at every boot. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017bJWtKmZ2mFpRFAmmmoeqe
1 parent c1da624 commit 3937ada

1 file changed

Lines changed: 94 additions & 0 deletions

File tree

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* [#2909 T2] Lock the seed semantics of bootstrapDeclaredPositions.
5+
*
6+
* sys_position is RECORD-AUTHORITATIVE (ADR-0094 addendum): the declared
7+
* `positions: []` metadata seeds row IDENTITY + display fields only. The
8+
* record side — permission-set bindings, `active`, `is_default`,
9+
* `delegatable`, `managed_by` provenance — belongs to the runtime/admin and
10+
* must never be touched by a re-seed. These tests exist to keep that
11+
* contract from regressing silently (the behavior predates them but was
12+
* never locked).
13+
*/
14+
15+
import { describe, it, expect } from 'vitest';
16+
import { bootstrapDeclaredPositions } from './bootstrap-declared-positions.js';
17+
18+
/** Minimal in-memory ql for sys_position seeding. */
19+
function makeQl(declared: any[] = []) {
20+
const rows: any[] = [];
21+
return {
22+
rows,
23+
_registry: { listItems: (type: string) => (type === 'position' ? declared.map((c) => ({ content: c })) : []) },
24+
async find(object: string, q: any) {
25+
if (object !== 'sys_position') return [];
26+
const where = q?.where ?? {};
27+
return rows.filter((r) => Object.entries(where).every(([k, v]) => r[k] === v));
28+
},
29+
async insert(object: string, data: any) {
30+
if (object !== 'sys_position') return null;
31+
rows.push({ ...data });
32+
return { id: data.id };
33+
},
34+
async update(object: string, data: any) {
35+
if (object !== 'sys_position') return;
36+
const r = rows.find((x) => x.id === data.id);
37+
if (r) Object.assign(r, data);
38+
},
39+
};
40+
}
41+
42+
describe('bootstrapDeclaredPositions (#2909 T2 — seed-only semantics locked)', () => {
43+
it('inserts new declared positions with identity + display fields only', async () => {
44+
const ql = makeQl([{ name: 'contributor', label: 'Contributor', description: 'Does work' }]);
45+
const r = await bootstrapDeclaredPositions(ql, null);
46+
expect(r.seeded).toBe(1);
47+
const row = ql.rows[0];
48+
expect(row).toMatchObject({ name: 'contributor', label: 'Contributor', active: true, is_default: false });
49+
// Provenance is NOT stamped by the declared seeder (bootstrapBuiltinRoles
50+
// owns the built-in anchors; declared positions carry the object default).
51+
expect(row.managed_by).toBeUndefined();
52+
});
53+
54+
it('refreshes ONLY label/description on existing rows', async () => {
55+
const ql = makeQl([{ name: 'contributor', label: 'Contributor v2', description: 'new text' }]);
56+
ql.rows.push({
57+
id: 'pos_1', name: 'contributor', label: 'Contributor', description: 'old',
58+
active: true, is_default: false,
59+
});
60+
const r = await bootstrapDeclaredPositions(ql, null);
61+
expect(r.updated).toBe(1);
62+
expect(ql.rows[0].label).toBe('Contributor v2');
63+
expect(ql.rows[0].description).toBe('new text');
64+
});
65+
66+
it('NEVER touches authoritative record fields (active/is_default/delegatable/managed_by)', async () => {
67+
const ql = makeQl([{ name: 'contributor', label: 'Contributor v2' }]);
68+
// Admin turned the position off, made it default, marked it delegatable,
69+
// and the row carries provenance — a re-seed must not reset any of it.
70+
ql.rows.push({
71+
id: 'pos_1', name: 'contributor', label: 'Contributor', description: 'old',
72+
active: false, is_default: true, delegatable: true, managed_by: 'package',
73+
permissions: ['something_admin_set'],
74+
});
75+
await bootstrapDeclaredPositions(ql, null);
76+
const row = ql.rows[0];
77+
expect(row.active).toBe(false);
78+
expect(row.is_default).toBe(true);
79+
expect(row.delegatable).toBe(true);
80+
expect(row.managed_by).toBe('package');
81+
expect(row.permissions).toEqual(['something_admin_set']);
82+
// …while the display fields did refresh.
83+
expect(row.label).toBe('Contributor v2');
84+
});
85+
86+
it('is idempotent — a re-run inserts nothing new', async () => {
87+
const ql = makeQl([{ name: 'a', label: 'A' }, { name: 'b', label: 'B' }]);
88+
const r1 = await bootstrapDeclaredPositions(ql, null);
89+
expect(r1.seeded).toBe(2);
90+
const r2 = await bootstrapDeclaredPositions(ql, null);
91+
expect(r2.seeded).toBe(0);
92+
expect(ql.rows).toHaveLength(2);
93+
});
94+
});

0 commit comments

Comments
 (0)