@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
@@ -353,8 +353,11 @@ select is(
353
353
  where p.pronamespace = 'agent_board'::regnamespace
354
354
  and p.proname <> '_federated_identity_slug'
355
355
  and agent_board_test.stripped_body(p.oid, true) like '%\_federated\_identity\_slug(%') x),
356
- '_enroll_federated_human_after_revocation_gate=1, _enroll_federated_identity_once=1, enroll_federated_human=1, enroll_federated_identity=2, onboard_federated_fleet_agent=1',
357
- 'CLS-3: exactly these five functions route through the helper, each with its own call count');
356
+ -- HOSTED (20261008000100) added two sites: exchange_delegate_credential and
357
+ -- tombstone_delegate_instance derive the hosted delegate's OWNER uid exactly as
358
+ -- enroll_federated_human derives the human's (IF-0-HOSTED-12's frozen owner identity).
359
+ '_enroll_federated_human_after_revocation_gate=1, _enroll_federated_identity_once=1, enroll_federated_human=1, enroll_federated_identity=2, exchange_delegate_credential=1, onboard_federated_fleet_agent=1, tombstone_delegate_instance=1',
360
+ 'CLS-3: exactly these seven functions route through the helper, each with its own call count');
358
361
 
359
362
  -- CLS-4. The helper is not a public lever. It is pure and mints nothing, but it is the
360
363
  -- function that decides which account a pair maps to, and CREATE FUNCTION grants EXECUTE
@@ -44,7 +44,10 @@ set search_path = extensions, public, agent_board;
44
44
 
45
45
  begin;
46
46
 
