@consiliency/agent-board-schema 1.59.0 → 1.63.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/README.md +80 -0
- package/fixtures/already-applied.json +16 -0
- package/migrations/20261010000100_agent_board_own_post_not_delivered.sql +637 -0
- package/migrations/20261011000100_agent_board_session_identities.sql +1964 -0
- package/migrations/20261012000100_agent_board_sidowner.sql +209 -0
- package/migrations/20261014000100_agent_board_grant_target_role.sql +218 -0
- package/package.json +1 -1
- package/schema-manifest.json +39 -3
- package/src/tooling.test.mjs +9 -2
- package/tests/agent_board_addressing.test.sql +3 -1
- package/tests/agent_board_atomic_fleet_onboarding.test.sql +13 -4
- package/tests/agent_board_consent_emission.test.sql +30 -11
- package/tests/agent_board_consent_gating.test.sql +16 -2
- package/tests/agent_board_delegate_role_class.test.sql +1 -1
- package/tests/agent_board_delegation_cap_ceiling.test.sql +26 -15
- package/tests/agent_board_delegation_enforcement.test.sql +1 -1
- package/tests/agent_board_enroll_resumption.test.sql +22 -4
- package/tests/agent_board_enroll_token_single_use.test.sql +18 -3
- package/tests/agent_board_federated_lock_order_class.test.sql +2 -2
- package/tests/agent_board_fk_lock_edges.test.sql +74 -14
- package/tests/agent_board_grant_target_role.test.sql +1118 -0
- package/tests/agent_board_inbox_rpcs.test.sql +5 -2
- package/tests/agent_board_invite_no_downgrade.test.sql +10 -8
- package/tests/agent_board_ledger_authority.test.sql +70 -12
- package/tests/agent_board_membership_current_row_class.test.sql +9 -1
- package/tests/agent_board_mint_fleet_agent_session.test.sql +2 -2
- package/tests/agent_board_mint_harden.test.sql +38 -27
- package/tests/agent_board_own_post_not_delivered.test.sql +490 -0
- package/tests/agent_board_rolereset.test.sql +8 -2
- package/tests/agent_board_roomdeleg_lane_b.test.sql +6 -6
- package/tests/agent_board_rowid.test.sql +3 -1
- package/tests/agent_board_session_identity_approval_revocation.test.sql +334 -0
- package/tests/agent_board_session_identity_backfill.test.sql +82 -0
- package/tests/agent_board_session_identity_backfill_refusal.test.sql +14 -0
- package/tests/agent_board_session_identity_behavior_classes.test.sql +176 -0
- package/tests/agent_board_session_identity_class.test.sql +118 -0
- package/tests/agent_board_session_identity_fingerprint.test.sql +271 -0
- package/tests/agent_board_session_identity_labels_limits.test.sql +205 -0
- package/tests/agent_board_session_identity_lease.test.sql +235 -0
- package/tests/agent_board_session_identity_lifetime.test.sql +230 -0
- package/tests/agent_board_session_identity_limits.test.sql +208 -0
- package/tests/agent_board_session_identity_lineage.test.sql +64 -0
- package/tests/agent_board_session_identity_link.test.sql +201 -0
- package/tests/agent_board_session_identity_mint.test.sql +233 -0
- package/tests/agent_board_session_identity_panel_r1.test.sql +194 -0
- package/tests/agent_board_session_identity_panel_r2.test.sql +174 -0
- package/tests/agent_board_session_identity_protected_actions.test.sql +156 -0
- package/tests/agent_board_session_identity_revoke_closure.test.sql +131 -0
- package/tests/agent_board_sidowner_leases.test.sql +107 -0
- package/tests/agent_board_sidowner_owner_required.test.sql +97 -0
- package/tests/agent_board_sidowner_read_class.test.sql +171 -0
- package/tests/agent_board_sidowner_reads.test.sql +136 -0
- package/tests/agent_board_signer_lifecycle.test.sql +26 -4
- package/tests/agent_board_signing_challenge_pending_cap.test.sql +1 -1
- package/tests/agent_board_v2_invariants.test.sql +4 -4
- package/tests/agent_board_write_enforcement_class.test.sql +74 -15
- package/tests/catalog/function_execute_privileges.json +218 -2
- package/tests/catalog/p_metadata_parameters.json +98 -2
- package/tests/isolation/expected/grant_target_role_race.out +348 -0
- package/tests/isolation/expected/session_identity_limits.out +165 -0
- package/tests/isolation/expected/session_identity_links.out +235 -0
- package/tests/isolation/expected/session_identity_reclaim.out +253 -0
- package/tests/isolation/expected/session_identity_renew_revoke.out +115 -0
- package/tests/isolation/expected/session_identity_renew_revoke_key_gate.out +138 -0
- package/tests/isolation/expected/session_identity_tombstone_onboarding.out +247 -0
- package/tests/isolation/specs/grant_target_role_race.spec +207 -0
- package/tests/isolation/specs/session_identity_limits.spec +81 -0
- package/tests/isolation/specs/session_identity_links.spec +80 -0
- package/tests/isolation/specs/session_identity_reclaim.spec +125 -0
- package/tests/isolation/specs/session_identity_renew_revoke.spec +84 -0
- package/tests/isolation/specs/session_identity_renew_revoke_key_gate.spec +94 -0
- package/tests/isolation/specs/session_identity_tombstone_onboarding.spec +108 -0
- package/tests/seeds/20261011000100_agent_board_session_identities.sql.pre.sql +47 -0
|
@@ -0,0 +1,1118 @@
|
|
|
1
|
+
-- #674 pgTAP: grant_board_role checks the TARGET's current role
|
|
2
|
+
-- (plans/plan-674-grant-target-role.md v4, §5.0-§5.3).
|
|
3
|
+
--
|
|
4
|
+
-- THE RULE UNDER TEST. For a grantor that is not the target (self = the same agent id):
|
|
5
|
+
-- C-A grantor rank < admin -> 42501 'grant on another member requires owner or admin role'
|
|
6
|
+
-- C-B grantor rank <= target's rank -> 42501 'grant requires a higher board role than the target'
|
|
7
|
+
-- after the unchanged ceiling; P0002, the fleet gate and the delegate refusal keep their order.
|
|
8
|
+
--
|
|
9
|
+
-- FIXTURE ISOLATION (§5.0). Every case runs on its OWN fresh board built by
|
|
10
|
+
-- pg_temp.g674_board(p_roles jsonb), by direct insert as the test owner (the seeding style of
|
|
11
|
+
-- agent_board_rowid.test.sql). No case observes another case's writes, so a mutation that flips
|
|
12
|
+
-- one case cannot cascade into the next case's preconditions. Every membership row is seeded with
|
|
13
|
+
-- created_at = updated_at pinned in 2020, because now() is constant inside this transaction: a
|
|
14
|
+
-- write shows as `written`, an untouched row as `pinned`.
|
|
15
|
+
--
|
|
16
|
+
-- NAMING (§5.0). Every case assertion is `<id>.pre` (the seeded rows of grantor and target,
|
|
17
|
+
-- before the call -- never red; a red .pre is a fixture defect), `<id>.call` (throws_ok with the
|
|
18
|
+
-- exact code and message, or lives_ok) and `<id>.post` (a SEPARATE state read: every row of the
|
|
19
|
+
-- target, pinned or written, plus the board's active-owner count). scripts/mutations-674.mjs keys
|
|
20
|
+
-- on those names.
|
|
21
|
+
--
|
|
22
|
+
-- CLAIMS. Runs as the postgres db role; request.jwt.claim.role/.sub select the caller exactly as
|
|
23
|
+
-- agent_board_roomown.test.sql does ('' => service_role, 'authenticated' + sub => that agent).
|
|
24
|
+
-- Authenticated cases pass p_grantor_agent_id = NULL, the shape UI hosts send (it is what makes
|
|
25
|
+
-- the plan's M5 discriminate). The SID block of EC-2 uses set local role, as the SID suites do.
|
|
26
|
+
create extension if not exists pgtap with schema extensions;
|
|
27
|
+
set search_path = extensions, public, agent_board;
|
|
28
|
+
|
|
29
|
+
begin;
|
|
30
|
+
grant usage on schema extensions to authenticated, service_role;
|
|
31
|
+
|
|
32
|
+
select plan(215);
|
|
33
|
+
|
|
34
|
+
-- ===========================================================================
|
|
35
|
+
-- Fixture helpers
|
|
36
|
+
-- ===========================================================================
|
|
37
|
+
create temp table _g674_case(case_id text primary key, board uuid not null) on commit drop;
|
|
38
|
+
create temp table _g674_agent(board uuid not null, label text not null, agent_id uuid not null,
|
|
39
|
+
auth_user uuid, key_row uuid, primary key (board, label)) on commit drop;
|
|
40
|
+
grant select, insert on _g674_case, _g674_agent to authenticated, service_role;
|
|
41
|
+
|
|
42
|
+
-- p_roles: { "<LABEL>": "<role>" -- one joined row
|
|
43
|
+
-- | "<LABEL>": [{"state":..,"role":..}, ..] -- rows in insertion (= created_at) order
|
|
44
|
+
-- | "<LABEL>": [] -- an agent with no row on the board
|
|
45
|
+
-- , "_creator": "<LABEL>" boards.created_by_agent_id (and every row's created_by)
|
|
46
|
+
-- , "_gate": true boards.role_gate_enforced
|
|
47
|
+
-- , "_delegate": ["<LABEL>"] agents.is_delegate (a hosted delegate)
|
|
48
|
+
-- , "_revoked": ["<LABEL>"] an agent-subject fleet_revocation_events row
|
|
49
|
+
-- , "_keys": ["<LABEL>"] a proven active signing key (the signed route's signer)
|
|
50
|
+
-- , "_human_of": {"<CHILD>": "<PARENT>"} same human: CHILD.owner_user_id = PARENT's }
|
|
51
|
+
create function pg_temp.g674_board(p_roles jsonb) returns uuid language plpgsql as $$
|
|
52
|
+
declare
|
|
53
|
+
v_board uuid := gen_random_uuid();
|
|
54
|
+
v_sfx text := substr(replace(v_board::text, '-', ''), 1, 12);
|
|
55
|
+
v_label text; v_agent uuid; v_user uuid; v_creator uuid; v_row jsonb; v_n int := 0;
|
|
56
|
+
v_ts timestamptz; v_key uuid; v_rows jsonb;
|
|
57
|
+
begin
|
|
58
|
+
for v_label in select k from jsonb_object_keys(p_roles) k where left(k, 1) <> '_' order by k loop
|
|
59
|
+
v_agent := gen_random_uuid();
|
|
60
|
+
v_user := gen_random_uuid();
|
|
61
|
+
insert into auth.users(id, instance_id, aud, role, email)
|
|
62
|
+
values (v_user, '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated',
|
|
63
|
+
'g674-' || lower(v_label) || '-' || v_sfx || '@t.test');
|
|
64
|
+
insert into agent_board.agents(id, agent_type, handle, display_name, status, auth_user_id, owner_user_id, is_delegate)
|
|
65
|
+
values (v_agent, 'agent', 'g674-' || lower(v_label) || '-' || v_sfx, 'G674 ' || v_label, 'active',
|
|
66
|
+
v_user, v_user, coalesce(p_roles -> '_delegate' ? v_label, false));
|
|
67
|
+
v_key := null;
|
|
68
|
+
if coalesce(p_roles -> '_keys' ? v_label, false) then
|
|
69
|
+
insert into agent_board.agent_signing_keys(agent_id, key_id, key_version, public_key, public_key_format,
|
|
70
|
+
fingerprint, status, proof_state)
|
|
71
|
+
values (v_agent, 'primary', 1, public.test_pubkey('g674-' || v_agent), 'raw_ed25519_base64url',
|
|
72
|
+
public.test_key_fingerprint('g674-' || v_agent), 'active', 'proven')
|
|
73
|
+
returning id into v_key;
|
|
74
|
+
end if;
|
|
75
|
+
insert into _g674_agent values (v_board, v_label, v_agent, v_user, v_key);
|
|
76
|
+
end loop;
|
|
77
|
+
|
|
78
|
+
for v_label in select k from jsonb_object_keys(coalesce(p_roles -> '_human_of', '{}'::jsonb)) k loop
|
|
79
|
+
update agent_board.agents
|
|
80
|
+
set owner_user_id = (select auth_user from _g674_agent where board = v_board and label = p_roles -> '_human_of' ->> v_label)
|
|
81
|
+
where id = (select agent_id from _g674_agent where board = v_board and label = v_label);
|
|
82
|
+
end loop;
|
|
83
|
+
|
|
84
|
+
v_creator := (select agent_id from _g674_agent where board = v_board and label = p_roles ->> '_creator');
|
|
85
|
+
if v_creator is null then
|
|
86
|
+
raise exception 'g674_board: _creator must name a label';
|
|
87
|
+
end if;
|
|
88
|
+
insert into agent_board.boards(id, slug, name, created_by_agent_id, visibility, status, role_gate_enforced)
|
|
89
|
+
values (v_board, 'g674-' || v_sfx, 'G674 ' || v_sfx, v_creator, 'private', 'active',
|
|
90
|
+
coalesce((p_roles ->> '_gate')::boolean, false));
|
|
91
|
+
|
|
92
|
+
for v_label in select k from jsonb_object_keys(p_roles) k where left(k, 1) <> '_' order by k loop
|
|
93
|
+
v_rows := case jsonb_typeof(p_roles -> v_label)
|
|
94
|
+
when 'string' then jsonb_build_array(jsonb_build_object('state', 'joined', 'role', p_roles ->> v_label))
|
|
95
|
+
else p_roles -> v_label end;
|
|
96
|
+
for v_row in select e from jsonb_array_elements(v_rows) with ordinality as t(e, n) order by n
|
|
97
|
+
loop
|
|
98
|
+
v_n := v_n + 1;
|
|
99
|
+
v_ts := timestamptz '2020-01-01 00:00:00+00' + v_n * interval '1 minute';
|
|
100
|
+
insert into agent_board.board_memberships(board_id, agent_id, membership_state, role, created_by_agent_id,
|
|
101
|
+
left_at, created_at, updated_at)
|
|
102
|
+
values (v_board, (select agent_id from _g674_agent where board = v_board and label = v_label),
|
|
103
|
+
v_row ->> 'state', v_row ->> 'role', v_creator,
|
|
104
|
+
case when v_row ->> 'state' in ('left', 'removed') then v_ts end, v_ts, v_ts);
|
|
105
|
+
end loop;
|
|
106
|
+
end loop;
|
|
107
|
+
|
|
108
|
+
insert into agent_board.fleet_revocation_events(subject_kind, subject_id, agent_id, revoked_by_agent_id, revocation_reason)
|
|
109
|
+
select 'agent', a.agent_id, a.agent_id, v_creator, 'g674-fixture'
|
|
110
|
+
from _g674_agent a
|
|
111
|
+
where a.board = v_board and coalesce(p_roles -> '_revoked' ? a.label, false);
|
|
112
|
+
return v_board;
|
|
113
|
+
end;
|
|
114
|
+
$$;
|
|
115
|
+
|
|
116
|
+
create function pg_temp.g674_b(p_case text) returns uuid language sql stable as $$
|
|
117
|
+
select board from _g674_case where case_id = p_case
|
|
118
|
+
$$;
|
|
119
|
+
create function pg_temp.g674_a(p_case text, p_label text) returns uuid language sql stable as $$
|
|
120
|
+
select a.agent_id from _g674_agent a join _g674_case c on c.board = a.board
|
|
121
|
+
where c.case_id = p_case and a.label = p_label
|
|
122
|
+
$$;
|
|
123
|
+
-- The seeded rows of the named labels (every row, any state), in label then insertion order.
|
|
124
|
+
create function pg_temp.g674_rows(p_case text, p_labels text)
|
|
125
|
+
returns table(label text, membership_state text, role text) language sql stable as $$
|
|
126
|
+
select a.label, bm.membership_state, bm.role
|
|
127
|
+
from _g674_case c
|
|
128
|
+
join _g674_agent a on a.board = c.board and a.label = any(string_to_array(p_labels, ','))
|
|
129
|
+
join agent_board.board_memberships bm on bm.board_id = c.board and bm.agent_id = a.agent_id
|
|
130
|
+
where c.case_id = p_case
|
|
131
|
+
order by a.label, bm.created_at, bm.id
|
|
132
|
+
$$;
|
|
133
|
+
-- The post-call state: every row of the label (state/role/pinned|written), then the board's
|
|
134
|
+
-- active-owner count.
|
|
135
|
+
create function pg_temp.g674_post(p_case text, p_label text) returns text language sql stable as $$
|
|
136
|
+
select p_label || '[' || coalesce((
|
|
137
|
+
select string_agg(bm.membership_state || '/' || bm.role || '/' ||
|
|
138
|
+
case when bm.updated_at < timestamptz '2021-01-01 00:00:00+00' then 'pinned' else 'written' end,
|
|
139
|
+
',' order by bm.created_at, bm.id)
|
|
140
|
+
from agent_board.board_memberships bm
|
|
141
|
+
where bm.board_id = pg_temp.g674_b(p_case) and bm.agent_id = pg_temp.g674_a(p_case, p_label)), '')
|
|
142
|
+
|| '] owners=' || (select count(*) from agent_board.board_memberships bm
|
|
143
|
+
where bm.board_id = pg_temp.g674_b(p_case)
|
|
144
|
+
and bm.membership_state in ('joined', 'subscribed') and bm.role = 'owner')
|
|
145
|
+
$$;
|
|
146
|
+
-- Several labels at once (the EC-2 bystander reads).
|
|
147
|
+
create function pg_temp.g674_who(p_case text, p_labels text) returns text language sql stable as $$
|
|
148
|
+
select string_agg(pg_temp.g674_post(p_case, l), ' ' order by l)
|
|
149
|
+
from unnest(string_to_array(p_labels, ',')) l
|
|
150
|
+
$$;
|
|
151
|
+
-- The same rows WITHOUT the owner count, for a before/after comparison across a call that
|
|
152
|
+
-- legitimately changes other rows on the board.
|
|
153
|
+
create function pg_temp.g674_rows_only(p_case text, p_labels text) returns text language sql stable as $$
|
|
154
|
+
select string_agg(l || '[' || coalesce((
|
|
155
|
+
select string_agg(bm.membership_state || '/' || bm.role || '/' ||
|
|
156
|
+
case when bm.updated_at < timestamptz '2021-01-01 00:00:00+00' then 'pinned' else 'written' end,
|
|
157
|
+
',' order by bm.created_at, bm.id)
|
|
158
|
+
from agent_board.board_memberships bm
|
|
159
|
+
where bm.board_id = pg_temp.g674_b(p_case) and bm.agent_id = pg_temp.g674_a(p_case, l)), '') || ']',
|
|
160
|
+
' ' order by l)
|
|
161
|
+
from unnest(string_to_array(p_labels, ',')) l
|
|
162
|
+
$$;
|
|
163
|
+
-- Evidence rows the signed wrapper recorded for THIS board and target (scoped, not agent-wide).
|
|
164
|
+
create function pg_temp.g674_evidence(p_case text, p_label text) returns text language sql stable as $$
|
|
165
|
+
select count(*)::text from agent_board.signature_evidence
|
|
166
|
+
where operation = 'grant_board_role'
|
|
167
|
+
and external_evidence_ref = 'board-authority:grant_board_role:' || pg_temp.g674_b(p_case) || ':'
|
|
168
|
+
|| pg_temp.g674_a(p_case, p_label)
|
|
169
|
+
$$;
|
|
170
|
+
create function pg_temp.g674_as(p_case text, p_label text) returns void language plpgsql as $$
|
|
171
|
+
begin
|
|
172
|
+
perform set_config('request.jwt.claim.role', 'authenticated', true);
|
|
173
|
+
perform set_config('request.jwt.claim.sub',
|
|
174
|
+
(select a.auth_user::text from _g674_agent a join _g674_case c on c.board = a.board
|
|
175
|
+
where c.case_id = p_case and a.label = p_label), true);
|
|
176
|
+
end;
|
|
177
|
+
$$;
|
|
178
|
+
create function pg_temp.g674_service() returns void language plpgsql as $$
|
|
179
|
+
begin
|
|
180
|
+
perform set_config('request.jwt.claim.role', '', true);
|
|
181
|
+
perform set_config('request.jwt.claim.sub', '', true);
|
|
182
|
+
end;
|
|
183
|
+
$$;
|
|
184
|
+
-- The gateway decision the signed wrapper is called with; agent_id is the VERIFIED signer.
|
|
185
|
+
create function pg_temp.g674_gw(p_actor uuid) returns jsonb language sql stable as $$
|
|
186
|
+
select jsonb_build_object(
|
|
187
|
+
'agent_id', p_actor,
|
|
188
|
+
'signing_key_id', (select key_row from _g674_agent where agent_id = p_actor),
|
|
189
|
+
'key_id', 'primary',
|
|
190
|
+
'key_version', 1,
|
|
191
|
+
'algorithm', 'Ed25519',
|
|
192
|
+
'verification_status', 'verified',
|
|
193
|
+
'payload_digest', 'sha256:' || repeat('a', 64),
|
|
194
|
+
'signature_digest', 'sha256:' || repeat('b', 64),
|
|
195
|
+
'canonicalization_version', 'agent-provenance-jcs.v1',
|
|
196
|
+
'enforcement_mode', 'require_for_agent_writes',
|
|
197
|
+
'redacted', true)
|
|
198
|
+
$$;
|
|
199
|
+
|
|
200
|
+
-- ===========================================================================
|
|
201
|
+
-- §5.1 cases. Ranks: owner 4, admin 3, member 2, observer 1. Each case: its own board, then
|
|
202
|
+
-- <id>.pre (seeded rows), <id>.call, <id>.post. Refusals read back an UNCHANGED target (every
|
|
203
|
+
-- row `pinned`) and an unchanged active-owner count; allows read back the active row `written`
|
|
204
|
+
-- with the new role. G9/A12 additionally count the signed wrapper's evidence rows for that board
|
|
205
|
+
-- and target: a state read only (O007) -- a raise anywhere rolls the insert back, so it is not
|
|
206
|
+
-- evidence of the wrapper's ordering and counts toward no mutation.
|
|
207
|
+
-- ===========================================================================
|
|
208
|
+
-- G1: admin -> owner, member: C-B (3<=4)
|
|
209
|
+
insert into _g674_case values ('G1', pg_temp.g674_board('{"O": "owner", "G": "admin", "T": "owner", "_creator": "O"}'::jsonb));
|
|
210
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G1', 'G,T'),
|
|
211
|
+
$$values ('G', 'joined', 'admin'), ('T', 'joined', 'owner')$$,
|
|
212
|
+
'G1.pre');
|
|
213
|
+
select pg_temp.g674_as('G1', 'G');
|
|
214
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G1'), pg_temp.g674_a('G1', 'T'), 'member'), '42501', 'grant requires a higher board role than the target', 'G1.call');
|
|
215
|
+
select pg_temp.g674_service();
|
|
216
|
+
select is(pg_temp.g674_post('G1', 'T'), 'T[joined/owner/pinned] owners=2', 'G1.post');
|
|
217
|
+
|
|
218
|
+
-- G1b: admin -> owner, observer: C-B
|
|
219
|
+
insert into _g674_case values ('G1b', pg_temp.g674_board('{"O": "owner", "G": "admin", "T": "owner", "_creator": "O"}'::jsonb));
|
|
220
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G1b', 'G,T'),
|
|
221
|
+
$$values ('G', 'joined', 'admin'), ('T', 'joined', 'owner')$$,
|
|
222
|
+
'G1b.pre');
|
|
223
|
+
select pg_temp.g674_as('G1b', 'G');
|
|
224
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G1b'), pg_temp.g674_a('G1b', 'T'), 'observer'), '42501', 'grant requires a higher board role than the target', 'G1b.call');
|
|
225
|
+
select pg_temp.g674_service();
|
|
226
|
+
select is(pg_temp.g674_post('G1b', 'T'), 'T[joined/owner/pinned] owners=2', 'G1b.post');
|
|
227
|
+
|
|
228
|
+
-- G2: member -> owner, member: C-A (2<3) before C-B
|
|
229
|
+
insert into _g674_case values ('G2', pg_temp.g674_board('{"O": "owner", "G": "member", "T": "owner", "_creator": "O"}'::jsonb));
|
|
230
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G2', 'G,T'),
|
|
231
|
+
$$values ('G', 'joined', 'member'), ('T', 'joined', 'owner')$$,
|
|
232
|
+
'G2.pre');
|
|
233
|
+
select pg_temp.g674_as('G2', 'G');
|
|
234
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G2'), pg_temp.g674_a('G2', 'T'), 'member'), '42501', 'grant on another member requires owner or admin role', 'G2.call');
|
|
235
|
+
select pg_temp.g674_service();
|
|
236
|
+
select is(pg_temp.g674_post('G2', 'T'), 'T[joined/owner/pinned] owners=2', 'G2.post');
|
|
237
|
+
|
|
238
|
+
-- G2b: member -> owner, observer: C-A
|
|
239
|
+
insert into _g674_case values ('G2b', pg_temp.g674_board('{"O": "owner", "G": "member", "T": "owner", "_creator": "O"}'::jsonb));
|
|
240
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G2b', 'G,T'),
|
|
241
|
+
$$values ('G', 'joined', 'member'), ('T', 'joined', 'owner')$$,
|
|
242
|
+
'G2b.pre');
|
|
243
|
+
select pg_temp.g674_as('G2b', 'G');
|
|
244
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G2b'), pg_temp.g674_a('G2b', 'T'), 'observer'), '42501', 'grant on another member requires owner or admin role', 'G2b.call');
|
|
245
|
+
select pg_temp.g674_service();
|
|
246
|
+
select is(pg_temp.g674_post('G2b', 'T'), 'T[joined/owner/pinned] owners=2', 'G2b.post');
|
|
247
|
+
|
|
248
|
+
-- G3: observer -> creator-owner, observer: C-A
|
|
249
|
+
insert into _g674_case values ('G3', pg_temp.g674_board('{"G": "observer", "T": "owner", "_creator": "T"}'::jsonb));
|
|
250
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G3', 'G,T'),
|
|
251
|
+
$$values ('G', 'joined', 'observer'), ('T', 'joined', 'owner')$$,
|
|
252
|
+
'G3.pre');
|
|
253
|
+
select pg_temp.g674_as('G3', 'G');
|
|
254
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G3'), pg_temp.g674_a('G3', 'T'), 'observer'), '42501', 'grant on another member requires owner or admin role', 'G3.call');
|
|
255
|
+
select pg_temp.g674_service();
|
|
256
|
+
select is(pg_temp.g674_post('G3', 'T'), 'T[joined/owner/pinned] owners=1', 'G3.post');
|
|
257
|
+
|
|
258
|
+
-- G4: member -> admin, observer: C-A
|
|
259
|
+
insert into _g674_case values ('G4', pg_temp.g674_board('{"O": "owner", "G": "member", "T": "admin", "_creator": "O"}'::jsonb));
|
|
260
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G4', 'G,T'),
|
|
261
|
+
$$values ('G', 'joined', 'member'), ('T', 'joined', 'admin')$$,
|
|
262
|
+
'G4.pre');
|
|
263
|
+
select pg_temp.g674_as('G4', 'G');
|
|
264
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G4'), pg_temp.g674_a('G4', 'T'), 'observer'), '42501', 'grant on another member requires owner or admin role', 'G4.call');
|
|
265
|
+
select pg_temp.g674_service();
|
|
266
|
+
select is(pg_temp.g674_post('G4', 'T'), 'T[joined/admin/pinned] owners=1', 'G4.post');
|
|
267
|
+
|
|
268
|
+
-- G5: admin -> admin, member: C-B (3<=3)
|
|
269
|
+
insert into _g674_case values ('G5', pg_temp.g674_board('{"O": "owner", "G": "admin", "T": "admin", "_creator": "O"}'::jsonb));
|
|
270
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G5', 'G,T'),
|
|
271
|
+
$$values ('G', 'joined', 'admin'), ('T', 'joined', 'admin')$$,
|
|
272
|
+
'G5.pre');
|
|
273
|
+
select pg_temp.g674_as('G5', 'G');
|
|
274
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G5'), pg_temp.g674_a('G5', 'T'), 'member'), '42501', 'grant requires a higher board role than the target', 'G5.call');
|
|
275
|
+
select pg_temp.g674_service();
|
|
276
|
+
select is(pg_temp.g674_post('G5', 'T'), 'T[joined/admin/pinned] owners=1', 'G5.post');
|
|
277
|
+
|
|
278
|
+
-- G5b: admin -> admin, observer: C-B
|
|
279
|
+
insert into _g674_case values ('G5b', pg_temp.g674_board('{"O": "owner", "G": "admin", "T": "admin", "_creator": "O"}'::jsonb));
|
|
280
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G5b', 'G,T'),
|
|
281
|
+
$$values ('G', 'joined', 'admin'), ('T', 'joined', 'admin')$$,
|
|
282
|
+
'G5b.pre');
|
|
283
|
+
select pg_temp.g674_as('G5b', 'G');
|
|
284
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G5b'), pg_temp.g674_a('G5b', 'T'), 'observer'), '42501', 'grant requires a higher board role than the target', 'G5b.call');
|
|
285
|
+
select pg_temp.g674_service();
|
|
286
|
+
select is(pg_temp.g674_post('G5b', 'T'), 'T[joined/admin/pinned] owners=1', 'G5b.post');
|
|
287
|
+
|
|
288
|
+
-- G6: owner -> owner, admin: C-B (4<=4)
|
|
289
|
+
insert into _g674_case values ('G6', pg_temp.g674_board('{"G": "owner", "T": "owner", "_creator": "G"}'::jsonb));
|
|
290
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G6', 'G,T'),
|
|
291
|
+
$$values ('G', 'joined', 'owner'), ('T', 'joined', 'owner')$$,
|
|
292
|
+
'G6.pre');
|
|
293
|
+
select pg_temp.g674_as('G6', 'G');
|
|
294
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G6'), pg_temp.g674_a('G6', 'T'), 'admin'), '42501', 'grant requires a higher board role than the target', 'G6.call');
|
|
295
|
+
select pg_temp.g674_service();
|
|
296
|
+
select is(pg_temp.g674_post('G6', 'T'), 'T[joined/owner/pinned] owners=2', 'G6.post');
|
|
297
|
+
|
|
298
|
+
-- G6b: owner -> owner, member: C-B
|
|
299
|
+
insert into _g674_case values ('G6b', pg_temp.g674_board('{"G": "owner", "T": "owner", "_creator": "G"}'::jsonb));
|
|
300
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G6b', 'G,T'),
|
|
301
|
+
$$values ('G', 'joined', 'owner'), ('T', 'joined', 'owner')$$,
|
|
302
|
+
'G6b.pre');
|
|
303
|
+
select pg_temp.g674_as('G6b', 'G');
|
|
304
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G6b'), pg_temp.g674_a('G6b', 'T'), 'member'), '42501', 'grant requires a higher board role than the target', 'G6b.call');
|
|
305
|
+
select pg_temp.g674_service();
|
|
306
|
+
select is(pg_temp.g674_post('G6b', 'T'), 'T[joined/owner/pinned] owners=2', 'G6b.post');
|
|
307
|
+
|
|
308
|
+
-- G7.1: G1 with the role gate on: C-B
|
|
309
|
+
insert into _g674_case values ('G7.1', pg_temp.g674_board('{"O": "owner", "G": "admin", "T": "owner", "_creator": "O", "_gate": true}'::jsonb));
|
|
310
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G7.1', 'G,T'),
|
|
311
|
+
$$values ('G', 'joined', 'admin'), ('T', 'joined', 'owner')$$,
|
|
312
|
+
'G7.1.pre');
|
|
313
|
+
select pg_temp.g674_as('G7.1', 'G');
|
|
314
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G7.1'), pg_temp.g674_a('G7.1', 'T'), 'member'), '42501', 'grant requires a higher board role than the target', 'G7.1.call');
|
|
315
|
+
select pg_temp.g674_service();
|
|
316
|
+
select is(pg_temp.g674_post('G7.1', 'T'), 'T[joined/owner/pinned] owners=2', 'G7.1.post');
|
|
317
|
+
|
|
318
|
+
-- G7.2: G2 with the role gate on: C-A
|
|
319
|
+
insert into _g674_case values ('G7.2', pg_temp.g674_board('{"O": "owner", "G": "member", "T": "owner", "_creator": "O", "_gate": true}'::jsonb));
|
|
320
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G7.2', 'G,T'),
|
|
321
|
+
$$values ('G', 'joined', 'member'), ('T', 'joined', 'owner')$$,
|
|
322
|
+
'G7.2.pre');
|
|
323
|
+
select pg_temp.g674_as('G7.2', 'G');
|
|
324
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G7.2'), pg_temp.g674_a('G7.2', 'T'), 'member'), '42501', 'grant on another member requires owner or admin role', 'G7.2.call');
|
|
325
|
+
select pg_temp.g674_service();
|
|
326
|
+
select is(pg_temp.g674_post('G7.2', 'T'), 'T[joined/owner/pinned] owners=2', 'G7.2.post');
|
|
327
|
+
|
|
328
|
+
-- G7.3: G3 with the role gate on: C-A
|
|
329
|
+
insert into _g674_case values ('G7.3', pg_temp.g674_board('{"G": "observer", "T": "owner", "_creator": "T", "_gate": true}'::jsonb));
|
|
330
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G7.3', 'G,T'),
|
|
331
|
+
$$values ('G', 'joined', 'observer'), ('T', 'joined', 'owner')$$,
|
|
332
|
+
'G7.3.pre');
|
|
333
|
+
select pg_temp.g674_as('G7.3', 'G');
|
|
334
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G7.3'), pg_temp.g674_a('G7.3', 'T'), 'observer'), '42501', 'grant on another member requires owner or admin role', 'G7.3.call');
|
|
335
|
+
select pg_temp.g674_service();
|
|
336
|
+
select is(pg_temp.g674_post('G7.3', 'T'), 'T[joined/owner/pinned] owners=1', 'G7.3.post');
|
|
337
|
+
|
|
338
|
+
-- G7.4: G4 with the role gate on: C-A
|
|
339
|
+
insert into _g674_case values ('G7.4', pg_temp.g674_board('{"O": "owner", "G": "member", "T": "admin", "_creator": "O", "_gate": true}'::jsonb));
|
|
340
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G7.4', 'G,T'),
|
|
341
|
+
$$values ('G', 'joined', 'member'), ('T', 'joined', 'admin')$$,
|
|
342
|
+
'G7.4.pre');
|
|
343
|
+
select pg_temp.g674_as('G7.4', 'G');
|
|
344
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G7.4'), pg_temp.g674_a('G7.4', 'T'), 'observer'), '42501', 'grant on another member requires owner or admin role', 'G7.4.call');
|
|
345
|
+
select pg_temp.g674_service();
|
|
346
|
+
select is(pg_temp.g674_post('G7.4', 'T'), 'T[joined/admin/pinned] owners=1', 'G7.4.post');
|
|
347
|
+
|
|
348
|
+
-- G8: service, supplied grantor admin -> owner: C-B
|
|
349
|
+
insert into _g674_case values ('G8', pg_temp.g674_board('{"O": "owner", "G": "admin", "T": "owner", "_creator": "O"}'::jsonb));
|
|
350
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G8', 'G,T'),
|
|
351
|
+
$$values ('G', 'joined', 'admin'), ('T', 'joined', 'owner')$$,
|
|
352
|
+
'G8.pre');
|
|
353
|
+
select pg_temp.g674_service();
|
|
354
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, %L::uuid)', pg_temp.g674_b('G8'), pg_temp.g674_a('G8', 'T'), 'member', pg_temp.g674_a('G8', 'G')), '42501', 'grant requires a higher board role than the target', 'G8.call');
|
|
355
|
+
select pg_temp.g674_service();
|
|
356
|
+
select is(pg_temp.g674_post('G8', 'T'), 'T[joined/owner/pinned] owners=2', 'G8.post');
|
|
357
|
+
|
|
358
|
+
-- G8b: service, supplied grantor member -> observer: C-A
|
|
359
|
+
insert into _g674_case values ('G8b', pg_temp.g674_board('{"O": "owner", "G": "member", "T": "observer", "_creator": "O"}'::jsonb));
|
|
360
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G8b', 'G,T'),
|
|
361
|
+
$$values ('G', 'joined', 'member'), ('T', 'joined', 'observer')$$,
|
|
362
|
+
'G8b.pre');
|
|
363
|
+
select pg_temp.g674_service();
|
|
364
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, %L::uuid)', pg_temp.g674_b('G8b'), pg_temp.g674_a('G8b', 'T'), 'member', pg_temp.g674_a('G8b', 'G')), '42501', 'grant on another member requires owner or admin role', 'G8b.call');
|
|
365
|
+
select pg_temp.g674_service();
|
|
366
|
+
select is(pg_temp.g674_post('G8b', 'T'), 'T[joined/observer/pinned] owners=1', 'G8b.post');
|
|
367
|
+
|
|
368
|
+
-- G9: signed, actor admin -> owner: C-B; no evidence row (state read only, O007)
|
|
369
|
+
insert into _g674_case values ('G9', pg_temp.g674_board('{"O": "owner", "G": "admin", "T": "owner", "_creator": "O", "_keys": ["G"]}'::jsonb));
|
|
370
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G9', 'G,T'),
|
|
371
|
+
$$values ('G', 'joined', 'admin'), ('T', 'joined', 'owner')$$,
|
|
372
|
+
'G9.pre');
|
|
373
|
+
select pg_temp.g674_service();
|
|
374
|
+
select throws_ok(format('select * from agent_board.signed_grant_board_role(jsonb_build_object(''p_board_id'', %L, ''p_agent_id'', %L, ''p_grantee_role'', %L), pg_temp.g674_gw(%L::uuid))', pg_temp.g674_b('G9'), pg_temp.g674_a('G9', 'T'), 'member', pg_temp.g674_a('G9', 'G')), '42501', 'grant requires a higher board role than the target', 'G9.call');
|
|
375
|
+
select pg_temp.g674_service();
|
|
376
|
+
select is(pg_temp.g674_post('G9', 'T') || ' evidence=' || pg_temp.g674_evidence('G9', 'T'), 'T[joined/owner/pinned] owners=2 evidence=0', 'G9.post');
|
|
377
|
+
|
|
378
|
+
-- G10: member -> observer, member: C-A
|
|
379
|
+
insert into _g674_case values ('G10', pg_temp.g674_board('{"O": "owner", "G": "member", "T": "observer", "_creator": "O"}'::jsonb));
|
|
380
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G10', 'G,T'),
|
|
381
|
+
$$values ('G', 'joined', 'member'), ('T', 'joined', 'observer')$$,
|
|
382
|
+
'G10.pre');
|
|
383
|
+
select pg_temp.g674_as('G10', 'G');
|
|
384
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G10'), pg_temp.g674_a('G10', 'T'), 'member'), '42501', 'grant on another member requires owner or admin role', 'G10.call');
|
|
385
|
+
select pg_temp.g674_service();
|
|
386
|
+
select is(pg_temp.g674_post('G10', 'T'), 'T[joined/observer/pinned] owners=1', 'G10.post');
|
|
387
|
+
|
|
388
|
+
-- G10b: member -> observer, observer: C-A
|
|
389
|
+
insert into _g674_case values ('G10b', pg_temp.g674_board('{"O": "owner", "G": "member", "T": "observer", "_creator": "O"}'::jsonb));
|
|
390
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G10b', 'G,T'),
|
|
391
|
+
$$values ('G', 'joined', 'member'), ('T', 'joined', 'observer')$$,
|
|
392
|
+
'G10b.pre');
|
|
393
|
+
select pg_temp.g674_as('G10b', 'G');
|
|
394
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G10b'), pg_temp.g674_a('G10b', 'T'), 'observer'), '42501', 'grant on another member requires owner or admin role', 'G10b.call');
|
|
395
|
+
select pg_temp.g674_service();
|
|
396
|
+
select is(pg_temp.g674_post('G10b', 'T'), 'T[joined/observer/pinned] owners=1', 'G10b.post');
|
|
397
|
+
|
|
398
|
+
-- G11: owner -> owner, owner (a retry): C-B (4<=4)
|
|
399
|
+
insert into _g674_case values ('G11', pg_temp.g674_board('{"G": "owner", "T": "owner", "_creator": "G"}'::jsonb));
|
|
400
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G11', 'G,T'),
|
|
401
|
+
$$values ('G', 'joined', 'owner'), ('T', 'joined', 'owner')$$,
|
|
402
|
+
'G11.pre');
|
|
403
|
+
select pg_temp.g674_as('G11', 'G');
|
|
404
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G11'), pg_temp.g674_a('G11', 'T'), 'owner'), '42501', 'grant requires a higher board role than the target', 'G11.call');
|
|
405
|
+
select pg_temp.g674_service();
|
|
406
|
+
select is(pg_temp.g674_post('G11', 'T'), 'T[joined/owner/pinned] owners=2', 'G11.post');
|
|
407
|
+
|
|
408
|
+
-- G12a: target whose only row is left/member: P0002
|
|
409
|
+
insert into _g674_case values ('G12a', pg_temp.g674_board('{"G": "owner", "T": [{"state": "left", "role": "member"}], "_creator": "G"}'::jsonb));
|
|
410
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G12a', 'G,T'),
|
|
411
|
+
$$values ('G', 'joined', 'owner'), ('T', 'left', 'member')$$,
|
|
412
|
+
'G12a.pre');
|
|
413
|
+
select pg_temp.g674_as('G12a', 'G');
|
|
414
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G12a'), pg_temp.g674_a('G12a', 'T'), 'member'), 'P0002', 'grantee has no membership on this board', 'G12a.call');
|
|
415
|
+
select pg_temp.g674_service();
|
|
416
|
+
select is(pg_temp.g674_post('G12a', 'T'), 'T[left/member/pinned] owners=1', 'G12a.post');
|
|
417
|
+
|
|
418
|
+
-- G12b: target whose only row is invited/member: P0002
|
|
419
|
+
insert into _g674_case values ('G12b', pg_temp.g674_board('{"G": "owner", "T": [{"state": "invited", "role": "member"}], "_creator": "G"}'::jsonb));
|
|
420
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G12b', 'G,T'),
|
|
421
|
+
$$values ('G', 'joined', 'owner'), ('T', 'invited', 'member')$$,
|
|
422
|
+
'G12b.pre');
|
|
423
|
+
select pg_temp.g674_as('G12b', 'G');
|
|
424
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G12b'), pg_temp.g674_a('G12b', 'T'), 'member'), 'P0002', 'grantee has no membership on this board', 'G12b.call');
|
|
425
|
+
select pg_temp.g674_service();
|
|
426
|
+
select is(pg_temp.g674_post('G12b', 'T'), 'T[invited/member/pinned] owners=1', 'G12b.post');
|
|
427
|
+
|
|
428
|
+
-- G12c: target whose only row is removed/admin: P0002
|
|
429
|
+
insert into _g674_case values ('G12c', pg_temp.g674_board('{"G": "owner", "T": [{"state": "removed", "role": "admin"}], "_creator": "G"}'::jsonb));
|
|
430
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G12c', 'G,T'),
|
|
431
|
+
$$values ('G', 'joined', 'owner'), ('T', 'removed', 'admin')$$,
|
|
432
|
+
'G12c.pre');
|
|
433
|
+
select pg_temp.g674_as('G12c', 'G');
|
|
434
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G12c'), pg_temp.g674_a('G12c', 'T'), 'member'), 'P0002', 'grantee has no membership on this board', 'G12c.call');
|
|
435
|
+
select pg_temp.g674_service();
|
|
436
|
+
select is(pg_temp.g674_post('G12c', 'T'), 'T[removed/admin/pinned] owners=1', 'G12c.post');
|
|
437
|
+
|
|
438
|
+
-- G12d: target whose only row is left/OWNER (the D.end shape; M16 witness): P0002
|
|
439
|
+
insert into _g674_case values ('G12d', pg_temp.g674_board('{"G": "owner", "T": [{"state": "left", "role": "owner"}], "_creator": "G"}'::jsonb));
|
|
440
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G12d', 'G,T'),
|
|
441
|
+
$$values ('G', 'joined', 'owner'), ('T', 'left', 'owner')$$,
|
|
442
|
+
'G12d.pre');
|
|
443
|
+
select pg_temp.g674_as('G12d', 'G');
|
|
444
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G12d'), pg_temp.g674_a('G12d', 'T'), 'member'), 'P0002', 'grantee has no membership on this board', 'G12d.call');
|
|
445
|
+
select pg_temp.g674_service();
|
|
446
|
+
select is(pg_temp.g674_post('G12d', 'T'), 'T[left/owner/pinned] owners=1', 'G12d.post');
|
|
447
|
+
|
|
448
|
+
-- G12e: absent target: P0002
|
|
449
|
+
insert into _g674_case values ('G12e', pg_temp.g674_board('{"G": "owner", "T": [], "_creator": "G"}'::jsonb));
|
|
450
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G12e', 'G,T'),
|
|
451
|
+
$$values ('G', 'joined', 'owner')$$,
|
|
452
|
+
'G12e.pre');
|
|
453
|
+
select pg_temp.g674_as('G12e', 'G');
|
|
454
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G12e'), pg_temp.g674_a('G12e', 'T'), 'member'), 'P0002', 'grantee has no membership on this board', 'G12e.call');
|
|
455
|
+
select pg_temp.g674_service();
|
|
456
|
+
select is(pg_temp.g674_post('G12e', 'T'), 'T[] owners=1', 'G12e.post');
|
|
457
|
+
|
|
458
|
+
-- G13: member -> owner, admin: the ceiling (3>2) before C-A
|
|
459
|
+
insert into _g674_case values ('G13', pg_temp.g674_board('{"O": "owner", "G": "member", "T": "owner", "_creator": "O"}'::jsonb));
|
|
460
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G13', 'G,T'),
|
|
461
|
+
$$values ('G', 'joined', 'member'), ('T', 'joined', 'owner')$$,
|
|
462
|
+
'G13.pre');
|
|
463
|
+
select pg_temp.g674_as('G13', 'G');
|
|
464
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G13'), pg_temp.g674_a('G13', 'T'), 'admin'), '42501', 'grant exceeds grantor role or board authority ceiling', 'G13.call');
|
|
465
|
+
select pg_temp.g674_service();
|
|
466
|
+
select is(pg_temp.g674_post('G13', 'T'), 'T[joined/owner/pinned] owners=2', 'G13.post');
|
|
467
|
+
|
|
468
|
+
-- G14a: fleet-revoked owner -> member: the fleet gate
|
|
469
|
+
insert into _g674_case values ('G14a', pg_temp.g674_board('{"G": "owner", "T": "member", "_creator": "G", "_revoked": ["G"]}'::jsonb));
|
|
470
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G14a', 'G,T'),
|
|
471
|
+
$$values ('G', 'joined', 'owner'), ('T', 'joined', 'member')$$,
|
|
472
|
+
'G14a.pre');
|
|
473
|
+
select pg_temp.g674_as('G14a', 'G');
|
|
474
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G14a'), pg_temp.g674_a('G14a', 'T'), 'admin'), '42501', 'fleet_revoked: grant_board_role is not permitted for a fleet-revoked agent', 'G14a.call');
|
|
475
|
+
select pg_temp.g674_service();
|
|
476
|
+
select is(pg_temp.g674_post('G14a', 'T'), 'T[joined/member/pinned] owners=1', 'G14a.post');
|
|
477
|
+
|
|
478
|
+
-- G14b: owner -> hosted-delegate target: step 2
|
|
479
|
+
insert into _g674_case values ('G14b', pg_temp.g674_board('{"G": "owner", "T": "member", "_creator": "G", "_delegate": ["T"]}'::jsonb));
|
|
480
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G14b', 'G,T'),
|
|
481
|
+
$$values ('G', 'joined', 'owner'), ('T', 'joined', 'member')$$,
|
|
482
|
+
'G14b.pre');
|
|
483
|
+
select pg_temp.g674_as('G14b', 'G');
|
|
484
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G14b'), pg_temp.g674_a('G14b', 'T'), 'admin'), '42501', 'grant_board_role cannot place or raise a hosted delegate; add_delegate_to_board is its only way onto a board', 'G14b.call');
|
|
485
|
+
select pg_temp.g674_service();
|
|
486
|
+
select is(pg_temp.g674_post('G14b', 'T'), 'T[joined/member/pinned] owners=1', 'G14b.post');
|
|
487
|
+
|
|
488
|
+
-- G16.C2: evidence C.2: admin -> granted owner: C-B
|
|
489
|
+
insert into _g674_case values ('G16.C2', pg_temp.g674_board('{"C": "owner", "G": "admin", "T": "owner", "_creator": "C"}'::jsonb));
|
|
490
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G16.C2', 'G,T'),
|
|
491
|
+
$$values ('G', 'joined', 'admin'), ('T', 'joined', 'owner')$$,
|
|
492
|
+
'G16.C2.pre');
|
|
493
|
+
select pg_temp.g674_as('G16.C2', 'G');
|
|
494
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G16.C2'), pg_temp.g674_a('G16.C2', 'T'), 'member'), '42501', 'grant requires a higher board role than the target', 'G16.C2.call');
|
|
495
|
+
select pg_temp.g674_service();
|
|
496
|
+
select is(pg_temp.g674_post('G16.C2', 'T'), 'T[joined/owner/pinned] owners=2', 'G16.C2.post');
|
|
497
|
+
|
|
498
|
+
-- G16.C4: evidence C.4: admin -> creator-owner: C-B
|
|
499
|
+
insert into _g674_case values ('G16.C4', pg_temp.g674_board('{"G": "admin", "T": "owner", "_creator": "T"}'::jsonb));
|
|
500
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G16.C4', 'G,T'),
|
|
501
|
+
$$values ('G', 'joined', 'admin'), ('T', 'joined', 'owner')$$,
|
|
502
|
+
'G16.C4.pre');
|
|
503
|
+
select pg_temp.g674_as('G16.C4', 'G');
|
|
504
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G16.C4'), pg_temp.g674_a('G16.C4', 'T'), 'member'), '42501', 'grant requires a higher board role than the target', 'G16.C4.call');
|
|
505
|
+
select pg_temp.g674_service();
|
|
506
|
+
select is(pg_temp.g674_post('G16.C4', 'T'), 'T[joined/owner/pinned] owners=1', 'G16.C4.post');
|
|
507
|
+
|
|
508
|
+
-- G16.C7: evidence C.7: member -> admin: C-A
|
|
509
|
+
insert into _g674_case values ('G16.C7', pg_temp.g674_board('{"O": "owner", "G": "member", "T": "admin", "_creator": "O"}'::jsonb));
|
|
510
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G16.C7', 'G,T'),
|
|
511
|
+
$$values ('G', 'joined', 'member'), ('T', 'joined', 'admin')$$,
|
|
512
|
+
'G16.C7.pre');
|
|
513
|
+
select pg_temp.g674_as('G16.C7', 'G');
|
|
514
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G16.C7'), pg_temp.g674_a('G16.C7', 'T'), 'observer'), '42501', 'grant on another member requires owner or admin role', 'G16.C7.call');
|
|
515
|
+
select pg_temp.g674_service();
|
|
516
|
+
select is(pg_temp.g674_post('G16.C7', 'T'), 'T[joined/admin/pinned] owners=1', 'G16.C7.post');
|
|
517
|
+
|
|
518
|
+
-- G16.E1: evidence E.1: member -> owner: C-A
|
|
519
|
+
insert into _g674_case values ('G16.E1', pg_temp.g674_board('{"O": "owner", "G": "member", "T": "owner", "_creator": "O"}'::jsonb));
|
|
520
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G16.E1', 'G,T'),
|
|
521
|
+
$$values ('G', 'joined', 'member'), ('T', 'joined', 'owner')$$,
|
|
522
|
+
'G16.E1.pre');
|
|
523
|
+
select pg_temp.g674_as('G16.E1', 'G');
|
|
524
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G16.E1'), pg_temp.g674_a('G16.E1', 'T'), 'member'), '42501', 'grant on another member requires owner or admin role', 'G16.E1.call');
|
|
525
|
+
select pg_temp.g674_service();
|
|
526
|
+
select is(pg_temp.g674_post('G16.E1', 'T'), 'T[joined/owner/pinned] owners=2', 'G16.E1.post');
|
|
527
|
+
|
|
528
|
+
-- G16.E3: evidence E.3: observer -> creator-owner: C-A
|
|
529
|
+
insert into _g674_case values ('G16.E3', pg_temp.g674_board('{"G": "observer", "T": "owner", "_creator": "T"}'::jsonb));
|
|
530
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G16.E3', 'G,T'),
|
|
531
|
+
$$values ('G', 'joined', 'observer'), ('T', 'joined', 'owner')$$,
|
|
532
|
+
'G16.E3.pre');
|
|
533
|
+
select pg_temp.g674_as('G16.E3', 'G');
|
|
534
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G16.E3'), pg_temp.g674_a('G16.E3', 'T'), 'observer'), '42501', 'grant on another member requires owner or admin role', 'G16.E3.call');
|
|
535
|
+
select pg_temp.g674_service();
|
|
536
|
+
select is(pg_temp.g674_post('G16.E3', 'T'), 'T[joined/owner/pinned] owners=1', 'G16.E3.post');
|
|
537
|
+
|
|
538
|
+
-- G16.E4b: evidence E.4b: member -> owner, gate on: C-A
|
|
539
|
+
insert into _g674_case values ('G16.E4b', pg_temp.g674_board('{"O": "owner", "G": "member", "T": "owner", "_creator": "O", "_gate": true}'::jsonb));
|
|
540
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G16.E4b', 'G,T'),
|
|
541
|
+
$$values ('G', 'joined', 'member'), ('T', 'joined', 'owner')$$,
|
|
542
|
+
'G16.E4b.pre');
|
|
543
|
+
select pg_temp.g674_as('G16.E4b', 'G');
|
|
544
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G16.E4b'), pg_temp.g674_a('G16.E4b', 'T'), 'member'), '42501', 'grant on another member requires owner or admin role', 'G16.E4b.call');
|
|
545
|
+
select pg_temp.g674_service();
|
|
546
|
+
select is(pg_temp.g674_post('G16.E4b', 'T'), 'T[joined/owner/pinned] owners=2', 'G16.E4b.post');
|
|
547
|
+
|
|
548
|
+
-- G16.E4c: evidence E.4c: observer -> creator-owner, gate on: C-A
|
|
549
|
+
insert into _g674_case values ('G16.E4c', pg_temp.g674_board('{"G": "observer", "T": "owner", "_creator": "T", "_gate": true}'::jsonb));
|
|
550
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G16.E4c', 'G,T'),
|
|
551
|
+
$$values ('G', 'joined', 'observer'), ('T', 'joined', 'owner')$$,
|
|
552
|
+
'G16.E4c.pre');
|
|
553
|
+
select pg_temp.g674_as('G16.E4c', 'G');
|
|
554
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G16.E4c'), pg_temp.g674_a('G16.E4c', 'T'), 'observer'), '42501', 'grant on another member requires owner or admin role', 'G16.E4c.call');
|
|
555
|
+
select pg_temp.g674_service();
|
|
556
|
+
select is(pg_temp.g674_post('G16.E4c', 'T'), 'T[joined/owner/pinned] owners=1', 'G16.E4c.post');
|
|
557
|
+
|
|
558
|
+
-- G17: admin -> admin, admin (a retry): C-B (3<=3)
|
|
559
|
+
insert into _g674_case values ('G17', pg_temp.g674_board('{"O": "owner", "G": "admin", "T": "admin", "_creator": "O"}'::jsonb));
|
|
560
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G17', 'G,T'),
|
|
561
|
+
$$values ('G', 'joined', 'admin'), ('T', 'joined', 'admin')$$,
|
|
562
|
+
'G17.pre');
|
|
563
|
+
select pg_temp.g674_as('G17', 'G');
|
|
564
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G17'), pg_temp.g674_a('G17', 'T'), 'admin'), '42501', 'grant requires a higher board role than the target', 'G17.call');
|
|
565
|
+
select pg_temp.g674_service();
|
|
566
|
+
select is(pg_temp.g674_post('G17', 'T'), 'T[joined/admin/pinned] owners=1', 'G17.post');
|
|
567
|
+
|
|
568
|
+
-- G19: owner R -> R's child C (owner, same human, different agent id): C-B
|
|
569
|
+
insert into _g674_case values ('G19', pg_temp.g674_board('{"R": "owner", "T": "owner", "_creator": "R", "_human_of": {"T": "R"}}'::jsonb));
|
|
570
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G19', 'R,T'),
|
|
571
|
+
$$values ('R', 'joined', 'owner'), ('T', 'joined', 'owner')$$,
|
|
572
|
+
'G19.pre');
|
|
573
|
+
select pg_temp.g674_as('G19', 'R');
|
|
574
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G19'), pg_temp.g674_a('G19', 'T'), 'member'), '42501', 'grant requires a higher board role than the target', 'G19.call');
|
|
575
|
+
select pg_temp.g674_service();
|
|
576
|
+
select is(pg_temp.g674_post('G19', 'T'), 'T[joined/owner/pinned] owners=2', 'G19.post');
|
|
577
|
+
|
|
578
|
+
-- G21: admin -> admin; grantor also holds an OLDER removed/owner row (M17 witness): C-B (3<=3)
|
|
579
|
+
insert into _g674_case values ('G21', pg_temp.g674_board('{"O": "owner", "G": [{"state": "removed", "role": "owner"}, {"state": "joined", "role": "admin"}], "T": "admin", "_creator": "O"}'::jsonb));
|
|
580
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G21', 'G,T'),
|
|
581
|
+
$$values ('G', 'removed', 'owner'), ('G', 'joined', 'admin'), ('T', 'joined', 'admin')$$,
|
|
582
|
+
'G21.pre');
|
|
583
|
+
select pg_temp.g674_as('G21', 'G');
|
|
584
|
+
select throws_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G21'), pg_temp.g674_a('G21', 'T'), 'member'), '42501', 'grant requires a higher board role than the target', 'G21.call');
|
|
585
|
+
select pg_temp.g674_service();
|
|
586
|
+
select is(pg_temp.g674_post('G21', 'T'), 'T[joined/admin/pinned] owners=1', 'G21.post');
|
|
587
|
+
|
|
588
|
+
-- A1: owner -> admin, member: 4>3
|
|
589
|
+
insert into _g674_case values ('A1', pg_temp.g674_board('{"G": "owner", "T": "admin", "_creator": "G"}'::jsonb));
|
|
590
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'A1', 'G,T'),
|
|
591
|
+
$$values ('G', 'joined', 'owner'), ('T', 'joined', 'admin')$$,
|
|
592
|
+
'A1.pre');
|
|
593
|
+
select pg_temp.g674_as('A1', 'G');
|
|
594
|
+
select lives_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('A1'), pg_temp.g674_a('A1', 'T'), 'member'), 'A1.call');
|
|
595
|
+
select pg_temp.g674_service();
|
|
596
|
+
select is(pg_temp.g674_post('A1', 'T'), 'T[joined/member/written] owners=1', 'A1.post');
|
|
597
|
+
|
|
598
|
+
-- A2: admin -> member, observer: 3>2
|
|
599
|
+
insert into _g674_case values ('A2', pg_temp.g674_board('{"O": "owner", "G": "admin", "T": "member", "_creator": "O"}'::jsonb));
|
|
600
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'A2', 'G,T'),
|
|
601
|
+
$$values ('G', 'joined', 'admin'), ('T', 'joined', 'member')$$,
|
|
602
|
+
'A2.pre');
|
|
603
|
+
select pg_temp.g674_as('A2', 'G');
|
|
604
|
+
select lives_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('A2'), pg_temp.g674_a('A2', 'T'), 'observer'), 'A2.call');
|
|
605
|
+
select pg_temp.g674_service();
|
|
606
|
+
select is(pg_temp.g674_post('A2', 'T'), 'T[joined/observer/written] owners=1', 'A2.post');
|
|
607
|
+
|
|
608
|
+
-- A3: creator-owner -> itself, admin: self
|
|
609
|
+
insert into _g674_case values ('A3', pg_temp.g674_board('{"G": "owner", "_creator": "G"}'::jsonb));
|
|
610
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'A3', 'G'),
|
|
611
|
+
$$values ('G', 'joined', 'owner')$$,
|
|
612
|
+
'A3.pre');
|
|
613
|
+
select pg_temp.g674_as('A3', 'G');
|
|
614
|
+
select lives_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('A3'), pg_temp.g674_a('A3', 'G'), 'admin'), 'A3.call');
|
|
615
|
+
select pg_temp.g674_service();
|
|
616
|
+
select is(pg_temp.g674_post('A3', 'G'), 'G[joined/admin/written] owners=0', 'A3.post');
|
|
617
|
+
|
|
618
|
+
-- A4: admin -> member, admin: ceiling 3<=3, then 3>2
|
|
619
|
+
insert into _g674_case values ('A4', pg_temp.g674_board('{"O": "owner", "G": "admin", "T": "member", "_creator": "O"}'::jsonb));
|
|
620
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'A4', 'G,T'),
|
|
621
|
+
$$values ('G', 'joined', 'admin'), ('T', 'joined', 'member')$$,
|
|
622
|
+
'A4.pre');
|
|
623
|
+
select pg_temp.g674_as('A4', 'G');
|
|
624
|
+
select lives_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('A4'), pg_temp.g674_a('A4', 'T'), 'admin'), 'A4.call');
|
|
625
|
+
select pg_temp.g674_service();
|
|
626
|
+
select is(pg_temp.g674_post('A4', 'T'), 'T[joined/admin/written] owners=1', 'A4.post');
|
|
627
|
+
|
|
628
|
+
-- A5: admin -> itself, member: self
|
|
629
|
+
insert into _g674_case values ('A5', pg_temp.g674_board('{"O": "owner", "G": "admin", "_creator": "O"}'::jsonb));
|
|
630
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'A5', 'G'),
|
|
631
|
+
$$values ('G', 'joined', 'admin')$$,
|
|
632
|
+
'A5.pre');
|
|
633
|
+
select pg_temp.g674_as('A5', 'G');
|
|
634
|
+
select lives_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('A5'), pg_temp.g674_a('A5', 'G'), 'member'), 'A5.call');
|
|
635
|
+
select pg_temp.g674_service();
|
|
636
|
+
select is(pg_temp.g674_post('A5', 'G'), 'G[joined/member/written] owners=1', 'A5.post');
|
|
637
|
+
|
|
638
|
+
-- A6: owner -> admin, owner: ceiling 4<=4, then 4>3
|
|
639
|
+
insert into _g674_case values ('A6', pg_temp.g674_board('{"G": "owner", "T": "admin", "_creator": "G"}'::jsonb));
|
|
640
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'A6', 'G,T'),
|
|
641
|
+
$$values ('G', 'joined', 'owner'), ('T', 'joined', 'admin')$$,
|
|
642
|
+
'A6.pre');
|
|
643
|
+
select pg_temp.g674_as('A6', 'G');
|
|
644
|
+
select lives_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('A6'), pg_temp.g674_a('A6', 'T'), 'owner'), 'A6.call');
|
|
645
|
+
select pg_temp.g674_service();
|
|
646
|
+
select is(pg_temp.g674_post('A6', 'T'), 'T[joined/owner/written] owners=2', 'A6.post');
|
|
647
|
+
|
|
648
|
+
-- A7: admin -> observer, member: 3>1
|
|
649
|
+
insert into _g674_case values ('A7', pg_temp.g674_board('{"O": "owner", "G": "admin", "T": "observer", "_creator": "O"}'::jsonb));
|
|
650
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'A7', 'G,T'),
|
|
651
|
+
$$values ('G', 'joined', 'admin'), ('T', 'joined', 'observer')$$,
|
|
652
|
+
'A7.pre');
|
|
653
|
+
select pg_temp.g674_as('A7', 'G');
|
|
654
|
+
select lives_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('A7'), pg_temp.g674_a('A7', 'T'), 'member'), 'A7.call');
|
|
655
|
+
select pg_temp.g674_service();
|
|
656
|
+
select is(pg_temp.g674_post('A7', 'T'), 'T[joined/member/written] owners=1', 'A7.post');
|
|
657
|
+
|
|
658
|
+
-- A8: owner -> admin, admin: 4>3
|
|
659
|
+
insert into _g674_case values ('A8', pg_temp.g674_board('{"G": "owner", "T": "admin", "_creator": "G"}'::jsonb));
|
|
660
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'A8', 'G,T'),
|
|
661
|
+
$$values ('G', 'joined', 'owner'), ('T', 'joined', 'admin')$$,
|
|
662
|
+
'A8.pre');
|
|
663
|
+
select pg_temp.g674_as('A8', 'G');
|
|
664
|
+
select lives_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('A8'), pg_temp.g674_a('A8', 'T'), 'admin'), 'A8.call');
|
|
665
|
+
select pg_temp.g674_service();
|
|
666
|
+
select is(pg_temp.g674_post('A8', 'T'), 'T[joined/admin/written] owners=1', 'A8.post');
|
|
667
|
+
|
|
668
|
+
-- A9.1: member -> itself, observer: self
|
|
669
|
+
insert into _g674_case values ('A9.1', pg_temp.g674_board('{"O": "owner", "G": "member", "_creator": "O"}'::jsonb));
|
|
670
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'A9.1', 'G'),
|
|
671
|
+
$$values ('G', 'joined', 'member')$$,
|
|
672
|
+
'A9.1.pre');
|
|
673
|
+
select pg_temp.g674_as('A9.1', 'G');
|
|
674
|
+
select lives_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('A9.1'), pg_temp.g674_a('A9.1', 'G'), 'observer'), 'A9.1.call');
|
|
675
|
+
select pg_temp.g674_service();
|
|
676
|
+
select is(pg_temp.g674_post('A9.1', 'G'), 'G[joined/observer/written] owners=1', 'A9.1.post');
|
|
677
|
+
|
|
678
|
+
-- A9.2: observer -> itself, observer: self
|
|
679
|
+
insert into _g674_case values ('A9.2', pg_temp.g674_board('{"O": "owner", "G": "observer", "_creator": "O"}'::jsonb));
|
|
680
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'A9.2', 'G'),
|
|
681
|
+
$$values ('G', 'joined', 'observer')$$,
|
|
682
|
+
'A9.2.pre');
|
|
683
|
+
select pg_temp.g674_as('A9.2', 'G');
|
|
684
|
+
select lives_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('A9.2'), pg_temp.g674_a('A9.2', 'G'), 'observer'), 'A9.2.call');
|
|
685
|
+
select pg_temp.g674_service();
|
|
686
|
+
select is(pg_temp.g674_post('A9.2', 'G'), 'G[joined/observer/written] owners=1', 'A9.2.post');
|
|
687
|
+
|
|
688
|
+
-- A10.1: A1 with the role gate on
|
|
689
|
+
insert into _g674_case values ('A10.1', pg_temp.g674_board('{"G": "owner", "T": "admin", "_creator": "G", "_gate": true}'::jsonb));
|
|
690
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'A10.1', 'G,T'),
|
|
691
|
+
$$values ('G', 'joined', 'owner'), ('T', 'joined', 'admin')$$,
|
|
692
|
+
'A10.1.pre');
|
|
693
|
+
select pg_temp.g674_as('A10.1', 'G');
|
|
694
|
+
select lives_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('A10.1'), pg_temp.g674_a('A10.1', 'T'), 'member'), 'A10.1.call');
|
|
695
|
+
select pg_temp.g674_service();
|
|
696
|
+
select is(pg_temp.g674_post('A10.1', 'T'), 'T[joined/member/written] owners=1', 'A10.1.post');
|
|
697
|
+
|
|
698
|
+
-- A10.2: A2 with the role gate on
|
|
699
|
+
insert into _g674_case values ('A10.2', pg_temp.g674_board('{"O": "owner", "G": "admin", "T": "member", "_creator": "O", "_gate": true}'::jsonb));
|
|
700
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'A10.2', 'G,T'),
|
|
701
|
+
$$values ('G', 'joined', 'admin'), ('T', 'joined', 'member')$$,
|
|
702
|
+
'A10.2.pre');
|
|
703
|
+
select pg_temp.g674_as('A10.2', 'G');
|
|
704
|
+
select lives_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('A10.2'), pg_temp.g674_a('A10.2', 'T'), 'observer'), 'A10.2.call');
|
|
705
|
+
select pg_temp.g674_service();
|
|
706
|
+
select is(pg_temp.g674_post('A10.2', 'T'), 'T[joined/observer/written] owners=1', 'A10.2.post');
|
|
707
|
+
|
|
708
|
+
-- A10.3: A3 with the role gate on
|
|
709
|
+
insert into _g674_case values ('A10.3', pg_temp.g674_board('{"G": "owner", "_creator": "G", "_gate": true}'::jsonb));
|
|
710
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'A10.3', 'G'),
|
|
711
|
+
$$values ('G', 'joined', 'owner')$$,
|
|
712
|
+
'A10.3.pre');
|
|
713
|
+
select pg_temp.g674_as('A10.3', 'G');
|
|
714
|
+
select lives_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('A10.3'), pg_temp.g674_a('A10.3', 'G'), 'admin'), 'A10.3.call');
|
|
715
|
+
select pg_temp.g674_service();
|
|
716
|
+
select is(pg_temp.g674_post('A10.3', 'G'), 'G[joined/admin/written] owners=0', 'A10.3.post');
|
|
717
|
+
|
|
718
|
+
-- A10.4: A4 with the role gate on
|
|
719
|
+
insert into _g674_case values ('A10.4', pg_temp.g674_board('{"O": "owner", "G": "admin", "T": "member", "_creator": "O", "_gate": true}'::jsonb));
|
|
720
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'A10.4', 'G,T'),
|
|
721
|
+
$$values ('G', 'joined', 'admin'), ('T', 'joined', 'member')$$,
|
|
722
|
+
'A10.4.pre');
|
|
723
|
+
select pg_temp.g674_as('A10.4', 'G');
|
|
724
|
+
select lives_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('A10.4'), pg_temp.g674_a('A10.4', 'T'), 'admin'), 'A10.4.call');
|
|
725
|
+
select pg_temp.g674_service();
|
|
726
|
+
select is(pg_temp.g674_post('A10.4', 'T'), 'T[joined/admin/written] owners=1', 'A10.4.post');
|
|
727
|
+
|
|
728
|
+
-- A11: service, supplied grantor owner -> admin, member: 4>3
|
|
729
|
+
insert into _g674_case values ('A11', pg_temp.g674_board('{"G": "owner", "T": "admin", "_creator": "G"}'::jsonb));
|
|
730
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'A11', 'G,T'),
|
|
731
|
+
$$values ('G', 'joined', 'owner'), ('T', 'joined', 'admin')$$,
|
|
732
|
+
'A11.pre');
|
|
733
|
+
select pg_temp.g674_service();
|
|
734
|
+
select lives_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, %L::uuid)', pg_temp.g674_b('A11'), pg_temp.g674_a('A11', 'T'), 'member', pg_temp.g674_a('A11', 'G')), 'A11.call');
|
|
735
|
+
select pg_temp.g674_service();
|
|
736
|
+
select is(pg_temp.g674_post('A11', 'T'), 'T[joined/member/written] owners=1', 'A11.post');
|
|
737
|
+
|
|
738
|
+
-- A12: signed, actor owner -> admin, member: 4>3; exactly one evidence row
|
|
739
|
+
insert into _g674_case values ('A12', pg_temp.g674_board('{"G": "owner", "T": "admin", "_creator": "G", "_keys": ["G"]}'::jsonb));
|
|
740
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'A12', 'G,T'),
|
|
741
|
+
$$values ('G', 'joined', 'owner'), ('T', 'joined', 'admin')$$,
|
|
742
|
+
'A12.pre');
|
|
743
|
+
select pg_temp.g674_service();
|
|
744
|
+
select lives_ok(format('select * from agent_board.signed_grant_board_role(jsonb_build_object(''p_board_id'', %L, ''p_agent_id'', %L, ''p_grantee_role'', %L), pg_temp.g674_gw(%L::uuid))', pg_temp.g674_b('A12'), pg_temp.g674_a('A12', 'T'), 'member', pg_temp.g674_a('A12', 'G')), 'A12.call');
|
|
745
|
+
select pg_temp.g674_service();
|
|
746
|
+
select is(pg_temp.g674_post('A12', 'T') || ' evidence=' || pg_temp.g674_evidence('A12', 'T'), 'T[joined/member/written] owners=1 evidence=1', 'A12.post');
|
|
747
|
+
|
|
748
|
+
-- G8c: service, p_grantor_agent_id = p_agent_id = an owner: the D8 self-exemption keyed on the SUPPLIED id (recorded, not endorsed)
|
|
749
|
+
insert into _g674_case values ('G8c', pg_temp.g674_board('{"O": "owner", "G": "owner", "_creator": "O"}'::jsonb));
|
|
750
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G8c', 'G'),
|
|
751
|
+
$$values ('G', 'joined', 'owner')$$,
|
|
752
|
+
'G8c.pre');
|
|
753
|
+
select pg_temp.g674_service();
|
|
754
|
+
select lives_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, %L::uuid)', pg_temp.g674_b('G8c'), pg_temp.g674_a('G8c', 'G'), 'member', pg_temp.g674_a('G8c', 'G')), 'G8c.call');
|
|
755
|
+
select pg_temp.g674_service();
|
|
756
|
+
select is(pg_temp.g674_post('G8c', 'G'), 'G[joined/member/written] owners=1', 'G8c.post');
|
|
757
|
+
|
|
758
|
+
-- G20: owner -> member with an OLDER left/member row (M15 witness): 4>2, only the active row changes
|
|
759
|
+
insert into _g674_case values ('G20', pg_temp.g674_board('{"G": "owner", "T": [{"state": "left", "role": "member"}, {"state": "joined", "role": "member"}], "_creator": "G"}'::jsonb));
|
|
760
|
+
select results_eq(format('select label, membership_state, role from pg_temp.g674_rows(%L, %L)', 'G20', 'G,T'),
|
|
761
|
+
$$values ('G', 'joined', 'owner'), ('T', 'left', 'member'), ('T', 'joined', 'member')$$,
|
|
762
|
+
'G20.pre');
|
|
763
|
+
select pg_temp.g674_as('G20', 'G');
|
|
764
|
+
select lives_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, %L, null)', pg_temp.g674_b('G20'), pg_temp.g674_a('G20', 'T'), 'observer'), 'G20.call');
|
|
765
|
+
select pg_temp.g674_service();
|
|
766
|
+
select is(pg_temp.g674_post('G20', 'T'), 'T[left/member/pinned,joined/observer/written] owners=1', 'G20.post');
|
|
767
|
+
|
|
768
|
+
|
|
769
|
+
-- ===========================================================================
|
|
770
|
+
-- EC-1 (rule 1): the caller set of grant_board_role, from the catalog (O006)
|
|
771
|
+
-- ===========================================================================
|
|
772
|
+
-- Every namespace except the system ones, pg_temp* (this file's own helpers call the RPC) and
|
|
773
|
+
-- the test tooling (agent_board_test, and pgTAP itself wherever it is installed: excluded as an
|
|
774
|
+
-- extension member, not by a schema name). The subject KEEPS literals (stripped_body(oid, true)),
|
|
775
|
+
-- so a dynamic-SQL caller -- execute 'select agent_board.grant_board_role(...' -- is caught. A
|
|
776
|
+
-- false positive names itself, which is the safe direction.
|
|
777
|
+
select is(
|
|
778
|
+
(select coalesce(string_agg(n.nspname || '.' || p.proname, ',' order by n.nspname, p.proname), '(none)')
|
|
779
|
+
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
|
780
|
+
where n.nspname not in ('pg_catalog', 'information_schema', 'pg_toast', 'agent_board_test')
|
|
781
|
+
and left(n.nspname, 7) <> 'pg_temp' and left(n.nspname, 13) <> 'pg_toast_temp'
|
|
782
|
+
and p.prokind in ('f', 'p')
|
|
783
|
+
and not exists (select 1 from pg_depend d
|
|
784
|
+
where d.classid = 'pg_proc'::regclass and d.objid = p.oid and d.deptype = 'e')
|
|
785
|
+
and agent_board_test.stripped_body(p.oid, true) ~ agent_board_test.call_re('grant_board_role')),
|
|
786
|
+
'agent_board.signed_grant_board_role',
|
|
787
|
+
'EC-1.callers: the only function in any non-system namespace that calls grant_board_role is signed_grant_board_role (literals kept, so dynamic SQL counts)');
|
|
788
|
+
select is(
|
|
789
|
+
(select coalesce(string_agg(n.nspname || '.' || c.relname, ',' order by n.nspname, c.relname), '(none)')
|
|
790
|
+
from pg_class c join pg_namespace n on n.oid = c.relnamespace
|
|
791
|
+
where c.relkind in ('v', 'm')
|
|
792
|
+
and n.nspname not in ('pg_catalog', 'information_schema', 'agent_board_test')
|
|
793
|
+
and agent_board_test.stripped_viewdef(c.oid) ~ agent_board_test.call_re('grant_board_role')),
|
|
794
|
+
'(none)',
|
|
795
|
+
'EC-1.views: no view calls grant_board_role (triggers resolve to functions, covered above)');
|
|
796
|
+
|
|
797
|
+
-- ===========================================================================
|
|
798
|
+
-- EC-2 (rule 1): every writer of board_memberships.role, as a closed-world registry
|
|
799
|
+
-- ===========================================================================
|
|
800
|
+
-- The class is the agent_board_delegate_role_class.test.sql derivation: every agent_board
|
|
801
|
+
-- function whose stripped body matches col_write_re('board_memberships', 'role'), plus the sink.
|
|
802
|
+
-- Each member carries exactly one disposition; the registry must EQUAL the derived set, both
|
|
803
|
+
-- directions, so a new writer (or a deleted one) names itself. Each disposition is then proved
|
|
804
|
+
-- POSITIVELY, by behavior, below: bystanders U (owner) and W (admin) keep their role and
|
|
805
|
+
-- updated_at, and the only role written is the one the disposition names.
|
|
806
|
+
create temp table _674_disposition(proname text primary key, disposition text not null) on commit drop;
|
|
807
|
+
insert into _674_disposition values
|
|
808
|
+
('grant_board_role', 'gated: C-A + C-B (cases G1-G21, A1-A12)'),
|
|
809
|
+
('create_board', 'new boards only: the creator, on the board it just inserted'),
|
|
810
|
+
('bootstrap_board_owner_memberships', 'service: seats or raises the CREATOR to owner only'),
|
|
811
|
+
('_fedsession_autojoin_default_board', 'inserts a new row for the joining agent only'),
|
|
812
|
+
('_redeem_unbounded_invite_for_agent', 'rank-max on the redeemer''s own row only'),
|
|
813
|
+
('mint_delegated_child', 'service: a fresh agent only, bounded by the parent'),
|
|
814
|
+
('link_session_alias', 'service: narrows the identity lineage only'),
|
|
815
|
+
('readmit_session_identity', 'a fresh agent only, role <= the old one'),
|
|
816
|
+
('_upsert_board_membership_state', 'the sink: member only, on non-active -> active re-entry');
|
|
817
|
+
create temp view _674_role_writers as
|
|
818
|
+
select p.proname::text as proname
|
|
819
|
+
from pg_proc p
|
|
820
|
+
where p.pronamespace = 'agent_board'::regnamespace
|
|
821
|
+
and agent_board_test.stripped_body(p.oid) ~ agent_board_test.col_write_re('board_memberships', 'role')
|
|
822
|
+
union
|
|
823
|
+
select '_upsert_board_membership_state';
|
|
824
|
+
select is(
|
|
825
|
+
(select coalesce(string_agg(x, ', ' order by x), '')
|
|
826
|
+
from (select 'derived, not registered: ' || w.proname as x from _674_role_writers w
|
|
827
|
+
where not exists (select 1 from _674_disposition d where d.proname = w.proname)
|
|
828
|
+
union all
|
|
829
|
+
select 'registered, not derived: ' || d.proname from _674_disposition d
|
|
830
|
+
where not exists (select 1 from _674_role_writers w where w.proname = d.proname)) s),
|
|
831
|
+
'',
|
|
832
|
+
'EC-2.registry: the board_memberships.role writers derived from pg_proc equal the disposition registry, both directions');
|
|
833
|
+
|
|
834
|
+
-- grant_board_role: gated. The D6a and D6b literals are present in the one body (loud-literal).
|
|
835
|
+
-- source-scan: loud-literal one body, named by regprocedure; a miss fails
|
|
836
|
+
select ok(
|
|
837
|
+
strpos(agent_board_test.stripped_body('agent_board.grant_board_role(uuid,uuid,text,uuid)'::regprocedure, true),
|
|
838
|
+
'grant on another member requires owner or admin role') > 0,
|
|
839
|
+
'EC-2.grant.D6a: grant_board_role carries the C-A refusal');
|
|
840
|
+
-- source-scan: loud-literal one body, named by regprocedure; a miss fails
|
|
841
|
+
select ok(
|
|
842
|
+
strpos(agent_board_test.stripped_body('agent_board.grant_board_role(uuid,uuid,text,uuid)'::regprocedure, true),
|
|
843
|
+
'grant requires a higher board role than the target') > 0,
|
|
844
|
+
'EC-2.grant.D6b: grant_board_role carries the C-B refusal');
|
|
845
|
+
|
|
846
|
+
-- create_board: new boards only (G2-002). Its only board_memberships write is the new-slug
|
|
847
|
+
-- branch; the slug-collision branch writes boards only and refuses a non-owner.
|
|
848
|
+
-- source-scan: loud-literal one body, named by regprocedure; a miss fails
|
|
849
|
+
select ok(
|
|
850
|
+
strpos(lower(agent_board_test.stripped_body('agent_board.create_board(uuid,text,text,text,jsonb,text)'::regprocedure)),
|
|
851
|
+
'insert into agent_board.board_memberships') > 0,
|
|
852
|
+
'EC-2.create_board.pin: the new-branch membership insert is present');
|
|
853
|
+
insert into _g674_case values ('EC2.cb', pg_temp.g674_board('{"O": "owner", "U": "owner", "W": "admin", "_creator": "O"}'::jsonb));
|
|
854
|
+
select pg_temp.g674_as('EC2.cb', 'O');
|
|
855
|
+
select lives_ok(
|
|
856
|
+
format('select * from agent_board.create_board(%L::uuid, %L, %L, null, ''{}''::jsonb, ''private'')',
|
|
857
|
+
pg_temp.g674_a('EC2.cb', 'O'), 'g674-ec2-new-' || pg_temp.g674_b('EC2.cb'), 'EC2 new board'),
|
|
858
|
+
'EC-2.create_board.a.call: a new slug creates a board');
|
|
859
|
+
select pg_temp.g674_service();
|
|
860
|
+
select is(
|
|
861
|
+
(select string_agg(a.label || '/' || bm.membership_state || '/' || bm.role, ',')
|
|
862
|
+
from agent_board.boards b
|
|
863
|
+
join agent_board.board_memberships bm on bm.board_id = b.id
|
|
864
|
+
left join _g674_agent a on a.agent_id = bm.agent_id and a.board = pg_temp.g674_b('EC2.cb')
|
|
865
|
+
where b.slug = 'g674-ec2-new-' || pg_temp.g674_b('EC2.cb')),
|
|
866
|
+
'O/joined/owner',
|
|
867
|
+
'EC-2.create_board.a.post: the new board has exactly one membership, the creator as owner');
|
|
868
|
+
select is(pg_temp.g674_who('EC2.cb', 'O,U,W'),
|
|
869
|
+
'O[joined/owner/pinned] owners=2 U[joined/owner/pinned] owners=2 W[joined/admin/pinned] owners=2',
|
|
870
|
+
'EC-2.create_board.a.bystanders: no row on the creator''s existing board changed');
|
|
871
|
+
select pg_temp.g674_as('EC2.cb', 'O');
|
|
872
|
+
select lives_ok(
|
|
873
|
+
format('select * from agent_board.create_board(%L::uuid, %L, %L, null, ''{}''::jsonb, ''private'')',
|
|
874
|
+
pg_temp.g674_a('EC2.cb', 'O'), (select slug from agent_board.boards where id = pg_temp.g674_b('EC2.cb')), 'EC2 renamed'),
|
|
875
|
+
'EC-2.create_board.b.call: the current owner''s slug collision is admitted (boards-only update)');
|
|
876
|
+
select pg_temp.g674_service();
|
|
877
|
+
select is(pg_temp.g674_who('EC2.cb', 'O,U,W'),
|
|
878
|
+
'O[joined/owner/pinned] owners=2 U[joined/owner/pinned] owners=2 W[joined/admin/pinned] owners=2',
|
|
879
|
+
'EC-2.create_board.b.post: an owner''s collision leaves every membership row (role and updated_at) unchanged');
|
|
880
|
+
select pg_temp.g674_as('EC2.cb', 'W');
|
|
881
|
+
select throws_ok(
|
|
882
|
+
format('select * from agent_board.create_board(%L::uuid, %L, %L, null, ''{}''::jsonb, ''open'')',
|
|
883
|
+
pg_temp.g674_a('EC2.cb', 'W'), (select slug from agent_board.boards where id = pg_temp.g674_b('EC2.cb')), 'HIJACK'),
|
|
884
|
+
'42501', 'board slug already in use',
|
|
885
|
+
'EC-2.create_board.c.call: an admin''s slug collision is refused');
|
|
886
|
+
select pg_temp.g674_service();
|
|
887
|
+
select is(pg_temp.g674_who('EC2.cb', 'O,U,W'),
|
|
888
|
+
'O[joined/owner/pinned] owners=2 U[joined/owner/pinned] owners=2 W[joined/admin/pinned] owners=2',
|
|
889
|
+
'EC-2.create_board.c.post: the refused collision changed nothing');
|
|
890
|
+
|
|
891
|
+
-- bootstrap_board_owner_memberships (service): seats the CREATOR as owner; bystanders unchanged.
|
|
892
|
+
insert into _g674_case values ('EC2.boot', pg_temp.g674_board('{"Z": [], "U": "owner", "W": "admin", "_creator": "Z"}'::jsonb));
|
|
893
|
+
select pg_temp.g674_service();
|
|
894
|
+
select lives_ok('select * from agent_board.bootstrap_board_owner_memberships()',
|
|
895
|
+
'EC-2.bootstrap.call: the service repair runs');
|
|
896
|
+
select is(pg_temp.g674_who('EC2.boot', 'U,W,Z'),
|
|
897
|
+
'U[joined/owner/pinned] owners=2 W[joined/admin/pinned] owners=2 Z[joined/owner/written] owners=2',
|
|
898
|
+
'EC-2.bootstrap.post: only the creator''s row is written (as owner); U and W unchanged');
|
|
899
|
+
|
|
900
|
+
-- _fedsession_autojoin_default_board: inserts a NEW row for the joining agent only.
|
|
901
|
+
insert into _g674_case values ('EC2.fed', pg_temp.g674_board('{"H": [], "U": "owner", "W": "admin", "_creator": "U"}'::jsonb));
|
|
902
|
+
insert into agent_board.identity_providers(issuer, audience, supported, policy_approved, allowed_agent_kinds, default_board_id)
|
|
903
|
+
values ('https://g674-' || pg_temp.g674_b('EC2.fed') || '.test', 'message-board', true, true, '{human}', pg_temp.g674_b('EC2.fed'));
|
|
904
|
+
select lives_ok(
|
|
905
|
+
format('select agent_board._fedsession_autojoin_default_board(ip, %L::uuid) from agent_board.identity_providers ip where ip.issuer = %L',
|
|
906
|
+
pg_temp.g674_a('EC2.fed', 'H'), 'https://g674-' || pg_temp.g674_b('EC2.fed') || '.test'),
|
|
907
|
+
'EC-2.autojoin.call: the default-board auto-join runs');
|
|
908
|
+
select is(pg_temp.g674_who('EC2.fed', 'H,U,W'),
|
|
909
|
+
'H[joined/member/written] owners=1 U[joined/owner/pinned] owners=1 W[joined/admin/pinned] owners=1',
|
|
910
|
+
'EC-2.autojoin.post: one new member row for the joining agent; U and W unchanged');
|
|
911
|
+
|
|
912
|
+
-- _redeem_unbounded_invite_for_agent: rank-max on the REDEEMER's own row only. A member
|
|
913
|
+
-- redeeming a lower (observer) preset keeps member (as agent_board_roomlink.test.sql R-LINK-1).
|
|
914
|
+
-- source-scan: loud-literal one body, named by regprocedure; a miss fails
|
|
915
|
+
select ok(
|
|
916
|
+
strpos(lower(agent_board_test.stripped_body('agent_board._redeem_unbounded_invite_for_agent(text,uuid)'::regprocedure)),
|
|
917
|
+
'agent_board._role_rank(excluded.role) >') > 0,
|
|
918
|
+
'EC-2.redeem.pin: the rank-max CASE (_role_rank(excluded.role) >) is present');
|
|
919
|
+
insert into _g674_case values ('EC2.redeem', pg_temp.g674_board('{"M": "member", "U": "owner", "W": "admin", "_creator": "U"}'::jsonb));
|
|
920
|
+
create temp table _674_link on commit drop as
|
|
921
|
+
select code from agent_board.create_invite_link(pg_temp.g674_b('EC2.redeem'), 'observer', 'joined', null, null,
|
|
922
|
+
pg_temp.g674_a('EC2.redeem', 'U'), '{}'::jsonb);
|
|
923
|
+
select lives_ok(
|
|
924
|
+
format('select * from agent_board._redeem_unbounded_invite_for_agent(%L, %L::uuid)',
|
|
925
|
+
(select code from _674_link), pg_temp.g674_a('EC2.redeem', 'M')),
|
|
926
|
+
'EC-2.redeem.call: a member redeems an observer-preset link');
|
|
927
|
+
select is(pg_temp.g674_who('EC2.redeem', 'M,U,W'),
|
|
928
|
+
'M[joined/member/written] owners=1 U[joined/owner/pinned] owners=1 W[joined/admin/pinned] owners=1',
|
|
929
|
+
'EC-2.redeem.post: the redeemer keeps member (rank-max, never lowered); U and W unchanged');
|
|
930
|
+
|
|
931
|
+
-- The sink: role is written only as member, on non-active -> active re-entry.
|
|
932
|
+
insert into _g674_case values ('EC2.sink', pg_temp.g674_board('{"B": "admin", "U": "owner", "W": "admin", "_creator": "U"}'::jsonb));
|
|
933
|
+
select lives_ok(
|
|
934
|
+
format('select agent_board._upsert_board_membership_state(%L::uuid, %L::uuid, ''left'')',
|
|
935
|
+
pg_temp.g674_b('EC2.sink'), pg_temp.g674_a('EC2.sink', 'B')),
|
|
936
|
+
'EC-2.sink.leave.call: a bystander admin leaves through the sink');
|
|
937
|
+
select lives_ok(
|
|
938
|
+
format('select agent_board._upsert_board_membership_state(%L::uuid, %L::uuid, ''joined'')',
|
|
939
|
+
pg_temp.g674_b('EC2.sink'), pg_temp.g674_a('EC2.sink', 'B')),
|
|
940
|
+
'EC-2.sink.rejoin.call: and rejoins through the sink');
|
|
941
|
+
select is(
|
|
942
|
+
(select bm.role from agent_board.board_memberships bm
|
|
943
|
+
where bm.board_id = pg_temp.g674_b('EC2.sink') and bm.agent_id = pg_temp.g674_a('EC2.sink', 'B')
|
|
944
|
+
and bm.membership_state in ('joined', 'subscribed')),
|
|
945
|
+
'member',
|
|
946
|
+
'EC-2.sink.post: re-entry is clamped to member (ROLERESET)');
|
|
947
|
+
select is(pg_temp.g674_who('EC2.sink', 'U,W'),
|
|
948
|
+
'U[joined/owner/pinned] owners=1 W[joined/admin/pinned] owners=1',
|
|
949
|
+
'EC-2.sink.bystanders: U and W unchanged across the leave and rejoin');
|
|
950
|
+
|
|
951
|
+
-- The session-identity writers, on one board seating root R (owner), its human owner HO
|
|
952
|
+
-- (owner), and bystanders U (owner) and W (admin). The fixture follows
|
|
953
|
+
-- agent_board_session_identity_link.test.sql: a real root policy, real mints, real lease claims.
|
|
954
|
+
insert into _g674_case values ('EC2.sid',
|
|
955
|
+
pg_temp.g674_board('{"HO": "owner", "R": "owner", "U": "owner", "W": "admin", "_creator": "R", "_human_of": {"R": "HO"}}'::jsonb));
|
|
956
|
+
create temp table _674_sid(label text primary key, agent_id uuid, key_id uuid, auth_id uuid, epoch bigint) on commit drop;
|
|
957
|
+
grant select, insert, update on _674_sid to authenticated, service_role;
|
|
958
|
+
grant execute on function public.test_pubkey(text) to authenticated;
|
|
959
|
+
update agent_board.agents set agent_type = 'human' where id = pg_temp.g674_a('EC2.sid', 'HO');
|
|
960
|
+
insert into agent_board.session_identities(agent_id, root_agent_id, depth, approved_risky_digest, current_risky_digest)
|
|
961
|
+
values (pg_temp.g674_a('EC2.sid', 'R'), pg_temp.g674_a('EC2.sid', 'R'), 0, 'approved', 'approved');
|
|
962
|
+
create function pg_temp.g674_mint(p_label text, p_parent text, p_role text, p_fork text default null) returns void language plpgsql as $$
|
|
963
|
+
declare v_parent uuid; v_res record;
|
|
964
|
+
begin
|
|
965
|
+
v_parent := coalesce((select agent_id from _674_sid where label = p_parent), pg_temp.g674_a('EC2.sid', 'R'));
|
|
966
|
+
select * into v_res from agent_board.mint_delegated_child(v_parent,
|
|
967
|
+
'g674-sid-' || lower(p_label) || '-' || substr(replace(pg_temp.g674_b('EC2.sid')::text, '-', ''), 1, 12),
|
|
968
|
+
'["read","write"]', public.test_pubkey('g674-sid-' || p_label || pg_temp.g674_b('EC2.sid')), interval '8 hours', false,
|
|
969
|
+
'claude', 'g674-sid-' || p_label, case when p_parent is null then null else '0' end,
|
|
970
|
+
jsonb_build_array(jsonb_build_object('board_id', pg_temp.g674_b('EC2.sid'), 'role', p_role)),
|
|
971
|
+
'{"resolution":"resolved","risky_digest":"approved"}', (select agent_id from _674_sid where label = p_fork));
|
|
972
|
+
insert into _674_sid select p_label, v_res.child_agent_id, v_res.signing_key_id, a.auth_user_id, null
|
|
973
|
+
from agent_board.agents a where a.id = v_res.child_agent_id;
|
|
974
|
+
end;
|
|
975
|
+
$$;
|
|
976
|
+
create function pg_temp.g674_claim(p_label text) returns void language plpgsql as $$
|
|
977
|
+
declare v_child record; v_challenge record;
|
|
978
|
+
begin
|
|
979
|
+
select * into v_child from _674_sid where label = p_label;
|
|
980
|
+
select * into v_challenge from agent_board.issue_signing_key_challenge(v_child.agent_id, 'delegated-ephemeral');
|
|
981
|
+
update _674_sid set epoch = (
|
|
982
|
+
select m.identity_lease_epoch from agent_board.mint_fleet_agent_session(v_challenge.challenge_id,
|
|
983
|
+
split_part(v_challenge.challenge, ':', 3), v_child.key_id, v_child.agent_id, 900, null, 'g674-sid-' || p_label) m)
|
|
984
|
+
where label = p_label;
|
|
985
|
+
end;
|
|
986
|
+
$$;
|
|
987
|
+
create function pg_temp.g674_link(p_label text, p_alias text) returns void language plpgsql as $$
|
|
988
|
+
begin
|
|
989
|
+
perform set_config('request.jwt.claim.sub', (select auth_id::text from _674_sid where label = p_label), true);
|
|
990
|
+
perform set_config('request.jwt.claim.identity_lease_epoch', (select epoch::text from _674_sid where label = p_label), true);
|
|
991
|
+
perform agent_board.link_session_alias((select agent_id from _674_sid where label = p_label),
|
|
992
|
+
(select epoch::text from _674_sid where label = p_label), p_alias);
|
|
993
|
+
end;
|
|
994
|
+
$$;
|
|
995
|
+
create function pg_temp.g674_sid_role(p_label text) returns text language sql stable as $$
|
|
996
|
+
select coalesce(string_agg(bm.membership_state || '/' || bm.role, ',' order by bm.created_at, bm.id), '(none)')
|
|
997
|
+
from agent_board.board_memberships bm
|
|
998
|
+
where bm.board_id = pg_temp.g674_b('EC2.sid') and bm.agent_id = (select agent_id from _674_sid where label = p_label)
|
|
999
|
+
$$;
|
|
1000
|
+
grant execute on function pg_temp.g674_mint(text, text, text, text), pg_temp.g674_claim(text), pg_temp.g674_link(text, text),
|
|
1001
|
+
pg_temp.g674_sid_role(text), pg_temp.g674_b(text), pg_temp.g674_a(text, text) to service_role, authenticated;
|
|
1002
|
+
|
|
1003
|
+
select set_config('request.jwt.claim.role', 'service_role', true);
|
|
1004
|
+
set local role service_role;
|
|
1005
|
+
select lives_ok(format('select agent_board.upsert_delegation_policy(%L::uuid, ''root_minted'', ''["read","write"]'', 200, interval ''7 days'', true)',
|
|
1006
|
+
pg_temp.g674_a('EC2.sid', 'R')),
|
|
1007
|
+
'EC-2.sid.policy: a real root policy permits the fixture');
|
|
1008
|
+
|
|
1009
|
+
-- mint_delegated_child: a fresh agent only, bounded by the parent.
|
|
1010
|
+
-- source-scan: loud-literal one body, named by regprocedure; a miss fails
|
|
1011
|
+
select ok(
|
|
1012
|
+
strpos(lower(agent_board_test.stripped_body('agent_board.mint_delegated_child(uuid,text,jsonb,text,interval,boolean,text,text,text,jsonb,jsonb,uuid)'::regprocedure)),
|
|
1013
|
+
'where id = v_membership.id') > 0,
|
|
1014
|
+
'EC-2.mint.pin: mint sets role only on the membership row it just seated');
|
|
1015
|
+
select lives_ok($q$select pg_temp.g674_mint('I', null, 'owner')$q$,
|
|
1016
|
+
'EC-2.mint.call: the root mints identity I onto the board as owner');
|
|
1017
|
+
reset role;
|
|
1018
|
+
select is(pg_temp.g674_sid_role('I'), 'joined/owner', 'EC-2.mint.post: the fresh child holds the requested role');
|
|
1019
|
+
select is(pg_temp.g674_who('EC2.sid', 'HO,R,U,W'),
|
|
1020
|
+
'HO[joined/owner/pinned] owners=4 R[joined/owner/pinned] owners=4 U[joined/owner/pinned] owners=4 W[joined/admin/pinned] owners=4',
|
|
1021
|
+
'EC-2.mint.bystanders: the root, its human owner, U and W are unchanged');
|
|
1022
|
+
set local role service_role;
|
|
1023
|
+
select lives_ok($q$select pg_temp.g674_claim('I'); select pg_temp.g674_mint('C', 'I', 'owner'); select pg_temp.g674_claim('C')$q$,
|
|
1024
|
+
'EC-2.sid.fixture: I claims its lease and mints its own child C (owner)');
|
|
1025
|
+
reset role;
|
|
1026
|
+
select is(pg_temp.g674_sid_role('C'), 'joined/owner', 'EC-2.sid.fixture-state: I and C both hold owner');
|
|
1027
|
+
|
|
1028
|
+
-- Narrow the parent (the root) to admin through its own self-grant (service, supplied = target).
|
|
1029
|
+
select pg_temp.g674_service();
|
|
1030
|
+
select lives_ok(format('select * from agent_board.grant_board_role(%L::uuid, %L::uuid, ''admin'', %L::uuid)',
|
|
1031
|
+
pg_temp.g674_b('EC2.sid'), pg_temp.g674_a('EC2.sid', 'R'), pg_temp.g674_a('EC2.sid', 'R')),
|
|
1032
|
+
'EC-2.link.narrow: the parent R is narrowed to admin by its own self-grant');
|
|
1033
|
+
|
|
1034
|
+
-- mint above the parent's role is refused.
|
|
1035
|
+
select set_config('request.jwt.claim.role', 'service_role', true);
|
|
1036
|
+
set local role service_role;
|
|
1037
|
+
select throws_ok($q$select pg_temp.g674_mint('X', null, 'owner')$q$,
|
|
1038
|
+
'42501', 'room_authority_exceeded',
|
|
1039
|
+
'EC-2.mint.above.call: a mint above the parent''s (now admin) role is refused');
|
|
1040
|
+
reset role;
|
|
1041
|
+
select is((select count(*)::int from _674_sid where label = 'X'), 0, 'EC-2.mint.above.post: no child was minted');
|
|
1042
|
+
|
|
1043
|
+
-- link_session_alias: narrows only the identity lineage (I and its child C), clamped to the
|
|
1044
|
+
-- parent's role. U, W, R and HO are outside the lineage and unchanged.
|
|
1045
|
+
create temp table _674_before on commit drop as
|
|
1046
|
+
select pg_temp.g674_rows_only('EC2.sid', 'HO,R,U,W') as who;
|
|
1047
|
+
select set_config('request.jwt.claim.role', 'service_role', true);
|
|
1048
|
+
set local role service_role;
|
|
1049
|
+
select lives_ok($q$select pg_temp.g674_link('I', 'g674-sid-I-later')$q$,
|
|
1050
|
+
'EC-2.link.call: I''s verified holder links a new alias');
|
|
1051
|
+
reset role;
|
|
1052
|
+
select is(pg_temp.g674_sid_role('I') || ' ' || pg_temp.g674_sid_role('C'), 'joined/admin joined/admin',
|
|
1053
|
+
'EC-2.link.post: I and its descendant C are clamped to the parent''s admin');
|
|
1054
|
+
select is(pg_temp.g674_rows_only('EC2.sid', 'HO,R,U,W'), (select who from _674_before),
|
|
1055
|
+
'EC-2.link.bystanders: R, HO, U and W are outside the lineage and unchanged');
|
|
1056
|
+
|
|
1057
|
+
-- readmit_session_identity: the human owner readmits a revoked identity as a FRESH agent with a
|
|
1058
|
+
-- role no higher than the old one. U and W are unchanged.
|
|
1059
|
+
-- source-scan: loud-literal one body, named by regprocedure; a miss fails
|
|
1060
|
+
select ok(
|
|
1061
|
+
strpos(lower(agent_board_test.stripped_body('agent_board.readmit_session_identity(uuid,text,text,jsonb,jsonb,interval)'::regprocedure)),
|
|
1062
|
+
'where id = v_membership.id') > 0,
|
|
1063
|
+
'EC-2.readmit.pin: readmit sets role only on the membership row it just seated');
|
|
1064
|
+
select set_config('request.jwt.claim.role', 'service_role', true);
|
|
1065
|
+
set local role service_role;
|
|
1066
|
+
select lives_ok($q$select pg_temp.g674_mint('F', null, 'admin', 'I')$q$,
|
|
1067
|
+
'EC-2.readmit.fixture: the root mints F, a fork of I, as admin (readmission needs a revoked fork)');
|
|
1068
|
+
reset role;
|
|
1069
|
+
create temp table _674_readmit(child_agent_id uuid, grant_expires_at timestamptz, signing_key_id uuid) on commit drop;
|
|
1070
|
+
grant insert, select on _674_readmit to authenticated;
|
|
1071
|
+
select set_config('request.jwt.claim.role', 'authenticated', true);
|
|
1072
|
+
select set_config('request.jwt.claim.sub', (select auth_user::text from _g674_agent where agent_id = pg_temp.g674_a('EC2.sid', 'HO')), true);
|
|
1073
|
+
set local role authenticated;
|
|
1074
|
+
select lives_ok(format('select agent_board.revoke_session_identity(%L::uuid)', (select agent_id from _674_sid where label = 'F')),
|
|
1075
|
+
'EC-2.readmit.revoke: the human owner revokes F');
|
|
1076
|
+
select lives_ok(format($q$insert into _674_readmit select * from agent_board.readmit_session_identity(%L::uuid, %L, %L, '["read"]',
|
|
1077
|
+
jsonb_build_array(jsonb_build_object('board_id', %L::uuid, 'role', 'observer')), interval '14 days')$q$,
|
|
1078
|
+
(select agent_id from _674_sid where label = 'F'),
|
|
1079
|
+
'g674-sid-readmit-' || substr(replace(pg_temp.g674_b('EC2.sid')::text, '-', ''), 1, 12),
|
|
1080
|
+
public.test_pubkey('g674-readmit-' || pg_temp.g674_b('EC2.sid')), pg_temp.g674_b('EC2.sid')),
|
|
1081
|
+
'EC-2.readmit.call: the human owner readmits F as a fresh principal');
|
|
1082
|
+
reset role;
|
|
1083
|
+
select pg_temp.g674_service();
|
|
1084
|
+
select is(
|
|
1085
|
+
(select bm.membership_state || '/' || bm.role from agent_board.board_memberships bm
|
|
1086
|
+
where bm.board_id = pg_temp.g674_b('EC2.sid') and bm.agent_id = (select child_agent_id from _674_readmit)),
|
|
1087
|
+
'joined/observer',
|
|
1088
|
+
'EC-2.readmit.post: only the fresh agent''s row carries the (lower) readmitted role');
|
|
1089
|
+
select is(pg_temp.g674_who('EC2.sid', 'U,W'),
|
|
1090
|
+
'U[joined/owner/pinned] owners=2 W[joined/admin/pinned] owners=2',
|
|
1091
|
+
'EC-2.readmit.bystanders: U and W unchanged');
|
|
1092
|
+
|
|
1093
|
+
-- ===========================================================================
|
|
1094
|
+
-- EC-3: the chokepoint's posture after the redefinition
|
|
1095
|
+
-- ===========================================================================
|
|
1096
|
+
select is(
|
|
1097
|
+
(select has_function_privilege('anon', p.oid, 'EXECUTE')::text || '/' ||
|
|
1098
|
+
has_function_privilege('authenticated', p.oid, 'EXECUTE')::text || '/' ||
|
|
1099
|
+
has_function_privilege('service_role', p.oid, 'EXECUTE')::text || '/' ||
|
|
1100
|
+
has_function_privilege('agent_board_delegate', p.oid, 'EXECUTE')::text
|
|
1101
|
+
from pg_proc p where p.oid = 'agent_board.grant_board_role(uuid,uuid,text,uuid)'::regprocedure), -- pgtap-allow-raise-checked: a regprocedure literal names the RPC, nothing calls it
|
|
1102
|
+
'false/true/true/false',
|
|
1103
|
+
'EC-3.acl: grant_board_role EXECUTE is anon f, authenticated t, service_role t, agent_board_delegate f');
|
|
1104
|
+
select is(
|
|
1105
|
+
(select p.prosecdef::text || ' ' || coalesce(array_to_string(p.proconfig, ';'), '(none)')
|
|
1106
|
+
from pg_proc p where p.oid = 'agent_board.grant_board_role(uuid,uuid,text,uuid)'::regprocedure), -- pgtap-allow-raise-checked: a regprocedure literal names the RPC, nothing calls it
|
|
1107
|
+
'true search_path=agent_board, public',
|
|
1108
|
+
'EC-3.secdef: still SECURITY DEFINER with the unchanged search_path');
|
|
1109
|
+
select ok(
|
|
1110
|
+
coalesce(obj_description('agent_board.grant_board_role(uuid,uuid,text,uuid)'::regprocedure, 'pg_proc'), '') like '%#674%',
|
|
1111
|
+
'EC-3.comment: the restated COMMENT names #674');
|
|
1112
|
+
select is(
|
|
1113
|
+
(select count(*)::int from pg_proc p
|
|
1114
|
+
where p.pronamespace = 'agent_board'::regnamespace and p.proname = 'grant_board_role'),
|
|
1115
|
+
1,
|
|
1116
|
+
'EC-3.overloads: exactly one grant_board_role');
|
|
1117
|
+
select * from finish();
|
|
1118
|
+
rollback;
|