@consiliency/agent-board-schema 1.60.0 → 1.63.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.
- package/README.md +80 -0
- package/fixtures/already-applied.json +12 -0
- package/migrations/20261011000100_agent_board_session_identities.sql +1964 -0
- package/migrations/20261012000100_agent_board_sidowner.sql +209 -0
- package/migrations/20261014000100_agent_board_grant_target_role.sql +218 -0
- package/package.json +1 -1
- package/schema-manifest.json +30 -3
- package/src/tooling.test.mjs +6 -2
- package/tests/agent_board_atomic_fleet_onboarding.test.sql +13 -4
- package/tests/agent_board_consent_emission.test.sql +30 -11
- package/tests/agent_board_consent_gating.test.sql +16 -2
- package/tests/agent_board_delegate_role_class.test.sql +1 -1
- package/tests/agent_board_delegation_cap_ceiling.test.sql +26 -15
- package/tests/agent_board_delegation_enforcement.test.sql +1 -1
- package/tests/agent_board_enroll_resumption.test.sql +22 -4
- package/tests/agent_board_enroll_token_single_use.test.sql +18 -3
- package/tests/agent_board_federated_lock_order_class.test.sql +2 -2
- package/tests/agent_board_fk_lock_edges.test.sql +74 -14
- package/tests/agent_board_grant_target_role.test.sql +1118 -0
- package/tests/agent_board_invite_no_downgrade.test.sql +10 -8
- package/tests/agent_board_ledger_authority.test.sql +70 -12
- package/tests/agent_board_membership_current_row_class.test.sql +9 -1
- package/tests/agent_board_mint_fleet_agent_session.test.sql +2 -2
- package/tests/agent_board_mint_harden.test.sql +38 -27
- package/tests/agent_board_rolereset.test.sql +8 -2
- package/tests/agent_board_roomdeleg_lane_b.test.sql +6 -6
- package/tests/agent_board_rowid.test.sql +3 -1
- package/tests/agent_board_session_identity_approval_revocation.test.sql +334 -0
- package/tests/agent_board_session_identity_backfill.test.sql +82 -0
- package/tests/agent_board_session_identity_backfill_refusal.test.sql +14 -0
- package/tests/agent_board_session_identity_behavior_classes.test.sql +176 -0
- package/tests/agent_board_session_identity_class.test.sql +118 -0
- package/tests/agent_board_session_identity_fingerprint.test.sql +271 -0
- package/tests/agent_board_session_identity_labels_limits.test.sql +205 -0
- package/tests/agent_board_session_identity_lease.test.sql +235 -0
- package/tests/agent_board_session_identity_lifetime.test.sql +230 -0
- package/tests/agent_board_session_identity_limits.test.sql +208 -0
- package/tests/agent_board_session_identity_lineage.test.sql +64 -0
- package/tests/agent_board_session_identity_link.test.sql +201 -0
- package/tests/agent_board_session_identity_mint.test.sql +233 -0
- package/tests/agent_board_session_identity_panel_r1.test.sql +194 -0
- package/tests/agent_board_session_identity_panel_r2.test.sql +174 -0
- package/tests/agent_board_session_identity_protected_actions.test.sql +156 -0
- package/tests/agent_board_session_identity_revoke_closure.test.sql +131 -0
- package/tests/agent_board_sidowner_leases.test.sql +107 -0
- package/tests/agent_board_sidowner_owner_required.test.sql +97 -0
- package/tests/agent_board_sidowner_read_class.test.sql +171 -0
- package/tests/agent_board_sidowner_reads.test.sql +136 -0
- package/tests/agent_board_signer_lifecycle.test.sql +26 -4
- package/tests/agent_board_signing_challenge_pending_cap.test.sql +1 -1
- package/tests/agent_board_write_enforcement_class.test.sql +74 -15
- package/tests/catalog/function_execute_privileges.json +218 -2
- package/tests/catalog/p_metadata_parameters.json +98 -2
- package/tests/isolation/expected/grant_target_role_race.out +348 -0
- package/tests/isolation/expected/session_identity_limits.out +165 -0
- package/tests/isolation/expected/session_identity_links.out +235 -0
- package/tests/isolation/expected/session_identity_reclaim.out +253 -0
- package/tests/isolation/expected/session_identity_renew_revoke.out +115 -0
- package/tests/isolation/expected/session_identity_renew_revoke_key_gate.out +138 -0
- package/tests/isolation/expected/session_identity_tombstone_onboarding.out +247 -0
- package/tests/isolation/specs/grant_target_role_race.spec +207 -0
- package/tests/isolation/specs/session_identity_limits.spec +81 -0
- package/tests/isolation/specs/session_identity_links.spec +80 -0
- package/tests/isolation/specs/session_identity_reclaim.spec +125 -0
- package/tests/isolation/specs/session_identity_renew_revoke.spec +84 -0
- package/tests/isolation/specs/session_identity_renew_revoke_key_gate.spec +94 -0
- package/tests/isolation/specs/session_identity_tombstone_onboarding.spec +108 -0
- package/tests/seeds/20261011000100_agent_board_session_identities.sql.pre.sql +47 -0
|
@@ -17,7 +17,7 @@ set search_path = extensions, public, agent_board;
|
|
|
17
17
|
|
|
18
18
|
begin;
|
|
19
19
|
|
|
20
|
-
select plan(
|
|
20
|
+
select plan(55);
|
|
21
21
|
|
|
22
22
|
create function pg_temp.claims(p_sub text) returns void language plpgsql as $$
|
|
23
23
|
begin
|
|
@@ -350,11 +350,25 @@ select is(
|
|
|
350
350
|
'invite_board_member', -- writes `invited` only -- an offer, not a membership
|
|
351
351
|
'request_join_board', -- writes `requested` only
|
|
352
352
|
'leave_board', -- writes `left` only
|
|
353
|
-
'grant_board_role'
|
|
353
|
+
'grant_board_role', -- requires an already-active membership; never SETs the state
|
|
354
|
+
'readmit_session_identity' -- human owner mints a NEW principal; positively pinned below and behaviorally in SID readmission
|
|
354
355
|
)),
|
|
355
356
|
'',
|
|
356
357
|
'EC-4 CLASS: every authenticated route reaching an active membership is gated or exempt');
|
|
357
358
|
|
|
359
|
+
select ok(
|
|
360
|
+
agent_board_test.stripped_body('agent_board.readmit_session_identity(uuid,text,text,jsonb,jsonb,interval)'::regprocedure)
|
|
361
|
+
~ agent_board_test.call_re('_session_identity_owner', false)
|
|
362
|
+
and agent_board_test.stripped_body('agent_board.readmit_session_identity(uuid,text,text,jsonb,jsonb,interval)'::regprocedure)
|
|
363
|
+
~ agent_board_test.stmt_re('insert into', 'agents', false)
|
|
364
|
+
and agent_board_test.stripped_body('agent_board.readmit_session_identity(uuid,text,text,jsonb,jsonb,interval)'::regprocedure, true)
|
|
365
|
+
~ agent_board_test.re(agent_board_test.call_frag('_upsert_board_membership_state', false) || '\s*v_board\s*,\s*v_new_id\s*,\s*''joined''')
|
|
366
|
+
and agent_board_test.stripped_body('agent_board._session_identity_owner(uuid)'::regprocedure, true)
|
|
367
|
+
like '%human.agent_type = ''human''%'
|
|
368
|
+
and agent_board_test.stripped_body('agent_board._session_identity_owner(uuid)'::regprocedure)
|
|
369
|
+
like '%root.owner_user_id = auth.uid()%',
|
|
370
|
+
'EC-4-readmit: human-root-owner placement is onto a fresh principal, never manufactured target consent');
|
|
371
|
+
|
|
358
372
|
-- ...and the exemptions are not assertions of convenience: the three "writes only X" claims are
|
|
359
373
|
-- checked, because an exemption that stops being true is how the fifth route hid.
|
|
360
374
|
-- Matches the literal passed to EITHER the shared sink OR update_board_membership. The first
|
|
@@ -301,7 +301,7 @@ select f.oid, f.name,
|
|
|
301
301
|
-- 24
|
|
302
302
|
select is(
|
|
303
303
|
(select string_agg(name || case when writes_role then '[role]' else '' end, ',' order by name) from _bm_writers),
|
|
304
|
-
'_fedsession_autojoin_default_board[role],_redeem_unbounded_invite_for_agent[role],bootstrap_board_owner_memberships[role],create_board[role],grant_board_role[role]',
|
|
304
|
+
'_fedsession_autojoin_default_board[role],_redeem_unbounded_invite_for_agent[role],bootstrap_board_owner_memberships[role],create_board[role],grant_board_role[role],link_session_alias[role],mint_delegated_child[role],readmit_session_identity[role],set_board_member_label',
|
|
305
305
|
'the direct board_memberships writers outside the sink, derived (a new one names itself)');
|
|
306
306
|
-- 25
|
|
307
307
|
select is(
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
-- * REQUIRE DURABILITY (invariant 4): an operator require=true is not relaxed to false by
|
|
14
14
|
-- self-service (the self-pin), so a keyless self-service mint is refused — mutate the pin
|
|
15
15
|
-- (true -> coalesce(p_require,true)) and both the state read and the keyless-mint throw flip.
|
|
16
|
-
-- *
|
|
17
|
-
--
|
|
16
|
+
-- * SIDSCHEMA: both mint paths consume the tighter operator ceiling. The operator
|
|
17
|
+
-- can still set/loosen that ceiling through its existing writer path.
|
|
18
18
|
-- * NO REGRESSION: a root with no operator policy self-services bounded only by the global
|
|
19
19
|
-- 25/24h ceiling.
|
|
20
20
|
-- * EXECUTE class: CREATE OR REPLACE (no DROP) kept the ACL — mint stays service_role-only,
|
|
@@ -31,7 +31,11 @@ set search_path = extensions, public, agent_board;
|
|
|
31
31
|
|
|
32
32
|
begin;
|
|
33
33
|
|
|
34
|
-
select plan(
|
|
34
|
+
select plan(42);
|
|
35
|
+
create temp table _a_opcap(child_agent_id uuid, grant_expires_at timestamptz, signing_key_id uuid);
|
|
36
|
+
create temp table _b_ssttl(like _a_opcap);
|
|
37
|
+
create temp table _b_opttl(like _a_opcap);
|
|
38
|
+
create temp table _d_ss(like _a_opcap);
|
|
35
39
|
|
|
36
40
|
create temp table _cf (
|
|
37
41
|
owner_id uuid,
|
|
@@ -148,12 +152,19 @@ select throws_ok(
|
|
|
148
152
|
'42501', 'max_active_children: root has reached its active delegated-child ceiling',
|
|
149
153
|
'A: self-service mint clamps max_active_children to the operator ceiling of 1 (stored 25 NOT trusted)');
|
|
150
154
|
|
|
151
|
-
--
|
|
152
|
-
|
|
155
|
+
-- SIDSCHEMA's operator ceiling bounds BOTH mint paths, not merely self-service.
|
|
156
|
+
select throws_ok(
|
|
157
|
+
(select format($$ select * from agent_board.mint_delegated_child(%L::uuid, %L, '[]'::jsonb, %L, null, false) $$,
|
|
158
|
+
cap, 'cf-opfull-'||replace(substr(gen_random_uuid()::text,1,8),'-',''), k1) from _cf),
|
|
159
|
+
'42501', 'max_active_children: root has reached its active delegated-child ceiling',
|
|
160
|
+
'A: operator mint cannot bypass its tighter stored operator ceiling');
|
|
161
|
+
select is((select count(*) from _a_opcap), 0::bigint,
|
|
162
|
+
'A: operator ceiling refusal leaves no returned child');
|
|
163
|
+
update agent_board.delegation_grants set revoked_at = now() where child_agent_id = (select cap_child from _cf);
|
|
153
164
|
select lives_ok(
|
|
154
|
-
(select format($$
|
|
165
|
+
(select format($$ insert into _a_opcap select * from agent_board.mint_delegated_child(%L::uuid, %L, '[]'::jsonb, %L, null, false) $$,
|
|
155
166
|
cap, 'cf-opcap-'||replace(substr(gen_random_uuid()::text,1,8),'-',''), k1) from _cf),
|
|
156
|
-
'A: operator mint
|
|
167
|
+
'A: operator mint succeeds below the tighter ceiling (0 active grants < 1)');
|
|
157
168
|
select is((select count(*) from _a_opcap), 1::bigint,
|
|
158
169
|
'A: operator cap mint produced exactly one child (not a silent zero-row pass)');
|
|
159
170
|
|
|
@@ -185,21 +196,21 @@ select is((select child_ttl_ceiling from agent_board.delegation_policies where r
|
|
|
185
196
|
select set_config('request.jwt.claim.role', '', true);
|
|
186
197
|
select set_config('request.jwt.claim.sub', '', true);
|
|
187
198
|
select lives_ok(
|
|
188
|
-
(select format($$
|
|
199
|
+
(select format($$ insert into _b_ssttl select * from agent_board.mint_delegated_child(%L::uuid, %L, '[]'::jsonb, %L, null, true) $$,
|
|
189
200
|
ttl, 'cf-ssttl-'||replace(substr(gen_random_uuid()::text,1,8),'-',''), k2) from _cf),
|
|
190
201
|
'B: self-service mint under the 2h operator ceiling lives');
|
|
191
202
|
select ok(
|
|
192
203
|
(select grant_expires_at <= now() + interval '2 hours' + interval '1 minute' from _b_ssttl),
|
|
193
204
|
'B: self-service child TTL clamped to the 2h operator ceiling (stored 24h NOT trusted)');
|
|
194
205
|
|
|
195
|
-
--
|
|
206
|
+
-- The operator mint also consumes the durable 2h ceiling, per SIDSCHEMA's matrix.
|
|
196
207
|
select lives_ok(
|
|
197
|
-
(select format($$
|
|
208
|
+
(select format($$ insert into _b_opttl select * from agent_board.mint_delegated_child(%L::uuid, %L, '[]'::jsonb, %L, null, false) $$,
|
|
198
209
|
ttl, 'cf-opttl-'||replace(substr(gen_random_uuid()::text,1,8),'-',''), k3) from _cf),
|
|
199
210
|
'B: operator mint under the same policy lives');
|
|
200
211
|
select ok(
|
|
201
|
-
(select grant_expires_at
|
|
202
|
-
'B: operator
|
|
212
|
+
(select grant_expires_at <= now() + interval '2 hours' + interval '1 minute' from _b_opttl),
|
|
213
|
+
'B: operator mint consumes the same tighter 2h operator ceiling');
|
|
203
214
|
|
|
204
215
|
-- ===========================================================================
|
|
205
216
|
-- C. Operator require_child_signing_key=true is durable: self-service cannot relax it
|
|
@@ -251,7 +262,7 @@ select is((select operator_child_ttl_ceiling from agent_board.delegation_policie
|
|
|
251
262
|
select set_config('request.jwt.claim.role', '', true);
|
|
252
263
|
select set_config('request.jwt.claim.sub', '', true);
|
|
253
264
|
select lives_ok(
|
|
254
|
-
(select format($$
|
|
265
|
+
(select format($$ insert into _d_ss select * from agent_board.mint_delegated_child(%L::uuid, %L, '[]'::jsonb, %L, null, true) $$,
|
|
255
266
|
none, 'cf-ssnone-'||replace(substr(gen_random_uuid()::text,1,8),'-',''), k4) from _cf),
|
|
256
267
|
'D: self-service mint with no operator policy lives');
|
|
257
268
|
select ok(
|
|
@@ -284,7 +295,7 @@ select is((select scope_attestation from agent_board.delegation_policies where r
|
|
|
284
295
|
-- re-opened to PUBLIC/anon.
|
|
285
296
|
-- ===========================================================================
|
|
286
297
|
select ok(not has_function_privilege('authenticated',
|
|
287
|
-
'agent_board.mint_delegated_child(uuid, text, jsonb, text, interval, boolean)', 'EXECUTE'),
|
|
298
|
+
'agent_board.mint_delegated_child(uuid, text, jsonb, text, interval, boolean, text, text, text, jsonb, jsonb, uuid)', 'EXECUTE'),
|
|
288
299
|
'F: mint_delegated_child stays service_role-only (authenticated has NO EXECUTE after recreate)');
|
|
289
300
|
select ok(not has_function_privilege('anon',
|
|
290
301
|
'agent_board.upsert_delegation_policy(uuid, text, jsonb, integer, interval, boolean)', 'EXECUTE'),
|
|
@@ -293,7 +304,7 @@ select ok(has_function_privilege('authenticated',
|
|
|
293
304
|
'agent_board.upsert_delegation_policy(uuid, text, jsonb, integer, interval, boolean)', 'EXECUTE'),
|
|
294
305
|
'F: authenticated retains EXECUTE on upsert_delegation_policy after recreate');
|
|
295
306
|
select ok(has_function_privilege('service_role',
|
|
296
|
-
'agent_board.mint_delegated_child(uuid, text, jsonb, text, interval, boolean)', 'EXECUTE'),
|
|
307
|
+
'agent_board.mint_delegated_child(uuid, text, jsonb, text, interval, boolean, text, text, text, jsonb, jsonb, uuid)', 'EXECUTE'),
|
|
297
308
|
'F: service_role retains EXECUTE on mint_delegated_child');
|
|
298
309
|
|
|
299
310
|
select set_config('request.jwt.claim.role', '', true);
|
|
@@ -87,7 +87,7 @@ end
|
|
|
87
87
|
$$;
|
|
88
88
|
|
|
89
89
|
-- Function existence.
|
|
90
|
-
select has_function('agent_board', 'mint_delegated_child', array['uuid','text','jsonb','text','interval','boolean'], 'mint_delegated_child exists');
|
|
90
|
+
select has_function('agent_board', 'mint_delegated_child', array['uuid','text','jsonb','text','interval','boolean','text','text','text','jsonb','jsonb','uuid'], 'mint_delegated_child exists');
|
|
91
91
|
select has_function('agent_board', 'renew_delegated_grant', array['uuid','interval'], 'renew_delegated_grant exists');
|
|
92
92
|
|
|
93
93
|
-- Happy mint: grant row shape.
|
|
@@ -27,7 +27,7 @@ set search_path = extensions, public, agent_board;
|
|
|
27
27
|
|
|
28
28
|
begin;
|
|
29
29
|
|
|
30
|
-
select plan(
|
|
30
|
+
select plan(37);
|
|
31
31
|
|
|
32
32
|
-- ---------------------------------------------------------------------------
|
|
33
33
|
-- Fixtures
|
|
@@ -185,7 +185,10 @@ select is(
|
|
|
185
185
|
'_enroll_federated_identity_once, enroll_federated_identity, onboard_federated_fleet_agent',
|
|
186
186
|
'EC-1: exactly three agent_board routes accept an enrolled key fingerprint (pg_proc-derived)');
|
|
187
187
|
|
|
188
|
-
--
|
|
188
|
+
-- Readmission creates a fresh pending key for a verified human owner, never an
|
|
189
|
+
-- enrollment resumption. The exemption is positively checked below and exercised
|
|
190
|
+
-- with a real human owner and proof challenge in the SIDSCHEMA readmission suite.
|
|
191
|
+
-- The other creators still reach an enrolled key through the enroll wrapper, so
|
|
189
192
|
-- onboard_federated_fleet_agent necessarily reaches an already-enrolled key through
|
|
190
193
|
-- enroll_federated_identity rather than deciding the duplicate case itself.
|
|
191
194
|
select is(
|
|
@@ -201,8 +204,23 @@ select is(
|
|
|
201
204
|
-- invisible to the #196 lint because that lint only inspects POSITIONAL and
|
|
202
205
|
-- NEGATIVE assertions, and this is a PRESENCE match -- see the note on the lint.
|
|
203
206
|
and agent_board_test.stripped_body(p.oid) ~ agent_board_test.stmt_re('insert into', 'agent_signing_keys')),
|
|
204
|
-
'_enroll_federated_identity_once, mint_delegated_child',
|
|
205
|
-
'EC-2: only
|
|
207
|
+
'_enroll_federated_identity_once, mint_delegated_child, readmit_session_identity',
|
|
208
|
+
'EC-2: only enroll, delegated-child mint and fresh readmission insert signing keys (strip_sql-derived)');
|
|
209
|
+
select is(
|
|
210
|
+
(select string_agg(r || '=' || has_function_privilege(r, p.oid, 'EXECUTE')::text, ' ' order by r)
|
|
211
|
+
from pg_proc p join pg_namespace n on n.oid = p.pronamespace,
|
|
212
|
+
unnest(array['anon', 'authenticated', 'public', 'service_role']) r
|
|
213
|
+
where n.nspname = 'agent_board' and p.proname = 'readmit_session_identity'),
|
|
214
|
+
'anon=false authenticated=true public=false service_role=false',
|
|
215
|
+
'EC-2: readmission is human-authenticated only, not a service enrollment route');
|
|
216
|
+
select ok(
|
|
217
|
+
(select count(*) = 1 and bool_and(
|
|
218
|
+
agent_board_test.stripped_body(p.oid) ~ agent_board_test.call_re('_session_identity_owner')
|
|
219
|
+
-- Keep literals only because these two key-state values are the markers.
|
|
220
|
+
and agent_board_test.stripped_body(p.oid, true) ~ $$'pending_proof'\s*,\s*'unproven'$$)
|
|
221
|
+
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
|
222
|
+
where n.nspname = 'agent_board' and p.proname = 'readmit_session_identity'),
|
|
223
|
+
'EC-2: readmission positively verifies the owner and writes a pending, unproven fresh key');
|
|
206
224
|
|
|
207
225
|
-- ---------------------------------------------------------------------------
|
|
208
226
|
-- R-1 -- THE #176 FIX: an unstamped enrollment resumes for its owner
|
|
@@ -19,7 +19,7 @@ set search_path = extensions, public, agent_board;
|
|
|
19
19
|
|
|
20
20
|
begin;
|
|
21
21
|
|
|
22
|
-
select plan(
|
|
22
|
+
select plan(68);
|
|
23
23
|
|
|
24
24
|
-- ---------------------------------------------------------------------------
|
|
25
25
|
-- Fixtures
|
|
@@ -452,8 +452,23 @@ select is(
|
|
|
452
452
|
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
|
453
453
|
where n.nspname = 'agent_board' and p.prokind = 'f'
|
|
454
454
|
and agent_board_test.stripped_body(p.oid) ~ agent_board_test.stmt_re('insert into', 'agent_signing_keys')),
|
|
455
|
-
'_enroll_federated_identity_once, mint_delegated_child',
|
|
456
|
-
'EC-T2: exactly
|
|
455
|
+
'_enroll_federated_identity_once, mint_delegated_child, readmit_session_identity',
|
|
456
|
+
'EC-T2: exactly three functions insert signing keys, including fresh readmission (the EC-2 form)');
|
|
457
|
+
select is(
|
|
458
|
+
(select string_agg(r || '=' || has_function_privilege(r, p.oid, 'EXECUTE')::text, ' ' order by r)
|
|
459
|
+
from pg_proc p join pg_namespace n on n.oid = p.pronamespace,
|
|
460
|
+
unnest(array['anon', 'authenticated', 'public', 'service_role']) r
|
|
461
|
+
where n.nspname = 'agent_board' and p.proname = 'readmit_session_identity'),
|
|
462
|
+
'anon=false authenticated=true public=false service_role=false',
|
|
463
|
+
'EC-T2: readmission is human-authenticated only, not a Portal-token enrollment route');
|
|
464
|
+
select ok(
|
|
465
|
+
(select count(*) = 1 and bool_and(
|
|
466
|
+
agent_board_test.stripped_body(p.oid) ~ agent_board_test.call_re('_session_identity_owner')
|
|
467
|
+
-- Keep literals only because these two key-state values are the markers.
|
|
468
|
+
and agent_board_test.stripped_body(p.oid, true) ~ $$'pending_proof'\s*,\s*'unproven'$$)
|
|
469
|
+
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
|
470
|
+
where n.nspname = 'agent_board' and p.proname = 'readmit_session_identity'),
|
|
471
|
+
'EC-T2: readmission positively verifies the owner and writes a pending, unproven fresh key');
|
|
457
472
|
select is(
|
|
458
473
|
(select string_agg(r || '=' || has_function_privilege(r, p.oid, 'EXECUTE')::text, ' ' order by r)
|
|
459
474
|
from pg_proc p join pg_namespace n on n.oid = p.pronamespace,
|
|
@@ -233,8 +233,8 @@ select is(
|
|
|
233
233
|
(select string_agg(split_part(sig, '(', 1), ', ' order by split_part(sig, '(', 1))
|
|
234
234
|
from _lockclass where reaches_board and reaches_owner),
|
|
235
235
|
'_enroll_federated_human_after_revocation_gate, _enroll_federated_identity_once, '
|
|
236
|
-
|| 'enroll_federated_human, enroll_federated_identity, onboard_federated_fleet_agent',
|
|
237
|
-
'EC-2: the board+owner lock class
|
|
236
|
+
|| 'enroll_federated_human, enroll_federated_identity, mint_delegated_child, onboard_federated_fleet_agent, readmit_session_identity',
|
|
237
|
+
'EC-2: the board+owner lock class includes the federated routes and both SID mints');
|
|
238
238
|
|
|
239
239
|
-- EC-3: THE ORDERING INVARIANT. Never acquire auth.users before boards. The failure
|
|
240
240
|
-- prints the offending signatures (pre-fix this named enroll_federated_identity and
|
|
@@ -40,7 +40,7 @@ set search_path = extensions, public, agent_board;
|
|
|
40
40
|
|
|
41
41
|
begin;
|
|
42
42
|
|
|
43
|
-
select plan(
|
|
43
|
+
select plan(21);
|
|
44
44
|
|
|
45
45
|
-- ---------------------------------------------------------------------------
|
|
46
46
|
-- The derivation, materialized once. `first_path` is the position PATH of the
|
|
@@ -49,7 +49,46 @@ select plan(18);
|
|
|
49
49
|
-- function takes on that relation anywhere, which over-reports conflicts rather
|
|
50
50
|
-- than under-reporting them.
|
|
51
51
|
-- ---------------------------------------------------------------------------
|
|
52
|
-
create temp table
|
|
52
|
+
create temp table _le_raw on commit drop as select * from agent_board_test.lock_edges();
|
|
53
|
+
-- The fixture conservatively assigns every target-table FK to every INSERT.
|
|
54
|
+
-- Bound these two exemptions by the executable column lists AND the catalog:
|
|
55
|
+
-- every INSERT must use an explicit list, omit all nullable/default-free FK
|
|
56
|
+
-- columns, and have no BEFORE INSERT trigger that could populate them. UPDATE
|
|
57
|
+
-- edges and any new writer/constructor remain in the graph.
|
|
58
|
+
create temp table _null_fk_insert on commit drop as
|
|
59
|
+
with candidates as (
|
|
60
|
+
select distinct e.origin_oid, e.origin_name, e.rel_oid, e.path[1] as pos,
|
|
61
|
+
c.oid as constraint_oid, c.conname, c.conrelid, c.conkey,
|
|
62
|
+
cl.relname, agent_board_test.stripped_body(e.origin_oid) as body
|
|
63
|
+
from _le_raw e join pg_constraint c on e.via='fk-insert:' || c.conname and c.confrelid=e.rel_oid
|
|
64
|
+
join pg_class cl on cl.oid=c.conrelid
|
|
65
|
+
where e.own and e.origin_name in ('_session_identity_agent_lineage','_revoke_agent_principal')
|
|
66
|
+
), proven as (
|
|
67
|
+
select c.*, (select count(*) from regexp_matches(c.body,
|
|
68
|
+
agent_board_test.re(agent_board_test.stmt_frag('insert into', c.relname, true)
|
|
69
|
+
|| '\s*(\([^)]*\))?'), 'g')) as insert_count
|
|
70
|
+
from candidates c
|
|
71
|
+
where not exists (select 1 from unnest(c.conkey) k join pg_attribute a
|
|
72
|
+
on a.attrelid=c.conrelid and a.attnum=k where a.attnotnull or a.atthasdef)
|
|
73
|
+
and not exists (select 1 from pg_trigger t where t.tgrelid=c.conrelid
|
|
74
|
+
and not t.tgisinternal and (t.tgtype & 2)=2 and (t.tgtype & 4)=4)
|
|
75
|
+
and not exists (select 1 from regexp_matches(c.body,
|
|
76
|
+
agent_board_test.re(agent_board_test.stmt_frag('insert into', c.relname, true)
|
|
77
|
+
|| '\s*(\([^)]*\))?'), 'g') m
|
|
78
|
+
where m[1] is null or exists (select 1 from unnest(c.conkey) k join pg_attribute a
|
|
79
|
+
on a.attrelid=c.conrelid and a.attnum=k
|
|
80
|
+
where a.attname = any(string_to_array(trim(both '()' from
|
|
81
|
+
translate(lower(m[1]), ' "' || chr(9) || chr(10) || chr(13), '')), ','))))
|
|
82
|
+
)
|
|
83
|
+
select * from proven where insert_count>0;
|
|
84
|
+
create temp table _le on commit drop as
|
|
85
|
+
select e.* from _le_raw e where not exists (
|
|
86
|
+
select 1 from _null_fk_insert n where n.origin_oid=e.origin_oid and n.rel_oid=e.rel_oid
|
|
87
|
+
and n.pos=e.path[cardinality(e.path)]
|
|
88
|
+
and not exists (select 1 from _le_raw kept where kept.own and kept.origin_oid=e.origin_oid
|
|
89
|
+
and kept.rel_oid=e.rel_oid and kept.path[1]=n.pos
|
|
90
|
+
and not exists (select 1 from _null_fk_insert omitted where omitted.origin_oid=kept.origin_oid
|
|
91
|
+
and omitted.rel_oid=kept.rel_oid and omitted.pos=kept.path[1] and kept.via='fk-insert:' || omitted.conname)));
|
|
53
92
|
create temp table _cl on commit drop as select * from agent_board_test.fn_call_closure();
|
|
54
93
|
|
|
55
94
|
create temp table _acq on commit drop as
|
|
@@ -187,8 +226,28 @@ select is(
|
|
|
187
226
|
join pg_class cl on cl.oid = t.tgrelid
|
|
188
227
|
join pg_namespace n on n.oid = cl.relnamespace
|
|
189
228
|
where not t.tgisinternal and n.nspname = 'agent_board')),
|
|
190
|
-
'',
|
|
191
|
-
'FKL-8:
|
|
229
|
+
'_session_identity_agent_lineage',
|
|
230
|
+
'FKL-8: the catalog-derived locking trigger class is exactly the positively bounded fresh legacy-identity constructor; new trigger locks name themselves');
|
|
231
|
+
|
|
232
|
+
-- This trigger may INSERT only the identity for NEW.id on an agents INSERT,
|
|
233
|
+
-- never another ancestor and never on UPDATE. Its own-agent FK is already
|
|
234
|
+
-- owned by the insert; parent/root KEY SHARE locks are the same FK targets the
|
|
235
|
+
-- agents INSERT already acquired. The constructor is exercised positively by
|
|
236
|
+
-- agent_board_session_identity_lineage.test.sql, including session-kind retention.
|
|
237
|
+
select is((select string_agg(distinct e.rel_name || ' ' || e.lock_mode || ' via ' || e.via, ' | '
|
|
238
|
+
order by e.rel_name || ' ' || e.lock_mode || ' via ' || e.via)
|
|
239
|
+
from _le e where e.fn_name='_session_identity_agent_lineage' and e.own),
|
|
240
|
+
'agent_board.agents key share via fk-insert:session_identities_agent_id_fkey | agent_board.agents key share via fk-insert:session_identities_parent_agent_id_fkey | agent_board.agents key share via fk-insert:session_identities_root_agent_id_fkey',
|
|
241
|
+
'FKL-8-constructor-positive: every actual constructor FK lock is on its already inserted agent or the same parent/root agents targets; it has no independent identity update lock');
|
|
242
|
+
|
|
243
|
+
select is((select string_agg(conname || ':' || insert_count, ', ' order by conname)
|
|
244
|
+
from _null_fk_insert where origin_name='_session_identity_agent_lineage'),
|
|
245
|
+
'session_identities_forked_from_fkey:1, session_identities_lease_alias_fkey:1, session_identities_readmitted_from_fkey:1',
|
|
246
|
+
'FKL-8-constructor-null-positive: all three nullable/default-free continuity columns are omitted from every constructor INSERT and no BEFORE INSERT trigger can populate them');
|
|
247
|
+
select is((select string_agg(conname || ':' || insert_count, ', ' order by conname)
|
|
248
|
+
from _null_fk_insert where origin_name='_revoke_agent_principal'),
|
|
249
|
+
'fleet_revocation_events_harness_registration_id_fkey:2, fleet_revocation_events_machine_id_fkey:2, fleet_revocation_events_repo_scope_id_fkey:2, fleet_revocation_events_trust_domain_id_fkey:2',
|
|
250
|
+
'FKL-8-revoke-null-positive: every principal ledger INSERT omits all four nullable/default-free fleet links with no BEFORE INSERT trigger; real FK edges are retained');
|
|
192
251
|
|
|
193
252
|
-- ===========================================================================
|
|
194
253
|
-- FKL-9: THE CLASS section 9 could not compute. Every function whose lock graph
|
|
@@ -239,8 +298,13 @@ select is(
|
|
|
239
298
|
'invite_board_member: memberships(update) then agents(key share) [unserialised]',
|
|
240
299
|
'join_board: memberships(update) then agents(key share) [unserialised]',
|
|
241
300
|
'leave_board: memberships(update) then agents(key share) [unserialised]',
|
|
301
|
+
-- SIDSCHEMA role writers use the existing consent sink and board mutex.
|
|
302
|
+
-- Their installed lock order is asserted, not exempted from the class.
|
|
303
|
+
'link_session_alias: agents(update) then memberships(update) [advisory]',
|
|
304
|
+
'mint_delegated_child: agents(update) then memberships(update) [advisory]',
|
|
242
305
|
'onboard_federated_fleet_agent: agents(update) then memberships(update) [advisory]',
|
|
243
306
|
'post_board_message: agents(key share) then memberships(key share) [unserialised]',
|
|
307
|
+
'readmit_session_identity: agents(update) then memberships(update) [advisory]',
|
|
244
308
|
'redeem_invite_link: memberships(key share) then agents(key share) [unserialised]',
|
|
245
309
|
'request_join_board: memberships(update) then agents(key share) [unserialised]',
|
|
246
310
|
'resolve_board_request: memberships(update) then agents(key share) [unserialised]',
|
|
@@ -252,12 +316,9 @@ select is(
|
|
|
252
316
|
'signed_send_direct_message: agents(key share) then memberships(key share) [unserialised]',
|
|
253
317
|
'signed_send_multicast_message: agents(key share) then memberships(key share) [unserialised]',
|
|
254
318
|
'subscribe_callback_endpoint: memberships(key share) then agents(key share) [unserialised]',
|
|
255
|
-
--
|
|
256
|
-
-- add_delegate_to_board
|
|
257
|
-
|
|
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]',
|
|
319
|
+
-- SIDSCHEMA integrity repair: board mutexes precede owner/instance and family row locks,
|
|
320
|
+
-- matching add_delegate_to_board's board-before-instance order. No withdrawal exemption.
|
|
321
|
+
'tombstone_delegate_instance: agents(update) then memberships(update) [advisory]',
|
|
261
322
|
'update_board_membership: memberships(update) then agents(key share) [unserialised]'
|
|
262
323
|
]::text[],
|
|
263
324
|
'FKL-9: the agents+board_memberships lock class, FK edges included (section 9''s text scan sees 3 of these)');
|
|
@@ -267,6 +328,9 @@ select is(
|
|
|
267
328
|
-- orders with CONFLICTING modes and no shared advisory lock. Pinned at the
|
|
268
329
|
-- relation-pair level rather than the function-pair level (146 of those today)
|
|
269
330
|
-- because a pair of tables entering this set is the reviewable event.
|
|
331
|
+
-- SIDSCHEMA's positively proved NULL-insert filtering above removes four
|
|
332
|
+
-- previously inferred fleet-machine/trust-domain pairs: PostgreSQL cannot take
|
|
333
|
+
-- an FK row lock for a NULL reference. No new relation pair is exempted.
|
|
270
334
|
--
|
|
271
335
|
-- KNOWN-BAD and deliberately not fixed -- see the file header. ONE pair has been
|
|
272
336
|
-- removed since: #281 R1 fixed `agent_board.tasks <-> agent_board.threads` by
|
|
@@ -340,14 +404,12 @@ select is(
|
|
|
340
404
|
'agent_board.agent_signing_keys <-> agent_board.agents',
|
|
341
405
|
'agent_board.agent_signing_keys <-> agent_board.board_memberships',
|
|
342
406
|
'agent_board.agent_signing_keys <-> agent_board.fleet_machine_agents',
|
|
343
|
-
'agent_board.agent_signing_keys <-> agent_board.fleet_machines',
|
|
344
407
|
'agent_board.agent_signing_keys <-> agent_board.fleet_trust_domains',
|
|
345
408
|
'agent_board.agents <-> agent_board.board_invite_tokens',
|
|
346
409
|
'agent_board.agents <-> agent_board.board_memberships',
|
|
347
410
|
'agent_board.agents <-> agent_board.boards',
|
|
348
411
|
'agent_board.agents <-> agent_board.enroll_token_uses',
|
|
349
412
|
'agent_board.agents <-> agent_board.fleet_machine_agents',
|
|
350
|
-
'agent_board.agents <-> agent_board.fleet_machines',
|
|
351
413
|
'agent_board.agents <-> agent_board.fleet_trust_domains',
|
|
352
414
|
'agent_board.agents <-> agent_board.identity_providers',
|
|
353
415
|
'agent_board.agents <-> auth.users',
|
|
@@ -361,8 +423,6 @@ select is(
|
|
|
361
423
|
'agent_board.fleet_harness_registrations <-> agent_board.fleet_machine_agents',
|
|
362
424
|
'agent_board.fleet_machine_agents <-> agent_board.fleet_repo_scopes',
|
|
363
425
|
'agent_board.fleet_machine_agents <-> agent_board.fleet_trust_domains',
|
|
364
|
-
'agent_board.fleet_machines <-> auth.users',
|
|
365
|
-
'agent_board.fleet_trust_domains <-> auth.users',
|
|
366
426
|
'agent_board.identity_providers <-> auth.users'
|
|
367
427
|
]::text[],
|
|
368
428
|
'FKL-10: the relation pairs held in opposite orders with conflicting modes and no shared advisory lock');
|