@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
@@ -40,7 +40,7 @@ select plan(40);
40
40
  -- The class, derived from pg_proc.
41
41
  -- ---------------------------------------------------------------------------
42
42
  select is(
43
- (select array_agg(p.proname::text order by p.proname)
43
+ (select array_agg(p.proname::text order by p.proname::text collate "C")
44
44
  from pg_proc p join pg_namespace n on n.oid = p.pronamespace
45
45
  where n.nspname = 'agent_board'
46
46
  and agent_board_test.stripped_body(p.oid) ~ agent_board_test.stmt_re('insert into|merge into', 'inbox_items')),
@@ -48,17 +48,17 @@ select is(
48
48
  'OPN-01: _upsert_inbox_item is the only function that inserts into inbox_items');
49
49
 
50
50
  select is(
51
- (select array_agg(p.proname::text order by p.proname)
51
+ (select array_agg(p.proname::text order by p.proname::text collate "C")
52
52
  from pg_proc p join pg_namespace n on n.oid = p.pronamespace
53
53
  where n.nspname = 'agent_board'
54
54
  and agent_board_test.stripped_body(p.oid) ~ agent_board_test.call_re('_upsert_inbox_item')
55
55
  and agent_board_test.stripped_body(p.oid) ~ agent_board_test.qname_re('board_memberships')
56
56
  and p.proname <> '_upsert_inbox_item'),
57
- array['create_tasking_delivery', 'post_board_message'],
57
+ array['_deliver_board_message', 'create_tasking_delivery'],
58
58
  'OPN-02: the board-expanding delivery writers are exactly the two this file asserts behaviourally');
59
59
 
60
60
  select is(
61
- (select array_agg(p.proname::text order by p.proname)
61
+ (select array_agg(p.proname::text order by p.proname::text collate "C")
62
62
  from pg_proc p join pg_namespace n on n.oid = p.pronamespace
63
63
  where n.nspname = 'agent_board'
64
64
  and agent_board_test.stripped_body(p.oid) ~ agent_board_test.call_re('_upsert_inbox_item')
@@ -73,7 +73,7 @@ select is(
73
73
  create temp table _opn (
74
74
  a_user uuid, b_user uuid,
75
75
  a_agent uuid, b_agent uuid,
76
- board_ab uuid, board_s uuid, thread uuid, thread_unlinked uuid, thread_unlinked2 uuid
76
+ board_ab uuid, board_s uuid, thread uuid, thread_unlinked uuid, thread_unlinked2 uuid, thread_sole_post uuid
77
77
  ) on commit drop;
78
78
  create temp table _opn_post (message_id uuid, board_id uuid, delivered_recipient_count integer,
79
79
  callback_outbox_count integer, created_at timestamptz) on commit drop;
@@ -106,6 +106,7 @@ declare
106
106
  v_thread uuid := '76080000-0000-4000-8000-000000000010';
107
107
  v_thread_unlinked uuid := '76080000-0000-4000-8000-000000000011';
108
108
  v_thread_unlinked2 uuid := '76080000-0000-4000-8000-000000000012';
109
+ v_thread_sole_post uuid := '76080000-0000-4000-8000-000000000013';
109
110
  v_board_ab uuid;
110
111
  v_board_s uuid;
111
112
  v_endpoint uuid;
@@ -120,7 +121,8 @@ begin
120
121
  insert into agent_board.threads(id, thread_type, created_by_agent_id, subject)
121
122
  values (v_thread, 'coordination', v_a, 'OPN thread'),
122
123
  (v_thread_unlinked, 'coordination', v_a, 'OPN unlinked thread'),
123
- (v_thread_unlinked2, 'coordination', v_a, 'OPN second unlinked thread');
124
+ (v_thread_unlinked2, 'coordination', v_a, 'OPN second unlinked thread'),
125
+ (v_thread_sole_post, 'coordination', v_a, 'OPN empty sole-post thread');
124
126
 
125
127
  select board_id into v_board_ab
126
128
  from agent_board.create_board(v_a, 'opn-board-ab', 'OPN AB', 'two members', '{}'::jsonb, 'private');
@@ -137,7 +139,7 @@ begin
137
139
  perform agent_board.verify_callback_endpoint(v_endpoint, v_b, '{"controlProofRef":"opn/b"}'::jsonb);
138
140
  perform agent_board.subscribe_callback_endpoint(v_endpoint, v_b, 'own_inbox', 'inbox.item.delivered', null, '{}'::jsonb);
139
141
 
140
- insert into _opn values (v_a_user, v_b_user, v_a, v_b, v_board_ab, v_board_s, v_thread, v_thread_unlinked, v_thread_unlinked2);
142
+ insert into _opn values (v_a_user, v_b_user, v_a, v_b, v_board_ab, v_board_s, v_thread, v_thread_unlinked, v_thread_unlinked2, v_thread_sole_post);
141
143
  end;
142
144
  $$;
143
145
 
@@ -219,7 +221,7 @@ set local role authenticated;
219
221
  select lives_ok(
220
222
  $$ insert into _opn_sole
221
223
  select * from agent_board.post_board_message(
222
- (select thread from _opn), null, (select board_s from _opn),
224
+ (select thread_sole_post from _opn), null, (select board_s from _opn),
223
225
  'opn sole post', 'the author posts to a board it is alone on', 'comment', 'medium', null, '{}'::jsonb) $$,
224
226
  'OPN-12: the author can post to a board it is the only member of');
225
227
 
@@ -479,7 +481,7 @@ select is(
479
481
  -- (explicit_set recursion), the read-only resolve_recipients, and _addressing_agent_ids_or_raise --
480
482
  -- the one delivery path, where the #608 rule lives. A new caller that delivers fails this by name.
481
483
  select is(
482
- (select array_agg(p.proname::text order by p.proname)
484
+ (select array_agg(p.proname::text order by p.proname::text collate "C")
483
485
  from pg_proc p join pg_namespace n on n.oid = p.pronamespace
484
486
  where n.nspname = 'agent_board'
485
487
  and agent_board_test.stripped_body(p.oid) ~ agent_board_test.call_re('_resolve_addressing_recipient_rows')),
@@ -0,0 +1,189 @@
1
+ create extension if not exists pgtap with schema extensions;
2
+ set search_path = extensions, public, agent_board;
3
+ begin;
4
+ select plan(41);
5
+ create function pg_temp.a(n integer) returns uuid language sql immutable as $$
6
+ select ('58900000-0000-4000-8000-'||lpad(n::text,12,'0'))::uuid
7
+ $$;
8
+ create function pg_temp.claim(n integer) returns void language plpgsql as $$
9
+ begin perform set_config('request.jwt.claims',json_build_object('role','authenticated','sub',pg_temp.a(n))::text,true); end $$;
10
+ insert into auth.users(id,instance_id,aud,role,email)
11
+ select pg_temp.a(n),'00000000-0000-0000-0000-000000000000','authenticated','authenticated','rls589-'||n||'@test.invalid' from generate_series(1,9)n;
12
+ insert into agent_board.agents(id,agent_type,handle,display_name,status,auth_user_id)
13
+ select pg_temp.a(n),'agent','rls589-'||n,'RLS '||n,'active',pg_temp.a(n) from generate_series(1,9)n;
14
+ insert into agent_board.boards(id,slug,name,created_by_agent_id,visibility) values(pg_temp.a(200),'rls589','RLS',pg_temp.a(1),'private');
15
+ insert into agent_board.board_memberships(id,board_id,agent_id,membership_state,role,created_by_agent_id)
16
+ values(pg_temp.a(201),pg_temp.a(200),pg_temp.a(1),'joined','owner',pg_temp.a(1)),
17
+ (pg_temp.a(202),pg_temp.a(200),pg_temp.a(3),'joined','member',pg_temp.a(3));
18
+ insert into agent_board.threads(id,created_by_agent_id,subject)
19
+ select pg_temp.a(n),pg_temp.a(1),'RLS '||n from generate_series(100,106)n;
20
+ insert into agent_board.messages(id,thread_id,author_agent_id,message_sequence,body)
21
+ values(pg_temp.a(300),pg_temp.a(100),pg_temp.a(2),1,'Board-visible message'),
22
+ (pg_temp.a(301),pg_temp.a(103),pg_temp.a(1),1,'Unrelated message');
23
+ insert into agent_board.tasks(id,thread_id,created_by_agent_id,assigned_agent_id,title,parent_task_id,root_task_id)
24
+ values(pg_temp.a(400),pg_temp.a(101),pg_temp.a(1),pg_temp.a(5),'Assigned task',null,null),
25
+ (pg_temp.a(401),pg_temp.a(102),pg_temp.a(6),pg_temp.a(6),'Parent',null,null),
26
+ (pg_temp.a(402),pg_temp.a(102),pg_temp.a(7),pg_temp.a(7),'Child',pg_temp.a(401),pg_temp.a(401)),
27
+ (pg_temp.a(403),pg_temp.a(103),pg_temp.a(1),pg_temp.a(1),'Unrelated parent',null,null),
28
+ (pg_temp.a(404),pg_temp.a(103),pg_temp.a(1),pg_temp.a(1),'Unrelated child',pg_temp.a(403),pg_temp.a(403)),
29
+ (pg_temp.a(405),pg_temp.a(105),pg_temp.a(1),pg_temp.a(1),'Board-delivered task',null,null);
30
+ insert into agent_board.delegations(id,parent_task_id,child_task_id,delegated_by_agent_id,delegated_to_agent_id)
31
+ values(pg_temp.a(500),pg_temp.a(401),pg_temp.a(402),pg_temp.a(8),pg_temp.a(9)),
32
+ (pg_temp.a(501),pg_temp.a(403),pg_temp.a(404),pg_temp.a(1),pg_temp.a(1));
33
+ insert into agent_board.delivery_recipients(delivery_mode,recipient_agent_id,source_kind,source_message_id,source_task_id,board_id,board_membership_id)
34
+ values('board',pg_temp.a(1),'message',pg_temp.a(300),null,pg_temp.a(200),pg_temp.a(201)),
35
+ ('direct',pg_temp.a(9),'message',pg_temp.a(300),null,null,null),
36
+ ('tasking',pg_temp.a(1),'task',null,pg_temp.a(405),pg_temp.a(200),pg_temp.a(201));
37
+ insert into agent_board.board_authority_ceilings(board_id) values(pg_temp.a(200));
38
+ insert into agent_board.thread_memberships(id,thread_id,agent_id,granted_by_agent_id)
39
+ values(pg_temp.a(600),pg_temp.a(100),pg_temp.a(2),pg_temp.a(1));
40
+ insert into agent_board.read_receipts(thread_id,agent_id) values(pg_temp.a(104),pg_temp.a(3));
41
+ create temp table captured(label text,id uuid);
42
+ grant select,insert on captured to authenticated;
43
+
44
+ -- Compare all policies, deriving FK/key column pairs from the constraint catalog.
45
+ create temp table policy_sites as
46
+ select p.oid,p.polname,c.relname,p.polroles,
47
+ agent_board_test.strip_sql(coalesce(pg_get_expr(p.polqual,p.polrelid),'')||' '||coalesce(pg_get_expr(p.polwithcheck,p.polrelid),'')) as expr
48
+ from pg_policy p join pg_class c on c.oid=p.polrelid join pg_namespace n on n.oid=c.relnamespace where n.nspname='agent_board';
49
+ with pairs as (
50
+ select distinct a.attname::text as fk,b.attname::text as key
51
+ from pg_constraint c join pg_class r on r.oid=c.conrelid join pg_namespace n on n.oid=r.relnamespace
52
+ cross join lateral unnest(c.conkey,c.confkey) k(src,dst)
53
+ join pg_attribute a on a.attrelid=c.conrelid and a.attnum=k.src
54
+ join pg_attribute b on b.attrelid=c.confrelid and b.attnum=k.dst
55
+ where c.contype='f' and n.nspname='agent_board'
56
+ ), refs as (
57
+ select s.relname||'.'||s.polname||': '||m[1]||'.'||m[2]||' = '||m[1]||'.'||m[3] as site,trim(both '"' from m[2]) as lhs,trim(both '"' from m[3]) as rhs
58
+ from policy_sites s cross join lateral regexp_matches(s.expr,agent_board_test.re('('||agent_board_test.ident_any_frag()||')\s*\.\s*('||agent_board_test.ident_any_frag()||')\s*=\s*\1\s*\.\s*('||agent_board_test.ident_any_frag()||')'),'g')m
59
+ )
60
+ select is((select coalesce(string_agg(distinct site,', ' order by site),'') from refs where lhs=rhs or exists(select 1 from pairs where (lhs=fk and rhs=key) or (rhs=fk and lhs=key))),'','RLS-1 no policy self-comparison or same-alias foreign-key/key comparison; offenders named');
61
+ create temp table policy_functions as
62
+ select distinct s.relname,s.polname,f.oid,f.oid::regprocedure::text as signature,f.prosecdef
63
+ from policy_sites s join pg_depend d on d.classid='pg_policy'::regclass and d.objid=s.oid and d.refclassid='pg_proc'::regclass
64
+ join pg_proc f on f.oid=d.refobjid join pg_namespace n on n.oid=f.pronamespace
65
+ where n.nspname='agent_board' and exists(select 1 from unnest(s.polroles) r(role_id)
66
+ where case when role_id=0 then true else pg_has_role('authenticated',role_id,'USAGE') end);
67
+ select is((select coalesce(string_agg(relname||'.'||polname||': '||signature,', ' order by relname,polname,signature),'') from policy_functions where not has_function_privilege('authenticated',oid,'EXECUTE')),'','RLS-2 every authenticated policy function is executable; offenders named');
68
+ select cmp_ok((select count(*) from policy_sites),'>',0::bigint,'RLS-3 catalog policy class is nonempty');
69
+ select is((select coalesce(string_agg(distinct signature,', ' order by signature),'') from policy_functions where prosecdef and has_function_privilege('anon',oid,'EXECUTE')),'','RLS-4 authenticated policy definers are not anonymous-callable');
70
+
71
+ select pg_temp.claim(2);
72
+ set local role authenticated;
73
+ select lives_ok($$insert into captured select 'message-author',id from agent_board.threads where id in (pg_temp.a(100),pg_temp.a(103))$$,'RLS-5 message-author SELECT executes');
74
+ reset role;
75
+ select is((select coalesce(array_agg(id order by id),array[]::uuid[]) from captured where label='message-author'),array[pg_temp.a(100)]::uuid[],'RLS-6 message-author visible rows are scoped');
76
+
77
+ select pg_temp.claim(3);
78
+ set local role authenticated;
79
+ select lives_ok($$insert into captured select 'board-member',id from agent_board.threads where id in (pg_temp.a(100),pg_temp.a(103))$$,'RLS-7 board-member SELECT executes');
80
+ reset role;
81
+ select is((select coalesce(array_agg(id order by id),array[]::uuid[]) from captured where label='board-member'),array[pg_temp.a(100)]::uuid[],'RLS-8 board-member visible rows are scoped');
82
+
83
+ select pg_temp.claim(5);
84
+ set local role authenticated;
85
+ select lives_ok($$insert into captured select 'task-assignee',id from agent_board.threads where id in (pg_temp.a(101),pg_temp.a(103))$$,'RLS-9 task-assignee SELECT executes');
86
+ reset role;
87
+ select is((select coalesce(array_agg(id order by id),array[]::uuid[]) from captured where label='task-assignee'),array[pg_temp.a(101)]::uuid[],'RLS-10 task-assignee visible rows are scoped');
88
+
89
+ select pg_temp.claim(6);
90
+ set local role authenticated;
91
+ select lives_ok($$insert into captured select 'parent-task-owner',id from agent_board.delegations where id in (pg_temp.a(500),pg_temp.a(501))$$,'RLS-11 parent-task-owner SELECT executes');
92
+ reset role;
93
+ select is((select coalesce(array_agg(id order by id),array[]::uuid[]) from captured where label='parent-task-owner'),array[pg_temp.a(500)]::uuid[],'RLS-12 parent-task-owner visible rows are scoped');
94
+
95
+ select pg_temp.claim(7);
96
+ set local role authenticated;
97
+ select lives_ok($$insert into captured select 'child-task-owner',id from agent_board.delegations where id in (pg_temp.a(500),pg_temp.a(501))$$,'RLS-13 child-task-owner SELECT executes');
98
+ reset role;
99
+ select is((select coalesce(array_agg(id order by id),array[]::uuid[]) from captured where label='child-task-owner'),array[pg_temp.a(500)]::uuid[],'RLS-14 child-task-owner visible rows are scoped');
100
+
101
+ select pg_temp.claim(8);
102
+ set local role authenticated;
103
+ select lives_ok($$insert into captured select 'delegator',id from agent_board.delegations where id in (pg_temp.a(500),pg_temp.a(501))$$,'RLS-15 delegator SELECT executes');
104
+ reset role;
105
+ select is((select coalesce(array_agg(id order by id),array[]::uuid[]) from captured where label='delegator'),array[pg_temp.a(500)]::uuid[],'RLS-16 delegator visible rows are scoped');
106
+
107
+ select pg_temp.claim(9);
108
+ set local role authenticated;
109
+ select lives_ok($$insert into captured select 'delegate-recipient',id from agent_board.delegations where id in (pg_temp.a(500),pg_temp.a(501))$$,'RLS-17 delegate-recipient SELECT executes');
110
+ reset role;
111
+ select is((select coalesce(array_agg(id order by id),array[]::uuid[]) from captured where label='delegate-recipient'),array[pg_temp.a(500)]::uuid[],'RLS-18 delegate-recipient visible rows are scoped');
112
+
113
+ select pg_temp.claim(1);
114
+ set local role authenticated;
115
+ select lives_ok($$insert into captured select 'thread-creator',id from agent_board.threads where id=pg_temp.a(106)$$,'RLS-19 thread-creator SELECT executes');
116
+ reset role;
117
+ select is((select coalesce(array_agg(id order by id),array[]::uuid[]) from captured where label='thread-creator'),array[pg_temp.a(106)]::uuid[],'RLS-20 thread-creator visible rows are scoped');
118
+
119
+ select pg_temp.claim(4);
120
+ set local role authenticated;
121
+ select lives_ok($$insert into captured select 'outsider-threads',id from agent_board.threads where id between pg_temp.a(100) and pg_temp.a(105)$$,'RLS-21 outsider-threads SELECT executes');
122
+ reset role;
123
+ select is((select coalesce(array_agg(id order by id),array[]::uuid[]) from captured where label='outsider-threads'),array[]::uuid[],'RLS-22 outsider-threads visible rows are scoped');
124
+
125
+ select pg_temp.claim(4);
126
+ set local role authenticated;
127
+ select lives_ok($$insert into captured select 'outsider-delegations',id from agent_board.delegations where id in (pg_temp.a(500),pg_temp.a(501))$$,'RLS-23 outsider-delegations SELECT executes');
128
+ reset role;
129
+ select is((select coalesce(array_agg(id order by id),array[]::uuid[]) from captured where label='outsider-delegations'),array[]::uuid[],'RLS-24 outsider-delegations visible rows are scoped');
130
+
131
+ select pg_temp.claim(3);
132
+ set local role authenticated;
133
+ select lives_ok($$insert into captured select 'member-ceiling',board_id from agent_board.board_authority_ceilings where board_id=pg_temp.a(200)$$,'RLS-25 member-ceiling SELECT executes');
134
+ reset role;
135
+ select is((select coalesce(array_agg(id order by id),array[]::uuid[]) from captured where label='member-ceiling'),array[pg_temp.a(200)]::uuid[],'RLS-26 member-ceiling visible rows are scoped');
136
+
137
+ select pg_temp.claim(4);
138
+ set local role authenticated;
139
+ select lives_ok($$insert into captured select 'outsider-ceiling',board_id from agent_board.board_authority_ceilings where board_id=pg_temp.a(200)$$,'RLS-27 outsider-ceiling SELECT executes');
140
+ reset role;
141
+ select is((select coalesce(array_agg(id order by id),array[]::uuid[]) from captured where label='outsider-ceiling'),array[]::uuid[],'RLS-28 outsider-ceiling visible rows are scoped');
142
+
143
+ select pg_temp.claim(3);
144
+ set local role authenticated;
145
+ select lives_ok($$insert into captured select 'member-thread-acl',id from agent_board.thread_memberships where id=pg_temp.a(600)$$,'RLS-29 member-thread-acl SELECT executes');
146
+ reset role;
147
+ select is((select coalesce(array_agg(id order by id),array[]::uuid[]) from captured where label='member-thread-acl'),array[pg_temp.a(600)]::uuid[],'RLS-30 member-thread-acl visible rows are scoped');
148
+
149
+ select pg_temp.claim(4);
150
+ set local role authenticated;
151
+ select lives_ok($$insert into captured select 'outsider-thread-acl',id from agent_board.thread_memberships where id=pg_temp.a(600)$$,'RLS-31 outsider-thread-acl SELECT executes');
152
+ reset role;
153
+ select is((select coalesce(array_agg(id order by id),array[]::uuid[]) from captured where label='outsider-thread-acl'),array[]::uuid[],'RLS-32 outsider-thread-acl visible rows are scoped');
154
+
155
+ select pg_temp.claim(3);
156
+ set local role authenticated;
157
+ select lives_ok($$insert into captured select 'receipt-reader',id from agent_board.threads where id=pg_temp.a(104)$$,'RLS-33 receipt-reader SELECT executes');
158
+ reset role;
159
+ select is((select coalesce(array_agg(id order by id),array[]::uuid[]) from captured where label='receipt-reader'),array[pg_temp.a(104)]::uuid[],'RLS-34 receipt-reader visible rows are scoped');
160
+
161
+ select pg_temp.claim(3);
162
+ set local role authenticated;
163
+ select lives_ok($$insert into captured select 'member-unrelated-delegation',id from agent_board.delegations where id in (pg_temp.a(500),pg_temp.a(501))$$,'RLS-35 member-unrelated-delegation SELECT executes');
164
+ reset role;
165
+ select is((select coalesce(array_agg(id order by id),array[]::uuid[]) from captured where label='member-unrelated-delegation'),array[]::uuid[],'RLS-36 member-unrelated-delegation visible rows are scoped');
166
+
167
+ select pg_temp.claim(3);
168
+ set local role authenticated;
169
+ select lives_ok($$insert into captured select 'board-task-delivery',id from agent_board.threads where id in (pg_temp.a(105),pg_temp.a(103))$$,'RLS-37 board-task-delivery SELECT executes');
170
+ reset role;
171
+ select is((select coalesce(array_agg(id order by id),array[]::uuid[]) from captured where label='board-task-delivery'),array[pg_temp.a(105)]::uuid[],'RLS-38 board-task-delivery visible rows are scoped');
172
+
173
+ select pg_temp.claim(9);
174
+ set local role authenticated;
175
+ select lives_ok($$insert into captured select 'direct-message-recipient',id from agent_board.threads where id in (pg_temp.a(100),pg_temp.a(103))$$,'RLS-39 direct-message-recipient SELECT executes');
176
+ reset role;
177
+ select is((select coalesce(array_agg(id order by id),array[]::uuid[]) from captured where label='direct-message-recipient'),array[pg_temp.a(100)]::uuid[],'RLS-40 direct-message-recipient visible rows are scoped');
178
+
179
+
180
+ -- Catalog provenance is part of the read contract: grants must not erase the ruling.
181
+ select ok(coalesce(
182
+ obj_description('agent_board._current_agent_can_access_thread(uuid)'::regprocedure, 'pg_proc') like '%message-board#291%'
183
+ and obj_description('agent_board._current_agent_can_access_thread(uuid)'::regprocedure, 'pg_proc') like '%REVERSING A RULING%'
184
+ and obj_description('agent_board._current_agent_can_access_thread(uuid)'::regprocedure, 'pg_proc') like '%restricted%is UNREACHABLE%'
185
+ and obj_description('agent_board._current_agent_can_access_thread(uuid)'::regprocedure, 'pg_proc') like '%Callable by authenticated for its RLS policies and by service_role.%',
186
+ false), 'RLS-41 thread access comment preserves the ratified claims ruling and dormant ACL');
187
+
188
+ select * from finish();
189
+ rollback;
@@ -0,0 +1,96 @@
1
+ create extension if not exists pgtap with schema extensions;
2
+ set search_path = extensions, public, agent_board;
3
+ begin;
4
+ select plan(33);
5
+ create function pg_temp.a(n integer) returns uuid language sql immutable as $$
6
+ select ('68000000-0000-4000-8000-' || lpad(n::text,12,'0'))::uuid
7
+ $$;
8
+ create function pg_temp.claim(n integer) returns void language plpgsql as $$
9
+ begin
10
+ perform set_config('request.jwt.claims', json_build_object('role','authenticated','sub',pg_temp.a(n))::text,true);
11
+ end $$;
12
+ insert into auth.users(id, instance_id, aud, role, email)
13
+ select pg_temp.a(n),'00000000-0000-0000-0000-000000000000','authenticated','authenticated','removed680-'||n||'@test.invalid'
14
+ from generate_series(1,9) n;
15
+ insert into agent_board.agents(id,agent_type,handle,display_name,status,auth_user_id)
16
+ select pg_temp.a(n),'agent','removed680-'||n,'Removed '||n,'active',pg_temp.a(n)
17
+ from generate_series(1,9) n;
18
+ insert into agent_board.boards(id,slug,name,created_by_agent_id,visibility)
19
+ values (pg_temp.a(100),'removed680-open','Open',pg_temp.a(1),'open'),
20
+ (pg_temp.a(101),'removed680-link','Link',pg_temp.a(1),'invite_only'),
21
+ (pg_temp.a(102),'removed680-private','Private',pg_temp.a(1),'private');
22
+ insert into agent_board.board_memberships(board_id,agent_id,membership_state,role,created_by_agent_id)
23
+ select pg_temp.a(n),pg_temp.a(1),'joined','owner',pg_temp.a(1) from generate_series(100,102) n;
24
+ insert into agent_board.board_memberships(board_id,agent_id,membership_state,role,created_by_agent_id,left_at,metadata)
25
+ values (pg_temp.a(100),pg_temp.a(2),'removed','member',pg_temp.a(1),now(),'{"eviction":true}'),
26
+ (pg_temp.a(101),pg_temp.a(2),'removed','member',pg_temp.a(1),now(),'{"eviction":true}'),
27
+ (pg_temp.a(100),pg_temp.a(3),'removed','member',pg_temp.a(1),now(),'{"eviction":true}'),
28
+ (pg_temp.a(100),pg_temp.a(3),'left','member',pg_temp.a(1),now(),'{"left":true}'),
29
+ (pg_temp.a(100),pg_temp.a(5),'left','member',pg_temp.a(5),now(),'{}'),
30
+ (pg_temp.a(100),pg_temp.a(6),'requested','member',pg_temp.a(6),null,'{}'),
31
+ (pg_temp.a(100),pg_temp.a(7),'removed','member',pg_temp.a(1),now(),'{"eviction":true}'),
32
+ (pg_temp.a(100),pg_temp.a(7),'joined','member',pg_temp.a(7),null,'{"active":true}'),
33
+ (pg_temp.a(100),pg_temp.a(8),'subscribed','member',pg_temp.a(8),null,'{"active":true}');
34
+ update agent_board.board_memberships set created_at=now()-interval '1 day'
35
+ where board_id=pg_temp.a(100) and agent_id=pg_temp.a(3) and membership_state='removed';
36
+ select pg_temp.claim(1);
37
+ create temp table live_link as select * from agent_board.create_invite_link(pg_temp.a(101),'member','joined',null,null,pg_temp.a(1));
38
+
39
+ create temp table request_sites as
40
+ select p.oid,p.proname::text as name,agent_board_test.stripped_body(p.oid) as body,
41
+ agent_board_test.stripped_body(p.oid,true) as literals
42
+ from pg_proc p join pg_namespace n on n.oid=p.pronamespace
43
+ where n.nspname='agent_board' and has_function_privilege('authenticated',p.oid,'EXECUTE')
44
+ and agent_board_test.stripped_body(p.oid,true) ~ agent_board_test.re(
45
+ agent_board_test.call_frag('_upsert_board_membership_state') || '[^,;]*,[^,;]*,\s*''requested''\s*,');
46
+ select is((select string_agg(name,',' order by name) from request_sites),'request_join_board','REM-1 literal-request sink callers enumerated from catalog');
47
+ select is((select coalesce(string_agg(name,','),'') from request_sites where body !~ agent_board_test.row_lock_re('board_memberships')),'','REM-2 every request writer locks membership rows; offenders named');
48
+ select is((select coalesce(string_agg(name,','),'') from request_sites
49
+ where regexp_instr(body,agent_board_test.row_lock_re('board_memberships'))=0
50
+ or regexp_instr(body,agent_board_test.row_lock_re('board_memberships')) >= regexp_instr(body,agent_board_test.call_re('_upsert_board_membership_state'))
51
+ or regexp_instr(body,agent_board_test.row_lock_re('board_memberships')) >= regexp_instr(body,agent_board_test.re(agent_board_test.ci_re('if') || '\s+' || agent_board_test.ci_re('exists') || '\s*\('))
52
+ or literals !~ 'board join request refused for removed standing'),
53
+ '','REM-3 every request writer checks removed standing before its sink');
54
+ select is((select coalesce(string_agg(name,','),'') from request_sites where has_function_privilege('anon',oid,'EXECUTE') or not has_function_privilege('service_role',oid,'EXECUTE') or obj_description(oid,'pg_proc') is null),'','REM-4 request writer ACL and comment');
55
+
56
+ select pg_temp.claim(2);
57
+ select throws_ok($$select agent_board.request_join_board(pg_temp.a(100),pg_temp.a(2),'{"overwrite":true}')$$,'42501',null,'REM-5 removed open-board request refused');
58
+ select is((select membership_state from agent_board.board_memberships where board_id=pg_temp.a(100) and agent_id=pg_temp.a(2)),'removed','REM-6 eviction persists after request');
59
+ select is((select count(*)::integer from agent_board.board_membership_events where board_id=pg_temp.a(100) and agent_id=pg_temp.a(2)),0,'REM-7 refused request emits no membership event');
60
+ select throws_ok($$select agent_board.join_board(pg_temp.a(100),pg_temp.a(2))$$,'P0002',null,'REM-8 request cannot unlock open self-join');
61
+ select is((select membership_state from agent_board.board_memberships where board_id=pg_temp.a(100) and agent_id=pg_temp.a(2)),'removed','REM-9 self-join leaves eviction intact');
62
+ select throws_ok($$select agent_board.request_join_board(pg_temp.a(101),pg_temp.a(2))$$,'42501',null,'REM-10 removed invite-only request refused');
63
+ select is((select membership_state from agent_board.board_memberships where board_id=pg_temp.a(101) and agent_id=pg_temp.a(2)),'removed','REM-11 invite-only eviction persists');
64
+ select throws_ok($$select agent_board.redeem_invite_link((select code from live_link))$$,'42501',null,'REM-12 request cannot unlock live-link redemption');
65
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(101) and agent_id=pg_temp.a(2)),'removed','REM-13 live link leaves eviction intact');
66
+ select is((select uses_count from agent_board.board_invite_tokens where id=(select invite_id from live_link)),0,'REM-14 refused redemption consumes no use');
67
+
68
+ select pg_temp.claim(3);
69
+ select throws_ok($$select agent_board.request_join_board(pg_temp.a(100),pg_temp.a(3),'{"overwrite":true}')$$,'42501',null,'REM-15 removed plus left standing refused');
70
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(100) and agent_id=pg_temp.a(3)),'left,removed','REM-16 whole row set preserved');
71
+ select is((select metadata from agent_board.board_memberships where board_id=pg_temp.a(100) and agent_id=pg_temp.a(3) and membership_state='removed'),'{"eviction":true}'::jsonb,'REM-17 denied request preserves eviction metadata');
72
+ select pg_temp.claim(4);
73
+ select lives_ok($$select agent_board.request_join_board(pg_temp.a(100),pg_temp.a(4))$$,'REM-18 first request succeeds');
74
+ select is((select membership_state from agent_board.board_memberships where board_id=pg_temp.a(100) and agent_id=pg_temp.a(4)),'requested','REM-19 first request stored');
75
+ select pg_temp.claim(5);
76
+ select lives_ok($$select agent_board.request_join_board(pg_temp.a(100),pg_temp.a(5))$$,'REM-20 voluntary leaver can request');
77
+ select is((select membership_state from agent_board.board_memberships where board_id=pg_temp.a(100) and agent_id=pg_temp.a(5)),'requested','REM-21 voluntary request stored');
78
+ select pg_temp.claim(6);
79
+ select lives_ok($$select agent_board.request_join_board(pg_temp.a(100),pg_temp.a(6))$$,'REM-22 repeated pending request succeeds');
80
+ select is((select count(*)::integer from agent_board.board_memberships where board_id=pg_temp.a(100) and agent_id=pg_temp.a(6) and membership_state='requested'),1,'REM-23 repeat preserves one pending row');
81
+ select pg_temp.claim(7);
82
+ select lives_ok($$select agent_board.request_join_board(pg_temp.a(100),pg_temp.a(7),'{"overwrite":true}')$$,'REM-24 active plus removed remains a no-op');
83
+ select is((select metadata from agent_board.board_memberships where board_id=pg_temp.a(100) and agent_id=pg_temp.a(7) and membership_state='joined'),'{"active":true}'::jsonb,'REM-25 active row remains unchanged');
84
+ select pg_temp.claim(8);
85
+ select lives_ok($$select agent_board.request_join_board(pg_temp.a(100),pg_temp.a(8))$$,'REM-26 subscribed request remains a no-op');
86
+ select is((select membership_state from agent_board.board_memberships where board_id=pg_temp.a(100) and agent_id=pg_temp.a(8)),'subscribed','REM-27 subscription preserved');
87
+ select set_config('request.jwt.claims','{"role":"service_role"}',true);
88
+ select throws_ok($$select agent_board.request_join_board(pg_temp.a(100),pg_temp.a(2))$$,'42501',null,'REM-28 service request cannot erase removal');
89
+ select is((select membership_state from agent_board.board_memberships where board_id=pg_temp.a(100) and agent_id=pg_temp.a(2)),'removed','REM-29 service refusal preserves eviction');
90
+ select pg_temp.claim(9);
91
+ select throws_ok($$select agent_board.request_join_board(pg_temp.a(102),pg_temp.a(9))$$,'P0002',null,'REM-30 private board request still hidden');
92
+ select is((select count(*)::integer from agent_board.board_memberships where board_id=pg_temp.a(102) and agent_id=pg_temp.a(9)),0,'REM-31 hidden request writes nothing');
93
+ select throws_ok($$select agent_board.request_join_board(pg_temp.a(100),pg_temp.a(4))$$,'42501',null,'REM-32 request remains self-only');
94
+ select is((select membership_state from agent_board.board_memberships where board_id=pg_temp.a(100) and agent_id=pg_temp.a(4)),'requested','REM-33 actor mismatch preserves target');
95
+ select * from finish();
96
+ rollback;
@@ -0,0 +1,190 @@
1
+ create extension if not exists pgtap with schema extensions;
2
+ set search_path = extensions, public, agent_board;
3
+ begin;
4
+ select plan(76);
5
+ create function pg_temp.a(n integer) returns uuid language sql immutable as $$
6
+ select ('66800000-0000-4000-8000-'||lpad(n::text,12,'0'))::uuid
7
+ $$;
8
+ create function pg_temp.claim(n integer) returns void language plpgsql as $$
9
+ begin perform set_config('request.jwt.claims',json_build_object('role','authenticated','sub',pg_temp.a(n))::text,true); end $$;
10
+ insert into auth.users(id,instance_id,aud,role,email)
11
+ select pg_temp.a(n),'00000000-0000-0000-0000-000000000000','authenticated','authenticated','resolve668-'||n||'@test.invalid' from generate_series(1,39)n;
12
+ insert into agent_board.agents(id,agent_type,handle,display_name,status,auth_user_id)
13
+ select pg_temp.a(n),'agent','resolve668-'||n,'Resolver '||n,'active',pg_temp.a(n) from generate_series(1,39)n;
14
+ insert into agent_board.boards(id,slug,name,created_by_agent_id,visibility,role_gate_enforced)
15
+ values(pg_temp.a(100),'resolve668-off','Gate off',pg_temp.a(1),'open',false),
16
+ (pg_temp.a(101),'resolve668-on','Gate on',pg_temp.a(1),'open',true),
17
+ (pg_temp.a(102),'resolve668-other','Other board',pg_temp.a(6),'private',false);
18
+ insert into agent_board.board_memberships(board_id,agent_id,membership_state,role,created_by_agent_id,left_at)
19
+ select pg_temp.a(b),pg_temp.a(a),state,role,pg_temp.a(1),case when state in ('left','removed') then now() else null end
20
+ from generate_series(100,101)b cross join (values
21
+ (1,'joined','observer'),(2,'joined','owner'),(3,'subscribed','admin'),
22
+ (4,'joined','member'),(5,'joined','observer'),(7,'left','owner'),(8,'removed','admin')
23
+ )r(a,state,role);
24
+ insert into agent_board.board_memberships(board_id,agent_id,membership_state,role,created_by_agent_id)
25
+ values(pg_temp.a(102),pg_temp.a(6),'joined','owner',pg_temp.a(6));
26
+ -- Real subject-authored requests establish consent for approval-side probes.
27
+ do $$declare b integer;a integer; begin
28
+ for b in 100..101 loop for a in 21..39 loop
29
+ perform pg_temp.claim(a);
30
+ perform agent_board.request_join_board(pg_temp.a(b),pg_temp.a(a));
31
+ end loop; end loop;
32
+ end $$;
33
+ create temp table resolution_sites as
34
+ select p.oid,p.proname::text as name,agent_board_test.stripped_body(p.oid) as body,agent_board_test.stripped_body(p.oid,true) as literals
35
+ from pg_proc p join pg_namespace n on n.oid=p.pronamespace
36
+ where n.nspname='agent_board' and has_function_privilege('authenticated',p.oid,'EXECUTE')
37
+ and 'p_membership_state'=any(p.proargnames)
38
+ and agent_board_test.stripped_body(p.oid) ~ agent_board_test.call_re('_upsert_board_membership_state')
39
+ and agent_board_test.stripped_body(p.oid,true) ~ agent_board_test.re(agent_board_test.col_ref_frag('membership_state')||'\s+'||agent_board_test.ci_re('in')||'\s*\(\s*(?:''requested''\s*,\s*''invited''|''invited''\s*,\s*''requested'')\s*\)');
40
+ select is((select string_agg(name,',' order by name) from resolution_sites),'resolve_board_request','AUTH-1 catalog-derived pending resolver population');
41
+ select is((select coalesce(string_agg(name,','),'') from resolution_sites where body ~ agent_board_test.ident_re('role_gate_enforced')),'','AUTH-2 pending resolution never depends on the role gate; offenders named');
42
+ select is((select coalesce(string_agg(name,','),'') from resolution_sites where literals !~ agent_board_test.re(agent_board_test.col_ref_frag('role')||'\s+'||agent_board_test.ci_re('in')||'\s*\(\s*''owner''\s*,\s*''admin''\s*\)')),'','AUTH-3 every resolver requires owner/admin standing');
43
+ select is((select coalesce(string_agg(name,','),'') from resolution_sites where has_function_privilege('anon',oid,'EXECUTE') or not has_function_privilege('service_role',oid,'EXECUTE') or obj_description(oid,'pg_proc') is null),'','AUTH-4 resolver ACL and comment');
44
+
45
+ select pg_temp.claim(2);
46
+ select lives_ok($$select agent_board.resolve_board_request(pg_temp.a(100),pg_temp.a(21),'joined',pg_temp.a(2))$$,'AUTH-5 gate off: active owner resolves joined');
47
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(100) and agent_id=pg_temp.a(21)),'joined','AUTH-6 gate off: allowed result stored');
48
+
49
+ select pg_temp.claim(2);
50
+ select lives_ok($$select agent_board.resolve_board_request(pg_temp.a(100),pg_temp.a(22),'removed',pg_temp.a(2))$$,'AUTH-7 gate off: active owner resolves removed');
51
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(100) and agent_id=pg_temp.a(22)),'left','AUTH-8 gate off: allowed result stored');
52
+
53
+ select pg_temp.claim(3);
54
+ select lives_ok($$select agent_board.resolve_board_request(pg_temp.a(100),pg_temp.a(23),'subscribed',pg_temp.a(3))$$,'AUTH-9 gate off: active admin resolves subscribed');
55
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(100) and agent_id=pg_temp.a(23)),'subscribed','AUTH-10 gate off: allowed result stored');
56
+
57
+ select pg_temp.claim(3);
58
+ select lives_ok($$select agent_board.resolve_board_request(pg_temp.a(100),pg_temp.a(24),'removed',pg_temp.a(3))$$,'AUTH-11 gate off: active admin resolves removed');
59
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(100) and agent_id=pg_temp.a(24)),'left','AUTH-12 gate off: allowed result stored');
60
+
61
+ select pg_temp.claim(4);
62
+ select throws_ok($$select agent_board.resolve_board_request(pg_temp.a(100),pg_temp.a(25),'joined',pg_temp.a(4))$$,'42501',null,'AUTH-13 gate off: member cannot resolve joined');
63
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(100) and agent_id=pg_temp.a(25)),'requested','AUTH-14 gate off: denied request unchanged');
64
+
65
+ select pg_temp.claim(4);
66
+ select throws_ok($$select agent_board.resolve_board_request(pg_temp.a(100),pg_temp.a(26),'removed',pg_temp.a(4))$$,'42501',null,'AUTH-15 gate off: member cannot resolve removed');
67
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(100) and agent_id=pg_temp.a(26)),'requested','AUTH-16 gate off: denied request unchanged');
68
+
69
+ select pg_temp.claim(5);
70
+ select throws_ok($$select agent_board.resolve_board_request(pg_temp.a(100),pg_temp.a(27),'joined',pg_temp.a(5))$$,'42501',null,'AUTH-17 gate off: observer cannot resolve joined');
71
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(100) and agent_id=pg_temp.a(27)),'requested','AUTH-18 gate off: denied request unchanged');
72
+
73
+ select pg_temp.claim(5);
74
+ select throws_ok($$select agent_board.resolve_board_request(pg_temp.a(100),pg_temp.a(28),'removed',pg_temp.a(5))$$,'42501',null,'AUTH-19 gate off: observer cannot resolve removed');
75
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(100) and agent_id=pg_temp.a(28)),'requested','AUTH-20 gate off: denied request unchanged');
76
+
77
+ select pg_temp.claim(1);
78
+ select throws_ok($$select agent_board.resolve_board_request(pg_temp.a(100),pg_temp.a(29),'joined',pg_temp.a(1))$$,'42501',null,'AUTH-21 gate off: demoted creator cannot resolve joined');
79
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(100) and agent_id=pg_temp.a(29)),'requested','AUTH-22 gate off: denied request unchanged');
80
+
81
+ select pg_temp.claim(1);
82
+ select throws_ok($$select agent_board.resolve_board_request(pg_temp.a(100),pg_temp.a(30),'removed',pg_temp.a(1))$$,'42501',null,'AUTH-23 gate off: demoted creator cannot resolve removed');
83
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(100) and agent_id=pg_temp.a(30)),'requested','AUTH-24 gate off: denied request unchanged');
84
+
85
+ select pg_temp.claim(6);
86
+ select throws_ok($$select agent_board.resolve_board_request(pg_temp.a(100),pg_temp.a(31),'joined',pg_temp.a(6))$$,'42501',null,'AUTH-25 gate off: outsider cannot resolve joined');
87
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(100) and agent_id=pg_temp.a(31)),'requested','AUTH-26 gate off: denied request unchanged');
88
+
89
+ select pg_temp.claim(6);
90
+ select throws_ok($$select agent_board.resolve_board_request(pg_temp.a(100),pg_temp.a(32),'removed',pg_temp.a(6))$$,'42501',null,'AUTH-27 gate off: outsider cannot resolve removed');
91
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(100) and agent_id=pg_temp.a(32)),'requested','AUTH-28 gate off: denied request unchanged');
92
+
93
+ select pg_temp.claim(7);
94
+ select throws_ok($$select agent_board.resolve_board_request(pg_temp.a(100),pg_temp.a(33),'joined',pg_temp.a(7))$$,'42501',null,'AUTH-29 gate off: departed owner cannot resolve joined');
95
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(100) and agent_id=pg_temp.a(33)),'requested','AUTH-30 gate off: denied request unchanged');
96
+
97
+ select pg_temp.claim(7);
98
+ select throws_ok($$select agent_board.resolve_board_request(pg_temp.a(100),pg_temp.a(34),'removed',pg_temp.a(7))$$,'42501',null,'AUTH-31 gate off: departed owner cannot resolve removed');
99
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(100) and agent_id=pg_temp.a(34)),'requested','AUTH-32 gate off: denied request unchanged');
100
+
101
+ select pg_temp.claim(8);
102
+ select throws_ok($$select agent_board.resolve_board_request(pg_temp.a(100),pg_temp.a(35),'joined',pg_temp.a(8))$$,'42501',null,'AUTH-33 gate off: removed admin cannot resolve joined');
103
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(100) and agent_id=pg_temp.a(35)),'requested','AUTH-34 gate off: denied request unchanged');
104
+
105
+ select pg_temp.claim(8);
106
+ select throws_ok($$select agent_board.resolve_board_request(pg_temp.a(100),pg_temp.a(36),'removed',pg_temp.a(8))$$,'42501',null,'AUTH-35 gate off: removed admin cannot resolve removed');
107
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(100) and agent_id=pg_temp.a(36)),'requested','AUTH-36 gate off: denied request unchanged');
108
+
109
+ select pg_temp.claim(2);
110
+ select lives_ok($$select agent_board.resolve_board_request(pg_temp.a(101),pg_temp.a(21),'joined',pg_temp.a(2))$$,'AUTH-37 gate on: active owner resolves joined');
111
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(101) and agent_id=pg_temp.a(21)),'joined','AUTH-38 gate on: allowed result stored');
112
+
113
+ select pg_temp.claim(2);
114
+ select lives_ok($$select agent_board.resolve_board_request(pg_temp.a(101),pg_temp.a(22),'removed',pg_temp.a(2))$$,'AUTH-39 gate on: active owner resolves removed');
115
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(101) and agent_id=pg_temp.a(22)),'left','AUTH-40 gate on: allowed result stored');
116
+
117
+ select pg_temp.claim(3);
118
+ select lives_ok($$select agent_board.resolve_board_request(pg_temp.a(101),pg_temp.a(23),'subscribed',pg_temp.a(3))$$,'AUTH-41 gate on: active admin resolves subscribed');
119
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(101) and agent_id=pg_temp.a(23)),'subscribed','AUTH-42 gate on: allowed result stored');
120
+
121
+ select pg_temp.claim(3);
122
+ select lives_ok($$select agent_board.resolve_board_request(pg_temp.a(101),pg_temp.a(24),'removed',pg_temp.a(3))$$,'AUTH-43 gate on: active admin resolves removed');
123
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(101) and agent_id=pg_temp.a(24)),'left','AUTH-44 gate on: allowed result stored');
124
+
125
+ select pg_temp.claim(4);
126
+ select throws_ok($$select agent_board.resolve_board_request(pg_temp.a(101),pg_temp.a(25),'joined',pg_temp.a(4))$$,'42501',null,'AUTH-45 gate on: member cannot resolve joined');
127
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(101) and agent_id=pg_temp.a(25)),'requested','AUTH-46 gate on: denied request unchanged');
128
+
129
+ select pg_temp.claim(4);
130
+ select throws_ok($$select agent_board.resolve_board_request(pg_temp.a(101),pg_temp.a(26),'removed',pg_temp.a(4))$$,'42501',null,'AUTH-47 gate on: member cannot resolve removed');
131
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(101) and agent_id=pg_temp.a(26)),'requested','AUTH-48 gate on: denied request unchanged');
132
+
133
+ select pg_temp.claim(5);
134
+ select throws_ok($$select agent_board.resolve_board_request(pg_temp.a(101),pg_temp.a(27),'joined',pg_temp.a(5))$$,'42501',null,'AUTH-49 gate on: observer cannot resolve joined');
135
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(101) and agent_id=pg_temp.a(27)),'requested','AUTH-50 gate on: denied request unchanged');
136
+
137
+ select pg_temp.claim(5);
138
+ select throws_ok($$select agent_board.resolve_board_request(pg_temp.a(101),pg_temp.a(28),'removed',pg_temp.a(5))$$,'42501',null,'AUTH-51 gate on: observer cannot resolve removed');
139
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(101) and agent_id=pg_temp.a(28)),'requested','AUTH-52 gate on: denied request unchanged');
140
+
141
+ select pg_temp.claim(1);
142
+ select throws_ok($$select agent_board.resolve_board_request(pg_temp.a(101),pg_temp.a(29),'joined',pg_temp.a(1))$$,'42501',null,'AUTH-53 gate on: demoted creator cannot resolve joined');
143
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(101) and agent_id=pg_temp.a(29)),'requested','AUTH-54 gate on: denied request unchanged');
144
+
145
+ select pg_temp.claim(1);
146
+ select throws_ok($$select agent_board.resolve_board_request(pg_temp.a(101),pg_temp.a(30),'removed',pg_temp.a(1))$$,'42501',null,'AUTH-55 gate on: demoted creator cannot resolve removed');
147
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(101) and agent_id=pg_temp.a(30)),'requested','AUTH-56 gate on: denied request unchanged');
148
+
149
+ select pg_temp.claim(6);
150
+ select throws_ok($$select agent_board.resolve_board_request(pg_temp.a(101),pg_temp.a(31),'joined',pg_temp.a(6))$$,'42501',null,'AUTH-57 gate on: outsider cannot resolve joined');
151
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(101) and agent_id=pg_temp.a(31)),'requested','AUTH-58 gate on: denied request unchanged');
152
+
153
+ select pg_temp.claim(6);
154
+ select throws_ok($$select agent_board.resolve_board_request(pg_temp.a(101),pg_temp.a(32),'removed',pg_temp.a(6))$$,'42501',null,'AUTH-59 gate on: outsider cannot resolve removed');
155
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(101) and agent_id=pg_temp.a(32)),'requested','AUTH-60 gate on: denied request unchanged');
156
+
157
+ select pg_temp.claim(7);
158
+ select throws_ok($$select agent_board.resolve_board_request(pg_temp.a(101),pg_temp.a(33),'joined',pg_temp.a(7))$$,'42501',null,'AUTH-61 gate on: departed owner cannot resolve joined');
159
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(101) and agent_id=pg_temp.a(33)),'requested','AUTH-62 gate on: denied request unchanged');
160
+
161
+ select pg_temp.claim(7);
162
+ select throws_ok($$select agent_board.resolve_board_request(pg_temp.a(101),pg_temp.a(34),'removed',pg_temp.a(7))$$,'42501',null,'AUTH-63 gate on: departed owner cannot resolve removed');
163
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(101) and agent_id=pg_temp.a(34)),'requested','AUTH-64 gate on: denied request unchanged');
164
+
165
+ select pg_temp.claim(8);
166
+ select throws_ok($$select agent_board.resolve_board_request(pg_temp.a(101),pg_temp.a(35),'joined',pg_temp.a(8))$$,'42501',null,'AUTH-65 gate on: removed admin cannot resolve joined');
167
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(101) and agent_id=pg_temp.a(35)),'requested','AUTH-66 gate on: denied request unchanged');
168
+
169
+ select pg_temp.claim(8);
170
+ select throws_ok($$select agent_board.resolve_board_request(pg_temp.a(101),pg_temp.a(36),'removed',pg_temp.a(8))$$,'42501',null,'AUTH-67 gate on: removed admin cannot resolve removed');
171
+ select is((select string_agg(membership_state,',' order by membership_state) from agent_board.board_memberships where board_id=pg_temp.a(101) and agent_id=pg_temp.a(36)),'requested','AUTH-68 gate on: denied request unchanged');
172
+
173
+ select set_config('request.jwt.claims','{"role":"service_role"}',true);
174
+ select throws_ok($$select agent_board.resolve_board_request(pg_temp.a(100),pg_temp.a(37),'removed',pg_temp.a(2))$$,'42501',null,'AUTH-69 unresolved service resolver has no owner identity');
175
+ select is((select membership_state from agent_board.board_memberships where board_id=pg_temp.a(100) and agent_id=pg_temp.a(37)),'requested','AUTH-70 unresolved service refusal preserves request');
176
+
177
+ select set_config('request.jwt.claims','{"role":"service_role"}',true);
178
+ select throws_ok($$select agent_board.resolve_board_request(pg_temp.a(101),pg_temp.a(37),'removed',pg_temp.a(2))$$,'42501',null,'AUTH-71 unresolved service resolver has no owner identity');
179
+ select is((select membership_state from agent_board.board_memberships where board_id=pg_temp.a(101) and agent_id=pg_temp.a(37)),'requested','AUTH-72 unresolved service refusal preserves request');
180
+
181
+ select set_config('request.jwt.claims',json_build_object('role','service_role','sub',pg_temp.a(2))::text,true);
182
+ select lives_ok($$select agent_board.resolve_board_request(pg_temp.a(100),pg_temp.a(38),'joined',pg_temp.a(2))$$,'AUTH-73 resolved service owner may approve');
183
+ select is((select membership_state from agent_board.board_memberships where board_id=pg_temp.a(100) and agent_id=pg_temp.a(38)),'joined','AUTH-74 service owner approval stored');
184
+
185
+ select set_config('request.jwt.claims','{"role":"authenticated"}',true);
186
+ select throws_ok($$select agent_board.resolve_board_request(pg_temp.a(100),pg_temp.a(39),'removed',pg_temp.a(2))$$,'42501',null,'AUTH-75 unresolved authenticated resolver refused');
187
+ select is((select membership_state from agent_board.board_memberships where board_id=pg_temp.a(100) and agent_id=pg_temp.a(39)),'requested','AUTH-76 unresolved authenticated refusal preserves request');
188
+
189
+ select * from finish();
190
+ rollback;
@@ -14,7 +14,7 @@ select * from _sid_behavior_fns f where f.body ~ agent_board_test.re(
14
14
  agent_board_test.write_frag('session_identities',agent_board_test.bare_ok(f.oid)) || '|'
15
15
  || agent_board_test.write_frag('session_id_aliases',agent_board_test.bare_ok(f.oid)));