47
- select plan(29);
47
+ -- 31 with HOSTED (20261008000100): two derivation sites, each exempt positively (13, 14).
48
+ -- 33 with the HOSTED fix lane (PR #590): the exchange joins the class through its owner-liveness
49
+ -- read and is exempted from the gate positively (assertions 2b, 2c).
50
+ select plan(33);
48
51
 
49
52
  -- ---------------------------------------------------------------------------
50
53
  -- Section A -- the CLASS, derived from pg_proc, not from a list written here
@@ -84,15 +87,48 @@ select p.oid, p.proname, agent_board_test.stripped_body(p.oid, true) as body
84
87
  ~ agent_board_test.re(agent_board_test.qname_frag('agents', agent_board_test.bare_ok(p.oid)) || agent_board_test.same_stmt()
85
88
  || agent_board_test.col_ref_frag('auth_user_id') || '\s*=');
86
89
 
90
+ -- HOSTED fix lane (PR #590, finding N2): exchange_delegate_credential now reads the owner's
91
+ -- human row on auth_user_id, to REFUSE an inactive owner (owner_inactive). It never hands that
92
+ -- row back, so the stored-identity gate (which guards a RETURN of a row found by the derivation)
93
+ -- has nothing to gate there. It is a class member exempted POSITIVELY, in two halves: the view
94
+ -- below admits it only while its body holds exactly ONE agents-on-auth_user_id read (counted with
95
+ -- the class query's own matcher, so a second read anywhere drops it back into the gated set and
96
+ -- reds 3-7 by name), and 2c asserts, over that one body, that the read is the owner_inactive
97
+ -- refusal. The exemption is keyed by name, a list of one; a new member is gated by default.
98
+ create temp view _c246_refusal_only as
99
+ select c.oid, c.proname
100
+ from _c246 c
101
+ where c.proname = 'exchange_delegate_credential'
102
+ and regexp_count(c.body, agent_board_test.re(agent_board_test.qname_frag('agents', agent_board_test.bare_ok(c.oid))
103
+ || agent_board_test.same_stmt() || agent_board_test.col_ref_frag('auth_user_id') || '\s*=')) = 1;
104
+ -- The members the gate applies to: the class, less the positively exempted refusal-only read.
105
+ create temp view _c246g as
106
+ select c.* from _c246 c where c.oid not in (select oid from _c246_refusal_only);
107
+
87
108
  -- 1 -- the class is NOT EMPTY. A class scan that finds nothing passes every
88
109
  -- "every member has X" assertion vacuously; this is the assertion that stops that.
89
- select is((select count(*)::int from _c246), 1,
90
- 'exactly one agent_board function both derives a fed-human owner uid and reads agents on auth_user_id');
110
+ select is((select count(*)::int from _c246), 2,
111
+ 'exactly two agent_board functions both derive a fed-human owner uid and read agents on auth_user_id');
91
112
 
92
- -- 2 -- and it is the one we think it is, named POSITIVELY (not "no unexpected members").
113
+ -- 2 -- and they are the ones we think they are, named POSITIVELY (not "no unexpected members").
93
114
  select is((select string_agg(proname, ',' order by proname) from _c246),
94
- '_enroll_federated_human_after_revocation_gate',
95
- 'that function is _enroll_federated_human_after_revocation_gate');
115
+ '_enroll_federated_human_after_revocation_gate,exchange_delegate_credential',
116
+ 'those functions are _enroll_federated_human_after_revocation_gate and exchange_delegate_credential');
117
+
118
+ -- 2b (TAP ok 3; every later "-- N --" comment in this file is two BEHIND its TAP number) --
119
+ -- the exempt and gated sets, by name.
120
+ select is(
121
+ (select string_agg(proname, ',' order by proname) from _c246_refusal_only)
122
+ || ' | ' || (select string_agg(proname, ',' order by proname) from _c246g),
123
+ 'exchange_delegate_credential | _enroll_federated_human_after_revocation_gate',
124
+ 'EXEMPT: exchange_delegate_credential has exactly one agents-on-auth_user_id read; the gated set is the one function that returns a found row');
125
+ -- 2c (TAP ok 4) -- and that one read is the owner_inactive refusal, over its one body.
126
+ select ok(
127
+ (select agent_board_test.stripped_body(p.oid, true)
128
+ from pg_proc p join pg_namespace n on n.oid = p.pronamespace
129
+ where n.nspname = 'agent_board' and p.proname = 'exchange_delegate_credential')
130
+ ~ 'exists\s*\(\s*select\s+1\s+from\s+agent_board\.agents\s+a\s+where\s+a\.auth_user_id\s*=\s*v_owner\s+and\s+a\.status\s*<>\s*''active''\s*\)\s*then\s+raise\s+exception\s+''owner_inactive''',
131
+ 'EXEMPT: exchange_delegate_credential''s agents-on-auth_user_id read only refuses: exists(... status <> active) then raise owner_inactive');
96
132
 
97
133
  -- BOTH return sites carry the gate -- asserted PER SITE, not as a count.
98
134
  --
@@ -120,14 +156,14 @@ select proname,
120
156
  (regexp_split_to_array(body, agent_board_test.re('\m' || agent_board_test.ci_re('exception when unique_violation') || '\M')))[1] as pre_handler,
121
157
  (regexp_split_to_array(body, agent_board_test.re('\m' || agent_board_test.ci_re('exception when unique_violation') || '\M')))[2] as in_handler,
122
158
  array_length(regexp_split_to_array(body, agent_board_test.re('\m' || agent_board_test.ci_re('exception when unique_violation') || '\M')), 1) as parts
123
- from _c246;
159
+ from _c246g;
124
160
 
125
161
  -- 3 -- the split boundary is UNAMBIGUOUS for every member. With a second `exception
126
162
  -- when unique_violation` the two assertions below would be reading arbitrary halves,
127
163
  -- so this is asserted before anything is read from them.
128
164
  select is(
129
165
  (select string_agg(proname, ',' order by proname) from _c246parts where parts = 2),
130
- (select string_agg(proname, ',' order by proname) from _c246),
166
+ (select string_agg(proname, ',' order by proname) from _c246g),
131
167
  'every class member has exactly one `exception when unique_violation`, so the per-site split is unambiguous');
132
168
 
133
169
  -- 4 -- SITE (7a): the idempotent branch, everything before the handler.
@@ -138,7 +174,7 @@ select is(
138
174
  || '\s+' || agent_board_test.ci_re('and') || '\s+'
139
175
  || agent_board_test.ident_re('v_existing') || '\s*\.\s*' || agent_board_test.ident_re('metadata') || '\s*#>>\s*''\{federation,subject\}''\s*=\s*' || agent_board_test.ident_re('p_subject')
140
176
  || '\s*,\s*' || agent_board_test.ci_re('false') || '\s*\)')),
