@consiliency/agent-board-schema 1.56.0 → 1.57.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.
@@ -616,6 +616,10 @@
616
616
  {
617
617
  "id": "20261006000100_agent_board_room_roster_active_first",
618
618
  "sha256": "637480feeca07dbb2fe7a4624dc6d063d0c1f23e6ea3c3ee9a946ad43de4e34e"
619
+ },
620
+ {
621
+ "id": "20261007000100_agent_board_onboard_requests",
622
+ "sha256": "fbdbecfb1c78e2d1b16b37845a74082dcdf1241c61c0356b992e1e01baddc41c"
619
623
  }
620
624
  ]
621
625
  }
@@ -0,0 +1,139 @@
1
+ -- ONBOARD (v11) SL-2: the caller's own join requests (IF-0-ONBOARD-4) and a service-side
2
+ -- invite label lookup for the consent page (IF-0-ONBOARD-11). Additive: two new read-only
3
+ -- functions and one index. schema_version stays 8.
4
+ --
5
+ -- Sources read, each at its latest definition:
6
+ -- * agent_board.current_agent_id() -- 20260920000100 (the resolver
7
+ -- request_join_board, 20260810000100, calls);
8
+ -- * agent_board.board_membership_events -- 20260806000100 (the append-only
9
+ -- consent ledger; seq is the total order, created_at is wall-clock age);
10
+ -- * agent_board.boards.join_request_ttl_days -- 20260806000100;
11
+ -- * the emitter _emit_membership_event -- 20260807000100: a request made by the
12
+ -- subject through request_join_board is `target-authored` / `requested`;
13
+ -- * agent_board.board_invite_tokens -- 20260724000100. invite_board_for_display
14
+ -- checks only the frozen IF-0-ONBOARD-11 subset: not revoked, expires_at > now(), uses
15
+ -- remaining. The redeem core (latest 20260909000100) refuses MORE -- a suspended or
16
+ -- fleet-revoked creator, a lapsed ceiling, a removed agent, fleet-bounded invites -- so a
17
+ -- code that displays a board can still fail to redeem.
18
+
19
+ -- =============================================================================
20
+ -- 1. list_my_board_requests() -- IF-0-ONBOARD-4
21
+ -- =============================================================================
22
+ -- WHICH REQUESTS. The ledger, never the membership row. A request is a (board, caller) with
23
+ -- at least one event where agent_id is the caller, provenance = 'target-authored' and
24
+ -- result_state = 'requested'. board_memberships.created_by_agent_id is NOT a source: the
25
+ -- sink overwrites it on every later write (an approval names the approver), so it cannot say
26
+ -- who asked.
27
+ --
28
+ -- IDENTITY. From the session only, by the resolver request_join_board uses. There is no
29
+ -- parameter, and a null identity (service_role, anon, a revoked credential) returns nothing.
30
+ --
31
+ -- CURRENT STATE. The caller's membership row on that board, chosen active-first (MBROWID 3a,
32
+ -- the key every current-standing reader uses), or 'none' when no row exists. A rejected request
33
+ -- reads 'left' (REJECTSEM, #89: a refusal lands on the recoverable state).
34
+ --
35
+ -- EXPIRY. expires_at = the latest request event's created_at + the board's
36
+ -- join_request_ttl_days. expired mirrors the consent predicates exactly
37
+ -- (created_at < now() - ttl): an expired request no longer counts as consent. It is a property
38
+ -- of the REQUEST and is reported whatever the membership's current state.
39
+ create or replace function agent_board.list_my_board_requests()
40
+ returns table(
41
+ board_id uuid,
42
+ board_name text,
43
+ board_slug text,
44
+ requested_at timestamptz,
45
+ membership_state text,
46
+ expires_at timestamptz,
47
+ expired boolean
48
+ )
49
+ language plpgsql
50
+ stable
51
+ security definer
52
+ set search_path = agent_board, public
53
+ as $$
54
+ declare
55
+ v_actor uuid := agent_board.current_agent_id();
56
+ begin
57
+ if v_actor is null then
58
+ return;
59
+ end if;
60
+
61
+ return query
62
+ select
63
+ b.id,
64
+ b.name,
65
+ b.slug,
66
+ req.created_at,
67
+ coalesce(cur.membership_state, 'none'),
68
+ req.created_at + make_interval(days => b.join_request_ttl_days),
69
+ req.created_at < now() - make_interval(days => b.join_request_ttl_days)
70
+ from (
71
+ -- The latest request event per board, by seq (the ledger's total order).
72
+ select distinct on (e.board_id) e.board_id, e.created_at
73
+ from agent_board.board_membership_events e
74
+ where e.agent_id = v_actor
75
+ and e.provenance = 'target-authored'
76
+ and e.result_state = 'requested'
77
+ order by e.board_id, e.seq desc
78
+ ) req
79
+ join agent_board.boards b on b.id = req.board_id
80
+ left join lateral (
81
+ select bm.membership_state
82
+ from agent_board.board_memberships bm
83
+ where bm.board_id = req.board_id
84
+ and bm.agent_id = v_actor
85
+ order by case when bm.membership_state in ('joined', 'subscribed') then 0 else 1 end,
86
+ bm.created_at desc, bm.id desc
87
+ limit 1
88
+ ) cur on true
89
+ order by req.created_at desc, b.id desc;
90
+ end;
91
+ $$;
92
+
93
+ comment on function agent_board.list_my_board_requests() is
94
+ 'IF-0-ONBOARD-4: the caller''s own join requests as they stand now. Source is the consent ledger (board_membership_events: agent_id = caller, provenance target-authored, result_state requested), never board_memberships.created_by_agent_id, which later writers overwrite. Identity is current_agent_id() only; there is no parameter and a null identity returns nothing, so service_role (no caller identity) is refused EXECUTE. Per board: id, name, slug, the latest request event''s time, the current membership state chosen active-first (MBROWID 3a) or none, expires_at = requested_at + join_request_ttl_days, and expired (the request no longer counts as consent). Requests made before the ledger existed carry only a board-authored backfill_neutral event and are not returned. No per-row history.';
95
+
96
+ revoke all on function agent_board.list_my_board_requests() from public, anon, service_role;
97
+ grant execute on function agent_board.list_my_board_requests() to authenticated;
98
+
99
+ -- =============================================================================
100
+ -- 2. invite_board_for_display(p_code_sha256) -- IF-0-ONBOARD-11
101
+ -- =============================================================================
102
+ -- The Portal's consent page shows which board an in-chat enrollment will join, and it holds
103
+ -- only the digest (the code itself never leaves the agent). The digest is the lowercase hex
104
+ -- sha256 of the code text; the argument is compared case-insensitively.
105
+ --
106
+ -- Only a code passing the IF-0-ONBOARD-11 subset resolves: not revoked, not expired, and uses
107
+ -- remaining. This is narrower than the redeem core (see the header), so display is not a promise
108
+ -- that redeem will succeed. Anything else -- including an unknown digest -- returns no row, one uniform
109
+ -- answer. Codes are 256-bit, so the digest is not a practical oracle; EXECUTE is service_role
110
+ -- only regardless, because the Portal calls it server-side.
111
+ create index if not exists idx_agent_board_board_invite_tokens_code_sha256
112
+ on agent_board.board_invite_tokens (encode(extensions.digest(code, 'sha256'), 'hex'));
113
+
114
+ create or replace function agent_board.invite_board_for_display(p_code_sha256 text)
115
+ returns table(
116
+ board_id uuid,
117
+ board_name text,
118
+ board_slug text,
119
+ preset_role text
120
+ )
121
+ language sql
122
+ stable
123
+ security definer
124
+ set search_path = agent_board, public
125
+ as $$
126
+ select b.id, b.name, b.slug, t.preset_role
127
+ from agent_board.board_invite_tokens t
128
+ join agent_board.boards b on b.id = t.board_id
129
+ where encode(extensions.digest(t.code, 'sha256'), 'hex') = lower(p_code_sha256)
130
+ and t.revoked_at is null
131
+ and t.expires_at > now()
132
+ and (t.max_uses is null or t.uses_count < t.max_uses);
133
+ $$;
134
+
135
+ comment on function agent_board.invite_board_for_display(text) is
136
+ 'IF-0-ONBOARD-11: resolves an invite code''s digest (lowercase hex sha256 of the code, compared case-insensitively) to (board_id, board_name, board_slug, preset_role) for display on the Portal consent page. Only a usable code resolves -- not revoked, expires_at > now(), and max_uses null or uses_count < max_uses (a subset of the redeem core''s checks, so a displayed board can still fail to redeem); revoked, expired, exhausted and unknown codes return no row. Read-only. EXECUTE is service_role only: the Portal calls it server-side.';
137
+
138
+ revoke all on function agent_board.invite_board_for_display(text) from public, anon, authenticated;
139
+ grant execute on function agent_board.invite_board_for_display(text) to service_role;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@consiliency/agent-board-schema",
3
- "version": "1.56.0",
3
+ "version": "1.57.0",
4
4
  "description": "Canonical, immutable, exact-pinnable agent_board schema bundle: ordered SQL migrations, digest + compatibility manifest, and deterministic materialize/plan/check tooling for adopters.",
5
5
  "repository": "https://github.com/Consiliency/message-board.git",
6
6
  "type": "module",
@@ -2,7 +2,7 @@
2
2
  "schema": "agent_board_schema_manifest.v1",
3
3
  "interface": "IF-0-MBPLANE-BUNDLE-1",
4
4
  "package": "@consiliency/agent-board-schema",
5
- "packageVersion": "1.56.0",
5
+ "packageVersion": "1.57.0",
6
6
  "schemaVersion": 8,
7
7
  "minClientVersion": "1.1.0",
8
8
  "source": {
@@ -46,8 +46,8 @@
46
46
  "usage": "upsert (retention/monitoring defaults; on conflict do nothing)"
47
47
  }
48
48
  ],
49
- "migrationCount": 154,
50
- "aggregateSha256": "d0b74be9b41032a78ad45227706a48283f931b40cd6350c63c619a0e153c0cee",
49
+ "migrationCount": 155,
50
+ "aggregateSha256": "347357b36b5ee37fbde189e005993c40df1e63a2d9d247b53823761304ae127e",
51
51
  "migrations": [
52
52
  {
53
53
  "order": 0,
@@ -1434,6 +1434,15 @@
1434
1434
  "sourceRef": "message-board",
1435
1435
  "bytes": 5470,
1436
1436
  "sha256": "637480feeca07dbb2fe7a4624dc6d063d0c1f23e6ea3c3ee9a946ad43de4e34e"
1437
+ },
1438
+ {
1439
+ "order": 154,
1440
+ "id": "20261007000100_agent_board_onboard_requests",
1441
+ "filename": "20261007000100_agent_board_onboard_requests.sql",
1442
+ "origin": "message-board-forward",
1443
+ "sourceRef": "message-board",
1444
+ "bytes": 7655,
1445
+ "sha256": "fbdbecfb1c78e2d1b16b37845a74082dcdf1241c61c0356b992e1e01baddc41c"
1437
1446
  }
1438
1447
  ],
1439
1448
  "excludedBridge": {
@@ -38,9 +38,9 @@ describe('manifest integrity', () => {
38
38
  expect(manifest.schemaVersion).toBe(8);
39
39
  expect(manifest.minClientVersion).toMatch(/^\d+\.\d+\.\d+$/);
40
40
  });
41
- it('registers the four MBAUTHZ + four MBSENDAUTHZ + four MBSESSION + four MBDELEG + three MBFED + three MBHARDEN + two MBPORTALGATE + two MBPORTAL-PR32 + two ROOMOWN + two ROOMLINK + one ROOMDELEG + one LOCALAUTH + one ROOMUIAPI + one FEDSESSION + one FEDSESSION-AUTOJOIN + one GATENULL + one MEMBSTATE + one AGENTSDIR + one LEAVEIDEM + one ROLERESET + one MBROWID + one MBHYGIENE + one MBCONSENT-A + one MBCONSENT-B(a) + one MBCONSENT-B(i) + one MBCONSENT-B(ii) + one MBCONSENT-B(iii) + one MBHARDEN-#97 + one MBHARDEN-#118 + one INVITEPOLICY + one OBSGATE + one OBSGATE-2 + one OBSGATE-3 + one OBSGATE-4 + one FLEETMACHINERLS + one RESOLVENOOP + one ROOMDELEG-LANEB + one CLAIMKEY + two MINTHARDEN + one FLEETMACHINEHIJACK + one CLAIMGATE-SHADOW + one CLAIMGATE-ENFORCE + one FLEETMACHINEREACTIVATION + one DELEGCAPCEILING + one FLEETMACHINERECLAIM + one DELEGSOLEWRITER + one FLEETDOMAINRECLAIM + five ATOMICFLEETONBOARDING (onboarding + ceiling-lock + revocation-evidence + fedsession-gate + fedsession-lock-order) + one OUTBOXWAKE + one ENROLLLOCKORDER + one FEDPROVIDERABSENCE + one WAKERECOVERY + one CONSENTRECONCILE + one ENROLLRESUMPTION + one SIGNEDAUTHORITY + one SIGNERLIFECYCLE + one LEDGERAUTHORITY + one CEILINGLOCKCOMMENT + one LEDGERBOARDCREATE + one RECEIPTSUBJECTGATE + one TASKTHREADLOCKORDER + one CLAIMSUBJECTGATE + one FEDSESSIONIDENTITYGATE + one FEDIDENTITYSLUG + one INVITENODOWNGRADE + one SIGNEDWRITEFLAG + one SIGNEDWRITEMODEL + one RTCREDCLAIM + one ORIGINPOSTURE + one BOARDCLAIMPOLICY + one HEARTBEATPRESERVE + one LIVENESSDENOM + one OWNERLESSRENEWTTL + one OWNERLESSQUOTACENSUS + one FLEETDOMAINNODOWNGRADE + one OWNERLESSQUOTACONVERGE + one ADVISORYREVERTRESET + one CLAIMPOLICYRACE + one CLAIMPOLICYLOCKGRANT + one SESSIONCHALLENGESUPERSEDE + one ROOMCONTRACT + one CALLBACKCONSUMER + one ENROLLTOKENSINGLEUSE + one ROOMCONTRACT-ACTIVEFIRST forward migrations (migrationCount = prior 113 + 5 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 = 154)', () => {
41
+ it('registers the four MBAUTHZ + four MBSENDAUTHZ + four MBSESSION + four MBDELEG + three MBFED + three MBHARDEN + two MBPORTALGATE + two MBPORTAL-PR32 + two ROOMOWN + two ROOMLINK + one ROOMDELEG + one LOCALAUTH + one ROOMUIAPI + one FEDSESSION + one FEDSESSION-AUTOJOIN + one GATENULL + one MEMBSTATE + one AGENTSDIR + one LEAVEIDEM + one ROLERESET + one MBROWID + one MBHYGIENE + one MBCONSENT-A + one MBCONSENT-B(a) + one MBCONSENT-B(i) + one MBCONSENT-B(ii) + one MBCONSENT-B(iii) + one MBHARDEN-#97 + one MBHARDEN-#118 + one INVITEPOLICY + one OBSGATE + one OBSGATE-2 + one OBSGATE-3 + one OBSGATE-4 + one FLEETMACHINERLS + one RESOLVENOOP + one ROOMDELEG-LANEB + one CLAIMKEY + two MINTHARDEN + one FLEETMACHINEHIJACK + one CLAIMGATE-SHADOW + one CLAIMGATE-ENFORCE + one FLEETMACHINEREACTIVATION + one DELEGCAPCEILING + one FLEETMACHINERECLAIM + one DELEGSOLEWRITER + one FLEETDOMAINRECLAIM + five ATOMICFLEETONBOARDING (onboarding + ceiling-lock + revocation-evidence + fedsession-gate + fedsession-lock-order) + one OUTBOXWAKE + one ENROLLLOCKORDER + one FEDPROVIDERABSENCE + one WAKERECOVERY + one CONSENTRECONCILE + one ENROLLRESUMPTION + one SIGNEDAUTHORITY + one SIGNERLIFECYCLE + one LEDGERAUTHORITY + one CEILINGLOCKCOMMENT + one LEDGERBOARDCREATE + one RECEIPTSUBJECTGATE + one TASKTHREADLOCKORDER + one CLAIMSUBJECTGATE + one FEDSESSIONIDENTITYGATE + one FEDIDENTITYSLUG + one INVITENODOWNGRADE + one SIGNEDWRITEFLAG + one SIGNEDWRITEMODEL + one RTCREDCLAIM + one ORIGINPOSTURE + one BOARDCLAIMPOLICY + one HEARTBEATPRESERVE + one LIVENESSDENOM + one OWNERLESSRENEWTTL + one OWNERLESSQUOTACENSUS + one FLEETDOMAINNODOWNGRADE + one OWNERLESSQUOTACONVERGE + one ADVISORYREVERTRESET + one CLAIMPOLICYRACE + one CLAIMPOLICYLOCKGRANT + one SESSIONCHALLENGESUPERSEDE + one ROOMCONTRACT + one CALLBACKCONSUMER + one ENROLLTOKENSINGLEUSE + one ROOMCONTRACT-ACTIVEFIRST + one ONBOARD forward migrations (migrationCount = prior 113 + 5 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 = 155)', () => {
42
42
  expect(manifest.migrationCount).toBe(manifest.migrations.length);
43
- expect(manifest.migrationCount).toBe(154);
43
+ expect(manifest.migrationCount).toBe(155);
44
44
  // MBSESSION switched enforcement on (scoped-send re-grant): the client-visible
45
45
  // schema version bumped to 8. MBDELEG tightens delegation and MBFED adds only
46
46
  // enrollment-only additive surface (identity_providers registry, enrollment audit,
@@ -343,6 +343,9 @@ describe('manifest integrity', () => {
343
343
  // ROOMCONTRACT-ACTIVEFIRST (#542): list_board_members chooses current standing
344
344
  // active-first (MBROWID 3a) at both its authorization and projection sites.
345
345
  '20261006000100_agent_board_room_roster_active_first.sql',
346
+ // ONBOARD (v11): list_my_board_requests (IF-0-ONBOARD-4) and the service-only
347
+ // invite_board_for_display digest lookup (IF-0-ONBOARD-11).
348
+ '20261007000100_agent_board_onboard_requests.sql',
346
349
  ]) {
347
350
  expect(names.has(f)).toBe(true);
348
351
  }
@@ -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;
@@ -1036,6 +1036,15 @@
1036
1036
  "authenticated": true
1037
1037
  }
1038
1038
  },
1039
+ {
1040
+ "name": "invite_board_for_display",
1041
+ "signature": "invite_board_for_display(p_code_sha256 text)",
1042
+ "executable_by": {
1043
+ "anon": false,
1044
+ "service_role": true,
1045
+ "authenticated": false
1046
+ }
1047
+ },
1039
1048
  {
1040
1049
  "name": "invite_board_member",
1041
1050
  "signature": "invite_board_member(p_board_id uuid, p_agent_id uuid, p_created_by_agent_id uuid, p_metadata jsonb)",
@@ -1090,6 +1099,15 @@
1090
1099
  "authenticated": true
1091
1100
  }
1092
1101
  },
1102
+ {
1103
+ "name": "list_my_board_requests",
1104
+ "signature": "list_my_board_requests()",
1105
+ "executable_by": {
1106
+ "anon": false,
1107
+ "service_role": false,
1108
+ "authenticated": true
1109
+ }
1110
+ },
1093
1111
  {
1094
1112
  "name": "mark_read",
1095
1113
  "signature": "mark_read(p_thread_id uuid, p_agent_id uuid, p_last_read_message_sequence bigint)",
@@ -461,6 +461,10 @@
461
461
  "signature": "heartbeat(p_agent_id uuid, p_thread_id uuid, p_availability text, p_activity_summary text, p_expires_in interval, p_metadata jsonb)",
462
462
  "declares_p_metadata": true
463
463
  },
464
+ {
465
+ "signature": "invite_board_for_display(p_code_sha256 text)",
466
+ "declares_p_metadata": false
467
+ },
464
468
  {
465
469
  "signature": "invite_board_member(p_board_id uuid, p_agent_id uuid, p_created_by_agent_id uuid, p_metadata jsonb)",
466
470
  "declares_p_metadata": true
@@ -485,6 +489,10 @@
485
489
  "signature": "list_board_pending_memberships(p_board_id uuid)",
486
490
  "declares_p_metadata": false
487
491
  },
492
+ {
493
+ "signature": "list_my_board_requests()",
494
+ "declares_p_metadata": false
495
+ },
488
496
  {
489
497
  "signature": "mark_read(p_thread_id uuid, p_agent_id uuid, p_last_read_message_sequence bigint)",
490
498
  "declares_p_metadata": false