@consiliency/agent-board-schema 1.57.0 → 1.59.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 (36) hide show
  1. package/fixtures/already-applied.json +8 -0
  2. package/migrations/20261008000100_agent_board_hosted_delegates.sql +3152 -0
  3. package/migrations/20261009000100_agent_board_hosted_followups.sql +665 -0
  4. package/package.json +1 -1
  5. package/schema-manifest.json +21 -3
  6. package/src/tooling.test.mjs +9 -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 +1278 -0
  14. package/tests/agent_board_ledger_authority.test.sql +34 -5
  15. package/tests/agent_board_signer_lifecycle.test.sql +3 -0
  16. package/tests/agent_board_write_enforcement_class.test.sql +8 -3
  17. package/tests/catalog/function_execute_privileges.json +269 -2
  18. package/tests/catalog/p_metadata_parameters.json +44 -0
  19. package/tests/isolation/expected/hosted_delegate_add_tombstone_race.out +105 -0
  20. package/tests/isolation/expected/hosted_delegate_exchange_agent_revoke_race.out +66 -0
  21. package/tests/isolation/expected/hosted_delegate_exchange_race.out +93 -0
  22. package/tests/isolation/expected/hosted_delegate_first_exchange_race.out +129 -0
  23. package/tests/isolation/expected/hosted_delegate_owner_provisioning_race.out +91 -0
  24. package/tests/isolation/expected/hosted_delegate_owner_revoke_race.out +137 -0
  25. package/tests/isolation/expected/hosted_delegate_revoke_race.out +87 -0
  26. package/tests/isolation/expected/hosted_delegate_tombstone_revoke_race.out +170 -0
  27. package/tests/isolation/expected/hosted_delegate_withdraw_evict_race.out +235 -0
  28. package/tests/isolation/specs/hosted_delegate_add_tombstone_race.spec +102 -0
  29. package/tests/isolation/specs/hosted_delegate_exchange_agent_revoke_race.spec +92 -0
  30. package/tests/isolation/specs/hosted_delegate_exchange_race.spec +64 -0
  31. package/tests/isolation/specs/hosted_delegate_first_exchange_race.spec +72 -0
  32. package/tests/isolation/specs/hosted_delegate_owner_provisioning_race.spec +73 -0
  33. package/tests/isolation/specs/hosted_delegate_owner_revoke_race.spec +93 -0
  34. package/tests/isolation/specs/hosted_delegate_revoke_race.spec +59 -0
  35. package/tests/isolation/specs/hosted_delegate_tombstone_revoke_race.spec +158 -0
  36. 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
@@ -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
@@ -1,5 +1,5 @@
1
1
  {
2
- "$comment": "GENERATED by scripts/build-function-privileges-snapshot.mjs from pg_catalog — do not hand-edit. EVERY agent_board function (each overload by signature) with has_function_privilege for the three PostgREST roles, and EVERY agent_board view with has_table_privilege(SELECT). Derived with NO name filter. The MCP tool-group boundary test ranks a tool elevated_admin when it reaches a function or view authenticated cannot use, refuses a name whose overloads disagree, and fails a reachable name with no row here (#425).",
2
+ "$comment": "GENERATED by scripts/build-function-privileges-snapshot.mjs from pg_catalog — do not hand-edit. EVERY agent_board function (each overload by signature) with has_function_privilege for the three PostgREST roles, and EVERY agent_board view with has_table_privilege(SELECT). Derived with NO name filter. The MCP tool-group boundary test ranks a tool elevated_admin when it reaches a function or view authenticated cannot use, refuses a name whose overloads disagree, and fails a reachable name with no row here (#425). The `delegate` block (HOSTED) is additive: the agent_board_delegate allow set as delegate_allowed_operations states it, and what that role can actually EXECUTE (non-definer functions keep PUBLIC EXECUTE and appear too) and SELECT.",
3
3
  "functions": [
4
4
  {
5
5
  "name": "_active_board_membership_id",
@@ -64,6 +64,15 @@
64
64
  "authenticated": false
65
65
  }
66
66
  },
67
+ {
68
+ "name": "_agents_is_delegate_immutable",
69
+ "signature": "_agents_is_delegate_immutable()",
70
+ "executable_by": {
71
+ "anon": false,
72
+ "service_role": false,
73
+ "authenticated": false
74
+ }
75
+ },
67
76
  {
68
77
  "name": "_append_task_event",
69
78
  "signature": "_append_task_event(p_origin_posture text, p_task_id uuid, p_actor_agent_id uuid, p_event_type text, p_payload jsonb, p_status text, p_assigned_agent_id uuid, p_completed_at timestamp with time zone)",
@@ -325,6 +334,51 @@
325
334
  "authenticated": false
326
335
  }
327
336
  },
