@consiliency/agent-board-schema 1.64.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 (171) hide show
  1. package/README.md +19 -0
  2. package/fixtures/already-applied.json +44 -4
  3. package/migrations/20261015000100_agent_board_session_provenance.sql +367 -0
  4. package/migrations/20261015000200_agent_board_request_preserve_removed.sql +87 -0
  5. package/migrations/20261015000300_agent_board_rls_correlation.sql +132 -0
  6. package/migrations/20261015000400_agent_board_resolution_authority.sql +146 -0
  7. package/migrations/20261026000100_agent_board_thread_association.sql +1287 -0
  8. package/migrations/20261101000100_agent_board_session_measured_mint.sql +145 -0
  9. package/migrations/20261104065658_agent_board_reply_delivery.sql +659 -0
  10. package/migrations/20261104170000_agent_board_tasking_replay_scope.sql +320 -0
  11. package/migrations/20261104180000_agent_board_delivery_writer_execute.sql +51 -0
  12. package/migrations/20261104190000_agent_board_message_replay_scope.sql +373 -0
  13. package/package.json +1 -1
  14. package/schema-manifest.json +93 -3
  15. package/src/deployment-drift-main.test.mjs +156 -0
  16. package/src/deployment-drift-script.test.mjs +14 -1
  17. package/src/policy-comment.test.mjs +17 -0
  18. package/src/tooling.test.mjs +12 -2
  19. package/tests/agent_board_661_reply_delivery.test.sql +185 -0
  20. package/tests/agent_board_667_boundary.test.sql +162 -0
  21. package/tests/agent_board_667_conversion_branches.test.sql +91 -0
  22. package/tests/agent_board_667_conversion_profile.test.sql +112 -0
  23. package/tests/agent_board_667_history_class.test.sql +785 -0
  24. package/tests/agent_board_667_native_cast_effect.test.sql +88 -0
  25. package/tests/agent_board_667_realtime_apply_behavior.test.sql +117 -0
  26. package/tests/agent_board_667_realtime_apply_controls.test.sql +143 -0
  27. package/tests/agent_board_667_realtime_apply_profile.test.sql +61 -0
  28. package/tests/agent_board_667_retention.test.sql +201 -0
  29. package/tests/agent_board_667_routing.test.sql +294 -0
  30. package/tests/agent_board_667_sender_effects.test.sql +100 -0
  31. package/tests/agent_board_667_sender_profile_controls.test.sql +29 -0
  32. package/tests/agent_board_667_storage_delimiter_behavior.test.sql +84 -0
  33. package/tests/agent_board_667_storage_delimiter_controls.test.sql +23 -0
  34. package/tests/agent_board_667_storage_delimiter_profile.test.sql +61 -0
  35. package/tests/agent_board_667_storage_search_behavior.test.sql +93 -0
  36. package/tests/agent_board_667_storage_search_controls.test.sql +22 -0
  37. package/tests/agent_board_667_storage_search_profile.test.sql +60 -0
  38. package/tests/agent_board_667_storage_timestamp_behavior.test.sql +58 -0
  39. package/tests/agent_board_667_storage_timestamp_controls.test.sql +22 -0
  40. package/tests/agent_board_667_storage_timestamp_profile.test.sql +54 -0
  41. package/tests/agent_board_667_writers.test.sql +281 -0
  42. package/tests/agent_board_717_message_replay_scope.test.sql +828 -0
  43. package/tests/agent_board_717_tasking_replay_scope.test.sql +661 -0
  44. package/tests/agent_board_addressing.test.sql +8 -5
  45. package/tests/agent_board_claim_subject_gate.test.sql +7 -3
  46. package/tests/agent_board_claimpolicy_race.test.sql +39 -11
  47. package/tests/agent_board_consent_gating.test.sql +4 -4
  48. package/tests/agent_board_delegate_role_class.test.sql +33 -15
  49. package/tests/agent_board_delivery_writer_execute.test.sql +371 -0
  50. package/tests/agent_board_fk_lock_edges.test.sql +3 -0
  51. package/tests/agent_board_gatenull_membstate.test.sql +7 -4
  52. package/tests/agent_board_hosted_delegates.test.sql +5 -2
  53. package/tests/agent_board_inbox_rpcs.test.sql +5 -3
  54. package/tests/agent_board_obsgate.test.sql +8 -4
  55. package/tests/agent_board_obsgate4.test.sql +2 -2
  56. package/tests/agent_board_outbox_wake_ordering.test.sql +25 -2
  57. package/tests/agent_board_own_post_not_delivered.test.sql +11 -9
  58. package/tests/agent_board_policy_correlation.test.sql +189 -0
  59. package/tests/agent_board_request_removed.test.sql +96 -0
  60. package/tests/agent_board_resolution_authority.test.sql +190 -0
  61. package/tests/agent_board_session_identity_behavior_classes.test.sql +2 -2
  62. package/tests/agent_board_session_identity_limits.test.sql +1 -1
  63. package/tests/agent_board_session_identity_measured_mint.test.sql +342 -0
  64. package/tests/agent_board_session_identity_predicate_isolation.test.sql +146 -0
  65. package/tests/agent_board_session_provenance.test.sql +106 -0
  66. package/tests/agent_board_sidserver_authority.test.sql +69 -0
  67. package/tests/agent_board_sidserver_catalog.test.sql +47 -0
  68. package/tests/agent_board_signed_write_gateway.test.sql +4 -2
  69. package/tests/agent_board_task_thread_lock_order.test.sql +67 -42
  70. package/tests/agent_board_v2_invariants.test.sql +5 -2
  71. package/tests/agent_board_write_enforcement_class.test.sql +3 -3
  72. package/tests/catalog/function_execute_privileges.json +43 -7
  73. package/tests/catalog/p_metadata_parameters.json +16 -0
  74. package/tests/fixtures/agent_board_667_apply_rls_guard.sql +195 -0
  75. package/tests/fixtures/agent_board_667_apply_rls_provenance.md +125 -0
  76. package/tests/fixtures/agent_board_667_apply_rls_reference.sql +1903 -0
  77. package/tests/fixtures/agent_board_667_conversion_guard.sql +162 -0
  78. package/tests/fixtures/agent_board_667_conversion_reference.md +23 -0
  79. package/tests/fixtures/agent_board_667_conversion_reference.sql +5325 -0
  80. package/tests/fixtures/agent_board_667_delimiter_guard.sql +92 -0
  81. package/tests/fixtures/agent_board_667_delimiter_provenance.md +116 -0
  82. package/tests/fixtures/agent_board_667_delimiter_reference.sql +10150 -0
  83. package/tests/fixtures/agent_board_667_search_guard.sql +92 -0
  84. package/tests/fixtures/agent_board_667_search_provenance.md +141 -0
  85. package/tests/fixtures/agent_board_667_search_reference.sql +4779 -0
  86. package/tests/fixtures/agent_board_667_sender_guard.sql +102 -0
  87. package/tests/fixtures/agent_board_667_sender_reference.md +20 -0
  88. package/tests/fixtures/agent_board_667_sender_reference.sql +875 -0
  89. package/tests/fixtures/agent_board_667_timestamp_graph.sql +84 -0
  90. package/tests/fixtures/agent_board_667_timestamp_guard.sql +94 -0
  91. package/tests/fixtures/agent_board_667_timestamp_provenance.md +99 -0
  92. package/tests/fixtures/agent_board_667_timestamp_reference.sql +2938 -0
  93. package/tests/isolation/expected/association_artifact_anchor_order.out +108 -0
  94. package/tests/isolation/expected/association_board_vs_board.out +71 -0
  95. package/tests/isolation/expected/association_post_wait_authority.out +77 -0
  96. package/tests/isolation/expected/association_strong_history_writers.out +203 -0
  97. package/tests/isolation/expected/association_strong_isolation.out +185 -0
  98. package/tests/isolation/expected/association_vs_artifact_claim_escalation.out +199 -0
  99. package/tests/isolation/expected/association_vs_heartbeat.out +78 -0
  100. package/tests/isolation/expected/association_vs_multi_anchor_artifact.out +67 -0
  101. package/tests/isolation/expected/association_vs_private_message.out +67 -0
  102. package/tests/isolation/expected/association_vs_task_and_conflict.out +133 -0
  103. package/tests/isolation/expected/association_vs_taskful_cross_thread.out +133 -0
  104. package/tests/isolation/expected/consent_approval_lock.out +1 -1
  105. package/tests/isolation/expected/message_replay_scope.out +209 -0
  106. package/tests/isolation/expected/tasking_replay_scope.out +125 -0
  107. package/tests/isolation/specs/association_artifact_anchor_order.spec +46 -0
  108. package/tests/isolation/specs/association_board_vs_board.spec +37 -0
  109. package/tests/isolation/specs/association_post_wait_authority.spec +48 -0
  110. package/tests/isolation/specs/association_strong_history_writers.spec +61 -0
  111. package/tests/isolation/specs/association_strong_isolation.spec +50 -0
  112. package/tests/isolation/specs/association_vs_artifact_claim_escalation.spec +41 -0
  113. package/tests/isolation/specs/association_vs_heartbeat.spec +47 -0
  114. package/tests/isolation/specs/association_vs_multi_anchor_artifact.spec +38 -0
  115. package/tests/isolation/specs/association_vs_private_message.spec +33 -0
  116. package/tests/isolation/specs/association_vs_task_and_conflict.spec +38 -0
  117. package/tests/isolation/specs/association_vs_taskful_cross_thread.spec +42 -0
  118. package/tests/isolation/specs/consent_approval_lock.spec +13 -7
  119. package/tests/isolation/specs/message_replay_scope.spec +77 -0
  120. package/tests/isolation/specs/tasking_replay_scope.spec +72 -0
  121. package/tests/mutations/717-f1/M0_prefix_main_acl_and_comments.sql +11 -0
  122. package/tests/mutations/717-f1/M10_wrapper_stops_delegating.sql +7 -0
  123. package/tests/mutations/717-f1/M11_transitive_recipient_content.sql +8 -0
  124. package/tests/mutations/717-f1/M12_transitive_recipient_visibility.sql +8 -0
  125. package/tests/mutations/717-f1/M13_transitive_worker_content.sql +15 -0
  126. package/tests/mutations/717-f1/M1a_regrant_upsert_delivery_recipient.sql +1 -0
  127. package/tests/mutations/717-f1/M1b_regrant_upsert_inbox_item.sql +1 -0
  128. package/tests/mutations/717-f1/M1c_regrant_queue_inbox_callbacks.sql +1 -0
  129. package/tests/mutations/717-f1/M1d_regrant_queue_board_post_callbacks.sql +1 -0
  130. package/tests/mutations/717-f1/M1e_regrant_next_thread_message_sequence.sql +1 -0
  131. package/tests/mutations/717-f1/M2_blanket_schema_grant.sql +1 -0
  132. package/tests/mutations/717-f1/M3_new_overload_of_sink.sql +10 -0
  133. package/tests/mutations/717-f1/M4_new_route_calling_sink.sql +7 -0
  134. package/tests/mutations/717-f1/M5_actor_trusts_supplied_id.sql +5 -0
  135. package/tests/mutations/717-f1/M6_signed_route_granted_to_authenticated.sql +1 -0
  136. package/tests/mutations/717-f1/M7_worker_granted_to_authenticated.sql +1 -0
  137. package/tests/mutations/717-f1/M8_recipient_state_rewrites_visibility.sql +5 -0
  138. package/tests/mutations/717-f1/M9_public_route_revoked_from_service_role.sql +1 -0
  139. package/tests/mutations/717-f1/outbox_wake_ordering/W1_regrant_queue_inbox_callbacks.sql +1 -0
  140. package/tests/mutations/717-f1/outbox_wake_ordering/W2_revoke_worker_from_service_role.sql +1 -0
  141. package/tests/mutations/717-f1/outbox_wake_ordering/W3_invoker_caller_of_sink.sql +1 -0
  142. package/tests/mutations/717-f1/outbox_wake_ordering/W4_public_regrant_board_post_sink.sql +1 -0
  143. package/tests/mutations/717-f1/run.sh +17 -0
  144. package/tests/mutations/717-f2b/C1_new_delivery_route.sql +4 -0
  145. package/tests/mutations/717-f2b/C2_new_replay_lookup.sql +3 -0
  146. package/tests/mutations/717-f2b/C3_internal_regranted.sql +2 -0
  147. package/tests/mutations/717-f2b/C4_wrapper_writes_delivery.sql +9 -0
  148. package/tests/mutations/717-f2b/C5_class_member_dynamic_sql.sql +9 -0
  149. package/tests/mutations/717-f2b/C6_drop_board_scope_constraint.sql +2 -0
  150. package/tests/mutations/717-f2b/C7_state_private_helper_content.sql +14 -0
  151. package/tests/mutations/717-f2b/L1_dm_lock_split_by_recipient.sql +8 -0
  152. package/tests/mutations/717-f2b/L2_dm_lock_removed.sql +12 -0
  153. package/tests/mutations/717-f2b/L3_mc_lock_split_by_recipients.sql +8 -0
  154. package/tests/mutations/717-f2b/L4_mc_lock_removed.sql +12 -0
  155. package/tests/mutations/717-f2b/M0_prefix_definitions.sql +17 -0
  156. package/tests/mutations/717-f2b/M10_mc_lookup_any_author.sql +15 -0
  157. package/tests/mutations/717-f2b/M11_dm_swallow_refusal.sql +8 -0
  158. package/tests/mutations/717-f2b/M12_mc_swallow_refusal.sql +8 -0
  159. package/tests/mutations/717-f2b/M13_signed_dm_swallows_refusal.sql +32 -0
  160. package/tests/mutations/717-f2b/M14_signed_mc_swallows_refusal.sql +20 -0
  161. package/tests/mutations/717-f2b/M15_mc_any_source.sql +7 -0
  162. package/tests/mutations/717-f2b/M1_dm_drop_recipient_proof.sql +16 -0
  163. package/tests/mutations/717-f2b/M2_mc_drop_recipient_proof.sql +16 -0
  164. package/tests/mutations/717-f2b/M3_dm_metadata_as_proof.sql +12 -0
  165. package/tests/mutations/717-f2b/M4_mc_metadata_as_proof.sql +16 -0
  166. package/tests/mutations/717-f2b/M5_dm_any_mode.sql +11 -0
  167. package/tests/mutations/717-f2b/M6_dm_board_delivery_counts.sql +11 -0
  168. package/tests/mutations/717-f2b/M7_mc_any_mode.sql +11 -0
  169. package/tests/mutations/717-f2b/M8_dm_thread_stamp_as_proof.sql +12 -0
  170. package/tests/mutations/717-f2b/M9_dm_lookup_any_author.sql +15 -0
  171. package/tests/mutations/717-f2b/run.sh +82 -0
