@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
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
create extension if not exists pgtap with schema extensions;
|
|
2
|
+
set search_path = extensions, public, agent_board;
|
|
3
|
+
begin;
|
|
4
|
+
grant usage on schema extensions to service_role, authenticated;
|
|
5
|
+
select plan(34);
|
|
6
|
+
create temp table _sid_limits_root(root_id uuid, owner_auth uuid);
|
|
7
|
+
create temp table _sid_limits_minted(label text primary key, agent_id uuid, key_id uuid);
|
|
8
|
+
create temp table _sid_limits_tokens(label text, epoch bigint);
|
|
9
|
+
create temp table _sid_limits_history(agent_id uuid, n int);
|
|
10
|
+
do $$ declare r uuid := gen_random_uuid(); u uuid; begin
|
|
11
|
+
u := public.ensure_github_user('sid-limits-' || r, 'sid-limits-' || r || '@example.test', 'sid-limits-' || r, 'Limits');
|
|
12
|
+
insert into agent_board.agents(id, agent_type, handle, display_name, status, auth_user_id, owner_user_id)
|
|
13
|
+
values (r, 'agent', 'sid-limits-' || r, 'Limits root', 'active', u, u);
|
|
14
|
+
insert into agent_board.session_identities(agent_id, root_agent_id, depth, approved_risky_digest, current_risky_digest)
|
|
15
|
+
values (r, r, 0, 'approved', 'approved');
|
|
16
|
+
insert into _sid_limits_root values (r, u);
|
|
17
|
+
end $$;
|
|
18
|
+
grant select on _sid_limits_root, _sid_limits_minted, _sid_limits_history to service_role;
|
|
19
|
+
grant insert on _sid_limits_minted, _sid_limits_tokens to service_role;
|
|
20
|
+
create function pg_temp.sid_limits_mint(p_label text, p_parent text default null)
|
|
21
|
+
returns void language plpgsql as $$ declare p uuid; begin
|
|
22
|
+
p := coalesce((select agent_id from _sid_limits_minted where label = p_parent), (select root_id from _sid_limits_root));
|
|
23
|
+
insert into _sid_limits_minted
|
|
24
|
+
select p_label, child_agent_id, signing_key_id from agent_board.mint_delegated_child(p,
|
|
25
|
+
'sid-limits-' || p_label || '-' || substr(p::text, 1, 8), '[]', public.test_pubkey(p_label || p), interval '7 days', false,
|
|
26
|
+
'claude', p_label, case when p_parent is not null then '0' end, '[]', '{"resolution":"resolved","risky_digest":"approved"}');
|
|
27
|
+
end $$;
|
|
28
|
+
create function pg_temp.sid_limits_claim(p_label text)
|
|
29
|
+
returns void language plpgsql as $$ declare m record; c record; begin
|
|
30
|
+
select * into m from _sid_limits_minted where label = p_label;
|
|
31
|
+
select * into c from agent_board.issue_signing_key_challenge(m.agent_id, 'delegated-ephemeral');
|
|
32
|
+
insert into _sid_limits_tokens select p_label, s.identity_lease_epoch from agent_board.mint_fleet_agent_session(
|
|
33
|
+
c.challenge_id, split_part(c.challenge, ':', 3), m.key_id, m.agent_id, 900, null, p_label) s;
|
|
34
|
+
end $$;
|
|
35
|
+
create function pg_temp.sid_limits_refuse(p_call text)
|
|
36
|
+
returns void language plpgsql as $$ begin
|
|
37
|
+
execute p_call;
|
|
38
|
+
raise exception 'expected refusal but RPC succeeded' using errcode = 'P0001';
|
|
39
|
+
end $$;
|
|
40
|
+
grant execute on function pg_temp.sid_limits_mint(text, text), pg_temp.sid_limits_claim(text), pg_temp.sid_limits_refuse(text) to service_role;
|
|
41
|
+
select set_config('request.jwt.claim.role', 'service_role', true);
|
|
42
|
+
set local role service_role;
|
|
43
|
+
select lives_ok($q$select agent_board.upsert_delegation_policy((select root_id from _sid_limits_root), 'root_minted', '[]', 200, interval '7 days', true)$q$,
|
|
44
|
+
'EC-SIDSCHEMA-6-limits-policy: real sole policy writer configures the accepted root cap');
|
|
45
|
+
reset role;
|
|
46
|
+
select is((select max_active_children from agent_board.delegation_policies where root_agent_id = (select root_id from _sid_limits_root)), 200,
|
|
47
|
+
'EC-SIDSCHEMA-6-limits-policy-state: the policy does not restore the old 25 limit');
|
|
48
|
+
set local role service_role;
|
|
49
|
+
select lives_ok($q$select pg_temp.sid_limits_mint('parent'); select pg_temp.sid_limits_claim('parent'); select pg_temp.sid_limits_mint('a'); select pg_temp.sid_limits_mint('b')$q$,
|
|
50
|
+
'EC-SIDSCHEMA-6-limits-control: real mints reserve two identities and claim their depth-one parent');
|
|
51
|
+
reset role;
|
|
52
|
+
select is((select count(*) from _sid_limits_minted), 3::bigint, 'EC-SIDSCHEMA-6-limits-control-state: all real minted principals exist');
|
|
53
|
+
-- A mixed historical census: 20 live depth-two sessions, 179 active legacy
|
|
54
|
+
-- depth-one grants, and one expired legacy grant available for reactivation.
|
|
55
|
+
insert into _sid_limits_history select gen_random_uuid(), n from generate_series(1, 200) n;
|
|
56
|
+
insert into agent_board.agents(id, agent_type, handle, display_name, status, owner_user_id, parent_agent_id, root_agent_id)
|
|
57
|
+
select h.agent_id, 'agent', 'sid-history-' || h.agent_id, 'Quota fixture', 'active', r.owner_auth,
|
|
58
|
+
case when h.n <= 20 then (select agent_id from _sid_limits_minted where label = 'parent') else r.root_id end, r.root_id
|
|
59
|
+
from _sid_limits_history h cross join _sid_limits_root r;
|
|
60
|
+
insert into agent_board.session_identities(agent_id, parent_agent_id, root_agent_id, depth, identity_kind, harness,
|
|
61
|
+
holder_secret_hash, last_heartbeat, approved_risky_digest, current_risky_digest, created_at)
|
|
62
|
+
select h.agent_id, a.parent_agent_id, a.root_agent_id, case when h.n <= 20 then 2 else 1 end,
|
|
63
|
+
case when h.n <= 20 then 'session' else 'legacy' end, case when h.n <= 20 then 'codex' end,
|
|
64
|
+
case when h.n <= 20 then extensions.digest(h.agent_id::text, 'sha256') end,
|
|
65
|
+
case when h.n <= 20 then now() end, 'approved', 'approved', now() - interval '2 hours'
|
|
66
|
+
from _sid_limits_history h join agent_board.agents a on a.id = h.agent_id;
|
|
67
|
+
insert into agent_board.session_id_aliases(identity_agent_id, root_agent_id, harness, session_id)
|
|
68
|
+
select h.agent_id, r.root_id, 'codex', h.agent_id::text from _sid_limits_history h cross join _sid_limits_root r where h.n <= 20;
|
|
69
|
+
update agent_board.session_identities si set lease_alias_id = al.id from agent_board.session_id_aliases al
|
|
70
|
+
where al.identity_agent_id = si.agent_id and si.agent_id in (select agent_id from _sid_limits_history);
|
|
71
|
+
insert into agent_board.delegation_grants(child_agent_id, root_agent_id, parent_agent_id, delegation_policy_id, granted_scopes, grant_expires_at, created_at)
|
|
72
|
+
select h.agent_id, r.root_id, a.parent_agent_id, dp.id, '[]',
|
|
73
|
+
case when h.n = 200 then now() - interval '1 second' else now() + interval '4 hours' end, now() - interval '2 hours'
|
|
74
|
+
from _sid_limits_history h cross join _sid_limits_root r join agent_board.agents a on a.id = h.agent_id
|
|
75
|
+
join agent_board.delegation_policies dp on dp.root_agent_id = r.root_id;
|
|
76
|
+
create temp view _sid_live_count as
|
|
77
|
+
select count(*) as total from agent_board.delegation_grants g join agent_board.agents a on a.id = g.child_agent_id
|
|
78
|
+
left join agent_board.session_identities si on si.agent_id = a.id
|
|
79
|
+
where g.root_agent_id = (select root_id from _sid_limits_root) and a.status = 'active' and g.revoked_at is null and g.grant_expires_at > now()
|
|
80
|
+
and (si.identity_kind is distinct from 'session' or agent_board._session_identity_lease_live(a.id));
|
|
81
|
+
select is((select total from _sid_live_count), 200::bigint,
|
|
82
|
+
'EC-SIDSCHEMA-6-depth-two-census: root boundary includes all 20 live depth-two identities plus legacy grants');
|
|
83
|
+
select is((select count(*) from agent_board.session_identities where root_agent_id = (select root_id from _sid_limits_root) and identity_kind = 'session' and holder_secret_hash is null), 2::bigint,
|
|
84
|
+
'EC-SIDSCHEMA-6-unclaimed-census: both reservations hold no active lease');
|
|
85
|
+
set local role service_role;
|
|
86
|
+
select throws_ok(format('select pg_temp.sid_limits_refuse(%L)', $q$select pg_temp.sid_limits_claim('a')$q$),
|
|
87
|
+
'42501', 'identity_limit_active_exceeded', 'EC-SIDSCHEMA-6-first-claim-limit: first claim recounts the mixed descendant-wide active boundary');
|
|
88
|
+
select throws_ok(format('select pg_temp.sid_limits_refuse(%L)', $q$select agent_board.renew_delegated_grant((select agent_id from _sid_limits_history where n = 200), interval '1 hour')$q$),
|
|
89
|
+
'42501', 'identity_limit_active_exceeded', 'EC-SIDSCHEMA-6-expired-renew-limit: expired legacy renewal cannot reactivate past the active limit');
|
|
90
|
+
reset role;
|
|
91
|
+
update agent_board.agents set status = 'suspended' where id = (select agent_id from _sid_limits_history where n = 199);
|
|
92
|
+
set local role service_role;
|
|
93
|
+
select lives_ok($q$select pg_temp.sid_limits_claim('a')$q$, 'EC-SIDSCHEMA-6-first-below-control: the same real first claim succeeds at 199 active identities');
|
|
94
|
+
reset role;
|
|
95
|
+
select is((select total from _sid_live_count), 200::bigint, 'EC-SIDSCHEMA-6-first-below-state: first claim reaches exactly 200, not 25');
|
|
96
|
+
select is((select epoch from _sid_limits_tokens where label = 'a'), 0::bigint, 'EC-SIDSCHEMA-6-first-epoch: below-limit first claim holds its original epoch');
|
|
97
|
+
update agent_board.session_identities set last_heartbeat = now() - interval '181 seconds' where agent_id = (select agent_id from _sid_limits_minted where label = 'a');
|
|
98
|
+
update agent_board.agents set status = 'active' where id = (select agent_id from _sid_limits_history where n = 199);
|
|
99
|
+
set local role service_role;
|
|
100
|
+
select throws_ok(format('select pg_temp.sid_limits_refuse(%L)', $q$select pg_temp.sid_limits_claim('a')$q$),
|
|
101
|
+
'42501', 'identity_limit_active_exceeded', 'EC-SIDSCHEMA-6-reclaim-limit: lapsed holder reclaim recounts rather than bypassing the active cap');
|
|
102
|
+
reset role;
|
|
103
|
+
select is((select credential_epoch from agent_board.session_identities where agent_id = (select agent_id from _sid_limits_minted where label = 'a')), 0::bigint,
|
|
104
|
+
'EC-SIDSCHEMA-6-reclaim-refusal-state: refused reclaim never rotates the holder epoch');
|
|
105
|
+
update agent_board.agents set status = 'suspended' where id = (select agent_id from _sid_limits_history where n = 199);
|
|
106
|
+
set local role service_role;
|
|
107
|
+
select lives_ok($q$select pg_temp.sid_limits_claim('a')$q$, 'EC-SIDSCHEMA-6-reclaim-below-control: the real continuity reclaim succeeds below the cap');
|
|
108
|
+
reset role;
|
|
109
|
+
select is((select max(epoch) from _sid_limits_tokens where label = 'a'), 1::bigint, 'EC-SIDSCHEMA-6-reclaim-below-state: allowed reclaim rotates the epoch once');
|
|
110
|
+
update agent_board.session_identities set last_heartbeat = now() - interval '181 seconds' where agent_id = (select agent_id from _sid_limits_minted where label = 'a');
|
|
111
|
+
set local role service_role;
|
|
112
|
+
select lives_ok($q$select agent_board.renew_delegated_grant((select agent_id from _sid_limits_history where n = 200), interval '1 hour')$q$,
|
|
113
|
+
'EC-SIDSCHEMA-6-expired-below-control: real expired legacy renewal succeeds below the mixed cap');
|
|
114
|
+
reset role;
|
|
115
|
+
select is((select total from _sid_live_count), 200::bigint, 'EC-SIDSCHEMA-6-expired-below-state: expired renewal consumes the last active slot');
|
|
116
|
+
update agent_board.agents set status = 'suspended' where id in (select agent_id from _sid_limits_history where n > 20);
|
|
117
|
+
set local role service_role;
|
|
118
|
+
select throws_ok(format('select pg_temp.sid_limits_refuse(%L)', $q$select pg_temp.sid_limits_mint('over-parent', 'parent')$q$),
|
|
119
|
+
'42501', 'identity_limit_active_exceeded', 'EC-SIDSCHEMA-6-parent-active: depth-one parent refuses its twenty-first active child');
|
|
120
|
+
reset role;
|
|
121
|
+
update agent_board.session_identities set last_heartbeat = now() - interval '181 seconds' where agent_id = (select agent_id from _sid_limits_history where n = 20);
|
|
122
|
+
set local role service_role;
|
|
123
|
+
select lives_ok($q$select pg_temp.sid_limits_mint('under-parent', 'parent'); select pg_temp.sid_limits_claim('under-parent')$q$,
|
|
124
|
+
'EC-SIDSCHEMA-6-parent-below-control: a real depth-two mint and claim take the twentieth parent slot');
|
|
125
|
+
reset role;
|
|
126
|
+
select is((select depth from agent_board.session_identities where agent_id = (select agent_id from _sid_limits_minted where label = 'under-parent')), 2::smallint,
|
|
127
|
+
'EC-SIDSCHEMA-6-parent-below-state: the allowed child has actual stored depth two');
|
|
128
|
+
update agent_board.delegation_grants set created_at = now() - interval '2 hours' where root_agent_id = (select root_id from _sid_limits_root);
|
|
129
|
+
update agent_board.delegation_grants set created_at = now(), revoked_at = now()
|
|
130
|
+
where child_agent_id in (select agent_id from _sid_limits_history where n between 21 and 79);
|
|
131
|
+
select is((select count(*) from agent_board.delegation_grants where root_agent_id = (select root_id from _sid_limits_root) and created_at > now() - interval '3600 seconds'), 59::bigint,
|
|
132
|
+
'EC-SIDSCHEMA-6-hourly-fixture: 59 revoked historical mints still consume the root rolling hour');
|
|
133
|
+
set local role service_role;
|
|
134
|
+
select lives_ok($q$select pg_temp.sid_limits_mint('hour-allowed')$q$,
|
|
135
|
+
'EC-SIDSCHEMA-6-hourly-sixty-control: a real root mint takes the sixtieth hourly slot, beyond the old 25 limit');
|
|
136
|
+
reset role;
|
|
137
|
+
select is((select count(*) from agent_board.delegation_grants where root_agent_id = (select root_id from _sid_limits_root) and created_at > now() - interval '3600 seconds'), 60::bigint,
|
|
138
|
+
'EC-SIDSCHEMA-6-hourly-sixty-state: root hourly accounting reaches exactly sixty');
|
|
139
|
+
set local role service_role;
|
|
140
|
+
select throws_ok(format('select pg_temp.sid_limits_refuse(%L)', $q$select pg_temp.sid_limits_mint('hour-refused')$q$),
|
|
141
|
+
'42501', 'identity_limit_hourly_exceeded', 'EC-SIDSCHEMA-6-hourly-root: sixty-first mint refuses by the accepted root code');
|
|
142
|
+
reset role;
|
|
143
|
+
select is((select count(*) from _sid_limits_minted where label = 'hour-refused'), 0::bigint,
|
|
144
|
+
'EC-SIDSCHEMA-6-hourly-refusal-state: hourly refusal provisions no identity');
|
|
145
|
+
update agent_board.delegation_grants set created_at = now() - interval '2 hours' where root_agent_id = (select root_id from _sid_limits_root);
|
|
146
|
+
update agent_board.delegation_grants set created_at = now()
|
|
147
|
+
where child_agent_id in (select agent_id from _sid_limits_history where n between 1 and 10);
|
|
148
|
+
update agent_board.session_identities set last_heartbeat = now() - interval '181 seconds' where agent_id = (select agent_id from _sid_limits_minted where label = 'under-parent');
|
|
149
|
+
set local role service_role;
|
|
150
|
+
select throws_ok(format('select pg_temp.sid_limits_refuse(%L)', $q$select pg_temp.sid_limits_mint('hour-parent-refused', 'parent')$q$),
|
|
151
|
+
'42501', 'identity_limit_hourly_exceeded', 'EC-SIDSCHEMA-6-hourly-parent: depth-one parent counts all ten depth-two mints in its hour');
|
|
152
|
+
reset role;
|
|
153
|
+
select is((select count(*) from _sid_limits_minted where label = 'hour-parent-refused'), 0::bigint,
|
|
154
|
+
'EC-SIDSCHEMA-6-hourly-parent-state: parent hourly refusal leaves no new principal');
|
|
155
|
+
-- The quota/activation writer population comes from column writes in the
|
|
156
|
+
-- catalog. Named-call closure also includes wrappers; no hand RPC exemption.
|
|
157
|
+
create temp view _sid_activation_writers as
|
|
158
|
+
select p.oid, p.proname, agent_board_test.stripped_body(p.oid) as body
|
|
159
|
+
from pg_proc p where p.pronamespace = 'agent_board'::regnamespace
|
|
160
|
+
and (agent_board_test.stripped_body(p.oid) ~ agent_board_test.col_write_re('delegation_grants', 'grant_expires_at')
|
|
161
|
+
or agent_board_test.stripped_body(p.oid) ~ agent_board_test.col_write_re('session_identities', 'holder_secret_hash'));
|
|
162
|
+
select is((select string_agg(proname, ', ' order by proname) from _sid_activation_writers),
|
|
163
|
+
'_session_identity_agent_lineage, mint_delegated_child, mint_fleet_agent_session, readmit_session_identity, renew_delegated_grant',
|
|
164
|
+
'EC-SIDSCHEMA-6-activation-population: every discovered expiry or holder writer has a positive activation path');
|
|
165
|
+
-- An INSERT is conservatively a writer of defaulted columns too. This one
|
|
166
|
+
-- writes a legacy/default-NULL identity, not a live holder or grant. Prove the
|
|
167
|
+
-- exemption through the real constructor, separately from the absence class.
|
|
168
|
+
create temp table _sid_limits_constructor(agent_id uuid);
|
|
169
|
+
grant insert,select on _sid_limits_constructor to service_role;
|
|
170
|
+
set local role service_role;
|
|
171
|
+
select lives_ok($$insert into _sid_limits_constructor select agent_id from agent_board.register_agent('system',
|
|
172
|
+
'sid-limit-constructor-' || gen_random_uuid(),'Quota-free legacy constructor'); set constraints all immediate$$,
|
|
173
|
+
'EC-SIDSCHEMA-6-constructor-control: real registration and deferred legacy identity construction execute');
|
|
174
|
+
reset role;
|
|
175
|
+
select is((select count(*) from _sid_limits_constructor c join agent_board.session_identities si on si.agent_id=c.agent_id
|
|
176
|
+
where si.identity_kind='legacy' and si.holder_secret_hash is null and si.last_heartbeat is null and si.lease_alias_id is null),
|
|
177
|
+
1::bigint,'EC-SIDSCHEMA-6-constructor-state: the positively exempt constructor writes only a lease-free legacy identity');
|
|
178
|
+
select is((select count(*) from agent_board.delegation_grants g join _sid_limits_constructor c on c.agent_id=g.child_agent_id),
|
|
179
|
+
0::bigint,'EC-SIDSCHEMA-6-constructor-grant-state: construction creates no grant or expiry and cannot activate a quota slot');
|
|
180
|
+
create temp view _sid_unlocked_activation as
|
|
181
|
+
select * from _sid_activation_writers
|
|
182
|
+
where oid<>'agent_board._session_identity_agent_lineage()'::regprocedure and (
|
|
183
|
+
regexp_instr(body, agent_board_test.row_lock_re('agents')) = 0
|
|
184
|
+
or regexp_instr(body, agent_board_test.row_lock_re('agents')) >= least(
|
|
185
|
+
nullif(regexp_instr(body, agent_board_test.col_write_re('delegation_grants', 'grant_expires_at')), 0),
|
|
186
|
+
nullif(regexp_instr(body, agent_board_test.col_write_re('session_identities', 'holder_secret_hash')), 0)));
|
|
187
|
+
create temp view _sid_unlocked_activation_class as
|
|
188
|
+
select distinct c.root_name from agent_board_test.fn_call_closure() c
|
|
189
|
+
join _sid_unlocked_activation u on u.oid = c.reached_oid;
|
|
190
|
+
select is((select coalesce(string_agg(root_name, ', ' order by root_name), '') from _sid_unlocked_activation_class), '',
|
|
191
|
+
'EC-SIDSCHEMA-6-root-lock-class: every discovered activation writer and wrapper locks the root before its census-changing write');
|
|
192
|
+
create function agent_board.sid_unlocked_activation_probe(p_child uuid)
|
|
193
|
+
returns void language sql security definer set search_path = '' as $probe$
|
|
194
|
+
/* SELECT id FROM agent_board.agents FOR UPDATE; */
|
|
195
|
+
UPDATE agent_board.delegation_grants SET grant_expires_at = now() + interval '1 hour' WHERE child_agent_id = p_child;
|
|
196
|
+
$probe$;
|
|
197
|
+
create function agent_board.sid_unlocked_activation_wrapper(p_child uuid)
|
|
198
|
+
returns void language sql security definer set search_path = '' as $probe$
|
|
199
|
+
SELECT agent_board.sid_unlocked_activation_probe(/* wrapper */ p_child);
|
|
200
|
+
$probe$;
|
|
201
|
+
select is((select string_agg(root_name, ', ' order by root_name) from _sid_unlocked_activation_class
|
|
202
|
+
where root_name in ('sid_unlocked_activation_probe', 'sid_unlocked_activation_wrapper')),
|
|
203
|
+
'sid_unlocked_activation_probe, sid_unlocked_activation_wrapper',
|
|
204
|
+
'EC-SIDSCHEMA-6-root-lock-canary: new uppercase unguarded writer and commented wrapper name themselves');
|
|
205
|
+
select is((select proname from _sid_unlocked_activation where proname = 'sid_unlocked_activation_probe'), 'sid_unlocked_activation_probe',
|
|
206
|
+
'EC-SIDSCHEMA-6-root-lock-decoy: a comment cannot masquerade as an executable accounting lock');
|
|
207
|
+
select finish();
|
|
208
|
+
rollback;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
create extension if not exists pgtap with schema extensions;
|
|
2
|
+
set search_path = extensions, public, agent_board;
|
|
3
|
+
begin;
|
|
4
|
+
select plan(8);
|
|
5
|
+
grant usage on schema extensions to service_role, authenticated;
|
|
6
|
+
create temp table _sid_lineage_ids(label text primary key, agent_id uuid);
|
|
7
|
+
insert into _sid_lineage_ids select label,gen_random_uuid() from unnest(array['root','other-root','child','grandchild']) label;
|
|
8
|
+
insert into agent_board.agents(id,agent_type,handle,display_name,status,parent_agent_id,root_agent_id)
|
|
9
|
+
select agent_id,'system','sid-lineage-' || agent_id,'Lineage fixture','active',null,null
|
|
10
|
+
from _sid_lineage_ids where label in ('root','other-root');
|
|
11
|
+
insert into agent_board.agents(id,agent_type,handle,display_name,status,parent_agent_id,root_agent_id)
|
|
12
|
+
select agent_id,'system','sid-lineage-' || agent_id,'Lineage fixture','active',
|
|
13
|
+
(select agent_id from _sid_lineage_ids where label='root'),(select agent_id from _sid_lineage_ids where label='root')
|
|
14
|
+
from _sid_lineage_ids where label='child';
|
|
15
|
+
insert into agent_board.agents(id,agent_type,handle,display_name,status,parent_agent_id,root_agent_id)
|
|
16
|
+
select agent_id,'system','sid-lineage-' || agent_id,'Lineage fixture','active',
|
|
17
|
+
(select agent_id from _sid_lineage_ids where label='child'),(select agent_id from _sid_lineage_ids where label='root')
|
|
18
|
+
from _sid_lineage_ids where label='grandchild';
|
|
19
|
+
insert into agent_board.session_identities(agent_id,parent_agent_id,root_agent_id,depth)
|
|
20
|
+
select a.id,a.parent_agent_id,coalesce(a.root_agent_id,a.id),
|
|
21
|
+
case l.label when 'child' then 1 when 'grandchild' then 2 else 0 end
|
|
22
|
+
from agent_board.agents a join _sid_lineage_ids l on l.agent_id=a.id;
|
|
23
|
+
select lives_ok('set constraints all immediate','EC-SIDSCHEMA-1-lineage-control: valid depth-zero, one and two lineage commits its constraints');
|
|
24
|
+
select is((select count(*) from agent_board.session_identities where agent_id in (select agent_id from _sid_lineage_ids)),4::bigint,
|
|
25
|
+
'EC-SIDSCHEMA-1-lineage-control-state: every fixture has exactly one identity');
|
|
26
|
+
create function pg_temp.sid_lineage_bad(p_mode text) returns void language plpgsql as $$
|
|
27
|
+
begin
|
|
28
|
+
set constraints all deferred;
|
|
29
|
+
if p_mode='agent-update' then
|
|
30
|
+
update agent_board.agents set root_agent_id=(select agent_id from _sid_lineage_ids where label='other-root')
|
|
31
|
+
where id=(select agent_id from _sid_lineage_ids where label='child');
|
|
32
|
+
elsif p_mode='missing-parent' then
|
|
33
|
+
delete from agent_board.session_identities where agent_id=(select agent_id from _sid_lineage_ids where label='root');
|
|
34
|
+
update agent_board.session_identities set depth=depth where agent_id=(select agent_id from _sid_lineage_ids where label='child');
|
|
35
|
+
elsif p_mode='cycle' then
|
|
36
|
+
update agent_board.agents set parent_agent_id=(select agent_id from _sid_lineage_ids where label='grandchild')
|
|
37
|
+
where id=(select agent_id from _sid_lineage_ids where label='child');
|
|
38
|
+
end if;
|
|
39
|
+
set constraints all immediate;
|
|
40
|
+
raise exception 'expected lineage refusal but change succeeded' using errcode='P0001';
|
|
41
|
+
end $$;
|
|
42
|
+
select throws_ok($$select pg_temp.sid_lineage_bad('agent-update')$$,'23514','session identity lineage mismatch',
|
|
43
|
+
'EC-SIDSCHEMA-1-lineage-agent-update: agents lineage cannot diverge from its identity');
|
|
44
|
+
select throws_ok($$select pg_temp.sid_lineage_bad('missing-parent')$$,'23514','session identity lineage mismatch',
|
|
45
|
+
'EC-SIDSCHEMA-1-lineage-missing-parent: a missing identity parent is refused, never a NULL comparison');
|
|
46
|
+
select throws_ok($$select pg_temp.sid_lineage_bad('cycle')$$,'23514',null,
|
|
47
|
+
'EC-SIDSCHEMA-1-lineage-cycle: agent lineage cannot become cyclic');
|
|
48
|
+
select ok((select a.parent_agent_id=si.parent_agent_id and a.root_agent_id=si.root_agent_id from agent_board.agents a
|
|
49
|
+
join agent_board.session_identities si on si.agent_id=a.id where a.id=(select agent_id from _sid_lineage_ids where label='child')),
|
|
50
|
+
'EC-SIDSCHEMA-1-lineage-refusal-state: every refused mutation rolls back both lineages');
|
|
51
|
+
create temp table _sid_lineage_registered(agent_id uuid);
|
|
52
|
+
grant insert,select on _sid_lineage_registered to service_role;
|
|
53
|
+
select set_config('request.jwt.claim.role','service_role',true);
|
|
54
|
+
set local role service_role;
|
|
55
|
+
select lives_ok($$insert into _sid_lineage_registered select agent_id from agent_board.register_agent('system',
|
|
56
|
+
'sid-lineage-registered-' || gen_random_uuid(),'Registered lineage'); set constraints all immediate$$,
|
|
57
|
+
'EC-SIDSCHEMA-1-lineage-constructor: the real legacy registration path commits without a session lease');
|
|
58
|
+
reset role;
|
|
59
|
+
select is((select identity_kind || '/' || depth::text || '/' || (holder_secret_hash is null)::text || '/' ||
|
|
60
|
+
(approved_risky_digest is null)::text || '/' || (forked_from is null and readmitted_from is null and lease_alias_id is null)::text
|
|
61
|
+
from agent_board.session_identities where agent_id=(select agent_id from _sid_lineage_registered)),
|
|
62
|
+
'legacy/0/true/true/true','EC-SIDSCHEMA-1-lineage-constructor-state: future legacy principals have an identity but no implicit lease, approval or continuity links');
|
|
63
|
+
select finish();
|
|
64
|
+
rollback;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
-- Enter the installed service boundary with a forwarded verified holder subject
|
|
2
|
+
-- and epoch. Authority changes use real policy/board RPCs, never a declared flag.
|
|
3
|
+
create extension if not exists pgtap with schema extensions;
|
|
4
|
+
set search_path = extensions, public, agent_board;
|
|
5
|
+
begin;
|
|
6
|
+
grant usage on schema extensions to authenticated, service_role;
|
|
7
|
+
select plan(41);
|
|
8
|
+
create temp table _sid_link_root(root_id uuid, root_auth uuid, owner_auth uuid, room_a uuid, room_b uuid, room_c uuid, suffix text);
|
|
9
|
+
create temp table _sid_link_children(label text primary key, agent_id uuid, key_id uuid, auth_id uuid);
|
|
10
|
+
create temp table _sid_link_tokens(label text, epoch bigint);
|
|
11
|
+
create temp table _sid_link_watermark(seq bigint);
|
|
12
|
+
do $$
|
|
13
|
+
declare v_root uuid := gen_random_uuid(); v_auth uuid; v_owner uuid;
|
|
14
|
+
v_a uuid := gen_random_uuid(); v_b uuid := gen_random_uuid(); v_c uuid := gen_random_uuid(); v_board uuid;
|
|
15
|
+
begin
|
|
16
|
+
v_owner := public.ensure_github_user('sid-link-owner-' || v_root, 'sid-link-owner-' || v_root || '@example.test', 'sid-link-owner-' || v_root, 'SID owner');
|
|
17
|
+
v_auth := public.ensure_github_user('sid-link-root-' || v_root, 'sid-link-root-' || v_root || '@example.test', 'sid-link-root-' || v_root, 'SID root');
|
|
18
|
+
insert into agent_board.agents(agent_type, handle, display_name, status, auth_user_id)
|
|
19
|
+
values ('human', 'sid-link-owner-' || v_root, 'SID owner', 'active', v_owner);
|
|
20
|
+
insert into agent_board.agents(id, agent_type, handle, display_name, status, auth_user_id, owner_user_id)
|
|
21
|
+
values (v_root, 'agent', 'sid-link-root-' || v_root, 'SID root', 'active', v_auth, v_owner);
|
|
22
|
+
insert into agent_board.session_identities(agent_id, root_agent_id, depth, approved_risky_digest, current_risky_digest)
|
|
23
|
+
values (v_root, v_root, 0, 'approved', 'approved');
|
|
24
|
+
foreach v_board in array array[v_a, v_b, v_c] loop
|
|
25
|
+
insert into agent_board.boards(id, slug, name, created_by_agent_id)
|
|
26
|
+
values (v_board, 'sid-link-' || v_board, 'SID link room', v_root);
|
|
27
|
+
insert into agent_board.board_memberships(board_id, agent_id, membership_state, role)
|
|
28
|
+
values (v_board, v_root, 'joined', 'owner');
|
|
29
|
+
insert into agent_board.board_memberships(board_id, agent_id, membership_state, role)
|
|
30
|
+
select v_board, id, 'joined', 'owner' from agent_board.agents where auth_user_id = v_owner;
|
|
31
|
+
end loop;
|
|
32
|
+
insert into _sid_link_root values (v_root, v_auth, v_owner, v_a, v_b, v_c, substr(v_root::text, 1, 8));
|
|
33
|
+
end;
|
|
34
|
+
$$;
|
|
35
|
+
grant select on _sid_link_root, _sid_link_children, _sid_link_tokens to service_role;
|
|
36
|
+
grant insert on _sid_link_children, _sid_link_tokens to service_role;
|
|
37
|
+
create function pg_temp.sid_link_mint(p_label text, p_parent text default null)
|
|
38
|
+
returns void language plpgsql as $$
|
|
39
|
+
declare v_root record; v_parent uuid; v_result record;
|
|
40
|
+
begin
|
|
41
|
+
select * into v_root from _sid_link_root;
|
|
42
|
+
v_parent := coalesce((select agent_id from _sid_link_children where label = p_parent), v_root.root_id);
|
|
43
|
+
select * into v_result from agent_board.mint_delegated_child(v_parent, 'sid-link-' || p_label || '-' || v_root.suffix,
|
|
44
|
+
'["read","write"]', public.test_pubkey(v_root.suffix || p_label), interval '8 hours', false, 'claude', 'sid-link-' || p_label,
|
|
45
|
+
case when p_parent is null then null else '0' end,
|
|
46
|
+
jsonb_build_array(jsonb_build_object('board_id', v_root.room_a, 'role', 'admin'), jsonb_build_object('board_id', v_root.room_b, 'role', 'admin')),
|
|
47
|
+
'{"resolution":"resolved","risky_digest":"approved"}');
|
|
48
|
+
insert into _sid_link_children select p_label, v_result.child_agent_id, v_result.signing_key_id, a.auth_user_id
|
|
49
|
+
from agent_board.agents a where a.id = v_result.child_agent_id;
|
|
50
|
+
end;
|
|
51
|
+
$$;
|
|
52
|
+
create function pg_temp.sid_link_claim(p_label text)
|
|
53
|
+
returns void language plpgsql as $$
|
|
54
|
+
declare v_child record; v_challenge record;
|
|
55
|
+
begin
|
|
56
|
+
select * into v_child from _sid_link_children where label = p_label;
|
|
57
|
+
select * into v_challenge from agent_board.issue_signing_key_challenge(v_child.agent_id, 'delegated-ephemeral');
|
|
58
|
+
insert into _sid_link_tokens select p_label, m.identity_lease_epoch
|
|
59
|
+
from agent_board.mint_fleet_agent_session(v_challenge.challenge_id, split_part(v_challenge.challenge, ':', 3),
|
|
60
|
+
v_child.key_id, v_child.agent_id, 900, null, 'sid-link-' || p_label) m;
|
|
61
|
+
end;
|
|
62
|
+
$$;
|
|
63
|
+
create function pg_temp.sid_link(p_actor text, p_target text, p_alias text)
|
|
64
|
+
returns void language plpgsql as $$
|
|
65
|
+
begin
|
|
66
|
+
perform set_config('request.jwt.claim.sub', (select auth_id::text from _sid_link_children where label = p_actor), true);
|
|
67
|
+
perform set_config('request.jwt.claim.identity_lease_epoch', (select epoch::text from _sid_link_tokens where label = p_actor), true);
|
|
68
|
+
perform agent_board.link_session_alias((select agent_id from _sid_link_children where label = p_target),
|
|
69
|
+
(select epoch::text from _sid_link_tokens where label = p_actor), p_alias);
|
|
70
|
+
end;
|
|
71
|
+
$$;
|
|
72
|
+
create function pg_temp.sid_link_refuse(p_call text)
|
|
73
|
+
returns void language plpgsql as $$
|
|
74
|
+
begin
|
|
75
|
+
execute p_call;
|
|
76
|
+
raise exception 'expected refusal but RPC succeeded' using errcode = 'P0001';
|
|
77
|
+
end;
|
|
78
|
+
$$;
|
|
79
|
+
grant execute on function pg_temp.sid_link_mint(text,text), pg_temp.sid_link_claim(text), pg_temp.sid_link(text,text,text), pg_temp.sid_link_refuse(text) to service_role;
|
|
80
|
+
select set_config('request.jwt.claim.role', 'service_role', true);
|
|
81
|
+
set local role service_role;
|
|
82
|
+
select lives_ok($q$select agent_board.upsert_delegation_policy((select root_id from _sid_link_root), 'root_minted', '["read","write"]', 200, interval '7 days', true)$q$,
|
|
83
|
+
'EC-SIDSCHEMA-3-policy: real root policy permits the authority fixture');
|
|
84
|
+
reset role;
|
|
85
|
+
select is((select count(*) from agent_board.delegation_policies where root_agent_id = (select root_id from _sid_link_root)), 1::bigint,
|
|
86
|
+
'EC-SIDSCHEMA-3-policy-state: one policy exists');
|
|
87
|
+
set local role service_role;
|
|
88
|
+
select lives_ok($q$select pg_temp.sid_link_mint('parent'); select pg_temp.sid_link_claim('parent'); select pg_temp.sid_link_mint('child', 'parent'); select pg_temp.sid_link_claim('child'); select pg_temp.sid_link_mint('sibling'); select pg_temp.sid_link_claim('sibling')$q$,
|
|
89
|
+
'EC-SIDSCHEMA-3-fixture: real parent, descendant and unrelated sibling hold proved leases');
|
|
90
|
+
reset role;
|
|
91
|
+
select is((select count(*) from _sid_link_tokens where epoch = 0), 3::bigint, 'EC-SIDSCHEMA-3-fixture-state: all three holder contexts exist');
|
|
92
|
+
set local role service_role;
|
|
93
|
+
select throws_ok(format('select pg_temp.sid_link_refuse(%L)', $q$select pg_temp.sid_link('sibling', 'parent', 'unrelated')$q$),
|
|
94
|
+
'42501', 'identity_lease_not_held', 'EC-SIDSCHEMA-3-unrelated: another identity credential cannot link this identity');
|
|
95
|
+
reset role;
|
|
96
|
+
select is((select count(*) from agent_board.session_id_aliases where root_agent_id = (select root_id from _sid_link_root) and session_id = 'unrelated'), 0::bigint,
|
|
97
|
+
'EC-SIDSCHEMA-3-unrelated-state: refusal creates no alias');
|
|
98
|
+
set local role service_role;
|
|
99
|
+
select lives_ok($q$select pg_temp.sid_link('parent', 'parent', 'before-shrink')$q$, 'EC-SIDSCHEMA-3-link-control: the actual holder links under unchanged authority');
|
|
100
|
+
reset role;
|
|
101
|
+
select is((select count(*) from agent_board.session_id_aliases where identity_agent_id = (select agent_id from _sid_link_children where label = 'parent')), 2::bigint,
|
|
102
|
+
'EC-SIDSCHEMA-3-link-state: holder creates exactly one new alias');
|
|
103
|
+
set local role service_role;
|
|
104
|
+
select lives_ok($q$select agent_board.upsert_delegation_policy((select root_id from _sid_link_root), 'root_minted', '["read"]', 200, interval '7 days', true);
|
|
105
|
+
select agent_board.grant_board_role((select room_a from _sid_link_root), (select root_id from _sid_link_root), 'observer', (select root_id from _sid_link_root));
|
|
106
|
+
select agent_board._upsert_board_membership_state((select room_b from _sid_link_root), (select root_id from _sid_link_root), 'left')$q$,
|
|
107
|
+
'EC-SIDSCHEMA-3-parent-shrink-control: real authority paths narrow policy, role and room');
|
|
108
|
+
reset role;
|
|
109
|
+
select is((select allowed_scopes from agent_board.delegation_policies where root_agent_id = (select root_id from _sid_link_root)), '["read"]'::jsonb,
|
|
110
|
+
'EC-SIDSCHEMA-3-parent-shrink-state: current policy is narrower');
|
|
111
|
+
insert into _sid_link_watermark select coalesce(max(seq), 0) from agent_board.board_membership_events;
|
|
112
|
+
set local role service_role;
|
|
113
|
+
select lives_ok($q$select pg_temp.sid_link('parent', 'parent', 'after-shrink')$q$, 'EC-SIDSCHEMA-3-shrink-control: holder link rechecks changed authority');
|
|
114
|
+
reset role;
|
|
115
|
+
select is((select granted_scopes from agent_board.delegation_grants where child_agent_id = (select agent_id from _sid_link_children where label = 'parent')), '["read"]'::jsonb,
|
|
116
|
+
'EC-SIDSCHEMA-3-shrink-scopes: link intersects original scopes with current parent authority');
|
|
117
|
+
select is((select role from agent_board.board_memberships where board_id = (select room_a from _sid_link_root) and agent_id = (select agent_id from _sid_link_children where label = 'parent')), 'observer',
|
|
118
|
+
'EC-SIDSCHEMA-3-shrink-role: link downgrades the identity role');
|
|
119
|
+
select is((select membership_state from agent_board.board_memberships where board_id = (select room_b from _sid_link_root) and agent_id = (select agent_id from _sid_link_children where label = 'parent')), 'removed',
|
|
120
|
+
'EC-SIDSCHEMA-3-shrink-room: link revokes a room the parent lost');
|
|
121
|
+
select is((select granted_scopes from agent_board.delegation_grants where child_agent_id = (select agent_id from _sid_link_children where label = 'child')), '["read"]'::jsonb,
|
|
122
|
+
'EC-SIDSCHEMA-3-descendant-scopes: narrowing cascades to depth two');
|
|
123
|
+
select is((select role from agent_board.board_memberships where board_id = (select room_a from _sid_link_root) and agent_id = (select agent_id from _sid_link_children where label = 'child')), 'observer',
|
|
124
|
+
'EC-SIDSCHEMA-3-descendant-role: descendant role narrows with its parent');
|
|
125
|
+
select is((select membership_state from agent_board.board_memberships where board_id = (select room_b from _sid_link_root) and agent_id = (select agent_id from _sid_link_children where label = 'child')), 'removed',
|
|
126
|
+
'EC-SIDSCHEMA-3-descendant-room: descendant loses the revoked room');
|
|
127
|
+
select is((select count(*) from agent_board.board_membership_events where seq > (select seq from _sid_link_watermark)
|
|
128
|
+
and board_id = (select room_b from _sid_link_root) and agent_id in (select agent_id from _sid_link_children where label in ('parent','child'))
|
|
129
|
+
and provenance = 'operator-designated' and actor_agent_id is null and result_state = 'removed'), 2::bigint,
|
|
130
|
+
'EC-SIDSCHEMA-3-consent: consent sink derives operator provenance for each removal from the service session');
|
|
131
|
+
select is((select granted_scopes from agent_board.delegation_grants where child_agent_id = (select agent_id from _sid_link_children where label = 'sibling')), '["read","write"]'::jsonb,
|
|
132
|
+
'EC-SIDSCHEMA-3-unrelated-preserved: linking one lineage never changes a sibling');
|
|
133
|
+
select is((select count(*) from agent_board.board_memberships where board_id = (select room_c from _sid_link_root) and agent_id in (select agent_id from _sid_link_children)), 0::bigint,
|
|
134
|
+
'EC-SIDSCHEMA-3-no-default-room: link does not grant another parent room');
|
|
135
|
+
-- Operator can widen the parent later, but this identity has permanently lost authority.
|
|
136
|
+
set local role service_role;
|
|
137
|
+
select lives_ok($q$select agent_board.upsert_delegation_policy((select root_id from _sid_link_root), 'root_minted', '["read","write"]', 200, interval '7 days', true);
|
|
138
|
+
select agent_board.grant_board_role((select room_a from _sid_link_root), (select root_id from _sid_link_root), 'owner',
|
|
139
|
+
(select id from agent_board.agents where auth_user_id = (select owner_auth from _sid_link_root)));
|
|
140
|
+
select agent_board._upsert_board_membership_state((select room_b from _sid_link_root), (select root_id from _sid_link_root), 'joined')$q$,
|
|
141
|
+
'EC-SIDSCHEMA-3-widen-control: current parent authority widens through its actual operators');
|
|
142
|
+
reset role;
|
|
143
|
+
select is((select allowed_scopes from agent_board.delegation_policies where root_agent_id = (select root_id from _sid_link_root)), '["read","write"]'::jsonb,
|
|
144
|
+
'EC-SIDSCHEMA-3-widen-state: parent regained scopes');
|
|
145
|
+
set local role service_role;
|
|
146
|
+
select lives_ok($q$select pg_temp.sid_link('parent', 'parent', 'after-widen')$q$, 'EC-SIDSCHEMA-3-no-regrant-control: holder links again after widening');
|
|
147
|
+
reset role;
|
|
148
|
+
select is((select granted_scopes from agent_board.delegation_grants where child_agent_id = (select agent_id from _sid_link_children where label = 'parent')), '["read"]'::jsonb,
|
|
149
|
+
'EC-SIDSCHEMA-3-no-regrant-scopes: widened parent cannot restore removed scopes');
|
|
150
|
+
select is((select role from agent_board.board_memberships where board_id = (select room_a from _sid_link_root) and agent_id = (select agent_id from _sid_link_children where label = 'parent')), 'observer',
|
|
151
|
+
'EC-SIDSCHEMA-3-no-regrant-role: widened parent cannot restore a higher role');
|
|
152
|
+
select is((select membership_state from agent_board.board_memberships where board_id = (select room_b from _sid_link_root) and agent_id = (select agent_id from _sid_link_children where label = 'parent')), 'removed',
|
|
153
|
+
'EC-SIDSCHEMA-3-no-regrant-room: widened parent cannot restore a removed room');
|
|
154
|
+
select is((select granted_scopes from agent_board.delegation_grants where child_agent_id = (select agent_id from _sid_link_children where label = 'child')), '["read"]'::jsonb,
|
|
155
|
+
'EC-SIDSCHEMA-3-no-regrant-descendant: descendants retain their narrowed scope');
|
|
156
|
+
select is((select count(*) from agent_board.session_id_aliases where identity_agent_id = (select agent_id from _sid_link_children where label = 'parent')), 4::bigint,
|
|
157
|
+
'EC-SIDSCHEMA-3-final-aliases: all three legitimate links map to one persistent identity');
|
|
158
|
+
select is((select credential_epoch from agent_board.session_identities where agent_id = (select agent_id from _sid_link_children where label = 'parent')), 0::bigint,
|
|
159
|
+
'EC-SIDSCHEMA-3-stable-identity: authority shrink never rotates the live holder epoch');
|
|
160
|
+
select is((select count(*) from agent_board.board_membership_events where seq > (select seq from _sid_link_watermark)
|
|
161
|
+
and board_id = (select room_b from _sid_link_root) and agent_id in (select agent_id from _sid_link_children where label in ('parent','child'))), 2::bigint,
|
|
162
|
+
'EC-SIDSCHEMA-3-append-only: repeated links neither rewrite removal events nor emit phantom removals');
|
|
163
|
+
select ok((select agent_board_test.stripped_body('agent_board.link_session_alias(uuid,text,text)'::regprocedure)
|
|
164
|
+
~ agent_board_test.row_lock_re('session_identities')),
|
|
165
|
+
'EC-SIDSCHEMA-3-identity-lock: deployed linker takes identity row locks as well as the root mutex');
|
|
166
|
+
select ok((select agent_board_test.stripped_body('agent_board.link_session_alias(uuid,text,text)'::regprocedure)
|
|
167
|
+
~ agent_board_test.row_lock_re('session_id_aliases')),
|
|
168
|
+
'EC-SIDSCHEMA-3-alias-lock: deployed linker locks an existing alias before rebinding it');
|
|
169
|
+
insert into agent_board.fleet_revocation_events(subject_kind, subject_id, agent_id, revoked_by_agent_id, revocation_reason)
|
|
170
|
+
select 'agent', root_id, root_id, root_id, 'sid-parent-ledger-control' from _sid_link_root;
|
|
171
|
+
select is((select status from agent_board.agents where id = (select root_id from _sid_link_root)), 'active',
|
|
172
|
+
'EC-SIDSCHEMA-3-ledger-control: ledger-only parent revocation leaves the principal status active');
|
|
173
|
+
set local role service_role;
|
|
174
|
+
select throws_ok(format('select pg_temp.sid_link_refuse(%L)', $q$select pg_temp.sid_link('parent', 'parent', 'ledger-parent')$q$),
|
|
175
|
+
'42501', 'identity_parent_inactive', 'EC-SIDSCHEMA-3-ledger-ancestor: link refuses a ledger-revoked ancestor even while status and grants are active');
|
|
176
|
+
select throws_ok(format('select pg_temp.sid_link_refuse(%L)', $q$select agent_board.mint_delegated_child((select root_id from _sid_link_root), 'sid-ledger-legacy-' || (select suffix from _sid_link_root), '["read"]', public.test_pubkey('sid-ledger-legacy'))$q$),
|
|
177
|
+
'42501', 'fleet_revoked: mint_delegated_child is not permitted for a fleet-revoked agent',
|
|
178
|
+
'EC-SIDSCHEMA-2-ledger-legacy: legacy mint cannot bypass the parent ledger through its no-session path');
|
|
179
|
+
reset role;
|
|
180
|
+
select is((select count(*) from agent_board.session_id_aliases where root_agent_id = (select root_id from _sid_link_root) and session_id = 'ledger-parent'), 0::bigint,
|
|
181
|
+
'EC-SIDSCHEMA-3-ledger-ancestor-state: parent refusal creates no alias');
|
|
182
|
+
select is((select count(*) from agent_board.agents where handle = 'sid-ledger-legacy-' || (select suffix from _sid_link_root)), 0::bigint,
|
|
183
|
+
'EC-SIDSCHEMA-2-ledger-legacy-state: failed legacy mint leaves no principal');
|
|
184
|
+
delete from agent_board.fleet_revocation_events where revocation_reason = 'sid-parent-ledger-control'
|
|
185
|
+
and subject_id = (select root_id from _sid_link_root);
|
|
186
|
+
insert into agent_board.fleet_revocation_events(subject_kind, subject_id, agent_id, revoked_by_agent_id, revocation_reason)
|
|
187
|
+
select 'agent', agent_id, agent_id, (select root_id from _sid_link_root), 'sid-holder-ledger-control'
|
|
188
|
+
from _sid_link_children where label = 'parent';
|
|
189
|
+
select is((select status from agent_board.agents where id = (select agent_id from _sid_link_children where label = 'parent')), 'active',
|
|
190
|
+
'EC-SIDSCHEMA-3-ledger-holder-control: ledger-only holder revocation does not change its active status');
|
|
191
|
+
set local role service_role;
|
|
192
|
+
select throws_ok(format('select pg_temp.sid_link_refuse(%L)', $q$select pg_temp.sid_link('parent', 'parent', 'ledger-holder')$q$),
|
|
193
|
+
'42501', 'fleet_revoked: link_session_alias is not permitted for a fleet-revoked agent',
|
|
194
|
+
'EC-SIDSCHEMA-3-ledger-holder: verified holder cannot bind an alias after permanent ledger revocation');
|
|
195
|
+
reset role;
|
|
196
|
+
select is((select count(*) from agent_board.session_id_aliases where root_agent_id = (select root_id from _sid_link_root) and session_id = 'ledger-holder'), 0::bigint,
|
|
197
|
+
'EC-SIDSCHEMA-3-ledger-holder-state: late ledger gate rolls back the alias write');
|
|
198
|
+
select is((select role from agent_board.board_memberships where board_id = (select room_a from _sid_link_root) and agent_id = (select agent_id from _sid_link_children where label = 'parent')), 'observer',
|
|
199
|
+
'EC-SIDSCHEMA-3-ledger-authority-state: refusal leaves narrowed membership authority unchanged');
|
|
200
|
+
select finish();
|
|
201
|
+
rollback;
|