337
+ {
338
+ "name": "_delegate_instance_slug",
339
+ "signature": "_delegate_instance_slug(p_issuer text, p_instance_id text)",
340
+ "executable_by": {
341
+ "anon": false,
342
+ "service_role": false,
343
+ "authenticated": false
344
+ }
345
+ },
346
+ {
347
+ "name": "_delegate_instances_owner_identity_immutable",
348
+ "signature": "_delegate_instances_owner_identity_immutable()",
349
+ "executable_by": {
350
+ "anon": false,
351
+ "service_role": false,
352
+ "authenticated": false
353
+ }
354
+ },
355
+ {
356
+ "name": "_delegate_principal_agent_id",
357
+ "signature": "_delegate_principal_agent_id(p_agent_id uuid)",
358
+ "executable_by": {
359
+ "anon": false,
360
+ "service_role": false,
361
+ "authenticated": false
362
+ }
363
+ },
364
+ {
365
+ "name": "_delegate_visible_agent",
366
+ "signature": "_delegate_visible_agent(p_agent_id uuid)",
367
+ "executable_by": {
368
+ "anon": false,
369
+ "service_role": false,
370
+ "authenticated": false
371
+ }
372
+ },
373
+ {
374
+ "name": "_delegate_visible_board_ids",
375
+ "signature": "_delegate_visible_board_ids()",
376
+ "executable_by": {
377
+ "anon": false,
378
+ "service_role": false,
379
+ "authenticated": false
380
+ }
381
+ },
328
382
  {
329
383
  "name": "_emit_membership_event",
330
384
  "signature": "_emit_membership_event(p_board_id uuid, p_agent_id uuid, p_membership_state text, p_event_kind text)",
@@ -658,6 +712,24 @@
658
712
  "authenticated": false
659
713
  }
660
714
  },
715
+ {
716
+ "name": "_refuse_delegate_addressee",
717
+ "signature": "_refuse_delegate_addressee(p_agent_id uuid, p_board_id uuid, p_what text)",
718
+ "executable_by": {
719
+ "anon": false,
720
+ "service_role": false,
721
+ "authenticated": false
722
+ }
723
+ },
724
+ {
725
+ "name": "_refuse_delegate_membership_target",
726
+ "signature": "_refuse_delegate_membership_target(p_agent_id uuid, p_operation text)",
727
+ "executable_by": {
728
+ "anon": false,
729
+ "service_role": false,
730
+ "authenticated": false
731
+ }
732
+ },
661
733
  {
662
734
  "name": "_require_platform_admin_user_id",
663
735
  "signature": "_require_platform_admin_user_id()",
@@ -784,6 +856,15 @@
784
856
  "authenticated": true
785
857
  }
786
858
  },
859
+ {
860
+ "name": "add_delegate_to_board",
861
+ "signature": "add_delegate_to_board(p_agent_id uuid, p_board_id uuid)",
862
+ "executable_by": {
863
+ "anon": false,
864
+ "service_role": false,
865
+ "authenticated": true
866
+ }
867
+ },
787
868
  {
788
869
  "name": "agent_liveness_summary",
789
870
  "signature": "agent_liveness_summary()",
@@ -991,6 +1072,15 @@
991
1072
  "authenticated": false
992
1073
  }
993
1074
  },
1075
+ {
1076
+ "name": "exchange_delegate_credential",
1077
+ "signature": "exchange_delegate_credential(p_claims jsonb)",
1078
+ "executable_by": {
1079
+ "anon": false,
1080
+ "service_role": true,
1081
+ "authenticated": false
1082
+ }
1083
+ },
994
1084
  {
995
1085
  "name": "expand_recipients_dry_run",
996
1086
  "signature": "expand_recipients_dry_run(p_actor_agent_id uuid, p_recipient_descriptors jsonb, p_thread_idempotency_key text)",
@@ -1675,6 +1765,15 @@
1675
1765
  "authenticated": true
1676
1766
  }
