@consiliency/agent-board-schema 1.57.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 (29) hide show
  1. package/fixtures/already-applied.json +4 -0
  2. package/migrations/20261008000100_agent_board_hosted_delegates.sql +3152 -0
  3. package/package.json +1 -1
  4. package/schema-manifest.json +12 -3
  5. package/src/tooling.test.mjs +5 -2
  6. package/tests/agent_board_atomic_fleet_onboarding.test.sql +13 -0
  7. package/tests/agent_board_consent_emission.test.sql +4 -2
  8. package/tests/agent_board_delegate_role_class.test.sql +736 -0
  9. package/tests/agent_board_federated_identity_injective_slug.test.sql +5 -2
  10. package/tests/agent_board_fedsession_stored_identity_gate.test.sql +71 -14
  11. package/tests/agent_board_fk_lock_edges.test.sql +9 -0
  12. package/tests/agent_board_hosted_delegates.test.sql +1134 -0
  13. package/tests/agent_board_ledger_authority.test.sql +34 -5
  14. package/tests/agent_board_signer_lifecycle.test.sql +3 -0
  15. package/tests/agent_board_write_enforcement_class.test.sql +8 -3
  16. package/tests/catalog/function_execute_privileges.json +260 -2
  17. package/tests/catalog/p_metadata_parameters.json +40 -0
  18. package/tests/isolation/expected/hosted_delegate_exchange_race.out +93 -0
  19. package/tests/isolation/expected/hosted_delegate_first_exchange_race.out +129 -0
  20. package/tests/isolation/expected/hosted_delegate_owner_provisioning_race.out +91 -0
  21. package/tests/isolation/expected/hosted_delegate_owner_revoke_race.out +137 -0
  22. package/tests/isolation/expected/hosted_delegate_revoke_race.out +87 -0
  23. package/tests/isolation/expected/hosted_delegate_withdraw_evict_race.out +235 -0
  24. package/tests/isolation/specs/hosted_delegate_exchange_race.spec +64 -0
  25. package/tests/isolation/specs/hosted_delegate_first_exchange_race.spec +72 -0
  26. package/tests/isolation/specs/hosted_delegate_owner_provisioning_race.spec +73 -0
  27. package/tests/isolation/specs/hosted_delegate_owner_revoke_race.spec +93 -0
  28. package/tests/isolation/specs/hosted_delegate_revoke_race.spec +59 -0
  29. 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,42 @@
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_principal_agent_id",
348
+ "signature": "_delegate_principal_agent_id(p_agent_id uuid)",
349
+ "executable_by": {
350
+ "anon": false,
351
+ "service_role": false,
352
+ "authenticated": false
353
+ }
354
+ },
355
+ {
356
+ "name": "_delegate_visible_agent",
357
+ "signature": "_delegate_visible_agent(p_agent_id uuid)",
358
+ "executable_by": {
359
+ "anon": false,
360
+ "service_role": false,
361
+ "authenticated": false
362
+ }
363
+ },
364
+ {
365
+ "name": "_delegate_visible_board_ids",
366
+ "signature": "_delegate_visible_board_ids()",
367
+ "executable_by": {
368
+ "anon": false,
369
+ "service_role": false,
370
+ "authenticated": false
371
+ }
372
+ },
328
373
  {
329
374
  "name": "_emit_membership_event",
330
375
  "signature": "_emit_membership_event(p_board_id uuid, p_agent_id uuid, p_membership_state text, p_event_kind text)",
@@ -658,6 +703,24 @@
658
703
  "authenticated": false
659
704
  }
660
705
  },
706
+ {
707
+ "name": "_refuse_delegate_addressee",
708
+ "signature": "_refuse_delegate_addressee(p_agent_id uuid, p_board_id uuid, p_what text)",
709
+ "executable_by": {
710
+ "anon": false,
711
+ "service_role": false,
712
+ "authenticated": false
713
+ }
714
+ },
715
+ {
716
+ "name": "_refuse_delegate_membership_target",
717
+ "signature": "_refuse_delegate_membership_target(p_agent_id uuid, p_operation text)",
718
+ "executable_by": {
719
+ "anon": false,
720
+ "service_role": false,
721
+ "authenticated": false
722
+ }
723
+ },
661
724
  {
662
725
  "name": "_require_platform_admin_user_id",
663
726
  "signature": "_require_platform_admin_user_id()",
@@ -784,6 +847,15 @@
784
847
  "authenticated": true
785
848
  }
