@consiliency/agent-board-schema 1.24.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.
Files changed (27) hide show
  1. package/fixtures/already-applied.json +8 -0
  2. package/migrations/20260906000100_agent_board_enroll_resumption.sql +749 -0
  3. package/migrations/20260907000100_agent_board_signed_authority_provenance.sql +243 -0
  4. package/package.json +1 -1
  5. package/schema-manifest.json +21 -3
  6. package/src/tooling.test.mjs +3 -3
  7. package/tests/agent_board_atomic_fleet_onboarding.test.sql +124 -112
  8. package/tests/agent_board_claimkey.test.sql +25 -3
  9. package/tests/agent_board_consent_emission.test.sql +17 -13
  10. package/tests/agent_board_consent_gating.test.sql +55 -11
  11. package/tests/agent_board_delegation_policies.test.sql +3 -1
  12. package/tests/agent_board_enroll_resumption.test.sql +412 -0
  13. package/tests/agent_board_federated_lock_order_class.test.sql +1 -3
  14. package/tests/agent_board_fedsession_provider_absence_gate.test.sql +9 -6
  15. package/tests/agent_board_fleet_machine_reactivation.test.sql +56 -5
  16. package/tests/agent_board_mint_harden.test.sql +39 -4
  17. package/tests/agent_board_outbox_wake_ordering.test.sql +27 -12
  18. package/tests/agent_board_rejectsem.test.sql +1 -1
  19. package/tests/agent_board_rolereset.test.sql +23 -2
  20. package/tests/agent_board_rowid.test.sql +24 -13
  21. package/tests/agent_board_scoped_send_regrant.test.sql +8 -4
  22. package/tests/agent_board_signed_authority_provenance.test.sql +454 -0
  23. package/tests/agent_board_source_strip_helper.test.sql +285 -0
  24. package/tests/agent_board_wake_recovery.test.sql +58 -40
  25. package/tests/catalog/p_metadata_parameters.json +689 -0
  26. package/tests/fixtures/test-prereqs.sql +313 -0
  27. package/tests/isolation/expected/atomic_fleet_lifecycle_races.out +3 -3
