@consiliency/agent-board-schema 1.12.0 → 1.22.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 (104) hide show
  1. package/fixtures/already-applied.json +124 -0
  2. package/migrations/20260731000100_agent_board_agentsdir_column_scope.sql +88 -0
  3. package/migrations/20260801000100_agent_board_leaveidem_terminal_noop.sql +317 -0
  4. package/migrations/20260802000100_agent_board_rolereset_reentry_clamp.sql +237 -0
  5. package/migrations/20260803000100_agent_board_rowid_total_order.sql +1201 -0
  6. package/migrations/20260804000100_agent_board_hygiene_actor_binding_rls.sql +229 -0
  7. package/migrations/20260806000100_agent_board_consent_substrate.sql +339 -0
  8. package/migrations/20260807000100_agent_board_consent_emission.sql +619 -0
  9. package/migrations/20260808000100_agent_board_rejectsem.sql +145 -0
  10. package/migrations/20260809000100_agent_board_consent_gating.sql +492 -0
  11. package/migrations/20260810000100_agent_board_consent_locking.sql +485 -0
  12. package/migrations/20260811000100_agent_board_retention_metadata_revoke.sql +23 -0
  13. package/migrations/20260812000100_agent_board_pubexec_class_revoke.sql +54 -0
  14. package/migrations/20260813000100_agent_board_invitepolicy_block.sql +420 -0
  15. package/migrations/20260814000100_agent_board_obsgate_thread_post_policy.sql +239 -0
  16. package/migrations/20260815000100_agent_board_obsgate2_authoring_gates.sql +287 -0
  17. package/migrations/20260816000100_agent_board_obsgate3_task_lifecycle.sql +118 -0
  18. package/migrations/20260817000100_agent_board_obsgate4_claims_lifecycle.sql +250 -0
  19. package/migrations/20260818000100_agent_board_fleet_machines_rls_correlation.sql +43 -0
  20. package/migrations/20260819000100_agent_board_resolve_noop_retire.sql +166 -0
  21. package/migrations/20260820000100_agent_board_roomdeleg_lane_b.sql +206 -0
  22. package/migrations/20260821000100_agent_board_claimgate_claimkey.sql +372 -0
  23. package/migrations/20260822000100_agent_board_mint_harden.sql +710 -0
  24. package/migrations/20260822000200_agent_board_mint_harden_fingerprint_backfill.sql +207 -0
  25. package/migrations/20260823000100_agent_board_fleet_machine_hijack.sql +106 -0
  26. package/migrations/20260824000100_agent_board_claimgate_shadow.sql +443 -0
  27. package/migrations/20260825000100_agent_board_claimgate_enforce.sql +660 -0
  28. package/migrations/20260826000100_agent_board_fleet_machine_reactivation.sql +514 -0
  29. package/migrations/20260828000100_agent_board_delegation_cap_ceiling.sql +474 -0
  30. package/migrations/20260829000100_agent_board_fleet_machine_reclaim.sql +432 -0
  31. package/migrations/20260830000100_agent_board_delegation_sole_writer.sql +41 -0
  32. package/migrations/20260831000100_agent_board_fleet_domain_reclaim.sql +389 -0
  33. package/package.json +1 -1
  34. package/schema-manifest.json +282 -3
  35. package/src/tooling.test.mjs +113 -2
  36. package/tests/agent_board_actorspoof.test.sql +349 -0
  37. package/tests/agent_board_agentsdir.test.sql +154 -0
  38. package/tests/agent_board_authbound_boundary.test.sql +9 -1
  39. package/tests/agent_board_claimgate_enforce.test.sql +464 -0
  40. package/tests/agent_board_claimgate_predicate.test.sql +360 -0
  41. package/tests/agent_board_claimkey.test.sql +229 -0
  42. package/tests/agent_board_consent_backfill.test.sql +48 -0
  43. package/tests/agent_board_consent_emission.test.sql +601 -0
  44. package/tests/agent_board_consent_gating.test.sql +489 -0
  45. package/tests/agent_board_consent_substrate.test.sql +720 -0
  46. package/tests/agent_board_delegation_cap_ceiling.test.sql +303 -0
  47. package/tests/agent_board_delegation_enforcement.test.sql +55 -6
  48. package/tests/agent_board_delegation_policies.test.sql +107 -1
  49. package/tests/agent_board_fedsession.test.sql +6 -4
  50. package/tests/agent_board_fleet_domain_reclaim.test.sql +419 -0
  51. package/tests/agent_board_fleet_machine_hijack.test.sql +190 -0
  52. package/tests/agent_board_fleet_machine_reactivation.test.sql +426 -0
  53. package/tests/agent_board_fleet_machine_reclaim.test.sql +509 -0
  54. package/tests/agent_board_fleet_machines_rls.test.sql +79 -0
  55. package/tests/agent_board_fleet_policy.test.sql +2 -2
  56. package/tests/agent_board_grants_hygiene.test.sql +408 -0
  57. package/tests/agent_board_identity_providers.test.sql +9 -0
  58. package/tests/agent_board_inbox_reads.test.sql +29 -12
  59. package/tests/agent_board_inbox_rpcs.test.sql +44 -34
  60. package/tests/agent_board_invitepolicy.test.sql +205 -0
  61. package/tests/agent_board_leaveidem.test.sql +194 -0
  62. package/tests/agent_board_mint_fleet_agent_session.test.sql +10 -10
  63. package/tests/agent_board_mint_harden.test.sql +303 -0
  64. package/tests/agent_board_mint_harden_backfill.test.sql +95 -0
  65. package/tests/agent_board_obsgate.test.sql +178 -0
  66. package/tests/agent_board_obsgate2.test.sql +153 -0
  67. package/tests/agent_board_obsgate3.test.sql +170 -0
  68. package/tests/agent_board_obsgate4.test.sql +196 -0
  69. package/tests/agent_board_portal_offline_invariant.test.sql +13 -2
  70. package/tests/agent_board_pubexec_class.test.sql +53 -0
  71. package/tests/agent_board_rejectsem.test.sql +271 -0
  72. package/tests/agent_board_resigning_refresh.test.sql +1 -1
  73. package/tests/agent_board_resolve_noop_retire.test.sql +71 -0
  74. package/tests/agent_board_retention_metadata_acl.test.sql +53 -0
  75. package/tests/agent_board_revoke_agent_subtree.test.sql +3 -3
  76. package/tests/agent_board_rolereset.test.sql +351 -0
  77. package/tests/agent_board_roomdeleg.test.sql +5 -3
  78. package/tests/agent_board_roomdeleg_lane_b.test.sql +144 -0
  79. package/tests/agent_board_roomlink.test.sql +25 -9
  80. package/tests/agent_board_rowid.test.sql +1274 -0
  81. package/tests/agent_board_signature_rls_reads.test.sql +3 -3
  82. package/tests/agent_board_signed_write_gateway.test.sql +3 -3
  83. package/tests/agent_board_signing_challenge_pending_cap.test.sql +2 -2
  84. package/tests/agent_board_signing_challenge_reaper.test.sql +2 -2
  85. package/tests/agent_board_signing_proof_rpcs.test.sql +4 -4
  86. package/tests/agent_board_signing_store.test.sql +6 -6
  87. package/tests/fixtures/isolation-substrate.sql +93 -0
  88. package/tests/fixtures/test-prereqs.sql +33 -0
  89. package/tests/isolation/expected/claimgate_ownerless_quota.out +36 -0
  90. package/tests/isolation/expected/claimkey_renew_race.out +38 -0
  91. package/tests/isolation/expected/consent_approval_lock.out +43 -0
  92. package/tests/isolation/expected/consent_emit_actual_state.out +37 -0
  93. package/tests/isolation/expected/domain_reclaim_revoke_race.out +40 -0
  94. package/tests/isolation/expected/reactivate_revoke_race.out +80 -0
  95. package/tests/isolation/expected/reclaim_revoke_race.out +40 -0
  96. package/tests/isolation/specs/claimgate_ownerless_quota.spec +77 -0
  97. package/tests/isolation/specs/claimkey_renew_race.spec +67 -0
  98. package/tests/isolation/specs/consent_approval_lock.spec +98 -0
  99. package/tests/isolation/specs/consent_emit_actual_state.spec +79 -0
  100. package/tests/isolation/specs/domain_reclaim_revoke_race.spec +88 -0
  101. package/tests/isolation/specs/reactivate_revoke_race.spec +115 -0
  102. package/tests/isolation/specs/reclaim_revoke_race.spec +94 -0
  103. package/tests/seeds/20260806000100_agent_board_consent_substrate.sql.pre.sql +19 -0
  104. package/tests/seeds/20260822000200_agent_board_mint_harden_fingerprint_backfill.sql.pre.sql +39 -0