786
849
  },
850
+ {
851
+ "name": "add_delegate_to_board",
852
+ "signature": "add_delegate_to_board(p_agent_id uuid, p_board_id uuid)",
853
+ "executable_by": {
854
+ "anon": false,
855
+ "service_role": false,
856
+ "authenticated": true
857
+ }
858
+ },
787
859
  {
788
860
  "name": "agent_liveness_summary",
789
861
  "signature": "agent_liveness_summary()",
@@ -991,6 +1063,15 @@
991
1063
  "authenticated": false
992
1064
  }
993
1065
  },
1066
+ {
1067
+ "name": "exchange_delegate_credential",
1068
+ "signature": "exchange_delegate_credential(p_claims jsonb)",
1069
+ "executable_by": {
1070
+ "anon": false,
1071
+ "service_role": true,
1072
+ "authenticated": false
1073
+ }
1074
+ },
994
1075
  {
995
1076
  "name": "expand_recipients_dry_run",
996
1077
  "signature": "expand_recipients_dry_run(p_actor_agent_id uuid, p_recipient_descriptors jsonb, p_thread_idempotency_key text)",
@@ -1675,6 +1756,15 @@
1675
1756
  "authenticated": true
1676
1757
  }
1677
1758
  },
1759
+ {
1760
+ "name": "tombstone_delegate_instance",
1761
+ "signature": "tombstone_delegate_instance(p_instance_id text, p_issuer text, p_owner_sub text, p_reason text)",
1762
+ "executable_by": {
1763
+ "anon": false,
1764
+ "service_role": true,
1765
+ "authenticated": false
1766
+ }
1767
+ },
1678
1768
  {
1679
1769
  "name": "unblock_board_inviter",
1680
1770
  "signature": "unblock_board_inviter(p_board_id uuid, p_inviter_agent_id uuid, p_target_agent_id uuid)",
@@ -1955,5 +2045,173 @@
1955
2045
  "authenticated": true
1956
2046
  }
1957
2047
  }
