@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.
Files changed (33) hide show
  1. package/fixtures/already-applied.json +8 -0
  2. package/migrations/20261007000100_agent_board_onboard_requests.sql +139 -0
  3. package/migrations/20261008000100_agent_board_hosted_delegates.sql +3152 -0
  4. package/package.json +1 -1
  5. package/schema-manifest.json +21 -3
  6. package/src/tooling.test.mjs +8 -2
  7. package/tests/agent_board_atomic_fleet_onboarding.test.sql +13 -0
  8. package/tests/agent_board_consent_emission.test.sql +4 -2
  9. package/tests/agent_board_delegate_role_class.test.sql +736 -0
  10. package/tests/agent_board_federated_identity_injective_slug.test.sql +5 -2
  11. package/tests/agent_board_fedsession_stored_identity_gate.test.sql +71 -14
  12. package/tests/agent_board_fk_lock_edges.test.sql +9 -0
  13. package/tests/agent_board_hosted_delegates.test.sql +1134 -0
  14. package/tests/agent_board_ledger_authority.test.sql +34 -5
  15. package/tests/agent_board_membership_current_row_class.test.sql +3 -1
  16. package/tests/agent_board_onboard_requests.test.sql +353 -0
  17. package/tests/agent_board_rowid.test.sql +18 -5
  18. package/tests/agent_board_signer_lifecycle.test.sql +3 -0
  19. package/tests/agent_board_write_enforcement_class.test.sql +8 -3
  20. package/tests/catalog/function_execute_privileges.json +278 -2
  21. package/tests/catalog/p_metadata_parameters.json +48 -0
  22. package/tests/isolation/expected/hosted_delegate_exchange_race.out +93 -0
  23. package/tests/isolation/expected/hosted_delegate_first_exchange_race.out +129 -0
  24. package/tests/isolation/expected/hosted_delegate_owner_provisioning_race.out +91 -0
  25. package/tests/isolation/expected/hosted_delegate_owner_revoke_race.out +137 -0
  26. package/tests/isolation/expected/hosted_delegate_revoke_race.out +87 -0
  27. package/tests/isolation/expected/hosted_delegate_withdraw_evict_race.out +235 -0
  28. package/tests/isolation/specs/hosted_delegate_exchange_race.spec +64 -0
  29. package/tests/isolation/specs/hosted_delegate_first_exchange_race.spec +72 -0
  30. package/tests/isolation/specs/hosted_delegate_owner_provisioning_race.spec +73 -0
  31. package/tests/isolation/specs/hosted_delegate_owner_revoke_race.spec +93 -0
  32. package/tests/isolation/specs/hosted_delegate_revoke_race.spec +59 -0
  33. 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
- select plan(91);
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 seven (#280 added '
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
- 'invite_board_member(p_board_id uuid, p_agent_id uuid, p_created_by_agent_id uuid, p_metadata jsonb)'
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 seven sites carry the MBROWID 3a active-first key');
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(99);
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: eight membership readers plus the two task/message idempotency
510
- -- readers. request_join_board joined the set in MBCONSENT-B(iii), and list_board_members joined
511
- -- it in ROOMCONTRACT; both read board_memberships with the created_at DESC + id DESC tiebreak.
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 eight membership readers plus two task/message idempotency readers');
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
- '60',
433
- '#325 canary: agent_board has 60 base tables — the universe the write scan runs over, pinned so an empty violating set below cannot mean an empty universe');
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 60 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');
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