@@ -0,0 +1,412 @@
1
+ -- Consiliency/message-board#176 pgTAP: OWNER-SCOPED ENROLL RESUMPTION.
2
+ --
3
+ -- The live failure: /enroll committed an identity, post-enroll provisioning failed, the
4
+ -- client reported total failure, and every retry was then refused forever with
5
+ -- `23505 this signing key is already enrolled`. 20260901000100's replay wrapper resumes
6
+ -- only rows carrying its own `metadata.federation.replay` STAMP -- which the trapped
7
+ -- enrollments, created before it shipped, do not have. 20260906000100 decides resumption
8
+ -- from evidence written at the ORIGINAL enrollment instead, so an unstamped row resumes
9
+ -- too.
10
+ --
11
+ -- Both directions are pinned here, and the SECURITY direction is the one that matters:
12
+ -- * same verified (issuer, subject) -> the existing identity is RETURNED (R-1, R-2);
13
+ -- * a DIFFERENT principal -> still raises, and the message names no handle (R-3, R-4).
14
+ -- Every allow-side assertion is `lives_ok(...)` followed by a separate state check, never
15
+ -- a bare `is((select ... from rpc(...)), ...)`, which aborts instead of failing (#113).
16
+ --
17
+ -- Failing calls are executed through pg_temp.try(), a BEGIN/EXCEPTION subtransaction that
18
+ -- records (sqlstate, message) into `_err` and records an explicit '00000 / (no error
19
+ -- raised)' row when the call SUCCEEDS -- so "the raise disappeared" fails an assertion
20
+ -- rather than silently matching nothing.
21
+ --
22
+ -- EC-1/EC-2 are the class assertions: the route class is enumerated from pg_proc, not
23
+ -- from a list written here, so a new fingerprint-accepting route or a new signing-key
24
+ -- creator fails and NAMES ITSELF.
25
+ create extension if not exists pgtap with schema extensions;
26
+ set search_path = extensions, public, agent_board;
27
+
28
+ begin;
29
+
30
+ select plan(35);
31
+
32
+ -- ---------------------------------------------------------------------------
33
+ -- Fixtures
34
+ -- ---------------------------------------------------------------------------
35
+ create temp table _v (name text primary key, val text) on commit drop;
36
+ create temp table _e (
37
+ label text, agent_id uuid, auth_user_id uuid, provider_id text, signing_key_id uuid,
38
+ granted text[], key_id text, key_version int, handle text, invite_redeemed boolean
39
+ ) on commit drop;
40
+ create temp table _err (label text primary key, state text, msg text) on commit drop;
41
+
42
+ -- The four resumption RESULTS, declared UP FRONT with the RETURNS TABLE shape rather
43
+ -- than created by `create temp table ... as <the call>`. If the call under test raises,
44
+ -- `lives_ok` records the failure and the CTAS form would leave the table MISSING -- so
45
+ -- every following assertion would abort the transaction instead of failing, and pgTAP
46
+ -- would emit fewer assertions than the plan with no `not ok` to read (#113). Declared
47
+ -- empty, those assertions return NULL and fail honestly.
48
+ create temp table _r1 (
49
+ agent_id uuid, auth_user_id uuid, provider_id text, signing_key_id uuid,
50
+ granted_scopes text[], key_id text, key_version int, agent_handle text,
51
+ invite_redeemed boolean
52
+ ) on commit drop;
53
+ create temp table _r2 (like _r1) on commit drop;
54
+ create temp table _r6 (like _r1) on commit drop;
55
+ create temp table _r13 (like _r1) on commit drop;
56
+
57
+
58
+ create function pg_temp.v(p_name text) returns text language sql stable as
59
+ $$ select val from _v where name = p_name $$;
60
+
61
+ -- The exact /enroll call, as SQL text. `p_actor_sub` is null and the kind is 'agent'
62
+ -- throughout, so the only inputs that vary across cases are the ones under test.
63
+ create function pg_temp.enroll_sql(
64
+ p_iss text, p_sub text, p_slug text, p_pub text, p_fp text, p_scopes text default '{board:send}'
65
+ ) returns text language sql stable as
66
+ $$
67
+ select format(
68
+ $q$select * from agent_board.enroll_federated_identity(%L,'message-board',%L,%L::jsonb,%L,%L,'raw_ed25519_base64url',%L,true,null,'agent',%L,'{}')$q$,
69
+ p_iss, p_sub,
70
+ jsonb_build_object('iss', p_iss, 'aud', 'message-board', 'sub', p_sub)::text,
71
+ p_slug, p_pub, p_fp, p_scopes)
72
+ $$;
73
+
74
+ -- Run a call that is EXPECTED to raise, capturing sqlstate + message. A call that does
75
+ -- NOT raise records '00000', so a vanished raise fails loudly instead of matching an
76
+ -- absent row.
77
+ create function pg_temp.try(p_label text, p_sql text) returns void language plpgsql as
78
+ $$
79
+ begin
80
+ begin
81
+ execute p_sql;
82
+ insert into _err values (p_label, '00000', '(no error raised)');
83
+ exception when others then
84
+ insert into _err values (p_label, sqlstate, sqlerrm);
85
+ end;
86
+ end
87
+ $$;
88
+
89
+ do $$
90
+ declare
91
+ s text := replace(substr(gen_random_uuid()::text, 1, 8), '-', '');
92
+ v_iss1 text := 'https://res-a-' || replace(substr(gen_random_uuid()::text, 1, 8), '-', '') || '.test';
93
+ v_iss2 text;
94
+ v_pub text;
95
+ i int;
96
+ begin
97
+ v_iss1 := 'https://res-a-' || s || '.test';
98
+ v_iss2 := 'https://res-b-' || s || '.test';
99
+
100
+ insert into agent_board.identity_providers
101
+ (issuer, audience, supported, policy_approved, allowed_scopes, allowed_board_scopes, allowed_agent_kinds)
102
+ values
103
+ (v_iss1, 'message-board', true, true, '{board:send,board:read}', '{board:x}', '{agent,human}'),
104
+ (v_iss2, 'message-board', true, true, '{board:send,board:read}', '{board:x}', '{agent,human}');
105
+
106
+ insert into _v values
107
+ ('s', s), ('iss1', v_iss1), ('iss2', v_iss2),
108
+ ('suba', 'portal|a-' || s), ('subb', 'portal|b-' || s);
109
+
110
+ -- Eight real Ed25519-shaped keys: 32 random bytes re-encoded through the SAME
111
+ -- canonical base64url form the enroll contract requires, and their in-DB fingerprints.
112
+ for i in 1..8 loop
113
+ v_pub := rtrim(translate(encode(gen_random_bytes(32), 'base64'), '+/', '-_'), '=');
114
+ insert into _v values ('pub' || i, v_pub), ('fp' || i, agent_board._raw_key_fingerprint(v_pub));
115
+ end loop;
116
+ end
117
+ $$;
118
+
119
+ -- Eight enrollments. E1/E3..E8 are then stripped of their replay stamp, which is exactly
120
+ -- the state every identity enrolled before 20260901000100 is in -- the #176 victim's
121
+ -- state. E2 keeps its stamp.
122
+ do $$
123
+ declare
124
+ i int;
125
+ v_slug text;
126
+ begin
127
+ for i in 1..8 loop
128
+ v_slug := 'res-' || i || '-' || pg_temp.v('s');
129
+ -- E2 is the STAMPED fixture and the only one that needs two scopes: the frozen
130
+ -- contract compares them as JSONB, which preserves ORDER, and R-14 pins that.
131
+ execute format('insert into _e select %L, * from (%s) t', 'E' || i,
132
+ pg_temp.enroll_sql(pg_temp.v('iss1'), pg_temp.v('suba'), v_slug,
133
+ pg_temp.v('pub' || i), pg_temp.v('fp' || i),
134
+ case when i = 2 then '{board:send,board:read}' else '{board:send}' end));
135
+ end loop;
136
+
137
+ update agent_board.agents a
138
+ set metadata = a.metadata #- '{federation,replay}'
139
+ where a.id in (select agent_id from _e where label <> 'E2');
140
+
141
+ -- E3: owned, unstamped, but the agent is revoked -> must NOT be revived by resumption.
142
+ update agent_board.agents a set status = 'revoked' where a.id = (select agent_id from _e where label = 'E3');
143
+
144
+ -- E4: evidence (i) broken -- owner_user_id repointed at an unrelated auth user.
145
+ update agent_board.agents a
146
+ set owner_user_id = agent_board._ensure_agent_auth_user('res-unrelated-owner-' || pg_temp.v('s'))
147
+ where a.id = (select agent_id from _e where label = 'E4');
148
+
149
+ -- E5: evidence (ii) broken -- the creation-time federation subject is gone.
150
+ update agent_board.agents a
151
+ set metadata = a.metadata #- '{federation,subject}'
152
+ where a.id = (select agent_id from _e where label = 'E5');
153
+
154
+ -- E6: evidence (iii) broken -- the original enrollment's audit row is gone.
155
+ delete from agent_board.enrollment_federation_audit e
156
+ where e.agent_id = (select agent_id from _e where label = 'E6');
157
+
158
+ -- E7: audit row present but recording no granted scopes -> ownership holds, the
159
+ -- identity is not resumable. This is what makes branch (b) reachable rather than
160
+ -- implied by the ownership conjunction.
161
+ update agent_board.enrollment_federation_audit e
162
+ set granted_scopes = null
163
+ where e.agent_id = (select agent_id from _e where label = 'E7');
164
+
165
+ -- E8: the ONE exemption in the ownership conjunction, modelled on the atomic-fleet
166
+ -- backfill fixture -- an agent that predates the owner model and carries NO
167
+ -- owner_user_id. Evidence (i) is unavailable there rather than contradicted.
168
+ update agent_board.agents a set owner_user_id = null
169
+ where a.id = (select agent_id from _e where label = 'E8');
170
+ end
171
+ $$;
172
+
173
+ -- ---------------------------------------------------------------------------
174
+ -- EC-1/EC-2 -- the class, enumerated from the catalog
175
+ -- ---------------------------------------------------------------------------
176
+
177
+ -- Every route a client can re-present an enrolled key to, taken from pg_proc.proargnames.
178
+ -- A fourth such route added later fails this and names itself in the diff.
179
+ select is(
180
+ (select string_agg(p.proname, ', ' order by p.proname)
181
+ from pg_proc p
182
+ join pg_namespace n on n.oid = p.pronamespace
183
+ where n.nspname = 'agent_board'
184
+ and p.proargnames && array['p_agent_public_key_fingerprint']),
185
+ '_enroll_federated_identity_once, enroll_federated_identity, onboard_federated_fleet_agent',
186
+ 'EC-1: exactly three agent_board routes accept an enrolled key fingerprint (pg_proc-derived)');
187
+
188
+ -- ...and only two functions in the whole schema can CREATE a signing key, so
189
+ -- onboard_federated_fleet_agent necessarily reaches an already-enrolled key through
190
+ -- enroll_federated_identity rather than deciding the duplicate case itself.
191
+ select is(
192
+ (select string_agg(p.proname, ', ' order by p.proname)
193
+ from pg_proc p
194
+ join pg_namespace n on n.oid = p.pronamespace
195
+ where n.nspname = 'agent_board'
196
+ and p.prokind = 'f'
197
+ and agent_board_test.strip_sql(p.prosrc) ~* 'insert into agent_board\.agent_signing_keys'),
198
+ '_enroll_federated_identity_once, mint_delegated_child',
199
+ 'EC-2: only the enroll creator and delegated-child mint insert signing keys (strip_sql-derived)');
200
+
201
+ -- ---------------------------------------------------------------------------
202
+ -- R-1 -- THE #176 FIX: an unstamped enrollment resumes for its owner
203
+ -- ---------------------------------------------------------------------------
204
+ select lives_ok(
205
+ (select 'insert into _r1 ' || pg_temp.enroll_sql(
206
+ pg_temp.v('iss1'), pg_temp.v('suba'), 'res-1-' || pg_temp.v('s'),
207
+ pg_temp.v('pub1'), pg_temp.v('fp1'), '{board:send}')),
208
+ 'R-1: re-presenting an already-enrolled key as the same verified (issuer, subject) SUCCEEDS (unstamped row)');
209
+ select is(
210
+ (select agent_id from _r1),
211
+ (select agent_id from _e where label = 'E1'),
212
+ 'R-1: the resumption returns the EXISTING agent identity');
213
+ select is(
214
+ (select signing_key_id from _r1),
215
+ (select signing_key_id from _e where label = 'E1'),
216
+ 'R-1: the resumption returns the EXISTING signing key, not a new one');
217
+ select is(
218
+ (select agent_handle from _r1),
219
+ (select handle from _e where label = 'E1'),
220
+ 'R-1: the resumption returns the server-assigned handle the client lost');
221
+ select is(
222
+ (select provider_id from _r1),
223
+ (select provider_id from _e where label = 'E1'),
224
+ 'R-1: provider_id survives the unstamped path (it is read from the creation-time federation object)');
225
+ select is(
226
+ (select count(*)::int from agent_board.agent_signing_keys k where k.fingerprint = pg_temp.v('fp1')),
227
+ 1, 'R-1: the resumption enrolled no second key for that fingerprint');
228
+ select is(
229
+ (select count(*)::int from agent_board.agents a where a.id in (select agent_id from _e) or a.handle like 'res-1-%'),
230
+ 8, 'R-1: the resumption created no ninth agent');
231
+
232
+ -- ---------------------------------------------------------------------------
233
+ -- R-2 -- an unstamped resumption cannot ESCALATE
234
+ -- ---------------------------------------------------------------------------
235
+ select lives_ok(
236
+ (select 'insert into _r2 ' || pg_temp.enroll_sql(
237
+ pg_temp.v('iss1'), pg_temp.v('suba'), 'res-1-different-slug',
238
+ pg_temp.v('pub1'), pg_temp.v('fp1'), '{board:send,board:read}')),
239
+ 'R-2: an unstamped resumption tolerates a different slug and a wider scope request');
240
+ select is(
241
+ (select granted_scopes from _r2),
242
+ '{board:send}'::text[],
243
+ 'R-2: it returns the ORIGINAL granted scopes -- the wider request grants nothing');
244
+ select is(
245
+ (select agent_id from _r2),
246
+ (select agent_id from _e where label = 'E1'),
247
+ 'R-2: and it still returns the original identity, not a second one');
248
+
249
+ -- ---------------------------------------------------------------------------
250
+ -- R-3 / R-4 -- THE SECURITY DIRECTION: a different principal is still refused
251
+ -- ---------------------------------------------------------------------------
252
+ select pg_temp.try('squat-subject', (select pg_temp.enroll_sql(
253
+ pg_temp.v('iss1'), pg_temp.v('subb'), 'res-squat-' || pg_temp.v('s'),
254
+ pg_temp.v('pub1'), pg_temp.v('fp1'), '{board:send}')));
255
+ -- sqlstate ALONE does not pin this: with the ownership refusal bypassed the request
256
+ -- falls through to the state gate, which raises the SAME 23505 for its own reason. The
257
+ -- assertion therefore requires the OWNERSHIP refusal specifically.
258
+ select ok(
259
+ (select state = '23505' and msg like '%to a different principal%'
260
+ from _err where label = 'squat-subject'),
261
+ 'R-3: a key owned by a DIFFERENT subject at the same issuer is refused AS a different principal (23505)');
262
+ select ok(
263
+ (select position(h in msg) = 0 from _err, (select handle h from _e where label = 'E1') x where label = 'squat-subject'),
264
+ 'R-3: the refusal names NO handle -- a squatter learns nothing about who holds the key');
265
+ select is(
266
+ (select count(*)::int from agent_board.agent_signing_keys k where k.fingerprint = pg_temp.v('fp1')),
267
+ 1, 'R-3: the refused squat left exactly one key on that fingerprint');
268
+
269
+ select pg_temp.try('squat-issuer', (select pg_temp.enroll_sql(
270
+ pg_temp.v('iss2'), pg_temp.v('suba'), 'res-squat2-' || pg_temp.v('s'),
271
+ pg_temp.v('pub1'), pg_temp.v('fp1'), '{board:send}')));
272
+ select ok(
273
+ (select state = '23505' and msg like '%to a different principal%'
274
+ from _err where label = 'squat-issuer'),
275
+ 'R-4: the same subject at a DIFFERENT approved issuer is refused as a different principal');
276
+ select ok(
277
+ (select position(h in msg) = 0 from _err, (select handle h from _e where label = 'E1') x where label = 'squat-issuer'),
278
+ 'R-4: that refusal names no handle either');
279
+
280
+ -- ---------------------------------------------------------------------------
281
+ -- R-5 / R-6 -- a STAMPED enrollment keeps its frozen contract; only the message improves
282
+ -- ---------------------------------------------------------------------------
283
+ select pg_temp.try('frozen', (select pg_temp.enroll_sql(
284
+ pg_temp.v('iss1'), pg_temp.v('suba'), 'res-2-renamed',
285
+ pg_temp.v('pub2'), pg_temp.v('fp2'), '{board:send,board:read}')));
286
+ select is((select state from _err where label = 'frozen'), '23505',
287
+ 'R-5: a stamped enrollment re-presented by its owner with a DIFFERENT slug still raises (frozen contract intact)');
288
+ select ok(
289
+ (select position(h in msg) > 0 from _err, (select handle h from _e where label = 'E2') x where label = 'frozen'),
290
+ 'R-5: but the message now NAMES the existing handle');
291
+ select ok(
292
+ (select msg like '%need not be repeated%' from _err where label = 'frozen'),
293
+ 'R-5: and says enrollment need not be repeated -- the actionable half of the ask');
294
+
295
+ select lives_ok(
296
+ (select 'insert into _r6 ' || pg_temp.enroll_sql(
297
+ pg_temp.v('iss1'), pg_temp.v('suba'), 'res-2-' || pg_temp.v('s'),
298
+ pg_temp.v('pub2'), pg_temp.v('fp2'), '{board:send,board:read}')),
299
+ 'R-6: an EXACT stamped replay still succeeds (the released path is unregressed)');
300
+ select is(
301
+ (select agent_id from _r6),
302
+ (select agent_id from _e where label = 'E2'),
303
+ 'R-6: and still returns the original identity');
304
+
305
+ -- ---------------------------------------------------------------------------
306
+ -- R-7 -- resumption never revives an identity that is not usable
307
+ -- ---------------------------------------------------------------------------
308
+ select pg_temp.try('revoked', (select pg_temp.enroll_sql(
309
+ pg_temp.v('iss1'), pg_temp.v('suba'), 'res-3-' || pg_temp.v('s'),
310
+ pg_temp.v('pub3'), pg_temp.v('fp3'), '{board:send}')));
311
+ select is((select state from _err where label = 'revoked'), '23505',
312
+ 'R-7: an owned but REVOKED identity is not resumable');
313
+ select ok(
314
+ (select position(h in msg) > 0 from _err, (select handle h from _e where label = 'E3') x where label = 'revoked'),
315
+ 'R-7: its owner is told which handle is stuck');
316
+
317
+ -- ---------------------------------------------------------------------------
318
+ -- R-8/R-9/R-10 -- each of the three ownership evidences is load-bearing
319
+ -- ---------------------------------------------------------------------------
320
+ select pg_temp.try('no-owner-match', (select pg_temp.enroll_sql(
321
+ pg_temp.v('iss1'), pg_temp.v('suba'), 'res-4-' || pg_temp.v('s'),
322
+ pg_temp.v('pub4'), pg_temp.v('fp4'), '{board:send}')));
323
+ select ok(
324
+ (select state = '23505' and msg like '%to a different principal%'
325
+ from _err where label = 'no-owner-match'),
326
+ 'R-8: evidence (i) -- owner_user_id not equal to the derived owner reads as NOT owned');
327
+
328
+ select pg_temp.try('no-creation-stamp', (select pg_temp.enroll_sql(
329
+ pg_temp.v('iss1'), pg_temp.v('suba'), 'res-5-' || pg_temp.v('s'),
330
+ pg_temp.v('pub5'), pg_temp.v('fp5'), '{board:send}')));
331
+ select ok(
332
+ (select state = '23505' and msg like '%to a different principal%'
333
+ from _err where label = 'no-creation-stamp'),
334
+ 'R-9: evidence (ii) -- a missing creation-time federation subject reads as NOT owned (null is not a pass)');
335
+
336
+ select pg_temp.try('no-audit-row', (select pg_temp.enroll_sql(
337
+ pg_temp.v('iss1'), pg_temp.v('suba'), 'res-6-' || pg_temp.v('s'),
338
+ pg_temp.v('pub6'), pg_temp.v('fp6'), '{board:send}')));
339
+ select ok(
340
+ (select state = '23505' and msg like '%to a different principal%'
341
+ from _err where label = 'no-audit-row'),
342
+ 'R-10: evidence (iii) -- a missing enrollment audit row reads as NOT owned');
343
+
344
+ -- ---------------------------------------------------------------------------
345
+ -- R-11 -- branch (b) is reachable on its own, not merely implied by ownership
346
+ -- ---------------------------------------------------------------------------
347
+ select pg_temp.try('no-granted-scopes', (select pg_temp.enroll_sql(
348
+ pg_temp.v('iss1'), pg_temp.v('suba'), 'res-7-' || pg_temp.v('s'),
349
+ pg_temp.v('pub7'), pg_temp.v('fp7'), '{board:send}')));
350
+ select is((select state from _err where label = 'no-granted-scopes'), '23505',
351
+ 'R-11: an audit row recording NO granted scopes is owned but not resumable');
352
+ select ok(
353
+ (select position(h in msg) > 0 from _err, (select handle h from _e where label = 'E7') x where label = 'no-granted-scopes'),
354
+ 'R-11: and because ownership IS proven there, the owner is told the handle');
355
+
356
+ -- ---------------------------------------------------------------------------
357
+ -- R-12 -- the internal creator stays a hard refusal, but an actionable one
358
+ -- ---------------------------------------------------------------------------
359
+ select pg_temp.try('once-direct', (select format(
360
+ $q$select * from agent_board._enroll_federated_identity_once(%L,'message-board',%L,%L::jsonb,%L,%L,'raw_ed25519_base64url',%L,true,null,'agent','{board:send}','{}')$q$,
361
+ pg_temp.v('iss1'), pg_temp.v('suba'),
362
+ jsonb_build_object('iss', pg_temp.v('iss1'), 'aud', 'message-board', 'sub', pg_temp.v('suba'))::text,
363
+ 'res-once-' || pg_temp.v('s'), pg_temp.v('pub1'), pg_temp.v('fp1'))));
364
+ select is((select state from _err where label = 'once-direct'), '23505',
365
+ 'R-12: _enroll_federated_identity_once still refuses an already-enrolled key outright (no twin resumption)');
366
+ select ok(
367
+ (select position(h in msg) > 0 and msg like '%need not be repeated%'
368
+ from _err, (select handle h from _e where label = 'E1') x where label = 'once-direct'),
369
+ 'R-12: and its refusal now names the holder and says enrollment need not be repeated');
370
+
371
+ -- ---------------------------------------------------------------------------
372
+ -- R-13 -- the null-owner exemption, asserted POSITIVELY and in BOTH directions
373
+ -- ---------------------------------------------------------------------------
374
+ select lives_ok(
375
+ (select 'insert into _r13 ' || pg_temp.enroll_sql(
376
+ pg_temp.v('iss1'), pg_temp.v('suba'), 'res-8-' || pg_temp.v('s'),
377
+ pg_temp.v('pub8'), pg_temp.v('fp8'), '{board:send}')),
378
+ 'R-13: a row with NO owner_user_id (pre-owner-model, the atomic-fleet backfill shape) still resumes for its owner');
379
+ select is(
380
+ (select agent_id from _r13),
381
+ (select agent_id from _e where label = 'E8'),
382
+ 'R-13: and it resumes to that same pre-owner-model identity');
383
+ select pg_temp.try('null-owner-squat', (select pg_temp.enroll_sql(
384
+ pg_temp.v('iss1'), pg_temp.v('subb'), 'res-8-squat-' || pg_temp.v('s'),
385
+ pg_temp.v('pub8'), pg_temp.v('fp8'), '{board:send}')));
386
+ select ok(
387
+ (select state = '23505' and msg like '%to a different principal%'
388
+ from _err where label = 'null-owner-squat'),
389
+ 'R-13: the exemption does NOT open a squat -- the same null-owner row refuses a different subject as one');
390
+
391
+ -- ---------------------------------------------------------------------------
392
+ -- R-14 -- the frozen scope comparison is ORDER-SENSITIVE, and that is asserted
393
+ -- rather than assumed
394
+ -- ---------------------------------------------------------------------------
395
+ -- `v_replay -> 'requestedScopes' is distinct from to_jsonb(v_requested_scopes)` is a
396
+ -- JSONB comparison and a jsonb array preserves order, so the SAME scopes in a different
397
+ -- order are a DIFFERENT request. That is pre-existing released behaviour of the frozen
398
+ -- contract (20260901000100), not something #176 introduced, and this migration
399
+ -- deliberately does not loosen it. It is pinned here because the offline MockEnroll
400
+ -- sorted its scope arrays and so disagreed with production silently; the client now
401
+ -- asserts the same property, and neither side can drift without one of them going red.
402
+ select pg_temp.try('frozen-scope-order', (select pg_temp.enroll_sql(
403
+ pg_temp.v('iss1'), pg_temp.v('suba'), 'res-2-' || pg_temp.v('s'),
404
+ pg_temp.v('pub2'), pg_temp.v('fp2'), '{board:read,board:send}')));
405
+ select ok(
406
+ (select state = '23505' and position(h in msg) > 0
407
+ from _err, (select handle h from _e where label = 'E2') x
408
+ where label = 'frozen-scope-order'),
409
+ 'R-14: the SAME frozen scopes REORDERED are a different frozen contract and raise, naming the handle');
410
+
411
+ select * from finish();
412
+ rollback;
@@ -86,9 +86,7 @@ fns as (
86
86
  select p.oid,
87
87
  p.proname,
88
88
  p.proname || '(' || pg_get_function_identity_arguments(p.oid) || ')' as sig,
89
- regexp_replace(p.prosrc,
90
- $re$(\$[A-Za-z_0-9]*\$).*?\1|('([^']|'')*')|(--[^\n]*)|(/\*([^*]|\*+[^*/])*\*+/)$re$,
91
- ' ', 'g') as body,
89
+ agent_board_test.stripped_body(p.oid) as body,
92
90
  p.proconfig
93
91
  from pg_proc p
94
92
  join pg_namespace n on n.oid = p.pronamespace
@@ -70,9 +70,12 @@ select is(
70
70
  $g$raise\s+exception\s+'issuer/audience not in the identity-provider allowlist \(rejected before any owner or board lock\)'$g$)), 0) as p_gate,