1958
- ]
2048
+ ],
2049
+ "delegate": {
2050
+ "role": "agent_board_delegate",
2051
+ "allowed_operations": [
2052
+ {
2053
+ "kind": "ambient_rpc",
2054
+ "name": "get_schema_version",
2055
+ "signature": "get_schema_version()"
2056
+ },
2057
+ {
2058
+ "kind": "predicate",
2059
+ "name": "_delegate_visible_agent",
2060
+ "signature": "_delegate_visible_agent(uuid)"
2061
+ },
2062
+ {
2063
+ "kind": "predicate",
2064
+ "name": "_delegate_visible_board_ids",
2065
+ "signature": "_delegate_visible_board_ids()"
2066
+ },
2067
+ {
2068
+ "kind": "predicate",
2069
+ "name": "current_agent_id",
2070
+ "signature": "current_agent_id()"
2071
+ },
2072
+ {
2073
+ "kind": "read_table",
2074
+ "name": "agent_signing_keys",
2075
+ "signature": "agent_signing_keys"
2076
+ },
2077
+ {
2078
+ "kind": "read_table",
2079
+ "name": "agents",
2080
+ "signature": "agents"
2081
+ },
2082
+ {
2083
+ "kind": "read_table",
2084
+ "name": "board_memberships",
2085
+ "signature": "board_memberships"
2086
+ },
2087
+ {
2088
+ "kind": "read_table",
2089
+ "name": "boards",
2090
+ "signature": "boards"
2091
+ },
2092
+ {
2093
+ "kind": "read_table",
2094
+ "name": "claims",
2095
+ "signature": "claims"
2096
+ },
2097
+ {
2098
+ "kind": "read_table",
2099
+ "name": "delivery_recipients",
2100
+ "signature": "delivery_recipients"
2101
+ },
2102
+ {
2103
+ "kind": "read_table",
2104
+ "name": "escalations",
2105
+ "signature": "escalations"
2106
+ },
2107
+ {
2108
+ "kind": "read_table",
2109
+ "name": "inbox_items",
2110
+ "signature": "inbox_items"
2111
+ },
2112
+ {
2113
+ "kind": "read_table",
2114
+ "name": "messages",
2115
+ "signature": "messages"
2116
+ },
2117
+ {
2118
+ "kind": "read_table",
2119
+ "name": "presence",
2120
+ "signature": "presence"
2121
+ },
2122
+ {
2123
+ "kind": "read_table",
2124
+ "name": "signature_evidence",
2125
+ "signature": "signature_evidence"
2126
+ },
2127
+ {
2128
+ "kind": "read_table",
2129
+ "name": "task_events",
2130
+ "signature": "task_events"
2131
+ },
2132
+ {
2133
+ "kind": "read_table",
2134
+ "name": "tasks",
2135
+ "signature": "tasks"
2136
+ },
2137
+ {
2138
+ "kind": "read_view",
2139
+ "name": "v_agent_dashboard",
2140
+ "signature": "v_agent_dashboard"
2141
+ },
2142
+ {
2143
+ "kind": "read_view",
2144
+ "name": "v_agent_inbox",
2145
+ "signature": "v_agent_inbox"
2146
+ },
2147
+ {
2148
+ "kind": "read_view",
2149
+ "name": "v_board_feed",
2150
+ "signature": "v_board_feed"
2151
+ },
2152
+ {
2153
+ "kind": "read_view",
2154
+ "name": "v_board_memberships",
2155
+ "signature": "v_board_memberships"
2156
+ },
2157
+ {
2158
+ "kind": "read_view",
2159
+ "name": "v_thread_feed",
2160
+ "signature": "v_thread_feed"
2161
+ },
2162
+ {
2163
+ "kind": "rpc",
2164
+ "name": "create_or_find_thread",
2165
+ "signature": "create_or_find_thread(uuid, text, text, text, jsonb)"
2166
+ },
2167
+ {
2168
+ "kind": "rpc",
2169
+ "name": "post_board_message",
2170
+ "signature": "post_board_message(uuid, uuid, text, text, text, text, text, text, text, jsonb)"
2171
+ },
2172
+ {
2173
+ "kind": "rpc",
2174
+ "name": "post_board_message",
2175
+ "signature": "post_board_message(uuid, uuid, uuid, text, text, text, text, text, jsonb)"
2176
+ },
2177
+ {
2178
+ "kind": "rpc",
2179
+ "name": "post_message",
2180
+ "signature": "post_message(uuid, uuid, text, text, uuid, jsonb)"
2181
+ }
2182
+ ],
2183
+ "executable_functions": [
2184
+ "_addressing_descriptors_array(p_descriptors jsonb)",
2185
+ "_board_membership_events_provenance_guard()",
2186
+ "_delegate_visible_agent(p_agent_id uuid)",
2187
+ "_delegate_visible_board_ids()",
2188
+ "create_or_find_thread(p_created_by_agent_id uuid, p_subject text, p_idempotency_key text, p_thread_type text, p_metadata jsonb)",
2189
+ "current_agent_id()",
2190
+ "get_schema_version()",
2191
+ "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)",
2192
+ "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)",
2193
+ "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)",
2194
+ "sync_agent_principal_columns()"
2195
+ ],
2196
+ "selectable_relations": [
2197
+ "agent_signing_keys",
2198
+ "agents",
2199
+ "board_memberships",
2200
+ "boards",
2201
+ "claims",
2202
+ "delivery_recipients",
2203
+ "escalations",
2204
+ "inbox_items",
2205
+ "messages",
2206
+ "presence",
2207
+ "signature_evidence",
2208
+ "task_events",
2209
+ "tasks",
2210
+ "v_agent_dashboard",
2211
+ "v_agent_inbox",
2212
+ "v_board_feed",
2213
+ "v_board_memberships",
2214
+ "v_thread_feed"
2215
+ ]
2216
+ }
1959
2217
  }