@@ -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;
@@ -0,0 +1,47 @@
1
+ -- Literal SQL call closure is the catalog model; dynamic callees are outside it.
2
+ create extension if not exists pgtap with schema extensions;
3
+ set search_path = extensions, public, agent_board;
4
+ begin;
5
+ select plan(6);
6
+
7
+ create temp view _sidserver_direct as
8
+ select p.oid, 'alias' kind from pg_proc p where p.pronamespace = 'agent_board'::regnamespace
9
+ and agent_board_test.stripped_body(p.oid, true) ~ agent_board_test.stmt_re(
10
+ 'insert into|update|delete from|merge into|truncate|truncate table', 'session_id_aliases', agent_board_test.bare_ok(p.oid))
11
+ union all
12
+ select p.oid, 'issuer' kind from pg_proc p where p.pronamespace = 'agent_board'::regnamespace
13
+ and agent_board_test.stripped_body(p.oid, true) ~ agent_board_test.stmt_re('insert into|merge into', 'nonfleet_sessions', agent_board_test.bare_ok(p.oid))
14
+ and agent_board_test.stripped_body(p.oid, true) like '%fleet_agent_session%';
15
+ create temp view _sidserver_class as
16
+ select distinct p.oid, d.kind
17
+ from pg_proc p join agent_board_test.fn_call_closure() c on c.root_oid = p.oid
18
+ join _sidserver_direct d on d.oid = c.reached_oid;
19
+
20
+ select ok(exists(select 1 from _sidserver_class where kind = 'alias')
21
+ and exists(select 1 from _sidserver_class where kind = 'issuer'),
22
+ 'SIDSERVER-CATALOG-01: discovered alias writers and session credential issuers are nonempty');
23
+ select is((select coalesce(string_agg(oid::regprocedure::text, ', ' order by oid::regprocedure::text), '')
24
+ from _sidserver_class where has_function_privilege('public', oid, 'EXECUTE')
25
+ or has_function_privilege('anon', oid, 'EXECUTE')), '',
26
+ 'SIDSERVER-CATALOG-02: every discovered issuer or alias writer/caller closes PUBLIC and anon');
27
+ select is((select coalesce(string_agg(oid::regprocedure::text, ', ' order by oid::regprocedure::text), '')
28
+ from _sidserver_class where kind = 'issuer' and has_function_privilege('authenticated', oid, 'EXECUTE')), '',
29
+ 'SIDSERVER-CATALOG-03: session credential issuers and callers are not authenticated RPCs');
30
+ -- Human readmission is positively required to reach owner authorization, rather than exempted.
31
+ select is((select coalesce(string_agg(s.oid::regprocedure::text, ', ' order by s.oid::regprocedure::text), '')
32
+ from _sidserver_class s where s.kind = 'alias' and has_function_privilege('authenticated', s.oid, 'EXECUTE')
33
+ and not exists(select 1 from agent_board_test.fn_call_closure() c
34
+ where c.root_oid = s.oid and c.reached_oid = 'agent_board._session_identity_owner(uuid)'::regprocedure)), '',
35
+ 'SIDSERVER-CATALOG-04: each authenticated alias writer positively reaches verified human ownership');
36
+ select is((select coalesce(string_agg(s.oid::regprocedure::text, ', ' order by s.oid::regprocedure::text), '')
37
+ from _sidserver_class s where s.kind = 'alias'
38
+ and not exists(select 1 from agent_board_test.fn_call_closure() c
39
+ where c.root_oid = s.oid and c.reached_oid = 'agent_board._session_identity_ancestor_ceiling(uuid)'::regprocedure)), '',
40
+ 'SIDSERVER-CATALOG-05: every alias writer/caller reaches the stored-lineage gate');
41
+ select is((select coalesce(string_agg(oid::regprocedure::text, ', ' order by oid::regprocedure::text), '')
42
+ from _sidserver_direct where kind = 'issuer' and has_function_privilege('service_role', oid, 'EXECUTE')
43
+ and (pg_get_function_result(oid) not like '%identity_lease_epoch%'
44
+ or pg_get_function_result(oid) not like '%holder_secret%')), '',
45
+ 'SIDSERVER-CATALOG-06: service session issuers expose the lease and holder result contract');
46
+ select finish();
47
+ rollback;
@@ -17,6 +17,7 @@ declare
17
17
  v_agent uuid := gen_random_uuid();
