@consiliency/agent-board-schema 1.10.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.
- package/fixtures/already-applied.json +136 -0
- package/migrations/20260729000100_agent_board_fedsession_autojoin_default_board.sql +364 -0
- package/migrations/20260730000100_agent_board_gatenull_service_role_bypass.sql +369 -0
- package/migrations/20260730000200_agent_board_membstate_authz.sql +195 -0
- package/migrations/20260731000100_agent_board_agentsdir_column_scope.sql +88 -0
- package/migrations/20260801000100_agent_board_leaveidem_terminal_noop.sql +317 -0
- package/migrations/20260802000100_agent_board_rolereset_reentry_clamp.sql +237 -0
- package/migrations/20260803000100_agent_board_rowid_total_order.sql +1201 -0
- package/migrations/20260804000100_agent_board_hygiene_actor_binding_rls.sql +229 -0
- package/migrations/20260806000100_agent_board_consent_substrate.sql +339 -0
- package/migrations/20260807000100_agent_board_consent_emission.sql +619 -0
- package/migrations/20260808000100_agent_board_rejectsem.sql +145 -0
- package/migrations/20260809000100_agent_board_consent_gating.sql +492 -0
- package/migrations/20260810000100_agent_board_consent_locking.sql +485 -0
- package/migrations/20260811000100_agent_board_retention_metadata_revoke.sql +23 -0
- package/migrations/20260812000100_agent_board_pubexec_class_revoke.sql +54 -0
- package/migrations/20260813000100_agent_board_invitepolicy_block.sql +420 -0
- package/migrations/20260814000100_agent_board_obsgate_thread_post_policy.sql +239 -0
- package/migrations/20260815000100_agent_board_obsgate2_authoring_gates.sql +287 -0
- package/migrations/20260816000100_agent_board_obsgate3_task_lifecycle.sql +118 -0
- package/migrations/20260817000100_agent_board_obsgate4_claims_lifecycle.sql +250 -0
- package/migrations/20260818000100_agent_board_fleet_machines_rls_correlation.sql +43 -0
- package/migrations/20260819000100_agent_board_resolve_noop_retire.sql +166 -0
- package/migrations/20260820000100_agent_board_roomdeleg_lane_b.sql +206 -0
- package/migrations/20260821000100_agent_board_claimgate_claimkey.sql +372 -0
- package/migrations/20260822000100_agent_board_mint_harden.sql +710 -0
- package/migrations/20260822000200_agent_board_mint_harden_fingerprint_backfill.sql +207 -0
- package/migrations/20260823000100_agent_board_fleet_machine_hijack.sql +106 -0
- package/migrations/20260824000100_agent_board_claimgate_shadow.sql +443 -0
- package/migrations/20260825000100_agent_board_claimgate_enforce.sql +660 -0
- package/migrations/20260826000100_agent_board_fleet_machine_reactivation.sql +514 -0
- package/migrations/20260828000100_agent_board_delegation_cap_ceiling.sql +474 -0
- package/migrations/20260829000100_agent_board_fleet_machine_reclaim.sql +432 -0
- package/migrations/20260830000100_agent_board_delegation_sole_writer.sql +41 -0
- package/migrations/20260831000100_agent_board_fleet_domain_reclaim.sql +389 -0
- package/package.json +1 -1
- package/schema-manifest.json +309 -3
- package/src/tooling.test.mjs +123 -2
- package/tests/agent_board_actorspoof.test.sql +349 -0
- package/tests/agent_board_agentsdir.test.sql +154 -0
- package/tests/agent_board_authbound_boundary.test.sql +9 -1
- package/tests/agent_board_claimgate_enforce.test.sql +464 -0
- package/tests/agent_board_claimgate_predicate.test.sql +360 -0
- package/tests/agent_board_claimkey.test.sql +229 -0
- package/tests/agent_board_consent_backfill.test.sql +48 -0
- package/tests/agent_board_consent_emission.test.sql +601 -0
- package/tests/agent_board_consent_gating.test.sql +489 -0
- package/tests/agent_board_consent_substrate.test.sql +720 -0
- package/tests/agent_board_delegation_cap_ceiling.test.sql +303 -0
- package/tests/agent_board_delegation_enforcement.test.sql +55 -6
- package/tests/agent_board_delegation_policies.test.sql +107 -1
- package/tests/agent_board_fedsession.test.sql +6 -4
- package/tests/agent_board_fedsession_autojoin.test.sql +181 -0
- package/tests/agent_board_fleet_domain_reclaim.test.sql +419 -0
- package/tests/agent_board_fleet_machine_hijack.test.sql +190 -0
- package/tests/agent_board_fleet_machine_reactivation.test.sql +426 -0
- package/tests/agent_board_fleet_machine_reclaim.test.sql +509 -0
- package/tests/agent_board_fleet_machines_rls.test.sql +79 -0
- package/tests/agent_board_fleet_policy.test.sql +2 -2
- package/tests/agent_board_gatenull_membstate.test.sql +352 -0
- package/tests/agent_board_grants_hygiene.test.sql +408 -0
- package/tests/agent_board_identity_providers.test.sql +9 -0
- package/tests/agent_board_inbox_reads.test.sql +29 -12
- package/tests/agent_board_inbox_rpcs.test.sql +44 -34
- package/tests/agent_board_invitepolicy.test.sql +205 -0
- package/tests/agent_board_leaveidem.test.sql +194 -0
- package/tests/agent_board_mint_fleet_agent_session.test.sql +10 -10
- package/tests/agent_board_mint_harden.test.sql +303 -0
- package/tests/agent_board_mint_harden_backfill.test.sql +95 -0
- package/tests/agent_board_obsgate.test.sql +178 -0
- package/tests/agent_board_obsgate2.test.sql +153 -0
- package/tests/agent_board_obsgate3.test.sql +170 -0
- package/tests/agent_board_obsgate4.test.sql +196 -0
- package/tests/agent_board_portal_offline_invariant.test.sql +13 -2
- package/tests/agent_board_pubexec_class.test.sql +53 -0
- package/tests/agent_board_rejectsem.test.sql +271 -0
- package/tests/agent_board_resigning_refresh.test.sql +1 -1
- package/tests/agent_board_resolve_noop_retire.test.sql +71 -0
- package/tests/agent_board_retention_metadata_acl.test.sql +53 -0
- package/tests/agent_board_revoke_agent_subtree.test.sql +3 -3
- package/tests/agent_board_rolereset.test.sql +351 -0
- package/tests/agent_board_roomdeleg.test.sql +5 -3
- package/tests/agent_board_roomdeleg_lane_b.test.sql +144 -0
- package/tests/agent_board_roomlink.test.sql +25 -9
- package/tests/agent_board_rowid.test.sql +1274 -0
- package/tests/agent_board_signature_rls_reads.test.sql +3 -3
- package/tests/agent_board_signed_write_gateway.test.sql +3 -3
- package/tests/agent_board_signing_challenge_pending_cap.test.sql +2 -2
- package/tests/agent_board_signing_challenge_reaper.test.sql +2 -2
- package/tests/agent_board_signing_proof_rpcs.test.sql +4 -4
- package/tests/agent_board_signing_store.test.sql +6 -6
- package/tests/fixtures/isolation-substrate.sql +93 -0
- package/tests/fixtures/test-prereqs.sql +33 -0
- package/tests/isolation/expected/claimgate_ownerless_quota.out +36 -0
- package/tests/isolation/expected/claimkey_renew_race.out +38 -0
- package/tests/isolation/expected/consent_approval_lock.out +43 -0
- package/tests/isolation/expected/consent_emit_actual_state.out +37 -0
- package/tests/isolation/expected/domain_reclaim_revoke_race.out +40 -0
- package/tests/isolation/expected/reactivate_revoke_race.out +80 -0
- package/tests/isolation/expected/reclaim_revoke_race.out +40 -0
- package/tests/isolation/specs/claimgate_ownerless_quota.spec +77 -0
- package/tests/isolation/specs/claimkey_renew_race.spec +67 -0
- package/tests/isolation/specs/consent_approval_lock.spec +98 -0
- package/tests/isolation/specs/consent_emit_actual_state.spec +79 -0
- package/tests/isolation/specs/domain_reclaim_revoke_race.spec +88 -0
- package/tests/isolation/specs/reactivate_revoke_race.spec +115 -0
- package/tests/isolation/specs/reclaim_revoke_race.spec +94 -0
- package/tests/seeds/20260806000100_agent_board_consent_substrate.sql.pre.sql +19 -0
- 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;
|