@consiliency/agent-board-schema 1.53.0 → 1.55.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 (65) hide show
  1. package/fixtures/already-applied.json +615 -607
  2. package/migrations/20261004000100_agent_board_callback_consumer_intent.sql +52 -0
  3. package/migrations/20261005000100_agent_board_enroll_token_single_use.sql +886 -0
  4. package/package.json +1 -1
  5. package/schema-manifest.json +21 -3
  6. package/src/tooling.test.mjs +5 -2
  7. package/tests/agent_board_atomic_fleet_onboarding.test.sql +35 -35
  8. package/tests/agent_board_atomic_fleet_onboarding_backfill.test.sql +5 -5
  9. package/tests/agent_board_atomic_fleet_onboarding_runtime.test.sql +32 -32
  10. package/tests/agent_board_enroll_federated_identity.test.sql +11 -10
  11. package/tests/agent_board_enroll_pop_binding.test.sql +12 -12
  12. package/tests/agent_board_enroll_resumption.test.sql +1 -1
  13. package/tests/agent_board_enroll_token_single_use.test.sql +538 -0
  14. package/tests/agent_board_enrollment_audit_content.test.sql +3 -3
  15. package/tests/agent_board_federated_lock_order_class.test.sql +5 -5
  16. package/tests/agent_board_fedsession.test.sql +1 -1
  17. package/tests/agent_board_fk_lock_edges.test.sql +23 -0
  18. package/tests/agent_board_identity_providers.test.sql +3 -2
  19. package/tests/agent_board_portal_offline_invariant.test.sql +1 -1
  20. package/tests/agent_board_roomlink.test.sql +2 -2
  21. package/tests/agent_board_write_enforcement_class.test.sql +5 -2
  22. package/tests/catalog/function_execute_privileges.json +12 -3
  23. package/tests/catalog/p_metadata_parameters.json +6 -2
  24. package/tests/isolation/expected/atomic_fedsession_fleet_lock_order.out +1 -1
  25. package/tests/isolation/expected/atomic_fleet_authority_races.out +8 -8
  26. package/tests/isolation/expected/atomic_fleet_binding_revoke.out +2 -2
  27. package/tests/isolation/expected/atomic_fleet_board_archive.out +2 -2
  28. package/tests/isolation/expected/atomic_fleet_human_fresh_revoke.out +2 -2
  29. package/tests/isolation/expected/atomic_fleet_identity_disposition.out +2 -2
  30. package/tests/isolation/expected/atomic_fleet_lifecycle_races.out +22 -22
  31. package/tests/isolation/expected/atomic_fleet_onboarding.out +2 -2
  32. package/tests/isolation/expected/atomic_fleet_subtree_lock_order.out +1 -1
  33. package/tests/isolation/expected/enroll_identity_board_owner_lock_order.out +1 -1
  34. package/tests/isolation/expected/enroll_token_fleet_abort.out +81 -0
  35. package/tests/isolation/expected/enroll_token_pk_backstop.out +36 -0
  36. package/tests/isolation/expected/enroll_token_placement.out +86 -0
  37. package/tests/isolation/expected/enroll_token_rollback_unstrands.out +59 -0
  38. package/tests/isolation/expected/enroll_token_route_race_fleet_first.out +72 -0
  39. package/tests/isolation/expected/enroll_token_route_race_identity_first.out +72 -0
  40. package/tests/isolation/expected/enroll_token_same_key_fleet_then_identity.out +73 -0
  41. package/tests/isolation/expected/enroll_token_same_key_identity_then_fleet.out +73 -0
  42. package/tests/isolation/expected/enroll_token_same_key_resume.out +59 -0
  43. package/tests/isolation/expected/enroll_token_same_token_race.out +58 -0
  44. package/tests/isolation/expected/onboarding_ceiling_lock_order.out +2 -2
  45. package/tests/isolation/specs/atomic_fedsession_fleet_lock_order.spec +1 -1
  46. package/tests/isolation/specs/atomic_fleet_authority_races.spec +2 -2
  47. package/tests/isolation/specs/atomic_fleet_binding_revoke.spec +2 -2
  48. package/tests/isolation/specs/atomic_fleet_board_archive.spec +2 -2
  49. package/tests/isolation/specs/atomic_fleet_human_fresh_revoke.spec +2 -2
  50. package/tests/isolation/specs/atomic_fleet_identity_disposition.spec +2 -2
  51. package/tests/isolation/specs/atomic_fleet_lifecycle_races.spec +2 -2
  52. package/tests/isolation/specs/atomic_fleet_onboarding.spec +2 -2
  53. package/tests/isolation/specs/atomic_fleet_subtree_lock_order.spec +2 -2
  54. package/tests/isolation/specs/enroll_identity_board_owner_lock_order.spec +1 -1
  55. package/tests/isolation/specs/enroll_token_fleet_abort.spec +138 -0
  56. package/tests/isolation/specs/enroll_token_pk_backstop.spec +37 -0
  57. package/tests/isolation/specs/enroll_token_placement.spec +136 -0
  58. package/tests/isolation/specs/enroll_token_rollback_unstrands.spec +71 -0
  59. package/tests/isolation/specs/enroll_token_route_race_fleet_first.spec +119 -0
  60. package/tests/isolation/specs/enroll_token_route_race_identity_first.spec +120 -0
  61. package/tests/isolation/specs/enroll_token_same_key_fleet_then_identity.spec +112 -0
  62. package/tests/isolation/specs/enroll_token_same_key_identity_then_fleet.spec +113 -0
  63. package/tests/isolation/specs/enroll_token_same_key_resume.spec +70 -0
  64. package/tests/isolation/specs/enroll_token_same_token_race.spec +80 -0
  65. package/tests/isolation/specs/onboarding_ceiling_lock_order.spec +1 -1
