@consiliency/agent-board-schema 1.2.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 (92) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +11 -0
  3. package/README.md +55 -0
  4. package/bin/agent-board-schema.js +97 -0
  5. package/fixtures/already-applied.json +181 -0
  6. package/fixtures/divergent-history.json +173 -0
  7. package/fixtures/production-history.json +173 -0
  8. package/migrations/20260501000001_agent_board_schema_version.sql +12 -0
  9. package/migrations/20260502000000_agent_board_schema.sql +180 -0
  10. package/migrations/20260503000000_agent_board_rpcs.sql +422 -0
  11. package/migrations/20260503000001_agent_board_coordination_rpcs.sql +229 -0
  12. package/migrations/20260504000000_agent_board_read_models.sql +206 -0
  13. package/migrations/20260505000000_agent_board_inbox_schema.sql +341 -0
  14. package/migrations/20260506000000_agent_board_inbox_delivery_rpcs.sql +1200 -0
  15. package/migrations/20260506000001_agent_board_inbox_callback_rpcs.sql +330 -0
  16. package/migrations/20260507000000_agent_board_inbox_reads.sql +334 -0
  17. package/migrations/20260508000000_agent_board_callback_runtime.sql +453 -0
  18. package/migrations/20260508000001_agent_board_inbox_service_role_reads.sql +10 -0
  19. package/migrations/20260510000000_agent_board_dblint_repairs.sql +413 -0
  20. package/migrations/20260511000000_agent_board_auth_schema.sql +146 -0
  21. package/migrations/20260511000001_agent_board_auth_credentials.sql +39 -0
  22. package/migrations/20260512000000_agent_board_auth_provisioning_rpcs.sql +622 -0
  23. package/migrations/20260512000001_agent_board_auth_v1_rpcs.sql +798 -0
  24. package/migrations/20260512000002_agent_board_auth_v2_rpcs.sql +1243 -0
  25. package/migrations/20260512000003_agent_board_auth_rls.sql +484 -0
  26. package/migrations/20260513000000_agent_board_backup_ops.sql +34 -0
  27. package/migrations/20260513000001_agent_board_retention.sql +160 -0
  28. package/migrations/20260513000002_agent_board_monitoring_views.sql +227 -0
  29. package/migrations/20260514000000_agent_board_auth_rls_id_qualification.sql +150 -0
  30. package/migrations/20260515000000_agent_board_boardopt.sql +493 -0
  31. package/migrations/20260516000000_agent_board_callbackcap.sql +113 -0
  32. package/migrations/20260517000000_agent_board_addressing.sql +542 -0
  33. package/migrations/20260628000000_agent_board_signing_store.sql +174 -0
  34. package/migrations/20260628000001_agent_board_signing_proof_rpcs.sql +215 -0
  35. package/migrations/20260628000002_agent_board_signature_rls_reads.sql +300 -0
  36. package/migrations/20260628000003_agent_board_signed_write_gateway.sql +288 -0
  37. package/migrations/20260628000004_agent_board_fleet_registry.sql +411 -0
  38. package/migrations/20260628000005_agent_board_fleet_policy.sql +509 -0
  39. package/migrations/20260628000006_agent_board_fleet_read_models.sql +170 -0
  40. package/migrations/20260629000100_agent_board_fleet_registry_repair.sql +411 -0
  41. package/migrations/20260629000101_agent_board_fleet_policy_refresh.sql +509 -0
  42. package/migrations/20260629000102_agent_board_fleet_read_models_refresh.sql +170 -0
  43. package/migrations/20260630000100_agent_board_harness_kind_open_vocab.sql +15 -0
  44. package/migrations/20260710000000_agent_board_authbound_boundary.sql +155 -0
  45. package/migrations/20260710000100_agent_board_schema_version_rpc.sql +14 -0
  46. package/migrations/20260710000400_agent_board_nonfleet_identity.sql +87 -0
  47. package/migrations/20260710000401_agent_board_workload_providers.sql +39 -0
  48. package/migrations/20260711000000_agent_board_provision_system_fail_closed.sql +242 -0
  49. package/migrations/20260711000100_canary_signed_fleet_enforcement.sql +234 -0
  50. package/migrations/20260712000000_agent_board_bundle_endpoint_compat.sql +97 -0
  51. package/migrations/20260713000000_agent_board_rls_reassert_board_scoping.sql +90 -0
  52. package/package.json +42 -0
  53. package/provenance.json +580 -0
  54. package/schema-manifest.json +458 -0
  55. package/src/check.js +23 -0
  56. package/src/index.js +50 -0
  57. package/src/materialize.js +73 -0
  58. package/src/plan.js +44 -0
  59. package/src/tooling.test.mjs +97 -0
  60. package/tests/agent_board_addressing.test.sql +166 -0
  61. package/tests/agent_board_auth_rls.test.sql +243 -0
  62. package/tests/agent_board_auth_rls_id_qualification.test.sql +225 -0
  63. package/tests/agent_board_auth_rpcs.test.sql +311 -0
  64. package/tests/agent_board_auth_schema.test.sql +411 -0
  65. package/tests/agent_board_authbound_boundary.test.sql +148 -0
  66. package/tests/agent_board_boardopt.test.sql +170 -0
  67. package/tests/agent_board_bundle_endpoint_compat.test.sql +49 -0
  68. package/tests/agent_board_callback_runtime.test.sql +425 -0
  69. package/tests/agent_board_callbackcap.test.sql +222 -0
  70. package/tests/agent_board_canary_signed_fleet_enforcement.test.sql +60 -0
  71. package/tests/agent_board_dblint_rpcs.test.sql +247 -0
  72. package/tests/agent_board_fleet_policy.test.sql +370 -0
  73. package/tests/agent_board_fleet_read_models.test.sql +158 -0
  74. package/tests/agent_board_fleet_registry.test.sql +149 -0
  75. package/tests/agent_board_inbox_reads.test.sql +471 -0
  76. package/tests/agent_board_inbox_rpcs.test.sql +452 -0
  77. package/tests/agent_board_inbox_schema.test.sql +355 -0
  78. package/tests/agent_board_nonfleet_identity.test.sql +26 -0
  79. package/tests/agent_board_rls_reassert_board_scoping.test.sql +39 -0
  80. package/tests/agent_board_schema_version.test.sql +24 -0
  81. package/tests/agent_board_schema_version_rpc.test.sql +40 -0
  82. package/tests/agent_board_signature_rls_reads.test.sql +133 -0
  83. package/tests/agent_board_signed_write_gateway.test.sql +219 -0
  84. package/tests/agent_board_signing_proof_rpcs.test.sql +143 -0
  85. package/tests/agent_board_signing_store.test.sql +183 -0
  86. package/tests/agent_board_smoke.test.sql +12 -0
  87. package/tests/agent_board_v1_invariants.test.sql +298 -0
  88. package/tests/agent_board_v2_invariants.test.sql +753 -0
  89. package/tests/agent_board_workload_providers.test.sql +15 -0
  90. package/tests/fixtures/adopter-base.sql +19 -0
  91. package/tests/fixtures/operator-hardening.sql +27 -0
  92. package/tests/fixtures/test-prereqs.sql +84 -0
