@consiliency/agent-board-schema 1.23.0 → 1.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -1
- package/fixtures/already-applied.json +12 -0
- package/migrations/20260905000100_agent_board_consent_gating_reconcile.sql +151 -0
- package/migrations/20260906000100_agent_board_enroll_resumption.sql +749 -0
- package/migrations/20260907000100_agent_board_signed_authority_provenance.sql +243 -0
- package/package.json +1 -1
- package/schema-manifest.json +30 -3
- package/src/check.js +23 -0
- package/src/index.js +20 -0
- package/src/plan.js +79 -5
- package/src/tooling.test.mjs +237 -4
- package/tests/agent_board_atomic_fleet_onboarding.test.sql +124 -112
- package/tests/agent_board_claimkey.test.sql +25 -3
- package/tests/agent_board_consent_emission.test.sql +17 -13
- package/tests/agent_board_consent_gating.test.sql +55 -11
- package/tests/agent_board_delegation_policies.test.sql +3 -1
- package/tests/agent_board_enroll_resumption.test.sql +412 -0
- package/tests/agent_board_federated_lock_order_class.test.sql +1 -3
- package/tests/agent_board_fedsession_provider_absence_gate.test.sql +9 -6
- package/tests/agent_board_fleet_machine_reactivation.test.sql +56 -5
- package/tests/agent_board_mint_harden.test.sql +39 -4
- package/tests/agent_board_outbox_wake_ordering.test.sql +27 -12
- package/tests/agent_board_rejectsem.test.sql +1 -1
- package/tests/agent_board_rolereset.test.sql +23 -2
- package/tests/agent_board_rowid.test.sql +24 -13
- package/tests/agent_board_scoped_send_regrant.test.sql +8 -4
- package/tests/agent_board_signed_authority_provenance.test.sql +454 -0
- package/tests/agent_board_source_strip_helper.test.sql +285 -0
- package/tests/agent_board_wake_recovery.test.sql +58 -40
- package/tests/catalog/p_metadata_parameters.json +689 -0
- package/tests/fixtures/test-prereqs.sql +313 -0
- package/tests/isolation/expected/atomic_fleet_lifecycle_races.out +3 -3
package/src/tooling.test.mjs
CHANGED
|
@@ -5,7 +5,8 @@ import { join } from 'node:path';
|
|
|
5
5
|
import { loadManifest, loadOrderedMigrations, sha256 } from './index.js';
|
|
6
6
|
import { materialize } from './materialize.js';
|
|
7
7
|
import { plan } from './plan.js';
|
|
8
|
-
import { check, EXIT } from './check.js';
|
|
8
|
+
import { check, EXIT, POSTURE_ACTIONS } from './check.js';
|
|
9
|
+
import { COLLISION_REMEDIATIONS } from './index.js';
|
|
9
10
|
|
|
10
11
|
const manifest = loadManifest();
|
|
11
12
|
const ordered = loadOrderedMigrations();
|
|
@@ -37,9 +38,9 @@ describe('manifest integrity', () => {
|
|
|
37
38
|
expect(manifest.schemaVersion).toBe(8);
|
|
38
39
|
expect(manifest.minClientVersion).toMatch(/^\d+\.\d+\.\d+$/);
|
|
39
40
|
});
|
|
40
|
-
it('registers the four MBAUTHZ + four MBSENDAUTHZ + four MBSESSION + four MBDELEG + three MBFED + three MBHARDEN + two MBPORTALGATE + two MBPORTAL-PR32 + two ROOMOWN + two ROOMLINK + one ROOMDELEG + one LOCALAUTH + one ROOMUIAPI + one FEDSESSION + one FEDSESSION-AUTOJOIN + one GATENULL + one MEMBSTATE + one AGENTSDIR + one LEAVEIDEM + one ROLERESET + one MBROWID + one MBHYGIENE + one MBCONSENT-A + one MBCONSENT-B(a) + one MBCONSENT-B(i) + one MBCONSENT-B(ii) + one MBCONSENT-B(iii) + one MBHARDEN-#97 + one MBHARDEN-#118 + one INVITEPOLICY + one OBSGATE + one OBSGATE-2 + one OBSGATE-3 + one OBSGATE-4 + one FLEETMACHINERLS + one RESOLVENOOP + one ROOMDELEG-LANEB + one CLAIMKEY + two MINTHARDEN + one FLEETMACHINEHIJACK + one CLAIMGATE-SHADOW + one CLAIMGATE-ENFORCE + one FLEETMACHINEREACTIVATION + one DELEGCAPCEILING + one FLEETMACHINERECLAIM + one DELEGSOLEWRITER + one FLEETDOMAINRECLAIM + five ATOMICFLEETONBOARDING (onboarding + ceiling-lock + revocation-evidence + fedsession-gate + fedsession-lock-order) + one OUTBOXWAKE + one ENROLLLOCKORDER + one FEDPROVIDERABSENCE + one WAKERECOVERY forward migrations (migrationCount = prior 113 + 5 + 1 + 1 + 1 + 1 =
|
|
41
|
+
it('registers the four MBAUTHZ + four MBSENDAUTHZ + four MBSESSION + four MBDELEG + three MBFED + three MBHARDEN + two MBPORTALGATE + two MBPORTAL-PR32 + two ROOMOWN + two ROOMLINK + one ROOMDELEG + one LOCALAUTH + one ROOMUIAPI + one FEDSESSION + one FEDSESSION-AUTOJOIN + one GATENULL + one MEMBSTATE + one AGENTSDIR + one LEAVEIDEM + one ROLERESET + one MBROWID + one MBHYGIENE + one MBCONSENT-A + one MBCONSENT-B(a) + one MBCONSENT-B(i) + one MBCONSENT-B(ii) + one MBCONSENT-B(iii) + one MBHARDEN-#97 + one MBHARDEN-#118 + one INVITEPOLICY + one OBSGATE + one OBSGATE-2 + one OBSGATE-3 + one OBSGATE-4 + one FLEETMACHINERLS + one RESOLVENOOP + one ROOMDELEG-LANEB + one CLAIMKEY + two MINTHARDEN + one FLEETMACHINEHIJACK + one CLAIMGATE-SHADOW + one CLAIMGATE-ENFORCE + one FLEETMACHINEREACTIVATION + one DELEGCAPCEILING + one FLEETMACHINERECLAIM + one DELEGSOLEWRITER + one FLEETDOMAINRECLAIM + five ATOMICFLEETONBOARDING (onboarding + ceiling-lock + revocation-evidence + fedsession-gate + fedsession-lock-order) + one OUTBOXWAKE + one ENROLLLOCKORDER + one FEDPROVIDERABSENCE + one WAKERECOVERY + one CONSENTRECONCILE + one ENROLLRESUMPTION + one SIGNEDAUTHORITY forward migrations (migrationCount = prior 113 + 5 + 1 + 1 + 1 + 1 + 1 + 1 + 1 = 125)', () => {
|
|
41
42
|
expect(manifest.migrationCount).toBe(manifest.migrations.length);
|
|
42
|
-
expect(manifest.migrationCount).toBe(
|
|
43
|
+
expect(manifest.migrationCount).toBe(125);
|
|
43
44
|
// MBSESSION switched enforcement on (scoped-send re-grant): the client-visible
|
|
44
45
|
// schema version bumped to 8. MBDELEG tightens delegation and MBFED adds only
|
|
45
46
|
// enrollment-only additive surface (identity_providers registry, enrollment audit,
|
|
@@ -199,7 +200,239 @@ describe('plan / check postures + exit codes (SemVer contract)', () => {
|
|
|
199
200
|
expect(check({ applied }).exitCode).toBe(11);
|
|
200
201
|
});
|
|
201
202
|
it('exit-code map is a frozen SemVer surface', () => {
|
|
202
|
-
expect(EXIT).toEqual({ exact: 0, behind: 10, ahead: 11, divergent: 12, error: 13 });
|
|
203
|
+
expect(EXIT).toEqual({ exact: 0, behind: 10, ahead: 11, divergent: 12, error: 13, collision: 14 });
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// VERSION COLLISION (message-board#217). Supabase keys schema_migrations on the
|
|
207
|
+
// 14-digit version prefix alone, so an adopter migration authored at a bundle
|
|
208
|
+
// migration's version makes the bundle's file skip SILENTLY -- no error, and
|
|
209
|
+
// schema_version still reports the bundle's number. The reported case is real:
|
|
210
|
+
// consiliency-portal recorded 20260809000100 as reconcile_panel_schema_drift.
|
|
211
|
+
// The adopter's own migration occupies the bundle's version, so the bundle's
|
|
212
|
+
// file was skipped. Two DISTINCT states matter, and conflating them is the
|
|
213
|
+
// defect a review seat caught: the collision ROW is permanent (Supabase keys
|
|
214
|
+
// schema_migrations on version, which is its primary key), so reporting on the
|
|
215
|
+
// row alone would fail a fully-healed database forever -- and an adopter forced
|
|
216
|
+
// to ignore exit 14 stops seeing genuine behind/divergent states too.
|
|
217
|
+
const portalHistory = (opts = {}) => ({
|
|
218
|
+
applied: [
|
|
219
|
+
...ordered
|
|
220
|
+
.filter((m) => m.id !== '20260809000100_agent_board_consent_gating')
|
|
221
|
+
.filter((m) => opts.healed || m.id !== '20260905000100_agent_board_consent_gating_reconcile')
|
|
222
|
+
.map((m) => ({ id: m.id, sha256: m.sha256 })),
|
|
223
|
+
{ id: '20260809000100_reconcile_panel_schema_drift', sha256: 'aa' },
|
|
224
|
+
],
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('collision when an adopter migration occupies a bundle version and is unhealed', () => {
|
|
228
|
+
const c = check(portalHistory());
|
|
229
|
+
expect(c.posture).toBe('collision');
|
|
230
|
+
expect(c.exitCode).toBe(14);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('collision names BOTH sides, so the report is an instruction not a puzzle', () => {
|
|
234
|
+
expect(plan(portalHistory()).collisions).toEqual([
|
|
235
|
+
{
|
|
236
|
+
version: '20260809000100',
|
|
237
|
+
expected: '20260809000100_agent_board_consent_gating',
|
|
238
|
+
recordedAs: '20260809000100_reconcile_panel_schema_drift',
|
|
239
|
+
remediatedBy: '20260905000100_agent_board_consent_gating_reconcile',
|
|
240
|
+
},
|
|
241
|
+
]);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('collision outranks behind — "behind" invites re-applying, which is the no-op that leaves the DB wrong', () => {
|
|
245
|
+
const p = plan(portalHistory());
|
|
246
|
+
expect(p.perId.filter((r) => r.action === 'pending').map((r) => r.id)).toContain(
|
|
247
|
+
'20260809000100_agent_board_consent_gating',
|
|
248
|
+
);
|
|
249
|
+
expect(p.posture).toBe('collision');
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('a HEALED database reports exact/0 — not merely "not collision"', () => {
|
|
253
|
+
// Asserting "not collision / not 14" was too weak and a review seat broke it:
|
|
254
|
+
// both halves of a remediated pair are PERMANENT (the bundle migration can
|
|
255
|
+
// never be applied on that database because its version is taken, and the
|
|
256
|
+
// adopter's row can never be removed), so leaving them in the counts gated
|
|
257
|
+
// the healed database at behind/10 forever — a different permanent red than
|
|
258
|
+
// the one being fixed. The claim is exact/0, so that is what is asserted.
|
|
259
|
+
const p = plan(portalHistory({ healed: true }));
|
|
260
|
+
expect(p.collisions).toEqual([]);
|
|
261
|
+
expect(p.collisionsRemediated.map((c) => c.expected)).toEqual([
|
|
262
|
+
'20260809000100_agent_board_consent_gating',
|
|
263
|
+
]);
|
|
264
|
+
expect(p.counts.pending ?? 0).toBe(0);
|
|
265
|
+
expect(p.ahead).toEqual([]);
|
|
266
|
+
expect(p.posture).toBe('exact');
|
|
267
|
+
expect(check(portalHistory({ healed: true })).exitCode).toBe(0);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('a NULL migration name is indeterminate, not a collision', () => {
|
|
271
|
+
// schema_migrations.name is nullable, and the documented psql recipe renders
|
|
272
|
+
// such a row as `<version>_`. A substring test for '_' called that a named
|
|
273
|
+
// row and invented a collision from a legitimate history.
|
|
274
|
+
const applied = [
|
|
275
|
+
...ordered
|
|
276
|
+
.filter((m) => m.id !== '20260809000100_agent_board_consent_gating')
|
|
277
|
+
.map((m) => ({ id: m.id, sha256: m.sha256 })),
|
|
278
|
+
{ id: '20260809000100_', sha256: 'aa' },
|
|
279
|
+
];
|
|
280
|
+
const p = plan({ applied });
|
|
281
|
+
expect(p.collisions).toEqual([]);
|
|
282
|
+
expect(check({ applied }).exitCode).toBe(10); // behind, which is true: the DDL is absent
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('a DIVERGENT healer does not remediate — it is not the migration we shipped', () => {
|
|
286
|
+
// The database applied something under the healer's id that is not this
|
|
287
|
+
// bundle's bytes, so nothing here can claim the skipped DDL was restored.
|
|
288
|
+
// Posture already goes divergent, but reporting the collision as REMEDIATED
|
|
289
|
+
// would be the same false-success the detector exists to prevent.
|
|
290
|
+
const healer = '20260905000100_agent_board_consent_gating_reconcile';
|
|
291
|
+
const applied = [
|
|
292
|
+
...ordered
|
|
293
|
+
.filter((m) => m.id !== '20260809000100_agent_board_consent_gating')
|
|
294
|
+
.map((m) => ({ id: m.id, sha256: m.id === healer ? 'deadbeef' : m.sha256 })),
|
|
295
|
+
{ id: '20260809000100_reconcile_panel_schema_drift', sha256: 'aa' },
|
|
296
|
+
];
|
|
297
|
+
const p = plan({ applied });
|
|
298
|
+
expect(p.collisionsRemediated).toEqual([]);
|
|
299
|
+
expect(p.collisions).toHaveLength(1);
|
|
300
|
+
expect(p.posture).toBe('collision');
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('an UNKNOWN healer digest still remediates — digest-less histories are supported', () => {
|
|
304
|
+
// normalizeHistory accepts entries without a digest. Requiring one would
|
|
305
|
+
// strand those adopters in the permanent red this design removes.
|
|
306
|
+
const applied = [
|
|
307
|
+
...ordered.filter((m) => m.id !== '20260809000100_agent_board_consent_gating').map((m) => m.id),
|
|
308
|
+
'20260809000100_reconcile_panel_schema_drift',
|
|
309
|
+
];
|
|
310
|
+
const p = plan({ applied });
|
|
311
|
+
expect(p.collisionsRemediated).toHaveLength(1);
|
|
312
|
+
expect(p.collisions).toEqual([]);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('the healer being present is what clears it — not merely being declared', () => {
|
|
316
|
+
// Same declaration, healer unapplied: still live. Without this the previous
|
|
317
|
+
// test would pass on a map lookup rather than on the database's actual state.
|
|
318
|
+
expect(plan(portalHistory()).collisionsRemediated).toEqual([]);
|
|
319
|
+
expect(plan(portalHistory()).collisions).toHaveLength(1);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('a bare-version history is indeterminate, not one collision per bundled migration', () => {
|
|
323
|
+
// normalizeHistory accepts plain version strings. Such an entry carries no
|
|
324
|
+
// name, so it cannot be told apart from the bundle's own migration at that
|
|
325
|
+
// version — reporting it as a collision turns a supported input shape into a
|
|
326
|
+
// wall of false positives.
|
|
327
|
+
const p = plan({ applied: ordered.map((m) => m.id.split('_')[0]) });
|
|
328
|
+
expect(p.collisions).toEqual([]);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('an unrelated adopter migration is NOT a collision, even with a bundle migration pending', () => {
|
|
332
|
+
// The history MUST have something pending as well as something ahead. A first
|
|
333
|
+
// draft of this test used an exact history plus one extra entry, and a mutation
|
|
334
|
+
// that reported every ahead entry as a collision SURVIVED it: with nothing
|
|
335
|
+
// pending there is no cross-product to over-report, so the assertion passed
|
|
336
|
+
// while checking nothing. A negative assertion driven by a clean input proves
|
|
337
|
+
// nothing (CLAUDE.md rule 2).
|
|
338
|
+
const applied = [
|
|
339
|
+
...ordered
|
|
340
|
+
.filter((m) => m.id !== '20260809000100_agent_board_consent_gating')
|
|
341
|
+
.map((m) => ({ id: m.id, sha256: m.sha256 })),
|
|
342
|
+
{ id: '99999999999999_extra', sha256: 'ff' },
|
|
343
|
+
];
|
|
344
|
+
const p = plan({ applied });
|
|
345
|
+
expect(p.perId.some((r) => r.action === 'pending')).toBe(true); // the input is not clean
|
|
346
|
+
expect(p.ahead).toContain('99999999999999_extra');
|
|
347
|
+
expect(p.collisions).toEqual([]);
|
|
348
|
+
expect(p.posture).toBe('behind');
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// OPERATOR GUIDANCE. Checks EXIT and POSTURE_ACTIONS agree with each other.
|
|
353
|
+
//
|
|
354
|
+
// This is NOT the completeness guard, and an earlier version of this comment
|
|
355
|
+
// claimed it was. EXIT is a SECOND hand list, not the catalog: a posture added to
|
|
356
|
+
// plan() without an EXIT entry passes everything here, falls through check()'s
|
|
357
|
+
// `EXIT[p.posture] ?? EXIT.error` to exit 13, and prints no action. The real
|
|
358
|
+
// catalog is the set of values plan() assigns, and scripts/check-posture-consumers.mjs
|
|
359
|
+
// reads it from there -- and also holds the live-drift consumer to using
|
|
360
|
+
// POSTURE_ACTIONS, which no test in this package can see. That script runs in
|
|
361
|
+
// `schema:verify`. What follows is the in-package half: the two tables stay
|
|
362
|
+
// consistent, and the collision action never tells an operator to re-apply.
|
|
363
|
+
describe('every posture has operator guidance', () => {
|
|
364
|
+
const postures = Object.keys(EXIT).filter((k) => k !== 'error');
|
|
365
|
+
|
|
366
|
+
it('EXIT and POSTURE_ACTIONS cover the same postures', () => {
|
|
367
|
+
expect(Object.keys(POSTURE_ACTIONS).sort()).toEqual(postures.sort());
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it('every non-exact posture has a non-empty action', () => {
|
|
371
|
+
for (const posture of postures) {
|
|
372
|
+
if (posture === 'exact') {
|
|
373
|
+
expect(POSTURE_ACTIONS.exact).toBeNull(); // nothing to do is not a missing action
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
expect(typeof POSTURE_ACTIONS[posture]).toBe('string');
|
|
377
|
+
expect(POSTURE_ACTIONS[posture].length).toBeGreaterThan(20);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it("the collision action does NOT tell an operator to re-apply", () => {
|
|
382
|
+
// Re-applying is the no-op that leaves the database wrong: the version is taken.
|
|
383
|
+
expect(POSTURE_ACTIONS.collision).toMatch(/no-op|not simply re-apply|Do NOT/i);
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// COLLISION REMEDIATION (message-board#217). The reconcile migration's whole
|
|
388
|
+
// no-op-on-a-healthy-database property rests on its join_board body being
|
|
389
|
+
// byte-identical to the one in the migration an affected database skipped. The
|
|
390
|
+
// PR asserted that in prose and nothing checked it -- the exact shape this repo
|
|
391
|
+
// keeps getting caught by. The digest tests freeze each file's bytes, which is
|
|
392
|
+
// not the same claim: two files can both be frozen and still differ.
|
|
393
|
+
describe('collision remediation is real, not merely declared', () => {
|
|
394
|
+
const read = (id) =>
|
|
395
|
+
readFileSync(new URL(`../migrations/${id}.sql`, import.meta.url), 'utf8');
|
|
396
|
+
const joinBoardBlock = (sql) => {
|
|
397
|
+
const start = sql.search(/create or replace function agent_board\.join_board\b/i);
|
|
398
|
+
const rest = sql.slice(start + 10);
|
|
399
|
+
const next = rest.search(/create or replace function agent_board\./i);
|
|
400
|
+
return (next === -1 ? rest : rest.slice(0, next)).trimEnd();
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
it('every declared remediation names migrations that exist in the bundle', () => {
|
|
404
|
+
const ids = new Set(ordered.map((m) => m.id));
|
|
405
|
+
for (const [skipped, healer] of Object.entries(COLLISION_REMEDIATIONS)) {
|
|
406
|
+
expect(ids.has(skipped)).toBe(true);
|
|
407
|
+
expect(ids.has(healer)).toBe(true);
|
|
408
|
+
// The healer must come LATER, or it cannot run after the skip.
|
|
409
|
+
expect(healer > skipped).toBe(true);
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('the healer restates the skipped function BYTE-IDENTICALLY, so a healthy DB is a true no-op', () => {
|
|
414
|
+
const skipped = joinBoardBlock(read('20260809000100_agent_board_consent_gating'));
|
|
415
|
+
const healer = joinBoardBlock(read('20260905000100_agent_board_consent_gating_reconcile'));
|
|
416
|
+
expect(healer.length).toBeGreaterThan(1000);
|
|
417
|
+
expect(healer).toBe(skipped);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it('the healer touches ONLY the stale function, so it cannot clobber a later definition', () => {
|
|
421
|
+
// resolve_board_request and update_board_membership are superseded by
|
|
422
|
+
// 20260819000100 and 20260813000100. Restating them here would re-apply the
|
|
423
|
+
// older bodies over the newer ones on every healthy database.
|
|
424
|
+
const sql = read('20260905000100_agent_board_consent_gating_reconcile');
|
|
425
|
+
const defined = [...sql.matchAll(/create or replace function agent_board\.(\w+)/gi)].map((m) => m[1]);
|
|
426
|
+
expect(defined).toEqual(['join_board']);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it('the healer adds no grant, revoke or comment — create-or-replace preserves the ACL', () => {
|
|
430
|
+
const sql = read('20260905000100_agent_board_consent_gating_reconcile')
|
|
431
|
+
.split('\n').filter((l) => !l.trimStart().startsWith('--')).join('\n');
|
|
432
|
+
expect(sql).not.toMatch(/\bgrant\b/i);
|
|
433
|
+
expect(sql).not.toMatch(/\brevoke\b/i);
|
|
434
|
+
expect(sql).not.toMatch(/comment\s+on/i);
|
|
435
|
+
expect(sql).not.toMatch(/drop\s+function/i);
|
|
203
436
|
});
|
|
204
437
|
});
|
|
205
438
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
-- runtime backstop at all.
|
|
13
13
|
--
|
|
14
14
|
-- WHAT THE FIX IS. Each positional assertion now locates its markers on
|
|
15
|
-
-- `
|
|
15
|
+
-- `agent_board_test.stripped_body(<fn>)` -- prosrc with dollar-quoted blocks, single-quoted
|
|
16
16
|
-- literals, line comments and block comments removed in ONE alternation pass. The
|
|
17
17
|
-- regex is copied BYTE-FOR-BYTE from agent_board_federated_lock_order_class.test.sql,
|
|
18
18
|
-- where its three failure modes were found the hard way: a block comment whose body
|
|
@@ -123,24 +123,23 @@ create extension if not exists pgtap with schema extensions;
|
|
|
123
123
|
set search_path = extensions, public, agent_board;
|
|
124
124
|
|
|
125
125
|
begin;
|
|
126
|
-
select plan(
|
|
126
|
+
select plan(54);
|
|
127
127
|
|
|
128
128
|
-- ---------------------------------------------------------------------------
|
|
129
|
-
-- The stripped-body reader every positional assertion below uses
|
|
130
|
-
--
|
|
131
|
-
--
|
|
132
|
-
--
|
|
133
|
-
--
|
|
134
|
-
--
|
|
129
|
+
-- The stripped-body reader every positional assertion below uses is
|
|
130
|
+
-- agent_board_test.stripped_body(), defined ONCE for the whole suite in
|
|
131
|
+
-- tests/fixtures/test-prereqs.sql (the runner applies that fixture before any
|
|
132
|
+
-- test, so it needs no include path).
|
|
133
|
+
--
|
|
134
|
+
-- It used to be a local regex copied here and kept byte-identical to
|
|
135
|
+
-- agent_board_federated_lock_order_class.test.sql so the two could not drift.
|
|
136
|
+
-- They could not drift, and they were both WRONG: no regular expression can
|
|
137
|
+
-- match a NESTED block comment, and `/* note /* x */ <code> */` therefore hid
|
|
138
|
+
-- <code> from both files at once (message-board#199) -- 52/52 and 15/15 green
|
|
139
|
+
-- with the boards lock genuinely relocated. The shared helper is a character
|
|
140
|
+
-- scanner for that reason; see its header for the full enumeration of escape
|
|
141
|
+
-- hatches and which ones a regex can and cannot close.
|
|
135
142
|
-- ---------------------------------------------------------------------------
|
|
136
|
-
create function pg_temp._stripped_body(p_fn regprocedure) returns text
|
|
137
|
-
language sql stable as $strip$
|
|
138
|
-
select regexp_replace(p.prosrc,
|
|
139
|
-
$re$(\$[A-Za-z_0-9]*\$).*?\1|('([^']|'')*')|(--[^\n]*)|(/\*([^*]|\*+[^*/])*\*+/)$re$,
|
|
140
|
-
' ', 'g')
|
|
141
|
-
from pg_proc p
|
|
142
|
-
where p.oid = p_fn;
|
|
143
|
-
$strip$;
|
|
144
143
|
|
|
145
144
|
select has_table('agent_board', 'fleet_onboarding_invite_bounds', 'immutable fleet invite bounds table exists');
|
|
146
145
|
select has_pk('agent_board', 'fleet_onboarding_invite_bounds', 'fleet invite bounds are keyed by invite id');
|
|
@@ -248,38 +247,22 @@ select ok(
|
|
|
248
247
|
'binding revocation is a permanent tombstone'
|
|
249
248
|
);
|
|
250
249
|
select ok(
|
|
251
|
-
strpos(
|
|
252
|
-
pg_temp._stripped_body('agent_board.revoke_fleet_machine_agent_binding(uuid,uuid,text,jsonb)'::regprocedure),
|
|
253
|
-
'from agent_board.fleet_machines m'
|
|
250
|
+
strpos(lower(agent_board_test.stripped_body('agent_board.revoke_fleet_machine_agent_binding(uuid,uuid,text,jsonb)'::regprocedure)), 'from agent_board.fleet_machines m'
|
|
254
251
|
) > 0
|
|
255
|
-
and strpos(
|
|
256
|
-
pg_temp._stripped_body('agent_board.revoke_fleet_machine_agent_binding(uuid,uuid,text,jsonb)'::regprocedure),
|
|
257
|
-
'from agent_board.fleet_machine_agents b where b.id = p_binding_id for update'
|
|
252
|
+
and strpos(lower(agent_board_test.stripped_body('agent_board.revoke_fleet_machine_agent_binding(uuid,uuid,text,jsonb)'::regprocedure)), 'from agent_board.fleet_machine_agents b where b.id = p_binding_id for update'
|
|
258
253
|
) > 0
|
|
259
|
-
and strpos(
|
|
260
|
-
|
|
261
|
-
'from agent_board.fleet_machines m'
|
|
262
|
-
) < strpos(
|
|
263
|
-
pg_temp._stripped_body('agent_board.revoke_fleet_machine_agent_binding(uuid,uuid,text,jsonb)'::regprocedure),
|
|
264
|
-
'from agent_board.fleet_machine_agents b where b.id = p_binding_id for update'
|
|
254
|
+
and strpos(lower(agent_board_test.stripped_body('agent_board.revoke_fleet_machine_agent_binding(uuid,uuid,text,jsonb)'::regprocedure)), 'from agent_board.fleet_machines m'
|
|
255
|
+
) < strpos(lower(agent_board_test.stripped_body('agent_board.revoke_fleet_machine_agent_binding(uuid,uuid,text,jsonb)'::regprocedure)), 'from agent_board.fleet_machine_agents b where b.id = p_binding_id for update'
|
|
265
256
|
),
|
|
266
257
|
'exact binding revocation locks the machine before the binding [SOLE PIN: no isolation spec discriminates this ordering -- measured against all 20]'
|
|
267
258
|
);
|
|
268
259
|
select ok(
|
|
269
|
-
strpos(
|
|
270
|
-
pg_temp._stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure),
|
|
271
|
-
'where fm.id = p_machine_id for update'
|
|
260
|
+
strpos(lower(agent_board_test.stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure)), 'where fm.id = p_machine_id for update'
|
|
272
261
|
) > 0
|
|
273
|
-
and strpos(
|
|
274
|
-
pg_temp._stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure),
|
|
275
|
-
'from agent_board.agents a where a.id = v_enroll.agent_id for update'
|
|
262
|
+
and strpos(lower(agent_board_test.stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure)), 'from agent_board.agents a where a.id = v_enroll.agent_id for update'
|
|
276
263
|
) > 0
|
|
277
|
-
and strpos(
|
|
278
|
-
|
|
279
|
-
'where fm.id = p_machine_id for update'
|
|
280
|
-
) < strpos(
|
|
281
|
-
pg_temp._stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure),
|
|
282
|
-
'from agent_board.agents a where a.id = v_enroll.agent_id for update'
|
|
264
|
+
and strpos(lower(agent_board_test.stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure)), 'where fm.id = p_machine_id for update'
|
|
265
|
+
) < strpos(lower(agent_board_test.stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure)), 'from agent_board.agents a where a.id = v_enroll.agent_id for update'
|
|
283
266
|
),
|
|
284
267
|
'atomic onboarding locks the machine before its enrolled agent [CONFIRMATORY: atomic_fleet_binding_revoke.spec and atomic_fleet_lifecycle_races.spec both discriminate the inversion]'
|
|
285
268
|
);
|
|
@@ -299,7 +282,7 @@ select ok(
|
|
|
299
282
|
-- does not fail the claim.
|
|
300
283
|
select ok(
|
|
301
284
|
(with b as (
|
|
302
|
-
select
|
|
285
|
+
select agent_board_test.stripped_body(
|
|
303
286
|
'agent_board.revoke_fleet_subject(text,uuid,uuid,text,jsonb)'::regprocedure) as t),
|
|
304
287
|
p as (
|
|
305
288
|
select t,
|
|
@@ -318,20 +301,12 @@ select ok(
|
|
|
318
301
|
'agent fleet revocation takes its machine locks before the agent lock [SOLE PIN: no isolation spec discriminates this ordering -- measured against all 20]'
|
|
319
302
|
);
|
|
320
303
|
select ok(
|
|
321
|
-
strpos(
|
|
322
|
-
pg_temp._stripped_body('agent_board._revoke_agent_principal(uuid,uuid,text)'::regprocedure),
|
|
323
|
-
'from agent_board.agents a where a.id = p_agent_id for update'
|
|
304
|
+
strpos(lower(agent_board_test.stripped_body('agent_board._revoke_agent_principal(uuid,uuid,text)'::regprocedure)), 'from agent_board.agents a where a.id = p_agent_id for update'
|
|
324
305
|
) > 0
|
|
325
|
-
and strpos(
|
|
326
|
-
pg_temp._stripped_body('agent_board._revoke_agent_principal(uuid,uuid,text)'::regprocedure),
|
|
327
|
-
'from agent_board.agent_signing_keys'
|
|
306
|
+
and strpos(lower(agent_board_test.stripped_body('agent_board._revoke_agent_principal(uuid,uuid,text)'::regprocedure)), 'from agent_board.agent_signing_keys'
|
|
328
307
|
) > 0
|
|
329
|
-
and strpos(
|
|
330
|
-
|
|
331
|
-
'from agent_board.agents a where a.id = p_agent_id for update'
|
|
332
|
-
) < strpos(
|
|
333
|
-
pg_temp._stripped_body('agent_board._revoke_agent_principal(uuid,uuid,text)'::regprocedure),
|
|
334
|
-
'from agent_board.agent_signing_keys'
|
|
308
|
+
and strpos(lower(agent_board_test.stripped_body('agent_board._revoke_agent_principal(uuid,uuid,text)'::regprocedure)), 'from agent_board.agents a where a.id = p_agent_id for update'
|
|
309
|
+
) < strpos(lower(agent_board_test.stripped_body('agent_board._revoke_agent_principal(uuid,uuid,text)'::regprocedure)), 'from agent_board.agent_signing_keys'
|
|
335
310
|
),
|
|
336
311
|
'subtree primitive locks the agent before signing keys [SOLE PIN: no isolation spec discriminates this ordering -- measured against all 20]'
|
|
337
312
|
);
|
|
@@ -359,20 +334,12 @@ select ok(
|
|
|
359
334
|
-- not-found case fail instead of pass; the other eight positional claims in this
|
|
360
335
|
-- file already carried them.
|
|
361
336
|
select ok(
|
|
362
|
-
strpos(
|
|
363
|
-
pg_temp._stripped_body('agent_board.revoke_agent(uuid,text)'::regprocedure),
|
|
364
|
-
'update agent_board.agents'
|
|
337
|
+
strpos(lower(agent_board_test.stripped_body('agent_board.revoke_agent(uuid,text)'::regprocedure)), 'update agent_board.agents'
|
|
365
338
|
) > 0
|
|
366
|
-
and strpos(
|
|
367
|
-
pg_temp._stripped_body('agent_board.revoke_agent(uuid,text)'::regprocedure),
|
|
368
|
-
'update agent_board.agent_credentials'
|
|
339
|
+
and strpos(lower(agent_board_test.stripped_body('agent_board.revoke_agent(uuid,text)'::regprocedure)), 'update agent_board.agent_credentials'
|
|
369
340
|
) > 0
|
|
370
|
-
and strpos(
|
|
371
|
-
|
|
372
|
-
'update agent_board.agents'
|
|
373
|
-
) < strpos(
|
|
374
|
-
pg_temp._stripped_body('agent_board.revoke_agent(uuid,text)'::regprocedure),
|
|
375
|
-
'update agent_board.agent_credentials'
|
|
341
|
+
and strpos(lower(agent_board_test.stripped_body('agent_board.revoke_agent(uuid,text)'::regprocedure)), 'update agent_board.agents'
|
|
342
|
+
) < strpos(lower(agent_board_test.stripped_body('agent_board.revoke_agent(uuid,text)'::regprocedure)), 'update agent_board.agent_credentials'
|
|
376
343
|
),
|
|
377
344
|
'legacy authenticated revoke_agent preserves agent-before-credential ordering [HISTORICAL: legacy platform-admin path, no live fleet counterparty and no isolation spec]'
|
|
378
345
|
);
|
|
@@ -404,10 +371,18 @@ select ok(
|
|
|
404
371
|
like '%from auth.users u where u.id = v_owner_user_id for update%fleet_human_revocations%r.issuer = p_issuer and r.subject = p_subject%',
|
|
405
372
|
'identity enrollment locks the human owner and rejects durable total revocation'
|
|
406
373
|
);
|
|
374
|
+
-- LITERALS KEPT (the `true` second argument): every marker below is itself a
|
|
375
|
+
-- string literal in the body -- `''federation''`, `''object''`, the
|
|
376
|
+
-- `'{federation,replay,issuer}'` path, the raise message. Stripping literals here
|
|
377
|
+
-- would delete the very text being located and make the `not like` vacuously true
|
|
378
|
+
-- forever. Comments (all forms, INCLUDING NESTED) are still removed, so the
|
|
379
|
+
-- message-board#196 comment masks are closed; a literal mask is inherent to a
|
|
380
|
+
-- literal marker, which is why the isolation spec and not this line is the
|
|
381
|
+
-- load-bearing guard.
|
|
407
382
|
select ok(
|
|
408
|
-
|
|
383
|
+
agent_board_test.stripped_body('agent_board.revoke_human_subtree(uuid,uuid,text)'::regprocedure, true)
|
|
409
384
|
like '%from auth.users u where u.id = p_owner_user_id for update%where a.owner_user_id = p_owner_user_id%jsonb_typeof(a.metadata -> ''federation'') = ''object''%not exists (%from agent_board.enrollment_federation_audit e%where e.agent_id = a.id%stable federation evidence not found for every linked agent%insert into agent_board.fleet_human_revocations%issuer, subject, owner_user_id%on conflict (issuer, subject) do nothing%where owner_user_id = p_owner_user_id%order by id%'
|
|
410
|
-
and
|
|
385
|
+
and agent_board_test.stripped_body('agent_board.revoke_human_subtree(uuid,uuid,text)'::regprocedure, true)
|
|
411
386
|
not like '%metadata #>> ''{federation,replay,issuer}''%',
|
|
412
387
|
'total human revocation requires per-agent audit evidence before marking and enumeration'
|
|
413
388
|
);
|
|
@@ -458,16 +433,10 @@ select ok(
|
|
|
458
433
|
-- Ordering, not mere presence: the lock has to be held BEFORE the authorization
|
|
459
434
|
-- decision reads the (possibly absent) ceiling, and BEFORE the writer upserts it.
|
|
460
435
|
select ok(
|
|
461
|
-
strpos(
|
|
462
|
-
pg_temp._stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure),
|
|
463
|
-
'perform agent_board._lock_board_authority_ceiling(v_inv.board_id)'
|
|
436
|
+
strpos(lower(agent_board_test.stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure)), 'perform agent_board._lock_board_authority_ceiling(v_inv.board_id)'
|
|
464
437
|
) > 0
|
|
465
|
-
and strpos(
|
|
466
|
-
|
|
467
|
-
'perform agent_board._lock_board_authority_ceiling(v_inv.board_id)'
|
|
468
|
-
) < strpos(
|
|
469
|
-
pg_temp._stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure),
|
|
470
|
-
'agent_board._grant_within_ceiling('
|
|
438
|
+
and strpos(lower(agent_board_test.stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure)), 'perform agent_board._lock_board_authority_ceiling(v_inv.board_id)'
|
|
439
|
+
) < strpos(lower(agent_board_test.stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure)), 'agent_board._grant_within_ceiling('
|
|
471
440
|
),
|
|
472
441
|
'atomic onboarding holds the board ceiling lock before evaluating creator authority [REDUNDANT: implied by the advisory-before-boards and boards-before-ceiling-read claims below; an advisory moved between those two points violates the real ordering and still passes THIS one]'
|
|
473
442
|
);
|
|
@@ -479,16 +448,10 @@ select ok(
|
|
|
479
448
|
-- that catches the inversion; it is deliberately separate from the "before the
|
|
480
449
|
-- ceiling read" assertion below, because that one passes in BOTH orders.
|
|
481
450
|
select ok(
|
|
482
|
-
strpos(
|
|
483
|
-
pg_temp._stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure),
|
|
484
|
-
'perform agent_board._lock_board_authority_ceiling(v_inv.board_id)'
|
|
451
|
+
strpos(lower(agent_board_test.stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure)), 'perform agent_board._lock_board_authority_ceiling(v_inv.board_id)'
|
|
485
452
|
) > 0
|
|
486
|
-
and strpos(
|
|
487
|
-
|
|
488
|
-
'perform agent_board._lock_board_authority_ceiling(v_inv.board_id)'
|
|
489
|
-
) < strpos(
|
|
490
|
-
pg_temp._stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure),
|
|
491
|
-
'from agent_board.boards b
|
|
453
|
+
and strpos(lower(agent_board_test.stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure)), 'perform agent_board._lock_board_authority_ceiling(v_inv.board_id)'
|
|
454
|
+
) < strpos(lower(agent_board_test.stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure)), 'from agent_board.boards b
|
|
492
455
|
where b.id = v_inv.board_id
|
|
493
456
|
for update'
|
|
494
457
|
),
|
|
@@ -508,34 +471,22 @@ select ok(
|
|
|
508
471
|
-- ordering nothing else states, because the boards lock is taken for board liveness,
|
|
509
472
|
-- not for the ceiling. Pin the ordering so a refactor that moves it is not silent.
|
|
510
473
|
select ok(
|
|
511
|
-
strpos(
|
|
512
|
-
pg_temp._stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure),
|
|
513
|
-
'from agent_board.boards b
|
|
474
|
+
strpos(lower(agent_board_test.stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure)), 'from agent_board.boards b
|
|
514
475
|
where b.id = v_inv.board_id
|
|
515
476
|
for update'
|
|
516
477
|
) > 0
|
|
517
|
-
and strpos(
|
|
518
|
-
pg_temp._stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure),
|
|
519
|
-
'from agent_board.boards b
|
|
478
|
+
and strpos(lower(agent_board_test.stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure)), 'from agent_board.boards b
|
|
520
479
|
where b.id = v_inv.board_id
|
|
521
480
|
for update'
|
|
522
|
-
) < strpos(
|
|
523
|
-
pg_temp._stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure),
|
|
524
|
-
'agent_board._grant_within_ceiling('
|
|
481
|
+
) < strpos(lower(agent_board_test.stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure)), 'agent_board._grant_within_ceiling('
|
|
525
482
|
),
|
|
526
483
|
'atomic onboarding holds the boards row FOR UPDATE before it reads the ceiling [CONFIRMATORY FOR FLAT MASKS ONLY: agent_board_federated_lock_order_class.test.sql EC-3 fails by name on the comment/literal mutants, but it carries the SAME strip, so a NESTED block comment takes this assertion and EC-3 green together -- see #199 in the header; atomic_fleet_authority_races.spec pins the OUTCOME only when this lock and the advisory are BOTH gone, and atomic_fleet_board_archive.spec BLOCKS rather than failing by name]'
|
|
527
484
|
);
|
|
528
485
|
select ok(
|
|
529
|
-
strpos(
|
|
530
|
-
pg_temp._stripped_body('agent_board.set_board_authority_ceiling(uuid,text)'::regprocedure),
|
|
531
|
-
'perform agent_board._lock_board_authority_ceiling(p_board_id)'
|
|
486
|
+
strpos(lower(agent_board_test.stripped_body('agent_board.set_board_authority_ceiling(uuid,text,uuid)'::regprocedure)), 'perform agent_board._lock_board_authority_ceiling(p_board_id)'
|
|
532
487
|
) > 0
|
|
533
|
-
and strpos(
|
|
534
|
-
|
|
535
|
-
'perform agent_board._lock_board_authority_ceiling(p_board_id)'
|
|
536
|
-
) < strpos(
|
|
537
|
-
pg_temp._stripped_body('agent_board.set_board_authority_ceiling(uuid,text)'::regprocedure),
|
|
538
|
-
'insert into agent_board.board_authority_ceilings'
|
|
488
|
+
and strpos(lower(agent_board_test.stripped_body('agent_board.set_board_authority_ceiling(uuid,text,uuid)'::regprocedure)), 'perform agent_board._lock_board_authority_ceiling(p_board_id)'
|
|
489
|
+
) < strpos(lower(agent_board_test.stripped_body('agent_board.set_board_authority_ceiling(uuid,text,uuid)'::regprocedure)), 'insert into agent_board.board_authority_ceilings'
|
|
539
490
|
),
|
|
540
491
|
'the ceiling setter holds the same lock before its owner gate and upsert [BACKSTOP: tests/isolation/specs/ceiling_setter_lock_before_upsert.spec]'
|
|
541
492
|
);
|
|
@@ -550,12 +501,22 @@ select ok(
|
|
|
550
501
|
select is( -- pgtap-allow-raise-checked: catalog read only; the RPC names below are string literals in the expected array, never invoked
|
|
551
502
|
(select array_agg(entry order by entry) from (
|
|
552
503
|
select 'agent_board.' || regexp_replace(p.oid::regprocedure::text, '^([^(]*\.)?', '') || ' => ' ||
|
|
553
|
-
|
|
504
|
+
-- CALL SHAPE, not strpos on a bare name: this is the dangerous
|
|
505
|
+
-- direction -- a reader that DROPS the lock but keeps a variable named
|
|
506
|
+
-- after it would still print "serialized".
|
|
507
|
+
-- SCHEMA-QUALIFIED, because a bare-name anchor proves call SYNTAX, not callee
|
|
508
|
+
-- IDENTITY: `\m_lock_board_authority_ceiling\s*\(` also matches
|
|
509
|
+
-- `zz_other._lock_board_authority_ceiling(...)` (measured true). Every live call
|
|
510
|
+
-- site in agent_board is `agent_board.`-qualified, so requiring the
|
|
511
|
+
-- qualification costs nothing today and is fail-CLOSED if someone later writes
|
|
512
|
+
-- an unqualified call (it reads as NOT calling). Overload count is pinned
|
|
513
|
+
-- separately, so schema + name is the identity.
|
|
514
|
+
case when agent_board_test.stripped_body(p.oid) ~* '\magent_board\._lock_board_authority_ceiling\s*\('
|
|
554
515
|
then 'serialized' else 'UNSERIALIZED' end as entry
|
|
555
516
|
from pg_proc p
|
|
556
517
|
join pg_namespace n on n.oid = p.pronamespace
|
|
557
518
|
where n.nspname = 'agent_board'
|
|
558
|
-
and strpos(p.
|
|
519
|
+
and strpos(lower(agent_board_test.stripped_body(p.oid)), '_grant_within_ceiling') > 0
|
|
559
520
|
) readers),
|
|
560
521
|
array[
|
|
561
522
|
'agent_board._redeem_unbounded_invite_for_agent(text,uuid) => UNSERIALIZED',
|
|
@@ -594,11 +555,18 @@ select is( -- pgtap-allow-raise-checked: catalog read only; the RPC names below
|
|
|
594
555
|
from pg_proc p
|
|
595
556
|
join pg_namespace n on n.oid = p.pronamespace
|
|
596
557
|
where n.nspname = 'agent_board'
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
558
|
+
-- The first four markers are CODE (relation and function names), so the
|
|
559
|
+
-- body is read with literals stripped. The fifth is the advisory-lock key
|
|
560
|
+
-- prefix, which exists ONLY as a string literal
|
|
561
|
+
-- (`hashtextextended('agent_board.board_authority_ceilings:' || ...)`), so
|
|
562
|
+
-- that one predicate must KEEP literals or _lock_board_authority_ceiling --
|
|
563
|
+
-- the single function this migration creates -- silently leaves the class.
|
|
564
|
+
-- Comments are stripped in both modes.
|
|
565
|
+
and (strpos(lower(agent_board_test.stripped_body(p.oid)), 'fleet_onboarding_invite_bounds') > 0
|
|
566
|
+
or strpos(lower(agent_board_test.stripped_body(p.oid)), 'fleet_human_revocations') > 0
|
|
567
|
+
or strpos(lower(agent_board_test.stripped_body(p.oid)), 'fleet_revocation_events') > 0
|
|
568
|
+
or strpos(lower(agent_board_test.stripped_body(p.oid)), '_lock_board_authority_ceiling') > 0
|
|
569
|
+
or strpos(agent_board_test.stripped_body(p.oid, true), 'board_authority_ceilings:') > 0)
|
|
602
570
|
) definers),
|
|
603
571
|
array[
|
|
604
572
|
'agent_board._lock_board_authority_ceiling(uuid) [invoker] => service_role',
|
|
@@ -611,13 +579,57 @@ select is( -- pgtap-allow-raise-checked: catalog read only; the RPC names below
|
|
|
611
579
|
'agent_board.evaluate_fleet_sender_policy(uuid,uuid,uuid,uuid,text,uuid,boolean) [definer] => authenticated,service_role',
|
|
612
580
|
'agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text) [definer] => service_role',
|
|
613
581
|
'agent_board.reactivate_fleet_machine(uuid,uuid,text) [definer] => service_role',
|
|
614
|
-
'agent_board.reclaim_fleet_machine(uuid,uuid,uuid,text,uuid) [definer] => service_role',
|
|
615
582
|
'agent_board.revoke_fleet_subject(text,uuid,uuid,text,jsonb) [definer] => authenticated,service_role',
|
|
616
583
|
'agent_board.revoke_human_subtree(uuid,uuid,text) [definer] => service_role',
|
|
617
|
-
'agent_board.set_board_authority_ceiling(uuid,text) [definer] => authenticated,service_role'
|
|
584
|
+
'agent_board.set_board_authority_ceiling(uuid,text,uuid) [definer] => authenticated,service_role'
|
|
618
585
|
]::text[],
|
|
619
586
|
'every function on this migration set''s surface is declared with its definer flag and exact EXECUTE grantees'
|
|
620
587
|
);
|
|
621
588
|
|
|
589
|
+
-- WHAT THE message-board#196 CONVERSION REMOVED FROM THE CLASS ABOVE, and why
|
|
590
|
+
-- it is restated here instead of being silently dropped.
|
|
591
|
+
--
|
|
592
|
+
-- reclaim_fleet_machine was a member of that class while the predicate read RAW
|
|
593
|
+
-- prosrc -- and ONLY because a `--` comment in its body says "no
|
|
594
|
+
-- fleet_revocation_events row is written per binding". It was in the surface
|
|
595
|
+
-- class for SAYING IT IS NOT ON THE SURFACE. Reading the stripped body removes
|
|
596
|
+
-- it, which is correct: it touches none of the five surfaces.
|
|
597
|
+
--
|
|
598
|
+
-- Its exact-grantee pin was real coverage, though, and an accidental member's
|
|
599
|
+
-- coverage is still coverage. Per CLAUDE.md rule 1 an exemption is asserted
|
|
600
|
+
-- POSITIVELY, never by absence of a match, so the claim it used to carry is
|
|
601
|
+
-- restated as its own assertion.
|
|
602
|
+
select is(
|
|
603
|
+
(select coalesce((
|
|
604
|
+
select string_agg(distinct coalesce(pg_get_userbyid(nullif(a.grantee, 0)), 'PUBLIC'), ','
|
|
605
|
+
order by coalesce(pg_get_userbyid(nullif(a.grantee, 0)), 'PUBLIC'))
|
|
606
|
+
from aclexplode(p.proacl) a
|
|
607
|
+
where a.privilege_type = 'EXECUTE'
|
|
608
|
+
and a.grantee <> p.proowner), '(none)')
|
|
609
|
+
|| case when p.prosecdef then ' [definer]' else ' [invoker]' end
|
|
610
|
+
from pg_proc p
|
|
611
|
+
join pg_namespace n on n.oid = p.pronamespace
|
|
612
|
+
where n.nspname = 'agent_board'
|
|
613
|
+
-- identity by OID + identity-arguments, never by a rendered regprocedure:
|
|
614
|
+
-- this file puts agent_board on search_path, so `oid::regprocedure::text`
|
|
615
|
+
-- renders UNQUALIFIED and an 'agent_board.…' comparison silently matches
|
|
616
|
+
-- nothing (measured: this assertion read NULL until it was written this way).
|
|
617
|
+
and 'agent_board.' || regexp_replace(p.oid::regprocedure::text, '^([^(]*\.)?', '')
|
|
618
|
+
= 'agent_board.reclaim_fleet_machine(uuid,uuid,uuid,text,uuid)'),
|
|
619
|
+
'service_role [definer]',
|
|
620
|
+
'reclaim_fleet_machine -- off this surface once comments are stripped -- keeps its service_role-only definer grant'
|
|
621
|
+
);
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
-- The schema-qualified call anchor above treats `agent_board._lock_board_authority_ceiling` as an
|
|
625
|
+
-- IDENTITY. That is only true while the name resolves to exactly one function:
|
|
626
|
+
-- a second overload would be a different implementation the same anchor accepts.
|
|
627
|
+
-- Catalog-derived, so a new overload fails here and the count names itself.
|
|
628
|
+
select is(
|
|
629
|
+
(select count(*)::int from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
|
630
|
+
where n.nspname = 'agent_board' and p.proname = '_lock_board_authority_ceiling'),
|
|
631
|
+
1,
|
|
632
|
+
'agent_board._lock_board_authority_ceiling has exactly ONE overload, so the schema-qualified call anchor identifies it uniquely');
|
|
633
|
+
|
|
622
634
|
select * from finish();
|
|
623
635
|
rollback;
|