@@ -0,0 +1,538 @@
1
+ -- Consiliency/message-board#521 pgTAP: A VERIFIED PORTAL TOKEN ENROLLS AT MOST ONE KEY.
2
+ --
3
+ -- /enroll's proof-of-possession is the new key's signature over the token, so whoever
4
+ -- holds a token can mint a fresh key and sign it again; the #176 replay path is keyed on
5
+ -- the KEY and a fresh key walks past it. 20261005000100 binds each token (the sha256 of
6
+ -- its RS256 signing input, computed by the Edge Function) to one key fingerprint, in ONE
7
+ -- place: enroll_federated_identity, after its owner lock, before the found/not-found
8
+ -- split. This file pins the behaviour (T1-T10), the placement, and the class (EC-T1..4).
9
+ --
10
+ -- Rule 3 throughout: every allow-side call is `lives_ok(...)` followed by a SEPARATE state
11
+ -- check, never a bare `is((select ... from rpc(...)))` (#113 -- that form aborts the file
12
+ -- instead of failing it). Every call expected to raise goes through pg_temp.try(), a
13
+ -- BEGIN/EXCEPTION subtransaction that records '00000' when the call does NOT raise, so a
14
+ -- vanished raise fails an assertion rather than matching an absent row. Result tables are
15
+ -- declared up front, never CTAS'd from the call under test. Counts are scoped to this
16
+ -- file's own fixtures (its issuer, its fingerprints, its token hashes).
17
+ create extension if not exists pgtap with schema extensions;
18
+ set search_path = extensions, public, agent_board;
19
+
20
+ begin;
21
+
22
+ select plan(66);
23
+
24
+ -- ---------------------------------------------------------------------------
25
+ -- Fixtures
26
+ -- ---------------------------------------------------------------------------
27
+ create temp table _v (name text primary key, val text) on commit drop;
28
+ create temp table _err (label text primary key, state text, msg text) on commit drop;
29
+ create temp table _r (
30
+ label text, agent_id uuid, auth_user_id uuid, provider_id text, signing_key_id uuid,
31
+ granted_scopes text[], key_id text, key_version int, agent_handle text,
32
+ invite_redeemed boolean
33
+ ) on commit drop;
34
+ create temp table _f (label text, agent_id uuid, identity_disposition text) on commit drop;
35
+
36
+ create function pg_temp.v(p_name text) returns text language sql stable as
37
+ $$ select val from _v where name = p_name $$;
38
+
39
+ -- A token identity: lowercase hex sha256, exactly what the Edge Function sends.
40
+ create function pg_temp.tok(p_label text) returns text language sql immutable as
41
+ $$ select encode(sha256(convert_to('tok521-test-token-' || p_label, 'UTF8')), 'hex') $$;
42
+
43
+ -- The exact identity-route call, as SQL text. The hash and expiry are passed as SQL
44
+ -- expressions so the T9 gate cases can pass NULL and malformed values through the same
45
+ -- builder the allow-side cases use.
46
+ create function pg_temp.enroll_sql(
47
+ p_sub text, p_slug text, p_key int, p_hash_sql text, p_exp_sql text default null
48
+ ) returns text language sql stable as
49
+ $$
50
+ select format(
51
+ $q$select %L::text, * from agent_board.enroll_federated_identity(%L,'message-board',%L,%L::jsonb,%L,%L,'raw_ed25519_base64url',%L,true,%s,%s,null,'agent','{board:send}','{}')$q$,
52
+ p_slug, pg_temp.v('iss'), p_sub,
53
+ jsonb_build_object('iss', pg_temp.v('iss'), 'aud', 'message-board', 'sub', p_sub)::text,
54
+ p_slug, pg_temp.v('pub' || p_key), pg_temp.v('fp' || p_key),
55
+ p_hash_sql, coalesce(p_exp_sql, quote_literal(pg_temp.v('exp')) || '::timestamptz'))
56
+ $$;
57
+
58
+ -- The exact fleet-route call. `p_inv` names one of the fixture invites; its bounds fix the
59
+ -- authorized handle, which is the slug.
60
+ create function pg_temp.fleet_sql(
61
+ p_sub text, p_inv text, p_key int, p_hash_sql text, p_exp_sql text default null
62
+ ) returns text language sql stable as
63
+ $$
64
+ select format(
65
+ $q$select %L::text, r.agent_id, r.identity_disposition from agent_board.onboard_federated_fleet_agent(%L,'message-board',%L,%L::jsonb,%L,%L,'raw_ed25519_base64url',%L,true,%s,%s,null,'agent','{board:send}','{}',%L,%L::uuid,%L::uuid,'codex','phase-loop','message-board','sha256:tok521-repo','worker','phase_worker',%L) r$q$,
66
+ p_inv, pg_temp.v('iss'), p_sub,
67
+ jsonb_build_object('iss', pg_temp.v('iss'), 'aud', 'message-board', 'sub', p_sub)::text,
68
+ pg_temp.v('handle_' || p_inv), pg_temp.v('pub' || p_key), pg_temp.v('fp' || p_key),
69
+ p_hash_sql, coalesce(p_exp_sql, quote_literal(pg_temp.v('exp')) || '::timestamptz'),
70
+ pg_temp.v('code_' || p_inv), pg_temp.v('board'), pg_temp.v('machine'),
71
+ pg_temp.v('handle_' || p_inv))
72
+ $$;
73
+
74
+ create function pg_temp.try(p_label text, p_sql text) returns void language plpgsql as
75
+ $$
76
+ begin
77
+ begin
78
+ execute p_sql;
79
+ insert into _err values (p_label, '00000', '(no error raised)');
80
+ exception when others then
81
+ insert into _err values (p_label, sqlstate, sqlerrm);
82
+ end;
83
+ end
84
+ $$;
85
+
86
+ -- The fingerprint a token is bound to, or NULL.
87
+ create function pg_temp.bound(p_label text) returns text language sql stable as
88
+ $$ select agent_public_key_fingerprint from agent_board.enroll_token_uses where token_sha256 = pg_temp.tok(p_label) $$;
89
+
90
+ do $$
91
+ declare
92
+ s text := replace(substr(gen_random_uuid()::text, 1, 8), '-', '');
93
+ v_creator uuid := gen_random_uuid();
94
+ v_board uuid;
95
+ v_domain uuid;
96
+ v_machine uuid;
97
+ v_code text;
98
+ v_pub text;
99
+ i int;
100
+ inv text;
101
+ begin
102
+ insert into _v values
103
+ ('s', s), ('iss', 'https://tok521-' || s || '.test'),
104
+ ('suba', 'portal|tok521-a-' || s), ('subr', 'portal|tok521-r-' || s),
105
+ -- One fixed expiry for every allow-side call, whole seconds so the stored value
106
+ -- round-trips exactly (T1 compares it).
107
+ ('exp', (date_trunc('second', now()) + interval '1 hour')::text),
108
+ ('creator', v_creator::text);
109
+
110
+ insert into agent_board.identity_providers
111
+ (issuer, audience, supported, policy_approved, allowed_scopes, allowed_board_scopes, allowed_agent_kinds)
112
+ values (pg_temp.v('iss'), 'message-board', true, true, '{board:send}', '{}', '{agent}');
113
+
114
+ for i in 1..14 loop
115
+ v_pub := rtrim(translate(encode(gen_random_bytes(32), 'base64'), '+/', '-_'), '=');
116
+ insert into _v values ('pub' || i, v_pub), ('fp' || i, agent_board._raw_key_fingerprint(v_pub));
117
+ end loop;
118
+
119
+ -- Fleet fixture: creator, board, trust domain, machine, and one bounded invite per
120
+ -- fleet scenario (each invite fixes one authorized handle).
121
+ insert into agent_board.agents(id, agent_type, handle, display_name, status)
122
+ values (v_creator, 'agent', 'tok521-creator-' || s, 'Tok521 Creator', 'active');
123
+ select board_id into v_board from agent_board.create_board(
124
+ v_creator, 'tok521-board-' || s, 'Tok521 Board', '#521 pgTAP', '{}'::jsonb, 'private');
125
+ insert into agent_board.fleet_trust_domains(slug, display_name, domain_kind, owner_agent_id, lifecycle_state, approved_at)
126
+ values ('tok521-domain-' || s, 'Tok521 Domain', 'tailnet', v_creator, 'active', now())
127
+ returning id into v_domain;
128
+ insert into agent_board.fleet_machines(trust_domain_id, owner_agent_id, machine_label, machine_fingerprint,
129
+ lifecycle_state, enrollment_source, enrolled_at)
130
+ values (v_domain, v_creator, 'claw', 'sha256:tok521-machine-' || s, 'active', 'portal', now())
131
+ returning id into v_machine;
132
+ insert into _v values ('board', v_board::text), ('machine', v_machine::text);
133
+
134
+ foreach inv in array array['f', 'x', 'y', 'g'] loop
135
+ select code into v_code from agent_board.create_fleet_onboarding_invite(
136
+ v_board, v_machine, 'codex', 'phase-loop', 'message-board', 'sha256:tok521-repo',
137
+ 'worker', 'phase_worker', 'tok521-' || inv || '-' || s, 10, now() + interval '1 hour',
138
+ v_creator, '{}'::jsonb);
139
+ insert into _v values ('code_' || inv, v_code), ('handle_' || inv, 'tok521-' || inv || '-' || s);
140
+ end loop;
141
+ end
142
+ $$;
143
+
144
+ -- ---------------------------------------------------------------------------
145
+ -- T1 -- first use: the token binds to the key it enrolls
146
+ -- ---------------------------------------------------------------------------
147
+ select lives_ok(
148
+ (select 'insert into _r ' || pg_temp.enroll_sql(pg_temp.v('suba'), 'tok521-one-' || pg_temp.v('s'), 1, quote_literal(pg_temp.tok('T')))),
149
+ 'T1: identity (T, K1) enrolls');
150
+ select is(
151
+ (select count(*)::int from agent_board.agent_signing_keys where fingerprint = pg_temp.v('fp1')),
152
+ 1, 'T1: exactly one K1 key');
153
+ select is(
154
+ (select count(*)::int from agent_board.enrollment_federation_audit e
155
+ where e.agent_id = (select agent_id from _r where label like 'tok521-one-%')),
156
+ 1, 'T1: exactly one audit row for the new agent');
157
+ select is(pg_temp.bound('T'), pg_temp.v('fp1'), 'T1: the token row binds T to fp(K1)');
158
+ select is(
159
+ (select token_expires_at from agent_board.enroll_token_uses where token_sha256 = pg_temp.tok('T')),
160
+ pg_temp.v('exp')::timestamptz, 'T1: the token row records the verified expiry');
161
+
162
+ -- ---------------------------------------------------------------------------
163
+ -- T2 -- same token, same key: an ordinary retry, resumption unchanged
164
+ -- ---------------------------------------------------------------------------
165
+ select lives_ok(
166
+ (select 'insert into _r ' || replace(pg_temp.enroll_sql(pg_temp.v('suba'), 'tok521-one-' || pg_temp.v('s'), 1, quote_literal(pg_temp.tok('T'))),
167
+ 'select ''tok521-one-', 'select ''retry-tok521-one-')),
168
+ 'T2: identity (T, K1) again lives (same-key retry)');
169
+ select is(
170
+ (select agent_id from _r where label like 'retry-%'),
171
+ (select agent_id from _r where label like 'tok521-one-%'),
172
+ 'T2: the retry resumes the SAME agent');
173
+ select is(
174
+ (select count(*)::int from agent_board.agent_signing_keys where fingerprint = pg_temp.v('fp1')),
175
+ 1, 'T2: still exactly one K1 key');
176
+ select is(
177
+ (select count(*)::int from agent_board.enroll_token_uses where token_sha256 = pg_temp.tok('T')),
178
+ 1, 'T2: still exactly one row for T');
179
+
180
+ -- ---------------------------------------------------------------------------
181
+ -- T3 -- same token, DIFFERENT key: MBT01, and nothing is created
182
+ -- ---------------------------------------------------------------------------
183
+ select pg_temp.try('T3', pg_temp.enroll_sql(pg_temp.v('suba'), 'tok521-three-' || pg_temp.v('s'), 2, quote_literal(pg_temp.tok('T'))));
184
+ select is((select state from _err where label = 'T3'), 'MBT01',
185
+ 'T3: identity (T, K2) after (T, K1) is refused with SQLSTATE MBT01');
186
+ select is(
187
+ (select count(*)::int from agent_board.agent_signing_keys where fingerprint = pg_temp.v('fp2')),
188
+ 0, 'T3: no K2 key was created');
189
+ select is(
190
+ (select count(*)::int from agent_board.agents a where a.handle like 'tok521-three-%'),
191
+ 0, 'T3: no new agent was created');
192
+ select is(pg_temp.bound('T'), pg_temp.v('fp1'), 'T3: the token row still binds T to fp(K1)');
193
+
194
+ -- ---------------------------------------------------------------------------
195
+ -- T4 -- a NEW token enrolls a second key: the legitimate path
196
+ -- ---------------------------------------------------------------------------
197
+ select lives_ok(
198
+ (select 'insert into _r ' || pg_temp.enroll_sql(pg_temp.v('suba'), 'tok521-four-' || pg_temp.v('s'), 2, quote_literal(pg_temp.tok('T4')))),
199
+ 'T4: identity (T'', K2) enrolls a second agent');
200
+ select isnt(
201
+ (select agent_id from _r where label like 'tok521-four-%'),
202
+ (select agent_id from _r where label like 'tok521-one-%'),
203
+ 'T4: it is a DIFFERENT agent');
204
+ select is(pg_temp.bound('T4'), pg_temp.v('fp2'), 'T4: the new token binds to fp(K2)');
205
+
206
+ -- ---------------------------------------------------------------------------
207
+ -- T5 -- one KEY may consume many tokens (#176 retry after a lost response), but
208
+ -- binding covers resumption: a token that resumed K1 cannot then create K3
209
+ -- ---------------------------------------------------------------------------
210
+ select lives_ok(
211
+ (select 'insert into _r ' || replace(pg_temp.enroll_sql(pg_temp.v('suba'), 'tok521-one-' || pg_temp.v('s'), 1, quote_literal(pg_temp.tok('T5'))),
212
+ 'select ''tok521-one-', 'select ''t5-tok521-one-')),
213
+ 'T5: (T'''', K1) -- an already-enrolled key under a NEW token -- lives');
214
+ select is(
215
+ (select agent_id from _r where label like 't5-%'),
216
+ (select agent_id from _r where label like 'tok521-one-%'),
217
+ 'T5: it resumes the same agent');
218
+ select is(pg_temp.bound('T5'), pg_temp.v('fp1'), 'T5: and binds the new token to fp(K1)');
219
+ select pg_temp.try('T5-K3', pg_temp.enroll_sql(pg_temp.v('suba'), 'tok521-five-' || pg_temp.v('s'), 3, quote_literal(pg_temp.tok('T5'))));
220
+ select is((select state from _err where label = 'T5-K3'), 'MBT01',
221
+ 'T5: (T'''', K3) is refused MBT01 -- a token that RESUMED a key is bound as firmly as one that created it');
222
+ select is(
223
+ (select count(*)::int from agent_board.agent_signing_keys where fingerprint = pg_temp.v('fp3')),
224
+ 0, 'T5: no K3 key was created');
225
+
226
+ -- ---------------------------------------------------------------------------
227
+ -- T6 -- the gates around the bind still run for the same key
228
+ -- ---------------------------------------------------------------------------
229
+ -- Two different claims, kept apart because they prove different things. The human-revocation
230
+ -- gate runs BEFORE the bind, so its 42501 shows only that the bind did not displace it. The
231
+ -- key-revoked case is the fall-through proof: resumption gate (b) runs AFTER the bind, and a
232
+ -- same-key bind that returned early would skip it (T7 is the same proof for gate (c)).
233
+ select lives_ok(
234
+ (select 'insert into _r ' || pg_temp.enroll_sql(pg_temp.v('subr'), 'tok521-six-' || pg_temp.v('s'), 6, quote_literal(pg_temp.tok('T6')))),
235
+ 'T6: setup -- (T6, K6) enrolls for the subject about to be revoked');
236
+ insert into agent_board.fleet_human_revocations(issuer, subject, revoked_by_agent_id, revocation_reason)
237
+ values (pg_temp.v('iss'), pg_temp.v('subr'), pg_temp.v('creator')::uuid, '#521 pgTAP T6');
238
+ select pg_temp.try('T6-revoked-human', pg_temp.enroll_sql(pg_temp.v('subr'), 'tok521-six-' || pg_temp.v('s'), 6, quote_literal(pg_temp.tok('T6'))));
239
+ select is((select state from _err where label = 'T6-revoked-human'), '42501',
240
+ 'T6: the revocation gate BEFORE the bind still refuses the same (T6, K6) once the human is revoked (42501)');
241
+
242
+ select lives_ok(
243
+ (select 'insert into _r ' || pg_temp.enroll_sql(pg_temp.v('suba'), 'tok521-seven-' || pg_temp.v('s'), 7, quote_literal(pg_temp.tok('T7')))),
244
+ 'T6: setup -- (T7, K7) enrolls a key about to be revoked');
245
+ update agent_board.agent_signing_keys
246
+ set status = 'revoked', proof_state = 'revoked', revoked_at = now()
247
+ where fingerprint = pg_temp.v('fp7');
248
+ select pg_temp.try('T6-revoked-key', pg_temp.enroll_sql(pg_temp.v('suba'), 'tok521-seven-' || pg_temp.v('s'), 7, quote_literal(pg_temp.tok('T7'))));
249
+ select ok(
250
+ (select state = '23505' and msg like '%is not resumable%' from _err where label = 'T6-revoked-key'),
251
+ 'T6: the same (T7, K7) after its key is revoked is refused 23505 NOT RESUMABLE (the bind no-op did not return early)');
252
+
253
+ -- ---------------------------------------------------------------------------
254
+ -- T7 -- fall-through, frozen contract: a stamped enrollment still refuses drift
255
+ -- ---------------------------------------------------------------------------
256
+ select pg_temp.try('T7', pg_temp.enroll_sql(pg_temp.v('suba'), 'tok521-one-renamed-' || pg_temp.v('s'), 1, quote_literal(pg_temp.tok('T'))));
257
+ select ok(
258
+ (select state = '23505' and msg like '%frozen identity contract%' from _err where label = 'T7'),
259
+ 'T7: (T, K1) with a DIFFERENT slug still raises the 23505 frozen-contract refusal');
260
+
261
+ -- ---------------------------------------------------------------------------
262
+ -- T8 -- the fleet route, and both directions across routes
263
+ -- ---------------------------------------------------------------------------
264
+ select lives_ok(
265
+ (select 'insert into _f ' || pg_temp.fleet_sql(pg_temp.v('suba'), 'f', 4, quote_literal(pg_temp.tok('Tf')))),
266
+ 'T8: fleet (Tf, K4) onboards');
267
+ select is((select identity_disposition from _f where label = 'f'), 'created',
268
+ 'T8: fleet (Tf, K4) created its identity');
269
+ select pg_temp.try('T8-fleet-K5', pg_temp.fleet_sql(pg_temp.v('suba'), 'f', 5, quote_literal(pg_temp.tok('Tf'))));
270
+ select is((select state from _err where label = 'T8-fleet-K5'), 'MBT01',
271
+ 'T8: fleet (Tf, K5) after fleet (Tf, K4) is refused MBT01');
272
+ select is(
273
+ (select count(*)::int from agent_board.agent_signing_keys where fingerprint = pg_temp.v('fp5')),
274
+ 0, 'T8: the refused onboarding left no K5 key');
275
+ select is(
276
+ (select count(*)::int from agent_board.fleet_machine_agents b where b.machine_id = pg_temp.v('machine')::uuid),
277
+ 1, 'T8: the machine carries exactly one binding (K4''s), none for K5');
278
+ select is(
279
+ (select count(*)::int from agent_board.fleet_harness_registrations h where h.machine_id = pg_temp.v('machine')::uuid),
280
+ 1, 'T8: exactly one harness registration on the machine (K4''s, reused -- none added by the refused call)');
281
+ select is(
282
+ (select count(*)::int from agent_board.fleet_repo_scopes r
283
+ join agent_board.fleet_harness_registrations h on h.id = r.harness_registration_id
284
+ where h.machine_id = pg_temp.v('machine')::uuid),
285
+ 1, 'T8: exactly one repo scope on the machine (none added by the refused call)');
286
+
287
+ select lives_ok(
288
+ (select 'insert into _r ' || pg_temp.enroll_sql(pg_temp.v('suba'), pg_temp.v('handle_x'), 8, quote_literal(pg_temp.tok('Tx')))),
289
+ 'T8: identity (Tx, K8) enrolls');
290
+ select lives_ok(
291
+ (select 'insert into _f ' || pg_temp.fleet_sql(pg_temp.v('suba'), 'x', 8, quote_literal(pg_temp.tok('Tx')))),
292
+ 'T8: fleet (Tx, K8) after identity (Tx, K8) lives -- same token, same key, across routes');
293
+ select is((select identity_disposition from _f where label = 'x'), 'reused',
294
+ 'T8: and it REUSES the identity-route identity');
295
+ select pg_temp.try('T8-cross-K9', pg_temp.fleet_sql(pg_temp.v('suba'), 'x', 9, quote_literal(pg_temp.tok('Tx'))));
296
+ select is((select state from _err where label = 'T8-cross-K9'), 'MBT01',
297
+ 'T8: fleet (Tx, K9) after identity (Tx, K8) is refused MBT01');
298
+
299
+ select lives_ok(
300
+ (select 'insert into _f ' || pg_temp.fleet_sql(pg_temp.v('suba'), 'y', 10, quote_literal(pg_temp.tok('Ty')))),
301
+ 'T8: fleet (Ty, K10) onboards');
302
+ select lives_ok(
303
+ (select 'insert into _r ' || pg_temp.enroll_sql(pg_temp.v('suba'), pg_temp.v('handle_y'), 10, quote_literal(pg_temp.tok('Ty')))),
304
+ 'T8: identity (Ty, K10) after fleet (Ty, K10) lives');
305
+ select is(
306
+ (select agent_id from _r where label = pg_temp.v('handle_y')),
307
+ (select agent_id from _f where label = 'y'),
308
+ 'T8: and resumes the fleet-created identity');
309
+
310
+ -- ---------------------------------------------------------------------------
311
+ -- T9 -- the bind's input gates, on BOTH routes, and nothing is written
312
+ -- ---------------------------------------------------------------------------
313
+ select pg_temp.try('T9-i-null-hash', pg_temp.enroll_sql(pg_temp.v('suba'), 'tok521-nine-' || pg_temp.v('s'), 11, 'null'));
314
+ select pg_temp.try('T9-i-nonhex', pg_temp.enroll_sql(pg_temp.v('suba'), 'tok521-nine-' || pg_temp.v('s'), 11, quote_literal(repeat('g', 64))));
315
+ select pg_temp.try('T9-i-63', pg_temp.enroll_sql(pg_temp.v('suba'), 'tok521-nine-' || pg_temp.v('s'), 11, quote_literal(left(pg_temp.tok('T9'), 63))));
316
+ select pg_temp.try('T9-i-null-exp', pg_temp.enroll_sql(pg_temp.v('suba'), 'tok521-nine-' || pg_temp.v('s'), 11, quote_literal(pg_temp.tok('T9')), 'null'));
317
+ select pg_temp.try('T9-i-expired', pg_temp.enroll_sql(pg_temp.v('suba'), 'tok521-nine-' || pg_temp.v('s'), 11, quote_literal(pg_temp.tok('T9')), 'now()'));
318
+ select pg_temp.try('T9-f-null-hash', pg_temp.fleet_sql(pg_temp.v('suba'), 'g', 11, 'null'));
319
+ select pg_temp.try('T9-f-nonhex', pg_temp.fleet_sql(pg_temp.v('suba'), 'g', 11, quote_literal(repeat('g', 64))));
320
+ select pg_temp.try('T9-f-63', pg_temp.fleet_sql(pg_temp.v('suba'), 'g', 11, quote_literal(left(pg_temp.tok('T9'), 63))));
321
+ select pg_temp.try('T9-f-null-exp', pg_temp.fleet_sql(pg_temp.v('suba'), 'g', 11, quote_literal(pg_temp.tok('T9')), 'null'));
322
+ select pg_temp.try('T9-f-expired', pg_temp.fleet_sql(pg_temp.v('suba'), 'g', 11, quote_literal(pg_temp.tok('T9')), 'now()'));
323
+ select is(
324
+ (select string_agg(label || '=' || state, ' ' order by label) from _err where label like 'T9-i-%'),
325
+ 'T9-i-63=22023 T9-i-expired=22023 T9-i-nonhex=22023 T9-i-null-exp=22023 T9-i-null-hash=22023',
326
+ 'T9: identity route -- null hash, non-hex hash, 63-char hash, null expiry and expiry <= now() each raise 22023');
327
+ select is(
328
+ (select string_agg(label || '=' || state, ' ' order by label) from _err where label like 'T9-f-%'),
329
+ 'T9-f-63=22023 T9-f-expired=22023 T9-f-nonhex=22023 T9-f-null-exp=22023 T9-f-null-hash=22023',
330
+ 'T9: fleet route -- the same five inputs each raise 22023');
331
+ select is(
332
+ (select count(*)::int from agent_board.agent_signing_keys where fingerprint = pg_temp.v('fp11'))
333
+ + (select count(*)::int from agent_board.enroll_token_uses where token_sha256 = pg_temp.tok('T9')),
334
+ 0, 'T9: none of the ten refused calls wrote a key or a token row');
335
+
336
+ -- ---------------------------------------------------------------------------
337
+ -- T10 -- the bounded prune: 50 per call, and never a row inside the 1-hour margin
338
+ -- ---------------------------------------------------------------------------
339
+ insert into agent_board.enroll_token_uses(token_sha256, agent_public_key_fingerprint, token_expires_at)
340
+ select pg_temp.tok('old-' || i), 'sha256:tok521-prune', now() - interval '2 hours' - make_interval(secs => i)
341
+ from generate_series(1, 60) i;
342
+ insert into agent_board.enroll_token_uses(token_sha256, agent_public_key_fingerprint, token_expires_at)
343
+ values (pg_temp.tok('recent'), 'sha256:tok521-prune', now() - interval '30 minutes');
344
+ select lives_ok(
345
+ (select 'insert into _r ' || pg_temp.enroll_sql(pg_temp.v('suba'), 'tok521-ten-' || pg_temp.v('s'), 12, quote_literal(pg_temp.tok('T10')))),
346
+ 'T10: one enrollment runs the prune');
347
+ select is(
348
+ (select count(*)::int from agent_board.enroll_token_uses
349
+ where token_sha256 in (select pg_temp.tok('old-' || i) from generate_series(1, 60) i)),
350
+ 10, 'T10: exactly 50 of the 60 rows expired more than an hour ago were deleted');
351
+ -- A second enrollment drains the 10 old rows left over, and the recent row is then the ONLY
352
+ -- expired row within reach of the batch. The first call alone cannot pin the margin: with 60
353
+ -- older rows ahead of it in expiry order, a 50-row batch never reaches the recent row even
354
+ -- when the margin is gone (measured: removing the margin left the first call's assertions
355
+ -- green). This is the call where a missing margin deletes it.
356
+ select lives_ok(
357
+ (select 'insert into _r ' || pg_temp.enroll_sql(pg_temp.v('suba'), 'tok521-ten-b-' || pg_temp.v('s'), 13, quote_literal(pg_temp.tok('T10b')))),
358
+ 'T10: a second enrollment runs the prune again');
359
+ select is(
360
+ (select count(*)::int from agent_board.enroll_token_uses
361
+ where token_sha256 in (select pg_temp.tok('old-' || i) from generate_series(1, 60) i)),
362
+ 0, 'T10: the second prune drained the remaining 10 old rows');
363
+ select is(
364
+ (select count(*)::int from agent_board.enroll_token_uses where token_sha256 = pg_temp.tok('recent')),
365
+ 1, 'T10: the row expired only 30 minutes ago is kept even when the batch reaches it (the clock-skew margin)');
366
+
367
+ -- ---------------------------------------------------------------------------
368
+ -- T11 -- the bind decides expiry at the time it RUNS, not when its transaction began
369
+ -- ---------------------------------------------------------------------------
370
+ -- now() is fixed at transaction start; a real request can start, wait on the owner lock for
371
+ -- as long as another enrollment holds it, and only then bind. This file's transaction began
372
+ -- long before this point (and sleeps to make the margin certain), so now() < exp <
373
+ -- clock_timestamp() below: the state of a request whose token expired while it waited. With
374
+ -- a now()-based refusal it would bind; had the prune meanwhile removed the token's old row,
375
+ -- that would be a second key under one token (#524 round 1).
376
+ select pg_sleep(0.2);
377
+ select pg_temp.try('T11-late', pg_temp.enroll_sql(pg_temp.v('suba'), 'tok521-eleven-' || pg_temp.v('s'), 14,
378
+ quote_literal(pg_temp.tok('T11')), $$now() + interval '100 milliseconds'$$));
379
+ select ok(
380
+ (select state = '22023' and msg like '%has expired%' from _err where label = 'T11-late'),
381
+ 'T11: a token whose exp passed while the request waited (now() < exp <= clock_timestamp()) is refused 22023 expired');
382
+ select is(
383
+ (select count(*)::int from agent_board.enroll_token_uses where token_sha256 = pg_temp.tok('T11')),
384
+ 0, 'T11: and no row was written for it');
385
+ -- The prune reads the SAME clock, so its window matches the refusal: a row whose exp is
386
+ -- between now() - 1h and clock_timestamp() - 1h is one the refusal already rejects, and it
387
+ -- is deleted. A now()-based cutoff would keep it (harmless, but the two clocks would differ).
388
+ insert into agent_board.enroll_token_uses(token_sha256, agent_public_key_fingerprint, token_expires_at)
389
+ values (pg_temp.tok('T11-prune'), 'sha256:tok521-prune', now() - interval '1 hour' + interval '100 milliseconds');
390
+ select lives_ok(
391
+ (select 'insert into _r ' || pg_temp.enroll_sql(pg_temp.v('suba'), 'tok521-eleven-b-' || pg_temp.v('s'), 14, quote_literal(pg_temp.tok('T11b')))),
392
+ 'T11: an enrollment runs the prune');
393
+ select is(
394
+ (select count(*)::int from agent_board.enroll_token_uses where token_sha256 = pg_temp.tok('T11-prune')),
395
+ 0, 'T11: the prune cutoff is clock_timestamp() - 1 hour, the same clock the refusal uses');
396
+
397
+ -- ---------------------------------------------------------------------------
398
+ -- PLACEMENT -- the bind sits after the owner lock and before the found/not-found split
399
+ -- ---------------------------------------------------------------------------
400
+ -- Over stripped_body(), so a comment cannot satisfy it. Above the owner lock the identity
401
+ -- route deadlocks against the fleet route (enroll_token_placement.spec shows the runtime
402
+ -- half); below `for update of a, k` it would cover creation only, not resumption.
403
+ select ok(
404
+ (with b as (
405
+ select lower(agent_board_test.stripped_body(p.oid)) as body
406
+ from pg_proc p join pg_namespace n on n.oid = p.pronamespace
407
+ where n.nspname = 'agent_board' and p.proname = 'enroll_federated_identity')
408
+ select count(*) = 1
409
+ and bool_and(strpos(body, 'from auth.users u where u.id = v_owner_user_id for update') > 0
410
+ and strpos(body, 'from auth.users u where u.id = v_owner_user_id for update')
411
+ < strpos(body, 'perform agent_board._enroll_token_bind(')
412
+ and strpos(body, 'perform agent_board._enroll_token_bind(')
413
+ < strpos(body, 'for update of a, k'))
414
+ from b),
415
+ 'PLACEMENT: enroll_federated_identity binds the token AFTER its owner lock and BEFORE the found/not-found split');
416
+
417
+ -- ---------------------------------------------------------------------------
418
+ -- EC-T1 -- every API-executable route that accepts a key fingerprint carries the token,
419
+ -- enumerated from pg_proc
420
+ -- ---------------------------------------------------------------------------
421
+ select is(
422
+ (select string_agg(p.proname, ', ' order by p.proname)
423
+ from pg_proc p join pg_namespace n on n.oid = p.pronamespace
424
+ where n.nspname = 'agent_board'
425
+ and p.proargnames && array['p_agent_public_key_fingerprint']
426
+ and (has_function_privilege('anon', p.oid, 'EXECUTE')
427
+ or has_function_privilege('authenticated', p.oid, 'EXECUTE')
428
+ or has_function_privilege('service_role', p.oid, 'EXECUTE'))),
429
+ 'enroll_federated_identity, onboard_federated_fleet_agent',
430
+ 'EC-T1: exactly two API-executable routes accept a key fingerprint, one overload each (pg_proc-derived)');
431
+ select is(
432
+ (select string_agg(p.proname || ':' || (
433
+ array_position(p.proargnames, 'p_token_sha256') is not null
434
+ and array_position(p.proargnames, 'p_token_expires_at') is not null
435
+ and array_position(p.proargnames, 'p_token_sha256') <= p.pronargs - p.pronargdefaults
436
+ and array_position(p.proargnames, 'p_token_expires_at') <= p.pronargs - p.pronargdefaults)::text,
437
+ ', ' order by p.proname)
438
+ from pg_proc p join pg_namespace n on n.oid = p.pronamespace
439
+ where n.nspname = 'agent_board'
440
+ and p.proargnames && array['p_agent_public_key_fingerprint']
441
+ and (has_function_privilege('anon', p.oid, 'EXECUTE')
442
+ or has_function_privilege('authenticated', p.oid, 'EXECUTE')
443
+ or has_function_privilege('service_role', p.oid, 'EXECUTE'))),
444
+ 'enroll_federated_identity:true, onboard_federated_fleet_agent:true',
445
+ 'EC-T1: each carries p_token_sha256 and p_token_expires_at with NO default');
446
+
447
+ -- ---------------------------------------------------------------------------
448
+ -- EC-T2 -- the signing-key creator set, each member justified POSITIVELY
449
+ -- ---------------------------------------------------------------------------
450
+ select is(
451
+ (select string_agg(p.proname, ', ' order by p.proname)
452
+ from pg_proc p join pg_namespace n on n.oid = p.pronamespace
453
+ where n.nspname = 'agent_board' and p.prokind = 'f'
454
+ and agent_board_test.stripped_body(p.oid) ~ agent_board_test.stmt_re('insert into', 'agent_signing_keys')),
455
+ '_enroll_federated_identity_once, mint_delegated_child',
456
+ 'EC-T2: exactly two functions insert signing keys (the EC-2 form)');
457
+ select is(
458
+ (select string_agg(r || '=' || has_function_privilege(r, p.oid, 'EXECUTE')::text, ' ' order by r)
459
+ from pg_proc p join pg_namespace n on n.oid = p.pronamespace,
460
+ unnest(array['anon', 'authenticated', 'service_role']) r
461
+ where n.nspname = 'agent_board' and p.proname = '_enroll_federated_identity_once'),
462
+ 'anon=false authenticated=false service_role=false',
463
+ 'EC-T2: _enroll_federated_identity_once is EXECUTE-able by no API role');
464
+ select is(
465
+ (select string_agg(p.proname, ', ' order by p.proname)
466
+ from pg_proc p join pg_namespace n on n.oid = p.pronamespace
467
+ where n.nspname = 'agent_board' and p.proname <> '_enroll_federated_identity_once'
468
+ and agent_board_test.stripped_body(p.oid) ~ agent_board_test.call_re('_enroll_federated_identity_once')),
469
+ 'enroll_federated_identity',
470
+ 'EC-T2: _enroll_federated_identity_once''s only caller is enroll_federated_identity');
471
+ -- Counted per CALL SITE, not per calling function: a second bind call inside
472
+ -- enroll_federated_identity is a no-op under fall-through, so only this count sees it.
473
+ select is(
474
+ (select string_agg(p.proname || '=' || regexp_count(agent_board_test.stripped_body(p.oid), agent_board_test.call_re('_enroll_token_bind')), ', ' order by p.proname)
475
+ from pg_proc p join pg_namespace n on n.oid = p.pronamespace
476
+ where n.nspname = 'agent_board' and p.proname <> '_enroll_token_bind'
477
+ and agent_board_test.stripped_body(p.oid) ~ agent_board_test.call_re('_enroll_token_bind')),
478
+ 'enroll_federated_identity=1',
479
+ 'EC-T2: _enroll_token_bind has exactly ONE call site in the schema, in enroll_federated_identity (one guard site)');
480
+ select ok(
481
+ (select bool_and('p_root_agent_id' = any(p.proargnames)
482
+ and not (p.proargnames && array['p_issuer', 'p_subject', 'p_human_identity_claim']))
483
+ from pg_proc p join pg_namespace n on n.oid = p.pronamespace
484
+ where n.nspname = 'agent_board' and p.proname = 'mint_delegated_child'),
485
+ 'EC-T2: mint_delegated_child''s authority input is a parent agent, not a Portal token');
486
+
487
+ -- ---------------------------------------------------------------------------
488
+ -- EC-T3 -- the token table and its only writer are unreachable from the API
489
+ -- ---------------------------------------------------------------------------
490
+ select is(
491
+ (select count(*)::int
492
+ from unnest(array['anon', 'authenticated', 'service_role']) r,
493
+ unnest(array['SELECT', 'INSERT', 'UPDATE', 'DELETE']) pr
494
+ where has_table_privilege(r, 'agent_board.enroll_token_uses', pr)),
495
+ 0, 'EC-T3: no API role holds SELECT/INSERT/UPDATE/DELETE on enroll_token_uses');
496
+ select ok(
497
+ (select relrowsecurity from pg_class where oid = 'agent_board.enroll_token_uses'::regclass),
498
+ 'EC-T3: RLS is enabled on enroll_token_uses');
499
+ select is(
500
+ (select string_agg(a.attname, ',')
501
+ from pg_constraint c
502
+ join pg_attribute a on a.attrelid = c.conrelid and a.attnum = any(c.conkey)
503
+ where c.conrelid = 'agent_board.enroll_token_uses'::regclass and c.contype = 'p'),
504
+ 'token_sha256', 'EC-T3: the primary key (the fail-closed backstop) is on token_sha256');
505
+ select is(
506
+ (select string_agg(r || '=' || has_function_privilege(r, p.oid, 'EXECUTE')::text, ' ' order by r)
507
+ from pg_proc p join pg_namespace n on n.oid = p.pronamespace,
508
+ unnest(array['anon', 'authenticated', 'service_role']) r
509
+ where n.nspname = 'agent_board' and p.proname = '_enroll_token_bind'),
510
+ 'anon=false authenticated=false service_role=false',
511
+ 'EC-T3: _enroll_token_bind is EXECUTE-able by no API role');
512
+
513
+ -- ---------------------------------------------------------------------------
514
+ -- EC-T4 -- the recreated and new functions kept their comment, owner and ACL
515
+ -- ---------------------------------------------------------------------------
516
+ select is(
517
+ (select string_agg(p.proname, ', ' order by p.proname)
518
+ from pg_proc p join pg_namespace n on n.oid = p.pronamespace
519
+ where n.nspname = 'agent_board'
520
+ and p.proname in ('enroll_federated_identity', 'onboard_federated_fleet_agent',
521
+ '_enroll_federated_identity_once', '_enroll_token_bind')
522
+ and obj_description(p.oid, 'pg_proc') like '%#521%'
523
+ and p.proowner = (select q.proowner from pg_proc q join pg_namespace m on m.oid = q.pronamespace
524
+ where m.nspname = 'agent_board' and q.proname = '_enroll_federated_identity_once')),
525
+ '_enroll_federated_identity_once, _enroll_token_bind, enroll_federated_identity, onboard_federated_fleet_agent',
526
+ 'EC-T4: all four carry a #521 comment and the same owner as _enroll_federated_identity_once');
527
+ select is(
528
+ (select string_agg(p.proname || ':' || r || '=' || has_function_privilege(r, p.oid, 'EXECUTE')::text, ' ' order by p.proname, r)
529
+ from pg_proc p join pg_namespace n on n.oid = p.pronamespace,
530
+ unnest(array['anon', 'authenticated', 'public', 'service_role']) r
531
+ where n.nspname = 'agent_board'
532
+ and p.proname in ('enroll_federated_identity', 'onboard_federated_fleet_agent')),
533
+ 'enroll_federated_identity:anon=false enroll_federated_identity:authenticated=false enroll_federated_identity:public=false enroll_federated_identity:service_role=true '
534
+ || 'onboard_federated_fleet_agent:anon=false onboard_federated_fleet_agent:authenticated=false onboard_federated_fleet_agent:public=false onboard_federated_fleet_agent:service_role=true',
535
+ 'EC-T4: the recreated RPCs are EXECUTE-able by service_role only (DROP discarded the ACL; it was restated)');
536
+
537
+ select * from finish();
538
+ rollback;
@@ -31,7 +31,7 @@ begin
31
31
  select e.agent_id into v_agent
32
32
  from agent_board.enroll_federated_identity(
33
33
  'https://mock-portal-'||s||'.test', 'message-board', 'portal|'||s,
34
- v_claim, 'acfx-'||s, v_pub, 'raw_ed25519_base64url', v_fp, true, null, 'human', '{board:send}', '{board:x}') e;
34
+ v_claim, 'acfx-'||s, v_pub, 'raw_ed25519_base64url', v_fp, true, md5(random()::text) || md5(random()::text), now() + make_interval(hours => 1), null, 'human', '{board:send}', '{board:x}') e;
35
35
 
36
36
  insert into _acfx values (v_agent, v_claim);
37
37
  end
@@ -120,10 +120,10 @@ begin
120
120
  values ('https://rls-portal-'||s||'.test','message-board',true,true,'{board:send}','{board:x}','{agent,human}');
121
121
  select e.agent_id, e.auth_user_id into v_a_agent, v_a_user
122
122
  from agent_board.enroll_federated_identity('https://rls-portal-'||s||'.test','message-board','portal|a-'||s,
123
- jsonb_build_object('iss','https://rls-portal-'||s||'.test','aud','message-board','sub','portal|a-'||s,'name','A'), 'rls-a-'||s, v_pub_a,'raw_ed25519_base64url',v_fp_a,true,null,'human','{board:send}','{board:x}') e;
123
+ jsonb_build_object('iss','https://rls-portal-'||s||'.test','aud','message-board','sub','portal|a-'||s,'name','A'), 'rls-a-'||s, v_pub_a,'raw_ed25519_base64url',v_fp_a,true, md5(random()::text) || md5(random()::text), now() + make_interval(hours => 1),null,'human','{board:send}','{board:x}') e;
124
124
  select e.agent_id into v_b_agent
