@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,41 @@
1
+ # #667: thread-only artifact, thread-only claim and taskless escalation each
2
+ # serialize with first association. Each writer and the association win once.
3
+ setup
4
+ {
5
+ insert into agent_board.agents(id,agent_type,handle,display_name,status)
6
+ values ('66720000-0000-4000-8000-000000000001','agent','xb-art-claim-esc','XB art claim escalation','active');
7
+ insert into agent_board.boards(id,slug,name,created_by_agent_id,visibility,status)
8
+ values ('66720000-0000-4000-8000-000000000020','xb-art-claim-esc-board','XB art claim escalation board',
9
+ '66720000-0000-4000-8000-000000000001','private','active');
10
+ insert into agent_board.threads(id,thread_type,created_by_agent_id,subject)
11
+ values ('66720000-0000-4000-8000-000000000010','coordination',
12
+ '66720000-0000-4000-8000-000000000001','artifact claim escalation race');
13
+ }
14
+ teardown
15
+ {
16
+ delete from agent_board.artifacts where thread_id='66720000-0000-4000-8000-000000000010';
17
+ delete from agent_board.claims where thread_id='66720000-0000-4000-8000-000000000010';
18
+ delete from agent_board.escalations where thread_id='66720000-0000-4000-8000-000000000010';
19
+ delete from agent_board.messages where thread_id='66720000-0000-4000-8000-000000000010';
20
+ delete from agent_board.threads where id='66720000-0000-4000-8000-000000000010';
21
+ delete from agent_board.boards where id='66720000-0000-4000-8000-000000000020';
22
+ delete from agent_board.agents where id='66720000-0000-4000-8000-000000000001';
23
+ }
24
+ session "w"
25
+ step "w_begin" { begin; select set_config('request.jwt.claim.role','service_role',true); }
26
+ step "w_artifact" { select count(*) from agent_board.add_artifact('66720000-0000-4000-8000-000000000001','https://example.test/xb',p_thread_id=>'66720000-0000-4000-8000-000000000010'); }
27
+ step "w_claim" { select count(*) from agent_board.acquire_claim('66720000-0000-4000-8000-000000000001','custom','xb-race-key',now()+interval '1 hour',p_thread_id=>'66720000-0000-4000-8000-000000000010'); }
28
+ step "w_escalation" { select count(*) from agent_board.raise_escalation('66720000-0000-4000-8000-000000000010','66720000-0000-4000-8000-000000000001','private escalation'); }
29
+ step "w_commit" { commit; }
30
+ session "a"
31
+ step "a_begin" { begin; select set_config('request.jwt.claim.role','service_role',true); }
32
+ step "a_post" { select count(*) from agent_board.post_board_message('66720000-0000-4000-8000-000000000010','66720000-0000-4000-8000-000000000001','66720000-0000-4000-8000-000000000020','s','b'); }
33
+ step "a_end" { end; }
34
+ session "check"
35
+ step "check" { select (t.board_id is not null) as stamped, (select count(*) from agent_board.artifacts where thread_id=t.id) as artifacts, (select count(*) from agent_board.claims where thread_id=t.id) as claims, (select count(*) from agent_board.escalations where thread_id=t.id) as escalations, (select count(*) from agent_board.messages where thread_id=t.id) as messages from agent_board.threads t where t.id='66720000-0000-4000-8000-000000000010'; }
36
+ permutation "w_begin" "w_artifact" "a_begin" "a_post" "w_commit" "a_end" "check"
37
+ permutation "a_begin" "a_post" "w_begin" "w_artifact" "a_end" "w_commit" "check"
38
+ permutation "w_begin" "w_claim" "a_begin" "a_post" "w_commit" "a_end" "check"
39
+ permutation "a_begin" "a_post" "w_begin" "w_claim" "a_end" "w_commit" "check"
40
+ permutation "w_begin" "w_escalation" "a_begin" "a_post" "w_commit" "a_end" "check"
41
+ permutation "a_begin" "a_post" "w_begin" "w_escalation" "a_end" "w_commit" "check"
@@ -0,0 +1,47 @@
1
+ # #667: a heartbeat retaining its old thread and a first board association
2
+ # serialize on the thread. The second permutation covers an empty association
3
+ # winning before a heartbeat explicitly supplies that thread.
4
+ setup
5
+ {
6
+ insert into agent_board.agents(id, agent_type, handle, display_name, status)
7
+ values ('66700000-0000-4000-8000-000000000001', 'agent', 'xb-heartbeat', 'XB heartbeat', 'active');
8
+ select set_config('request.jwt.claim.role','service_role',false);
9
+ insert into agent_board.boards(id,slug,name,created_by_agent_id,visibility,status)
10
+ values ('66700000-0000-4000-8000-000000000020','xb-heartbeat-board','XB heartbeat board',
11
+ '66700000-0000-4000-8000-000000000001','private','active');
12
+ insert into agent_board.threads(id,thread_type,created_by_agent_id,subject)
13
+ values ('66700000-0000-4000-8000-000000000010','coordination',
14
+ '66700000-0000-4000-8000-000000000001','heartbeat race');
15
+ }
16
+
17
+ teardown
18
+ {
19
+ delete from agent_board.callback_outbox_events where source_message_id in (select id from agent_board.messages where thread_id='66700000-0000-4000-8000-000000000010');
20
+ delete from agent_board.inbox_items where source_message_id in (select id from agent_board.messages where thread_id='66700000-0000-4000-8000-000000000010');
21
+ delete from agent_board.delivery_recipients where source_message_id in (select id from agent_board.messages where thread_id='66700000-0000-4000-8000-000000000010');
22
+ delete from agent_board.messages where thread_id='66700000-0000-4000-8000-000000000010';
23
+ delete from agent_board.presence where agent_id='66700000-0000-4000-8000-000000000001';
24
+ delete from agent_board.threads where id='66700000-0000-4000-8000-000000000010';
25
+ delete from agent_board.board_membership_events where board_id in (select id from agent_board.boards where slug='xb-heartbeat-board');
26
+ delete from agent_board.board_memberships where board_id in (select id from agent_board.boards where slug='xb-heartbeat-board');
27
+ delete from agent_board.boards where slug='xb-heartbeat-board';
28
+ delete from agent_board.agents where id='66700000-0000-4000-8000-000000000001';
29
+ }
30
+
31
+ session "h"
32
+ step "h_prime" { select set_config('request.jwt.claim.role','service_role',false); select count(*) from agent_board.heartbeat('66700000-0000-4000-8000-000000000001','66700000-0000-4000-8000-000000000010',p_activity_summary => 'initial'); }
33
+ step "h_begin" { begin; select set_config('request.jwt.claim.role','service_role',true); }
34
+ step "h_retain" { select count(*) from agent_board.heartbeat('66700000-0000-4000-8000-000000000001',null,p_activity_summary => 'retained',p_metadata => jsonb_build_object('note','private')); }
35
+ step "h_attach" { select count(*) from agent_board.heartbeat('66700000-0000-4000-8000-000000000001','66700000-0000-4000-8000-000000000010',p_activity_summary => 'after association'); }
36
+ step "h_commit" { commit; }
37
+
38
+ session "a"
39
+ step "a_begin" { begin; select set_config('request.jwt.claim.role','service_role',true); }
40
+ step "a_post" { select count(*) from agent_board.post_board_message('66700000-0000-4000-8000-000000000010','66700000-0000-4000-8000-000000000001',(select id from agent_board.boards where slug='xb-heartbeat-board'),'s','b'); }
41
+ step "a_end" { end; }
42
+
43
+ session "check"
44
+ step "check" { select (t.board_id is not null) as stamped, p.thread_id is not null as presence_has_thread, p.activity_summary from agent_board.threads t left join agent_board.presence p on p.agent_id='66700000-0000-4000-8000-000000000001' where t.id='66700000-0000-4000-8000-000000000010'; }
45
+
46
+ permutation "h_prime" "h_begin" "h_retain" "a_begin" "a_post" "h_commit" "a_end" "check"
47
+ permutation "a_begin" "a_post" "h_begin" "h_attach" "a_end" "h_commit" "check"
@@ -0,0 +1,38 @@
1
+ # #667: an artifact may name an empty explicit thread and a task on another
2
+ # thread. The explicit thread must serialize with first association even when a
3
+ # different canonical anchor is also present.
4
+ setup
5
+ {
6
+ insert into agent_board.agents(id,agent_type,handle,display_name,status)
7
+ values ('66730000-0000-4000-8000-000000000001','agent','xb-multi-anchor','XB multi anchor','active');
8
+ insert into agent_board.boards(id,slug,name,created_by_agent_id,visibility,status)
9
+ values ('66730000-0000-4000-8000-000000000020','xb-multi-anchor-board','XB multi anchor board',
10
+ '66730000-0000-4000-8000-000000000001','private','active');
11
+ insert into agent_board.threads(id,thread_type,created_by_agent_id,subject)
12
+ values ('66730000-0000-4000-8000-000000000010','coordination','66730000-0000-4000-8000-000000000001','explicit empty'),
13
+ ('66730000-0000-4000-8000-000000000011','coordination','66730000-0000-4000-8000-000000000001','task parent');
14
+ insert into agent_board.tasks(id,thread_id,created_by_agent_id,title)
15
+ values ('66730000-0000-4000-8000-000000000030','66730000-0000-4000-8000-000000000011','66730000-0000-4000-8000-000000000001','parent task');
16
+ }
17
+ teardown
18
+ {
19
+ delete from agent_board.artifacts where thread_id='66730000-0000-4000-8000-000000000010';
20
+ delete from agent_board.task_events where task_id='66730000-0000-4000-8000-000000000030';
21
+ delete from agent_board.tasks where id='66730000-0000-4000-8000-000000000030';
22
+ delete from agent_board.messages where thread_id='66730000-0000-4000-8000-000000000010';
23
+ delete from agent_board.threads where id in ('66730000-0000-4000-8000-000000000010','66730000-0000-4000-8000-000000000011');
24
+ delete from agent_board.boards where id='66730000-0000-4000-8000-000000000020';
25
+ delete from agent_board.agents where id='66730000-0000-4000-8000-000000000001';
26
+ }
27
+ session "w"
28
+ step "w_begin" { begin; select set_config('request.jwt.claim.role','service_role',true); }
29
+ step "w_artifact" { select count(*) from agent_board.add_artifact('66730000-0000-4000-8000-000000000001','https://example.test/mixed',p_thread_id=>'66730000-0000-4000-8000-000000000010',p_task_id=>'66730000-0000-4000-8000-000000000030'); }
30
+ step "w_commit" { commit; }
31
+ session "a"
32
+ step "a_begin" { begin; select set_config('request.jwt.claim.role','service_role',true); }
33
+ step "a_post" { select count(*) from agent_board.post_board_message('66730000-0000-4000-8000-000000000010','66730000-0000-4000-8000-000000000001','66730000-0000-4000-8000-000000000020','s','b'); }
34
+ step "a_end" { end; }
35
+ session "check"
36
+ step "check" { select (t.board_id is not null) as stamped, (select count(*) from agent_board.artifacts where thread_id=t.id and task_id='66730000-0000-4000-8000-000000000030') as mixed_artifacts, (select count(*) from agent_board.messages where thread_id=t.id) as messages from agent_board.threads t where t.id='66730000-0000-4000-8000-000000000010'; }
37
+ permutation "w_begin" "w_artifact" "a_begin" "a_post" "w_commit" "a_end" "check"
38
+ permutation "a_begin" "a_post" "w_begin" "w_artifact" "a_end" "w_commit" "check"
@@ -0,0 +1,33 @@
1
+ # #667: a private append and first board association serialize on the thread.
2
+ # The private writer wins with committed history and the association refuses;
3
+ # if association wins, the later service writer uses the stamped thread.
4
+ setup
5
+ {
6
+ insert into agent_board.agents(id,agent_type,handle,display_name,status)
7
+ values ('66810000-0000-4000-8000-000000000001','agent','xb-private-race','XB private race','active');
8
+ insert into agent_board.boards(id,slug,name,created_by_agent_id,visibility,status)
9
+ values ('66810000-0000-4000-8000-000000000020','xb-private-race-board','XB private race board',
10
+ '66810000-0000-4000-8000-000000000001','private','active');
11
+ insert into agent_board.threads(id,thread_type,created_by_agent_id,subject)
12
+ values ('66810000-0000-4000-8000-000000000010','coordination',
13
+ '66810000-0000-4000-8000-000000000001','private race');
14
+ }
15
+ teardown
16
+ {
17
+ delete from agent_board.messages where thread_id='66810000-0000-4000-8000-000000000010';
18
+ delete from agent_board.threads where id='66810000-0000-4000-8000-000000000010';
19
+ delete from agent_board.boards where id='66810000-0000-4000-8000-000000000020';
20
+ delete from agent_board.agents where id='66810000-0000-4000-8000-000000000001';
21
+ }
22
+ session "w"
23
+ step "w_begin" { begin; select set_config('request.jwt.claim.role','service_role',true); }
24
+ step "w_post" { select count(*) from agent_board.post_message('66810000-0000-4000-8000-000000000010','66810000-0000-4000-8000-000000000001','private race message'); }
25
+ step "w_commit" { commit; }
26
+ session "a"
27
+ step "a_begin" { begin; select set_config('request.jwt.claim.role','service_role',true); }
28
+ step "a_post" { select count(*) from agent_board.post_board_message('66810000-0000-4000-8000-000000000010','66810000-0000-4000-8000-000000000001','66810000-0000-4000-8000-000000000020','board','board race message'); }
29
+ step "a_end" { end; }
30
+ session "check"
31
+ step "check" { select (t.board_id is not null) as stamped, (select count(*) from agent_board.messages m where m.thread_id=t.id and m.body='private race message') as private_messages, (select count(*) from agent_board.messages m where m.thread_id=t.id and m.body='board race message') as board_messages from agent_board.threads t where t.id='66810000-0000-4000-8000-000000000010'; }
32
+ permutation "w_begin" "w_post" "a_begin" "a_post" "w_commit" "a_end" "check"
33
+ permutation "a_begin" "a_post" "w_begin" "w_post" "a_end" "w_commit" "check"
@@ -0,0 +1,38 @@
1
+ # #667: private task creation and taskless conflict serialize with first board
2
+ # association. Each operation wins once; the checker observes the resulting rows.
3
+ setup
4
+ {
5
+ insert into agent_board.agents(id,agent_type,handle,display_name,status)
6
+ values ('66710000-0000-4000-8000-000000000001','agent','xb-task-conflict','XB task conflict','active');
7
+ insert into agent_board.boards(id,slug,name,created_by_agent_id,visibility,status)
8
+ values ('66710000-0000-4000-8000-000000000020','xb-task-conflict-board','XB task conflict board',
9
+ '66710000-0000-4000-8000-000000000001','private','active');
10
+ insert into agent_board.threads(id,thread_type,created_by_agent_id,subject)
11
+ values ('66710000-0000-4000-8000-000000000010','coordination',
12
+ '66710000-0000-4000-8000-000000000001','task conflict race');
13
+ }
14
+ teardown
15
+ {
16
+ delete from agent_board.conflicts where thread_id='66710000-0000-4000-8000-000000000010';
17
+ delete from agent_board.task_events where thread_id='66710000-0000-4000-8000-000000000010';
18
+ delete from agent_board.tasks where thread_id='66710000-0000-4000-8000-000000000010';
19
+ delete from agent_board.messages where thread_id='66710000-0000-4000-8000-000000000010';
20
+ delete from agent_board.threads where id='66710000-0000-4000-8000-000000000010';
21
+ delete from agent_board.boards where id='66710000-0000-4000-8000-000000000020';
22
+ delete from agent_board.agents where id='66710000-0000-4000-8000-000000000001';
23
+ }
24
+ session "w"
25
+ step "w_begin" { begin; select set_config('request.jwt.claim.role','service_role',true); }
26
+ step "w_task" { select count(*) from agent_board.create_task('66710000-0000-4000-8000-000000000010','66710000-0000-4000-8000-000000000001','private task'); }
27
+ step "w_conflict" { select count(*) from agent_board.raise_conflict('66710000-0000-4000-8000-000000000010','66710000-0000-4000-8000-000000000001','private conflict'); }
28
+ step "w_commit" { commit; }
29
+ session "a"
30
+ step "a_begin" { begin; select set_config('request.jwt.claim.role','service_role',true); }
31
+ step "a_post" { select count(*) from agent_board.post_board_message('66710000-0000-4000-8000-000000000010','66710000-0000-4000-8000-000000000001','66710000-0000-4000-8000-000000000020','s','b'); }
32
+ step "a_end" { end; }
33
+ session "check"
34
+ step "check" { select (t.board_id is not null) as stamped, (select count(*) from agent_board.tasks where thread_id=t.id) as tasks, (select count(*) from agent_board.conflicts where thread_id=t.id) as conflicts, (select count(*) from agent_board.messages where thread_id=t.id) as messages from agent_board.threads t where t.id='66710000-0000-4000-8000-000000000010'; }
35
+ permutation "w_begin" "w_task" "a_begin" "a_post" "w_commit" "a_end" "check"
36
+ permutation "a_begin" "a_post" "w_begin" "w_task" "a_end" "w_commit" "check"
37
+ permutation "w_begin" "w_conflict" "a_begin" "a_post" "w_commit" "a_end" "check"
38
+ permutation "a_begin" "a_post" "w_begin" "w_conflict" "a_end" "w_commit" "check"
@@ -0,0 +1,42 @@
1
+ # #667: a task argument does not prove that a conflict or escalation's own
2
+ # thread equals its task thread. Both taskful writers serialize on their supplied
3
+ # thread before appending an event to the separately anchored task.
4
+ setup
5
+ {
6
+ insert into agent_board.agents(id,agent_type,handle,display_name,status)
7
+ values ('66760000-0000-4000-8000-000000000001','agent','xb-cross-task','XB cross task','active');
8
+ insert into agent_board.boards(id,slug,name,created_by_agent_id,visibility,status)
9
+ values ('66760000-0000-4000-8000-000000000020','xb-cross-task-board','XB cross task board',
10
+ '66760000-0000-4000-8000-000000000001','private','active');
11
+ insert into agent_board.threads(id,thread_type,created_by_agent_id,subject)
12
+ values ('66760000-0000-4000-8000-000000000010','coordination','66760000-0000-4000-8000-000000000001','empty target'),
13
+ ('66760000-0000-4000-8000-000000000011','coordination','66760000-0000-4000-8000-000000000001','task parent');
14
+ insert into agent_board.tasks(id,thread_id,created_by_agent_id,title)
15
+ values ('66760000-0000-4000-8000-000000000030','66760000-0000-4000-8000-000000000011','66760000-0000-4000-8000-000000000001','parent task');
16
+ }
17
+ teardown
18
+ {
19
+ delete from agent_board.conflicts where thread_id='66760000-0000-4000-8000-000000000010';
20
+ delete from agent_board.escalations where thread_id='66760000-0000-4000-8000-000000000010';
21
+ delete from agent_board.task_events where task_id='66760000-0000-4000-8000-000000000030';
22
+ delete from agent_board.tasks where id='66760000-0000-4000-8000-000000000030';
23
+ delete from agent_board.messages where thread_id='66760000-0000-4000-8000-000000000010';
24
+ delete from agent_board.threads where id in ('66760000-0000-4000-8000-000000000010','66760000-0000-4000-8000-000000000011');
25
+ delete from agent_board.boards where id='66760000-0000-4000-8000-000000000020';
26
+ delete from agent_board.agents where id='66760000-0000-4000-8000-000000000001';
27
+ }
28
+ session "w"
29
+ step "w_begin" { begin; select set_config('request.jwt.claim.role','service_role',true); }
30
+ step "w_conflict" { select count(*) from agent_board.raise_conflict('66760000-0000-4000-8000-000000000010','66760000-0000-4000-8000-000000000001','private conflict',p_task_id=>'66760000-0000-4000-8000-000000000030'); }
31
+ step "w_escalation" { select count(*) from agent_board.raise_escalation('66760000-0000-4000-8000-000000000010','66760000-0000-4000-8000-000000000001','private escalation',p_task_id=>'66760000-0000-4000-8000-000000000030'); }
32
+ step "w_commit" { commit; }
33
+ session "a"
34
+ step "a_begin" { begin; select set_config('request.jwt.claim.role','service_role',true); }
35
+ step "a_post" { select count(*) from agent_board.post_board_message('66760000-0000-4000-8000-000000000010','66760000-0000-4000-8000-000000000001','66760000-0000-4000-8000-000000000020','s','b'); }
36
+ step "a_end" { end; }
37
+ session "check"
38
+ step "check" { select (t.board_id is not null) as stamped, (select count(*) from agent_board.conflicts where thread_id=t.id and task_id='66760000-0000-4000-8000-000000000030') as conflicts, (select count(*) from agent_board.escalations where thread_id=t.id and task_id='66760000-0000-4000-8000-000000000030') as escalations, (select count(*) from agent_board.messages where thread_id=t.id) as messages from agent_board.threads t where t.id='66760000-0000-4000-8000-000000000010'; }
39
+ permutation "w_begin" "w_conflict" "a_begin" "a_post" "w_commit" "a_end" "check"
40
+ permutation "a_begin" "a_post" "w_begin" "w_conflict" "a_end" "w_commit" "check"
41
+ permutation "w_begin" "w_escalation" "a_begin" "a_post" "w_commit" "a_end" "check"
42
+ permutation "a_begin" "a_post" "w_begin" "w_escalation" "a_end" "w_commit" "check"
@@ -24,9 +24,9 @@
24
24
  # it does to A, isolating the lock as the single variable. The withdrawer withdraws
