@consiliency/agent-board-schema 1.56.0 → 1.58.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fixtures/already-applied.json +8 -0
- package/migrations/20261007000100_agent_board_onboard_requests.sql +139 -0
- package/migrations/20261008000100_agent_board_hosted_delegates.sql +3152 -0
- package/package.json +1 -1
- package/schema-manifest.json +21 -3
- package/src/tooling.test.mjs +8 -2
- package/tests/agent_board_atomic_fleet_onboarding.test.sql +13 -0
- package/tests/agent_board_consent_emission.test.sql +4 -2
- package/tests/agent_board_delegate_role_class.test.sql +736 -0
- package/tests/agent_board_federated_identity_injective_slug.test.sql +5 -2
- package/tests/agent_board_fedsession_stored_identity_gate.test.sql +71 -14
- package/tests/agent_board_fk_lock_edges.test.sql +9 -0
- package/tests/agent_board_hosted_delegates.test.sql +1134 -0
- package/tests/agent_board_ledger_authority.test.sql +34 -5
- package/tests/agent_board_membership_current_row_class.test.sql +3 -1
- package/tests/agent_board_onboard_requests.test.sql +353 -0
- package/tests/agent_board_rowid.test.sql +18 -5
- package/tests/agent_board_signer_lifecycle.test.sql +3 -0
- package/tests/agent_board_write_enforcement_class.test.sql +8 -3
- package/tests/catalog/function_execute_privileges.json +278 -2
- package/tests/catalog/p_metadata_parameters.json +48 -0
- package/tests/isolation/expected/hosted_delegate_exchange_race.out +93 -0
- package/tests/isolation/expected/hosted_delegate_first_exchange_race.out +129 -0
- package/tests/isolation/expected/hosted_delegate_owner_provisioning_race.out +91 -0
- package/tests/isolation/expected/hosted_delegate_owner_revoke_race.out +137 -0
- package/tests/isolation/expected/hosted_delegate_revoke_race.out +87 -0
- package/tests/isolation/expected/hosted_delegate_withdraw_evict_race.out +235 -0
- package/tests/isolation/specs/hosted_delegate_exchange_race.spec +64 -0
- package/tests/isolation/specs/hosted_delegate_first_exchange_race.spec +72 -0
- package/tests/isolation/specs/hosted_delegate_owner_provisioning_race.spec +73 -0
- package/tests/isolation/specs/hosted_delegate_owner_revoke_race.spec +93 -0
- package/tests/isolation/specs/hosted_delegate_revoke_race.spec +59 -0
- package/tests/isolation/specs/hosted_delegate_withdraw_evict_race.spec +145 -0
|
@@ -49,7 +49,8 @@ begin;
|
|
|
49
49
|
|
|
50
50
|
-- 88 at #276; #280 deletes 7 (EXEMPTION B1/B2/B3, the three RESIDUAL assertions, and a fourth
|
|
51
51
|
-- residual companion in section 10 that named only the slug) and adds 10 in section 11.
|
|
52
|
-
|
|
52
|
+
-- 92 with HOSTED (20261008000100): EXEMPTION E below licenses add_delegate_to_board.
|
|
53
|
+
select plan(92);
|
|
53
54
|
|
|
54
55
|
create function pg_temp.claims(p_sub text) returns void language plpgsql as $$
|
|
55
56
|
begin
|
|
@@ -566,10 +567,10 @@ select is(
|
|
|
566
567
|
and agent_board_test.stripped_body(p.oid)
|
|
567
568
|
~ agent_board_test.re(agent_board_test.call_frag('_assert_actor_not_fleet_revoked')
|
|
568
569
|
|| '|' || agent_board_test.call_frag('_actor_fleet_revoked'))),
|
|
569
|
-
'_redeem_unbounded_invite_for_agent,create_board,create_invite_link,grant_board_role,'
|
|
570
|
+
'_redeem_unbounded_invite_for_agent,add_delegate_to_board,create_board,create_invite_link,grant_board_role,'
|
|
570
571
|
|| 'set_board_authority_ceiling,set_board_claim_policy,set_board_role_gate',
|
|
571
|
-
'the functions that consult the fleet revocation ledger are exactly these
|
|
572
|
-
'create_board)');
|
|
572
|
+
'the functions that consult the fleet revocation ledger are exactly these eight (#280 added '
|
|
573
|
+
'create_board; HOSTED added add_delegate_to_board)');
|
|
573
574
|
|
|
574
575
|
-- The gate proper: the two sets must differ by EXACTLY the three asserted exemptions, and any
|
|
575
576
|
-- other member of the difference is NAMED in the failure. Each excluded name is licensed by a
|
|
@@ -593,6 +594,9 @@ select is(
|
|
|
593
594
|
-- the three asserted exemptions (create_board was the fourth until #280)...
|
|
594
595
|
'_upsert_board_membership_state',
|
|
595
596
|
'_fedsession_autojoin_default_board', 'bootstrap_board_owner_memberships',
|
|
597
|
+
-- ...HOSTED's add_delegate_to_board, which consults the ledger for the OWNER and
|
|
598
|
+
-- writes no authority relation itself (EXEMPTION E, positively, below)...
|
|
599
|
+
'add_delegate_to_board',
|
|
596
600
|
-- ...and the two helpers, which ARE the gate rather than users of it
|
|
597
601
|
'_assert_actor_not_fleet_revoked', '_actor_fleet_revoked')
|
|
598
602
|
and ((agent_board_test.stripped_body(p.oid) ~ agent_board_test.stmt_re('insert into', 'board_memberships')
|
|
@@ -688,7 +692,8 @@ select is(
|
|
|
688
692
|
where n.nspname = 'agent_board'
|
|
689
693
|
and p.proname <> '_upsert_board_membership_state'
|
|
690
694
|
and agent_board_test.stripped_body(p.oid) ~ agent_board_test.call_re('_upsert_board_membership_state')),
|
|
691
|
-
'
|
|
695
|
+
'add_delegate_to_board(p_agent_id uuid, p_board_id uuid)'
|
|
696
|
+
|| ' | invite_board_member(p_board_id uuid, p_agent_id uuid, p_created_by_agent_id uuid, p_metadata jsonb)'
|
|
692
697
|
|| ' | join_board(p_board_id uuid, p_agent_id uuid, p_membership_state text, p_created_by_agent_id uuid, p_metadata jsonb)'
|
|
693
698
|
|| ' | request_join_board(p_board_id uuid, p_agent_id uuid, p_metadata jsonb)'
|
|
694
699
|
|| ' | resolve_board_request(p_board_id uuid, p_agent_id uuid, p_membership_state text, p_created_by_agent_id uuid, p_metadata jsonb)'
|
|
@@ -726,6 +731,30 @@ select is(
|
|
|
726
731
|
'EXEMPTION D: bootstrap_board_owner_memberships takes NO arguments — it backfills the board '
|
|
727
732
|
'own recorded owner_agent_id and can name no other principal');
|
|
728
733
|
|
|
734
|
+
-- EXEMPTION E — add_delegate_to_board (HOSTED, 20261008000100). It is in the ledger-consult set
|
|
735
|
+
-- (the owner must not be fleet-revoked, IF-0-HOSTED-12) and NOT in the authority-writer set, so
|
|
736
|
+
-- the pairing assertion above excludes it by name. Licensed positively here: it consults the
|
|
737
|
+
-- ledger, it reaches board_memberships ONLY through the sink, it names no role, and it writes
|
|
738
|
+
-- no other authority relation in its own body. Each conjunct is reported, so a change names
|
|
739
|
+
-- which one moved.
|
|
740
|
+
select is(
|
|
741
|
+
(select string_agg(
|
|
742
|
+
'ledger=' || (b ~ agent_board_test.call_re('_assert_actor_not_fleet_revoked', false))::text
|
|
743
|
+
|| ' sink=' || (b ~ agent_board_test.call_re('_upsert_board_membership_state', false))::text
|
|
744
|
+
|| ' direct_membership_write=' || (b ~ agent_board_test.write_re('board_memberships'))::text
|
|
745
|
+
|| ' role_write=' || (b ~ agent_board_test.col_write_re('board_memberships', 'role'))::text
|
|
746
|
+
|| ' other_authority_write=' || (b ~ agent_board_test.stmt_re('insert into|update', 'board_authority_ceilings')
|
|
747
|
+
or b ~ agent_board_test.col_write_re('boards', 'role_gate_enforced')
|
|
748
|
+
or b ~ agent_board_test.col_write_re('boards', 'claim_policy')
|
|
749
|
+
or b ~ agent_board_test.stmt_re('insert into', 'board_invite_tokens'))::text,
|
|
750
|
+
' | ')
|
|
751
|
+
from (select agent_board_test.stripped_body(p.oid) as b
|
|
752
|
+
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
|
753
|
+
where n.nspname = 'agent_board' and p.proname = 'add_delegate_to_board') x),
|
|
754
|
+
'ledger=true sink=true direct_membership_write=false role_write=false other_authority_write=false',
|
|
755
|
+
'EXEMPTION E: add_delegate_to_board consults the ledger and writes membership only through the '
|
|
756
|
+
'sink — no role, no other authority relation');
|
|
757
|
+
|
|
729
758
|
|
|
730
759
|
-- ===========================================================================
|
|
731
760
|
-- 10. G3 — THE INVITE ROUTE. An admin-preset link is a deferred grant of admin, so a
|
|
@@ -309,6 +309,7 @@ select is(
|
|
|
309
309
|
'list_board_members/distinct/active-first',
|
|
310
310
|
'list_board_members/limit/active-first',
|
|
311
311
|
'list_board_pending_memberships/distinct/active-first',
|
|
312
|
+
'list_my_board_requests/limit/active-first',
|
|
312
313
|
'request_join_board/limit/filtered:''joined'',''subscribed''',
|
|
313
314
|
'resolve_board_request/limit/filtered:''joined'',''subscribed''',
|
|
314
315
|
'resolve_board_request/limit/filtered:''requested'',''invited''',
|
|
@@ -367,10 +368,11 @@ select is(
|
|
|
367
368
|
'list_board_members/distinct',
|
|
368
369
|
'list_board_members/limit',
|
|
369
370
|
'list_board_pending_memberships/distinct',
|
|
371
|
+
'list_my_board_requests/limit',
|
|
370
372
|
'update_board_membership/limit',
|
|
371
373
|
'update_board_membership/limit'
|
|
372
374
|
]::text[],
|
|
373
|
-
'CRC-4: these
|
|
375
|
+
'CRC-4: these eight sites carry the MBROWID 3a active-first key');
|
|
374
376
|
|
|
375
377
|
-- ---------------------------------------------------------------------------
|
|
376
378
|
-- CRC-5..14: behaviour rows over fixture text, through the same objects.
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
-- ONBOARD (v11) SL-2 pgTAP: list_my_board_requests() (IF-0-ONBOARD-4) and
|
|
2
|
+
-- invite_board_for_display(text) (IF-0-ONBOARD-11), migration 20261007000100.
|
|
3
|
+
--
|
|
4
|
+
-- Driven by the real operations only: request_join_board, resolve_board_request (approve and
|
|
5
|
+
-- reject), leave_board then request_join_board again, create_invite_link / redeem_invite_link /
|
|
6
|
+
-- revoke_invite_link, enroll_federated_human. Two fixtures are AGE only, because now() is
|
|
7
|
+
-- constant inside the test transaction and neither the TTL nor an invite expiry can be waited
|
|
8
|
+
-- out: one request event's created_at is moved 31 days back (OB-L13), and one invite's
|
|
9
|
+
-- expires_at is moved into the past (OB-I05). Both rows were first written by the real RPC.
|
|
10
|
+
--
|
|
11
|
+
-- Identity is set by the whole-JSON claim GUC only (the consent suite's method): SET LOCAL
|
|
12
|
+
-- ROLE is never used, so EXECUTE posture is asserted separately through
|
|
13
|
+
-- has_function_privilege, not by calling as a role.
|
|
14
|
+
--
|
|
15
|
+
-- Every call to a function under test is a lives_ok capture into a temp table, then separate
|
|
16
|
+
-- state checks (AGENTS.md rule 3): a missing or re-signatured function FAILS the capture and
|
|
17
|
+
-- leaves the checks to fail on an empty table, instead of aborting the file.
|
|
18
|
+
--
|
|
19
|
+
-- Mutation receipts (plans/evidence/onboard/mutation-receipts.md, "## SL-2"):
|
|
20
|
+
-- identity parameter added -> OB-L02
|
|
21
|
+
-- agent_id = caller dropped -> OB-L10, OB-L16
|
|
22
|
+
-- created_by_agent_id as the source -> OB-L10
|
|
23
|
+
-- service_role granted -> OB-L05
|
|
24
|
+
-- IF-11 granted to authenticated -> OB-I13
|
|
25
|
+
-- revoked / expired / exhausted predicate dropped -> OB-I04 / OB-I05 / OB-I07
|
|
26
|
+
--
|
|
27
|
+
-- HAND-COUNTED plan: OB-L01..L23 (23) + OB-I01..I16 with I03b (17) + OB-R01..R03 and the two
|
|
28
|
+
-- named redeem pins (5) = 45.
|
|
29
|
+
create extension if not exists pgtap with schema extensions;
|
|
30
|
+
set search_path = extensions, public, agent_board;
|
|
31
|
+
|
|
32
|
+
begin;
|
|
33
|
+
|
|
34
|
+
select plan(45);
|
|
35
|
+
|
|
36
|
+
create function pg_temp.claims(p_sub text) returns void language plpgsql as $$
|
|
37
|
+
begin
|
|
38
|
+
perform set_config('request.jwt.claims', json_build_object('role','authenticated','sub',p_sub)::text, true);
|
|
39
|
+
end $$;
|
|
40
|
+
create function pg_temp.service() returns void language plpgsql as $$
|
|
41
|
+
begin perform set_config('request.jwt.claims', '', true); end $$;
|
|
42
|
+
create function pg_temp.state(b uuid, a uuid) returns text language sql as $$
|
|
43
|
+
select membership_state from agent_board.board_memberships
|
|
44
|
+
where board_id = b and agent_id = a
|
|
45
|
+
order by case when membership_state in ('joined','subscribed') then 0 else 1 end,
|
|
46
|
+
created_at desc, id desc
|
|
47
|
+
limit 1;
|
|
48
|
+
$$;
|
|
49
|
+
create function pg_temp.sha(p_code text) returns text language sql as $$
|
|
50
|
+
select encode(extensions.digest(p_code, 'sha256'), 'hex');
|
|
51
|
+
$$;
|
|
52
|
+
|
|
53
|
+
-- ---------------------------------------------------------------------------
|
|
54
|
+
-- Fixture: principals and boards (service context; create_board seeds the owner).
|
|
55
|
+
-- ---------------------------------------------------------------------------
|
|
56
|
+
insert into auth.users(id, instance_id, aud, role, email)
|
|
57
|
+
select ('0b000000-0000-0000-0000-' || lpad(n::text,12,'0'))::uuid,
|
|
58
|
+
'00000000-0000-0000-0000-000000000000','authenticated','authenticated','onb'||n||'@t.test'
|
|
59
|
+
from generate_series(1,4) n;
|
|
60
|
+
|
|
61
|
+
-- 1 = owner, 2 = requester R, 3 = another principal X, 4 = invite redeemer / exhauster Y.
|
|
62
|
+
insert into agent_board.agents(id, agent_type, kind, handle, display_name, status, auth_user_id)
|
|
63
|
+
select ('0b100000-0000-4000-8000-' || lpad(n::text,12,'0'))::uuid,
|
|
64
|
+
'agent','agent','onb-agent-'||n,'ONB Agent '||n,'active',
|
|
65
|
+
('0b000000-0000-0000-0000-' || lpad(n::text,12,'0'))::uuid
|
|
66
|
+
from generate_series(1,4) n;
|
|
67
|
+
|
|
68
|
+
create temp table _ob (
|
|
69
|
+
owner uuid, r uuid, x uuid, y uuid, h uuid, h_user uuid,
|
|
70
|
+
b_approve uuid, b_reject uuid, b_rerequest uuid, b_expire uuid,
|
|
71
|
+
b_invite uuid, b_other uuid, b_human uuid, b_links uuid,
|
|
72
|
+
code_join text, code_live text, code_revoked text, code_expired text,
|
|
73
|
+
code_exhaust text, code_replay text, code_other text
|
|
74
|
+
) on commit drop;
|
|
75
|
+
|
|
76
|
+
do $$
|
|
77
|
+
declare
|
|
78
|
+
v_owner uuid := '0b100000-0000-4000-8000-000000000001';
|
|
79
|
+
v_iss text := 'https://onb-fed-' || replace(substr(gen_random_uuid()::text, 1, 8), '-', '') || '.test';
|
|
80
|
+
v_h uuid; v_h_user uuid;
|
|
81
|
+
v_ba uuid; v_bb uuid; v_bc uuid; v_bd uuid; v_be uuid; v_bg uuid; v_bf uuid; v_bk uuid;
|
|
82
|
+
begin
|
|
83
|
+
select board_id into v_ba from agent_board.create_board(v_owner, 'onb-approve', 'ONB Approve', null, '{}'::jsonb, 'open');
|
|
84
|
+
select board_id into v_bb from agent_board.create_board(v_owner, 'onb-reject', 'ONB Reject', null, '{}'::jsonb, 'open');
|
|
85
|
+
select board_id into v_bc from agent_board.create_board(v_owner, 'onb-rerequest', 'ONB Rerequest', null, '{}'::jsonb, 'open');
|
|
86
|
+
select board_id into v_bd from agent_board.create_board(v_owner, 'onb-expire', 'ONB Expire', null, '{}'::jsonb, 'invite_only');
|
|
87
|
+
select board_id into v_be from agent_board.create_board(v_owner, 'onb-invite', 'ONB Invite', null, '{}'::jsonb, 'open');
|
|
88
|
+
select board_id into v_bg from agent_board.create_board(v_owner, 'onb-other', 'ONB Other', null, '{}'::jsonb, 'open');
|
|
89
|
+
select board_id into v_bf from agent_board.create_board(v_owner, 'onb-human', 'ONB Human', null, '{}'::jsonb, 'open');
|
|
90
|
+
select board_id into v_bk from agent_board.create_board(v_owner, 'onb-links', 'ONB Links', null, '{}'::jsonb, 'private');
|
|
91
|
+
|
|
92
|
+
-- A Portal human: the federated provider, then the fed-human agent it provisions.
|
|
93
|
+
insert into agent_board.identity_providers
|
|
94
|
+
(issuer, audience, supported, policy_approved, allowed_agent_kinds, default_board_id)
|
|
95
|
+
values (v_iss, 'message-board', true, true, '{human}', null);
|
|
96
|
+
select e.agent_id, e.auth_user_id into v_h, v_h_user
|
|
97
|
+
from agent_board.enroll_federated_human(
|
|
98
|
+
v_iss, 'message-board', 'onb-human-sub',
|
|
99
|
+
jsonb_build_object('sub', 'onb-human-sub', 'email_verified', false), 'ONB Human') e;
|
|
100
|
+
|
|
101
|
+
insert into _ob(owner, r, x, y, h, h_user,
|
|
102
|
+
b_approve, b_reject, b_rerequest, b_expire, b_invite, b_other, b_human, b_links)
|
|
103
|
+
values (v_owner,
|
|
104
|
+
'0b100000-0000-4000-8000-000000000002', '0b100000-0000-4000-8000-000000000003',
|
|
105
|
+
'0b100000-0000-4000-8000-000000000004', v_h, v_h_user,
|
|
106
|
+
v_ba, v_bb, v_bc, v_bd, v_be, v_bg, v_bf, v_bk);
|
|
107
|
+
|
|
108
|
+
update _ob set
|
|
109
|
+
code_join = (select code from agent_board.create_invite_link(v_be, 'member', 'joined', null, null, v_owner, '{}'::jsonb)),
|
|
110
|
+
code_live = (select code from agent_board.create_invite_link(v_bk, 'member', 'joined', null, null, v_owner, '{}'::jsonb)),
|
|
111
|
+
code_revoked = (select code from agent_board.create_invite_link(v_bk, 'member', 'joined', null, null, v_owner, '{}'::jsonb)),
|
|
112
|
+
code_expired = (select code from agent_board.create_invite_link(v_bk, 'member', 'joined', null, null, v_owner, '{}'::jsonb)),
|
|
113
|
+
code_exhaust = (select code from agent_board.create_invite_link(v_bk, 'member', 'joined', 1, null, v_owner, '{}'::jsonb)),
|
|
114
|
+
code_replay = (select code from agent_board.create_invite_link(v_bk, 'member', 'joined', null, null, v_owner, '{}'::jsonb)),
|
|
115
|
+
code_other = (select code from agent_board.create_invite_link(v_bk, 'member', 'joined', null, null, v_owner, '{}'::jsonb));
|
|
116
|
+
end
|
|
117
|
+
$$;
|
|
118
|
+
|
|
119
|
+
create temp table _req (
|
|
120
|
+
who text, board_id uuid, board_name text, board_slug text, requested_at timestamptz,
|
|
121
|
+
membership_state text, expires_at timestamptz, expired boolean
|
|
122
|
+
) on commit drop;
|
|
123
|
+
create temp table _disp (
|
|
124
|
+
label text, board_id uuid, board_name text, board_slug text, preset_role text
|
|
125
|
+
) on commit drop;
|
|
126
|
+
|
|
127
|
+
-- ===========================================================================
|
|
128
|
+
-- list_my_board_requests: catalog posture
|
|
129
|
+
-- ===========================================================================
|
|
130
|
+
select ok(to_regprocedure('agent_board.list_my_board_requests()') is not null,
|
|
131
|
+
'OB-L01: list_my_board_requests() exists with no arguments');
|
|
132
|
+
select is(
|
|
133
|
+
(select string_agg(coalesce(pg_get_function_arguments(p.oid), ''), '|')
|
|
134
|
+
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
|
135
|
+
where n.nspname = 'agent_board' and p.proname = 'list_my_board_requests'),
|
|
136
|
+
'',
|
|
137
|
+
'OB-L02: exactly one list_my_board_requests overload, and it takes NO parameter (identity is never an argument)');
|
|
138
|
+
select is(
|
|
139
|
+
(select p.prosecdef::text || '/' || p.provolatile::text
|
|
140
|
+
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
|
141
|
+
where n.nspname = 'agent_board' and p.proname = 'list_my_board_requests'),
|
|
142
|
+
'true/s',
|
|
143
|
+
'OB-L03: list_my_board_requests is SECURITY DEFINER and STABLE');
|
|
144
|
+
select is(has_function_privilege('authenticated', to_regprocedure('agent_board.list_my_board_requests()'), 'EXECUTE'),
|
|
145
|
+
true, 'OB-L04: authenticated holds EXECUTE on list_my_board_requests');
|
|
146
|
+
select is(has_function_privilege('service_role', to_regprocedure('agent_board.list_my_board_requests()'), 'EXECUTE'),
|
|
147
|
+
false, 'OB-L05: service_role holds NO EXECUTE on list_my_board_requests (no caller identity)');
|
|
148
|
+
select is(has_function_privilege('anon', to_regprocedure('agent_board.list_my_board_requests()'), 'EXECUTE'),
|
|
149
|
+
false, 'OB-L06: anon holds NO EXECUTE on list_my_board_requests');
|
|
150
|
+
select is(has_function_privilege('public', to_regprocedure('agent_board.list_my_board_requests()'), 'EXECUTE'),
|
|
151
|
+
false, 'OB-L07: PUBLIC holds NO EXECUTE on list_my_board_requests');
|
|
152
|
+
|
|
153
|
+
-- ===========================================================================
|
|
154
|
+
-- Real operations
|
|
155
|
+
-- ===========================================================================
|
|
156
|
+
-- R requests four boards.
|
|
157
|
+
select pg_temp.claims('0b000000-0000-0000-0000-000000000002');
|
|
158
|
+
do $$ declare o _ob; begin select * into o from _ob;
|
|
159
|
+
perform agent_board.request_join_board(o.b_approve, o.r);
|
|
160
|
+
perform agent_board.request_join_board(o.b_reject, o.r);
|
|
161
|
+
perform agent_board.request_join_board(o.b_rerequest, o.r);
|
|
162
|
+
perform agent_board.request_join_board(o.b_expire, o.r);
|
|
163
|
+
end $$;
|
|
164
|
+
|
|
165
|
+
-- X requests the approve board and a board R never asked for.
|
|
166
|
+
select pg_temp.claims('0b000000-0000-0000-0000-000000000003');
|
|
167
|
+
do $$ declare o _ob; begin select * into o from _ob;
|
|
168
|
+
perform agent_board.request_join_board(o.b_approve, o.x);
|
|
169
|
+
perform agent_board.request_join_board(o.b_other, o.x);
|
|
170
|
+
end $$;
|
|
171
|
+
|
|
172
|
+
-- The owner approves A and C, rejects B. Passing the owner as p_created_by_agent_id makes the
|
|
173
|
+
-- sink overwrite created_by_agent_id on R's rows -- the reason that column is not a source.
|
|
174
|
+
select pg_temp.claims('0b000000-0000-0000-0000-000000000001');
|
|
175
|
+
do $$ declare o _ob; begin select * into o from _ob;
|
|
176
|
+
perform agent_board.resolve_board_request(o.b_approve, o.r, 'joined', o.owner, '{}'::jsonb);
|
|
177
|
+
perform agent_board.resolve_board_request(o.b_reject, o.r, 'removed', o.owner, '{}'::jsonb);
|
|
178
|
+
perform agent_board.resolve_board_request(o.b_rerequest, o.r, 'joined', o.owner, '{}'::jsonb);
|
|
179
|
+
end $$;
|
|
180
|
+
|
|
181
|
+
-- R leaves C and requests it again; R joins E through an invite with no request.
|
|
182
|
+
select pg_temp.claims('0b000000-0000-0000-0000-000000000002');
|
|
183
|
+
do $$ declare o _ob; begin select * into o from _ob;
|
|
184
|
+
perform agent_board.leave_board(o.b_rerequest, o.r);
|
|
185
|
+
perform agent_board.request_join_board(o.b_rerequest, o.r);
|
|
186
|
+
perform agent_board.redeem_invite_link(o.code_join);
|
|
187
|
+
end $$;
|
|
188
|
+
|
|
189
|
+
-- The Portal human requests F from its own session.
|
|
190
|
+
select pg_temp.claims((select h_user::text from _ob));
|
|
191
|
+
do $$ declare o _ob; begin select * into o from _ob;
|
|
192
|
+
perform agent_board.request_join_board(o.b_human, o.h);
|
|
193
|
+
end $$;
|
|
194
|
+
|
|
195
|
+
-- AGE fixture: R's request on D is moved past the board's 30-day TTL.
|
|
196
|
+
select pg_temp.service();
|
|
197
|
+
update agent_board.board_membership_events e
|
|
198
|
+
set created_at = now() - interval '31 days'
|
|
199
|
+
from _ob
|
|
200
|
+
where e.board_id = _ob.b_expire and e.agent_id = _ob.r
|
|
201
|
+
and e.provenance = 'target-authored' and e.result_state = 'requested';
|
|
202
|
+
|
|
203
|
+
-- ===========================================================================
|
|
204
|
+
-- list_my_board_requests: behaviour
|
|
205
|
+
-- ===========================================================================
|
|
206
|
+
select pg_temp.claims('0b000000-0000-0000-0000-000000000002');
|
|
207
|
+
select lives_ok($$ insert into _req select 'R', * from agent_board.list_my_board_requests() $$,
|
|
208
|
+
'OB-L08: R lists its requests [call succeeds]');
|
|
209
|
+
select pg_temp.claims('0b000000-0000-0000-0000-000000000003');
|
|
210
|
+
select lives_ok($$ insert into _req select 'X', * from agent_board.list_my_board_requests() $$,
|
|
211
|
+
'OB-L09: X lists its requests [call succeeds]');
|
|
212
|
+
|
|
213
|
+
select is(
|
|
214
|
+
(select string_agg(board_slug, ',' order by board_slug) from _req where who = 'R'),
|
|
215
|
+
'onb-approve,onb-expire,onb-reject,onb-rerequest',
|
|
216
|
+
'OB-L10: R sees exactly its four requested boards -- approved, rejected, re-requested and expired');
|
|
217
|
+
select is((select membership_state from _req r, _ob where r.who = 'R' and r.board_id = _ob.b_approve),
|
|
218
|
+
'joined', 'OB-L11: an approved request reads its current state, joined');
|
|
219
|
+
select is((select membership_state from _req r, _ob where r.who = 'R' and r.board_id = _ob.b_reject),
|
|
220
|
+
'left', 'OB-L12: a rejected request reads its current state, left (REJECTSEM)');
|
|
221
|
+
select is((select membership_state || '/' || expired::text from _req r, _ob where r.who = 'R' and r.board_id = _ob.b_expire),
|
|
222
|
+
'requested/true', 'OB-L13: a request older than join_request_ttl_days is returned with expired = true');
|
|
223
|
+
select is((select count(*)::int from _req where who = 'R' and not expired),
|
|
224
|
+
3, 'OB-L14: the three requests inside the TTL are expired = false');
|
|
225
|
+
select is(
|
|
226
|
+
(select membership_state || '/' || (select count(*) from _req q where q.who = 'R' and q.board_id = r.board_id)::text
|
|
227
|
+
from _req r, _ob where r.who = 'R' and r.board_id = _ob.b_rerequest),
|
|
228
|
+
'requested/1',
|
|
229
|
+
'OB-L15: leave then re-request reads requested, in ONE row for the board');
|
|
230
|
+
select is((select count(*)::int from _req r, _ob where r.who = 'R' and r.board_id in (_ob.b_other, _ob.b_human)),
|
|
231
|
+
0, 'OB-L16: another principal''s requests are invisible to R');
|
|
232
|
+
select is(
|
|
233
|
+
(select pg_temp.state(_ob.b_invite, _ob.r) || '/' ||
|
|
234
|
+
(select count(*) from _req r where r.who = 'R' and r.board_id = _ob.b_invite)::text from _ob),
|
|
235
|
+
'joined/0',
|
|
236
|
+
'OB-L17: an invite-only join (R is joined) is NOT returned as a request');
|
|
237
|
+
select is(
|
|
238
|
+
(select (r.board_name = 'ONB Approve' and r.expires_at = r.requested_at + interval '30 days')::text
|
|
239
|
+
from _req r, _ob where r.who = 'R' and r.board_id = _ob.b_approve),
|
|
240
|
+
'true', 'OB-L18: a row carries the board name and expires_at = requested_at + join_request_ttl_days');
|
|
241
|
+
select is(
|
|
242
|
+
(select string_agg(board_slug || '=' || membership_state, ',' order by board_slug) from _req where who = 'X'),
|
|
243
|
+
'onb-approve=requested,onb-other=requested',
|
|
244
|
+
'OB-L19: X sees only its own requests, with its own state on the shared board');
|
|
245
|
+
|
|
246
|
+
select pg_temp.claims((select h_user::text from _ob));
|
|
247
|
+
select lives_ok($$ insert into _req select 'H', * from agent_board.list_my_board_requests() $$,
|
|
248
|
+
'OB-L20: the Portal human session lists [call succeeds]');
|
|
249
|
+
select is((select string_agg(board_slug || '=' || membership_state, ',') from _req where who = 'H'),
|
|
250
|
+
'onb-human=requested',
|
|
251
|
+
'OB-L21: a Portal human session sees its fed-human agent''s request');
|
|
252
|
+
|
|
253
|
+
select pg_temp.service();
|
|
254
|
+
select lives_ok($$ insert into _req select 'S', * from agent_board.list_my_board_requests() $$,
|
|
255
|
+
'OB-L22: a session with no caller identity calls the function [call succeeds]');
|
|
256
|
+
select is((select count(*)::int from _req where who = 'S'),
|
|
257
|
+
0, 'OB-L23: a session with no caller identity lists nothing');
|
|
258
|
+
|
|
259
|
+
-- ===========================================================================
|
|
260
|
+
-- invite_board_for_display: catalog posture
|
|
261
|
+
-- ===========================================================================
|
|
262
|
+
select ok(to_regprocedure('agent_board.invite_board_for_display(text)') is not null,
|
|
263
|
+
'OB-I01: invite_board_for_display(text) exists');
|
|
264
|
+
select is(
|
|
265
|
+
(select count(*)::int from pg_indexes
|
|
266
|
+
where schemaname = 'agent_board' and tablename = 'board_invite_tokens'
|
|
267
|
+
and indexname = 'idx_agent_board_board_invite_tokens_code_sha256'
|
|
268
|
+
and indexdef like '%digest(code, ''sha256''%'),
|
|
269
|
+
1, 'OB-I02: board_invite_tokens has the functional digest index');
|
|
270
|
+
|
|
271
|
+
-- ===========================================================================
|
|
272
|
+
-- Real operations on the links board
|
|
273
|
+
-- ===========================================================================
|
|
274
|
+
select pg_temp.claims('0b000000-0000-0000-0000-000000000001');
|
|
275
|
+
do $$ begin perform agent_board.revoke_invite_link((select code_revoked from _ob)); end $$;
|
|
276
|
+
select pg_temp.service();
|
|
277
|
+
-- AGE fixture: a code minted by create_invite_link is moved past its expiry.
|
|
278
|
+
update agent_board.board_invite_tokens t set expires_at = now() - interval '1 second'
|
|
279
|
+
from _ob where t.code = _ob.code_expired;
|
|
280
|
+
|
|
281
|
+
select lives_ok($$ insert into _disp select 'exhaust-before', * from agent_board.invite_board_for_display(pg_temp.sha((select code_exhaust from _ob))) $$,
|
|
282
|
+
'OB-I03: the capped code is looked up before its last use [call succeeds]');
|
|
283
|
+
|
|
284
|
+
-- Y spends the capped code's only use.
|
|
285
|
+
select pg_temp.claims('0b000000-0000-0000-0000-000000000004');
|
|
286
|
+
do $$ begin perform agent_board.redeem_invite_link((select code_exhaust from _ob)); end $$;
|
|
287
|
+
select pg_temp.service();
|
|
288
|
+
|
|
289
|
+
select lives_ok($$
|
|
290
|
+
insert into _disp select 'live', * from agent_board.invite_board_for_display(pg_temp.sha((select code_live from _ob)));
|
|
291
|
+
insert into _disp select 'live-upper', * from agent_board.invite_board_for_display(upper(pg_temp.sha((select code_live from _ob))));
|
|
292
|
+
insert into _disp select 'revoked', * from agent_board.invite_board_for_display(pg_temp.sha((select code_revoked from _ob)));
|
|
293
|
+
insert into _disp select 'expired', * from agent_board.invite_board_for_display(pg_temp.sha((select code_expired from _ob)));
|
|
294
|
+
insert into _disp select 'exhausted', * from agent_board.invite_board_for_display(pg_temp.sha((select code_exhaust from _ob)));
|
|
295
|
+
insert into _disp select 'unknown', * from agent_board.invite_board_for_display(pg_temp.sha('onb-no-such-code'));
|
|
296
|
+
$$, 'OB-I03b: every code is looked up [call succeeds]');
|
|
297
|
+
|
|
298
|
+
select is((select count(*)::int from _disp where label = 'revoked'),
|
|
299
|
+
0, 'OB-I04: a revoked code returns no row');
|
|
300
|
+
select is((select count(*)::int from _disp where label = 'expired'),
|
|
301
|
+
0, 'OB-I05: an expired code returns no row');
|
|
302
|
+
select is(
|
|
303
|
+
(select uses_count::text || '/' || max_uses::text from agent_board.board_invite_tokens t, _ob where t.code = _ob.code_exhaust),
|
|
304
|
+
'1/1', 'OB-I06: the capped code was exhausted by a real redeem (uses_count = max_uses)');
|
|
305
|
+
select is((select count(*)::int from _disp where label = 'exhausted'),
|
|
306
|
+
0, 'OB-I07: an exhausted code returns no row');
|
|
307
|
+
select is((select count(*)::int from _disp where label = 'exhaust-before'),
|
|
308
|
+
1, 'OB-I08: the same code resolved while a use remained (the exhausted check is discriminating)');
|
|
309
|
+
select is((select count(*)::int from _disp where label = 'unknown'),
|
|
310
|
+
0, 'OB-I09: an unknown digest returns no row');
|
|
311
|
+
select is(
|
|
312
|
+
(select (d.board_id = _ob.b_links)::text || '/' || d.board_name || '/' || d.board_slug || '/' || d.preset_role
|
|
313
|
+
from _disp d, _ob where d.label = 'live'),
|
|
314
|
+
'true/ONB Links/onb-links/member',
|
|
315
|
+
'OB-I10: a usable code resolves to its board id, name, slug and preset role');
|
|
316
|
+
select is((select count(*)::int from _disp where label = 'live-upper'),
|
|
317
|
+
1, 'OB-I11: the digest is compared case-insensitively');
|
|
318
|
+
select is(has_function_privilege('service_role', to_regprocedure('agent_board.invite_board_for_display(text)'), 'EXECUTE'),
|
|
319
|
+
true, 'OB-I12: service_role holds EXECUTE on invite_board_for_display');
|
|
320
|
+
select is(has_function_privilege('authenticated', to_regprocedure('agent_board.invite_board_for_display(text)'), 'EXECUTE'),
|
|
321
|
+
false, 'OB-I13: authenticated holds NO EXECUTE on invite_board_for_display');
|
|
322
|
+
select is(has_function_privilege('anon', to_regprocedure('agent_board.invite_board_for_display(text)'), 'EXECUTE'),
|
|
323
|
+
false, 'OB-I14: anon holds NO EXECUTE on invite_board_for_display');
|
|
324
|
+
select is(has_function_privilege('public', to_regprocedure('agent_board.invite_board_for_display(text)'), 'EXECUTE'),
|
|
325
|
+
false, 'OB-I15: PUBLIC holds NO EXECUTE on invite_board_for_display');
|
|
326
|
+
select is(
|
|
327
|
+
(select p.prosecdef::text || '/' || p.provolatile::text
|
|
328
|
+
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
|
329
|
+
where n.nspname = 'agent_board' and p.proname = 'invite_board_for_display'),
|
|
330
|
+
'true/s',
|
|
331
|
+
'OB-I16: invite_board_for_display is SECURITY DEFINER and STABLE');
|
|
332
|
+
|
|
333
|
+
-- ===========================================================================
|
|
334
|
+
-- Redeem behaviour on the real RPC (EC-ONBOARD-1's pin)
|
|
335
|
+
-- ===========================================================================
|
|
336
|
+
select pg_temp.claims('0b000000-0000-0000-0000-000000000002');
|
|
337
|
+
select lives_ok($$ select agent_board.redeem_invite_link((select code_replay from _ob)) $$,
|
|
338
|
+
'OB-R01: R redeems an unlimited invite [call succeeds]');
|
|
339
|
+
select lives_ok($$ select agent_board.redeem_invite_link((select code_replay from _ob)) $$,
|
|
340
|
+
'OB-R02: R replays the same invite [call succeeds]');
|
|
341
|
+
select is(
|
|
342
|
+
(select uses_count from agent_board.board_invite_tokens t, _ob where t.code = _ob.code_replay),
|
|
343
|
+
1, 'redeem_replay_no_use: replaying the same invite consumes no use');
|
|
344
|
+
select lives_ok($$ select agent_board.redeem_invite_link((select code_other from _ob)) $$,
|
|
345
|
+
'OB-R03: R, already a member, redeems a different invite [call succeeds]');
|
|
346
|
+
select pg_temp.service();
|
|
347
|
+
select is(
|
|
348
|
+
(select pg_temp.state(_ob.b_links, _ob.r) || '/' || t.uses_count::text
|
|
349
|
+
from agent_board.board_invite_tokens t, _ob where t.code = _ob.code_other),
|
|
350
|
+
'joined/1', 'redeem_other_invite_consumes: redeeming a different invite while already a member consumes one');
|
|
351
|
+
|
|
352
|
+
select * from finish();
|
|
353
|
+
rollback;
|
|
@@ -19,7 +19,7 @@ set search_path = extensions, public, agent_board;
|
|
|
19
19
|
|
|
20
20
|
begin;
|
|
21
21
|
|
|
22
|
-
select plan(
|
|
22
|
+
select plan(100);
|
|
23
23
|
|
|
24
24
|
create temp view _rowid_order_sites as
|
|
25
25
|
with defs as (
|
|
@@ -506,9 +506,10 @@ select is(
|
|
|
506
506
|
true,
|
|
507
507
|
'control: fixture and structural block really runs as service_role');
|
|
508
508
|
|
|
509
|
-
-- 2. Catalog-derived inventory:
|
|
510
|
-
-- readers. request_join_board joined the set in MBCONSENT-B(iii),
|
|
511
|
-
-- it in ROOMCONTRACT
|
|
509
|
+
-- 2. Catalog-derived inventory: nine membership readers plus the two task/message idempotency
|
|
510
|
+
-- readers. request_join_board joined the set in MBCONSENT-B(iii), list_board_members joined
|
|
511
|
+
-- it in ROOMCONTRACT, and list_my_board_requests joined it in ONBOARD (v11); all read
|
|
512
|
+
-- board_memberships with the created_at DESC + id DESC tiebreak.
|
|
512
513
|
select is(
|
|
513
514
|
(select array_agg(proname order by proname)
|
|
514
515
|
from _rowid_function_order_counts),
|
|
@@ -519,12 +520,13 @@ select is(
|
|
|
519
520
|
'join_board',
|
|
520
521
|
'list_board_members',
|
|
521
522
|
'list_board_pending_memberships',
|
|
523
|
+
'list_my_board_requests',
|
|
522
524
|
'post_board_message',
|
|
523
525
|
'request_join_board',
|
|
524
526
|
'resolve_board_request',
|
|
525
527
|
'update_board_membership'
|
|
526
528
|
]::text[],
|
|
527
|
-
'live catalog exposes
|
|
529
|
+
'live catalog exposes nine membership readers plus two task/message idempotency readers');
|
|
528
530
|
|
|
529
531
|
-- 3-10. COUNT-EQUALITY, never substring containment.
|
|
530
532
|
select is(
|
|
@@ -1313,6 +1315,17 @@ select is(
|
|
|
1313
1315
|
array[2, 2],
|
|
1314
1316
|
'list_board_members: BOTH created_at DESC sites (authorization + projection) have the id DESC tiebreak');
|
|
1315
1317
|
|
|
1318
|
+
-- 100. list_my_board_requests (ONBOARD v11, 20261007000100): one statement, two created_at
|
|
1319
|
+
-- DESC sites -- the lateral that picks the caller's current membership row (active-first),
|
|
1320
|
+
-- and the result order -- and both carry the id DESC tiebreak. The exact pair, as for 99,
|
|
1321
|
+
-- so a site that disappears is not read as agreement 0 = 0.
|
|
1322
|
+
select is(
|
|
1323
|
+
(select array[created_at_desc_count, total_order_count]
|
|
1324
|
+
from _rowid_function_order_counts
|
|
1325
|
+
where proname = 'list_my_board_requests'),
|
|
1326
|
+
array[2, 2],
|
|
1327
|
+
'list_my_board_requests: BOTH created_at DESC sites (current row + result order) have the id DESC tiebreak');
|
|
1328
|
+
|
|
1316
1329
|
select finish();
|
|
1317
1330
|
|
|
1318
1331
|
rollback;
|
|
@@ -727,6 +727,9 @@ select is(
|
|
|
727
727
|
from agg
|
|
728
728
|
where locks_agents and locks_memberships),
|
|
729
729
|
array[
|
|
730
|
+
-- HOSTED (20261008000100): the fleet-ledger gate on the owner row-locks agents, and the sink
|
|
731
|
+
-- writes the delegate's membership. Serialised by the advisory lock, as grant_board_role is.
|
|
732
|
+
'add_delegate_to_board => serialised by the board advisory lock',
|
|
730
733
|
-- Joined this class in #276 (20260909000100). The fleet-ledger gate row-locks the actor's
|
|
731
734
|
-- agents row, so the live grant chokepoint -- and therefore every caller of it -- now locks
|
|
732
735
|
-- both relations. It takes the advisory lock rather than arguing from its ordering: this
|
|
@@ -427,10 +427,13 @@ select distinct p.oid,
|
|
|
427
427
|
-- 59 -> 60 with #521's agent_board.enroll_token_uses. Its only writer, _enroll_token_bind, is
|
|
428
428
|
-- EXECUTE-able by no API role and is reached only from the service_role-only
|
|
429
429
|
-- enroll_federated_identity, so it adds no member to the `authenticated` writer population.
|
|
430
|
+
-- 60 -> 62 with HOSTED's delegate_instances and delegate_allowed_operations (20261008000100).
|
|
431
|
+
-- Both are written only by service_role-only definers or the migration itself, so neither joins
|
|
432
|
+
-- the `authenticated` writer population below.
|
|
430
433
|
select is(
|
|
431
434
|
(select count(*)::text from _wec_target where relkind = 'r'),
|
|
432
|
-
'
|
|
433
|
-
'#325 canary: agent_board has
|
|
435
|
+
'62',
|
|
436
|
+
'#325 canary: agent_board has 62 base tables — the universe the write scan runs over, pinned so an empty violating set below cannot mean an empty universe');
|
|
434
437
|
|
|
435
438
|
-- WEC-2. The relations the class writes, as an EXACT SET. A new one names itself.
|
|
436
439
|
select is(
|
|
@@ -489,6 +492,8 @@ select is( -- pgtap-allow-raise-checked
|
|
|
489
492
|
array[
|
|
490
493
|
'agent_board.acquire_claim(uuid,text,text,timestamp with time zone,uuid,uuid,jsonb)',
|
|
491
494
|
'agent_board.add_artifact(uuid,text,text,text,uuid,uuid,uuid,uuid,jsonb)',
|
|
495
|
+
-- HOSTED (20261008000100): the owner places its hosted delegate through the sink.
|
|
496
|
+
'agent_board.add_delegate_to_board(uuid,uuid)',
|
|
492
497
|
'agent_board.append_task_event(uuid,uuid,text,jsonb,text,uuid,timestamp with time zone)',
|
|
493
498
|
'agent_board.block_board_inviter(uuid,uuid,uuid,jsonb)',
|
|
494
499
|
'agent_board.create_board(uuid,text,text,text,jsonb,text)',
|
|
@@ -548,7 +553,7 @@ select is( -- pgtap-allow-raise-checked
|
|
|
548
553
|
'agent_board.upsert_delegation_policy(uuid,text,jsonb,integer,interval,boolean)',
|
|
549
554
|
'agent_board.verify_callback_endpoint(uuid,uuid,jsonb)'
|
|
550
555
|
]::text[],
|
|
551
|
-
'#325: the effect-derived class is exactly these
|
|
556
|
+
'#325: the effect-derived class is exactly these 61 authenticated-executable writer OVERLOADS — per-OID, and TRANSITIVE, so a delegating wrapper whose own body holds no DML is a member rather than a blind spot');
|
|
552
557
|
|
|
553
558
|
-- WEC-4. THE DELEGATION-CHAIN LIVENESS PIN, and the mitigation for the fail-open inversion the
|
|
554
559
|
-- header describes. Everything above depends on `fn_call_closure()` actually resolving edges; a
|