1677
1767
  },
1768
+ {
1769
+ "name": "tombstone_delegate_instance",
1770
+ "signature": "tombstone_delegate_instance(p_instance_id text, p_issuer text, p_owner_sub text, p_reason text)",
1771
+ "executable_by": {
1772
+ "anon": false,
1773
+ "service_role": true,
1774
+ "authenticated": false
1775
+ }
1776
+ },
1678
1777
  {
1679
1778
  "name": "unblock_board_inviter",
1680
1779
  "signature": "unblock_board_inviter(p_board_id uuid, p_inviter_agent_id uuid, p_target_agent_id uuid)",
@@ -1955,5 +2054,173 @@
1955
2054
  "authenticated": true
1956
2055
  }
1957
2056
  }
1958
- ]
2057
+ ],
2058
+ "delegate": {
2059
+ "role": "agent_board_delegate",
2060
+ "allowed_operations": [
2061
+ {
2062
+ "kind": "ambient_rpc",
2063
+ "name": "get_schema_version",
2064
+ "signature": "get_schema_version()"
2065
+ },
2066
+ {
2067
+ "kind": "predicate",
2068
+ "name": "_delegate_visible_agent",
2069
+ "signature": "_delegate_visible_agent(uuid)"
2070
+ },
2071
+ {
2072
+ "kind": "predicate",
2073
+ "name": "_delegate_visible_board_ids",
2074
+ "signature": "_delegate_visible_board_ids()"
2075
+ },
2076
+ {
2077
+ "kind": "predicate",
2078
+ "name": "current_agent_id",
2079
+ "signature": "current_agent_id()"
2080
+ },
2081
+ {
2082
+ "kind": "read_table",
2083
+ "name": "agent_signing_keys",
2084
+ "signature": "agent_signing_keys"
2085
+ },
2086
+ {
2087
+ "kind": "read_table",
2088
+ "name": "agents",
2089
+ "signature": "agents"
2090
+ },
2091
+ {
2092
+ "kind": "read_table",
2093
+ "name": "board_memberships",
2094
+ "signature": "board_memberships"
2095
+ },
2096
+ {
2097
+ "kind": "read_table",
2098
+ "name": "boards",
2099
+ "signature": "boards"
2100
+ },
2101
+ {
2102
+ "kind": "read_table",
2103
+ "name": "claims",
2104
+ "signature": "claims"
2105
+ },
2106
+ {
2107
+ "kind": "read_table",
2108
+ "name": "delivery_recipients",
2109
+ "signature": "delivery_recipients"
2110
+ },
2111
+ {
2112
+ "kind": "read_table",
2113
+ "name": "escalations",
2114
+ "signature": "escalations"
2115
+ },
2116
+ {
2117
+ "kind": "read_table",
2118
+ "name": "inbox_items",
2119
+ "signature": "inbox_items"
2120
+ },
2121
+ {
2122
+ "kind": "read_table",
2123
+ "name": "messages",
2124
+ "signature": "messages"
2125
+ },
2126
+ {
2127
+ "kind": "read_table",
2128
+ "name": "presence",
2129
+ "signature": "presence"
2130
+ },
2131
+ {
2132
+ "kind": "read_table",
2133
+ "name": "signature_evidence",
2134
+ "signature": "signature_evidence"
2135
+ },
2136
+ {
2137
+ "kind": "read_table",
2138
+ "name": "task_events",
2139
+ "signature": "task_events"
2140
+ },
2141
+ {
2142
+ "kind": "read_table",
2143
+ "name": "tasks",
2144
+ "signature": "tasks"
2145
+ },
2146
+ {
2147
+ "kind": "read_view",
2148
+ "name": "v_agent_dashboard",
2149
+ "signature": "v_agent_dashboard"
2150
+ },
2151
+ {
2152
+ "kind": "read_view",
2153
+ "name": "v_agent_inbox",
2154
+ "signature": "v_agent_inbox"
2155
+ },
2156
+ {
2157
+ "kind": "read_view",
2158
+ "name": "v_board_feed",
2159
+ "signature": "v_board_feed"
2160
+ },
2161
+ {
2162
+ "kind": "read_view",
2163
+ "name": "v_board_memberships",
2164
+ "signature": "v_board_memberships"
2165
+ },
2166
+ {
2167
+ "kind": "read_view",
2168
+ "name": "v_thread_feed",
2169
+ "signature": "v_thread_feed"
2170
+ },
2171
+ {
2172
+ "kind": "rpc",
2173
+ "name": "create_or_find_thread",
2174
+ "signature": "create_or_find_thread(uuid, text, text, text, jsonb)"
2175
+ },
2176
+ {
2177
+ "kind": "rpc",
2178
+ "name": "post_board_message",
2179
+ "signature": "post_board_message(uuid, uuid, text, text, text, text, text, text, text, jsonb)"
2180
+ },
2181
+ {
2182
+ "kind": "rpc",
2183
+ "name": "post_board_message",
2184
+ "signature": "post_board_message(uuid, uuid, uuid, text, text, text, text, text, jsonb)"
2185
+ },
2186
+ {
2187
+ "kind": "rpc",
2188
+ "name": "post_message",
2189
+ "signature": "post_message(uuid, uuid, text, text, uuid, jsonb)"
2190
+ }
2191
+ ],
2192
+ "executable_functions": [
2193
+ "_addressing_descriptors_array(p_descriptors jsonb)",
2194
+ "_board_membership_events_provenance_guard()",
2195
+ "_delegate_visible_agent(p_agent_id uuid)",
2196
+ "_delegate_visible_board_ids()",
2197
+ "create_or_find_thread(p_created_by_agent_id uuid, p_subject text, p_idempotency_key text, p_thread_type text, p_metadata jsonb)",
2198
+ "current_agent_id()",
2199
+ "get_schema_version()",
2200
+ "post_board_message(p_thread_id uuid, p_author_agent_id uuid, p_board_id uuid, p_subject text, p_body text, p_message_type text, p_priority text, p_idempotency_key text, p_metadata jsonb)",
2201
+ "post_board_message(p_thread_id uuid, p_author_agent_id uuid, p_board_slug text, p_addressing_mode text, p_subject text, p_body text, p_message_type text, p_priority text, p_idempotency_key text, p_metadata jsonb)",
2202
+ "post_message(p_thread_id uuid, p_author_agent_id uuid, p_body text, p_message_type text, p_task_id uuid, p_metadata jsonb)",
2203
+ "sync_agent_principal_columns()"
2204
+ ],
2205
+ "selectable_relations": [
2206
+ "agent_signing_keys",
2207
+ "agents",
2208
+ "board_memberships",
2209
+ "boards",
2210
+ "claims",
2211
+ "delivery_recipients",
2212
+ "escalations",
2213
+ "inbox_items",
2214
+ "messages",
2215
+ "presence",
2216
+ "signature_evidence",
2217
+ "task_events",
2218
+ "tasks",
2219
+ "v_agent_dashboard",
2220
+ "v_agent_inbox",
2221
+ "v_board_feed",
2222
+ "v_board_memberships",
2223
+ "v_thread_feed"
2224
+ ]
2225
+ }
1959
2226
  }