71
71
  strpos(d, '_ensure_agent_auth_user') as p_ensure,
72
72
  strpos(d, substring(d from 'from\s+auth\.users[^;]{0,250}?for\s+update')) as p_lock
73
- from (select regexp_replace(p.prosrc,
74
- $re$(\$[A-Za-z_0-9]*\$).*?\1|('([^']|'')*')|(--[^\n]*)|(/\*([^*]|\*+[^*/])*\*+/)$re$,
75
- '\2', 'g') as d
73
+ -- LITERALS KEPT (the `true`): the gate marker G-2 locates IS the raise
74
+ -- message, so stripping literals would delete it and the positional claim
75
+ -- would read 'gate-missing' forever. Comments -- including the NESTED
76
+ -- block comment of message-board#199, which the regex this replaced could
77
+ -- not see -- are still removed.
78
+ from (select agent_board_test.stripped_body(p.oid, true) as d
76
79
  from pg_proc p
77
80
  join pg_namespace n on n.oid = p.pronamespace
78
81
  where n.nspname = 'agent_board'
@@ -159,9 +162,9 @@ select is(
159
162
  join pg_namespace n on n.oid = p.pronamespace
160
163
  where n.nspname = 'agent_board'
161
164
  and p.prokind = 'f'
162
- and regexp_replace(p.prosrc,
163
- $re$(\$[A-Za-z_0-9]*\$).*?\1|('([^']|'')*')|(--[^\n]*)|(/\*([^*]|\*+[^*/])*\*+/)$re$,
164
- '\2', 'g') ~ 'not in the identity-provider allowlist'),
165
+ -- LITERALS KEPT: membership in this class is defined by a function raising
166
+ -- that MESSAGE, so the marker is a literal by construction.
167
+ and agent_board_test.stripped_body(p.oid, true) ~ 'not in the identity-provider allowlist'),
165
168
  '_enroll_federated_human_after_revocation_gate, _enroll_federated_identity_once, '