18
18
  v_recipient uuid := gen_random_uuid();
19
19
  v_thread uuid := gen_random_uuid();
20
+ v_board_thread uuid := gen_random_uuid();
20
21
  v_task uuid := gen_random_uuid();
21
22
  v_board uuid := gen_random_uuid();
22
23
  v_key uuid;
@@ -32,7 +33,8 @@ begin
32
33
  (v_recipient, 'agent', 'signgateway-recipient', 'SIGNGATEWAY Recipient');
33
34
 
34
35
  insert into agent_board.threads(id, created_by_agent_id, subject)
35
- values (v_thread, v_agent, 'SIGNGATEWAY thread');
36
+ values (v_thread, v_agent, 'SIGNGATEWAY thread'),
37
+ (v_board_thread, v_agent, 'SIGNGATEWAY empty board thread');
36
38
 
37
39
  insert into agent_board.tasks(id, thread_id, created_by_agent_id, title)
38
40
  values (v_task, v_thread, v_agent, 'SIGNGATEWAY task');
@@ -124,7 +126,7 @@ begin
124
126
  into v_board_post
125
127
  from agent_board.signed_post_board_message(
126
128
  jsonb_build_object(
127
- 'p_thread_id', v_thread,
129
+ 'p_thread_id', v_board_thread,
128
130
  'p_author_agent_id', v_agent,
129
131
  'p_board_id', v_board,
130
132
  'p_subject', 'Signed board',
@@ -5,7 +5,7 @@
5
5
  -- WHY A CLASS AND NOT TWO FUNCTION CHECKS. The #281 triage said reordering
6
6
  -- append_task_event alone "removes all 40 rows -- no other explicit lock on either
7
7
  -- relation participates". That was true of EXPLICIT locks and false of the fix:
8
- -- `_upsert_delivery_recipient` takes no explicit lock at all (KEY SHARE on tasks through
8
+ -- the old `_upsert_delivery_recipient` took no explicit lock (KEY SHARE on tasks through
9
9
  -- delivery_recipients_source_task_id_fkey, then an UPDATE on threads), and moving
10
10
  -- append_task_event to threads-first promoted it into the cycle set -- 186 -> 150 with four
11
11
  -- rows still on the pair. A hand-written list of "the functions that matter" produced
@@ -15,12 +15,9 @@
15
15
  -- run here as a closure claim), so a NEW function that takes tasks before threads fails and
16
16
  -- NAMES ITSELF.
17
17
  --
18
- -- TIES ARE RENDERED, NOT ROUNDED. Six of these functions take both relations in ONE
19
- -- statement (`create_task`, `acquire_claim`, `add_artifact`, `delegate_task`,
20
- -- `raise_conflict`, `raise_escalation` -- an insert whose FKs reach both parents). FKL-9's
21
- -- two-branch `case` maps a tie to its else-branch, which would report those six as
22
- -- "threads-first" -- a claim the derivation does not support. They are rendered as
23
- -- `same statement` here, so the assertion says only what it knows.
18
+ -- TIES ARE RENDERED, NOT ROUNDED. The derivation distinguishes one-statement FK
19
+ -- acquisitions from distinct statements. #667 adds conditional thread locks to the
20
+ -- writers, and the rows below describe the resulting catalog paths.
24
21
  --
25
22
  -- WHAT IS ASSERTED, AND WHAT IS NOT.
26
23
  -- * TT-1/TT-2 are the ORDER. They do not claim, and this file does not claim, that a
@@ -40,7 +37,7 @@ set search_path = extensions, public, agent_board;
40
37
 
41
38
  begin;
42
39
 
43
- select plan(13);
40
+ select plan(20);
44
41
 
45
42
  -- ---------------------------------------------------------------------------
46
43
  -- The derivation, materialized once. Same shape as agent_board_fk_lock_edges.test.sql:
@@ -84,23 +81,29 @@ select is(
84
81
  -- with it. The public append_task_event still appears below, reaching the same two
85
82
  -- acquisitions `via callee` -- so this row is a NEW member, not a relocated one.
86
83
  '_append_task_event: threads(key share) then tasks(update)',
84
+ '_deliver_board_message: threads(update) then tasks(key share)',
85
+ '_insert_thread_message: threads(update) then tasks(key share)',
87
86
  '_upsert_delivery_recipient: threads(update) then tasks(key share)',
88
- 'acquire_claim: same statement: threads(key share) + tasks(update)',
89
- 'add_artifact: same statement: threads(key share) + tasks(update)',
87
+ -- #667: acquire_claim and add_artifact now lock unstamped anchor threads.
88
+ 'acquire_claim: threads(no key update) then tasks(update)',
89
+ 'add_artifact: threads(no key update) then tasks(update)',
90
90
  'append_task_event: threads(key share) then tasks(update)',
91
- 'create_task: same statement: threads(key share) + tasks(key share)',
92
- 'create_tasking_delivery: same statement: threads(update) + tasks(key share)',
93
- 'delegate_task: same statement: threads(key share) + tasks(update)',
91
+ -- #667: create_task, its tasking and delegation callers take the thread first.
92
+ 'create_task: threads(no key update) then tasks(key share)',
93
+ 'create_tasking_delivery: threads(update) then tasks(key share)',
94
+ 'delegate_task: threads(no key update) then tasks(update)',
94
95
  'post_board_message: threads(update) then tasks(key share)',
95
96
  'post_message: threads(update) then tasks(key share)',
96
- 'raise_conflict: same statement: threads(key share) + tasks(update)',
97
- 'raise_escalation: same statement: threads(key share) + tasks(update)',
97
+ -- #667: both conflict and escalation lock their actual thread before task events.
98
+ 'raise_conflict: threads(no key update) then tasks(update)',
99
+ 'raise_escalation: threads(no key update) then tasks(update)',
98
100
  'resolve_conflict: threads(key share) then tasks(update)',
99
101
  'resolve_escalation: threads(key share) then tasks(update)',
100
102
  'send_direct_message: threads(update) then tasks(key share)',
101
103
  'send_multicast_message: threads(update) then tasks(key share)',
102
104
  'signed_append_task_event: threads(key share) then tasks(update)',
103
- 'signed_create_tasking_delivery: same statement: threads(update) + tasks(key share)',
105
+ -- #667: the signed tasking wrapper inherits its early association boundary.
106
+ 'signed_create_tasking_delivery: threads(update) then tasks(key share)',
104
107
  'signed_post_board_message: threads(update) then tasks(key share)',
105
108
  'signed_send_direct_message: threads(update) then tasks(key share)',
106
109
  'signed_send_multicast_message: threads(update) then tasks(key share)'
@@ -116,8 +119,8 @@ select is(
116
119
  (select count(*)::text || ' functions acquire both; '
117
120
  || count(*) filter (where tasks_first)::text || ' take tasks before threads'
118
121
  from _tt_ord),
119
- '21 functions acquire both; 0 take tasks before threads',
120
- 'TT-2: 21 functions acquire both relations and NONE takes tasks before threads');
122
+ '23 functions acquire both; 0 take tasks before threads',
123
+ 'TT-2: 23 functions acquire both relations and NONE takes tasks before threads');
121
124
 
122
125
  -- ===========================================================================
123
126
  -- TT-3: append_task_event's acquisitions on the two relations, in path order, with mode
@@ -190,17 +193,16 @@ select is(
190
193
  'TT-6: _append_task_event still takes an EXPLICIT FOR UPDATE on tasks -- the sequence guard is intact');
191
194
 
192
195
  -- ===========================================================================
193
- -- TT-7: the second half of the fix, on its own. `_upsert_delivery_recipient` has no
194
- -- explicit lock at all, so it is invisible to every textual lock-order guard in this repo;
195
- -- its order is entirely a statement order. The OBSGATE (#78) threads stamp must precede the
196
- -- delivery_recipients insert whose FK reaches tasks.
196
+ -- TT-7: the sink reaches the association helper before the delivery INSERT. The
197
+ -- helper locks and stamps the thread before the task FK acquisition.
197
198
  -- ===========================================================================
198
199
  select is(
199
200
  (select string_agg(e.rel_name || ' ' || e.lock_mode || ' via ' || e.via, ' -> ' order by e.path)
200
201
  from _tt_le e
201
202
  where e.fn_name = '_upsert_delivery_recipient'
202
203
  and e.rel_name in ('agent_board.tasks', 'agent_board.threads')),
203
- 'agent_board.threads update via write-update'
204
+ 'agent_board.threads no key update via callee:_associate_empty_thread'
205
+ || ' -> agent_board.threads update via callee:_associate_empty_thread'
204
206
  || ' -> agent_board.tasks key share via fk-insert:delivery_recipients_source_task_id_fkey',
205
207
  'TT-7: _upsert_delivery_recipient stamps threads BEFORE the insert that FK-locks tasks');
206
208
 
@@ -214,12 +216,13 @@ select is(
214
216
  -- simply stops, and a summary-line reader calls that a pass (#113).
215
217
  --
216
218
  -- The fixture is: a board with a member and an observer; a board-linked thread carrying one
217
- -- task; and a SECOND board plus a board-LESS thread carrying its own task, which TT-12/13
218
- -- need in order to observe a stamp actually being written rather than a no-op.
219
+ -- task; a SECOND board; a boardless thread holding a private task; and a separate
220
+ -- empty thread. TT-12/13 distinguish legitimate first association of the empty
221
+ -- thread from private-history promotion and a conflicting second-board stamp.
219
222
  -- ===========================================================================
220
223
  create temp table _tt_fixture (
221
224
  board uuid, board2 uuid, actor uuid, observer uuid,
222
- thread uuid, task uuid, free_thread uuid, free_task uuid,
225
+ thread uuid, task uuid, free_thread uuid, free_task uuid, empty_thread uuid,
223
226
  actor_user uuid, observer_user uuid, membership uuid, membership2 uuid
224
227
  ) on commit drop;
225
228
 
@@ -233,6 +236,7 @@ declare
233
236
  v_board uuid; v_board2 uuid; v_membership uuid; v_membership2 uuid;
234
237
  v_thread uuid := gen_random_uuid(); v_task uuid;
235
238
  v_free_thread uuid := gen_random_uuid(); v_free_task uuid;
239
+ v_empty_thread uuid := gen_random_uuid();
236
240
  begin
237
241
  v_actor_user := public.ensure_github_user('tt-actor-' || v_suffix, 'tt-actor-' || v_suffix || '@example.test', 'tt-actor-' || v_suffix, 'TT Actor');
238
242
  v_observer_user := public.ensure_github_user('tt-obs-' || v_suffix, 'tt-obs-' || v_suffix || '@example.test', 'tt-obs-' || v_suffix, 'TT Observer');
@@ -262,7 +266,8 @@ begin
262
266
 
263
267
  insert into agent_board.threads(id, thread_type, created_by_agent_id, subject)
264
268
  values (v_thread, 'coordination', v_actor, 'TT board thread'),
265
- (v_free_thread, 'coordination', v_actor, 'TT board-less thread');
269
+ (v_free_thread, 'coordination', v_actor, 'TT board-less thread'),
270
+ (v_empty_thread, 'coordination', v_actor, 'TT empty thread');
266
271
 
267
272
  perform set_config('request.jwt.claim.role', 'authenticated', true);
268
273
  perform set_config('request.jwt.claim.sub', v_actor_user::text, true);
@@ -273,7 +278,7 @@ begin
273
278
  select task_id into v_free_task from agent_board.create_task(v_free_thread, v_actor, 'tt free task', 'd');
274
279
 
275
280
  insert into _tt_fixture values (v_board, v_board2, v_actor, v_observer, v_thread, v_task,
276
- v_free_thread, v_free_task, v_actor_user, v_observer_user,
281
+ v_free_thread, v_free_task, v_empty_thread, v_actor_user, v_observer_user,
277
282
  v_membership, v_membership2);
278
283
  end
279
284
  $$;
@@ -317,24 +322,44 @@ select throws_ok(
317
322
  'task mutation requires a non-observer board role, or task assignment/creation',
318
323
  'TT-11: a board observer that is not a party to the task is still denied');
319
324
 
320
- -- TT-12: the hoisted OBSGATE (#78) stamp still runs. service_role -- the helper is
321
- -- service_role-only by grant. Two calls: one that must STAMP (board-less thread), one that
322
- -- must NOT overwrite (same thread, different board).
325
+ -- First association belongs to the operation creating its own task, before the
326
+ -- task FK or event exists. The pre-existing private task remains a sink refusal.
323
327
  select lives_ok(
328
+ $$ select * from agent_board.create_tasking_delivery(
329
+ (select empty_thread from _tt_fixture), (select actor from _tt_fixture),
330
+ 'first board task', p_board_id => (select board from _tt_fixture)) $$,
331
+ 'TT-12: an empty thread accepts its own first board task');
332
+ select is(
333
+ (select board_id from agent_board.threads where id=(select empty_thread from _tt_fixture)),
334
+ (select board from _tt_fixture), 'TT-12 state: first board is stamped');
335
+ select throws_ok(
336
+ $$ select * from agent_board.create_tasking_delivery(
337
+ (select empty_thread from _tt_fixture), (select actor from _tt_fixture),
338
+ 'second board task', p_board_id => (select board2 from _tt_fixture)) $$,
339
+ '42501', 'thread belongs to a different board',
340
+ 'TT-13: a second board is refused at the association boundary');
341
+ select is(
342
+ (select count(*) from agent_board.tasks where thread_id=(select empty_thread from _tt_fixture)),
343
+ 1::bigint, 'TT-13 state: mismatch creates no second task');
344
+ select is(
345
+ (select board_id from agent_board.threads where id=(select empty_thread from _tt_fixture)),
346
+ (select board from _tt_fixture), 'TT-13 state: mismatch preserves first board');
347
+ select is(
348
+ (select count(*) from agent_board.delivery_recipients d join agent_board.tasks t on t.id=d.source_task_id
349
+ where t.thread_id=(select empty_thread from _tt_fixture) and d.board_id=(select board2 from _tt_fixture)),
350
+ 0::bigint, 'TT-13 state: mismatch creates no second-board delivery');
351
+ select throws_ok(
324
352
  $$ select set_config('request.jwt.claim.role', 'service_role', true);
325
353
  select agent_board._upsert_delivery_recipient('tasking', f.actor, 'task', null,
326
- f.free_task, null, f.board, f.membership) from _tt_fixture f;
327
- select agent_board._upsert_delivery_recipient('tasking', f.actor, 'task', null,
328
- f.free_task, null, f.board2, f.membership2) from _tt_fixture f $$,
329
- 'TT-12: the hoisted stamp runs for both a first and a second board delivery');
330
-
331
- -- TT-13: the state TT-12 wrote -- first-board-wins survived the hoist. `false` on the
332
- -- second element would mean the reorder turned the stamp into an overwrite.
354
+ f.free_task, null, f.board, f.membership) from _tt_fixture f $$,
355
+ '42501', 'thread has private history',
356
+ 'TT-14: direct sink cannot promote a pre-existing private task');
357
+ select is(
358
+ (select board_id from agent_board.threads where id=(select free_thread from _tt_fixture)),
359
+ null::uuid, 'TT-14 state: private thread remains unstamped');
333
360
  select is(
334
- (select (t.board_id = f.board)::text || '/' || (t.board_id = f.board2)::text
335
- from agent_board.threads t, _tt_fixture f where t.id = f.free_thread),
336
- 'true/false',
337
- 'TT-13: the board-less thread was stamped with the FIRST board and not overwritten by the second');
361
+ (select count(*) from agent_board.tasks where id=(select free_task from _tt_fixture)),
362
+ 1::bigint, 'TT-14 state: private task remains intact');
338
363
 
339
364
  select * from finish();
340
365
 
@@ -21,6 +21,9 @@ values (
21
21
  'coordination',
22
22
  '20000000-0000-0000-0000-000000000101',
23
23
  'V2 invariant thread'
24
+ ), (
25
+ '20000000-0000-0000-0000-000000000202', 'coordination',
26
+ '20000000-0000-0000-0000-000000000101', 'V2 empty board thread'
24
27
  );
25
28
 
26
29
  create temporary table v2_board on commit drop as
@@ -310,7 +313,7 @@ select is(
310
313
  create temporary table v2_board_delivery on commit drop as
311
314
  select *
312
315
  from agent_board.post_board_message(
313
- '20000000-0000-0000-0000-000000000201',
316
+ '20000000-0000-0000-0000-000000000202',
314
317
  '20000000-0000-0000-0000-000000000101',
315
318
  (select board_id from v2_board),
316
319
  'Board subject',
@@ -355,7 +358,7 @@ select is(
355
358
  create temporary table v2_tasking_delivery on commit drop as
356
359
  select *
357
360
  from agent_board.create_tasking_delivery(
358
- '20000000-0000-0000-0000-000000000201',
361
+ '20000000-0000-0000-0000-000000000202',
359
362
  '20000000-0000-0000-0000-000000000101',
360
363
  'Tasking invariant title',
361
364
  'Tasking invariant details',
@@ -579,12 +579,12 @@ select is( -- pgtap-allow-raise-checked
579
579
  select is(
580
580
  (select (select count(*) from _wec_closure cl join pg_proc rp on rp.oid = cl.root_oid
581
581
  where rp.pronamespace = 'agent_board'::regnamespace
582
- and rp.proname = 'send_direct_message' and cl.reached_name = 'post_message')::text
582
+ and rp.proname = 'send_direct_message' and cl.reached_name = '_insert_thread_message')::text
583
583
  || '/' ||
584
584
  (select count(*) from _wec_direct d join pg_proc p on p.oid = d.fn_oid
585
- where p.proname = 'post_message' and d.relname = 'messages')::text),
585
+ where p.proname = '_insert_thread_message' and d.relname = 'messages')::text),
586
586
  '2/1',
587
- '#325 instrument liveness: BOTH send_direct_message overloads reach post_message, and post_message writes messages — the delegation chain is real, so a closure that stopped resolving edges reds HERE instead of silently emptying the class');
587
+ '#325 instrument liveness: BOTH send_direct_message overloads reach _insert_thread_message, and _insert_thread_message writes messages — the delegation chain is real, so a closure that stopped resolving edges reds HERE instead of silently emptying the class');
588
588
 
589
589
  -- ===========================================================================================
590
590
  -- HALF A — every writer derives its actor FROM THE SESSION.
@@ -100,6 +100,15 @@
100
100
  "authenticated": false
101
101
  }
102
102
  },
103
+ {
104
+ "name": "_associate_empty_thread",
105
+ "signature": "_associate_empty_thread(p_thread_id uuid, p_board_id uuid)",
106
+ "executable_by": {
107
+ "anon": false,
108
+ "service_role": false,
109
+ "authenticated": false
110
+ }
111
+ },
103
112
  {
104
113
  "name": "_board_membership_events_provenance_guard",
105
114
  "signature": "_board_membership_events_provenance_guard()",
@@ -295,7 +304,7 @@
295
304
  "executable_by": {
296
305
  "anon": false,
297
306
  "service_role": true,
298
- "authenticated": false
307
+ "authenticated": true
299
308
  }
300
309
  },
301
310
  {
@@ -331,7 +340,7 @@
331
340
  "executable_by": {
332
341
  "anon": false,
333
342
  "service_role": true,
334
- "authenticated": false
343
+ "authenticated": true
335
344
  }
336
345
  },
337
346
  {
@@ -388,6 +397,15 @@
388
397
  "authenticated": false
389
398
  }
390
399
  },
400
+ {
401
+ "name": "_deliver_board_message",
402
+ "signature": "_deliver_board_message(p_message_id uuid, p_subject text, p_body text, p_priority text)",
403
+ "executable_by": {
404
+ "anon": false,
405
+ "service_role": false,
406
+ "authenticated": false
407
+ }
408
+ },
391
409
  {
392
410
  "name": "_emit_membership_event",
393
411
  "signature": "_emit_membership_event(p_board_id uuid, p_agent_id uuid, p_membership_state text, p_event_kind text)",
@@ -496,6 +514,15 @@
496
514
  "authenticated": true
497
515
  }
498
516
  },
