@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,454 @@
1
+ -- #247 pgTAP: signed provenance for the two authority-granting writes.
2
+ --
3
+ -- The claim under test: `grant_board_role` and `set_board_authority_ceiling` can now be
4
+ -- performed through a signed-write gateway that records a `signature_evidence` row, and the
5
+ -- principal that row names is the one the write was actually AUTHORIZED as -- never a
6
+ -- caller-supplied argument.
7
+ --
8
+ -- Methodology:
9
+ -- * RPC probes are driven by the JWT claim blob only. SET LOCAL ROLE is never used: it
10
+ -- would revoke _is_service_role() and mask the gateway branch behind a permission error
11
+ -- on the function itself (same reason as agent_board_consent_emission).
12
+ -- * Allow-side probes are lives_ok(...) followed by a SEPARATE state check. A bare
13
+ -- `select ... from <raising rpc>(...)` aborts the transaction instead of failing, which
14
+ -- would silently drop every later assertion (#113).
15
+ -- * The payload deliberately names a DIFFERENT grantor than the gateway decision in the
16
+ -- anti-forge cases. If provenance were keyed off the payload rather than the gateway's
17
+ -- verified actor, those assertions flip.
18
+ -- * The classification tripwires at the end are catalog-derived (pg_proc.proargnames),
19
+ -- not a list written here: if a p_metadata parameter is ever added to one of the three
20
+ -- RPCs classified as unable to carry client-side signing evidence, the assertion fails
21
+ -- and NAMES the member, forcing reclassification rather than leaving the client-side
22
+ -- exclusion silently stale.
23
+
24
+ create extension if not exists pgtap with schema extensions;
25
+ set search_path = extensions, public, agent_board;
26
+
27
+ begin;
28
+
29
+ select plan(38);
30
+
31
+ create function pg_temp.claims(p_sub text) returns void language plpgsql as $$
32
+ begin
33
+ perform set_config('request.jwt.claims', json_build_object('role','authenticated','sub',p_sub)::text, true);
34
+ end $$;
35
+ create function pg_temp.service() returns void language plpgsql as $$
36
+ begin
37
+ perform set_config('request.jwt.claims', '', true);
38
+ end $$;
39
+
40
+ -- Fixture -------------------------------------------------------------------
41
+ -- owner = agent 1 (board owner, and the signer the gateway verifies)
42
+ -- grantee = agent 2 (an active member whose role the owner raises)
43
+ -- outsider = agent 3 (a member with NO authority; the forged grantor in the M7 probes)
44
+
45
+ insert into auth.users(id, instance_id, aud, role, email)
46
+ select ('24700000-0000-0000-0000-' || lpad(n::text,12,'0'))::uuid,
47
+ '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated',
48
+ 'k247u'||n||'@t.test'
49
+ from generate_series(1,3) n;
50
+
51
+ insert into agent_board.agents(id, agent_type, kind, handle, display_name, status, auth_user_id)
52
+ select ('24710000-0000-4000-8000-' || lpad(n::text,12,'0'))::uuid,
53
+ 'agent','agent','k247-agent-'||n,'K247 Agent '||n,'active',
54
+ ('24700000-0000-0000-0000-' || lpad(n::text,12,'0'))::uuid
55
+ from generate_series(1,3) n;
56
+
57
+ insert into agent_board.boards(id, slug, name, created_by_agent_id, visibility, status)
58
+ values ('24720000-0000-4000-8000-000000000001','k247-board','K247 Board',
59
+ '24710000-0000-4000-8000-000000000001'::uuid, 'private','active');
60
+
61
+ insert into agent_board.board_memberships(board_id, agent_id, role, membership_state, created_by_agent_id)
62
+ values
63
+ ('24720000-0000-4000-8000-000000000001','24710000-0000-4000-8000-000000000001','owner','joined','24710000-0000-4000-8000-000000000001'),
64
+ ('24720000-0000-4000-8000-000000000001','24710000-0000-4000-8000-000000000002','member','joined','24710000-0000-4000-8000-000000000001'),
65
+ ('24720000-0000-4000-8000-000000000001','24710000-0000-4000-8000-000000000003','member','joined','24710000-0000-4000-8000-000000000001');
66
+
67
+ insert into agent_board.agent_signing_keys(
68
+ id, agent_id, key_id, key_version, public_key, public_key_format, fingerprint, status, proof_state)
69
+ values (
70
+ '24730000-0000-4000-8000-000000000001',
71
+ '24710000-0000-4000-8000-000000000001',
72
+ 'primary', 1,
73
+ public.test_pubkey('k247-primary'), 'raw_ed25519_base64url',
74
+ public.test_key_fingerprint('k247-primary'), 'active', 'proven');
75
+
76
+ -- The gateway decision the wrappers are called with. `agent_id` is the VERIFIED signer.
77
+ create function pg_temp.gw(p_actor text) returns jsonb language sql immutable as $$
78
+ select jsonb_build_object(
79
+ 'agent_id', p_actor,
80
+ 'signing_key_id', '24730000-0000-4000-8000-000000000001',
81
+ 'key_id', 'primary',
82
+ 'key_version', 1,
83
+ 'algorithm', 'Ed25519',
84
+ 'verification_status', 'verified',
85
+ 'payload_digest', 'sha256:' || repeat('a', 64),
86
+ 'signature_digest', 'sha256:' || repeat('b', 64),
87
+ 'canonicalization_version', 'agent-provenance-jcs.v1',
88
+ 'enforcement_mode', 'require_for_agent_writes',
89
+ 'redacted', true);
90
+ $$;
91
+
92
+ -- ===========================================================================
93
+ -- 1. The surface exists, and the DROP did not leave the ACL or COMMENT behind.
94
+ -- (DROP FUNCTION discards both; a recreated SECURITY DEFINER function grants
95
+ -- EXECUTE to PUBLIC by default. That hole opened for real in this repo once.)
96
+ -- ===========================================================================
97
+
98
+ select has_function('agent_board', 'signed_grant_board_role', array['jsonb','jsonb'],
99
+ 'signed_grant_board_role wrapper exists');
100
+ select has_function('agent_board', 'signed_set_board_authority_ceiling', array['jsonb','jsonb'],
101
+ 'signed_set_board_authority_ceiling wrapper exists');
102
+ select has_function('agent_board', 'set_board_authority_ceiling', array['uuid','text','uuid'],
103
+ 'the ceiling setter carries the delegable actor parameter');
104
+
105
+ select is(
106
+ (select string_agg(distinct coalesce(pg_get_userbyid(nullif(a.grantee,0)),'PUBLIC'), ','
107
+ order by coalesce(pg_get_userbyid(nullif(a.grantee,0)),'PUBLIC'))
108
+ from pg_proc p, aclexplode(p.proacl) a
109
+ where p.oid = 'agent_board.signed_grant_board_role(jsonb,jsonb)'::regprocedure
110
+ and a.privilege_type = 'EXECUTE'),
111
+ 'postgres,service_role',
112
+ 'signed_grant_board_role executes for service_role only (never PUBLIC/anon/authenticated)');
113
+
114
+ select is(
115
+ (select string_agg(distinct coalesce(pg_get_userbyid(nullif(a.grantee,0)),'PUBLIC'), ','
116
+ order by coalesce(pg_get_userbyid(nullif(a.grantee,0)),'PUBLIC'))
117
+ from pg_proc p, aclexplode(p.proacl) a
118
+ where p.oid = 'agent_board.signed_set_board_authority_ceiling(jsonb,jsonb)'::regprocedure
119
+ and a.privilege_type = 'EXECUTE'),
120
+ 'postgres,service_role',
121
+ 'signed_set_board_authority_ceiling executes for service_role only');
122
+
123
+ select is(
124
+ (select string_agg(distinct coalesce(pg_get_userbyid(nullif(a.grantee,0)),'PUBLIC'), ','
125
+ order by coalesce(pg_get_userbyid(nullif(a.grantee,0)),'PUBLIC'))
126
+ from pg_proc p, aclexplode(p.proacl) a
127
+ where p.oid = 'agent_board.set_board_authority_ceiling(uuid,text,uuid)'::regprocedure
128
+ and a.privilege_type = 'EXECUTE'),
129
+ 'authenticated,postgres,service_role',
130
+ 'the recreated ceiling setter restated its grants and did NOT keep PUBLIC EXECUTE');
131
+
132
+ select isnt(
133
+ (select obj_description('agent_board.set_board_authority_ceiling(uuid,text,uuid)'::regprocedure, 'pg_proc')),
134
+ null,
135
+ 'the recreated ceiling setter restated its COMMENT (DROP FUNCTION discards it)');
136
+
137
+ select ok(
138
+ strpos(lower(agent_board_test.stripped_body('agent_board.set_board_authority_ceiling(uuid,text,uuid)'::regprocedure)),
139
+ 'perform agent_board._lock_board_authority_ceiling(p_board_id)') > 0
140
+ and strpos(lower(agent_board_test.stripped_body('agent_board.set_board_authority_ceiling(uuid,text,uuid)'::regprocedure)),
141
+ 'perform agent_board._lock_board_authority_ceiling(p_board_id)')
142
+ < strpos(lower(agent_board_test.stripped_body('agent_board.set_board_authority_ceiling(uuid,text,uuid)'::regprocedure)),
143
+ 'insert into agent_board.board_authority_ceilings'),
144
+ 'the recreated setter still takes the advisory lock before its gate and upsert');
145
+
146
+ -- ===========================================================================
147
+ -- 2. D4 -- the new parameter changes NO existing unsigned call.
148
+ -- ===========================================================================
149
+
150
+ select pg_temp.claims('24700000-0000-0000-0000-000000000001');
151
+
152
+ select lives_ok(
153
+ $$select * from agent_board.set_board_authority_ceiling(
154
+ '24720000-0000-4000-8000-000000000001'::uuid, 'admin')$$,
155
+ 'D4: the pre-#247 two-argument call by an authenticated owner still succeeds');
156
+
157
+ select is(
158
+ (select max_grantable_role from agent_board.board_authority_ceilings
159
+ where board_id = '24720000-0000-4000-8000-000000000001'),
160
+ 'admin',
161
+ 'D4: and it wrote the ceiling it was asked for');
162
+
163
+ select pg_temp.claims('24700000-0000-0000-0000-000000000003');
164
+
165
+ select throws_like(
166
+ $$select * from agent_board.set_board_authority_ceiling(
167
+ '24720000-0000-4000-8000-000000000001'::uuid, 'owner')$$,
168
+ '%only a board owner%',
169
+ 'D4: a non-owner is still refused by the owner gate, with the same message');
170
+
171
+ -- The new parameter cannot be used to act as someone else while authenticated.
172
+ select throws_like(
173
+ $$select * from agent_board.set_board_authority_ceiling(
174
+ '24720000-0000-4000-8000-000000000001'::uuid, 'owner',
175
+ '24710000-0000-4000-8000-000000000001'::uuid)$$,
176
+ '%must match current_agent_id()%',
177
+ 'an authenticated caller cannot borrow another agent as p_actor_agent_id');
178
+
179
+ -- ===========================================================================
180
+ -- 3. signed_grant_board_role -- the write happens AND leaves evidence.
181
+ -- ===========================================================================
182
+
183
+ select pg_temp.service();
184
+
185
+ select lives_ok(
186
+ $$select * from agent_board.signed_grant_board_role(
187
+ jsonb_build_object(
188
+ 'p_board_id', '24720000-0000-4000-8000-000000000001',
189
+ 'p_agent_id', '24710000-0000-4000-8000-000000000002',
190
+ 'p_grantee_role', 'admin'),
191
+ pg_temp.gw('24710000-0000-4000-8000-000000000001'))$$,
192
+ 'a signed grant by the verified board owner succeeds');
193
+
194
+ select is(
195
+ (select role from agent_board.board_memberships
196
+ where board_id = '24720000-0000-4000-8000-000000000001'
197
+ and agent_id = '24710000-0000-4000-8000-000000000002'),
198
+ 'admin',
199
+ 'the signed grant actually raised the grantee role');
200
+
201
+ select is(
202
+ (select count(*)::int from agent_board.signature_evidence
203
+ where operation = 'grant_board_role'),
204
+ 1,
205
+ 'the signed grant recorded exactly one signature_evidence row');
206
+
207
+ select is(
208
+ (select agent_id from agent_board.signature_evidence where operation = 'grant_board_role'),
209
+ '24710000-0000-4000-8000-000000000001'::uuid,
210
+ 'M7: the evidence row names the GATEWAY-VERIFIED grantor');
211
+
212
+ select is(
213
+ (select verification_status from agent_board.signature_evidence where operation = 'grant_board_role'),
214
+ 'verified',
215
+ 'the evidence row carries the gateway verification status');
216
+
217
+ select is(
218
+ (select external_evidence_ref from agent_board.signature_evidence where operation = 'grant_board_role'),
219
+ 'board-authority:grant_board_role:24720000-0000-4000-8000-000000000001:24710000-0000-4000-8000-000000000002',
220
+ 'the evidence ref is derived server-side from the board and grantee');
221
+
222
+ -- The source-shape CHECK is asserted, not inferred from the insert having succeeded.
223
+ select ok(
224
+ (select source_kind = 'external'
225
+ and source_message_id is null
226
+ and source_task_event_id is null
227
+ and source_artifact_id is null
228
+ and source_delivery_recipient_id is null
229
+ and external_evidence_ref is not null
230
+ from agent_board.signature_evidence where operation = 'grant_board_role'),
231
+ 'the authority evidence row satisfies the external source shape the CHECK requires');
232
+
233
+ -- ===========================================================================
234
+ -- 4. M7 -- a caller-supplied grantor can never become the recorded principal.
235
+ -- ===========================================================================
236
+
237
+ select throws_like(
238
+ $$select * from agent_board.signed_grant_board_role(
239
+ jsonb_build_object(
240
+ 'p_board_id', '24720000-0000-4000-8000-000000000001',
241
+ 'p_agent_id', '24710000-0000-4000-8000-000000000002',
242
+ 'p_grantee_role', 'owner',
243
+ 'p_grantor_agent_id', '24710000-0000-4000-8000-000000000003'),
244
+ pg_temp.gw('24710000-0000-4000-8000-000000000001'))$$,
245
+ '%actor_mismatch%',
246
+ 'M7: a payload grantor that differs from the verified signer is refused outright');
247
+
248
+ select is(
249
+ (select count(*)::int from agent_board.signature_evidence where operation = 'grant_board_role'),
250
+ 1,
251
+ 'M7: and the refused forge attempt recorded no second evidence row');
252
+
253
+ select is(
254
+ (select role from agent_board.board_memberships
255
+ where board_id = '24720000-0000-4000-8000-000000000001'
256
+ and agent_id = '24710000-0000-4000-8000-000000000002'),
257
+ 'admin',
258
+ 'M7: and the refused forge attempt did not raise the grantee to owner');
259
+
260
+ -- The DIVERGENCE case, which is the one M7 is really about. Above, the forged grantor had
261
+ -- no authority, so a mutation that trusted the payload would still be stopped by the
262
+ -- ceiling gate -- the damage would be caught downstream, not by provenance. Here the forged
263
+ -- grantor is the OWNER and the verified signer is a mere member: a wrapper that took the
264
+ -- grantor from the payload would perform a grant AUTHORIZED AS THE OWNER while
265
+ -- _record_signed_write_evidence (which reads only the gateway actor) recorded it against
266
+ -- the member. That row would be evidence naming a principal who did not authorize the
267
+ -- write, which is worse than no row at all.
268
+ select throws_like(
269
+ $$select * from agent_board.signed_grant_board_role(
270
+ jsonb_build_object(
271
+ 'p_board_id', '24720000-0000-4000-8000-000000000001',
272
+ 'p_agent_id', '24710000-0000-4000-8000-000000000002',
273
+ 'p_grantee_role', 'admin',
274
+ 'p_grantor_agent_id', '24710000-0000-4000-8000-000000000001'),
275
+ pg_temp.gw('24710000-0000-4000-8000-000000000003'))$$,
276
+ '%actor_mismatch%',
277
+ 'M7: a payload grantor with MORE authority than the verified signer is refused');
278
+
279
+ select is(
280
+ (select count(*)::int from agent_board.signature_evidence where operation = 'grant_board_role'),
281
+ 1,
282
+ 'M7: the divergence attempt recorded no evidence row naming the wrong principal');
283
+
284
+ -- The wrapper adds no authority: an unprivileged verified signer is still refused,
285
+ -- by the SAME gates the unsigned function applies.
286
+ select throws_like(
287
+ $$select * from agent_board.signed_grant_board_role(
288
+ jsonb_build_object(
289
+ 'p_board_id', '24720000-0000-4000-8000-000000000001',
290
+ 'p_agent_id', '24710000-0000-4000-8000-000000000002',
291
+ 'p_grantee_role', 'owner'),
292
+ pg_temp.gw('24710000-0000-4000-8000-000000000003'))$$,
293
+ '%exceeds grantor role or board authority ceiling%',
294
+ 'the wrapper grants no authority: a member signer still cannot mint an owner');
295
+
296
+ select throws_like(
297
+ $$select * from agent_board.signed_grant_board_role(
298
+ jsonb_build_object(
299
+ 'p_board_id', '24720000-0000-4000-8000-000000000001',
300
+ 'p_agent_id', '24710000-0000-4000-8000-000000000002',
301
+ 'p_grantee_role', 'admin'),
302
+ jsonb_build_object('verification_status','failed'))$$,
303
+ '%actor_mismatch%',
304
+ 'a gateway decision naming no actor cannot write evidence at all');
305
+
306
+ -- ===========================================================================
307
+ -- 5. signed_set_board_authority_ceiling.
308
+ -- ===========================================================================
309
+
310
+ select lives_ok(
311
+ $$select * from agent_board.signed_set_board_authority_ceiling(
312
+ jsonb_build_object(
313
+ 'p_board_id', '24720000-0000-4000-8000-000000000001',
314
+ 'p_max_grantable_role', 'member'),
315
+ pg_temp.gw('24710000-0000-4000-8000-000000000001'))$$,
316
+ 'a signed ceiling change by the verified board owner succeeds');
317
+
318
+ select is(
319
+ (select max_grantable_role from agent_board.board_authority_ceilings
320
+ where board_id = '24720000-0000-4000-8000-000000000001'),
321
+ 'member',
322
+ 'the signed ceiling change actually wrote the ceiling');
323
+
324
+ select is(
325
+ (select agent_id from agent_board.signature_evidence
326
+ where operation = 'set_board_authority_ceiling'),
327
+ '24710000-0000-4000-8000-000000000001'::uuid,
328
+ 'M7: the ceiling evidence row names the gateway-verified actor');
329
+
330
+ select throws_like(
331
+ $$select * from agent_board.signed_set_board_authority_ceiling(
332
+ jsonb_build_object(
333
+ 'p_board_id', '24720000-0000-4000-8000-000000000001',
334
+ 'p_max_grantable_role', 'owner'),
335
+ pg_temp.gw('24710000-0000-4000-8000-000000000003'))$$,
336
+ '%only a board owner%',
337
+ 'the ceiling wrapper grants no authority: a non-owner signer is still refused');
338
+
339
+ select is(
340
+ (select count(*)::int from agent_board.signature_evidence
341
+ where operation = 'set_board_authority_ceiling'),
342
+ 1,
343
+ 'and the refused ceiling change recorded no evidence row');
344
+
345
+ -- ===========================================================================
346
+ -- 6. Classification tripwires (catalog-derived, CLAUDE.md rule 1).
347
+ --
348
+ -- The client classifies these three as unable to carry client-side signing
349
+ -- evidence BECAUSE they declare no p_metadata parameter -- the client's
350
+ -- attachSigningMetadata merges into p_metadata and nowhere else. That reason is
351
+ -- asserted here positively and from pg_catalog. If a p_metadata parameter is ever
352
+ -- added to one of them, this fails and names it, forcing the client-side
353
+ -- classification to be revisited instead of going quietly stale.
354
+ --
355
+ -- WHERE THE CONVERSE CLASS IS ASSERTED -- not here, and pointing at it rather than
356
+ -- leaving the gap this note used to record. "Every RPC the client signs into p_metadata
357
+ -- declares that parameter" was FALSE for nine of the thirty-nine (append_task_event,
358
+ -- delete_callback_endpoint, mark_read, raise_conflict, raise_escalation, release_claim,
359
+ -- renew_claim, resolve_conflict, resolve_escalation -- all inherited from the pre-#247
360
+ -- hand-written signed set), which is why it could not be written green when this file
361
+ -- was authored. message-board#261 reclassified all nine and pinned the class:
362
+ -- scripts/build-p-metadata-snapshot.mjs derives EVERY agent_board function from
363
+ -- pg_catalog with no name filter, each with a declares_p_metadata boolean, into
364
+ -- tests/catalog/p_metadata_parameters.json; `--check`s it against a live database in the
365
+ -- same CI job that runs this suite; and the client's projection test asserts that every
366
+ -- OVERLOAD of every signed RPC declares the parameter, naming any signature that does
367
+ -- not. The class is pinned on the CLIENT side because that is where the signed set
368
+ -- lives; this section stays the catalog-side tripwire for the three RPCs #247
369
+ -- adjudicated.
370
+ -- ===========================================================================
371
+
372
+ select is(
373
+ (select string_agg(p.proname || '(' || pg_get_function_identity_arguments(p.oid) || ')', '; '
374
+ order by p.proname)
375
+ from pg_proc p
376
+ join pg_namespace n on n.oid = p.pronamespace
377
+ where n.nspname = 'agent_board'
378
+ and p.proname in ('grant_board_role','set_board_authority_ceiling','set_board_role_gate')
379
+ and not ('p_metadata' = any(coalesce(p.proargnames, array[]::text[])))),
380
+ 'grant_board_role(p_board_id uuid, p_agent_id uuid, p_grantee_role text, p_grantor_agent_id uuid); '
381
+ || 'set_board_authority_ceiling(p_board_id uuid, p_max_grantable_role text, p_actor_agent_id uuid); '
382
+ || 'set_board_role_gate(p_board_id uuid, p_enforced boolean)',
383
+ 'the three authority RPCs that cannot carry p_metadata.signed_write are exactly these, with these signatures');
384
+
385
+ select is(
386
+ (select count(*)::int
387
+ from pg_proc p
388
+ join pg_namespace n on n.oid = p.pronamespace
389
+ where n.nspname = 'agent_board'
390
+ and p.proname in ('grant_board_role','set_board_authority_ceiling','set_board_role_gate')
391
+ and 'p_metadata' = any(coalesce(p.proargnames, array[]::text[]))),
392
+ 0,
393
+ 'none of the three has acquired a p_metadata parameter');
394
+
395
+ select is(
396
+ (select string_agg(distinct p.proname, ',' order by p.proname)
397
+ from pg_proc p
398
+ join pg_namespace n on n.oid = p.pronamespace
399
+ where n.nspname = 'agent_board'
400
+ and p.proname in ('create_invite_link','create_fleet_onboarding_invite','create_or_find_thread')
401
+ and 'p_metadata' = any(coalesce(p.proargnames, array[]::text[]))),
402
+ 'create_fleet_onboarding_invite,create_invite_link,create_or_find_thread',
403
+ -- NOT "the RPCs the client signs": create_or_find_thread is in this set and the client
404
+ -- classifies it unsigned-deliberate, and the client signs 39 RPCs, not three. The set is
405
+ -- exactly the three whose p_metadata premise #247 adjudicated -- two it now signs, one it
406
+ -- deliberately does not -- so the label states the set it actually queries. The wider
407
+ -- claim this once implied ("every signed RPC declares p_metadata") is NOT asserted here
408
+ -- and must not be read out of it; see the note above section 6.
409
+ 'the three RPCs #247 adjudicated that declare p_metadata are exactly these');
410
+
411
+ -- create_or_find_thread is classified unsigned-deliberate on the strength of being
412
+ -- idempotent -- a key-collision returns the EXISTING thread rather than writing a new
413
+ -- one. Asserted positively rather than left as an unexplained exclusion.
414
+ --
415
+ -- Driven through lives_ok + a captured result, NOT a bare `is((select ... from rpc(...)))`.
416
+ -- create_or_find_thread is an ADMISSION rpc: the bare form ABORTS the transaction on a
417
+ -- raise instead of failing it, and pgTAP then emits no `not ok` at all while the remaining
418
+ -- assertions silently never run (#113, CLAUDE.md rule 3). The runner's lint did not flag
419
+ -- the bare form here only because its MUT_RPCS list did not name this function; that list
420
+ -- is corrected in the same change, so the next admission rpc used this way is caught by the
421
+ -- lint rather than by a reviewer.
422
+ create temporary table k247_idem(
423
+ seq int generated always as identity,
424
+ thread_id uuid,
425
+ reused boolean
426
+ ) on commit drop;
427
+
428
+ select lives_ok($$
429
+ insert into k247_idem(thread_id, reused)
430
+ select thread_id, reused
431
+ from agent_board.create_or_find_thread(
432
+ '24710000-0000-4000-8000-000000000001'::uuid, 'K247 idem', 'k247-idem-key')
433
+ $$, 'create_or_find_thread admits the first call for the key');
434
+
435
+ select lives_ok($$
436
+ insert into k247_idem(thread_id, reused)
437
+ select thread_id, reused
438
+ from agent_board.create_or_find_thread(
439
+ '24710000-0000-4000-8000-000000000001'::uuid, 'K247 idem', 'k247-idem-key')
440
+ $$, 'and admits an identical second call rather than raising');
441
+
442
+ select is(
443
+ (select count(distinct thread_id)::int from k247_idem),
444
+ 1,
445
+ 'create_or_find_thread is idempotent on its key -- the stated reason it is deliberately unsigned');
446
+
447
+ select is(
448
+ (select reused from k247_idem order by seq desc limit 1),
449
+ true,
450
+ 'and it reports the reuse rather than performing a second authority-free write');
451
+
452
+ select finish();
453
+
454
+ rollback;