@@ -0,0 +1,408 @@
1
+ -- MBHYGIENE pgTAP: schema-version coverage and callback-policy liveness.
2
+ --
3
+ -- SECURITY DEFINER RPC controls are driven by JWT claims only. Direct-table RLS
4
+ -- probes additionally SET LOCAL ROLE authenticated because PostgreSQL RLS binds
5
+ -- to the database role, not to JWT claims.
6
+ create extension if not exists pgtap with schema extensions;
7
+ set search_path = extensions, public, agent_board;
8
+
9
+ begin;
10
+
11
+ select plan(20);
12
+
13
+ create temp table _grants_hygiene_fixture (
14
+ owner_user uuid,
15
+ owner_agent uuid,
16
+ subscriber_user uuid,
17
+ subscriber_agent uuid,
18
+ unrelated_user uuid,
19
+ unrelated_agent uuid,
20
+ endpoint_id uuid,
21
+ subscription_id uuid,
22
+ outbox_id uuid,
23
+ attempt_id uuid
24
+ ) on commit drop;
25
+ grant select on table _grants_hygiene_fixture to authenticated, service_role;
26
+
27
+ do $$
28
+ declare
29
+ v_suffix text := replace(substr(gen_random_uuid()::text, 1, 8), '-', '');
30
+ v_owner_user uuid;
31
+ v_owner_agent uuid := gen_random_uuid();
32
+ v_subscriber_user uuid;
33
+ v_subscriber_agent uuid := gen_random_uuid();
34
+ v_unrelated_user uuid;
35
+ v_unrelated_agent uuid := gen_random_uuid();
36
+ v_thread_id uuid := gen_random_uuid();
37
+ v_message_id uuid := gen_random_uuid();
38
+ v_endpoint_id uuid := gen_random_uuid();
39
+ v_subscription_id uuid := gen_random_uuid();
40
+ v_outbox_id uuid := gen_random_uuid();
41
+ v_attempt_id uuid := gen_random_uuid();
42
+ begin
43
+ v_owner_user := public.ensure_github_user(
44
+ 'hygiene-owner-' || v_suffix,
45
+ 'hygiene-owner-' || v_suffix || '@example.test',
46
+ 'hygiene-owner-' || v_suffix,
47
+ 'HYGIENE Owner'
48
+ );
49
+ v_subscriber_user := public.ensure_github_user(
50
+ 'hygiene-subscriber-' || v_suffix,
51
+ 'hygiene-subscriber-' || v_suffix || '@example.test',
52
+ 'hygiene-subscriber-' || v_suffix,
53
+ 'HYGIENE Subscriber'
54
+ );
55
+ v_unrelated_user := public.ensure_github_user(
56
+ 'hygiene-unrelated-' || v_suffix,
57
+ 'hygiene-unrelated-' || v_suffix || '@example.test',
58
+ 'hygiene-unrelated-' || v_suffix,
59
+ 'HYGIENE Unrelated'
60
+ );
61
+
62
+ insert into agent_board.agents(
63
+ id, agent_type, kind, handle, display_name, status, auth_user_id, owner_user_id
64
+ )
65
+ values
66
+ (
67
+ v_owner_agent,
68
+ 'agent',
69
+ 'agent',
70
+ 'hygiene-owner-' || v_suffix,
71
+ 'HYGIENE Owner',
72
+ 'active',
73
+ v_owner_user,
74
+ v_owner_user
75
+ ),
76
+ (
77
+ v_subscriber_agent,
78
+ 'agent',
79
+ 'agent',
80
+ 'hygiene-subscriber-' || v_suffix,
81
+ 'HYGIENE Subscriber',
82
+ 'active',
83
+ v_subscriber_user,
84
+ v_subscriber_user
85
+ ),
86
+ (
87
+ v_unrelated_agent,
88
+ 'agent',
89
+ 'agent',
90
+ 'hygiene-unrelated-' || v_suffix,
91
+ 'HYGIENE Unrelated',
92
+ 'active',
93
+ v_unrelated_user,
94
+ v_unrelated_user
95
+ );
96
+
97
+ insert into agent_board.threads(
98
+ id, thread_type, created_by_agent_id, subject
99
+ )
100
+ values (
101
+ v_thread_id, 'coordination', v_owner_agent, 'HYGIENE callback source'
102
+ );
103
+
104
+ insert into agent_board.messages(
105
+ id, thread_id, author_agent_id, message_sequence, message_type, body
106
+ )
107
+ values (
108
+ v_message_id, v_thread_id, v_owner_agent, 1, 'comment', 'HYGIENE callback source'
109
+ );
110
+
111
+ -- Load-bearing dead-arm arrangement: the subscriber is NOT the endpoint owner,
112
+ -- and the outbox recipient is also NOT the subscriber. The subscriber can reach
113
+ -- these rows only through callback_subscriptions.agent_id.
114
+ insert into agent_board.callback_endpoints(
115
+ id,
116
+ owner_agent_id,
117
+ endpoint_url,
118
+ endpoint_status,
119
+ verification_requested_at,
120
+ verified_at
121
+ )
122
+ values (
123
+ v_endpoint_id,
124
+ v_owner_agent,
125
+ 'https://example.test/hygiene-' || v_suffix,
126
+ 'verified',
127
+ timestamptz '2020-01-01',
128
+ timestamptz '2020-01-01'
129
+ );
130
+
131
+ insert into agent_board.callback_subscriptions(
132
+ id,
133
+ endpoint_id,
134
+ agent_id,
135
+ subscription_scope,
136
+ event_category,
137
+ subscription_status
138
+ )
139
+ values (
140
+ v_subscription_id,
141
+ v_endpoint_id,
142
+ v_subscriber_agent,
143
+ 'own_inbox',
144
+ 'inbox.item.delivered',
145
+ 'active'
146
+ );
147
+
148
+ insert into agent_board.callback_outbox_events(
149
+ id,
150
+ endpoint_id,
151
+ subscription_id,
152
+ recipient_agent_id,
153
+ event_category,
154
+ delivery_mode,
155
+ source_kind,
156
+ source_message_id,
157
+ idempotency_key,
158
+ fetch_descriptor
159
+ )
160
+ values (
161
+ v_outbox_id,
162
+ v_endpoint_id,
163
+ v_subscription_id,
164
+ v_owner_agent,
165
+ 'inbox.item.delivered',
166
+ 'direct',
167
+ 'message',
168
+ v_message_id,
169
+ 'hygiene-outbox-' || v_suffix,
170
+ jsonb_build_object(
171
+ 'surface', 'thread',
172
+ 'targetId', v_thread_id::text,
173
+ 'view', 'agent_board.v_thread_feed',
174
+ 'filters', jsonb_build_object(),
175
+ 'reason', 'MBHYGIENE callback policy liveness'
176
+ )
177
+ );
178
+
179
+ insert into agent_board.callback_delivery_attempts(
180
+ id,
181
+ outbox_event_id,
182
+ endpoint_id,
183
+ attempt_number,
184
+ attempt_outcome,
185
+ started_at,
186
+ finished_at
187
+ )
188
+ values (
189
+ v_attempt_id,
190
+ v_outbox_id,
191
+ v_endpoint_id,
192
+ 1,
193
+ 'delivered',
194
+ timestamptz '2020-01-01',
195
+ timestamptz '2020-01-01'
196
+ );
197
+
198
+ insert into _grants_hygiene_fixture(
199
+ owner_user,
200
+ owner_agent,
201
+ subscriber_user,
202
+ subscriber_agent,
203
+ unrelated_user,
204
+ unrelated_agent,
205
+ endpoint_id,
206
+ subscription_id,
207
+ outbox_id,
208
+ attempt_id
209
+ )
210
+ values (
211
+ v_owner_user,
212
+ v_owner_agent,
213
+ v_subscriber_user,
214
+ v_subscriber_agent,
215
+ v_unrelated_user,
216
+ v_unrelated_agent,
217
+ v_endpoint_id,
218
+ v_subscription_id,
219
+ v_outbox_id,
220
+ v_attempt_id
221
+ );
222
+ end
223
+ $$;
224
+
225
+ -- 1. Initial service/operator control.
226
+ select set_config('request.jwt.claim.role', '', true);
227
+ select set_config('request.jwt.claim.sub', '', true);
228
+ select is(
229
+ agent_board._is_service_role(),
230
+ true,
231
+ 'control: grants-hygiene fixture block really runs as service_role');
232
+
233
+ -- 2-7. schema_version is a service-role island with defense-in-depth RLS.
234
+ select is(
235
+ (
236
+ select c.relrowsecurity
237
+ from pg_class c
238
+ join pg_namespace n on n.oid = c.relnamespace
239
+ where n.nspname = 'agent_board'
240
+ and c.relname = 'schema_version'
241
+ and c.relkind = 'r'
242
+ ),
243
+ true,
244
+ 'schema_version has row level security enabled');
245
+
246
+ select ok(
247
+ exists (
248
+ select 1
249
+ from pg_policy p
250
+ where p.polrelid = 'agent_board.schema_version'::regclass
251
+ and p.polpermissive
252
+ and p.polcmd = 'r'
253
+ and pg_get_expr(p.polqual, p.polrelid) = 'true'
254
+ ),
255
+ 'schema_version has an explicit permissive read policy');
256
+
257
+ select ok(
258
+ not has_table_privilege(
259
+ 'authenticated',
260
+ 'agent_board.schema_version',
261
+ 'SELECT'
262
+ ),
263
+ 'schema_version has no authenticated SELECT grant');
264
+
265
+ select ok(
266
+ not has_table_privilege('anon', 'agent_board.schema_version', 'SELECT'),
267
+ 'schema_version has no anon SELECT grant');
268
+
269
+ select is(
270
+ (select version from agent_board.get_schema_version()),
271
+ 8,
272
+ 'service/operator get_schema_version RPC still returns version 8');
273
+
274
+ set local role service_role;
275
+ select is(
276
+ (select version from agent_board.schema_version),
277
+ 8,
278
+ 'service_role direct schema_version read remains allowed under RLS');
279
+ reset role;
280
+
281
+ -- Subscriber JWT controls run before SET LOCAL ROLE so the internal helpers remain
282
+ -- callable. The following RPC probe intentionally stays JWT-only.
283
+ select set_config('request.jwt.claim.role', 'authenticated', true);
284
+ select set_config(
285
+ 'request.jwt.claim.sub',
286
+ (select subscriber_user::text from _grants_hygiene_fixture),
287
+ true
288
+ );
289
+
290
+ -- 8-10. Required authenticated controls plus the RPC-only ALLOW contract.
291
+ select is(
292
+ agent_board._is_service_role(),
293
+ false,
294
+ 'control: subscriber callback block is not service_role');
295
+ select is(
296
+ agent_board.current_agent_id(),
297
+ (select subscriber_agent from _grants_hygiene_fixture),
298
+ 'control: subscriber callback block resolves the expected actor');
299
+ select is(
300
+ (select version from agent_board.get_schema_version()),
301
+ 8,
302
+ 'authenticated get_schema_version RPC remains available without direct table access');
303
+
304
+ -- Direct table and RLS probes require the database role switch.
305
+ set local role authenticated;
306
+
307
+ -- 11. Direct schema_version remains denied by the deployed RPC-only grant posture.
308
+ select throws_ok(
309
+ $$select version from agent_board.schema_version$$,
310
+ '42501',
311
+ null,
312
+ 'authenticated direct schema_version read remains denied');
313
+
314
+ -- 12-13. Arrangement controls: subscriber is not endpoint owner, while the
315
+ -- callback_subscriptions policy's separate EXISTS keeps its subscriber arm live.
316
+ select is(
317
+ (
318
+ select count(*)::int
319
+ from agent_board.callback_endpoints ce
320
+ where ce.id = (select endpoint_id from _grants_hygiene_fixture)
321
+ ),
322
+ 0,
323
+ 'subscriber cannot read the foreign-owned callback endpoint');
324
+
325
+ select is(
326
+ (
327
+ select count(*)::int
328
+ from agent_board.callback_subscriptions cs
329
+ where cs.id = (select subscription_id from _grants_hygiene_fixture)
330
+ ),
331
+ 1,
332
+ 'subscriber can read its callback subscription despite not owning the endpoint');
333
+
334
+ -- 14-15. ALLOW: both formerly-dead subscriber arms must return their own rows.
335
+ select is(
336
+ (
337
+ select count(*)::int
338
+ from agent_board.callback_outbox_events coe
339
+ where coe.id = (select outbox_id from _grants_hygiene_fixture)
340
+ ),
341
+ 1,
342
+ 'non-owner subscriber can read its callback outbox event');
343
+
344
+ select is(
345
+ (
346
+ select count(*)::int
347
+ from agent_board.callback_delivery_attempts cda
348
+ where cda.id = (select attempt_id from _grants_hygiene_fixture)
349
+ ),
350
+ 1,
351
+ 'non-owner subscriber can read its callback delivery attempt');
352
+
353
+ reset role;
354
+
355
+ -- Unrelated-agent DENY twin, again with JWT controls before the DB role switch.
356
+ select set_config(
357
+ 'request.jwt.claim.sub',
358
+ (select unrelated_user::text from _grants_hygiene_fixture),
359
+ true
360
+ );
361
+
362
+ -- 16-17. Required non-vacuity controls.
363
+ select is(
364
+ agent_board._is_service_role(),
365
+ false,
366
+ 'control: unrelated callback block is not service_role');
367
+ select is(
368
+ agent_board.current_agent_id(),
369
+ (select unrelated_agent from _grants_hygiene_fixture),
370
+ 'control: unrelated callback block resolves the expected actor');
371
+
372
+ set local role authenticated;
373
+
374
+ -- 18-20. DENY: an unrelated agent sees none of the callback chain.
375
+ select is(
376
+ (
377
+ select count(*)::int
378
+ from agent_board.callback_subscriptions cs
379
+ where cs.id = (select subscription_id from _grants_hygiene_fixture)
380
+ ),
381
+ 0,
382
+ 'unrelated agent cannot read the callback subscription');
383
+
384
+ select is(
385
+ (
386
+ select count(*)::int
387
+ from agent_board.callback_outbox_events coe
388
+ where coe.id = (select outbox_id from _grants_hygiene_fixture)
389
+ ),
390
+ 0,
391
+ 'unrelated agent cannot read the callback outbox event');
392
+
393
+ select is(
394
+ (
395
+ select count(*)::int
396
+ from agent_board.callback_delivery_attempts cda
397
+ where cda.id = (select attempt_id from _grants_hygiene_fixture)
398
+ ),
399
+ 0,
400
+ 'unrelated agent cannot read the callback delivery attempt');
401
+
402
+ reset role;
403
+ select set_config('request.jwt.claim.role', '', true);
404
+ select set_config('request.jwt.claim.sub', '', true);
405
+
406
+ select finish();
407
+
408
+ rollback;
@@ -77,6 +77,15 @@ select is(
77
77
  where issuer = 'https://portal.consiliency.io'),