517
+ {
518
+ "name": "_insert_thread_message",
519
+ "signature": "_insert_thread_message(p_thread_id uuid, p_author_agent_id uuid, p_body text, p_message_type text, p_task_id uuid, p_metadata jsonb)",
520
+ "executable_by": {
521
+ "anon": false,
522
+ "service_role": false,
523
+ "authenticated": false
524
+ }
525
+ },
499
526
  {
500
527
  "name": "_invite_permitted",
501
528
  "signature": "_invite_permitted(p_board_id uuid, p_target_agent_id uuid, p_inviter_agent_id uuid)",
@@ -600,7 +627,7 @@
600
627
  "signature": "_next_thread_message_sequence(p_thread_id uuid)",
601
628
  "executable_by": {
602
629
  "anon": false,
603
- "service_role": true,
630
+ "service_role": false,
604
631
  "authenticated": false
605
632
  }
606
633
  },
@@ -645,7 +672,7 @@
645
672
  "signature": "_queue_board_post_callbacks(p_board_id uuid, p_message_id uuid, p_subject text, p_preview text, p_priority text, p_fetch_descriptor jsonb)",
646
673
  "executable_by": {
647
674
  "anon": false,
648
- "service_role": true,
675
+ "service_role": false,
649
676
  "authenticated": false
650
677
  }