125
125
  from agent_board.enroll_federated_identity('https://rls-portal-'||s||'.test','message-board','portal|b-'||s,
126
- jsonb_build_object('iss','https://rls-portal-'||s||'.test','aud','message-board','sub','portal|b-'||s,'name','B'), 'rls-b-'||s, v_pub_b,'raw_ed25519_base64url',v_fp_b,true,null,'human','{board:send}','{board:x}') e;
126
+ jsonb_build_object('iss','https://rls-portal-'||s||'.test','aud','message-board','sub','portal|b-'||s,'name','B'), 'rls-b-'||s, v_pub_b,'raw_ed25519_base64url',v_fp_b,true, md5(random()::text) || md5(random()::text), now() + make_interval(hours => 1),null,'human','{board:send}','{board:x}') e;
127
127
  insert into _rls values (v_a_agent, v_a_user, v_b_agent);
128
128
  end
129
129
  $$;
@@ -321,7 +321,7 @@ select lives_ok(
321
321
  'https://lockorder194.test', 'message-board', 'sub-lo194',
322
322
  jsonb_build_object('iss','https://lockorder194.test','aud','message-board','sub','sub-lo194'),
323
323
  'lo194-a', public.test_pubkey('lo194-k1'), 'raw_ed25519_base64url',
324
- public.test_key_fingerprint('lo194-k1'), true, null, 'agent',
324
+ public.test_key_fingerprint('lo194-k1'), true, md5(random()::text) || md5(random()::text), now() + make_interval(hours => 1), null, 'agent',
325
325
  ARRAY[]::text[], ARRAY[]::text[], 'lo194-code-1') $$,