25
25
  # the ACTIVE row A; it blocks on the SET-lock, then applies after commit.
26
26
  #
27
- # Both sessions run as service_role — the SET-lock sits AFTER the actor check and is
28
- # actor-independent, so this needs no auth.users identity substrate; the lock trace
29
- # is identical to the authenticated path.
27
+ # Consiliency/message-board#668: the approver has a resolved active owner identity,
28
+ # matching the resolver's flat authority gate. The withdrawer remains service_role;
29
+ # the whole-set lock must still serialize the same two physical rows.
30
30
  #
31
31
  # MUTATION SIGNAL (a TRACE diff): drop the `for update` block, OR narrow it with
32
32
  # `and bm.membership_state in ('requested','invited')` — either leaves A unlocked, so
@@ -35,15 +35,20 @@
35
35
 
36
36
  setup
37
37
  {
38
- insert into agent_board.agents(id, agent_type, handle, display_name, status) values
39
- ('115a0000-0000-4000-8000-000000000001', 'agent', 'iso115-owner', 'Iso115 Owner', 'active'),
40
- ('115a0000-0000-4000-8000-000000000002', 'agent', 'iso115-subject', 'Iso115 Subject', 'active');
38
+ insert into auth.users(id, instance_id, aud, role, email) values
39
+ ('115d0000-0000-4000-8000-000000000001', '00000000-0000-0000-0000-000000000000',
40
+ 'authenticated', 'authenticated', 'iso115-owner@example.test');
41
+ insert into agent_board.agents(id, agent_type, handle, display_name, status, auth_user_id, owner_user_id) values
42
+ ('115a0000-0000-4000-8000-000000000001', 'agent', 'iso115-owner', 'Iso115 Owner', 'active', '115d0000-0000-4000-8000-000000000001', '115d0000-0000-4000-8000-000000000001'),
43
+ ('115a0000-0000-4000-8000-000000000002', 'agent', 'iso115-subject', 'Iso115 Subject', 'active', null, null);
41
44
  insert into agent_board.boards(id, slug, name, created_by_agent_id, visibility, status)
42
45
  values ('115b0000-0000-4000-8000-000000000001', 'iso115-board', 'Iso115 Board',
43
46
  '115a0000-0000-4000-8000-000000000001', 'private', 'active');
44
47
  -- Legal (active, pending) duplicate for the subject: A is joined (active), P is a
45
48
  -- separate pending request. The active-unique index permits it (only A is active).
46
49
  insert into agent_board.board_memberships(id, board_id, agent_id, membership_state, role, created_by_agent_id) values
50
+ ('115c0000-0000-4000-8000-000000000001', '115b0000-0000-4000-8000-000000000001',
51
+ '115a0000-0000-4000-8000-000000000001', 'joined', 'owner', '115a0000-0000-4000-8000-000000000001'),
47
52
  ('115c0000-0000-4000-8000-0000000000aa', '115b0000-0000-4000-8000-000000000001',
48
53
  '115a0000-0000-4000-8000-000000000002', 'joined', 'member', '115a0000-0000-4000-8000-000000000002'),
49
54
  ('115c0000-0000-4000-8000-0000000000bb', '115b0000-0000-4000-8000-000000000001',
@@ -55,13 +60,14 @@ teardown
55
60
  delete from agent_board.board_membership_events where board_id = '115b0000-0000-4000-8000-000000000001';
56
61
  delete from agent_board.boards where id = '115b0000-0000-4000-8000-000000000001';
57
62
  delete from agent_board.agents where id in ('115a0000-0000-4000-8000-000000000001','115a0000-0000-4000-8000-000000000002');
63
+ delete from auth.users where id = '115d0000-0000-4000-8000-000000000001';
58
64
  }
59
65
 
60
66
  # approver resolves the subject to 'joined'. With active row A present this is the
61
67
  # Option-1 path — it acquires the whole-set `for update` and returns A untouched
62
68
  # (retiring the dangling pending row P via the sink, but never writing A).
63
69
  session "approver"
64
- step "a_begin" { BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role'; }
70
+ step "a_begin" { BEGIN; SET LOCAL "request.jwt.claim.role" = 'authenticated'; SET LOCAL "request.jwt.claim.sub" = '115d0000-0000-4000-8000-000000000001'; }
65
71
  step "a_resolve" {
66
72
  select membership_state
67
73
  from agent_board.resolve_board_request(
@@ -0,0 +1,77 @@
1
+ # #717 F2b: concurrent same-key send_direct_message / send_multicast_message calls serialize on the
2
+ # unchanged (rpc, thread, resolved author, key) lock, and the second one is judged against what the
3
+ # first COMMITTED. Two sends naming DIFFERENT recipients on one key: whichever commits first is the
4
+ # message, and the other is refused (42501) rather than widening it. Two identical retries: one
5
+ # message, one delivery per recipient, one inbox item each, one callback each.
6
+ # A lock keyed by recipient (split domain) or removed lets the second send miss the uncommitted
7
+ # first message and mint a second one; the check step then counts two messages.
8
+ setup
9
+ {
10
+ insert into auth.users(id) values ('717b4000-0000-4000-8000-0000000000a1'),('717b4000-0000-4000-8000-0000000000c1'),('717b4000-0000-4000-8000-0000000000c2'),('717b4000-0000-4000-8000-0000000000e1');
11
+ insert into agent_board.agents(id,agent_type,handle,display_name,status,auth_user_id)
12
+ values ('717b4000-0000-4000-8000-000000000001','agent','f2b717-iso-a','F2b iso A','active','717b4000-0000-4000-8000-0000000000a1'),
13
+ ('717b4000-0000-4000-8000-000000000003','agent','f2b717-iso-c','F2b iso C','active','717b4000-0000-4000-8000-0000000000c1'),
14
+ ('717b4000-0000-4000-8000-000000000004','agent','f2b717-iso-c2','F2b iso C2','active','717b4000-0000-4000-8000-0000000000c2'),
15
+ ('717b4000-0000-4000-8000-000000000005','agent','f2b717-iso-x','F2b iso X','active','717b4000-0000-4000-8000-0000000000e1');
16
+ insert into agent_board.threads(id,thread_type,created_by_agent_id,subject)
17
+ values ('717b4000-0000-4000-8000-000000000010','coordination','717b4000-0000-4000-8000-000000000001','f2b iso thread');
18
+ insert into agent_board.callback_endpoints(id,owner_agent_id,endpoint_url,endpoint_status)
19
+ values ('717b4000-0000-4000-8000-000000000030','717b4000-0000-4000-8000-000000000003','https://example.test/f2b717-iso-c','verified'),
20
+ ('717b4000-0000-4000-8000-000000000031','717b4000-0000-4000-8000-000000000004','https://example.test/f2b717-iso-c2','verified'),
21
+ ('717b4000-0000-4000-8000-000000000032','717b4000-0000-4000-8000-000000000005','https://example.test/f2b717-iso-x','verified');
22
+ insert into agent_board.callback_subscriptions(endpoint_id,agent_id,subscription_scope,event_category)
23
+ values ('717b4000-0000-4000-8000-000000000030','717b4000-0000-4000-8000-000000000003','own_inbox','inbox.item.delivered'),
24
+ ('717b4000-0000-4000-8000-000000000031','717b4000-0000-4000-8000-000000000004','own_inbox','inbox.item.delivered'),
25
+ ('717b4000-0000-4000-8000-000000000032','717b4000-0000-4000-8000-000000000005','own_inbox','inbox.item.delivered');
26
+ }
27
+ teardown
28
+ {
29
+ delete from agent_board.callback_outbox_events where recipient_agent_id in ('717b4000-0000-4000-8000-000000000003','717b4000-0000-4000-8000-000000000004','717b4000-0000-4000-8000-000000000005');
30
+ delete from agent_board.callback_subscriptions where endpoint_id in ('717b4000-0000-4000-8000-000000000030','717b4000-0000-4000-8000-000000000031','717b4000-0000-4000-8000-000000000032');
31
+ delete from agent_board.callback_endpoints where id in ('717b4000-0000-4000-8000-000000000030','717b4000-0000-4000-8000-000000000031','717b4000-0000-4000-8000-000000000032');
32
+ delete from agent_board.inbox_items where recipient_agent_id in ('717b4000-0000-4000-8000-000000000003','717b4000-0000-4000-8000-000000000004','717b4000-0000-4000-8000-000000000005');
33
+ delete from agent_board.delivery_recipients where recipient_agent_id in ('717b4000-0000-4000-8000-000000000003','717b4000-0000-4000-8000-000000000004','717b4000-0000-4000-8000-000000000005');
34
+ delete from agent_board.messages where thread_id='717b4000-0000-4000-8000-000000000010';
35
+ delete from agent_board.threads where id='717b4000-0000-4000-8000-000000000010';
36
+ delete from agent_board.agents where id in ('717b4000-0000-4000-8000-000000000001','717b4000-0000-4000-8000-000000000003','717b4000-0000-4000-8000-000000000004','717b4000-0000-4000-8000-000000000005');
37
+ delete from auth.users where id in ('717b4000-0000-4000-8000-0000000000a1','717b4000-0000-4000-8000-0000000000c1','717b4000-0000-4000-8000-0000000000c2','717b4000-0000-4000-8000-0000000000e1');
38
+ }
39
+ # Each sending session is the author A: authenticated role AND matching JSON claims (built with
40
+ # json_build_object: a literal brace would end the step), with the flattened claim GUCs cleared so
41
+ # they cannot stand in for another identity.
42
+ session "p"
43
+ step "p_begin" { begin; set local role authenticated; select set_config('request.jwt.claim.role','',true), set_config('request.jwt.claim.sub','',true), set_config('request.jwt.claims',json_build_object('role','authenticated','sub','717b4000-0000-4000-8000-0000000000a1')::text,true); }
44
+ step "p_dm_c" { select callback_outbox_count from agent_board.send_direct_message('717b4000-0000-4000-8000-000000000010','717b4000-0000-4000-8000-000000000001','717b4000-0000-4000-8000-000000000003','iso','iso dm','comment','medium','f2b717-kd',jsonb_build_object()); }
45
+ step "p_mc_cc2" { select delivered_recipient_count, inbox_item_count, callback_outbox_count from agent_board.send_multicast_message('717b4000-0000-4000-8000-000000000010','717b4000-0000-4000-8000-000000000001',array['717b4000-0000-4000-8000-000000000003','717b4000-0000-4000-8000-000000000004']::uuid[],'iso','iso mc','comment','medium','f2b717-km',jsonb_build_object()); }
46
+ step "p_end" { end; }
47
+ session "w"
48
+ step "w_begin" { begin; set local role authenticated; select set_config('request.jwt.claim.role','',true), set_config('request.jwt.claim.sub','',true), set_config('request.jwt.claims',json_build_object('role','authenticated','sub','717b4000-0000-4000-8000-0000000000a1')::text,true); }
49
+ step "w_dm_x" { select callback_outbox_count from agent_board.send_direct_message('717b4000-0000-4000-8000-000000000010','717b4000-0000-4000-8000-000000000001','717b4000-0000-4000-8000-000000000005','iso','iso dm','comment','medium','f2b717-kd',jsonb_build_object()); }
50
+ step "w_mc_cx" { select delivered_recipient_count, inbox_item_count, callback_outbox_count from agent_board.send_multicast_message('717b4000-0000-4000-8000-000000000010','717b4000-0000-4000-8000-000000000001',array['717b4000-0000-4000-8000-000000000003','717b4000-0000-4000-8000-000000000005']::uuid[],'iso','iso mc','comment','medium','f2b717-km',jsonb_build_object()); }
51
+ step "w_end" { end; }
52
+ session "r"
53
+ step "r_begin" { begin; set local role authenticated; select set_config('request.jwt.claim.role','',true), set_config('request.jwt.claim.sub','',true), set_config('request.jwt.claims',json_build_object('role','authenticated','sub','717b4000-0000-4000-8000-0000000000a1')::text,true); }
54
+ step "r_dm_c" { select callback_outbox_count from agent_board.send_direct_message('717b4000-0000-4000-8000-000000000010','717b4000-0000-4000-8000-000000000001','717b4000-0000-4000-8000-000000000003','iso','iso dm','comment','medium','f2b717-kd',jsonb_build_object()); }
55
+ step "r_mc_c2c" { select delivered_recipient_count, inbox_item_count, callback_outbox_count from agent_board.send_multicast_message('717b4000-0000-4000-8000-000000000010','717b4000-0000-4000-8000-000000000001',array['717b4000-0000-4000-8000-000000000004','717b4000-0000-4000-8000-000000000003','717b4000-0000-4000-8000-000000000004']::uuid[],'iso','iso mc','comment','medium','f2b717-km',jsonb_build_object()); }
56
+ step "r_end" { end; }
57
+ session "check"
58
+ step "check" {
59
+ select count(*) as messages,
60
+ (select string_agg(a.handle || ':' || dr.delivery_mode, ',' order by a.handle, dr.delivery_mode)
61
+ from agent_board.delivery_recipients dr join agent_board.messages m on m.id = dr.source_message_id
62
+ join agent_board.agents a on a.id = dr.recipient_agent_id
63
+ where m.thread_id = '717b4000-0000-4000-8000-000000000010') as deliveries,
64
+ (select count(*) from agent_board.inbox_items ii join agent_board.messages m on m.id = ii.source_message_id where m.thread_id = '717b4000-0000-4000-8000-000000000010') as inbox,
65
+ (select count(*) from agent_board.callback_outbox_events co join agent_board.messages m on m.id = co.source_message_id where m.thread_id = '717b4000-0000-4000-8000-000000000010') as callbacks
66
+ from agent_board.messages where thread_id = '717b4000-0000-4000-8000-000000000010';
67
+ }
68
+ # direct, C wins: the send to X waits on the key lock, then is refused against the committed message
69
+ permutation "p_begin" "p_dm_c" "w_begin" "w_dm_x" "p_end" "w_end" "check"
70
+ # direct, X wins: the send to C waits, then is refused (C holds no direct delivery of X's message)
71
+ permutation "w_begin" "w_dm_x" "p_begin" "p_dm_c" "w_end" "p_end" "check"
72
+ # direct, identical retries: the second waits and reuses -- one message, delivery, inbox item, callback
73
+ permutation "p_begin" "p_dm_c" "r_begin" "r_dm_c" "p_end" "r_end" "check"
74
+ # multicast, {C,C2} wins: {C,X} waits, then is refused (X is new)
75
+ permutation "p_begin" "p_mc_cc2" "w_begin" "w_mc_cx" "p_end" "w_end" "check"
76
+ # multicast, identical set reordered and duplicated: one message, two deliveries, two inbox items, two callbacks
77
+ permutation "p_begin" "p_mc_cc2" "r_begin" "r_mc_c2c" "p_end" "r_end" "check"
@@ -0,0 +1,72 @@
1
+ # #717 F2: concurrent same-key create_tasking_delivery sends serialize on the unchanged
2
+ # (thread, resolved creator, key) lock, and the second one is judged against what the first
3
+ # COMMITTED. A private send and a board send racing on one key: whichever commits first is the
4
+ # task, and the other is refused (42501) rather than widening it. Two identical retries racing:
5
+ # one task, one created event, one delivery, one inbox item, one callback.
6
+ # The thread is already stamped to the board, so first association never serializes them: only
7
+ # the idempotency lock does.
8
+ setup
9
+ {
10
+ insert into auth.users(id) values ('71740000-0000-4000-8000-0000000000a1'),('71740000-0000-4000-8000-0000000000c1');
11
+ insert into agent_board.agents(id,agent_type,handle,display_name,status,auth_user_id)
12
+ values ('71740000-0000-4000-8000-000000000001','agent','f2717-iso-a','F2 iso A','active','71740000-0000-4000-8000-0000000000a1'),
13
+ ('71740000-0000-4000-8000-000000000003','agent','f2717-iso-c','F2 iso C','active','71740000-0000-4000-8000-0000000000c1');
14
+ insert into agent_board.boards(id,slug,name,created_by_agent_id,visibility,status)
15
+ values ('71740000-0000-4000-8000-000000000020','f2717-iso-b','F2 iso board','71740000-0000-4000-8000-000000000001','private','active');
16
+ select set_config('request.jwt.claim.role','service_role',false);
17
+ insert into agent_board.board_memberships(board_id,agent_id,membership_state,created_by_agent_id,role)
18
+ values ('71740000-0000-4000-8000-000000000020','71740000-0000-4000-8000-000000000001','joined','71740000-0000-4000-8000-000000000001','member');
19
+ insert into agent_board.threads(id,thread_type,created_by_agent_id,subject,board_id)
20
+ values ('71740000-0000-4000-8000-000000000010','coordination','71740000-0000-4000-8000-000000000001','f2 iso thread','71740000-0000-4000-8000-000000000020');
21
+ insert into agent_board.callback_endpoints(id,owner_agent_id,endpoint_url,endpoint_status)
22
+ values ('71740000-0000-4000-8000-000000000030','71740000-0000-4000-8000-000000000003','https://example.test/f2717-iso','verified');
23
+ insert into agent_board.callback_subscriptions(endpoint_id,agent_id,subscription_scope,event_category)
24
+ values ('71740000-0000-4000-8000-000000000030','71740000-0000-4000-8000-000000000003','own_inbox','inbox.item.delivered');
25
+ }
26
+ teardown
27
+ {
28
+ delete from agent_board.callback_outbox_events where recipient_agent_id in ('71740000-0000-4000-8000-000000000001','71740000-0000-4000-8000-000000000003');
29
+ delete from agent_board.callback_subscriptions where endpoint_id='71740000-0000-4000-8000-000000000030';
30
+ delete from agent_board.callback_endpoints where id='71740000-0000-4000-8000-000000000030';
31
+ delete from agent_board.inbox_items where recipient_agent_id in ('71740000-0000-4000-8000-000000000001','71740000-0000-4000-8000-000000000003');
32
+ delete from agent_board.delivery_recipients where recipient_agent_id in ('71740000-0000-4000-8000-000000000001','71740000-0000-4000-8000-000000000003');
33
+ delete from agent_board.task_events where thread_id='71740000-0000-4000-8000-000000000010';
34
+ delete from agent_board.tasks where thread_id='71740000-0000-4000-8000-000000000010';
35
+ delete from agent_board.threads where id='71740000-0000-4000-8000-000000000010';
36
+ delete from agent_board.board_membership_events where board_id='71740000-0000-4000-8000-000000000020';
37
+ delete from agent_board.boards where id='71740000-0000-4000-8000-000000000020';
38
+ delete from agent_board.agents where id in ('71740000-0000-4000-8000-000000000001','71740000-0000-4000-8000-000000000003');
39
+ delete from auth.users where id in ('71740000-0000-4000-8000-0000000000a1','71740000-0000-4000-8000-0000000000c1');
40
+ }
41
+ # Each sending session is the creator A: authenticated role AND matching JSON claims (built with
42
+ # json_build_object: a literal brace would end the step), with the
43
+ # flattened claim GUCs cleared so they cannot stand in for another identity.
44
+ session "p"
45
+ step "p_begin" { begin; set local role authenticated; select set_config('request.jwt.claim.role','',true), set_config('request.jwt.claim.sub','',true), set_config('request.jwt.claims',json_build_object('role','authenticated','sub','71740000-0000-4000-8000-0000000000a1')::text,true); }
46
+ step "p_private" { select delivered_recipient_count, inbox_item_count, callback_outbox_count from agent_board.create_tasking_delivery('71740000-0000-4000-8000-000000000010','71740000-0000-4000-8000-000000000001','iso private',null,array['71740000-0000-4000-8000-000000000003']::uuid[],null,'medium','distributed','f2717-kc',jsonb_build_object()); }
47
+ step "p_end" { end; }
48
+ session "b"
49
+ step "b_begin" { begin; set local role authenticated; select set_config('request.jwt.claim.role','',true), set_config('request.jwt.claim.sub','',true), set_config('request.jwt.claims',json_build_object('role','authenticated','sub','71740000-0000-4000-8000-0000000000a1')::text,true); }
50
+ step "b_board" { select delivered_recipient_count, inbox_item_count, callback_outbox_count from agent_board.create_tasking_delivery('71740000-0000-4000-8000-000000000010','71740000-0000-4000-8000-000000000001','iso board',null,null,'71740000-0000-4000-8000-000000000020','medium','distributed','f2717-kc',jsonb_build_object()); }
51
+ step "b_end" { end; }
52
+ session "r"
53
+ step "r_begin" { begin; set local role authenticated; select set_config('request.jwt.claim.role','',true), set_config('request.jwt.claim.sub','',true), set_config('request.jwt.claims',json_build_object('role','authenticated','sub','71740000-0000-4000-8000-0000000000a1')::text,true); }
54
+ step "r_private" { select delivered_recipient_count, inbox_item_count, callback_outbox_count from agent_board.create_tasking_delivery('71740000-0000-4000-8000-000000000010','71740000-0000-4000-8000-000000000001','iso private',null,array['71740000-0000-4000-8000-000000000003']::uuid[],null,'medium','distributed','f2717-kc',jsonb_build_object()); }
55
+ step "r_end" { end; }
56
+ session "check"
57
+ step "check" {
58
+ select count(*) as tasks,
59
+ (select count(*) from agent_board.task_events te join agent_board.tasks t on t.id = te.task_id where t.thread_id = '71740000-0000-4000-8000-000000000010') as events,
60
+ (select count(*) from agent_board.delivery_recipients dr join agent_board.tasks t on t.id = dr.source_task_id where t.thread_id = '71740000-0000-4000-8000-000000000010' and dr.board_id is null) as private_deliveries,
61
+ (select count(*) from agent_board.delivery_recipients dr join agent_board.tasks t on t.id = dr.source_task_id where t.thread_id = '71740000-0000-4000-8000-000000000010' and dr.board_id is not null) as board_deliveries,
62
+ (select count(*) from agent_board.inbox_items ii join agent_board.tasks t on t.id = ii.source_task_id where t.thread_id = '71740000-0000-4000-8000-000000000010') as inbox,
63
+ (select count(*) from agent_board.callback_outbox_events co join agent_board.tasks t on t.id = co.source_task_id where t.thread_id = '71740000-0000-4000-8000-000000000010') as callbacks,
64
+ string_agg(title, ',' order by title) as titles
65
+ from agent_board.tasks where thread_id = '71740000-0000-4000-8000-000000000010';
66
+ }
67
+ # private wins: the board send waits on the key lock, then is refused against the committed private task
68
+ permutation "p_begin" "p_private" "b_begin" "b_board" "p_end" "b_end" "check"
69
+ # board wins: the private send waits, then is refused (C holds no private delivery of the board task)
70
+ permutation "b_begin" "b_board" "p_begin" "p_private" "b_end" "p_end" "check"
71
+ # identical retries: the second waits and reuses -- one task, event, delivery, inbox item, callback
72
+ permutation "p_begin" "p_private" "r_begin" "r_private" "p_end" "r_end" "check"
@@ -0,0 +1,11 @@
1
+ -- Reverse of 20261104180000: the exact origin/main 21b7b158 ACL (service_role EXECUTE) and comments.
2
+ grant execute on function agent_board._upsert_delivery_recipient(text,uuid,text,uuid,uuid,uuid,uuid,uuid,jsonb) to service_role;
3
+ grant execute on function agent_board._upsert_inbox_item(uuid,uuid,uuid,text,text,uuid,uuid,uuid,text,text,text,boolean,boolean,jsonb,jsonb) to service_role;
4
+ grant execute on function agent_board._queue_inbox_callbacks(uuid,text) to service_role;
5
+ grant execute on function agent_board._queue_board_post_callbacks(uuid,uuid,text,text,text,jsonb) to service_role;
6
+ grant execute on function agent_board._next_thread_message_sequence(uuid) to service_role;
7
+ comment on function agent_board._upsert_delivery_recipient(text,uuid,text,uuid,uuid,uuid,uuid,uuid,jsonb) is 'Enforces thread/board identity and the locked empty-history boundary for message, task and delegation delivery sources. Supplying a source ID does not exempt its private history. The sending RPC validates caller authority before its own writes.';
8
+ comment on function agent_board._upsert_inbox_item(uuid,uuid,uuid,text,text,uuid,uuid,uuid,text,text,text,boolean,boolean,jsonb,jsonb) is null;
9
+ comment on function agent_board._queue_inbox_callbacks(uuid,text) is null;
10
+ comment on function agent_board._queue_board_post_callbacks(uuid,uuid,text,text,text,jsonb) is null;
11
+ comment on function agent_board._next_thread_message_sequence(uuid) is null;
@@ -0,0 +1,7 @@
1
+ do $m$ declare d text := pg_get_functiondef('agent_board.post_board_message(uuid,uuid,text,text,text,text,text,text,text,jsonb)'::regprocedure);
2
+ hdr text; body text;
3
+ begin
4
+ hdr := split_part(d, '$function$', 1); body := split_part(d, '$function$', 2);
5
+ if body !~ 'agent_board\.post_board_message\(' then raise exception 'mutation anchor missing'; end if;
6
+ execute hdr || '$function$' || regexp_replace(body, 'agent_board\.post_board_message\(', 'agent_board.post_board_message_gone(', 'g') || '$function$';
7
+ end $m$;
@@ -0,0 +1,8 @@
1
+ create function agent_board.review_f1_widen(p_inbox uuid) returns void language sql security definer set search_path=agent_board,public as $f$
2
+ update agent_board.inbox_items set preview='forged preview through state update' where id=p_inbox;
3
+ $f$;
4
+ revoke all on function agent_board.review_f1_widen(uuid) from public,anon,authenticated,service_role,agent_board_delegate;
5
+ do $m$ declare d text := pg_get_functiondef('agent_board.update_inbox_item_state(uuid,uuid,text,jsonb)'::regprocedure); begin
6
+ if position('v_callback_count := agent_board._queue_inbox_callbacks' in d)=0 then raise exception 'missing anchor'; end if;
7
+ execute replace(d, 'v_callback_count := agent_board._queue_inbox_callbacks', 'perform agent_board.review_f1_widen(v_inbox.id); v_callback_count := agent_board._queue_inbox_callbacks');
8
+ end $m$;
@@ -0,0 +1,8 @@
1
+ create function agent_board.review_f1_widen(p_inbox uuid) returns void language sql security definer set search_path=agent_board,public as $f$
2
+ update agent_board.inbox_items set visible_in_board_feed=true where id=p_inbox;
3
+ $f$;
4
+ revoke all on function agent_board.review_f1_widen(uuid) from public,anon,authenticated,service_role,agent_board_delegate;
5
+ do $m$ declare d text := pg_get_functiondef('agent_board.update_inbox_item_state(uuid,uuid,text,jsonb)'::regprocedure); begin
6
+ if position('v_callback_count := agent_board._queue_inbox_callbacks' in d)=0 then raise exception 'missing anchor'; end if;
7
+ execute replace(d, 'v_callback_count := agent_board._queue_inbox_callbacks', 'perform agent_board.review_f1_widen(v_inbox.id); v_callback_count := agent_board._queue_inbox_callbacks');
8
+ end $m$;
@@ -0,0 +1,15 @@
1
+ -- Two private hops must not hide a worker's content write from DWX-4c.
2
+ create function agent_board.dwx_worker_sink() returns void language sql security definer
3
+ set search_path=agent_board,public as $f$
4
+ update agent_board.inbox_items set preview='forged worker preview' where false;
5
+ $f$;
6
+ create function agent_board.dwx_worker_bridge() returns void language sql security definer
7
+ set search_path=agent_board,public as $f$ select agent_board.dwx_worker_sink(); $f$;
8
+ revoke all on function agent_board.dwx_worker_sink(), agent_board.dwx_worker_bridge()
9
+ from public,anon,authenticated,service_role,agent_board_delegate;
10
+ do $m$
11
+ declare d text := pg_get_functiondef('agent_board.claim_callback_outbox_events(integer)'::regprocedure);
12
+ begin
13
+ if position(E'begin\n' in d)=0 then raise exception 'missing worker begin anchor'; end if;
14
+ execute replace(d, E'begin\n', E'begin\n perform agent_board.dwx_worker_bridge();\n');
15
+ end $m$;
@@ -0,0 +1 @@
1
+ grant execute on function agent_board._upsert_delivery_recipient(text,uuid,text,uuid,uuid,uuid,uuid,uuid,jsonb) to service_role;
@@ -0,0 +1 @@
1
+ grant execute on function agent_board._upsert_inbox_item(uuid,uuid,uuid,text,text,uuid,uuid,uuid,text,text,text,boolean,boolean,jsonb,jsonb) to service_role;
@@ -0,0 +1 @@
1
+ grant execute on function agent_board._queue_inbox_callbacks(uuid,text) to service_role;
@@ -0,0 +1 @@
1
+ grant execute on function agent_board._queue_board_post_callbacks(uuid,uuid,text,text,text,jsonb) to service_role;
@@ -0,0 +1 @@
1
+ grant execute on function agent_board._next_thread_message_sequence(uuid) to service_role;
@@ -0,0 +1 @@
1
+ grant execute on all functions in schema agent_board to service_role;
@@ -0,0 +1,10 @@
1
+ create function agent_board._upsert_delivery_recipient(p_delivery_mode text, p_recipient_agent_id uuid, p_source_kind text, p_source_message_id uuid, p_extra text)
2
+ returns uuid language plpgsql security definer set search_path = agent_board, public as $f$
3
+ declare v uuid;
4
+ begin
5
+ insert into agent_board.delivery_recipients(delivery_mode, recipient_agent_id, source_kind, source_message_id)
6
+ values (p_delivery_mode, p_recipient_agent_id, p_source_kind, p_source_message_id) returning id into v;
7
+ return v;
8
+ end $f$;
9
+ revoke all on function agent_board._upsert_delivery_recipient(text,uuid,text,uuid,text) from public;
10
+ grant execute on function agent_board._upsert_delivery_recipient(text,uuid,text,uuid,text) to service_role;
@@ -0,0 +1,7 @@
1
+ create function agent_board.relay_inbox_item(p_dr uuid, p_recipient uuid)
2
+ returns uuid language plpgsql security definer set search_path = agent_board, public as $f$
3
+ begin
4
+ return agent_board._upsert_inbox_item(p_dr, p_recipient, null, 'direct', 'message', null, null, null, 's', 'p', 'medium', true, false, '{}'::jsonb, '{}'::jsonb);
5
+ end $f$;
6
+ revoke all on function agent_board.relay_inbox_item(uuid,uuid) from public;
7
+ grant execute on function agent_board.relay_inbox_item(uuid,uuid) to authenticated;
@@ -0,0 +1,5 @@
1
+ do $m$ begin
2
+ execute replace(pg_get_functiondef('agent_board._resolved_actor_agent_id(uuid,text)'::regprocedure),
3
+ 'if p_supplied_agent_id is not null and p_supplied_agent_id <> v_current_agent_id then',
4
+ 'if p_supplied_agent_id is not null then return p_supplied_agent_id; end if; if false then');
5
+ end $m$;
@@ -0,0 +1 @@
1
+ grant execute on function agent_board.signed_post_board_message(jsonb,jsonb) to authenticated;
@@ -0,0 +1 @@
1
+ grant execute on function agent_board.claim_callback_outbox_events(integer) to authenticated;
@@ -0,0 +1,5 @@
1
+ do $m$ begin
2
+ execute replace(pg_get_functiondef('agent_board.update_inbox_item_state(uuid,uuid,text,jsonb)'::regprocedure),
3
+ 'set action_state = p_action_state,',
4
+ 'set action_state = p_action_state, visible_in_board_feed = true,');
5
+ end $m$;
@@ -0,0 +1 @@
1
+ revoke execute on function agent_board.send_direct_message(uuid,uuid,uuid,text,text,text,text,text,jsonb) from service_role;