651
678
  },
@@ -654,7 +681,7 @@
654
681
  "signature": "_queue_inbox_callbacks(p_inbox_item_id uuid, p_event_category text)",
655
682
  "executable_by": {
656
683
  "anon": false,
657
- "service_role": true,
684
+ "service_role": false,
658
685
  "authenticated": false
659
686
  }
660
687
  },
@@ -951,7 +978,7 @@
951
978
  "signature": "_upsert_delivery_recipient(p_delivery_mode text, p_recipient_agent_id uuid, p_source_kind text, p_source_message_id uuid, p_source_task_id uuid, p_source_delegation_id uuid, p_board_id uuid, p_board_membership_id uuid, p_metadata jsonb)",
952
979
  "executable_by": {
953
980
  "anon": false,
954
- "service_role": true,
981
+ "service_role": false,
955
982
  "authenticated": false
956
983
  }
957
984
  },
@@ -960,7 +987,7 @@
960
987
  "signature": "_upsert_inbox_item(p_delivery_recipient_id uuid, p_recipient_agent_id uuid, p_board_id uuid, p_delivery_mode text, p_source_kind text, p_source_message_id uuid, p_source_task_id uuid, p_source_delegation_id uuid, p_subject text, p_preview text, p_priority text, p_visible_in_inbox boolean, p_visible_in_board_feed boolean, p_fetch_descriptor jsonb, p_metadata jsonb)",