@@ -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,22 @@
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_principal_agent_id(p_agent_id uuid)",
158
+ "declares_p_metadata": false
159
+ },
160
+ {
161
+ "signature": "_delegate_visible_agent(p_agent_id uuid)",
162
+ "declares_p_metadata": false
163
+ },
164
+ {
165
+ "signature": "_delegate_visible_board_ids()",
166
+ "declares_p_metadata": false
167
+ },
148
168
  {
149
169
  "signature": "_emit_membership_event(p_board_id uuid, p_agent_id uuid, p_membership_state text, p_event_kind text)",
150
170
  "declares_p_metadata": false
@@ -293,6 +313,14 @@
293
313
  "signature": "_redeem_unbounded_invite_for_agent(p_code text, p_agent_id uuid)",
294
314
  "declares_p_metadata": false
295
315
  },
316
+ {
317
+ "signature": "_refuse_delegate_addressee(p_agent_id uuid, p_board_id uuid, p_what text)",
318
+ "declares_p_metadata": false
319
+ },
320
+ {
321
+ "signature": "_refuse_delegate_membership_target(p_agent_id uuid, p_operation text)",
322
+ "declares_p_metadata": false
323
+ },
296
324
  {
297
325
  "signature": "_require_platform_admin_user_id()",
298
326
  "declares_p_metadata": false
@@ -349,6 +377,10 @@
349
377
  "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
378
  "declares_p_metadata": true
351
379
  },
380
+ {
381
+ "signature": "add_delegate_to_board(p_agent_id uuid, p_board_id uuid)",
382
+ "declares_p_metadata": false
383
+ },
352
384
  {
353
385
  "signature": "agent_liveness_summary()",
354
386
  "declares_p_metadata": false
@@ -441,6 +473,10 @@
441
473
  "signature": "evaluate_signed_write_fleet_policy(p_operation text, p_payload jsonb, p_gateway_decision jsonb)",
442
474
  "declares_p_metadata": false
443
475
  },
476
+ {
477
+ "signature": "exchange_delegate_credential(p_claims jsonb)",
478
+ "declares_p_metadata": false
479
+ },
444
480
  {
445
481
  "signature": "expand_recipients_dry_run(p_actor_agent_id uuid, p_recipient_descriptors jsonb, p_thread_idempotency_key text)",
446
482
  "declares_p_metadata": false
@@ -745,6 +781,10 @@
745
781
  "signature": "sync_agent_principal_columns()",
746
782
  "declares_p_metadata": false
747
783
  },