166
169
  || 'enroll_federated_human, enroll_federated_identity',
167
170
  'G-8: exactly four routes reject an absent identity provider');
@@ -43,7 +43,7 @@ set search_path = extensions, public, agent_board;
43
43
 
44
44
  begin;
45
45
 
46
- select plan(22);
46
+ select plan(23);
47
47
 
48
48
  -- (1)(2) Shape.
49
49
  select has_function(
@@ -378,6 +378,30 @@ select is(
378
378
  -- CLASS (rule #1): catalog-derived — every machine-subject revocation READER honors
379
379
  -- reactivated_at; the WRITER exemption is asserted positively.
380
380
  ------------------------------------------------------------------------------------------
381
+ -- (21a) NON-VACUITY, asserted BEFORE the class below. That class is
382
+ -- `string_agg(offender) is null` -- so it passes just as happily when the
383
+ -- enumeration reaches NOTHING as when every reader conforms. Found while
384
+ -- re-deriving the strip modes for the message-board#196 review: flipping the
385
+ -- membership predicate to a literal-stripped read empties the class (the marker
386
+ -- `fre.subject_kind = ''machine''` has a literal operand and cannot survive it)
387
+ -- and the file stayed 22/22 GREEN. A green that survives its own class being
388
+ -- deleted is not evidence, so the membership set is pinned as an exact set here
389
+ -- and a new reader NAMES ITSELF in this diff.
390
+ select is(
391
+ (select
392
+ coalesce((select string_agg(p.proname, ', ' order by p.proname)
393
+ from pg_proc p join pg_namespace n on n.oid = p.pronamespace
394
+ where n.nspname = 'agent_board' and p.prokind = 'f'
395
+ -- DATA literal -> case-SENSITIVE; see the rule at the offender class below
396
+ and agent_board_test.stripped_body(p.oid, true) ~ 'fre\.subject_kind = ''machine'''), '(none)')
397
+ || ' | ' ||
398
+ coalesce((select string_agg(c.relname, ', ' order by c.relname)
399
+ from pg_class c join pg_namespace n on n.oid = c.relnamespace
400
+ where n.nspname = 'agent_board' and c.relkind = 'v'
401
+ and agent_board_test.stripped_viewdef(c.oid, true) ~ 'fre\.subject_kind = ''machine'''), '(none)')),
402
+ 'evaluate_fleet_sender_policy, reactivate_fleet_machine | v_fleet_machine_status',
403
+ 'the machine-subject revocation reader class is REACHED and is exactly these three -- the class assertion below cannot pass by finding nothing');
404
+
381
405
  -- (21) Enumerate readers (functions + views) whose definition tests a machine-subject
382
406
  -- revocation event (`fre.subject_kind = 'machine'`) but does NOT reference reactivated_at.
383
407
  -- Expected: none. A new non-conforming reader appears here BY NAME (rule #1: offenders name
@@ -391,16 +415,43 @@ select is(
391
415
  join pg_namespace n on n.oid = p.pronamespace
392
416
  where n.nspname = 'agent_board'
393
417
  and p.prokind = 'f'
394
- and pg_get_functiondef(p.oid) ~ 'fre\.subject_kind = ''machine'''
395
- and pg_get_functiondef(p.oid) not ilike '%reactivated_at%'
418
+ -- TWO MODES ON PURPOSE, and mixing them was a live fail-open
419
+ -- (message-board#196 review, red-team seat).
420
+ --
421
+ -- MEMBERSHIP (literals KEPT): the marker `fre.subject_kind = ''machine''`
422
+ -- has a string literal as its operand, so it cannot be located in a
423
+ -- literal-stripped body. This direction is fail-CLOSED anyway -- a
424
+ -- literal that fakes the marker ADDS an offender and reddens the
425
+ -- expected-empty set.
426
+ --
427
+ -- THE GUARD CHECK (literals STRIPPED): `reactivated_at` is a COLUMN
428
+ -- NAME, i.e. code. Read with literals kept, a reader that dropped the
429
+ -- guard while any literal still mentioned `reactivated_at` fell out of
430
+ -- the offender set and the class went 22/22 GREEN with the guard gone
431
+ -- (measured on evaluate_fleet_sender_policy). That is the exemption
432
+ -- category becoming the hole, CLAUDE.md rule 1's named trap.
433
+ -- CASE: this marker is compared as DATA, so it stays case-SENSITIVE.
434
+ -- The rule the suite now follows, and the line the round-1 `gi` sweep
435
+ -- got wrong here: a marker SPELLED AS CODE (a relation, function or
436
+ -- column name) is matched case-INSENSITIVELY, because PostgreSQL folds
437
+ -- unquoted identifiers and a case-sensitive scan is fail-open for a
438
+ -- class filter. A marker COMPARED AS DATA -- a string literal operand,
439
+ -- or a raise message -- stays case-SENSITIVE, because nothing folds it:
440
+ -- `'machine' = 'MACHINE'` is FALSE at runtime, so a `~*` here cannot
441
+ -- tell a working predicate from a broken one. Measured: rewriting the
442
+ -- live predicate to `= 'MACHINE'` -- which matches no rows -- left this
443
+ -- file 23/23 GREEN under `~*`.
444
+ and agent_board_test.stripped_body(p.oid, true) ~ 'fre\.subject_kind = ''machine'''
445
+ and agent_board_test.stripped_body(p.oid) not ilike '%reactivated_at%'
396
446
  union all
397
447
  select c.relname
398
448
  from pg_class c
399
449
  join pg_namespace n on n.oid = c.relnamespace
400
450
  where n.nspname = 'agent_board'
401
451
  and c.relkind = 'v'
402
- and pg_get_viewdef(c.oid) ~ 'fre\.subject_kind = ''machine'''
403
- and pg_get_viewdef(c.oid) not ilike '%reactivated_at%'
452
+ -- same split for the view half: marker keeps literals, guard does not
453
+ and agent_board_test.stripped_viewdef(c.oid, true) ~ 'fre\.subject_kind = ''machine'''
454
+ and agent_board_test.stripped_viewdef(c.oid) not ilike '%reactivated_at%'
404
455
  ) offenders
405
456
  ),
406
457
  null,
@@ -22,7 +22,7 @@ set search_path = extensions, public, agent_board;
22
22
 
23
23
  begin;
24
24
 
25
- select plan(31);
25
+ select plan(32);
26
26
 
27
27
  create temp table _mh (
28
28
  owner_id uuid,
@@ -119,9 +119,33 @@ select is(
119
119
  where n.nspname = 'agent_board' and p.prokind = 'f')
120
120
  select coalesce(string_agg(proname, ', ' order by proname), '')
121
121
  from fns
122
- where pg_get_functiondef(oid) ~* 'insert[[:space:]]+into[[:space:]]+(agent_board\.)?agent_signing_keys'
123
- and pg_get_functiondef(oid) ~ 'fingerprint'
124
- and pg_get_functiondef(oid) !~ '_raw_key_fingerprint'),
122
+ where agent_board_test.stripped_body(oid) ~* 'insert[[:space:]]+into[[:space:]]+(agent_board\.)?agent_signing_keys'
123
+ -- CASE-INSENSITIVE, and that is load-bearing rather than tidiness. SQL folds
124
+ -- unquoted identifiers, but `prosrc` is stored VERBATIM, so a writer spelling
125
+ -- the same call `AGENT_BOARD.…` or the same column `FINGERPRINT` is identical
126
+ -- to PostgreSQL and invisible to a case-sensitive scan. For a MEMBERSHIP
127
+ -- filter that is the fail-OPEN direction: the offender simply leaves the
128
+ -- class and the expected-empty assertion stays green. (Same mechanism
129
+ -- agent_board_wake_recovery.test.sql records for its own WR-7 filter.)
130
+ and agent_board_test.stripped_body(oid) ~* 'fingerprint'
131
+ -- message-board#196 Bucket B, demonstrated: read RAW, ONE comment mentioning
132
+ -- _raw_key_fingerprint dropped the forging writer out of this set and the
133
+ -- file went fully green with the bypass live.
134
+ -- ANCHORED TO THE CALL SHAPE, not to a bare identifier substring
135
+ -- (message-board#196 review). `!~ '_raw_key_fingerprint'` is satisfied by ANY identifier
136
+ -- containing it, so a forging writer with an argument named
137
+ -- `p_raw_key_fingerprint` -- not even a suspicious name -- fell out of this
138
+ -- offender set and the file stayed green. `\m` will not match inside
139
+ -- `p_...` (both are word characters, so there is no boundary), and the
140
+ -- trailing `\(` requires an actual call rather than a mention.
141
+ -- SCHEMA-QUALIFIED, because a bare-name anchor proves call SYNTAX, not callee
142
+ -- IDENTITY: `\m_raw_key_fingerprint\s*\(` also matches
143
+ -- `zz_other._raw_key_fingerprint(...)` (measured true). Every live call
144
+ -- site in agent_board is `agent_board.`-qualified, so requiring the
145
+ -- qualification costs nothing today and is fail-CLOSED if someone later writes
146
+ -- an unqualified call (it reads as NOT calling). Overload count is pinned
147
+ -- separately, so schema + name is the identity.
148
+ and agent_board_test.stripped_body(oid) !~* '\magent_board\._raw_key_fingerprint\s*\('),
125
149
  '',
126
150
  'no agent_signing_keys.fingerprint writer bypasses _raw_key_fingerprint (catalog-derived; a new writer names itself)');
127
151
 
@@ -299,5 +323,16 @@ select ok(has_function_privilege('service_role',
299
323
  'agent_board.mint_delegated_child(uuid, text, jsonb, text, interval, boolean)', 'EXECUTE'),
300
324
  'service_role retains EXECUTE on mint_delegated_child');
301
325
 
326
+
327
+ -- The schema-qualified call anchor above treats `agent_board._raw_key_fingerprint` as an
328
+ -- IDENTITY. That is only true while the name resolves to exactly one function:
329
+ -- a second overload would be a different implementation the same anchor accepts.
330
+ -- Catalog-derived, so a new overload fails here and the count names itself.
331
+ select is(
332
+ (select count(*)::int from pg_proc p join pg_namespace n on n.oid = p.pronamespace
333
+ where n.nspname = 'agent_board' and p.proname = '_raw_key_fingerprint'),
334
+ 1,
335
+ 'agent_board._raw_key_fingerprint has exactly ONE overload, so the schema-qualified call anchor identifies it uniquely');
336
+
302
337
  select finish();
303
338
  rollback;