961
988
  "executable_by": {
962
989
  "anon": false,
963
- "service_role": true,
990
+ "service_role": false,
964
991
  "authenticated": false
965
992
  }
966
993
  },
@@ -1405,6 +1432,15 @@
1405
1432
  "authenticated": false
1406
1433
  }
1407
1434
  },
1435
+ {
1436
+ "name": "mint_fleet_agent_session_measured",
1437
+ "signature": "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)",
1438
+ "executable_by": {
1439
+ "anon": false,
1440
+ "service_role": true,
1441
+ "authenticated": false
1442
+ }
1443
+ },
1408
1444
  {
1409
1445
  "name": "onboard_federated_fleet_agent",
1410
1446
  "signature": "onboard_federated_fleet_agent(p_issuer text, p_audience text, p_subject text, p_human_identity_claim jsonb, p_slug text, p_agent_public_key text, p_agent_public_key_format text, p_agent_public_key_fingerprint text, p_pop_verified boolean, p_token_sha256 text, p_token_expires_at timestamp with time zone, p_actor_sub text, p_agent_kind text, p_requested_scopes text[], p_requested_board_scopes text[], p_invite_code text, p_board_id uuid, p_machine_id uuid, p_harness_kind text, p_harness_label text, p_repo_slug text, p_repo_fingerprint text, p_repo_scope_role text, p_agent_role text, p_expected_agent_handle text)",
@@ -45,6 +45,10 @@
45
45
  "signature": "_assert_signed_authority_signer_live(p_actor_agent_id uuid, p_gateway_decision jsonb)",
46
46
  "declares_p_metadata": false
47
47
  },