@@ -29,6 +29,10 @@
29
29
  "signature": "_agent_principal_active(p_agent_id uuid)",
30
30
  "declares_p_metadata": false
31
31
  },
32
+ {
33
+ "signature": "_agents_is_delegate_immutable()",
34
+ "declares_p_metadata": false
35
+ },
32
36
  {
33
37
  "signature": "_append_task_event(p_origin_posture text, p_task_id uuid, p_actor_agent_id uuid, p_event_type text, p_payload jsonb, p_status text, p_assigned_agent_id uuid, p_completed_at timestamp with time zone)",
34
38
  "declares_p_metadata": false
@@ -145,6 +149,26 @@
145
149
  "signature": "_default_provision_display_name(p_slug text)",
146
150
  "declares_p_metadata": false
147
151
  },
152
+ {
153
+ "signature": "_delegate_instance_slug(p_issuer text, p_instance_id text)",
154
+ "declares_p_metadata": false
155
+ },
156
+ {
157
+ "signature": "_delegate_instances_owner_identity_immutable()",
158
+ "declares_p_metadata": false
159
+ },
160
+ {
161
+ "signature": "_delegate_principal_agent_id(p_agent_id uuid)",
162
+ "declares_p_metadata": false
163
+ },
164
+ {
165
+ "signature": "_delegate_visible_agent(p_agent_id uuid)",
166
+ "declares_p_metadata": false
167
+ },
168
+ {
169
+ "signature": "_delegate_visible_board_ids()",
170
+ "declares_p_metadata": false
171
+ },
148
172
  {
149
173
  "signature": "_emit_membership_event(p_board_id uuid, p_agent_id uuid, p_membership_state text, p_event_kind text)",
150
174
  "declares_p_metadata": false
@@ -293,6 +317,14 @@
293
317
  "signature": "_redeem_unbounded_invite_for_agent(p_code text, p_agent_id uuid)",
294
318
  "declares_p_metadata": false
295
319
  },