326
326
  'R-1: fresh federated enrollment with a usable invite succeeds');
327
327
  select is((select invite_redeemed from _e1), true,
@@ -342,7 +342,7 @@ select lives_ok(
342
342
  'https://lockorder194.test', 'message-board', 'sub-lo194',
343
343
  jsonb_build_object('iss','https://lockorder194.test','aud','message-board','sub','sub-lo194'),
344
344
  'lo194-b', public.test_pubkey('lo194-k2'), 'raw_ed25519_base64url',
345
- public.test_key_fingerprint('lo194-k2'), true, null, 'agent',
345
+ public.test_key_fingerprint('lo194-k2'), true, md5(random()::text) || md5(random()::text), now() + make_interval(hours => 1), null, 'agent',
346
346
  ARRAY[]::text[], ARRAY[]::text[], 'lo194-no-such-code') $$,
347
347
  'R-4: an unusable invite code still enrolls (soft-honor preserved)');
348
348
  select is((select invite_redeemed from _e2), false,
@@ -368,7 +368,7 @@ select throws_ok(
368
368
  'https://lockorder194.test', 'message-board', 'sub-lo194',
369
369
  jsonb_build_object('iss','https://lockorder194.test','aud','message-board','sub','sub-lo194'),
370
370
  'lo194-c', public.test_pubkey('lo194-k3'), 'raw_ed25519_base64url',
371
- public.test_key_fingerprint('lo194-k3'), true, null, 'agent',
371
+ public.test_key_fingerprint('lo194-k3'), true, md5(random()::text) || md5(random()::text), now() + make_interval(hours => 1), null, 'agent',
372
372
  ARRAY[]::text[], ARRAY[]::text[], 'lo194-code-2') $$,
373
373
  '40001', null,
374
374
  'R-6: class 40 during the CREATE-path redemption propagates to the caller');