141
- (select string_agg(proname, ',' order by proname) from _c246),
177
+ (select string_agg(proname, ',' order by proname) from _c246g),
142
178
  'every class member carries the gate at return site (7a) -- the idempotent branch, BEFORE the unique_violation handler');
143
179
 
144
180
  -- 5 -- SITE (8a): the race handler, everything after. This is the site a single
@@ -151,7 +187,7 @@ select is(
151
187
  || '\s+' || agent_board_test.ci_re('and') || '\s+'
152
188
  || agent_board_test.ident_re('v_existing') || '\s*\.\s*' || agent_board_test.ident_re('metadata') || '\s*#>>\s*''\{federation,subject\}''\s*=\s*' || agent_board_test.ident_re('p_subject')
153
189
  || '\s*,\s*' || agent_board_test.ci_re('false') || '\s*\)')),
154
- (select string_agg(proname, ',' order by proname) from _c246),
190
+ (select string_agg(proname, ',' order by proname) from _c246g),
155
191
  'every class member carries the gate at return site (8a) -- INSIDE the unique_violation race handler');
156
192
 
157
193
  -- 6 and 7 -- both sites raise the SAME refusal, so neither becomes a
@@ -168,14 +204,14 @@ select is(
168
204
  select is(
169
205
  (select string_agg(proname, ',' order by proname) from _c246parts
170
206
  where pre_handler ~ 'federated identity mismatch'),
171
- (select string_agg(proname, ',' order by proname) from _c246),
207
+ (select string_agg(proname, ',' order by proname) from _c246g),
172
208
  'every class member raises the "federated identity mismatch" refusal at site (7a) -- the idempotent branch');
173
209
 
174
210
  -- 7
175
211
  select is(
176
212
  (select string_agg(proname, ',' order by proname) from _c246parts
177
213
  where in_handler ~ 'federated identity mismatch'),
178
- (select string_agg(proname, ',' order by proname) from _c246),
214
+ (select string_agg(proname, ',' order by proname) from _c246g),
179
215
  'every class member raises the "federated identity mismatch" refusal at site (8a) -- INSIDE the race handler');
180
216
 
181
217
  -- ---------------------------------------------------------------------------
@@ -203,8 +239,29 @@ select is(
203
239
  where n.nspname = 'agent_board' and p.prokind in ('f', 'p')
204
240
  and p.proname <> '_federated_identity_slug'
205
241
  and agent_board_test.stripped_body(p.oid, true) like '%\_federated\_identity\_slug(%'),
206
- '_enroll_federated_human_after_revocation_gate,_enroll_federated_identity_once,enroll_federated_human,enroll_federated_identity,onboard_federated_fleet_agent',
207
- 'the fed-human derivation set is exactly these five functions');
242
+ '_enroll_federated_human_after_revocation_gate,_enroll_federated_identity_once,enroll_federated_human,enroll_federated_identity,exchange_delegate_credential,onboard_federated_fleet_agent,tombstone_delegate_instance',
243
+ 'the fed-human derivation set is exactly these seven functions (HOSTED added the exchange and the tombstone)');
244
+
245
+ -- 13, 14 -- HOSTED (20261008000100): exchange_delegate_credential and
246
+ -- tombstone_delegate_instance derive the OWNER's uid from the verified (issuer, subject),
247
+ -- exactly as enroll_federated_human does (IF-0-HOSTED-12's frozen owner identity). They are
248
+ -- exempt because neither returns an agent found by that uid: each uses it only as the
249
+ -- instance's owner, inserted as delegate_instances.owner_user_id and compared with the stored
250
+ -- one (owner_mismatch). The comparison is what is pinned, positively. The tombstone's absence
251
+ -- of an agents read on auth_user_id is the class query's half (assertions 1-2); the exchange
252
+ -- has one such read since the fix lane, the owner_inactive refusal, exempted by 2b and 2c.
253
+ select ok(
254
+ (select agent_board_test.stripped_body(p.oid, true)
255
+ from pg_proc p join pg_namespace n on n.oid = p.pronamespace
256
+ where n.nspname = 'agent_board' and p.proname = 'exchange_delegate_credential')
257
+ ~ 'v_row\.owner_user_id\s+is\s+distinct\s+from\s+v_owner',
258
+ 'EXEMPT: exchange_delegate_credential compares the stored instance owner with the derived uid (owner_mismatch)');
259
+ select ok(
260
+ (select agent_board_test.stripped_body(p.oid, true)
261
+ from pg_proc p join pg_namespace n on n.oid = p.pronamespace
262
+ where n.nspname = 'agent_board' and p.proname = 'tombstone_delegate_instance')
263
+ ~ 'v_row\.owner_user_id\s+is\s+distinct\s+from\s+v_owner',
264
+ 'EXEMPT: tombstone_delegate_instance compares the stored instance owner with the derived uid (owner_mismatch)');
208
265
 
209
266
  -- 9 -- enroll_federated_identity is exempt because it ALREADY compares the caller's
210
267
  -- pair against the creation-time stamp (#176/#244). Asserted on BOTH conjuncts: a
@@ -230,6 +230,9 @@ select is(
230
230
  '_redeem_unbounded_invite_for_agent: memberships(key share) then agents(key share) [unserialised]',
231
231
  '_upsert_board_membership_state: memberships(update) then agents(key share) [unserialised]',
232
232
  '_upsert_delivery_recipient: memberships(key share) then agents(key share) [unserialised]',
233
+ -- HOSTED (20261008000100). Advisory-serialised, as grant_board_role is: the fleet-ledger gate
234
+ -- row-locks the owner's agents row before the sink writes the delegate's membership.
235
+ 'add_delegate_to_board: agents(share) then memberships(update) [advisory]',
233
236
  'create_tasking_delivery: agents(key share) then memberships(key share) [unserialised]',
234
237
  'enroll_federated_identity: agents(update) then memberships(key share) [unserialised]',
235
238
  'grant_board_role: agents(share) then memberships(update) [advisory]',
@@ -249,6 +252,12 @@ select is(
249
252
  'signed_send_direct_message: agents(key share) then memberships(key share) [unserialised]',
250
253
  'signed_send_multicast_message: agents(key share) then memberships(key share) [unserialised]',
251
254
  'subscribe_callback_endpoint: memberships(key share) then agents(key share) [unserialised]',
255
+ -- HOSTED (20261008000100). Unserialised, and deliberately NOT given the advisory lock:
256
+ -- add_delegate_to_board takes the instance row and THEN the advisory lock, so a tombstone
257
+ -- (instance row FOR UPDATE first) taking the advisory lock after it would be the ABBA this
258
+ -- file exists to catch. Every row it locks belongs to the tombstoned delegate -- its agents
259
+ -- row, its credentials, its memberships -- which onboarding never locks.
260
+ 'tombstone_delegate_instance: agents(update) then memberships(update) [unserialised]',
252
261
  'update_board_membership: memberships(update) then agents(key share) [unserialised]'
253
262
  ]::text[],
254
263
  'FKL-9: the agents+board_memberships lock class, FK edges included (section 9''s text scan sees 3 of these)');