320
+ {
321
+ "signature": "_refuse_delegate_addressee(p_agent_id uuid, p_board_id uuid, p_what text)",
322
+ "declares_p_metadata": false
323
+ },
324
+ {
325
+ "signature": "_refuse_delegate_membership_target(p_agent_id uuid, p_operation text)",
326
+ "declares_p_metadata": false
327
+ },
296
328
  {
297
329
  "signature": "_require_platform_admin_user_id()",
298
330
  "declares_p_metadata": false
@@ -349,6 +381,10 @@
349
381
  "signature": "add_artifact(p_created_by_agent_id uuid, p_uri text, p_artifact_type text, p_storage_kind text, p_thread_id uuid, p_task_id uuid, p_message_id uuid, p_task_event_id uuid, p_metadata jsonb)",
350
382
  "declares_p_metadata": true
351
383
  },
384
+ {
385
+ "signature": "add_delegate_to_board(p_agent_id uuid, p_board_id uuid)",
386
+ "declares_p_metadata": false
387
+ },
352
388
  {
353
389
  "signature": "agent_liveness_summary()",
354
390
  "declares_p_metadata": false
@@ -441,6 +477,10 @@
441
477
  "signature": "evaluate_signed_write_fleet_policy(p_operation text, p_payload jsonb, p_gateway_decision jsonb)",
442
478
  "declares_p_metadata": false
443
479
  },
480
+ {
481
+ "signature": "exchange_delegate_credential(p_claims jsonb)",
482
+ "declares_p_metadata": false
483
+ },
444
484
  {
445
485
  "signature": "expand_recipients_dry_run(p_actor_agent_id uuid, p_recipient_descriptors jsonb, p_thread_idempotency_key text)",
446
486
  "declares_p_metadata": false
@@ -745,6 +785,10 @@
745
785
  "signature": "sync_agent_principal_columns()",
746
786
  "declares_p_metadata": false
747
787
  },
