@consiliency/agent-board-schema 1.68.0 → 1.74.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 (155) hide show
  1. package/fixtures/already-applied.json +28 -4
  2. package/migrations/20261026000100_agent_board_thread_association.sql +1287 -0
  3. package/migrations/20261101000100_agent_board_session_measured_mint.sql +145 -0
  4. package/migrations/20261104065658_agent_board_reply_delivery.sql +659 -0
  5. package/migrations/20261104170000_agent_board_tasking_replay_scope.sql +320 -0
  6. package/migrations/20261104180000_agent_board_delivery_writer_execute.sql +51 -0
  7. package/migrations/20261104190000_agent_board_message_replay_scope.sql +373 -0
  8. package/package.json +1 -1
  9. package/schema-manifest.json +57 -3
  10. package/src/tooling.test.mjs +8 -2
  11. package/tests/agent_board_661_reply_delivery.test.sql +185 -0
  12. package/tests/agent_board_667_boundary.test.sql +162 -0
  13. package/tests/agent_board_667_conversion_branches.test.sql +91 -0
  14. package/tests/agent_board_667_conversion_profile.test.sql +112 -0
  15. package/tests/agent_board_667_history_class.test.sql +785 -0
  16. package/tests/agent_board_667_native_cast_effect.test.sql +88 -0
  17. package/tests/agent_board_667_realtime_apply_behavior.test.sql +117 -0
  18. package/tests/agent_board_667_realtime_apply_controls.test.sql +143 -0
  19. package/tests/agent_board_667_realtime_apply_profile.test.sql +61 -0
  20. package/tests/agent_board_667_retention.test.sql +201 -0
  21. package/tests/agent_board_667_routing.test.sql +294 -0
  22. package/tests/agent_board_667_sender_effects.test.sql +100 -0
  23. package/tests/agent_board_667_sender_profile_controls.test.sql +29 -0
  24. package/tests/agent_board_667_storage_delimiter_behavior.test.sql +84 -0
  25. package/tests/agent_board_667_storage_delimiter_controls.test.sql +23 -0
  26. package/tests/agent_board_667_storage_delimiter_profile.test.sql +61 -0
  27. package/tests/agent_board_667_storage_search_behavior.test.sql +93 -0
  28. package/tests/agent_board_667_storage_search_controls.test.sql +22 -0
  29. package/tests/agent_board_667_storage_search_profile.test.sql +60 -0
  30. package/tests/agent_board_667_storage_timestamp_behavior.test.sql +58 -0
  31. package/tests/agent_board_667_storage_timestamp_controls.test.sql +22 -0
  32. package/tests/agent_board_667_storage_timestamp_profile.test.sql +54 -0
  33. package/tests/agent_board_667_writers.test.sql +281 -0
  34. package/tests/agent_board_717_message_replay_scope.test.sql +828 -0
  35. package/tests/agent_board_717_tasking_replay_scope.test.sql +661 -0
  36. package/tests/agent_board_addressing.test.sql +8 -5
  37. package/tests/agent_board_claim_subject_gate.test.sql +7 -3
  38. package/tests/agent_board_claimpolicy_race.test.sql +39 -11
  39. package/tests/agent_board_delegate_role_class.test.sql +33 -15
  40. package/tests/agent_board_delivery_writer_execute.test.sql +371 -0
  41. package/tests/agent_board_fk_lock_edges.test.sql +3 -0
  42. package/tests/agent_board_hosted_delegates.test.sql +5 -2
  43. package/tests/agent_board_inbox_rpcs.test.sql +5 -3
  44. package/tests/agent_board_obsgate.test.sql +8 -4
  45. package/tests/agent_board_obsgate4.test.sql +2 -2
  46. package/tests/agent_board_outbox_wake_ordering.test.sql +25 -2
  47. package/tests/agent_board_own_post_not_delivered.test.sql +11 -9
  48. package/tests/agent_board_session_identity_behavior_classes.test.sql +2 -2
  49. package/tests/agent_board_session_identity_limits.test.sql +1 -1
  50. package/tests/agent_board_session_identity_measured_mint.test.sql +342 -0
  51. package/tests/agent_board_session_identity_predicate_isolation.test.sql +146 -0
  52. package/tests/agent_board_sidserver_authority.test.sql +69 -0
  53. package/tests/agent_board_sidserver_catalog.test.sql +47 -0
  54. package/tests/agent_board_signed_write_gateway.test.sql +4 -2
  55. package/tests/agent_board_task_thread_lock_order.test.sql +67 -42
  56. package/tests/agent_board_v2_invariants.test.sql +5 -2
  57. package/tests/agent_board_write_enforcement_class.test.sql +3 -3
  58. package/tests/catalog/function_execute_privileges.json +41 -5
  59. package/tests/catalog/p_metadata_parameters.json +16 -0
  60. package/tests/fixtures/agent_board_667_apply_rls_guard.sql +195 -0
  61. package/tests/fixtures/agent_board_667_apply_rls_provenance.md +125 -0
  62. package/tests/fixtures/agent_board_667_apply_rls_reference.sql +1903 -0
  63. package/tests/fixtures/agent_board_667_conversion_guard.sql +162 -0
  64. package/tests/fixtures/agent_board_667_conversion_reference.md +23 -0
  65. package/tests/fixtures/agent_board_667_conversion_reference.sql +5325 -0
  66. package/tests/fixtures/agent_board_667_delimiter_guard.sql +92 -0
  67. package/tests/fixtures/agent_board_667_delimiter_provenance.md +116 -0
  68. package/tests/fixtures/agent_board_667_delimiter_reference.sql +10150 -0
  69. package/tests/fixtures/agent_board_667_search_guard.sql +92 -0
  70. package/tests/fixtures/agent_board_667_search_provenance.md +141 -0
  71. package/tests/fixtures/agent_board_667_search_reference.sql +4779 -0
  72. package/tests/fixtures/agent_board_667_sender_guard.sql +102 -0
  73. package/tests/fixtures/agent_board_667_sender_reference.md +20 -0
  74. package/tests/fixtures/agent_board_667_sender_reference.sql +875 -0
  75. package/tests/fixtures/agent_board_667_timestamp_graph.sql +84 -0
  76. package/tests/fixtures/agent_board_667_timestamp_guard.sql +94 -0
  77. package/tests/fixtures/agent_board_667_timestamp_provenance.md +99 -0
  78. package/tests/fixtures/agent_board_667_timestamp_reference.sql +2938 -0
  79. package/tests/isolation/expected/association_artifact_anchor_order.out +108 -0
  80. package/tests/isolation/expected/association_board_vs_board.out +71 -0
  81. package/tests/isolation/expected/association_post_wait_authority.out +77 -0
  82. package/tests/isolation/expected/association_strong_history_writers.out +203 -0
  83. package/tests/isolation/expected/association_strong_isolation.out +185 -0
  84. package/tests/isolation/expected/association_vs_artifact_claim_escalation.out +199 -0
  85. package/tests/isolation/expected/association_vs_heartbeat.out +78 -0
  86. package/tests/isolation/expected/association_vs_multi_anchor_artifact.out +67 -0
  87. package/tests/isolation/expected/association_vs_private_message.out +67 -0
  88. package/tests/isolation/expected/association_vs_task_and_conflict.out +133 -0
  89. package/tests/isolation/expected/association_vs_taskful_cross_thread.out +133 -0
  90. package/tests/isolation/expected/message_replay_scope.out +209 -0
  91. package/tests/isolation/expected/tasking_replay_scope.out +125 -0
  92. package/tests/isolation/specs/association_artifact_anchor_order.spec +46 -0
  93. package/tests/isolation/specs/association_board_vs_board.spec +37 -0
  94. package/tests/isolation/specs/association_post_wait_authority.spec +48 -0
  95. package/tests/isolation/specs/association_strong_history_writers.spec +61 -0
  96. package/tests/isolation/specs/association_strong_isolation.spec +50 -0
  97. package/tests/isolation/specs/association_vs_artifact_claim_escalation.spec +41 -0
  98. package/tests/isolation/specs/association_vs_heartbeat.spec +47 -0
  99. package/tests/isolation/specs/association_vs_multi_anchor_artifact.spec +38 -0
  100. package/tests/isolation/specs/association_vs_private_message.spec +33 -0
  101. package/tests/isolation/specs/association_vs_task_and_conflict.spec +38 -0
  102. package/tests/isolation/specs/association_vs_taskful_cross_thread.spec +42 -0
  103. package/tests/isolation/specs/message_replay_scope.spec +77 -0
  104. package/tests/isolation/specs/tasking_replay_scope.spec +72 -0
  105. package/tests/mutations/717-f1/M0_prefix_main_acl_and_comments.sql +11 -0
  106. package/tests/mutations/717-f1/M10_wrapper_stops_delegating.sql +7 -0
  107. package/tests/mutations/717-f1/M11_transitive_recipient_content.sql +8 -0
  108. package/tests/mutations/717-f1/M12_transitive_recipient_visibility.sql +8 -0
  109. package/tests/mutations/717-f1/M13_transitive_worker_content.sql +15 -0
  110. package/tests/mutations/717-f1/M1a_regrant_upsert_delivery_recipient.sql +1 -0
  111. package/tests/mutations/717-f1/M1b_regrant_upsert_inbox_item.sql +1 -0
  112. package/tests/mutations/717-f1/M1c_regrant_queue_inbox_callbacks.sql +1 -0
  113. package/tests/mutations/717-f1/M1d_regrant_queue_board_post_callbacks.sql +1 -0
  114. package/tests/mutations/717-f1/M1e_regrant_next_thread_message_sequence.sql +1 -0
  115. package/tests/mutations/717-f1/M2_blanket_schema_grant.sql +1 -0
  116. package/tests/mutations/717-f1/M3_new_overload_of_sink.sql +10 -0
  117. package/tests/mutations/717-f1/M4_new_route_calling_sink.sql +7 -0
  118. package/tests/mutations/717-f1/M5_actor_trusts_supplied_id.sql +5 -0
  119. package/tests/mutations/717-f1/M6_signed_route_granted_to_authenticated.sql +1 -0
  120. package/tests/mutations/717-f1/M7_worker_granted_to_authenticated.sql +1 -0
  121. package/tests/mutations/717-f1/M8_recipient_state_rewrites_visibility.sql +5 -0
  122. package/tests/mutations/717-f1/M9_public_route_revoked_from_service_role.sql +1 -0
  123. package/tests/mutations/717-f1/outbox_wake_ordering/W1_regrant_queue_inbox_callbacks.sql +1 -0
  124. package/tests/mutations/717-f1/outbox_wake_ordering/W2_revoke_worker_from_service_role.sql +1 -0
  125. package/tests/mutations/717-f1/outbox_wake_ordering/W3_invoker_caller_of_sink.sql +1 -0
  126. package/tests/mutations/717-f1/outbox_wake_ordering/W4_public_regrant_board_post_sink.sql +1 -0
  127. package/tests/mutations/717-f1/run.sh +17 -0
  128. package/tests/mutations/717-f2b/C1_new_delivery_route.sql +4 -0
  129. package/tests/mutations/717-f2b/C2_new_replay_lookup.sql +3 -0
  130. package/tests/mutations/717-f2b/C3_internal_regranted.sql +2 -0
  131. package/tests/mutations/717-f2b/C4_wrapper_writes_delivery.sql +9 -0
  132. package/tests/mutations/717-f2b/C5_class_member_dynamic_sql.sql +9 -0
  133. package/tests/mutations/717-f2b/C6_drop_board_scope_constraint.sql +2 -0
  134. package/tests/mutations/717-f2b/C7_state_private_helper_content.sql +14 -0
  135. package/tests/mutations/717-f2b/L1_dm_lock_split_by_recipient.sql +8 -0
  136. package/tests/mutations/717-f2b/L2_dm_lock_removed.sql +12 -0
  137. package/tests/mutations/717-f2b/L3_mc_lock_split_by_recipients.sql +8 -0
  138. package/tests/mutations/717-f2b/L4_mc_lock_removed.sql +12 -0
  139. package/tests/mutations/717-f2b/M0_prefix_definitions.sql +17 -0
  140. package/tests/mutations/717-f2b/M10_mc_lookup_any_author.sql +15 -0
  141. package/tests/mutations/717-f2b/M11_dm_swallow_refusal.sql +8 -0
  142. package/tests/mutations/717-f2b/M12_mc_swallow_refusal.sql +8 -0
  143. package/tests/mutations/717-f2b/M13_signed_dm_swallows_refusal.sql +32 -0
  144. package/tests/mutations/717-f2b/M14_signed_mc_swallows_refusal.sql +20 -0
  145. package/tests/mutations/717-f2b/M15_mc_any_source.sql +7 -0
  146. package/tests/mutations/717-f2b/M1_dm_drop_recipient_proof.sql +16 -0
  147. package/tests/mutations/717-f2b/M2_mc_drop_recipient_proof.sql +16 -0
  148. package/tests/mutations/717-f2b/M3_dm_metadata_as_proof.sql +12 -0
  149. package/tests/mutations/717-f2b/M4_mc_metadata_as_proof.sql +16 -0
  150. package/tests/mutations/717-f2b/M5_dm_any_mode.sql +11 -0
  151. package/tests/mutations/717-f2b/M6_dm_board_delivery_counts.sql +11 -0
  152. package/tests/mutations/717-f2b/M7_mc_any_mode.sql +11 -0
  153. package/tests/mutations/717-f2b/M8_dm_thread_stamp_as_proof.sql +12 -0
  154. package/tests/mutations/717-f2b/M9_dm_lookup_any_author.sql +15 -0
  155. package/tests/mutations/717-f2b/run.sh +82 -0
