@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,1274 @@
1
+ -- MBROWID pgTAP: deterministic board-membership row identity.
2
+ --
3
+ -- This suite is intentionally written against the complete post-fix contract:
4
+ -- total-order ties and every current-membership reader must prefer an active row.
5
+ --
6
+ -- Load-bearing methodology:
7
+ -- * structural total-order proof strips SQL comments and scopes every counted
8
+ -- ORDER BY site to its source relation. Every created_at DESC occurrence in
9
+ -- each read statement must also be a created_at DESC, id DESC occurrence.
10
+ -- * equal explicit past created_at values are the named exception to the normal
11
+ -- distinct-timestamp fixture rule. Distinct timestamps never consult the UUID
12
+ -- tiebreak and would make the behavioral proof vacuous.
13
+ -- * UUID id DESC provides agreement, not temporal recency. Explicit IDs control
14
+ -- both arrangements below.
15
+ -- * every expected raise uses throws_ok; authenticated SECURITY DEFINER probes are
16
+ -- driven by JWT claims only and carry both required principal controls.
17
+ create extension if not exists pgtap with schema extensions;
18
+ set search_path = extensions, public, agent_board;
19
+
20
+ begin;
21
+
22
+ select plan(98);
23
+
24
+ create temp view _rowid_order_sites as
25
+ with defs as (
26
+ select
27
+ p.proname::text as proname,
28
+ pg_get_function_identity_arguments(p.oid) as args,
29
+ regexp_replace(
30
+ regexp_replace(
31
+ pg_get_functiondef(p.oid),
32
+ E'--[^\\n]*(\\n|$)',
33
+ E'\\n',
34
+ 'g'
35
+ ),
36
+ E'/\\*([^*]|\\*+[^*/])*\\*+/',
37
+ ' ',
38
+ 'g'
39
+ ) as def
40
+ from pg_proc p
41
+ join pg_namespace n on n.oid = p.pronamespace
42
+ where n.nspname = 'agent_board'
43
+ and p.prokind = 'f'
44
+ ),
45
+ statements as (
46
+ select
47
+ proname,
48
+ args,
49
+ statement_ordinality::int as statement_ordinality,
50
+ statement
51
+ from defs
52
+ cross join lateral regexp_split_to_table(def, ';')
53
+ with ordinality as split(statement, statement_ordinality)
54
+ )
55
+ select
56
+ proname,
57
+ args,
58
+ statement_ordinality,
59
+ case
60
+ when statement ~* 'from\s+agent_board\.board_memberships'
61
+ then 'board_memberships'
62
+ when statement ~* 'from\s+agent_board\.tasks'
63
+ then 'tasks'
64
+ when statement ~* 'from\s+agent_board\.messages'
65
+ then 'messages'
66
+ end as source_relation,
67
+ (select count(*)::int
68
+ from regexp_matches(statement, 'created_at\s+desc', 'gi')) as created_at_desc_count,
69
+ (select count(*)::int
70
+ from regexp_matches(
71
+ statement,
72
+ 'created_at\s+desc\s*,\s*(?:[a-z_][a-z_0-9]*\.)?id\s+desc',
73
+ 'gi'
74
+ )) as total_order_count
75
+ from statements
76
+ where statement ~* 'order\s+by.*created_at\s+desc'
77
+ and (
78
+ statement ~* 'from\s+agent_board\.board_memberships'
79
+ or (
80
+ proname = 'create_tasking_delivery'
81
+ and statement ~* 'from\s+agent_board\.tasks'
82
+ )
83
+ or (
84
+ proname = 'post_board_message'
85
+ and statement ~* 'from\s+agent_board\.messages'
86
+ )
87
+ );
88
+
89
+ create temp view _rowid_function_order_counts as
90
+ select
91
+ proname,
92
+ args,
93
+ sum(created_at_desc_count)::int as created_at_desc_count,
94
+ sum(total_order_count)::int as total_order_count
95
+ from _rowid_order_sites
96
+ group by proname, args;
97
+
98
+ create temp table _rowid_fixture (
99
+ removed_board uuid,
100
+ removed_creator uuid,
101
+ left_board uuid,
102
+ left_creator uuid,
103
+ no_row_board uuid,
104
+ no_row_creator uuid,
105
+ requested_board uuid,
106
+ requested_creator uuid,
107
+ invited_board uuid,
108
+ invited_creator uuid,
109
+ mixed_board uuid,
110
+ mixed_creator uuid,
111
+ writer_board uuid,
112
+ writer_owner uuid,
113
+ writer_owner_user uuid,
114
+ redeem_board uuid,
115
+ redeem_owner uuid,
116
+ redeem_removed uuid,
117
+ redeem_left uuid,
118
+ redeem_code text,
119
+ fed_board uuid,
120
+ fed_agent uuid,
121
+ fed_provider text,
122
+ standing_a_board uuid,
123
+ standing_a_owner uuid,
124
+ standing_a_agent uuid,
125
+ standing_a_user uuid,
126
+ standing_a_expected_id uuid,
127
+ standing_b_board uuid,
128
+ standing_b_owner uuid,
129
+ standing_b_agent uuid,
130
+ standing_b_user uuid,
131
+ standing_b_expected_id uuid,
132
+ active_a_board uuid,
133
+ active_a_owner uuid,
134
+ active_a_owner_user uuid,
135
+ active_a_agent uuid,
136
+ active_a_agent_user uuid,
137
+ active_a_expected_id uuid,
138
+ active_b_board uuid,
139
+ active_b_owner uuid,
140
+ active_b_agent uuid,
141
+ active_b_agent_user uuid,
142
+ active_b_expected_id uuid,
143
+ active_c_board uuid,
144
+ active_c_owner uuid,
145
+ active_c_agent uuid,
146
+ active_c_agent_user uuid,
147
+ active_c_expected_id uuid,
148
+ pending_board uuid,
149
+ pending_agent uuid
150
+ ) on commit drop;
151
+
152
+ do $$
153
+ declare
154
+ s text := replace(substr(gen_random_uuid()::text, 1, 8), '-', '');
155
+ v_removed_creator uuid := gen_random_uuid();
156
+ v_left_creator uuid := gen_random_uuid();
157
+ v_no_row_creator uuid := gen_random_uuid();
158
+ v_requested_creator uuid := gen_random_uuid();
159
+ v_invited_creator uuid := gen_random_uuid();
160
+ v_mixed_creator uuid := gen_random_uuid();
161
+ v_writer_owner uuid := gen_random_uuid();
162
+ v_writer_owner_user uuid;
163
+ v_redeem_owner uuid := gen_random_uuid();
164
+ v_redeem_removed uuid := gen_random_uuid();
165
+ v_redeem_left uuid := gen_random_uuid();
166
+ v_fed_owner uuid := gen_random_uuid();
167
+ v_fed_agent uuid := gen_random_uuid();
168
+ v_standing_a_owner uuid := gen_random_uuid();
169
+ v_standing_b_owner uuid := gen_random_uuid();
170
+ v_active_a_owner uuid := gen_random_uuid();
171
+ v_active_b_owner uuid := gen_random_uuid();
172
+ v_active_c_owner uuid := gen_random_uuid();
173
+ v_standing_a_agent uuid := '10000000-0000-4000-8000-000000000001';
174
+ v_standing_b_agent uuid := '10000000-0000-4000-8000-000000000002';
175
+ v_active_a_agent uuid := '10000000-0000-4000-8000-000000000003';
176
+ v_active_b_agent uuid := '10000000-0000-4000-8000-000000000004';
177
+ v_active_c_agent uuid := '10000000-0000-4000-8000-000000000005';
178
+ v_pending_agent uuid := '10000000-0000-4000-8000-000000000006';
179
+ v_standing_a_user uuid;
180
+ v_standing_b_user uuid;
181
+ v_active_a_owner_user uuid;
182
+ v_active_a_agent_user uuid;
183
+ v_active_b_agent_user uuid;
184
+ v_active_c_agent_user uuid;
185
+ v_removed_board uuid := gen_random_uuid();
186
+ v_left_board uuid;
187
+ v_no_row_board uuid := gen_random_uuid();
188
+ v_requested_board uuid := gen_random_uuid();
189
+ v_invited_board uuid := gen_random_uuid();
190
+ v_mixed_board uuid := gen_random_uuid();
191
+ v_writer_board uuid;
192
+ v_redeem_board uuid;
193
+ v_fed_board uuid;
194
+ v_standing_a_board uuid;
195
+ v_standing_b_board uuid;
196
+ v_active_a_board uuid;
197
+ v_active_b_board uuid;
198
+ v_active_c_board uuid;
199
+ v_pending_board uuid;
200
+ v_redeem_code text;
201
+ v_fed_provider text;
202
+ begin
203
+ v_writer_owner_user := public.ensure_github_user(
204
+ 'rowid-writer-' || s,
205
+ 'rowid-writer-' || s || '@example.test',
206
+ 'rowid-writer-' || s,
207
+ 'ROWID Writer'
208
+ );
209
+ v_standing_a_user := public.ensure_github_user(
210
+ 'rowid-standing-a-' || s,
211
+ 'rowid-standing-a-' || s || '@example.test',
212
+ 'rowid-standing-a-' || s,
213
+ 'ROWID Standing A'
214
+ );
215
+ v_standing_b_user := public.ensure_github_user(
216
+ 'rowid-standing-b-' || s,
217
+ 'rowid-standing-b-' || s || '@example.test',
218
+ 'rowid-standing-b-' || s,
219
+ 'ROWID Standing B'
220
+ );
221
+ v_active_a_owner_user := public.ensure_github_user(
222
+ 'rowid-active-owner-' || s,
223
+ 'rowid-active-owner-' || s || '@example.test',
224
+ 'rowid-active-owner-' || s,
225
+ 'ROWID Active Owner'
226
+ );
227
+ v_active_a_agent_user := public.ensure_github_user(
228
+ 'rowid-active-a-' || s,
229
+ 'rowid-active-a-' || s || '@example.test',
230
+ 'rowid-active-a-' || s,
231
+ 'ROWID Active A'
232
+ );
233
+ v_active_b_agent_user := public.ensure_github_user(
234
+ 'rowid-active-b-' || s,
235
+ 'rowid-active-b-' || s || '@example.test',
236
+ 'rowid-active-b-' || s,
237
+ 'ROWID Active B'
238
+ );
239
+ v_active_c_agent_user := public.ensure_github_user(
240
+ 'rowid-active-c-' || s,
241
+ 'rowid-active-c-' || s || '@example.test',
242
+ 'rowid-active-c-' || s,
243
+ 'ROWID Active C'
244
+ );
245
+
246
+ insert into agent_board.agents(
247
+ id, agent_type, handle, display_name, status, auth_user_id, owner_user_id
248
+ )
249
+ values
250
+ (v_removed_creator, 'agent', 'rowid-removed-' || s, 'ROWID Removed', 'active', null, null),
251
+ (v_left_creator, 'agent', 'rowid-left-' || s, 'ROWID Left', 'active', null, null),
252
+ (v_no_row_creator, 'agent', 'rowid-none-' || s, 'ROWID None', 'active', null, null),
253
+ (v_requested_creator, 'agent', 'rowid-requested-' || s, 'ROWID Requested', 'active', null, null),
254
+ (v_invited_creator, 'agent', 'rowid-invited-' || s, 'ROWID Invited', 'active', null, null),
255
+ (v_mixed_creator, 'agent', 'rowid-mixed-' || s, 'ROWID Mixed', 'active', null, null),
256
+ (v_writer_owner, 'agent', 'rowid-writer-' || s, 'ROWID Writer', 'active',
257
+ v_writer_owner_user, v_writer_owner_user),
258
+ (v_redeem_owner, 'agent', 'rowid-redeem-owner-' || s, 'ROWID Redeem Owner', 'active', null, null),
259
+ (v_redeem_removed, 'agent', 'rowid-redeem-removed-' || s, 'ROWID Redeem Removed', 'active', null, null),
260
+ (v_redeem_left, 'agent', 'rowid-redeem-left-' || s, 'ROWID Redeem Left', 'active', null, null),
261
+ (v_fed_owner, 'agent', 'rowid-fed-owner-' || s, 'ROWID Fed Owner', 'active', null, null),
262
+ (v_fed_agent, 'agent', 'rowid-fed-agent-' || s, 'ROWID Fed Agent', 'active', null, null),
263
+ (v_standing_a_owner, 'agent', 'rowid-standing-a-owner-' || s, 'ROWID Standing A Owner', 'active', null, null),
264
+ (v_standing_b_owner, 'agent', 'rowid-standing-b-owner-' || s, 'ROWID Standing B Owner', 'active', null, null),
265
+ (v_active_a_owner, 'agent', 'rowid-active-a-owner-' || s, 'ROWID Active A Owner', 'active',
266
+ v_active_a_owner_user, v_active_a_owner_user),
267
+ (v_active_b_owner, 'agent', 'rowid-active-b-owner-' || s, 'ROWID Active B Owner', 'active', null, null),
268
+ (v_active_c_owner, 'agent', 'rowid-active-c-owner-' || s, 'ROWID Active C Owner', 'active', null, null),
269
+ (v_standing_a_agent, 'agent', 'rowid-standing-a-agent-' || s, 'ROWID Standing A Agent', 'active',
270
+ v_standing_a_user, v_standing_a_user),
271
+ (v_standing_b_agent, 'agent', 'rowid-standing-b-agent-' || s, 'ROWID Standing B Agent', 'active',
272
+ v_standing_b_user, v_standing_b_user),
273
+ (v_active_a_agent, 'agent', 'rowid-active-a-agent-' || s, 'ROWID Active A Agent', 'active',
274
+ v_active_a_agent_user, v_active_a_agent_user),
275
+ (v_active_b_agent, 'agent', 'rowid-active-b-agent-' || s, 'ROWID Active B Agent', 'active',
276
+ v_active_b_agent_user, v_active_b_agent_user),
277
+ (v_active_c_agent, 'agent', 'rowid-active-c-agent-' || s, 'ROWID Active C Agent', 'active',
278
+ v_active_c_agent_user, v_active_c_agent_user),
279
+ (v_pending_agent, 'agent', 'rowid-pending-agent-' || s, 'ROWID Pending Agent', 'active', null, null);
280
+
281
+ -- Bootstrap matrix. left/removed rows satisfy board_memberships_state_left_at;
282
+ -- requested/invited rows carry NULL left_at.
283
+ insert into agent_board.boards(id, slug, name, created_by_agent_id, visibility)
284
+ values
285
+ (v_removed_board, 'rowid-bootstrap-removed-' || s, 'ROWID Bootstrap Removed', v_removed_creator, 'open'),
286
+ (v_no_row_board, 'rowid-bootstrap-none-' || s, 'ROWID Bootstrap None', v_no_row_creator, 'open'),
287
+ (v_requested_board, 'rowid-bootstrap-requested-' || s, 'ROWID Bootstrap Requested', v_requested_creator, 'open'),
288
+ (v_invited_board, 'rowid-bootstrap-invited-' || s, 'ROWID Bootstrap Invited', v_invited_creator, 'open'),
289
+ (v_mixed_board, 'rowid-bootstrap-mixed-' || s, 'ROWID Bootstrap Mixed', v_mixed_creator, 'open');
290
+
291
+ insert into agent_board.board_memberships(
292
+ board_id, agent_id, membership_state, role, created_by_agent_id, left_at, created_at
293
+ )
294
+ values
295
+ (v_removed_board, v_removed_creator, 'removed', 'owner', v_removed_creator,
296
+ timestamptz '2019-06-01', timestamptz '2019-06-01'),
297
+ (v_requested_board, v_requested_creator, 'requested', 'owner', v_requested_creator,
298
+ null, timestamptz '2019-06-02'),
299
+ (v_invited_board, v_invited_creator, 'invited', 'owner', v_invited_creator,
300
+ null, timestamptz '2019-06-03'),
301
+ (v_mixed_board, v_mixed_creator, 'removed', 'owner', v_mixed_creator,
302
+ timestamptz '2019-06-04', timestamptz '2019-06-04'),
303
+ (v_mixed_board, v_mixed_creator, 'left', 'owner', v_mixed_creator,
304
+ timestamptz '2020-06-04', timestamptz '2020-06-04');
305
+
306
+ -- The assertion-23 recovery fixture starts as a normal create_board owner seed.
307
+ select board_id into v_left_board
308
+ from agent_board.create_board(
309
+ v_left_creator, 'rowid-bootstrap-left-' || s, 'ROWID Bootstrap Left',
310
+ 'leave-again recovery', '{}'::jsonb, 'open'
311
+ );
312
+
313
+ -- Writer 1: a genuine create_board insert.
314
+ select board_id into v_writer_board
315
+ from agent_board.create_board(
316
+ v_writer_owner, 'rowid-writer-' || s, 'ROWID Writer',
317
+ 'create_board raw writer', '{}'::jsonb, 'private'
318
+ );
319
+
320
+ -- Writer 3: one removed and one left target sharing an unlimited member link.
321
+ select board_id into v_redeem_board
322
+ from agent_board.create_board(
323
+ v_redeem_owner, 'rowid-redeem-' || s, 'ROWID Redeem',
324
+ 'invite redemption raw writer', '{}'::jsonb, 'private'
325
+ );
326
+ insert into agent_board.board_memberships(
327
+ board_id, agent_id, membership_state, role, created_by_agent_id, left_at, created_at
328
+ )
329
+ values
330
+ (v_redeem_board, v_redeem_removed, 'removed', 'member', v_redeem_owner,
331
+ timestamptz '2019-07-01', timestamptz '2019-07-01'),
332
+ (v_redeem_board, v_redeem_left, 'left', 'member', v_redeem_owner,
333
+ timestamptz '2019-07-02', timestamptz '2019-07-02');
334
+ select code into v_redeem_code
335
+ from agent_board.create_invite_link(
336
+ v_redeem_board, 'member', 'joined', null, null, v_redeem_owner, '{}'::jsonb
337
+ );
338
+
339
+ -- Writer 4: any pre-existing row must suppress FEDSESSION auto-join.
340
+ select board_id into v_fed_board
341
+ from agent_board.create_board(
342
+ v_fed_owner, 'rowid-fed-' || s, 'ROWID Fed',
343
+ 'fedsession raw writer', '{}'::jsonb, 'private'
344
+ );
345
+ insert into agent_board.board_memberships(
346
+ board_id, agent_id, membership_state, role, created_by_agent_id, created_at
347
+ )
348
+ values (
349
+ v_fed_board, v_fed_agent, 'invited', 'member', v_fed_owner,
350
+ timestamptz '2019-08-01'
351
+ );
352
+ insert into agent_board.identity_providers(
353
+ issuer, audience, supported, policy_approved, allowed_agent_kinds, default_board_id
354
+ )
355
+ values (
356
+ 'https://rowid-fed-' || s || '.test', 'message-board', true, true,
357
+ '{human}', v_fed_board
358
+ )
359
+ returning id into v_fed_provider;
360
+
361
+ -- Equal-timestamp standing arrangements. Both pairs are removed+left, so
362
+ -- R-LINK-7 says "removed" regardless of which UUID sorts higher.
363
+ select board_id into v_standing_a_board
364
+ from agent_board.create_board(
365
+ v_standing_a_owner, 'rowid-standing-a-' || s, 'ROWID Standing A',
366
+ 'removed id high', '{}'::jsonb, 'open'
367
+ );
368
+ select board_id into v_standing_b_board
369
+ from agent_board.create_board(
370
+ v_standing_b_owner, 'rowid-standing-b-' || s, 'ROWID Standing B',
371
+ 'left id high', '{}'::jsonb, 'open'
372
+ );
373
+
374
+ -- A: low LEFT is inserted first; high REMOVED must win the total order.
375
+ insert into agent_board.board_memberships(
376
+ id, board_id, agent_id, membership_state, role, created_by_agent_id, left_at, created_at
377
+ )
378
+ values
379
+ ('00000000-0000-4000-8000-000000000101', v_standing_a_board, v_standing_a_agent,
380
+ 'left', 'admin', v_standing_a_owner, timestamptz '2020-01-01', timestamptz '2020-01-01'),
381
+ ('ffffffff-ffff-4fff-8fff-000000000101', v_standing_a_board, v_standing_a_agent,
382
+ 'removed', 'admin', v_standing_a_owner, timestamptz '2020-01-01', timestamptz '2020-01-01');
383
+
384
+ -- B: low REMOVED is inserted first; high LEFT must win the total order, while
385
+ -- the R-LINK-7 set predicate must still preserve removed standing.
386
+ insert into agent_board.board_memberships(
387
+ id, board_id, agent_id, membership_state, role, created_by_agent_id, left_at, created_at
388
+ )
389
+ values
390
+ ('00000000-0000-4000-8000-000000000102', v_standing_b_board, v_standing_b_agent,
391
+ 'removed', 'admin', v_standing_b_owner, timestamptz '2020-01-01', timestamptz '2020-01-01'),
392
+ ('ffffffff-ffff-4fff-8fff-000000000102', v_standing_b_board, v_standing_b_agent,
393
+ 'left', 'admin', v_standing_b_owner, timestamptz '2020-01-01', timestamptz '2020-01-01');
394
+
395
+ -- EC-MBROWID-3 exact duplicate shape: removed+active at equal created_at.
396
+ -- The active row is the current standing under R-LINK-7 in BOTH ID arrangements.
397
+ select board_id into v_active_a_board
398
+ from agent_board.create_board(
399
+ v_active_a_owner, 'rowid-active-a-' || s, 'ROWID Active A',
400
+ 'active id high', '{}'::jsonb, 'private'
401
+ );
402
+ select board_id into v_active_b_board
403
+ from agent_board.create_board(
404
+ v_active_b_owner, 'rowid-active-b-' || s, 'ROWID Active B',
405
+ 'removed id high', '{}'::jsonb, 'private'
406
+ );
407
+ select board_id into v_active_c_board
408
+ from agent_board.create_board(
409
+ v_active_c_owner, 'rowid-active-c-' || s, 'ROWID Active C',
410
+ 'removed id high invite-only', '{}'::jsonb, 'invite_only'
411
+ );
412
+ select board_id into v_pending_board
413
+ from agent_board.create_board(
414
+ v_active_a_owner, 'rowid-pending-' || s, 'ROWID Pending',
415
+ 'active standing excludes stale pending rows', '{}'::jsonb, 'private'
416
+ );
417
+
418
+ -- A: low REMOVED inserted before high ACTIVE.
419
+ insert into agent_board.board_memberships(
420
+ id, board_id, agent_id, membership_state, role, created_by_agent_id, left_at, created_at
421
+ )
422
+ values
423
+ ('00000000-0000-4000-8000-000000000201', v_active_a_board, v_active_a_agent,
424
+ 'removed', 'observer', v_active_a_owner, timestamptz '2020-01-01', timestamptz '2020-01-01'),
425
+ ('ffffffff-ffff-4fff-8fff-000000000201', v_active_a_board, v_active_a_agent,
426
+ 'joined', 'admin', v_active_a_owner, null, timestamptz '2020-01-01');
427
+
428
+ -- B: low ACTIVE inserted before high REMOVED.
429
+ insert into agent_board.board_memberships(
430
+ id, board_id, agent_id, membership_state, role, created_by_agent_id, left_at, created_at
431
+ )
432
+ values
433
+ ('00000000-0000-4000-8000-000000000202', v_active_b_board, v_active_b_agent,
434
+ 'joined', 'admin', v_active_b_owner, null, timestamptz '2020-01-01'),
435
+ ('ffffffff-ffff-4fff-8fff-000000000202', v_active_b_board, v_active_b_agent,
436
+ 'removed', 'observer', v_active_b_owner, timestamptz '2020-01-01', timestamptz '2020-01-01');
437
+
438
+ -- C: the same low-ACTIVE/high-REMOVED shape on invite_only joinability.
439
+ insert into agent_board.board_memberships(
440
+ id, board_id, agent_id, membership_state, role, created_by_agent_id, left_at, created_at
441
+ )
442
+ values
443
+ ('00000000-0000-4000-8000-000000000203', v_active_c_board, v_active_c_agent,
444
+ 'joined', 'member', v_active_c_owner, null, timestamptz '2020-01-01'),
445
+ ('ffffffff-ffff-4fff-8fff-000000000203', v_active_c_board, v_active_c_agent,
446
+ 'removed', 'observer', v_active_c_owner, timestamptz '2020-01-01', timestamptz '2020-01-01');
447
+
448
+ -- Pending-list duplicate: ACTIVE is lowest, REMOVED is higher, and a stale
449
+ -- REQUESTED row is highest. A bare UUID order exposes the stale request;
450
+ -- active-preferring selection excludes the agent from the pending list.
451
+ insert into agent_board.board_memberships(
452
+ id, board_id, agent_id, membership_state, role, created_by_agent_id, left_at, created_at
453
+ )
454
+ values
455
+ ('00000000-0000-4000-8000-000000000204', v_pending_board, v_pending_agent,
456
+ 'joined', 'member', v_active_a_owner, null, timestamptz '2020-01-01'),
457
+ ('eeeeeeee-eeee-4eee-8eee-000000000204', v_pending_board, v_pending_agent,
458
+ 'removed', 'member', v_active_a_owner, timestamptz '2020-01-01', timestamptz '2020-01-01'),
459
+ ('ffffffff-ffff-4fff-8fff-000000000204', v_pending_board, v_pending_agent,
460
+ 'requested', 'member', v_active_a_owner, null, timestamptz '2020-01-01');
461
+
462
+ insert into _rowid_fixture values (
463
+ v_removed_board, v_removed_creator,
464
+ v_left_board, v_left_creator,
465
+ v_no_row_board, v_no_row_creator,
466
+ v_requested_board, v_requested_creator,
467
+ v_invited_board, v_invited_creator,
468
+ v_mixed_board, v_mixed_creator,
469
+ v_writer_board, v_writer_owner, v_writer_owner_user,
470
+ v_redeem_board, v_redeem_owner, v_redeem_removed, v_redeem_left, v_redeem_code,
471
+ v_fed_board, v_fed_agent, v_fed_provider,
472
+ v_standing_a_board, v_standing_a_owner, v_standing_a_agent, v_standing_a_user,
473
+ 'ffffffff-ffff-4fff-8fff-000000000101',
474
+ v_standing_b_board, v_standing_b_owner, v_standing_b_agent, v_standing_b_user,
475
+ 'ffffffff-ffff-4fff-8fff-000000000102',
476
+ v_active_a_board, v_active_a_owner, v_active_a_owner_user,
477
+ v_active_a_agent, v_active_a_agent_user, 'ffffffff-ffff-4fff-8fff-000000000201',
478
+ v_active_b_board, v_active_b_owner, v_active_b_agent, v_active_b_agent_user,
479
+ '00000000-0000-4000-8000-000000000202',
480
+ v_active_c_board, v_active_c_owner, v_active_c_agent, v_active_c_agent_user,
481
+ '00000000-0000-4000-8000-000000000203',
482
+ v_pending_board, v_pending_agent
483
+ );
484
+ end
485
+ $$;
486
+
487
+ -- 1. Initial service-role posture control.
488
+ select set_config('request.jwt.claim.role', '', true);
489
+ select set_config('request.jwt.claim.sub', '', true);
490
+ select is(
491
+ agent_board._is_service_role(),
492
+ true,
493
+ 'control: fixture and structural block really runs as service_role');
494
+
495
+ -- 2. Catalog-derived inventory: seven membership readers plus the two task/message idempotency
496
+ -- readers. request_join_board joined the set in MBCONSENT-B(iii): its Option-3 active-member
497
+ -- no-op reads board_memberships with the created_at DESC + id DESC tiebreak like the others.
498
+ select is(
499
+ (select array_agg(proname order by proname)
500
+ from _rowid_function_order_counts),
501
+ array[
502
+ '_active_board_membership_id',
503
+ '_upsert_board_membership_state',
504
+ 'create_tasking_delivery',
505
+ 'join_board',
506
+ 'list_board_pending_memberships',
507
+ 'post_board_message',
508
+ 'request_join_board',
509
+ 'resolve_board_request',
510
+ 'update_board_membership'
511
+ ]::text[],
512
+ 'live catalog exposes seven membership readers plus two task/message idempotency readers');
513
+
514
+ -- 3-10. COUNT-EQUALITY, never substring containment.
515
+ select is(
516
+ (select created_at_desc_count from _rowid_function_order_counts
517
+ where proname = '_active_board_membership_id'),
518
+ (select total_order_count from _rowid_function_order_counts
519
+ where proname = '_active_board_membership_id'),
520
+ '_active_board_membership_id: every created_at DESC site has the id DESC tiebreak');
521
+ select is(
522
+ (select created_at_desc_count from _rowid_function_order_counts
523
+ where proname = '_upsert_board_membership_state'),
524
+ (select total_order_count from _rowid_function_order_counts
525
+ where proname = '_upsert_board_membership_state'),
526
+ '_upsert_board_membership_state: every created_at DESC site has the id DESC tiebreak');
527
+ select is(
528
+ (select created_at_desc_count from _rowid_function_order_counts
529
+ where proname = 'create_tasking_delivery'),
530
+ (select total_order_count from _rowid_function_order_counts
531
+ where proname = 'create_tasking_delivery'),
532
+ 'create_tasking_delivery: its tasks idempotency read has the id DESC tiebreak');
533
+ select is(
534
+ (select created_at_desc_count from _rowid_function_order_counts
535
+ where proname = 'join_board'),
536
+ (select total_order_count from _rowid_function_order_counts
537
+ where proname = 'join_board'),
538
+ 'join_board: every created_at DESC site has the id DESC tiebreak');
539
+ select is(
540
+ (select created_at_desc_count from _rowid_function_order_counts
541
+ where proname = 'list_board_pending_memberships'),
542
+ (select total_order_count from _rowid_function_order_counts
543
+ where proname = 'list_board_pending_memberships'),
544
+ 'list_board_pending_memberships: BOTH created_at DESC sites have the id DESC tiebreak');
545
+ select is(
546
+ (select created_at_desc_count from _rowid_function_order_counts
547
+ where proname = 'post_board_message'),
548
+ (select total_order_count from _rowid_function_order_counts
549
+ where proname = 'post_board_message'),
550
+ 'post_board_message: its messages idempotency read has the id DESC tiebreak');
551
+ select is(
552
+ (select created_at_desc_count from _rowid_function_order_counts
553
+ where proname = 'resolve_board_request'),
554
+ (select total_order_count from _rowid_function_order_counts
555
+ where proname = 'resolve_board_request'),
556
+ 'resolve_board_request: every created_at DESC site has the id DESC tiebreak');
557
+ select is(
558
+ (select created_at_desc_count from _rowid_function_order_counts
559
+ where proname = 'update_board_membership'),
560
+ (select total_order_count from _rowid_function_order_counts
561
+ where proname = 'update_board_membership'),
562
+ 'update_board_membership: BOTH created_at DESC sites have the id DESC tiebreak');
563
+
564
+ -- 11. The live view is catalog-derived too; its last definer uses DROP + CREATE.
565
+ with d as (
566
+ select pg_get_viewdef(c.oid, true) as def
567
+ from pg_class c
568
+ join pg_namespace n on n.oid = c.relnamespace
569
+ where n.nspname = 'agent_board'
570
+ and c.relname = 'v_board_directory'
571
+ and c.relkind = 'v'
572
+ )
573
+ select is(
574
+ (select count(*)::int from d, regexp_matches(def, 'created_at\s+desc', 'gi')),
575
+ (select count(*)::int
576
+ from d, regexp_matches(
577
+ def,
578
+ 'created_at\s+desc\s*,\s*(?:[a-z_][a-z_0-9]*\.)?id\s+desc',
579
+ 'gi'
580
+ )),
581
+ 'v_board_directory live catalog definition gives its membership read the id DESC tiebreak');
582
+
583
+ -- 12. EC-MBROWID-3 requires set-based removed standing, not one selected row.
584
+ select ok(
585
+ (select regexp_replace(pg_get_functiondef(p.oid), '\s+', ' ', 'g') ~*
586
+ 'exists\s*\(\s*select\s+1\s+from\s+agent_board\.board_memberships.*membership_state\s*=\s*''removed''.*\)\s+and\s+not\s+exists\s*\(\s*select\s+1\s+from\s+agent_board\.board_memberships.*membership_state\s+in\s*\(\s*''joined''\s*,\s*''subscribed''\s*\)'
587
+ from pg_proc p
588
+ join pg_namespace n on n.oid = p.pronamespace
589
+ where n.nspname = 'agent_board'
590
+ and p.proname = '_upsert_board_membership_state'
591
+ limit 1),
592
+ '_upsert_board_membership_state carries the R-LINK-7 removed-and-no-active set predicate');
593
+
594
+ -- 13. Bootstrap must carry the same R-LINK-7 shape.
595
+ select ok(
596
+ (select regexp_replace(pg_get_functiondef(p.oid), '\s+', ' ', 'g') ~*
597
+ 'not\s+exists\s*\(\s*select\s+1\s+from\s+agent_board\.board_memberships.*membership_state\s+in\s*\(\s*''joined''\s*,\s*''subscribed''\s*\).*\)\s+and\s+not\s+exists\s*\(\s*select\s+1\s+from\s+agent_board\.board_memberships.*membership_state\s*=\s*''removed'''
598
+ from pg_proc p
599
+ join pg_namespace n on n.oid = p.pronamespace
600
+ where n.nspname = 'agent_board'
601
+ and p.proname = 'bootstrap_board_owner_memberships'
602
+ limit 1),
603
+ 'bootstrap_board_owner_memberships guard is no-active AND no-removed, not any-row');
604
+
605
+ -- 14. MBROWID does not change the API/read-model schema version.
606
+ select is(agent_board.get_schema_version(), 8, 'agent_board schema_version remains 8');
607
+
608
+ -- 15-19. ALLOW: preserve assertion-23 leave-again recovery end to end.
609
+ select lives_ok(
610
+ (select format(
611
+ $q$select agent_board.update_board_membership(%L::uuid, %L::uuid, 'left')$q$,
612
+ left_board, left_creator
613
+ ) from _rowid_fixture),
614
+ 'left recovery: creator can leave the first time');
615
+ select lives_ok(
616
+ (select format(
617
+ $q$select agent_board.join_board(%L::uuid, %L::uuid, 'joined')$q$,
618
+ left_board, left_creator
619
+ ) from _rowid_fixture),
620
+ 'left recovery: creator can rejoin before the recovery repro');
621
+ select is(
622
+ (select role
623
+ from agent_board.board_memberships bm
624
+ join _rowid_fixture f
625
+ on bm.board_id = f.left_board and bm.agent_id = f.left_creator
626
+ where bm.membership_state in ('joined', 'subscribed')),
627
+ 'member',
628
+ 'left recovery: ROLERESET rejoin clamps the sole creator to member');
629
+ select lives_ok(
630
+ (select format(
631
+ $q$select agent_board.update_board_membership(%L::uuid, %L::uuid, 'left')$q$,
632
+ left_board, left_creator
633
+ ) from _rowid_fixture),
634
+ 'left recovery: creator can leave again before bootstrap');
635
+ select lives_ok(
636
+ $$select agent_board.bootstrap_board_owner_memberships()$$,
637
+ 'bootstrap service-role sweep runs');
638
+
639
+ -- 20-21. DENY: a removed creator is not resurrected.
640
+ select is(
641
+ (select count(*)::int
642
+ from agent_board.board_memberships bm
643
+ join _rowid_fixture f
644
+ on bm.board_id = f.removed_board and bm.agent_id = f.removed_creator),
645
+ 1,
646
+ 'removed creator keeps exactly one row after bootstrap (no resurrection duplicate)');
647
+ select is(
648
+ (select count(*)::int
649
+ from agent_board.board_memberships bm
650
+ join _rowid_fixture f
651
+ on bm.board_id = f.removed_board and bm.agent_id = f.removed_creator
652
+ where bm.membership_state in ('joined', 'subscribed')),
653
+ 0,
654
+ 'removed creator keeps zero active rows after bootstrap');
655
+
656
+ -- 22-25. ALLOW: left recovery and no-row bootstrap purpose remain intact.
657
+ select is(
658
+ (select count(*)::int
659
+ from agent_board.board_memberships bm
660
+ join _rowid_fixture f
661
+ on bm.board_id = f.left_board and bm.agent_id = f.left_creator),
662
+ 2,
663
+ 'left creator recovery produces the normal historical-left plus active pair');
664
+ select is(
665
+ (select count(*)::int
666
+ from agent_board.board_memberships bm
667
+ join _rowid_fixture f
668
+ on bm.board_id = f.left_board and bm.agent_id = f.left_creator
669
+ where bm.membership_state in ('joined', 'subscribed') and bm.role = 'owner'),
670
+ 1,
671
+ 'left creator recovery restores exactly one active owner');
672
+ select is(
673
+ (select count(*)::int
674
+ from agent_board.board_memberships bm
675
+ join _rowid_fixture f
676
+ on bm.board_id = f.no_row_board and bm.agent_id = f.no_row_creator),
677
+ 1,
678
+ 'no-row creator gets exactly one membership from bootstrap');
679
+ select is(
680
+ (select count(*)::int
681
+ from agent_board.board_memberships bm
682
+ join _rowid_fixture f
683
+ on bm.board_id = f.no_row_board and bm.agent_id = f.no_row_creator
684
+ where bm.membership_state = 'joined' and bm.role = 'owner'),
685
+ 1,
686
+ 'no-row creator is still seeded as joined owner');
687
+
688
+ -- 26-29. The plan rules clearly: requested-only and invited-only still re-seed.
689
+ select is(
690
+ (select count(*)::int
691
+ from agent_board.board_memberships bm
692
+ join _rowid_fixture f
693
+ on bm.board_id = f.requested_board and bm.agent_id = f.requested_creator),
694
+ 2,
695
+ 'requested-only creator is re-seeded (R-LINK-7 is not an any-row guard)');
696
+ select is(
697
+ (select count(*)::int
698
+ from agent_board.board_memberships bm
699
+ join _rowid_fixture f
700
+ on bm.board_id = f.requested_board and bm.agent_id = f.requested_creator
701
+ where bm.membership_state in ('joined', 'subscribed')),
702
+ 1,
703
+ 'requested-only creator gets exactly one active row, never a second active row');
704
+ select is(
705
+ (select count(*)::int
706
+ from agent_board.board_memberships bm
707
+ join _rowid_fixture f
708
+ on bm.board_id = f.invited_board and bm.agent_id = f.invited_creator),
709
+ 2,
710
+ 'invited-only creator is re-seeded (R-LINK-7 is not an any-row guard)');
711
+ select is(
712
+ (select count(*)::int
713
+ from agent_board.board_memberships bm
714
+ join _rowid_fixture f
715
+ on bm.board_id = f.invited_board and bm.agent_id = f.invited_creator
716
+ where bm.membership_state in ('joined', 'subscribed')),
717
+ 1,
718
+ 'invited-only creator gets exactly one active row, never a second active row');
719
+
720
+ -- 30-31. Historical removed+left is still removed standing and is suppressed.
721
+ select is(
722
+ (select count(*)::int
723
+ from agent_board.board_memberships bm
724
+ join _rowid_fixture f
725
+ on bm.board_id = f.mixed_board and bm.agent_id = f.mixed_creator),
726
+ 2,
727
+ 'removed+left creator pair gets no third bootstrap row');
728
+ select is(
729
+ (select count(*)::int
730
+ from agent_board.board_memberships bm
731
+ join _rowid_fixture f
732
+ on bm.board_id = f.mixed_board and bm.agent_id = f.mixed_creator
733
+ where bm.membership_state in ('joined', 'subscribed')),
734
+ 0,
735
+ 'removed+left creator pair stays non-active');
736
+
737
+ -- 32-33. Writer 1 kept raw: genuine create_board cannot duplicate its owner seed.
738
+ select is(
739
+ (select count(*)::int
740
+ from agent_board.board_memberships bm
741
+ join _rowid_fixture f
742
+ on bm.board_id = f.writer_board and bm.agent_id = f.writer_owner),
743
+ 1,
744
+ 'create_board fresh-board writer creates exactly one membership row');
745
+ select is(
746
+ (select count(*)::int
747
+ from agent_board.board_memberships bm
748
+ join _rowid_fixture f
749
+ on bm.board_id = f.writer_board and bm.agent_id = f.writer_owner
750
+ where bm.membership_state in ('joined', 'subscribed') and bm.role = 'owner'),
751
+ 1,
752
+ 'create_board fresh-board writer creates exactly one active owner row');
753
+
754
+ -- 34-36. DENY twin for bootstrap, with both authenticated controls.
755
+ select set_config('request.jwt.claim.role', 'authenticated', true);
756
+ select set_config(
757
+ 'request.jwt.claim.sub',
758
+ (select writer_owner_user from _rowid_fixture)::text,
759
+ true
760
+ );
761
+ select is(
762
+ agent_board._is_service_role(),
763
+ false,
764
+ 'control: authenticated bootstrap block is not service_role');
765
+ select is(
766
+ agent_board.current_agent_id(),
767
+ (select writer_owner from _rowid_fixture),
768
+ 'control: authenticated bootstrap actor resolves to the expected agent');
769
+ select throws_ok(
770
+ $$select agent_board.bootstrap_board_owner_memberships()$$,
771
+ '42501',
772
+ 'bootstrap_board_owner_memberships is service_role-only',
773
+ 'authenticated caller cannot invoke the bootstrap repair writer');
774
+
775
+ -- 37. Back to service role before internal raw-writer probes.
776
+ select set_config('request.jwt.claim.role', '', true);
777
+ select set_config('request.jwt.claim.sub', '', true);
778
+ select is(
779
+ agent_board._is_service_role(),
780
+ true,
781
+ 'control: invite and FEDSESSION writer block really runs as service_role');
782
+
783
+ -- 38-44. Writer 3 kept raw: removed DENY plus left ALLOW, never two active rows.
784
+ select throws_ok(
785
+ (select format(
786
+ $q$select agent_board._redeem_invite_for_agent(%L, %L::uuid)$q$,
787
+ redeem_code, redeem_removed
788
+ ) from _rowid_fixture),
789
+ '42501',
790
+ 'invite is not usable',
791
+ 'invite redemption refuses a removed agent via R-LINK-7');
792
+ select is(
793
+ (select count(*)::int
794
+ from agent_board.board_memberships bm
795
+ join _rowid_fixture f
796
+ on bm.board_id = f.redeem_board and bm.agent_id = f.redeem_removed),
797
+ 1,
798
+ 'refused removed redemption leaves exactly the original row');
799
+ select is(
800
+ (select count(*)::int
801
+ from agent_board.board_memberships bm
802
+ join _rowid_fixture f
803
+ on bm.board_id = f.redeem_board and bm.agent_id = f.redeem_removed
804
+ where bm.membership_state in ('joined', 'subscribed')),
805
+ 0,
806
+ 'refused removed redemption leaves zero active rows');
807
+ select lives_ok(
808
+ (select format(
809
+ $q$select agent_board._redeem_invite_for_agent(%L, %L::uuid)$q$,
810
+ redeem_code, redeem_left
811
+ ) from _rowid_fixture),
812
+ 'invite redemption still admits a voluntarily-left agent');
813
+ select is(
814
+ (select count(*)::int
815
+ from agent_board.board_memberships bm
816
+ join _rowid_fixture f
817
+ on bm.board_id = f.redeem_board and bm.agent_id = f.redeem_left),
818
+ 2,
819
+ 'left redemption produces the normal historical-left plus active pair');
820
+ select is(
821
+ (select count(*)::int
822
+ from agent_board.board_memberships bm
823
+ join _rowid_fixture f
824
+ on bm.board_id = f.redeem_board and bm.agent_id = f.redeem_left
825
+ where bm.membership_state in ('joined', 'subscribed')),
826
+ 1,
827
+ 'left redemption creates exactly one active row, never a second active row');
828
+ select is(
829
+ (select role
830
+ from agent_board.board_memberships bm
831
+ join _rowid_fixture f
832
+ on bm.board_id = f.redeem_board and bm.agent_id = f.redeem_left
833
+ where bm.membership_state in ('joined', 'subscribed')),
834
+ 'member',
835
+ 'left redemption preserves the member preset');
836
+
837
+ -- 45-47. Writer 4 kept raw: any pre-existing row suppresses auto-join.
838
+ select lives_ok(
839
+ (select format(
840
+ $q$select agent_board._fedsession_autojoin_default_board(p, %L::uuid)
841
+ from agent_board.identity_providers p where p.id = %L$q$,
842
+ fed_agent, fed_provider
843
+ ) from _rowid_fixture),
844
+ 'FEDSESSION auto-join helper treats a pre-existing row as a non-fatal skip');
845
+ select is(
846
+ (select count(*)::int
847
+ from agent_board.board_memberships bm
848
+ join _rowid_fixture f
849
+ on bm.board_id = f.fed_board and bm.agent_id = f.fed_agent),
850
+ 1,
851
+ 'FEDSESSION any-row guard leaves exactly the pre-existing row');
852
+ select is(
853
+ (select count(*)::int
854
+ from agent_board.board_memberships bm
855
+ join _rowid_fixture f
856
+ on bm.board_id = f.fed_board and bm.agent_id = f.fed_agent
857
+ where bm.membership_state in ('joined', 'subscribed')),
858
+ 0,
859
+ 'FEDSESSION any-row guard creates no active duplicate');
860
+
861
+ -- 48-53. Joinability agrees with R-LINK-7 removed standing in both UUID arrangements.
862
+ select set_config('request.jwt.claim.role', 'authenticated', true);
863
+ select set_config(
864
+ 'request.jwt.claim.sub',
865
+ (select standing_a_user from _rowid_fixture)::text,
866
+ true
867
+ );
868
+ select is(
869
+ agent_board._is_service_role(),
870
+ false,
871
+ 'control: standing arrangement A joinability block is not service_role');
872
+ select is(
873
+ agent_board.current_agent_id(),
874
+ (select standing_a_agent from _rowid_fixture),
875
+ 'control: standing arrangement A resolves the expected actor');
876
+ select throws_ok(
877
+ (select format(
878
+ $q$select agent_board.join_board(%L::uuid, %L::uuid, 'joined')$q$,
879
+ standing_a_board, standing_a_agent
880
+ ) from _rowid_fixture),
881
+ 'P0002',
882
+ 'board is not joinable by this agent',
883
+ 'joinability reads removed standing when the removed row has the higher UUID');
884
+ -- Restore the pair if current main incorrectly admitted the LEFT row.
885
+ update agent_board.board_memberships bm
886
+ set membership_state = case
887
+ when bm.id = '00000000-0000-4000-8000-000000000101' then 'left'
888
+ else 'removed'
889
+ end,
890
+ role = 'admin',
891
+ left_at = timestamptz '2020-01-01'
892
+ from _rowid_fixture f
893
+ where bm.board_id = f.standing_a_board
894
+ and bm.agent_id = f.standing_a_agent;
895
+
896
+ select set_config(
897
+ 'request.jwt.claim.sub',
898
+ (select standing_b_user from _rowid_fixture)::text,
899
+ true
900
+ );
901
+ select is(
902
+ agent_board._is_service_role(),
903
+ false,
904
+ 'control: standing arrangement B joinability block is not service_role');
905
+ select is(
906
+ agent_board.current_agent_id(),
907
+ (select standing_b_agent from _rowid_fixture),
908
+ 'control: standing arrangement B resolves the expected actor');
909
+ select throws_ok(
910
+ (select format(
911
+ $q$select agent_board.join_board(%L::uuid, %L::uuid, 'joined')$q$,
912
+ standing_b_board, standing_b_agent
913
+ ) from _rowid_fixture),
914
+ 'P0002',
915
+ 'board is not joinable by this agent',
916
+ 'joinability keeps removed standing when the left row has the higher UUID');
917
+ update agent_board.board_memberships bm
918
+ set membership_state = case
919
+ when bm.id = '00000000-0000-4000-8000-000000000102' then 'removed'
920
+ else 'left'
921
+ end,
922
+ role = 'admin',
923
+ left_at = timestamptz '2020-01-01'
924
+ from _rowid_fixture f
925
+ where bm.board_id = f.standing_b_board
926
+ and bm.agent_id = f.standing_b_agent;
927
+
928
+ -- 54. Service-role control for grant and mutator standing probes.
929
+ select set_config('request.jwt.claim.role', '', true);
930
+ select set_config('request.jwt.claim.sub', '', true);
931
+ select is(
932
+ agent_board._is_service_role(),
933
+ true,
934
+ 'control: grant and mutator standing block really runs as service_role');
935
+
936
+ -- 55-56. grant_board_role agrees: neither removed+left pair is active.
937
+ select throws_ok(
938
+ (select format(
939
+ $q$select agent_board.grant_board_role(%L::uuid, %L::uuid, 'member', %L::uuid)$q$,
940
+ standing_a_board, standing_a_agent, standing_a_owner
941
+ ) from _rowid_fixture),
942
+ 'P0002',
943
+ 'grantee has no membership on this board',
944
+ 'grant_board_role rejects arrangement A because removed+left has no active row');
945
+ select throws_ok(
946
+ (select format(
947
+ $q$select agent_board.grant_board_role(%L::uuid, %L::uuid, 'member', %L::uuid)$q$,
948
+ standing_b_board, standing_b_agent, standing_b_owner
949
+ ) from _rowid_fixture),
950
+ 'P0002',
951
+ 'grantee has no membership on this board',
952
+ 'grant_board_role rejects arrangement B because removed+left has no active row');
953
+
954
+ -- 57-64. Mutator agreement + behavioral total-order proof in both arrangements.
955
+ select lives_ok(
956
+ (select format(
957
+ $q$select agent_board._upsert_board_membership_state(%L::uuid, %L::uuid, 'joined', %L::uuid)$q$,
958
+ standing_a_board, standing_a_agent, standing_a_owner
959
+ ) from _rowid_fixture),
960
+ 'mutator re-entry succeeds for arrangement A');
961
+ select is(
962
+ (select bm.id
963
+ from agent_board.board_memberships bm
964
+ join _rowid_fixture f
965
+ on bm.board_id = f.standing_a_board and bm.agent_id = f.standing_a_agent
966
+ where bm.membership_state in ('joined', 'subscribed')),
967
+ (select standing_a_expected_id from _rowid_fixture),
968
+ 'arrangement A mutator updates the explicit higher UUID row');
969
+ select is(
970
+ (select count(*)::int
971
+ from agent_board.board_memberships bm
972
+ join _rowid_fixture f
973
+ on bm.board_id = f.standing_a_board and bm.agent_id = f.standing_a_agent
974
+ where bm.membership_state in ('joined', 'subscribed')),
975
+ 1,
976
+ 'arrangement A mutator leaves exactly one active row');
977
+ select is(
978
+ (select role
979
+ from agent_board.board_memberships bm
980
+ join _rowid_fixture f
981
+ on bm.board_id = f.standing_a_board and bm.agent_id = f.standing_a_agent
982
+ where bm.membership_state in ('joined', 'subscribed')),
983
+ 'member',
984
+ 'arrangement A removed-standing re-entry clamps role to member');
985
+
986
+ select lives_ok(
987
+ (select format(
988
+ $q$select agent_board._upsert_board_membership_state(%L::uuid, %L::uuid, 'joined', %L::uuid)$q$,
989
+ standing_b_board, standing_b_agent, standing_b_owner
990
+ ) from _rowid_fixture),
991
+ 'mutator re-entry succeeds for arrangement B');
992
+ select is(
993
+ (select bm.id
994
+ from agent_board.board_memberships bm
995
+ join _rowid_fixture f
996
+ on bm.board_id = f.standing_b_board and bm.agent_id = f.standing_b_agent
997
+ where bm.membership_state in ('joined', 'subscribed')),
998
+ (select standing_b_expected_id from _rowid_fixture),
999
+ 'arrangement B mutator updates the explicit higher UUID row');
1000
+ select is(
1001
+ (select count(*)::int
1002
+ from agent_board.board_memberships bm
1003
+ join _rowid_fixture f
1004
+ on bm.board_id = f.standing_b_board and bm.agent_id = f.standing_b_agent
1005
+ where bm.membership_state in ('joined', 'subscribed')),
1006
+ 1,
1007
+ 'arrangement B mutator leaves exactly one active row');
1008
+ select is(
1009
+ (select role
1010
+ from agent_board.board_memberships bm
1011
+ join _rowid_fixture f
1012
+ on bm.board_id = f.standing_b_board and bm.agent_id = f.standing_b_agent
1013
+ where bm.membership_state in ('joined', 'subscribed')),
1014
+ 'member',
1015
+ 'arrangement B removed-standing re-entry clamps role to member');
1016
+
1017
+ -- 65-66. ALLOW twin: after re-entry, the grant filter admits the active row.
1018
+ select lives_ok(
1019
+ (select format(
1020
+ $q$select agent_board.grant_board_role(%L::uuid, %L::uuid, 'admin', %L::uuid)$q$,
1021
+ standing_a_board, standing_a_agent, standing_a_owner
1022
+ ) from _rowid_fixture),
1023
+ 'grant_board_role admits the active arrangement-A row after re-entry');
1024
+ select is(
1025
+ (select role
1026
+ from agent_board.board_memberships bm
1027
+ join _rowid_fixture f
1028
+ on bm.board_id = f.standing_a_board and bm.agent_id = f.standing_a_agent
1029
+ where bm.membership_state in ('joined', 'subscribed')),
1030
+ 'admin',
1031
+ 'grant_board_role ALLOW really updates the active row');
1032
+
1033
+ -- 67-69. ALLOW twin for joinability, with both authenticated controls.
1034
+ select set_config('request.jwt.claim.role', 'authenticated', true);
1035
+ select set_config(
1036
+ 'request.jwt.claim.sub',
1037
+ (select standing_a_user from _rowid_fixture)::text,
1038
+ true
1039
+ );
1040
+ select is(
1041
+ agent_board._is_service_role(),
1042
+ false,
1043
+ 'control: active joinability ALLOW block is not service_role');
1044
+ select is(
1045
+ agent_board.current_agent_id(),
1046
+ (select standing_a_agent from _rowid_fixture),
1047
+ 'control: active joinability ALLOW block resolves the expected actor');
1048
+ select lives_ok(
1049
+ (select format(
1050
+ $q$select agent_board.join_board(%L::uuid, %L::uuid, 'joined')$q$,
1051
+ standing_a_board, standing_a_agent
1052
+ ) from _rowid_fixture),
1053
+ 'joinability admits the same pair after it has one active row');
1054
+
1055
+ -- 70. Service-role control for exact removed+active duplicate probes.
1056
+ select set_config('request.jwt.claim.role', '', true);
1057
+ select set_config('request.jwt.claim.sub', '', true);
1058
+ select is(
1059
+ agent_board._is_service_role(),
1060
+ true,
1061
+ 'control: removed+active duplicate block really runs as service_role');
1062
+
1063
+ -- 71-78. EC-MBROWID-3: active standing wins over historical removed in both
1064
+ -- explicit-ID arrangements. The mutator must not attempt to activate the removed row
1065
+ -- and collide with the one-active-row partial unique index.
1066
+ select lives_ok(
1067
+ (select format(
1068
+ $q$select agent_board._upsert_board_membership_state(%L::uuid, %L::uuid, 'subscribed', null)$q$,
1069
+ active_a_board, active_a_agent
1070
+ ) from _rowid_fixture),
1071
+ 'removed+active arrangement A mutator follows active standing without unique violation');
1072
+ select is(
1073
+ (select count(*)::int
1074
+ from agent_board.board_memberships bm
1075
+ join _rowid_fixture f
1076
+ on bm.board_id = f.active_a_board and bm.agent_id = f.active_a_agent
1077
+ where bm.membership_state in ('joined', 'subscribed')),
1078
+ 1,
1079
+ 'removed+active arrangement A retains exactly one active row');
1080
+ select is(
1081
+ (select role
1082
+ from agent_board.board_memberships bm
1083
+ join _rowid_fixture f
1084
+ on bm.board_id = f.active_a_board and bm.agent_id = f.active_a_agent
1085
+ where bm.membership_state in ('joined', 'subscribed')),
1086
+ 'admin',
1087
+ 'removed+active arrangement A preserves active-to-active admin role');
1088
+ select is(
1089
+ (select id
1090
+ from agent_board.board_memberships bm
1091
+ join _rowid_fixture f
1092
+ on bm.board_id = f.active_a_board and bm.agent_id = f.active_a_agent
1093
+ where bm.membership_state in ('joined', 'subscribed')),
1094
+ (select active_a_expected_id from _rowid_fixture),
1095
+ 'removed+active arrangement A retains the explicit active membership identity');
1096
+
1097
+ select lives_ok(
1098
+ (select format(
1099
+ $q$select agent_board._upsert_board_membership_state(%L::uuid, %L::uuid, 'subscribed', null)$q$,
1100
+ active_b_board, active_b_agent
1101
+ ) from _rowid_fixture),
1102
+ 'removed+active arrangement B mutator follows active standing without unique violation');
1103
+ select is(
1104
+ (select count(*)::int
1105
+ from agent_board.board_memberships bm
1106
+ join _rowid_fixture f
1107
+ on bm.board_id = f.active_b_board and bm.agent_id = f.active_b_agent
1108
+ where bm.membership_state in ('joined', 'subscribed')),
1109
+ 1,
1110
+ 'removed+active arrangement B retains exactly one active row');
1111
+ select is(
1112
+ (select role
1113
+ from agent_board.board_memberships bm
1114
+ join _rowid_fixture f
1115
+ on bm.board_id = f.active_b_board and bm.agent_id = f.active_b_agent
1116
+ where bm.membership_state in ('joined', 'subscribed')),
1117
+ 'admin',
1118
+ 'removed+active arrangement B preserves active-to-active admin role');
1119
+ select is(
1120
+ (select id
1121
+ from agent_board.board_memberships bm
1122
+ join _rowid_fixture f
1123
+ on bm.board_id = f.active_b_board and bm.agent_id = f.active_b_agent
1124
+ where bm.membership_state in ('joined', 'subscribed')),
1125
+ (select active_b_expected_id from _rowid_fixture),
1126
+ 'removed+active arrangement B retains the explicit active membership identity');
1127
+
1128
+ -- 79-84. LOW ACTIVE + HIGH REMOVED: private view, joinability, and
1129
+ -- update_board_membership must all follow the active row.
1130
+ select set_config('request.jwt.claim.role', 'authenticated', true);
1131
+ select set_config(
1132
+ 'request.jwt.claim.sub',
1133
+ (select active_b_agent_user from _rowid_fixture)::text,
1134
+ true
1135
+ );
1136
+ select is(
1137
+ agent_board._is_service_role(),
1138
+ false,
1139
+ 'control: private low-active/high-removed block is not service_role');
1140
+ select is(
1141
+ agent_board.current_agent_id(),
1142
+ (select active_b_agent from _rowid_fixture),
1143
+ 'control: private low-active/high-removed block resolves the expected actor');
1144
+ select is(
1145
+ (select d.current_membership_state
1146
+ from agent_board.v_board_directory d
1147
+ join _rowid_fixture f on d.board_id = f.active_b_board),
1148
+ 'subscribed',
1149
+ 'v_board_directory prefers the low-ID active row over the high-ID removed row');
1150
+ select lives_ok(
1151
+ (select format(
1152
+ $q$select agent_board.join_board(%L::uuid, %L::uuid, 'joined')$q$,
1153
+ active_b_board, active_b_agent
1154
+ ) from _rowid_fixture),
1155
+ 'idempotent join_board succeeds for a private board with low-ID active standing');
1156
+ select lives_ok(
1157
+ (select format(
1158
+ $q$select agent_board.update_board_membership(%L::uuid, %L::uuid, 'joined')$q$,
1159
+ active_b_board, active_b_agent
1160
+ ) from _rowid_fixture),
1161
+ 'update_board_membership authorizes the low-ID active row, not the high-ID removed row');
1162
+ select is(
1163
+ (select bm.membership_state
1164
+ from agent_board.board_memberships bm
1165
+ join _rowid_fixture f on bm.id = f.active_b_expected_id),
1166
+ 'joined',
1167
+ 'update_board_membership changes the intended low-ID active row');
1168
+
1169
+ -- 85-87. The same broken arrangement is idempotently joinable on invite_only.
1170
+ select set_config(
1171
+ 'request.jwt.claim.sub',
1172
+ (select active_c_agent_user from _rowid_fixture)::text,
1173
+ true
1174
+ );
1175
+ select is(
1176
+ agent_board._is_service_role(),
1177
+ false,
1178
+ 'control: invite-only low-active/high-removed block is not service_role');
1179
+ select is(
1180
+ agent_board.current_agent_id(),
1181
+ (select active_c_agent from _rowid_fixture),
1182
+ 'control: invite-only low-active/high-removed block resolves the expected actor');
1183
+ select lives_ok(
1184
+ (select format(
1185
+ $q$select agent_board.join_board(%L::uuid, %L::uuid, 'joined')$q$,
1186
+ active_c_board, active_c_agent
1187
+ ) from _rowid_fixture),
1188
+ 'idempotent join_board succeeds for invite_only with low-ID active standing');
1189
+
1190
+ -- 88-92. Mirror proof: HIGH ACTIVE + LOW REMOVED also resolves active, so an
1191
+ -- implementation cannot repair the bug by flipping bare UUID direction.
1192
+ select set_config(
1193
+ 'request.jwt.claim.sub',
1194
+ (select active_a_agent_user from _rowid_fixture)::text,
1195
+ true
1196
+ );
1197
+ select is(
1198
+ agent_board._is_service_role(),
1199
+ false,
1200
+ 'control: mirror high-active/low-removed block is not service_role');
1201
+ select is(
1202
+ agent_board.current_agent_id(),
1203
+ (select active_a_agent from _rowid_fixture),
1204
+ 'control: mirror high-active/low-removed block resolves the expected actor');
1205
+ select is(
1206
+ (select d.current_membership_state
1207
+ from agent_board.v_board_directory d
1208
+ join _rowid_fixture f on d.board_id = f.active_a_board),
1209
+ 'subscribed',
1210
+ 'v_board_directory also resolves the mirror high-ID active arrangement');
1211
+ select lives_ok(
1212
+ (select format(
1213
+ $q$select agent_board.update_board_membership(%L::uuid, %L::uuid, 'joined')$q$,
1214
+ active_a_board, active_a_agent
1215
+ ) from _rowid_fixture),
1216
+ 'update_board_membership follows the active row in the mirror arrangement');
1217
+ select is(
1218
+ (select bm.membership_state
1219
+ from agent_board.board_memberships bm
1220
+ join _rowid_fixture f on bm.id = f.active_a_expected_id),
1221
+ 'joined',
1222
+ 'mirror update_board_membership changes the intended high-ID active row');
1223
+
1224
+ -- 93-95. Pending selection must suppress stale non-active rows whenever an
1225
+ -- active membership exists for the same agent.
1226
+ select set_config(
1227
+ 'request.jwt.claim.sub',
1228
+ (select active_a_owner_user from _rowid_fixture)::text,
1229
+ true
1230
+ );
1231
+ select is(
1232
+ agent_board._is_service_role(),
1233
+ false,
1234
+ 'control: pending-list active-preference block is not service_role');
1235
+ select is(
1236
+ agent_board.current_agent_id(),
1237
+ (select active_a_owner from _rowid_fixture),
1238
+ 'control: pending-list active-preference block resolves the expected owner');
1239
+ select is(
1240
+ (select count(*)::int
1241
+ from agent_board.list_board_pending_memberships(
1242
+ (select pending_board from _rowid_fixture)
1243
+ ) p
1244
+ join _rowid_fixture f on p.agent_id = f.pending_agent),
1245
+ 0,
1246
+ 'pending-list selection prefers active standing over stale removed/requested rows');
1247
+
1248
+ -- 96-98. Standing agreement: the earlier mutator proof, the active-state
1249
+ -- grant filter, and joinability all recognize arrangement B as active.
1250
+ select set_config('request.jwt.claim.role', '', true);
1251
+ select set_config('request.jwt.claim.sub', '', true);
1252
+ select is(
1253
+ agent_board._is_service_role(),
1254
+ true,
1255
+ 'control: active standing agreement grant block runs as service_role');
1256
+ select lives_ok(
1257
+ (select format(
1258
+ $q$select agent_board.grant_board_role(%L::uuid, %L::uuid, 'member', %L::uuid)$q$,
1259
+ active_b_board, active_b_agent, active_b_owner
1260
+ ) from _rowid_fixture),
1261
+ 'grant_board_role state filter agrees the low-ID row is an active membership');
1262
+ select is(
1263
+ (select bm.role
1264
+ from agent_board.board_memberships bm
1265
+ join _rowid_fixture f on bm.id = f.active_b_expected_id),
1266
+ 'member',
1267
+ 'grant_board_role updates the same active row used by mutator and joinability');
1268
+
1269
+ select set_config('request.jwt.claim.role', '', true);
1270
+ select set_config('request.jwt.claim.sub', '', true);
1271
+
1272
+ select finish();
1273
+
1274
+ rollback;