788
+ {
789
+ "signature": "tombstone_delegate_instance(p_instance_id text, p_issuer text, p_owner_sub text, p_reason text)",
790
+ "declares_p_metadata": false
791
+ },
748
792
  {
749
793
  "signature": "unblock_board_inviter(p_board_id uuid, p_inviter_agent_id uuid, p_target_agent_id uuid)",
750
794
  "declares_p_metadata": false
@@ -0,0 +1,105 @@
1
+ Parsed test spec with 3 sessions
2
+
3
+ starting permutation: o_begin o_add t_begin t_tomb o_commit t_commit check
4
+ step o_begin:
5
+ BEGIN;
6
+ select set_config('request.jwt.claims', json_build_object('role', 'authenticated', 'sub',
7
+ (select auth_user_id from agent_board.agents
8
+ where handle = agent_board._federated_identity_slug('https://iso-at.test', 'iso-at-owner')))::text, true) is not null as owner_session;
9
+
10
+ owner_session
11
+ -------------
12
+ t
13
+ (1 row)
14
+
15
+ step o_add:
16
+ select membership_state from agent_board.add_delegate_to_board(
17
+ (select agent_id from agent_board.delegate_instances where instance_id = 'iso_at_instance_00001'),
18
+ '16591300-0000-4000-8000-000000000001');
19
+
20
+ membership_state
21
+ ----------------
22
+ joined
23
+ (1 row)
24
+
25
+ step t_begin: BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role';
26
+ step t_tomb:
27
+ select first from agent_board.tombstone_delegate_instance(
28
+ 'iso_at_instance_00001', 'https://iso-at.test', 'iso-at-owner', 'owner_revoked');
29
+ <waiting ...>
30
+ step o_commit: COMMIT;
31
+ step t_tomb: <... completed>
32
+ first
33
+ -----
34
+ t
35
+ (1 row)
36
+
37
+ step t_commit: COMMIT;
38
+ step check:
39
+ select (select (di.tombstoned_at is not null)::text from agent_board.delegate_instances di
40
+ where di.instance_id = 'iso_at_instance_00001') as tombstoned,
41
+ (select a.status from agent_board.agents a
42
+ join agent_board.delegate_instances di on di.agent_id = a.id
43
+ where di.instance_id = 'iso_at_instance_00001') as delegate_status,
44
+ (select string_agg(bm.membership_state, ',' order by bm.created_at, bm.id) from agent_board.board_memberships bm
45
+ join agent_board.delegate_instances di on di.agent_id = bm.agent_id
46
+ where di.instance_id = 'iso_at_instance_00001') as delegate_memberships,
47
+ (select count(*) from agent_board.board_memberships bm
48
+ join agent_board.delegate_instances di on di.agent_id = bm.agent_id
49
+ where di.tombstoned_at is not null and bm.membership_state in ('joined', 'subscribed')) as tombstoned_and_active;
50
+
51
+ tombstoned|delegate_status|delegate_memberships|tombstoned_and_active
52
+ ----------+---------------+--------------------+---------------------
53
+ true |revoked |left | 0
54
+ (1 row)
55
+
56
+
57
+ starting permutation: t_begin t_tomb o_begin o_add t_commit o_commit check
58
+ step t_begin: BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role';
59
+ step t_tomb:
60
+ select first from agent_board.tombstone_delegate_instance(
61
+ 'iso_at_instance_00001', 'https://iso-at.test', 'iso-at-owner', 'owner_revoked');
62
+
63
+ first
64
+ -----
65
+ t
66
+ (1 row)
67
+
68
+ step o_begin:
69
+ BEGIN;
70
+ select set_config('request.jwt.claims', json_build_object('role', 'authenticated', 'sub',
71
+ (select auth_user_id from agent_board.agents
72
+ where handle = agent_board._federated_identity_slug('https://iso-at.test', 'iso-at-owner')))::text, true) is not null as owner_session;
73
+
74
+ owner_session
75
+ -------------
76
+ t
77
+ (1 row)
78
+
79
+ step o_add:
80
+ select membership_state from agent_board.add_delegate_to_board(
81
+ (select agent_id from agent_board.delegate_instances where instance_id = 'iso_at_instance_00001'),
82
+ '16591300-0000-4000-8000-000000000001');
83
+ <waiting ...>
84
+ step t_commit: COMMIT;
85
+ step o_add: <... completed>
86
+ ERROR: the hosted delegate instance is not live
87
+ step o_commit: COMMIT;
88
+ step check:
89
+ select (select (di.tombstoned_at is not null)::text from agent_board.delegate_instances di
90
+ where di.instance_id = 'iso_at_instance_00001') as tombstoned,
91
+ (select a.status from agent_board.agents a
92
+ join agent_board.delegate_instances di on di.agent_id = a.id
93
+ where di.instance_id = 'iso_at_instance_00001') as delegate_status,
94
+ (select string_agg(bm.membership_state, ',' order by bm.created_at, bm.id) from agent_board.board_memberships bm
95
+ join agent_board.delegate_instances di on di.agent_id = bm.agent_id
96
+ where di.instance_id = 'iso_at_instance_00001') as delegate_memberships,
97
+ (select count(*) from agent_board.board_memberships bm
98
+ join agent_board.delegate_instances di on di.agent_id = bm.agent_id
99
+ where di.tombstoned_at is not null and bm.membership_state in ('joined', 'subscribed')) as tombstoned_and_active;
100
+
101
+ tombstoned|delegate_status|delegate_memberships|tombstoned_and_active
102
+ ----------+---------------+--------------------+---------------------
103
+ true |revoked | | 0
104
+ (1 row)
105
+