784
+ {
785
+ "signature": "tombstone_delegate_instance(p_instance_id text, p_issuer text, p_owner_sub text, p_reason text)",
786
+ "declares_p_metadata": false
787
+ },
748
788
  {
749
789
  "signature": "unblock_board_inviter(p_board_id uuid, p_inviter_agent_id uuid, p_target_agent_id uuid)",
750
790
  "declares_p_metadata": false
@@ -0,0 +1,93 @@
1
+ Parsed test spec with 3 sessions
2
+
3
+ starting permutation: new_begin new_exchange old_begin old_exchange new_commit old_commit check
4
+ count
5
+ -----
6
+ 1
7
+ (1 row)
8
+
9
+ step new_begin: BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role';
10
+ step new_exchange:
11
+ select count(*) as issued from agent_board.exchange_delegate_credential(jsonb_build_object(
12
+ 'iss', 'https://iso-portal.test', 'sub', 'iso-owner', 'slot_resource', null,
13
+ 'agent_board_delegate_instance', jsonb_build_object(
14
+ 'instance_id', 'iso_exchange_race_0001', 'grant_id', 'iso_grant_0000000003', 'generation', 3,
15
+ 'owner_sub', 'iso-owner', 'client_id', 'iso-client', 'status', 'active', 'client_change', null)));
16
+
17
+ issued
18
+ ------
19
+ 1
20
+ (1 row)
21
+
22
+ step old_begin: BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role';
23
+ step old_exchange:
24
+ select count(*) as issued from agent_board.exchange_delegate_credential(jsonb_build_object(
25
+ 'iss', 'https://iso-portal.test', 'sub', 'iso-owner', 'slot_resource', null,
26
+ 'agent_board_delegate_instance', jsonb_build_object(
27
+ 'instance_id', 'iso_exchange_race_0001', 'grant_id', 'iso_grant_0000000002', 'generation', 2,
28
+ 'owner_sub', 'iso-owner', 'client_id', 'iso-client', 'status', 'active', 'client_change', null)));
29
+ <waiting ...>
30
+ step new_commit: COMMIT;
31
+ step old_exchange: <... completed>
32
+ ERROR: generation_superseded
33
+ step old_commit: COMMIT;
34
+ step check:
35
+ select di.generation as recorded_generation, di.grant_id as recorded_grant,
36
+ (select string_agg(ac.generation::text, ',' order by ac.generation)
37
+ from agent_board.agent_credentials ac where ac.agent_id = di.agent_id and ac.revoked_at is null) as live_generations,
38
+ (select count(*) from agent_board.agents a where a.handle like 'delegate-%') as delegate_agents
39
+ from agent_board.delegate_instances di where di.instance_id = 'iso_exchange_race_0001';
40
+
41
+ recorded_generation|recorded_grant |live_generations|delegate_agents
42
+ -------------------+--------------------+----------------+---------------
43
+ 3|iso_grant_0000000003| 3| 1
44
+ (1 row)
45
+
46
+
47
+ starting permutation: old_begin old_exchange new_begin new_exchange old_commit new_commit check
48
+ count
49
+ -----
50
+ 1
51
+ (1 row)
52
+
53
+ step old_begin: BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role';
54
+ step old_exchange:
55
+ select count(*) as issued from agent_board.exchange_delegate_credential(jsonb_build_object(
56
+ 'iss', 'https://iso-portal.test', 'sub', 'iso-owner', 'slot_resource', null,
57
+ 'agent_board_delegate_instance', jsonb_build_object(
58
+ 'instance_id', 'iso_exchange_race_0001', 'grant_id', 'iso_grant_0000000002', 'generation', 2,
59
+ 'owner_sub', 'iso-owner', 'client_id', 'iso-client', 'status', 'active', 'client_change', null)));
60
+
61
+ issued
62
+ ------
63
+ 1
64
+ (1 row)
65
+
66
+ step new_begin: BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role';
67
+ step new_exchange:
68
+ select count(*) as issued from agent_board.exchange_delegate_credential(jsonb_build_object(
69
+ 'iss', 'https://iso-portal.test', 'sub', 'iso-owner', 'slot_resource', null,
70
+ 'agent_board_delegate_instance', jsonb_build_object(
71
+ 'instance_id', 'iso_exchange_race_0001', 'grant_id', 'iso_grant_0000000003', 'generation', 3,
72
+ 'owner_sub', 'iso-owner', 'client_id', 'iso-client', 'status', 'active', 'client_change', null)));
73
+ <waiting ...>
74
+ step old_commit: COMMIT;
75
+ step new_exchange: <... completed>
76
+ issued
77
+ ------
78
+ 1
79
+ (1 row)
80
+
81
+ step new_commit: COMMIT;
82
+ step check:
83
+ select di.generation as recorded_generation, di.grant_id as recorded_grant,
84
+ (select string_agg(ac.generation::text, ',' order by ac.generation)
85
+ from agent_board.agent_credentials ac where ac.agent_id = di.agent_id and ac.revoked_at is null) as live_generations,
86
+ (select count(*) from agent_board.agents a where a.handle like 'delegate-%') as delegate_agents
87
+ from agent_board.delegate_instances di where di.instance_id = 'iso_exchange_race_0001';
88
+
89
+ recorded_generation|recorded_grant |live_generations|delegate_agents
90
+ -------------------+--------------------+----------------+---------------
91
+ 3|iso_grant_0000000003| 3| 1
92
+ (1 row)
93
+