78
78
  'https://message-board.consiliency.io',
79
79
  'real Portal audience is byte-identical to the frozen resource constant (RFC 8707)');
80
+ -- Freeze the delegated-actor claim too (message-board#87): MBPORTALGATE froze both
81
+ -- the resource-audience AND actor_claim; the audience is broken out above, this is
82
+ -- the matching actor_claim freeze the plan budgeted but never wrote.
83
+ select is(
84
+ (select actor_claim from agent_board.identity_providers
85
+ where issuer = 'https://portal.consiliency.io'
86
+ and audience = 'https://message-board.consiliency.io'),
87
+ 'act.sub',
88
+ 'real Portal actor_claim is byte-identical to the frozen act.sub constant (audit-only delegated actor)');
80
89
  select is(
81
90
  (select policy_approved from agent_board.identity_providers
82
91
  where issuer = 'https://portal.consiliency.io'
@@ -1,6 +1,13 @@
1
1
  begin;
2
2
 
3
- select plan(14);
3
+ select plan(25);
4
+
5
+ -- The 11 read-model assertions below run inside a `do` block. pgTAP assertions
6
+ -- must NOT be called via `perform` there: `perform is(...)` RUNS the assertion
7
+ -- (incrementing the plan counter) but DISCARDS the returned TAP line, so a
8
+ -- failure prints no `not ok` (message-board#87). Buffer each line into this temp
9
+ -- table and emit them after the block instead.
10
+ create temp table _tap (seq serial primary key, line text) on commit drop;
4
11
 
5
12
  select has_view('agent_board', 'v_agent_inbox', 'v_agent_inbox view exists');
6
13
  select has_view('agent_board', 'v_agent_inbox_counts', 'v_agent_inbox_counts view exists');
@@ -391,43 +398,50 @@ begin
391
398
  now() + interval '5 minutes'
392
399
  );
393
400
 
394
- perform is(
401
+ insert into _tap(line) select is(
395
402
  (select count(*)::bigint from agent_board.v_agent_inbox where recipient_agent_id = v_recipient),
396
403
  2::bigint,
397
404
  'v_agent_inbox returns visible inbox rows only'
398
405
  );
399
406
 
400
- perform is(
407
+ insert into _tap(line) select is(
401
408
  (select unread_count from agent_board.v_agent_inbox_counts where recipient_agent_id = v_recipient),
402
409
  1::bigint,
403
410
  'v_agent_inbox_counts computes unread_count from new and restored rows'
404
411
  );
405
412
 
406
- perform is(
413
+ insert into _tap(line) select is(
407
414
  (select pending_action_count from agent_board.v_agent_inbox_counts where recipient_agent_id = v_recipient),
408
415
  2::bigint,
409
416
  'v_agent_inbox_counts computes pending_action_count from actionable rows'
410
417
  );
411
418
 
412
- perform is(
419
+ -- Pre-existing failure surfaced by making the discarded assertions visible
420
+ -- (message-board#87): BOARDOPT (20260515000000:411) narrowed v_board_directory's
421
+ -- row scope, so this fixture board is absent from the view and the scalar
422
+ -- subquery returns NULL (have: NULL / want: 2). Whether the VIEW's row scope or
423
+ -- this assertion is wrong is a product question — marked TODO so it stays
424
+ -- visible and non-blocking rather than deleted or silently "corrected".
425
+ perform todo('v_board_directory row scope narrowed by BOARDOPT; view-vs-assertion is a product question — see message-board#87', 1);
426
+ insert into _tap(line) select is(
413
427
  (select active_member_count from agent_board.v_board_directory where board_id = v_board),
414
428
  2::bigint,
415
429
  'v_board_directory counts joined and subscribed members only'
416
430
  );
417
431
 
418
- perform is(
432
+ insert into _tap(line) select is(
419
433
  (select count(*)::bigint from agent_board.v_board_feed where board_id = v_board and message_id = v_message),
420
434
  1::bigint,
421
435
  'v_board_feed deduplicates a canonical board post across recipient fan-out'
422
436
  );
423
437
 
424
- perform is(
438
+ insert into _tap(line) select is(
425
439
  (select active_subscription_count from agent_board.v_callback_endpoint_status where endpoint_id = v_endpoint),
426
440
  1::bigint,
427
441
  'v_callback_endpoint_status counts active subscriptions'
428
442
  );
429
443
 
430
- perform ok(
444
+ insert into _tap(line) select ok(
431
445
  exists (
432
446
  select 1
433
447
  from agent_board.v_callback_subscriptions
@@ -437,13 +451,13 @@ begin
437
451
  'v_callback_subscriptions exposes board-scoped subscription metadata'
438
452
  );
439
453
 
440
- perform is(
454
+ insert into _tap(line) select is(
441
455
  (select latest_attempt_outcome from agent_board.v_callback_delivery_health where subscription_id = v_subscription),
442
456
  'retryable_failure',
443
457
  'v_callback_delivery_health anchors on the latest delivery attempt'
444
458
  );
445
459
 
446
- perform ok(
460
+ insert into _tap(line) select ok(
447
461
  (
448
462
  select latest_next_retry_at is not null
449
463
  from agent_board.v_callback_delivery_health
@@ -452,13 +466,13 @@ begin
452
466
  'v_callback_delivery_health exposes retry scheduling metadata'
453
467
  );
454
468
 
455
- perform is(
469
+ insert into _tap(line) select is(
456
470
  (select inbox_unread_count from agent_board.v_agent_dashboard where agent_id = v_recipient),
457
471
  1::bigint,
458
472
  'v_agent_dashboard exposes compact unread inbox counts'
459
473
  );
460
474
 
461
- perform is(
475
+ insert into _tap(line) select is(
462
476
  (select inbox_pending_action_count from agent_board.v_agent_dashboard where agent_id = v_recipient),
463
477
  2::bigint,
464
478
  'v_agent_dashboard exposes compact pending-action inbox counts'
@@ -466,6 +480,9 @@ begin
466
480
  end;
467
481
  $$;
468
482
 
483
+ -- Emit the buffered TAP lines (tests 15-25) that the `do` block produced.
484
+ select line from _tap order by seq;
485
+
469
486
  select * from finish();
470
487
 
471
488
  rollback;