@@ -14,7 +14,7 @@ select * from _sid_behavior_fns f where f.body ~ agent_board_test.re(
14
14
  agent_board_test.write_frag('session_identities',agent_board_test.bare_ok(f.oid)) || '|'
15
15
  || agent_board_test.write_frag('session_id_aliases',agent_board_test.bare_ok(f.oid)));
16
16
  select is((select string_agg(proname,', ' order by proname) from _sid_behavior_writers),
17
- '_revoke_agent_principal, _session_identity_agent_lineage, approve_identity_state, heartbeat_session_identity, link_session_alias, mint_delegated_child, mint_fleet_agent_session, readmit_session_identity, record_session_identity_state, renew_session_identity',
17
+ '_revoke_agent_principal, _session_identity_agent_lineage, approve_identity_state, heartbeat_session_identity, link_session_alias, mint_delegated_child, mint_fleet_agent_session, mint_fleet_agent_session_measured, readmit_session_identity, record_session_identity_state, renew_session_identity',
18
18
  'EC-SIDSCHEMA-5-protected-population: every catalog-derived identity/alias writer has a gate or a positively proved lifecycle exemption');
19
19
  create temp view _sid_behavior_unguarded as
20
20
  select w.* from _sid_behavior_writers w
@@ -66,7 +66,7 @@ select is((select string_agg(proname,', ' order by proname) from _sid_behavior_i
66
66
  select is((select coalesce(string_agg(c.root_name,', ' order by c.root_name),'')
67
67
  from agent_board_test.fn_call_closure() c
68
68
  where c.reached_oid='agent_board._mint_fleet_agent_session_legacy(uuid,text,uuid,uuid,integer)'::regprocedure
69
- and c.root_oid<>c.reached_oid and c.root_name<>'mint_fleet_agent_session'),'',
69
+ and c.root_oid<>c.reached_oid and c.root_name<>'mint_fleet_agent_session'),'mint_fleet_agent_session_measured',
70
70
  'EC-SIDSCHEMA-4-issuer-wrapper-class: only the gated fleet session mint reaches the private legacy token issuer; a new bypass wrapper names itself');
71
71
  select ok(not has_function_privilege('service_role',
72
72
  'agent_board._mint_fleet_agent_session_legacy(uuid,text,uuid,uuid,integer)','EXECUTE')
@@ -160,7 +160,7 @@ from pg_proc p where p.pronamespace = 'agent_board'::regnamespace
160
160
  and (agent_board_test.stripped_body(p.oid) ~ agent_board_test.col_write_re('delegation_grants', 'grant_expires_at')
161
161
  or agent_board_test.stripped_body(p.oid) ~ agent_board_test.col_write_re('session_identities', 'holder_secret_hash'));
162
162
  select is((select string_agg(proname, ', ' order by proname) from _sid_activation_writers),
163
- '_session_identity_agent_lineage, mint_delegated_child, mint_fleet_agent_session, readmit_session_identity, renew_delegated_grant',
163
+ '_session_identity_agent_lineage, mint_delegated_child, mint_fleet_agent_session, mint_fleet_agent_session_measured, readmit_session_identity, renew_delegated_grant',
164
164
  'EC-SIDSCHEMA-6-activation-population: every discovered expiry or holder writer has a positive activation path');
165
165
  -- An INSERT is conservatively a writer of defaulted columns too. This one
166
166
  -- writes a legacy/default-NULL identity, not a live holder or grant. Prove the
@@ -0,0 +1,342 @@
1
+ -- SIDSERVER blocker fix (Consiliency/message-board#653 BLOCKs 1 and 2): the MEASURED mint
2
+ -- agent_board.mint_fleet_agent_session_measured, and its parity with every gate of the
3
+ -- seven-argument trusted mint_fleet_agent_session it is meant to replace for the relay. Allowed paths use lives_ok then a separate state
4
+ -- check (AGENTS.md rule 3). Refusal probes that must COMMIT their recording are asserted
5
+ -- with lives_ok (a raise would roll the freeze back), then by state.
6
+ create extension if not exists pgtap with schema extensions;
7
+ set search_path = extensions, public, agent_board;
8
+ begin;
9
+ grant usage on schema extensions to authenticated, service_role;
10
+ select plan(63);
11
+
12
+ create temp table _mm_fixture(label text primary key, root_id uuid, agent_id uuid, key_id uuid);
13
+ create temp table _mm_results(label text, session_id text, expires_at timestamptz, epoch bigint, secret text, refusal text);
14
+ do $$
15
+ declare v_label text; v_root uuid; v_agent uuid; v_key uuid; v_auth uuid; v_alias uuid; v_harness text;
16
+ begin
17
+ foreach v_label in array array['codex', 'codex-b', 'claude', 'claude-lapse', 'legacy', 'recorded', 'unresolved',
18
+ 'tomb', 'unproven', 'unclaimed', 'ancestor', 'replay'] loop
19
+ v_root := gen_random_uuid(); v_agent := gen_random_uuid(); v_key := gen_random_uuid();
20
+ v_harness := case when v_label like 'claude%' then 'claude' when v_label = 'legacy' then null else 'codex' end;
21
+ v_auth := public.ensure_github_user('mm-' || v_agent, 'mm-' || v_agent || '@example.test', 'mm-' || v_agent, 'MM');
22
+ insert into agent_board.agents(id, agent_type, handle, display_name, status)
23
+ values (v_root, 'agent', 'mm-root-' || v_root, 'MM root', 'active');
24
+ insert into agent_board.agents(id, agent_type, handle, display_name, status, auth_user_id, parent_agent_id, root_agent_id)
25
+ values (v_agent, 'agent', 'mm-' || v_agent, 'MM', 'active', v_auth, v_root, v_root);
26
+ insert into agent_board.agent_signing_keys(id, agent_id, key_id, key_version, public_key, public_key_format, fingerprint, status, proof_state)
27
+ values (v_key, v_agent, 'primary', 1, public.test_pubkey(v_key::text), 'raw_ed25519_base64url', public.test_key_fingerprint(v_key::text), 'active', 'proven');
28
+ insert into agent_board.session_identities(agent_id, root_agent_id, depth) values (v_root, v_root, 0);
29
+ insert into agent_board.session_identities(agent_id, parent_agent_id, root_agent_id, depth, identity_kind, harness, approved_risky_digest, current_risky_digest)
30
+ values (v_agent, v_root, v_root, 1, case when v_label = 'legacy' then 'legacy' else 'session' end, v_harness, 'approved', 'approved');
31
+ if v_harness is not null then
32
+ insert into agent_board.session_id_aliases(identity_agent_id, root_agent_id, harness, session_id)
33
+ values (v_agent, v_root, v_harness, 'bound-' || v_label) returning id into v_alias;
34
+ update agent_board.session_identities set lease_alias_id = v_alias where agent_id = v_agent;
35
+ end if;
36
+ insert into _mm_fixture values (v_label, v_root, v_agent, v_key);
37
+ end loop;
38
+ update agent_board.session_identities set tombstoned_at = now(), tombstone_reason = 'revoked'
39
+ where agent_id = (select agent_id from _mm_fixture where label = 'tomb');
40
+ update agent_board.session_identities set resolution_state = 'frozen'
41
+ where agent_id = (select root_id from _mm_fixture where label = 'ancestor');
42
+ end;
43
+ $$;
44
+ grant select on _mm_fixture to service_role;
45
+ grant select, insert on _mm_results to service_role;
46
+
47
+ -- INVOKER, so the real RPC runs as service_role. p_digest is the MEASURED risky digest.
48
+ create function pg_temp.mm(p_label text, p_result text, p_digest text, p_alias text, p_continuity text,
49
+ p_predecessor uuid default null, p_secret text default null, p_resolution text default 'resolved', p_recorded jsonb default '{}')
50
+ returns void language plpgsql as $$
51
+ declare v_fx record; v_issued record;
52
+ begin
53
+ select * into v_fx from _mm_fixture where label = p_label;
54
+ select * into v_issued from agent_board.issue_signing_key_challenge(v_fx.agent_id, 'primary');
55
+ insert into _mm_results
56
+ select p_result, m.session_id, m.expires_at, m.identity_lease_epoch, m.holder_secret, m.refusal
57
+ from agent_board.mint_fleet_agent_session_measured(v_issued.challenge_id, split_part(v_issued.challenge, ':', 3), v_fx.key_id, v_fx.agent_id,
58
+ 900, p_secret, p_alias, jsonb_build_object('resolution', p_resolution, 'risky_digest', p_digest, 'recorded', p_recorded),
59
+ p_continuity, p_predecessor) m;
60
+ end;
61
+ $$;
62
+ -- A fixed challenge, so a second use can prove the inherited single-use gate.
63
+ create temp table _mm_challenge(label text primary key, challenge_id uuid, nonce text);
64
+ grant select, insert on _mm_challenge to service_role;
65
+ create function pg_temp.mm_issue(p_label text) returns void language plpgsql as $$
66
+ declare v_fx record; v_issued record;
67
+ begin
68
+ select * into v_fx from _mm_fixture where label = p_label;
69
+ select * into v_issued from agent_board.issue_signing_key_challenge(v_fx.agent_id, 'primary');
70
+ insert into _mm_challenge values (p_label, v_issued.challenge_id, split_part(v_issued.challenge, ':', 3));
71
+ end;
72
+ $$;
73
+ create function pg_temp.mm_fixed(p_label text, p_result text) returns void language plpgsql as $$
74
+ declare v_fx record; v_ch record;
75
+ begin
76
+ select * into v_fx from _mm_fixture where label = p_label;
77
+ select * into v_ch from _mm_challenge where label = p_label;
78
+ insert into _mm_results
79
+ select p_result, m.session_id, m.expires_at, m.identity_lease_epoch, m.holder_secret, m.refusal
80
+ from agent_board.mint_fleet_agent_session_measured(v_ch.challenge_id, v_ch.nonce, v_fx.key_id, v_fx.agent_id, 900, null,
81
+ 'bound-' || p_label, '{"resolution":"resolved","risky_digest":"approved"}'::jsonb, 'same_alias', null) m;
82
+ end;
83
+ $$;
84
+ grant execute on function pg_temp.mm_issue(text), pg_temp.mm_fixed(text, text) to service_role;
85
+ grant execute on function pg_temp.mm(text, text, text, text, text, uuid, text, text, jsonb) to service_role;
86
+ -- The trusted seven-argument form, for the legacy-key relay exemption's positive footprint.
87
+ create function pg_temp.mm_trusted(p_label text, p_alias text)
88
+ returns void language plpgsql as $$
89
+ declare v_fx record; v_issued record;
90
+ begin
91
+ select * into v_fx from _mm_fixture where label = p_label;
92
+ select * into v_issued from agent_board.issue_signing_key_challenge(v_fx.agent_id, 'primary');
93
+ perform * from agent_board.mint_fleet_agent_session(v_issued.challenge_id, split_part(v_issued.challenge, ':', 3), v_fx.key_id, v_fx.agent_id, 900, null, p_alias);
94
+ end;
95
+ $$;
96
+ grant execute on function pg_temp.mm_trusted(text, text) to service_role;
97
+ create function pg_temp.mm_refuse(p_call text)
98
+ returns void language plpgsql as $$
99
+ begin
100
+ execute p_call;
101
+ raise exception 'expected refusal but RPC succeeded' using errcode = 'P0001';
102
+ end;
103
+ $$;
104
+ grant execute on function pg_temp.mm_refuse(text) to service_role;
105
+ create function pg_temp.mm_state(p_label text)
106
+ returns table(resolution_state text, current_risky_digest text, approved_risky_digest text, epoch bigint, holder boolean, recorded jsonb)
107
+ language sql as $$
108
+ select si.resolution_state, si.current_risky_digest, si.approved_risky_digest, si.credential_epoch, si.holder_secret_hash is not null, si.recorded_fingerprint
109
+ from agent_board.session_identities si join _mm_fixture f on f.agent_id = si.agent_id where f.label = p_label;
110
+ $$;
111
+ grant execute on function pg_temp.mm_state(text) to service_role;
112
+ -- Fixture clock: the runner's own role ages the lease (service_role has no UPDATE).
113
+ create function pg_temp.mm_lapse(p_label text) returns void language sql security definer as $$
114
+ update agent_board.session_identities set last_heartbeat = now() - interval '181 seconds'
115
+ where agent_id = (select agent_id from _mm_fixture where label = p_label);
116
+ $$;
117
+ create function pg_temp.mm_alias_count(p_label text, p_session text) returns bigint language sql as $$
118
+ select count(*) from agent_board.session_id_aliases a join _mm_fixture f on f.agent_id = a.identity_agent_id
119
+ where f.label = p_label and a.session_id = p_session;
120
+ $$;
121
+
122
+ -- Catalog: the mint class, enumerated from pg_proc, never from a written list.
123
+ select is((select string_agg(p.proname || '(' || pg_get_function_identity_arguments(p.oid) || ')', ' | ' order by p.proname)
124
+ from pg_proc p where p.pronamespace = 'agent_board'::regnamespace and p.proname in ('mint_fleet_agent_session', 'mint_fleet_agent_session_measured')),
125
+ 'mint_fleet_agent_session(p_challenge_id uuid, p_nonce text, p_signing_key_id uuid, p_agent_id uuid, p_max_ttl_seconds integer, p_holder_secret text, p_session_id text)'
126
+ || ' | mint_fleet_agent_session_measured(p_challenge_id uuid, p_nonce text, p_signing_key_id uuid, p_agent_id uuid, p_max_ttl_seconds integer, p_holder_secret text, p_session_id text, p_fingerprint jsonb, p_continuity text, p_predecessor_agent_id uuid)',
127
+ 'SIDSERVER-MM-CAT-01: exactly one trusted and one measured mint exist; any added overload names itself');
128
+ select ok((select bool_and(p.prosecdef and p.proconfig @> array['search_path=agent_board, public']
129
+ and has_function_privilege('service_role', p.oid, 'EXECUTE')
130
+ and not has_function_privilege('anon', p.oid, 'EXECUTE') and not has_function_privilege('authenticated', p.oid, 'EXECUTE')
131
+ and not exists (select 1 from aclexplode(coalesce(p.proacl, acldefault('f', p.proowner))) a where a.grantee = 0 and a.privilege_type = 'EXECUTE')
132
+ and obj_description(p.oid, 'pg_proc') is not null)
133
+ from pg_proc p where p.pronamespace = 'agent_board'::regnamespace and p.proname in ('mint_fleet_agent_session', 'mint_fleet_agent_session_measured')),
134
+ 'SIDSERVER-MM-CAT-02: both mints are pinned-search_path definers, service_role-only (no PUBLIC/anon/authenticated) and commented');
135
+ select is((select coalesce(string_agg(distinct c.root_name, ', ' order by c.root_name), '')
136
+ from agent_board_test.fn_call_closure() c join pg_proc p on p.oid = c.reached_oid
137
+ where p.pronamespace = 'agent_board'::regnamespace and p.proname in ('mint_fleet_agent_session', 'mint_fleet_agent_session_measured')
138
+ and c.root_oid <> c.reached_oid), '',
139
+ 'SIDSERVER-MM-CAT-03: no SQL function calls either mint; the relay is the only caller class (repo side: check-sidserver-measured-mint-callers)');
140
+ select ok(agent_board_test.stripped_body('agent_board.mint_fleet_agent_session_measured(uuid,text,uuid,uuid,integer,text,text,jsonb,text,uuid)'::regprocedure, true)
141
+ like '%_mint_fleet_agent_session_legacy(%',
142
+ 'SIDSERVER-MM-CAT-04: the measured mint validates and consumes the challenge through the private legacy issuer');
143
+ -- Gate parity, catalog half: every function the trusted form reaches (derived from pg_catalog,
144
+ -- not listed here) is reached by the measured form, so no inherited gate can be dropped by
145
+ -- omitting its call. The behavioural half is SIDSERVER-MM-GATE-* below.
146
+ select is((select coalesce(string_agg(distinct t.reached_oid::regprocedure::text, ', '), '')
147
+ from agent_board_test.fn_call_closure() t
148
+ where t.root_oid = 'agent_board.mint_fleet_agent_session(uuid,text,uuid,uuid,integer,text,text)'::regprocedure
149
+ and t.reached_oid <> t.root_oid
150
+ and not exists (select 1 from agent_board_test.fn_call_closure() m
151
+ where m.root_oid = 'agent_board.mint_fleet_agent_session_measured(uuid,text,uuid,uuid,integer,text,text,jsonb,text,uuid)'::regprocedure
152
+ and m.reached_oid = t.reached_oid)), '',
153
+ 'SIDSERVER-MM-PARITY-01: the measured mint reaches every function the trusted mint reaches');
154
+ select ok((select count(distinct t.reached_oid) >= 5 from agent_board_test.fn_call_closure() t
155
+ where t.root_oid = 'agent_board.mint_fleet_agent_session(uuid,text,uuid,uuid,integer,text,text)'::regprocedure and t.reached_oid <> t.root_oid),
156
+ 'SIDSERVER-MM-PARITY-01-nonvacuous: the trusted closure is populated (legacy issuer, ceiling, limits, lease, service-role gate)');
157
+
158
+ set local role service_role;
159
+ select set_config('request.jwt.claim.role', 'service_role', true);
160
+
161
+ -- BLOCK 1 control: a first claim measuring the approved digest issues a token.
162
+ select lives_ok($q$select pg_temp.mm('codex', 'first-a', 'approved', 'bound-codex', 'same_alias')$q$,
163
+ 'SIDSERVER-MM-FP-control: a first claim measuring the approved digest enters the measured mint');
164
+ select is((select (refusal is null and session_id is not null and epoch is not null and secret ~ '^[0-9a-f]{64}$') from _mm_results where label = 'first-a'),
165
+ true, 'SIDSERVER-MM-FP-control-state: the approved reading claims with a token, epoch and holder secret');
166
+ select is((select row(resolution_state, current_risky_digest)::text from pg_temp.mm_state('codex')), '(active,approved)',
167
+ 'SIDSERVER-MM-FP-control-recorded: the approved reading is recorded and stays active');
168
+
169
+ -- BLOCK 1: a live holder re-minting with a CHANGED measured digest is refused AND frozen.
170
+ select lives_ok(format($q$select pg_temp.mm('codex', 'remint-b', 'changed-b', 'bound-codex', 'same_alias', null, %L)$q$,
171
+ (select secret from _mm_results where label = 'first-a')),
172
+ 'SIDSERVER-MM-FP-remint-returns: a changed re-mint RETURNS its refusal so the freeze commits');
173
+ select is((select row(refusal, session_id, epoch, secret)::text from _mm_results where label = 'remint-b'),
174
+ '(identity_fingerprint_unapproved,,,)', 'SIDSERVER-MM-FP-remint-refused: a changed re-mint gets no session, epoch or holder secret');
175
+ select is((select row(resolution_state, current_risky_digest, approved_risky_digest)::text from pg_temp.mm_state('codex')),
176
+ '(frozen,changed-b,approved)', 'SIDSERVER-MM-FP-remint-frozen: the measured digest B is recorded and the identity frozen');
177
+ select is((select count(*) from agent_board.nonfleet_sessions s join _mm_fixture f on f.agent_id = s.agent_id
178
+ where f.label = 'codex' and s.revoked_at is null and s.session_id <> (select session_id from _mm_results where label = 'first-a')), 0::bigint,
179
+ 'SIDSERVER-MM-FP-remint-session-revoked: the refused mint leaves no unrevoked session row');
180
+ select is((select count(*) from agent_board.agent_signing_key_challenges c join _mm_fixture f on f.agent_id = c.agent_id
181
+ where f.label = 'codex' and c.consumed_at is null), 0::bigint,
182
+ 'SIDSERVER-MM-FP-remint-challenge-consumed: the refused proof is single-use');
183
+ select lives_ok(format($q$select pg_temp.mm('codex', 'after-freeze-a', 'approved', 'bound-codex', 'same_alias', null, %L)$q$,
184
+ (select secret from _mm_results where label = 'first-a')),
185
+ 'SIDSERVER-MM-FP-sticky-returns: an approved reading after the freeze still enters the measured mint');
186
+ select is((select refusal from _mm_results where label = 'after-freeze-a'), 'identity_fingerprint_unapproved',
187
+ 'SIDSERVER-MM-FP-sticky: the freeze is sticky; restoring the reading does not unfreeze');
188
+
189
+ -- BLOCK 1: a FIRST claim measuring digest B is refused and frozen, never claimed.
190
+ select lives_ok($q$select pg_temp.mm('codex-b', 'first-b', 'changed-b', 'bound-codex-b', 'same_alias')$q$,
191
+ 'SIDSERVER-MM-FP-first-returns: a changed first claim RETURNS its refusal so the freeze commits');
192
+ select is((select row(refusal, session_id, secret)::text from _mm_results where label = 'first-b'),
193
+ '(identity_fingerprint_unapproved,,)', 'SIDSERVER-MM-FP-first-refused: a changed first claim gets no session or holder secret');
194
+ select is((select row(resolution_state, current_risky_digest, holder)::text from pg_temp.mm_state('codex-b')),
195
+ '(frozen,changed-b,f)', 'SIDSERVER-MM-FP-first-frozen: digest B is recorded, frozen, and the lease is never claimed');
196
+
197
+ -- Unresolved never approves; recorded-only changes do not freeze.
198
+ -- The digest equals the approved one, so only the resolution term can freeze this reading.
199
+ select lives_ok($q$select pg_temp.mm('unresolved', 'unresolved', 'approved', 'bound-unresolved', 'same_alias', null, null, 'unresolved')$q$,
200
+ 'SIDSERVER-MM-FP-unresolved-returns: an unresolved reading enters and returns');
201
+ select is((select row(r.refusal, s.resolution_state)::text from _mm_results r, pg_temp.mm_state('unresolved') s where r.label = 'unresolved'),
202
+ '(identity_fingerprint_unapproved,frozen)', 'SIDSERVER-MM-FP-unresolved: an unresolved reading freezes and refuses');
203
+ select lives_ok($q$select pg_temp.mm('recorded', 'recorded', 'approved', 'bound-recorded', 'same_alias', null, null, 'resolved', '{"model":"m2"}')$q$,
204
+ 'SIDSERVER-MM-FP-recorded-only-control: a recorded-only change enters the measured mint');
205
+ select is((select row(r.refusal is null, s.resolution_state, s.recorded ->> 'model')::text from _mm_results r, pg_temp.mm_state('recorded') s where r.label = 'recorded'),
206
+ '(t,active,m2)', 'SIDSERVER-MM-FP-recorded-only: a recorded-only change is stored without freezing and still claims');
207
+
208
+ -- Measurement shape is mandatory on the measured form.
209
+ select throws_ok($q$select pg_temp.mm_refuse('select * from agent_board.mint_fleet_agent_session_measured(gen_random_uuid(), ''n'', gen_random_uuid(), gen_random_uuid(), 900, null, ''s'', null::jsonb, ''same_alias'', null)')$q$,
210
+ '22023', 'invalid identity fingerprint reading', 'SIDSERVER-MM-FP-reading-required: the measured form refuses a missing reading');
211
+ select throws_ok($q$select pg_temp.mm_refuse('select pg_temp.mm(''legacy'', ''legacy'', ''approved'', ''x'', ''same_alias'')')$q$,
212
+ '22023', 'measured mint requires a session identity', 'SIDSERVER-MM-FP-legacy-refused: a legacy identity cannot enter the measured form');
213
+
214
+ -- BLOCK 2: lapsed Claude reclaim through an UNFAMILIAR alias.
215
+ select lives_ok($q$select pg_temp.mm('claude', 'claude-first', 'approved', 'bound-claude', 'same_alias')$q$,
216
+ 'SIDSERVER-MM-CONT-control-first: the Claude initial reserved alias claims through the measured form');
217
+ select pg_temp.mm_lapse('claude');
218
+ select throws_ok($q$select pg_temp.mm_refuse('select pg_temp.mm(''claude'', ''x'', ''approved'', ''unseen-same'', ''same_alias'')')$q$,
219
+ '42501', 'identity_continuity_refused', 'SIDSERVER-MM-CONT-same-alias: a lapsed Claude same_alias claim cannot bind an unseen id');
220
+ select is(pg_temp.mm_alias_count('claude', 'unseen-same'), 0::bigint, 'SIDSERVER-MM-CONT-same-alias-no-row: no alias row is written');
221
+ select throws_ok($q$select pg_temp.mm_refuse('select pg_temp.mm(''claude'', ''x'', ''approved'', ''unseen-new'', ''new'')')$q$,
222
+ '42501', 'identity_continuity_refused', 'SIDSERVER-MM-CONT-new: a lapsed Claude "new" claim cannot bind an unseen id');
223
+ select is(pg_temp.mm_alias_count('claude', 'unseen-new'), 0::bigint, 'SIDSERVER-MM-CONT-new-no-row: no alias row is written');
224
+ select is((select epoch from pg_temp.mm_state('claude')), (select epoch from _mm_results where label = 'claude-first'),
225
+ 'SIDSERVER-MM-CONT-epoch-unchanged: refused unfamiliar-alias claims never rotate the epoch');
226
+ select throws_ok(format($q$select pg_temp.mm_refuse(%L)$q$, format($r$select pg_temp.mm('claude', 'x', 'approved', 'unseen-other', 'preserved_uuids', %L)$r$,
227
+ (select agent_id from _mm_fixture where label = 'codex'))),
228
+ '42501', 'identity_continuity_refused', 'SIDSERVER-MM-CONT-predecessor-other: preserved UUIDs naming another identity are refused');
229
+ select is(pg_temp.mm_alias_count('claude', 'unseen-other'), 0::bigint, 'SIDSERVER-MM-CONT-predecessor-other-no-row: no alias row is written');
230
+ select throws_ok(format($q$select pg_temp.mm_refuse(%L)$q$, format($r$select pg_temp.mm('claude', 'x', 'approved', 'bound-claude', 'same_alias', %L)$r$,
231
+ (select agent_id from _mm_fixture where label = 'claude'))),
232
+ '42501', 'identity_continuity_refused', 'SIDSERVER-MM-CONT-predecessor-without-preserved: only preserved UUIDs may name a predecessor');
233
+ reset role;
234
+ insert into agent_board.session_id_aliases(identity_agent_id, root_agent_id, harness, session_id)
235
+ select root_id, root_id, 'claude', 'taken-claude' from _mm_fixture where label = 'claude';
236
+ set local role service_role;
237
+ select throws_ok(format($q$select pg_temp.mm_refuse(%L)$q$, format($r$select pg_temp.mm('claude', 'x', 'approved', 'taken-claude', 'preserved_uuids', %L)$r$,
238
+ (select agent_id from _mm_fixture where label = 'claude'))),
239
+ '42501', 'identity_continuity_refused', 'SIDSERVER-MM-GATE-alias-collision: an id already bound under the same root and harness is never rebound');
240
+ select lives_ok(format($q$select pg_temp.mm('claude', 'claude-resumed', 'approved', 'resumed-claude', 'preserved_uuids', %L)$q$,
241
+ (select agent_id from _mm_fixture where label = 'claude')),
242
+ 'SIDSERVER-MM-CONT-preserved: measured preserved-UUID continuity of this identity reclaims through a new id');
243
+ select is((select row(r.refusal is null, r.secret ~ '^[0-9a-f]{64}$', r.epoch - (select epoch from _mm_results where label = 'claude-first'),
244
+ pg_temp.mm_alias_count('claude', 'resumed-claude'))::text from _mm_results r where r.label = 'claude-resumed'),
245
+ '(t,t,1,1)', 'SIDSERVER-MM-CONT-preserved-state: the resumed id is bound, a new holder issued and the epoch fenced');
246
+
247
+ -- Own-alias reclaim and the live-lease refusal are unchanged.
248
+ select pg_temp.mm_lapse('claude');
249
+ select lives_ok($q$select pg_temp.mm('claude', 'claude-own', 'approved', 'bound-claude', 'same_alias')$q$,
250
+ 'SIDSERVER-MM-CONT-own-alias: a lapsed holder reclaims through its own bound alias');
251
+ select is((select r.epoch - (select epoch from _mm_results where label = 'claude-resumed') from _mm_results r where r.label = 'claude-own'), 1::bigint,
252
+ 'SIDSERVER-MM-CONT-own-alias-epoch: own-alias reclaim fences the prior holder');
253
+ select throws_ok($q$select pg_temp.mm_refuse('select pg_temp.mm(''claude'', ''x'', ''approved'', ''bound-claude'', ''same_alias'')')$q$,
254
+ '42501', 'duplicate_live_session', 'SIDSERVER-MM-CONT-live-duplicate: the identical live session is still refused');
255
+ select throws_ok($q$select pg_temp.mm_refuse('select pg_temp.mm(''claude'', ''x'', ''approved'', ''resumed-claude'', ''same_alias'')')$q$,
256
+ '42501', 'identity_lease_live', 'SIDSERVER-MM-CONT-live-lease: another id cannot take a live lease');
257
+
258
+ -- Codex never binds a new alias, whatever it measures ('recorded' is a claimed Codex identity).
259
+ select pg_temp.mm_lapse('recorded');
260
+ select throws_ok($q$select pg_temp.mm_refuse('select pg_temp.mm(''recorded'', ''x'', ''approved'', ''unseen-codex'', ''same_alias'')')$q$,
261
+ '42501', 'identity_continuity_refused', 'SIDSERVER-MM-CONT-codex-same-alias: a lapsed Codex identity cannot bind an unseen id');
262
+ select throws_ok(format($q$select pg_temp.mm_refuse(%L)$q$, format($r$select pg_temp.mm('recorded', 'x', 'approved', 'unseen-codex', 'preserved_uuids', %L)$r$,
263
+ (select agent_id from _mm_fixture where label = 'recorded'))),
264
+ '42501', 'identity_continuity_refused', 'SIDSERVER-MM-CONT-codex-preserved: a lapsed Codex identity cannot bind an unseen id even claiming preserved UUIDs');
265
+ select is(pg_temp.mm_alias_count('recorded', 'unseen-codex'), 0::bigint, 'SIDSERVER-MM-CONT-codex-no-row: no Codex alias row is written');
266
+
267
+ -- Behavioural half of gate parity: one refusal per gate of the trusted form, entered through the
268
+ -- measured mint with every other gate satisfied.
269
+ select throws_ok($q$select pg_temp.mm_refuse('select pg_temp.mm(''tomb'', ''x'', ''approved'', ''bound-tomb'', ''same_alias'')')$q$,
270
+ '42501', 'identity_tombstoned', 'SIDSERVER-MM-GATE-tombstoned: a tombstoned identity is refused');
271
+ -- The challenge is issued while the key is still active and proven (issuance refuses an inactive
272
+ -- key); the key then loses its proof before the mint, so only the mint's own gate can refuse it.
273
+ select pg_temp.mm_issue('unproven');
274
+ reset role;
275
+ update agent_board.agent_signing_keys set status = 'revoked', revoked_at = now(), proof_state = 'unproven'
276
+ where id = (select key_id from _mm_fixture where label = 'unproven');
277
+ set local role service_role;
278
+ select throws_ok($q$select pg_temp.mm_refuse('select pg_temp.mm_fixed(''unproven'', ''x'')')$q$,
279
+ '42501', 'key_unproven: session key requires proof', 'SIDSERVER-MM-GATE-key-unproven: an unproven session key is refused before the challenge is used');
280
+ select throws_ok($q$select pg_temp.mm_refuse('select pg_temp.mm(''unclaimed'', ''x'', ''approved'', ''not-reserved'', ''same_alias'')')$q$,
281
+ '42501', 'identity_continuity_refused', 'SIDSERVER-MM-GATE-first-claim-alias: a first claim must use the reserved alias');
282
+ select lives_ok($q$select pg_temp.mm('ancestor', 'ancestor', 'approved', 'bound-ancestor', 'same_alias')$q$,
283
+ 'SIDSERVER-MM-GATE-frozen-ancestor-returns: a frozen ancestor refusal is returned, not raised');
284
+ select is((select row(r.refusal, r.secret, s.resolution_state)::text from _mm_results r, pg_temp.mm_state('ancestor') s where r.label = 'ancestor'),
285
+ '(identity_fingerprint_unapproved,,active)', 'SIDSERVER-MM-GATE-frozen-ancestor: a frozen ancestor refuses an approved child without freezing it');
286
+ select pg_temp.mm_issue('replay');
287
+ select lives_ok($q$select pg_temp.mm_fixed('replay', 'replay-first')$q$, 'SIDSERVER-MM-GATE-replay-control: the fixed challenge mints once');
288
+ reset role;
289
+ select pg_temp.mm_lapse('replay');
290
+ set local role service_role;
291
+ select throws_ok($q$select pg_temp.mm_refuse('select pg_temp.mm_fixed(''replay'', ''replay-again'')')$q$,
292
+ '23505', null, 'SIDSERVER-MM-GATE-nonce-replay: the inherited challenge gate refuses a second use');
293
+ select throws_ok(format($q$select pg_temp.mm_refuse(%L)$q$, format($r$select pg_temp.mm('claude', 'x', 'approved', 'resumed-claude', 'same_alias', null, %L)$r$,
294
+ repeat('0', 64))),
295
+ '42501', 'identity_lease_not_held', 'SIDSERVER-MM-GATE-holder-mismatch: a live lease with the wrong holder secret is refused');
296
+
297
+ -- Claimant-chosen holder secret (#653 re-panel BLOCK B): a claim stores the presented secret, so
298
+ -- a claimant whose response was lost refreshes its own live lease by re-presenting it, while any
299
+ -- other claimant of the identical session id is still refused.
300
+ select pg_temp.mm_lapse('claude');
301
+ select lives_ok(format($q$select pg_temp.mm('claude', 'claimant-claim', 'approved', 'bound-claude', 'same_alias', null, %L)$q$, repeat('a', 64)),
302
+ 'SIDSERVER-MM-CLAIMANT-claim-control: a lapsed holder reclaims presenting its own secret');
303
+ select lives_ok(format($q$select pg_temp.mm('claude', 'claimant-retry', 'approved', 'bound-claude', 'same_alias', null, %L)$q$, repeat('a', 64)),
304
+ 'SIDSERVER-MM-CLAIMANT-lost-response: re-presenting the claimed secret after a lost response is not refused');
305
+ select is((select row(c.secret = repeat('a', 64), r.refusal is null, r.secret is null, r.epoch = c.epoch)::text
306
+ from _mm_results c, _mm_results r where c.label = 'claimant-claim' and r.label = 'claimant-retry'),
307
+ '(t,t,t,t)', 'SIDSERVER-MM-CLAIMANT-stored: the claim stored the presented secret and the retry refreshed it at the same epoch');
308
+ select throws_ok(format($q$select pg_temp.mm_refuse(%L)$q$, format($r$select pg_temp.mm('claude', 'x', 'approved', 'bound-claude', 'same_alias', null, %L)$r$,
309
+ repeat('b', 64))),
310
+ '42501', 'duplicate_live_session', 'SIDSERVER-MM-CLAIMANT-other: another claimant of the identical live session id is refused whatever it presents');
311
+ select throws_ok(format($q$select pg_temp.mm_refuse(%L)$q$, $r$select pg_temp.mm('claude', 'x', 'approved', 'bound-claude', 'same_alias', null, 'not-a-secret')$r$),
312
+ '22023', 'invalid holder secret', 'SIDSERVER-MM-CLAIMANT-malformed: a presented secret that is not 64 lower hex is refused before it could be stored');
313
+
314
+ -- Positive footprint of the legacy-key relay exemption (deps.ts five-key call): the trusted
315
+ -- form WITHOUT a session id can never claim a session identity.
316
+ select lives_ok($q$select pg_temp.mm('claude-lapse', 'cl-first', 'approved', 'bound-claude-lapse', 'same_alias')$q$,
317
+ 'SIDSERVER-MM-EXEMPT-control: a Claude fixture claims before its lease lapses');
318
+ select pg_temp.mm_lapse('claude-lapse');
319
+ select throws_ok($q$select pg_temp.mm_refuse('select pg_temp.mm_trusted(''claude-lapse'', null)')$q$,
320
+ '42501', 'identity_continuity_refused', 'SIDSERVER-MM-EXEMPT-legacy-key-lapsed: the trusted form without a session id refuses a lapsed session identity');
321
+ select throws_ok($q$select pg_temp.mm_refuse('select pg_temp.mm_trusted(''codex-b'', null)')$q$,
322
+ '42501', 'identity_fingerprint_unapproved', 'SIDSERVER-MM-EXEMPT-legacy-key-frozen: the trusted form refuses the identity frozen by a measured mint');
323
+ reset role;
324
+ update agent_board.session_identities set resolution_state = 'active', current_risky_digest = 'approved'
325
+ where agent_id = (select agent_id from _mm_fixture where label = 'unresolved');
326
+ set local role service_role;
327
+ select throws_ok($q$select pg_temp.mm_refuse('select pg_temp.mm_trusted(''unresolved'', null)')$q$,
328
+ '42501', 'identity_continuity_refused', 'SIDSERVER-MM-EXEMPT-legacy-key-unclaimed: the trusted form without a session id refuses an unclaimed session identity');
329
+ -- And a direct non-service caller is refused by both overloads.
330
+ reset role;
331
+ select set_config('request.jwt.claim.role', 'authenticated', true);
332
+ set local role authenticated;
333
+ select throws_ok($q$select * from agent_board.mint_fleet_agent_session_measured(gen_random_uuid(), 'n', gen_random_uuid(), gen_random_uuid(), 900, null, 's', '{"resolution":"resolved","risky_digest":"approved"}'::jsonb, 'same_alias', null)$q$,
334
+ '42501', null, 'SIDSERVER-MM-CAT-05: authenticated cannot execute the measured mint');
335
+ reset role;
336
+ -- Inherited service-role gate, entered by a caller that CAN execute it (the owner) without the
337
+ -- service_role claim, so the grant cannot mask the in-body gate.
338
+ select throws_ok($q$select pg_temp.mm_refuse('select * from agent_board.mint_fleet_agent_session_measured(gen_random_uuid(), ''n'', gen_random_uuid(), gen_random_uuid(), 900, null, ''s'', ''{"resolution":"resolved","risky_digest":"approved"}''::jsonb, ''same_alias'', null)')$q$,
339
+ '42501', 'session mint is service_role-only', 'SIDSERVER-MM-GATE-service-role: a non-service claim is refused in the body, not only by the grant');
340
+
341
+ select * from finish();
342
+ rollback;
@@ -0,0 +1,146 @@
1
+ -- SIDSERVER SL-5 acceptance-audit closure (plan amendment 2026-11-01, decision 2): RPC-level predicate isolation for
2
+ -- the matrix rows `mismatch-renewed` (row 17) and `ancestor-digest-bypassed` (row 42). Every allow path is lives_ok
3
+ -- followed by a separate state check. Where another guard would mask a predicate, the assertion isolates it with
4
+ -- every other gate satisfied. renew_session_identity calls renew_delegated_grant, and that function repeats the
5
+ -- refusal, so these assertions pin the function that RAISED as well as the SQLSTATE and message. States the public
6
+ -- writers cannot produce (active but drifted, active with a null approval) are constructed inside this rolled-back
7
+ -- transaction, only to isolate a predicate. They are never an approved test parent.
8
+ create extension if not exists pgtap with schema extensions;
9
+ set search_path = extensions, public, agent_board;
10
+ begin;
11
+ grant usage on schema extensions to authenticated, service_role;
12
+ select plan(21);
13
+ create temp table _sid_iso_root(root_id uuid, root_auth uuid);
14
+ create temp table _sid_iso_children(label text primary key, agent_id uuid, key_id uuid, expires_at timestamptz);
15
+ create temp table _sid_iso_tokens(label text, expires_at timestamptz, epoch bigint);
16
+ create temp table _sid_iso_results(label text primary key, value boolean);
17
+ do $$
18
+ declare v_root uuid := gen_random_uuid(); v_auth uuid;
19
+ begin
20
+ v_auth := public.ensure_github_user('sid-iso-' || v_root, 'sid-iso-' || v_root || '@example.test', 'sid-iso-' || v_root, 'Isolation root');
21
+ insert into agent_board.agents(id, agent_type, handle, display_name, status, auth_user_id, owner_user_id)
22
+ values (v_root, 'agent', 'sid-iso-' || v_root, 'Isolation root', 'active', v_auth, v_auth);
23
+ insert into agent_board.session_identities(agent_id, root_agent_id, depth, current_risky_digest, approved_risky_digest)
24
+ values (v_root, v_root, 0, 'approved', 'approved');
25
+ insert into _sid_iso_root values (v_root, v_auth);
26
+ end;
27
+ $$;
28
+ grant select on _sid_iso_root, _sid_iso_children to authenticated, service_role;
29
+ grant insert on _sid_iso_children, _sid_iso_tokens, _sid_iso_results to service_role;
30
+ create function pg_temp.sid_iso_mint(p_label text, p_parent text default null)
31
+ returns void language plpgsql as $$
32
+ declare v_parent uuid;
33
+ begin
34
+ v_parent := coalesce((select agent_id from _sid_iso_children where label = p_parent), (select root_id from _sid_iso_root));
35
+ insert into _sid_iso_children
36
+ select p_label, m.child_agent_id, m.signing_key_id, m.grant_expires_at
37
+ from agent_board.mint_delegated_child(v_parent, 'sid-iso-' || p_label || '-' || substr(v_parent::text, 1, 8), '[]',
38
+ public.test_pubkey(p_label || v_parent), interval '30 days', false, 'claude', 'iso-' || p_label,
39
+ case when p_parent is not null then '0' end, '[]', '{"resolution":"resolved","risky_digest":"approved"}'::jsonb) m;
40
+ end;
41
+ $$;
42
+ create function pg_temp.sid_iso_token(p_label text)
43
+ returns void language plpgsql as $$
44
+ declare v_child record; v_challenge record;
45
+ begin
46
+ select * into v_child from _sid_iso_children where label = p_label;
47
+ select * into v_challenge from agent_board.issue_signing_key_challenge(v_child.agent_id, 'delegated-ephemeral');
48
+ insert into _sid_iso_tokens
49
+ select p_label, m.expires_at, m.identity_lease_epoch from agent_board.mint_fleet_agent_session(
50
+ v_challenge.challenge_id, split_part(v_challenge.challenge, ':', 3), v_child.key_id, v_child.agent_id, 900, null, 'iso-' || p_label) m;
51
+ end;
52
+ $$;
53
+ -- Never raises: returns '<sqlstate>|<message>|<name of the function that raised>' for a refusal, or 'no refusal'.
54
+ create function pg_temp.sid_iso_refusal(p_call text)
55
+ returns text language plpgsql as $$
56
+ declare v_state text; v_message text; v_context text;
57
+ begin
58
+ execute p_call;
59
+ return 'no refusal';
60
+ exception when others then
61
+ get stacked diagnostics v_state = returned_sqlstate, v_message = message_text, v_context = pg_exception_context;
62
+ return v_state || '|' || v_message || '|'
63
+ || coalesce(substring(split_part(v_context, E'\n', 1) from 'function (?:[a-z_]+\.)?([a-z_]+)\('), 'unknown');
64
+ end;
65
+ $$;
66
+ grant execute on function pg_temp.sid_iso_mint(text, text), pg_temp.sid_iso_token(text), pg_temp.sid_iso_refusal(text) to service_role, authenticated;
67
+ select set_config('request.jwt.claim.role', 'service_role', true);
68
+ set local role service_role;
69
+ select lives_ok($q$select agent_board.upsert_delegation_policy((select root_id from _sid_iso_root), 'root_minted', '[]', 200, interval '30 days', true)$q$,
70
+ 'SIDSERVER-FP-03/isolation-policy-control: operator configures the root delegation policy');
71
+ select lives_ok($q$select pg_temp.sid_iso_mint('parent'); select pg_temp.sid_iso_mint('target', 'parent')$q$,
72
+ 'SIDSERVER-FP-03/isolation-mint-control: approved parent and target session children enter the real issuer');
73
+ reset role;
74
+ select is((select count(*) from _sid_iso_children), 2::bigint, 'SIDSERVER-FP-03/isolation-mint-state: both principals exist');
75
+ select is((select array[approved_risky_digest, current_risky_digest, resolution_state] from agent_board.session_identities
76
+ where agent_id = (select agent_id from _sid_iso_children where label = 'parent')), array['approved', 'approved', 'active'],
77
+ 'SIDSERVER-FP-03/isolation-parent-state: the parent is approved, unchanged and active');
78
+ set local role service_role;
79
+ select lives_ok($q$select pg_temp.sid_iso_token('parent')$q$,
80
+ 'SIDSERVER-FP-03/isolation-token-control: real challenge and key gates claim the parent lease');
81
+ reset role;
82
+ select is((select count(*) from _sid_iso_tokens where label = 'parent'), 1::bigint, 'SIDSERVER-FP-03/isolation-token-state: the parent holds an epoch-fenced lease');
83
+ select set_config('request.jwt.claim.sub', (select auth_user_id::text from agent_board.agents where id = (select agent_id from _sid_iso_children where label = 'parent')), true);
84
+ select set_config('request.jwt.claim.identity_lease_epoch', (select epoch::text from _sid_iso_tokens where label = 'parent'), true);
85
+ update agent_board.delegation_grants set grant_expires_at = now() + interval '1 hour' where child_agent_id = (select agent_id from _sid_iso_children where label = 'parent');
86
+ set local role service_role;
87
+ select lives_ok($q$select agent_board.renew_session_identity((select agent_id from _sid_iso_children where label = 'parent'), 'approved')$q$,
88
+ 'SIDSERVER-FP-03/isolation-renew-control: the approved live holder renews through renew_session_identity');
89
+ reset role;
90
+ select is((select grant_expires_at from agent_board.delegation_grants where child_agent_id = (select agent_id from _sid_iso_children where label = 'parent')), now() + interval '7 days',
91
+ 'SIDSERVER-FP-03/isolation-renew-state: that renewal extends the grant to the seven-day ceiling');
92
+ update agent_board.delegation_grants set grant_expires_at = now() + interval '1 hour' where child_agent_id = (select agent_id from _sid_iso_children where label = 'parent');
93
+ set local role service_role;
94
+ select lives_ok($q$select agent_board.renew_delegated_grant((select agent_id from _sid_iso_children where label = 'parent'), interval '2 hours')$q$,
95
+ 'SIDSERVER-FP-03/isolation-delegated-control: the approved live holder renews through renew_delegated_grant');
96
+ reset role;
97
+ select is((select grant_expires_at from agent_board.delegation_grants where child_agent_id = (select agent_id from _sid_iso_children where label = 'parent')), now() + interval '2 hours',
98
+ 'SIDSERVER-FP-03/isolation-delegated-state: that renewal extends the grant by the requested interval');
99
+
100
+ -- Row 17, renew_session_identity/current-vs-approved: active, approved = supplied, current drifted.
101
+ update agent_board.session_identities set current_risky_digest = 'changed' where agent_id = (select agent_id from _sid_iso_children where label = 'parent');
102
+ set local role service_role;
103
+ select is(pg_temp.sid_iso_refusal($q$select agent_board.renew_session_identity((select agent_id from _sid_iso_children where label = 'parent'), 'approved')$q$),
104
+ '42501|identity_fingerprint_unapproved|renew_session_identity',
105
+ 'SIDSERVER-FP-03/mismatch/renew_session_identity/current-vs-approved: renew_session_identity itself refuses a drifted current reading before the delegated grant gate');
106
+ reset role;
107
+ -- Row 17, renew_session_identity/approved-null and renew_delegated_grant/approved-null: active, no approval, no reading.
108
+ update agent_board.session_identities set current_risky_digest = null, approved_risky_digest = null where agent_id = (select agent_id from _sid_iso_children where label = 'parent');
109
+ select is((select array[coalesce(approved_risky_digest, '<null>'), coalesce(current_risky_digest, '<null>'), resolution_state] from agent_board.session_identities
110
+ where agent_id = (select agent_id from _sid_iso_children where label = 'parent')), array['<null>', '<null>', 'active'],
111
+ 'SIDSERVER-FP-03/isolation-null-state: the constructed identity is active with neither approval nor reading');
112
+ set local role service_role;
113
+ select is(pg_temp.sid_iso_refusal($q$select agent_board.renew_session_identity((select agent_id from _sid_iso_children where label = 'parent'), null)$q$),
114
+ '42501|identity_fingerprint_unapproved|renew_session_identity',
115
+ 'SIDSERVER-FP-03/mismatch/renew_session_identity/approved-null: renew_session_identity itself refuses an identity without an approval before the delegated grant gate');
116
+ select is(pg_temp.sid_iso_refusal($q$select agent_board.renew_delegated_grant((select agent_id from _sid_iso_children where label = 'parent'), interval '3 hours')$q$),
117
+ '42501|identity_fingerprint_unapproved|renew_delegated_grant',
118
+ 'SIDSERVER-FP-03/mismatch/renew_delegated_grant/approved-null: renew_delegated_grant refuses a session identity without an approval');
119
+ reset role;
120
+ select is((select grant_expires_at from agent_board.delegation_grants where child_agent_id = (select agent_id from _sid_iso_children where label = 'parent')), now() + interval '2 hours',
121
+ 'SIDSERVER-FP-03/isolation-null-no-renewal: neither refused renewal extended the grant');
122
+ update agent_board.session_identities set current_risky_digest = 'approved', approved_risky_digest = 'approved' where agent_id = (select agent_id from _sid_iso_children where label = 'parent');
123
+
124
+ -- Row 42: _session_identity_ancestor_actor with the parent as the verified current actor and the target's reading approved.
125
+ set local role service_role;
126
+ select lives_ok($q$insert into _sid_iso_results select 'ancestor-control', agent_board._session_identity_ancestor_actor((select agent_id from _sid_iso_children where label = 'target'))$q$,
127
+ 'SIDSERVER-APPROVE-01/ancestor-digest/isolation-control: the predicate is evaluated for the approved live parent');
128
+ reset role;
129
+ select is((select value from _sid_iso_results where label = 'ancestor-control'), true,
130
+ 'SIDSERVER-APPROVE-01/ancestor-digest/isolation-state: the approved live parent is the target''s ancestor approver');
131
+ update agent_board.session_identities set current_risky_digest = 'drifted' where agent_id = (select agent_id from _sid_iso_children where label = 'parent');
132
+ set local role service_role;
133
+ select lives_ok($q$insert into _sid_iso_results select 'ancestor-current', agent_board._session_identity_ancestor_actor((select agent_id from _sid_iso_children where label = 'target'))$q$,
134
+ 'SIDSERVER-APPROVE-01/ancestor-digest/current-control: the predicate is evaluated for an active parent whose reading drifted');
135
+ reset role;
136
+ select is((select value from _sid_iso_results where label = 'ancestor-current'), false,
137
+ 'SIDSERVER-APPROVE-01/ancestor-digest/current: an active parent whose own current reading drifted from its approval cannot approve the target');
138
+ update agent_board.session_identities set current_risky_digest = null, approved_risky_digest = null where agent_id = (select agent_id from _sid_iso_children where label = 'parent');
139
+ set local role service_role;
140
+ select lives_ok($q$insert into _sid_iso_results select 'ancestor-null', agent_board._session_identity_ancestor_actor((select agent_id from _sid_iso_children where label = 'target'))$q$,
141
+ 'SIDSERVER-APPROVE-01/ancestor-digest/non-null-control: the predicate is evaluated for an active parent without any approval');
142
+ reset role;
143
+ select is((select value from _sid_iso_results where label = 'ancestor-null'), false,
144
+ 'SIDSERVER-APPROVE-01/ancestor-digest/non-null: an active parent without any approval cannot approve the target');
145
+ select * from finish();
146
+ rollback;
@@ -0,0 +1,69 @@
1
+ create extension if not exists pgtap with schema extensions;
2
+ set search_path = extensions, public, agent_board;
3
+ begin;
4
+ grant usage on schema extensions to service_role;
5
+ select plan(12);
6
+ create temp table _sidserver_authority(root_id uuid, board_id uuid, absent_board_id uuid, suffix text);
7
+ create temp table _sidserver_children(label text primary key, agent_id uuid);
8
+ do $$
9
+ declare r uuid := gen_random_uuid(); b uuid := gen_random_uuid(); other_b uuid := gen_random_uuid(); a uuid;
10
+ begin
11
+ a := public.ensure_github_user('sidserver-authority-'||r, 'sidserver-authority-'||r||'@example.test', 'sidserver-authority-'||r, 'SIDSERVER parent');
12
+ insert into agent_board.agents(id,agent_type,handle,display_name,status,auth_user_id)
13
+ values(r,'agent','sidserver-authority-'||r,'SIDSERVER parent','active',a);
14
+ insert into agent_board.session_identities(agent_id,root_agent_id,depth,current_risky_digest,approved_risky_digest)
15
+ values(r,r,0,repeat('a',64),repeat('a',64));
16
+ insert into agent_board.boards(id,slug,name,created_by_agent_id)
17
+ values(b,'sidserver-authority-'||b,'SIDSERVER room',r), (other_b,'sidserver-authority-'||other_b,'SIDSERVER absent room',r);
18
+ insert into agent_board.board_memberships(board_id,agent_id,role,membership_state) values(b,r,'member','joined');
19
+ insert into _sidserver_authority values(r,b,other_b,substr(r::text,1,8));
20
+ end;
21
+ $$;
22
+ grant select on _sidserver_authority, _sidserver_children to service_role;
23
+ grant insert on _sidserver_children to service_role;
24
+ create function pg_temp.sidserver_child(p_label text,p_scopes jsonb,p_role text default null,p_absent boolean default false)
25
+ returns void language plpgsql as $$
26
+ declare r uuid; b uuid; s text; rooms jsonb := '[]';
27
+ begin
28
+ select root_id,case when p_absent then absent_board_id else board_id end,suffix into r,b,s from _sidserver_authority;
29
+ if p_role is not null then rooms := jsonb_build_array(jsonb_build_object('board_id',b,'role',p_role)); end if;
30
+ insert into _sidserver_children select p_label,m.child_agent_id
31
+ from agent_board.mint_delegated_child(r,'sidserver-'||p_label||'-'||s,p_scopes,public.test_pubkey(s||p_label),interval '1 hour',false,
32
+ 'codex','sidserver-'||p_label,null,rooms,jsonb_build_object('resolution','resolved','risky_digest',repeat('a',64),'recorded','{}'::jsonb),null) m;
33
+ end;
34
+ $$;
35
+ grant execute on function pg_temp.sidserver_child(text,jsonb,text,boolean) to service_role;
36
+ select set_config('request.jwt.claim.role','service_role',true);
37
+ set local role service_role;
38
+ select lives_ok($q$select agent_board.upsert_delegation_policy((select root_id from _sidserver_authority),'root_minted','["read"]',200,interval '1 hour',true)$q$,
39
+ 'SIDSERVER-authority-policy-control: actual parent scope policy is configured');
40
+ select lives_ok($q$select pg_temp.sidserver_child('allowed','["read"]','observer')$q$,
41
+ 'SIDSERVER-authority-room-control: a narrower explicit room enters the real mint');
42
+ reset role;
43
+ select is((select bm.role from agent_board.board_memberships bm join _sidserver_children c on c.agent_id=bm.agent_id where c.label='allowed'), 'observer',
44
+ 'SIDSERVER-ID-01/explicit-policy: the only requested room retains its narrower role');
45
+ set local role service_role;
46
+ select lives_ok($q$select pg_temp.sidserver_child('empty','["read"]')$q$,
47
+ 'SIDSERVER-authority-empty-control: empty explicit rooms enter the real mint');
48
+ reset role;
49
+ select is((select count(*) from agent_board.board_memberships bm join _sidserver_children c on c.agent_id=bm.agent_id where c.label='empty'),0::bigint,
50
+ 'SIDSERVER-ID-01/no-room-fallback: empty policy creates no parent membership copy');
51
+ set local role service_role;
52
+ select throws_ok($q$select pg_temp.sidserver_child('wider-role','["read"]','admin')$q$,'42501','room_authority_exceeded',
53
+ 'SIDSERVER-ID-01/parent-authority-role: a child cannot outrank its parent');
54
+ select throws_ok($q$select pg_temp.sidserver_child('absent-room','["read"]','observer',true)$q$,'42501','room_authority_exceeded',
55
+ 'SIDSERVER-ID-01/parent-authority-room: a child cannot acquire a room its parent lacks');
56
+ select throws_ok($q$select pg_temp.sidserver_child('wider-scope','["read","write"]')$q$,'42501',
57
+ 'scope_exceeded: requested scopes are not a subset of the root allowed_scopes',
58
+ 'SIDSERVER-ID-01/parent-scopes: a child cannot widen parent scopes');
59
+ select lives_ok($q$select pg_temp.sidserver_child('after-refusal','["read"]','observer')$q$,
60
+ 'SIDSERVER-authority-after-refusal-control: refusal does not stop an otherwise valid child');
61
+ reset role;
62
+ select is((select count(*) from _sidserver_children where label in ('wider-role','absent-room','wider-scope')),0::bigint,
63
+ 'SIDSERVER-authority-refusal-state: rejected mints create no child');
64
+ select is((select g.granted_scopes from agent_board.delegation_grants g join _sidserver_children c on c.agent_id=g.child_agent_id where c.label='allowed'),'["read"]'::jsonb,
65
+ 'SIDSERVER-authority-granted-scope-control: allowed grant retains exact read scope');
66
+ select is((select count(*) from agent_board.board_memberships bm join _sidserver_children c on c.agent_id=bm.agent_id where bm.board_id=(select absent_board_id from _sidserver_authority)),0::bigint,
67
+ 'SIDSERVER-authority-absent-room-state: no child membership escapes the parent room set');
68
+ select finish();
69
+ rollback;