@@ -380,7 +380,7 @@ select throws_ok(
380
380
  'https://lockorder194.test', 'message-board', 'sub-lo194',
381
381
  jsonb_build_object('iss','https://lockorder194.test','aud','message-board','sub','sub-lo194'),
382
382
  'lo194-a', public.test_pubkey('lo194-k1'), 'raw_ed25519_base64url',
383
- public.test_key_fingerprint('lo194-k1'), true, null, 'agent',
383
+ public.test_key_fingerprint('lo194-k1'), true, md5(random()::text) || md5(random()::text), now() + make_interval(hours => 1), null, 'agent',
384
384
  ARRAY[]::text[], ARRAY[]::text[], 'lo194-code-3') $$,
385
385
  '40001', null,
386
386
  'R-7: class 40 during the REPLAY-path redemption propagates to the caller');
@@ -395,7 +395,7 @@ select lives_ok(
395
395
  'https://lockorder194.test', 'message-board', 'sub-lo194',
396
396
  jsonb_build_object('iss','https://lockorder194.test','aud','message-board','sub','sub-lo194'),
397
397
  'lo194-a', public.test_pubkey('lo194-k1'), 'raw_ed25519_base64url',
398
- public.test_key_fingerprint('lo194-k1'), true, null, 'agent',
398
+ public.test_key_fingerprint('lo194-k1'), true, md5(random()::text) || md5(random()::text), now() + make_interval(hours => 1), null, 'agent',
399
399
  ARRAY[]::text[], ARRAY[]::text[], 'lo194-no-such-code') $$,
400
400
  'R-8: replay with an unusable code still succeeds');
401
401
  select is((select invite_redeemed from _e3), false,
@@ -233,7 +233,7 @@ begin
233
233
  from agent_board.enroll_federated_identity(
234
234
  f.iss, 'message-board', f.sub_a, jsonb_build_object('iss',f.iss,'aud','message-board','sub', f.sub_a),
235
235
  'idlink-'||f.s, v_pub, 'raw_ed25519_base64url', v_fp,
236
- true, null, 'agent', '{board:send}', '{}') e;
236
+ true, md5(random()::text) || md5(random()::text), now() + make_interval(hours => 1), null, 'agent', '{board:send}', '{}') e;
237
237
  insert into _idlink select a.owner_user_id from agent_board.agents a where a.id = v_agent;
238
238
  end
239
239
  $$;