48
+ {
49
+ "signature": "_associate_empty_thread(p_thread_id uuid, p_board_id uuid)",
50
+ "declares_p_metadata": false
51
+ },
48
52
  {
49
53
  "signature": "_board_membership_events_provenance_guard()",
50
54
  "declares_p_metadata": false
@@ -173,6 +177,10 @@
173
177
  "signature": "_delegate_visible_board_ids()",
174
178
  "declares_p_metadata": false
175
179
  },
180
+ {
181
+ "signature": "_deliver_board_message(p_message_id uuid, p_subject text, p_body text, p_priority text)",
182
+ "declares_p_metadata": false
183
+ },
176
184
  {
177
185
  "signature": "_emit_membership_event(p_board_id uuid, p_agent_id uuid, p_membership_state text, p_event_kind text)",
178
186
  "declares_p_metadata": false
@@ -221,6 +229,10 @@
221
229
  "signature": "_grant_within_ceiling(p_grantor_agent_id uuid, p_board_id uuid, p_grantee_role text)",
222
230
  "declares_p_metadata": false
223
231
  },
232
+ {
233
+ "signature": "_insert_thread_message(p_thread_id uuid, p_author_agent_id uuid, p_body text, p_message_type text, p_task_id uuid, p_metadata jsonb)",
234
+ "declares_p_metadata": true
235
+ },
224
236
  {
225
237
  "signature": "_invite_permitted(p_board_id uuid, p_target_agent_id uuid, p_inviter_agent_id uuid)",
226
238
  "declares_p_metadata": false
@@ -625,6 +637,10 @@
625
637
  "signature": "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)",
626
638
  "declares_p_metadata": false
627
639
  },
640
+ {
641
+ "signature": "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)",
642
+ "declares_p_metadata": false
643
+ },
628
644
  {
629
645
  "signature": "onboard_federated_fleet_agent(p_issuer text, p_audience text, p_subject text, p_human_identity_claim jsonb, p_slug text, p_agent_public_key text, p_agent_public_key_format text, p_agent_public_key_fingerprint text, p_pop_verified boolean, p_token_sha256 text, p_token_expires_at timestamp with time zone, p_actor_sub text, p_agent_kind text, p_requested_scopes text[], p_requested_board_scopes text[], p_invite_code text, p_board_id uuid, p_machine_id uuid, p_harness_kind text, p_harness_label text, p_repo_slug text, p_repo_fingerprint text, p_repo_scope_role text, p_agent_role text, p_expected_agent_handle text)",
630
646
  "declares_p_metadata": false