@@ -0,0 +1,452 @@
1
+ begin;
2
+
3
+ select plan(19);
4
+
5
+ select has_function(
6
+ 'agent_board',
7
+ 'send_direct_message',
8
+ array['uuid', 'uuid', 'uuid', 'text', 'text', 'text', 'text', 'text', 'jsonb'],
9
+ 'send_direct_message rpc exists'
10
+ );
11
+ select has_function(
12
+ 'agent_board',
13
+ 'send_multicast_message',
14
+ array['uuid', 'uuid', 'uuid[]', 'text', 'text', 'text', 'text', 'text', 'jsonb'],
15
+ 'send_multicast_message rpc exists'
16
+ );
17
+ select has_function(
18
+ 'agent_board',
19
+ 'post_board_message',
20
+ array['uuid', 'uuid', 'uuid', 'text', 'text', 'text', 'text', 'text', 'jsonb'],
21
+ 'post_board_message rpc exists'
22
+ );
23
+ select has_function(
24
+ 'agent_board',
25
+ 'create_tasking_delivery',
26
+ array['uuid', 'uuid', 'text', 'text', 'uuid[]', 'uuid', 'text', 'text', 'text', 'jsonb'],
27
+ 'create_tasking_delivery rpc exists'
28
+ );
29
+ select has_function(
30
+ 'agent_board',
31
+ 'create_board',
32
+ array['uuid', 'text', 'text', 'text', 'jsonb', 'text'],
33
+ 'create_board rpc exists'
34
+ );
35
+ select has_function(
36
+ 'agent_board',
37
+ 'join_board',
38
+ array['uuid', 'uuid', 'text', 'uuid', 'jsonb'],
39
+ 'join_board rpc exists'
40
+ );
41
+ select has_function(
42
+ 'agent_board',
43
+ 'leave_board',
44
+ array['uuid', 'uuid', 'jsonb'],
45
+ 'leave_board rpc exists'
46
+ );
47
+ select has_function(
48
+ 'agent_board',
49
+ 'update_board_membership',
50
+ array['uuid', 'uuid', 'text', 'jsonb'],
51
+ 'update_board_membership rpc exists'
52
+ );
53
+ select has_function(
54
+ 'agent_board',
55
+ 'update_inbox_item_state',
56
+ array['uuid', 'uuid', 'text', 'jsonb'],
57
+ 'update_inbox_item_state rpc exists'
58
+ );
59
+ select has_function(
60
+ 'agent_board',
61
+ 'register_callback_endpoint',
62
+ array['uuid', 'text', 'jsonb'],
63
+ 'register_callback_endpoint rpc exists'
64
+ );
65
+ select has_function(
66
+ 'agent_board',
67
+ 'verify_callback_endpoint',
68
+ array['uuid', 'uuid', 'jsonb'],
69
+ 'verify_callback_endpoint rpc exists'
70
+ );
71
+ select has_function(
72
+ 'agent_board',
73
+ 'disable_callback_endpoint',
74
+ array['uuid', 'uuid', 'text', 'jsonb'],
75
+ 'disable_callback_endpoint rpc exists'
76
+ );
77
+ select has_function(
78
+ 'agent_board',
79
+ 'rotate_callback_signing_key',
80
+ array['uuid', 'uuid', 'jsonb'],
81
+ 'rotate_callback_signing_key rpc exists'
82
+ );
83
+ select has_function(
84
+ 'agent_board',
85
+ 'delete_callback_endpoint',
86
+ array['uuid', 'uuid'],
87
+ 'delete_callback_endpoint rpc exists'
88
+ );
89
+ select has_function(
90
+ 'agent_board',
91
+ 'subscribe_callback_endpoint',
92
+ array['uuid', 'uuid', 'text', 'text', 'uuid', 'jsonb'],
93
+ 'subscribe_callback_endpoint rpc exists'
94
+ );
95
+ select has_function(
96
+ 'agent_board',
97
+ 'unsubscribe_callback_endpoint',
98
+ array['uuid', 'uuid', 'text', 'text', 'uuid', 'jsonb'],
99
+ 'unsubscribe_callback_endpoint rpc exists'
100
+ );
101
+
102
+ select ok(
103
+ (
104
+ select count(distinct routine_name) = 16
105
+ from information_schema.routine_privileges
106
+ where specific_schema = 'agent_board'
107
+ and routine_name in (
108
+ 'send_direct_message',
109
+ 'send_multicast_message',
110
+ 'post_board_message',
111
+ 'create_tasking_delivery',
112
+ 'create_board',
113
+ 'join_board',
114
+ 'leave_board',
115
+ 'update_board_membership',
116
+ 'update_inbox_item_state',
117
+ 'register_callback_endpoint',
118
+ 'verify_callback_endpoint',
119
+ 'disable_callback_endpoint',
120
+ 'rotate_callback_signing_key',
121
+ 'delete_callback_endpoint',
122
+ 'subscribe_callback_endpoint',
123
+ 'unsubscribe_callback_endpoint'
124
+ )
125
+ and grantee = 'service_role'
126
+ and privilege_type = 'EXECUTE'
127
+ ),
128
+ 'service_role can execute the public inbox rpc surface'
129
+ );
130
+
131
+ select ok(
132
+ (
133
+ select count(distinct routine_name) = 12
134
+ from information_schema.routine_privileges
135
+ where specific_schema = 'agent_board'
136
+ and routine_name in (
137
+ 'send_direct_message',
138
+ 'send_multicast_message',
139
+ 'post_board_message',
140
+ 'create_tasking_delivery',
141
+ 'create_board',
142
+ 'join_board',
143
+ 'leave_board',
144
+ 'update_board_membership',
145
+ 'update_inbox_item_state',
146
+ 'register_callback_endpoint',
147
+ 'verify_callback_endpoint',
148
+ 'disable_callback_endpoint',
149
+ 'rotate_callback_signing_key',
150
+ 'delete_callback_endpoint',
151
+ 'subscribe_callback_endpoint',
152
+ 'unsubscribe_callback_endpoint'
153
+ )
154
+ and grantee = 'authenticated'
155
+ and privilege_type = 'EXECUTE'
156
+ )
157
+ and not has_function_privilege('authenticated', 'agent_board.send_direct_message(uuid,uuid,uuid,text,text,text,text,text,jsonb)', 'EXECUTE')
158
+ and not has_function_privilege('authenticated', 'agent_board.send_multicast_message(uuid,uuid,uuid[],text,text,text,text,text,jsonb)', 'EXECUTE')
159
+ and not has_function_privilege('authenticated', 'agent_board.post_board_message(uuid,uuid,uuid,text,text,text,text,text,jsonb)', 'EXECUTE')
160
+ and not has_function_privilege('authenticated', 'agent_board.create_tasking_delivery(uuid,uuid,text,text,uuid[],uuid,text,text,text,jsonb)', 'EXECUTE'),
161
+ 'authenticated keeps non-delivery inbox RPCs while signed delivery RPC overloads stay gateway-only'
162
+ );
163
+
164
+ select ok(
165
+ (
166
+ select count(*) = 0
167
+ from information_schema.routine_privileges
168
+ where specific_schema = 'agent_board'
169
+ and routine_name in (
170
+ 'send_direct_message',
171
+ 'send_multicast_message',
172
+ 'post_board_message',
173
+ 'create_tasking_delivery',
174
+ 'create_board',
175
+ 'join_board',
176
+ 'leave_board',
177
+ 'update_board_membership',
178
+ 'update_inbox_item_state',
179
+ 'register_callback_endpoint',
180
+ 'verify_callback_endpoint',
181
+ 'disable_callback_endpoint',
182
+ 'rotate_callback_signing_key',
183
+ 'delete_callback_endpoint',
184
+ 'subscribe_callback_endpoint',
185
+ 'unsubscribe_callback_endpoint'
186
+ )
187
+ and grantee in ('PUBLIC', 'anon')
188
+ and privilege_type = 'EXECUTE'
189
+ ),
190
+ 'public and anon have no execute grant on the inbox rpc surface'
191
+ );
192
+
193
+ do $$
194
+ declare
195
+ v_sender uuid := gen_random_uuid();
196
+ v_direct_recipient uuid := gen_random_uuid();
197
+ v_multicast_recipient uuid := gen_random_uuid();
198
+ v_board_member uuid := gen_random_uuid();
199
+ v_left_member uuid := gen_random_uuid();
200
+ v_outsider uuid := gen_random_uuid();
201
+ v_thread uuid := gen_random_uuid();
202
+ v_board uuid;
203
+ v_direct_endpoint uuid;
204
+ v_board_endpoint uuid;
205
+ v_outsider_endpoint uuid;
206
+ v_direct_inbox uuid;
207
+ v_direct_message uuid;
208
+ v_board_message uuid;
209
+ v_task uuid;
210
+ v_result record;
211
+ begin
212
+ insert into agent_board.agents(id, agent_type, handle, display_name)
213
+ values
214
+ (v_sender, 'agent', 'test_inboxrpc_sender', 'Inbox RPC Sender'),
215
+ (v_direct_recipient, 'agent', 'test_inboxrpc_direct', 'Inbox RPC Direct'),
216
+ (v_multicast_recipient, 'agent', 'test_inboxrpc_multi', 'Inbox RPC Multi'),
217
+ (v_board_member, 'agent', 'test_inboxrpc_board', 'Inbox RPC Board'),
218
+ (v_left_member, 'agent', 'test_inboxrpc_left', 'Inbox RPC Left'),
219
+ (v_outsider, 'agent', 'test_inboxrpc_outsider', 'Inbox RPC Outsider');
220
+
221
+ insert into agent_board.threads(id, thread_type, created_by_agent_id, subject)
222
+ values (v_thread, 'coordination', v_sender, 'Inbox RPC Test Thread');
223
+
224
+ select * into v_result
225
+ from agent_board.create_board(
226
+ v_sender,
227
+ 'test-inboxrpc-board',
228
+ 'Inbox RPC Board',
229
+ 'board for inbox rpc tests',
230
+ '{}'::jsonb,
231
+ 'private'
232
+ );
233
+ v_board := v_result.board_id;
234
+ perform ok(v_board is not null, 'create_board returns a board id');
235
+
236
+ select * into v_result
237
+ from agent_board.join_board(v_board, v_board_member, 'subscribed', v_sender, '{}'::jsonb);
238
+ perform is(v_result.membership_state, 'subscribed', 'join_board supports subscribed state');
239
+
240
+ select * into v_result
241
+ from agent_board.join_board(v_board, v_direct_recipient, 'joined', v_sender, '{}'::jsonb);
242
+ perform is(v_result.membership_state, 'joined', 'join_board supports joined state');
243
+
244
+ select * into v_result
245
+ from agent_board.join_board(v_board, v_left_member, 'joined', v_sender, '{}'::jsonb);
246
+ select * into v_result
247
+ from agent_board.leave_board(v_board, v_left_member, '{}'::jsonb);
248
+ perform is(v_result.membership_state, 'left', 'leave_board marks memberships as left');
249
+
250
+ select * into v_result
251
+ from agent_board.register_callback_endpoint(v_direct_recipient, 'https://example.test/direct', '{}'::jsonb);
252
+ v_direct_endpoint := v_result.endpoint_id;
253
+ perform is(v_result.endpoint_status, 'pending_verification', 'register_callback_endpoint starts pending verification');
254
+
255
+ select * into v_result
256
+ from agent_board.verify_callback_endpoint(v_direct_endpoint, v_direct_recipient, '{"controlProofRef":"inbox-rpcs/direct"}'::jsonb);
257
+ perform is(v_result.endpoint_status, 'verified', 'verify_callback_endpoint marks endpoint verified');
258
+
259
+ select * into v_result
260
+ from agent_board.subscribe_callback_endpoint(v_direct_endpoint, v_direct_recipient, 'own_inbox', 'inbox.item.delivered', null, '{}'::jsonb);
261
+ perform is(v_result.subscription_status, 'active', 'own-inbox callback subscription activates');
262
+
263
+ select * into v_result
264
+ from agent_board.subscribe_callback_endpoint(v_direct_endpoint, v_direct_recipient, 'own_inbox', 'inbox.item.updated', null, '{}'::jsonb);
265
+ perform is(v_result.subscription_status, 'active', 'own-inbox update subscription activates');
266
+
267
+ select * into v_result
268
+ from agent_board.register_callback_endpoint(v_board_member, 'https://example.test/board', '{}'::jsonb);
269
+ v_board_endpoint := v_result.endpoint_id;
270
+ select * into v_result
271
+ from agent_board.verify_callback_endpoint(v_board_endpoint, v_board_member, '{"controlProofRef":"inbox-rpcs/board"}'::jsonb);
272
+ select * into v_result
273
+ from agent_board.subscribe_callback_endpoint(v_board_endpoint, v_board_member, 'board', 'board.post.delivered', v_board, '{}'::jsonb);
274
+ perform is(v_result.subscription_status, 'active', 'board callback subscription activates for active member');
275
+
276
+ select * into v_result
277
+ from agent_board.register_callback_endpoint(v_outsider, 'https://example.test/outsider', '{}'::jsonb);
278
+ v_outsider_endpoint := v_result.endpoint_id;
279
+
280
+ begin
281
+ perform agent_board.subscribe_callback_endpoint(v_outsider_endpoint, v_outsider, 'board', 'board.post.delivered', v_board, '{}'::jsonb);
282
+ perform ok(false, 'board subscription without membership should fail');
283
+ exception
284
+ when sqlstate 'P0002' then
285
+ perform ok(true, 'board subscription requires active board membership');
286
+ end;
287
+
288
+ select * into v_result
289
+ from agent_board.send_direct_message(
290
+ v_thread,
291
+ v_sender,
292
+ v_direct_recipient,
293
+ 'Direct subject',
294
+ 'Direct body for idempotent delivery',
295
+ 'comment',
296
+ 'high',
297
+ 'direct-key',
298
+ '{}'::jsonb
299
+ );
300
+ v_direct_message := v_result.message_id;
301
+ v_direct_inbox := v_result.inbox_item_id;
302
+ perform is(v_result.callback_outbox_count, 1, 'direct delivery enqueues one own-inbox callback');
303
+ perform is(
304
+ (select count(*)::integer from agent_board.delivery_recipients where source_message_id = v_direct_message),
305
+ 1,
306
+ 'direct delivery writes one recipient row'
307
+ );
308
+ perform is(
309
+ (select count(*)::integer from agent_board.inbox_items where source_message_id = v_direct_message),
310
+ 1,
311
+ 'direct delivery writes one inbox row'
312
+ );
313
+
314
+ select * into v_result
315
+ from agent_board.send_direct_message(
316
+ v_thread,
317
+ v_sender,
318
+ v_direct_recipient,
319
+ 'Direct subject',
320
+ 'Direct body for idempotent delivery',
321
+ 'comment',
322
+ 'high',
323
+ 'direct-key',
324
+ '{}'::jsonb
325
+ );
326
+ perform is(v_result.message_id, v_direct_message, 'direct idempotency reuses canonical message');
327
+ perform is(
328
+ (select count(*)::integer from agent_board.callback_outbox_events where source_message_id = v_direct_message and event_category = 'inbox.item.delivered'),
329
+ 1,
330
+ 'direct idempotency does not duplicate delivered callback events'
331
+ );
332
+
333
+ select * into v_result
334
+ from agent_board.update_inbox_item_state(v_direct_inbox, v_direct_recipient, 'read', '{}'::jsonb);
335
+ perform is(v_result.action_state, 'read', 'update_inbox_item_state changes action state');
336
+ perform is(
337
+ (select count(*)::integer from agent_board.callback_outbox_events where inbox_item_id = v_direct_inbox and event_category = 'inbox.item.updated'),
338
+ 1,
339
+ 'updating inbox state enqueues one inbox.item.updated callback'
340
+ );
341
+
342
+ select * into v_result
343
+ from agent_board.send_multicast_message(
344
+ v_thread,
345
+ v_sender,
346
+ array[v_direct_recipient, v_multicast_recipient, v_direct_recipient]::uuid[],
347
+ 'Multicast subject',
348
+ 'Multicast body for duplicate suppression',
349
+ 'comment',
350
+ 'medium',
351
+ 'multicast-key',
352
+ '{}'::jsonb
353
+ );
354
+ perform is(v_result.delivered_recipient_count, 2, 'multicast collapses duplicate recipients');
355
+ perform is(
356
+ (select count(*)::integer from agent_board.delivery_recipients where source_message_id = v_result.message_id),
357
+ 2,
358
+ 'multicast writes one recipient row per unique recipient'
359
+ );
360
+
361
+ select * into v_result
362
+ from agent_board.post_board_message(
363
+ v_thread,
364
+ v_sender,
365
+ v_board,
366
+ 'Board subject',
367
+ 'Board body for subscribed members',
368
+ 'comment',
369
+ 'urgent',
370
+ 'board-key',
371
+ '{}'::jsonb
372
+ );
373
+ v_board_message := v_result.message_id;
374
+ perform is(v_result.delivered_recipient_count, 2, 'board delivery reaches joined and subscribed members only');
375
+ perform is(
376
+ (select count(*)::integer from agent_board.delivery_recipients where source_message_id = v_board_message and recipient_agent_id = v_left_member),
377
+ 0,
378
+ 'left board members do not receive board delivery'
379
+ );
380
+ perform ok(
381
+ exists(
382
+ select 1
383
+ from agent_board.callback_outbox_events
384
+ where source_message_id = v_board_message
385
+ and event_category = 'board.post.delivered'
386
+ and recipient_agent_id = v_board_member
387
+ ),
388
+ 'board.post.delivered callback events are queued for board subscribers'
389
+ );
390
+
391
+ select * into v_result
392
+ from agent_board.create_tasking_delivery(
393
+ v_thread,
394
+ v_sender,
395
+ 'Tasking title',
396
+ 'Tasking details for duplicate suppression',
397
+ array[v_direct_recipient]::uuid[],
398
+ v_board,
399
+ 'high',
400
+ 'distributed',
401
+ 'task-key',
402
+ '{}'::jsonb
403
+ );
404
+ v_task := v_result.task_id;
405
+ perform is(v_result.delivered_recipient_count, 3, 'tasking counts explicit and board fan-out attempts');
406
+ perform is(
407
+ (select count(*)::integer from agent_board.delivery_recipients where source_task_id = v_task),
408
+ 2,
409
+ 'tasking suppresses duplicate recipient rows across explicit and board fan-out'
410
+ );
411
+ perform is(
412
+ (select count(*)::integer from agent_board.inbox_items where source_task_id = v_task),
413
+ 2,
414
+ 'tasking writes one inbox row per unique recipient'
415
+ );
416
+
417
+ select * into v_result
418
+ from agent_board.rotate_callback_signing_key(v_direct_endpoint, v_direct_recipient, '{}'::jsonb);
419
+ perform is(v_result.signing_key_version, 2, 'rotate_callback_signing_key bumps signing key version');
420
+ perform is(v_result.endpoint_status, 'pending_verification', 'signing key rotation returns endpoint to pending verification');
421
+
422
+ select * into v_result
423
+ from agent_board.disable_callback_endpoint(v_board_endpoint, v_board_member, 'test disable', '{}'::jsonb);
424
+ perform is(v_result.endpoint_status, 'disabled', 'disable_callback_endpoint marks endpoint disabled');
425
+ perform ok(
426
+ exists(
427
+ select 1
428
+ from agent_board.callback_subscriptions
429
+ where endpoint_id = v_board_endpoint
430
+ and subscription_status = 'disabled'
431
+ ),
432
+ 'disabling an endpoint disables attached subscriptions'
433
+ );
434
+
435
+ select * into v_result
436
+ from agent_board.unsubscribe_callback_endpoint(v_direct_endpoint, v_direct_recipient, 'own_inbox', 'inbox.item.updated', null, '{}'::jsonb);
437
+ perform is(v_result.subscription_status, 'disabled', 'unsubscribe_callback_endpoint disables an active subscription');
438
+
439
+ select * into v_result
440
+ from agent_board.delete_callback_endpoint(v_outsider_endpoint, v_outsider);
441
+ perform ok(v_result.deleted, 'delete_callback_endpoint removes owned endpoints');
442
+
443
+ perform is(
444
+ (select count(*)::integer from agent_board.callback_delivery_attempts),
445
+ 0,
446
+ 'callback management remains metadata-only before runtime delivery exists'
447
+ );
448
+ end;
449
+ $$;
450
+
451
+ select * from finish();
452
+ rollback;