16
16
  select is((select string_agg(proname,', ' order by proname) from _sid_behavior_writers),
17
- '_revoke_agent_principal, _session_identity_agent_lineage, approve_identity_state, heartbeat_session_identity, link_session_alias, mint_delegated_child, mint_fleet_agent_session, readmit_session_identity, record_session_identity_state, renew_session_identity',
17
+ '_revoke_agent_principal, _session_identity_agent_lineage, approve_identity_state, heartbeat_session_identity, link_session_alias, mint_delegated_child, mint_fleet_agent_session, mint_fleet_agent_session_measured, readmit_session_identity, record_session_identity_state, renew_session_identity',
18
18
  'EC-SIDSCHEMA-5-protected-population: every catalog-derived identity/alias writer has a gate or a positively proved lifecycle exemption');
19
19
  create temp view _sid_behavior_unguarded as
20
20
  select w.* from _sid_behavior_writers w
@@ -66,7 +66,7 @@ select is((select string_agg(proname,', ' order by proname) from _sid_behavior_i
66
66
  select is((select coalesce(string_agg(c.root_name,', ' order by c.root_name),'')
67
67
  from agent_board_test.fn_call_closure() c
68
68
  where c.reached_oid='agent_board._mint_fleet_agent_session_legacy(uuid,text,uuid,uuid,integer)'::regprocedure
69
- and c.root_oid<>c.reached_oid and c.root_name<>'mint_fleet_agent_session'),'',
69
+ and c.root_oid<>c.reached_oid and c.root_name<>'mint_fleet_agent_session'),'mint_fleet_agent_session_measured',
70
70
  'EC-SIDSCHEMA-4-issuer-wrapper-class: only the gated fleet session mint reaches the private legacy token issuer; a new bypass wrapper names itself');
71
71
  select ok(not has_function_privilege('service_role',
72
72
  'agent_board._mint_fleet_agent_session_legacy(uuid,text,uuid,uuid,integer)','EXECUTE')
@@ -160,7 +160,7 @@ from pg_proc p where p.pronamespace = 'agent_board'::regnamespace
160
160
  and (agent_board_test.stripped_body(p.oid) ~ agent_board_test.col_write_re('delegation_grants', 'grant_expires_at')
161
161
  or agent_board_test.stripped_body(p.oid) ~ agent_board_test.col_write_re('session_identities', 'holder_secret_hash'));
162
162
  select is((select string_agg(proname, ', ' order by proname) from _sid_activation_writers),
163
- '_session_identity_agent_lineage, mint_delegated_child, mint_fleet_agent_session, readmit_session_identity, renew_delegated_grant',
163
+ '_session_identity_agent_lineage, mint_delegated_child, mint_fleet_agent_session, mint_fleet_agent_session_measured, readmit_session_identity, renew_delegated_grant',
164
164
  'EC-SIDSCHEMA-6-activation-population: every discovered expiry or holder writer has a positive activation path');
165
165
  -- An INSERT is conservatively a writer of defaulted columns too. This one
166
166
  -- writes a legacy/default-NULL identity, not a live holder or grant. Prove the