@consiliency/agent-board-schema 1.60.0 → 1.64.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 +86 -0
- package/fixtures/already-applied.json +16 -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/migrations/20261014000200_agent_board_last_owner.sql +239 -0
- package/package.json +1 -1
- package/schema-manifest.json +39 -3
- package/src/tooling.test.mjs +10 -2
- package/tests/agent_board_atomic_fleet_onboarding.test.sql +15 -4
- package/tests/agent_board_atomic_fleet_onboarding_runtime.test.sql +7 -0
- 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 +77 -16
- package/tests/agent_board_grant_target_role.test.sql +1118 -0
- package/tests/agent_board_hosted_delegates.test.sql +5 -3
- package/tests/agent_board_invite_no_downgrade.test.sql +15 -11
- package/tests/agent_board_ledger_authority.test.sql +77 -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_role_guard.test.sql +319 -0
- package/tests/agent_board_rolereset.test.sql +25 -29
- package/tests/agent_board_roomdeleg.test.sql +8 -0
- package/tests/agent_board_roomdeleg_lane_b.test.sql +6 -6
- package/tests/agent_board_roomown.test.sql +3 -1
- package/tests/agent_board_rowid.test.sql +7 -2
- 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 +227 -2
- package/tests/catalog/p_metadata_parameters.json +102 -2
- package/tests/isolation/expected/grant_target_role_race.out +348 -0
- package/tests/isolation/expected/last_owner_departure.out +562 -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/atomic_fleet_authority_races.spec +8 -3
- package/tests/isolation/specs/atomic_fleet_binding_revoke.spec +0 -3
- package/tests/isolation/specs/atomic_fleet_board_archive.spec +0 -3
- package/tests/isolation/specs/atomic_fleet_human_fresh_revoke.spec +0 -3
- package/tests/isolation/specs/atomic_fleet_identity_disposition.spec +0 -3
- package/tests/isolation/specs/atomic_fleet_lifecycle_races.spec +0 -3
- package/tests/isolation/specs/atomic_fleet_subtree_lock_order.spec +0 -3
- package/tests/isolation/specs/ceiling_setter_lock_before_upsert.spec +0 -3
- package/tests/isolation/specs/claimpolicy_acquire_revert_race.spec +0 -2
- package/tests/isolation/specs/claimpolicy_board_scope.spec +0 -2
- package/tests/isolation/specs/claimpolicy_claimrow_lock_order.spec +0 -2
- package/tests/isolation/specs/claimpolicy_isolation_refusal.spec +0 -2
- package/tests/isolation/specs/claimpolicy_renew_revert_race.spec +0 -2
- package/tests/isolation/specs/claimpolicy_writer_keyshare.spec +0 -2
- package/tests/isolation/specs/consent_approval_lock.spec +0 -1
- package/tests/isolation/specs/consent_emit_actual_state.spec +0 -1
- package/tests/isolation/specs/grant_target_role_race.spec +206 -0
- package/tests/isolation/specs/hosted_delegate_add_tombstone_race.spec +0 -1
- package/tests/isolation/specs/hosted_delegate_tombstone_revoke_race.spec +0 -1
- package/tests/isolation/specs/hosted_delegate_withdraw_evict_race.spec +0 -1
- package/tests/isolation/specs/last_owner_departure.spec +137 -0
- package/tests/isolation/specs/ledger_authority_revoke.spec +0 -3
- package/tests/isolation/specs/onboarding_ceiling_lock_order.spec +0 -3
- package/tests/isolation/specs/outbox_wake_ordering.spec +0 -1
- package/tests/isolation/specs/session_identity_limits.spec +81 -0
- package/tests/isolation/specs/session_identity_links.spec +82 -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 +107 -0
- package/tests/isolation/specs/signed_grant_onboarding_deadlock.spec +0 -1
- package/tests/isolation/specs/signer_lifecycle_revoke.spec +0 -1
- package/tests/seeds/20261011000100_agent_board_session_identities.sql.pre.sql +47 -0
|
@@ -0,0 +1,235 @@
|
|
|
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 authenticated, service_role;
|
|
5
|
+
select plan(56);
|
|
6
|
+
|
|
7
|
+
create temp table _sid_fixture(label text primary key, root_id uuid, agent_id uuid, key_id uuid, auth_id uuid);
|
|
8
|
+
create temp table _sid_results(label text, session_id text, expires_at timestamptz, epoch bigint, secret text);
|
|
9
|
+
do $$
|
|
10
|
+
declare v_label text; v_root uuid; v_agent uuid; v_key uuid; v_auth uuid; v_alias uuid;
|
|
11
|
+
begin
|
|
12
|
+
foreach v_label in array array['codex', 'claude', 'legacy'] loop
|
|
13
|
+
v_root := gen_random_uuid(); v_agent := gen_random_uuid(); v_key := gen_random_uuid();
|
|
14
|
+
v_auth := public.ensure_github_user('sid-' || v_agent, 'sid-' || v_agent || '@example.test', 'sid-' || v_agent, 'SID');
|
|
15
|
+
insert into agent_board.agents(id, agent_type, handle, display_name, status)
|
|
16
|
+
values (v_root, 'agent', 'sid-root-' || v_root, 'SID root', 'active');
|
|
17
|
+
insert into agent_board.agents(id, agent_type, handle, display_name, status, auth_user_id, parent_agent_id, root_agent_id)
|
|
18
|
+
values (v_agent, 'agent', 'sid-' || v_agent, 'SID', 'active', v_auth, v_root, v_root);
|
|
19
|
+
insert into agent_board.agent_signing_keys(id, agent_id, key_id, key_version, public_key, public_key_format, fingerprint, status, proof_state)
|
|
20
|
+
values (v_key, v_agent, 'primary', 1, public.test_pubkey(v_key::text), 'raw_ed25519_base64url', public.test_key_fingerprint(v_key::text), 'active', 'proven');
|
|
21
|
+
insert into agent_board.session_identities(agent_id, root_agent_id, depth)
|
|
22
|
+
values (v_root, v_root, 0);
|
|
23
|
+
insert into agent_board.session_identities(agent_id, parent_agent_id, root_agent_id, depth, identity_kind, harness, approved_risky_digest, current_risky_digest)
|
|
24
|
+
values (v_agent, v_root, v_root, 1, case when v_label = 'legacy' then 'legacy' else 'session' end,
|
|
25
|
+
case when v_label = 'legacy' then null else v_label end, 'approved', 'approved');
|
|
26
|
+
if v_label <> 'legacy' then
|
|
27
|
+
insert into agent_board.session_id_aliases(identity_agent_id, root_agent_id, harness, session_id)
|
|
28
|
+
values (v_agent, v_root, v_label, 'bound-' || v_label) returning id into v_alias;
|
|
29
|
+
update agent_board.session_identities set lease_alias_id = v_alias where agent_id = v_agent;
|
|
30
|
+
end if;
|
|
31
|
+
insert into _sid_fixture values (v_label, v_root, v_agent, v_key, v_auth);
|
|
32
|
+
end loop;
|
|
33
|
+
end;
|
|
34
|
+
$$;
|
|
35
|
+
grant select on _sid_fixture to service_role, authenticated;
|
|
36
|
+
grant select, insert on _sid_results to service_role;
|
|
37
|
+
|
|
38
|
+
-- INVOKER, so the actual RPC runs as service_role rather than a fixture definer.
|
|
39
|
+
create function pg_temp.sid_mint(p_label text, p_result text, p_secret text default null, p_alias text default null)
|
|
40
|
+
returns void language plpgsql as $$
|
|
41
|
+
declare v_fx record; v_issued record;
|
|
42
|
+
begin
|
|
43
|
+
select * into v_fx from _sid_fixture where label = p_label;
|
|
44
|
+
select * into v_issued from agent_board.issue_signing_key_challenge(v_fx.agent_id, 'primary');
|
|
45
|
+
insert into _sid_results
|
|
46
|
+
select p_result, m.session_id, m.expires_at, m.identity_lease_epoch, m.holder_secret
|
|
47
|
+
from agent_board.mint_fleet_agent_session(v_issued.challenge_id, split_part(v_issued.challenge, ':', 3), v_fx.key_id, v_fx.agent_id, 900, p_secret, p_alias) m;
|
|
48
|
+
end;
|
|
49
|
+
$$;
|
|
50
|
+
grant execute on function pg_temp.sid_mint(text, text, text, text) to service_role;
|
|
51
|
+
-- A successful refusal probe raises a distinct sentinel, rolling its writes
|
|
52
|
+
-- back in throws_ok's subtransaction instead of poisoning independent cases.
|
|
53
|
+
-- Only the RPC's original refusal can pass the assertion.
|
|
54
|
+
create function pg_temp.sid_refuse(p_call text)
|
|
55
|
+
returns void language plpgsql as $$
|
|
56
|
+
begin
|
|
57
|
+
execute p_call;
|
|
58
|
+
raise exception 'expected refusal but RPC succeeded' using errcode = 'P0001';
|
|
59
|
+
end;
|
|
60
|
+
$$;
|
|
61
|
+
grant execute on function pg_temp.sid_refuse(text) to authenticated, service_role;
|
|
62
|
+
select set_config('request.jwt.claim.role', 'authenticated', true);
|
|
63
|
+
select set_config('request.jwt.claim.sub', (select auth_id::text from _sid_fixture where label = 'codex'), true);
|
|
64
|
+
select set_config('request.jwt.claims', '{}', true);
|
|
65
|
+
select set_config('request.jwt.claim.identity_lease_epoch', '', true);
|
|
66
|
+
set local role authenticated;
|
|
67
|
+
select is(agent_board.current_agent_id(), null::uuid,
|
|
68
|
+
'EC-SIDSCHEMA-1-epoch-unclaimed: immutable session kind fences even before the first lease');
|
|
69
|
+
reset role;
|
|
70
|
+
set local role service_role;
|
|
71
|
+
select set_config('request.jwt.claim.role', 'service_role', true);
|
|
72
|
+
select throws_ok(format('select pg_temp.sid_refuse(%L)', $q$select pg_temp.sid_mint('codex', 'wrong-first', null, 'other')$q$),
|
|
73
|
+
'42501', 'identity_continuity_refused', 'EC-SIDSCHEMA-1-first-id: unclaimed claim must equal its reserved alias');
|
|
74
|
+
select lives_ok($q$select pg_temp.sid_mint('codex', 'first', null, 'bound-codex')$q$,
|
|
75
|
+
'EC-SIDSCHEMA-1-first: a non-Claude initial claim succeeds without a holder secret');
|
|
76
|
+
reset role;
|
|
77
|
+
select is((select count(*) from _sid_results where label = 'first'), 1::bigint,
|
|
78
|
+
'EC-SIDSCHEMA-1-first-state: the first claim returns one result');
|
|
79
|
+
select is((select epoch from _sid_results where label = 'first'), 0::bigint,
|
|
80
|
+
'EC-SIDSCHEMA-1-first-epoch: first claim keeps epoch zero');
|
|
81
|
+
select ok((select secret ~ '^[0-9a-f]{64}$' from _sid_results where label = 'first'),
|
|
82
|
+
'EC-SIDSCHEMA-1-first-secret: first claim returns 256 bits of holder secret');
|
|
83
|
+
select is((select si.holder_secret_hash from agent_board.session_identities si join _sid_fixture f on si.agent_id = f.agent_id where f.label = 'codex'),
|
|
84
|
+
(select digest(secret, 'sha256') from _sid_results where label = 'first'),
|
|
85
|
+
'EC-SIDSCHEMA-1-secret-hash: only the sha256 is stored');
|
|
86
|
+
select is((select si.last_heartbeat from agent_board.session_identities si join _sid_fixture f on si.agent_id = f.agent_id where f.label = 'codex'), now(),
|
|
87
|
+
'EC-SIDSCHEMA-1-first-heartbeat: claim starts its heartbeat');
|
|
88
|
+
select ok(not exists (select 1 from agent_board.session_identities si cross join _sid_results r
|
|
89
|
+
where r.label = 'first' and to_jsonb(si)::text like '%' || r.secret || '%'),
|
|
90
|
+
'EC-SIDSCHEMA-1-secret-identity: no identity column stores the plaintext secret');
|
|
91
|
+
select ok(not exists (select 1 from agent_board.nonfleet_sessions s cross join _sid_results r
|
|
92
|
+
where r.label = 'first' and to_jsonb(s)::text like '%' || r.secret || '%'),
|
|
93
|
+
'EC-SIDSCHEMA-1-secret-session: no token session column stores the plaintext secret');
|
|
94
|
+
|
|
95
|
+
set local role service_role;
|
|
96
|
+
select throws_ok(format('select pg_temp.sid_refuse(%L)', $q$select pg_temp.sid_mint('codex', 'wrong', 'former-holder', 'bound-codex')$q$),
|
|
97
|
+
'42501', 'identity_lease_not_held', 'EC-SIDSCHEMA-1-live-wrong: a wrong secret cannot claim the live lease');
|
|
98
|
+
select throws_ok(format('select pg_temp.sid_refuse(%L)', $q$select pg_temp.sid_mint('codex', 'duplicate', null, 'bound-codex')$q$),
|
|
99
|
+
'42501', 'duplicate_live_session', 'EC-SIDSCHEMA-1-live-duplicate: no secret with the identical bound id refuses duplicate');
|
|
100
|
+
select throws_ok(format('select pg_temp.sid_refuse(%L)', $q$select pg_temp.sid_mint('codex', 'other', null, 'other')$q$),
|
|
101
|
+
'42501', 'identity_lease_live', 'EC-SIDSCHEMA-1-live-other: no secret with another id refuses live');
|
|
102
|
+
select throws_ok(format('select pg_temp.sid_refuse(%L)', $q$select pg_temp.sid_mint('codex', 'absent', null, null)$q$),
|
|
103
|
+
'42501', 'identity_lease_live', 'EC-SIDSCHEMA-1-live-absent: no secret and no id refuses live');
|
|
104
|
+
select lives_ok($q$select pg_temp.sid_mint('codex', 'holder', (select secret from _sid_results where label = 'first'), null)$q$,
|
|
105
|
+
'EC-SIDSCHEMA-1-live-holder: matching holder secret mints without rotating');
|
|
106
|
+
select throws_ok((select format('select pg_temp.sid_refuse(%L)', format('select agent_board.link_session_alias(%L, %L, %L)', agent_id, '0', 'new-codex-id')) from _sid_fixture where label = 'codex'),
|
|
107
|
+
'42501', 'identity_continuity_refused', 'EC-SIDSCHEMA-1-link-nonclaude: live non-Claude holder cannot bind a new id');
|
|
108
|
+
select throws_ok((select format('select pg_temp.sid_refuse(%L)', format('select agent_board.link_session_alias(%L, %L, %L)', agent_id, '1', 'bound-codex')) from _sid_fixture where label = 'codex'),
|
|
109
|
+
'42501', 'identity_lease_not_held', 'EC-SIDSCHEMA-1-link-epoch: a stale forwarded epoch cannot link');
|
|
110
|
+
reset role;
|
|
111
|
+
select is((select count(*) from agent_board.session_id_aliases where identity_agent_id = (select agent_id from _sid_fixture where label = 'codex')), 1::bigint,
|
|
112
|
+
'EC-SIDSCHEMA-1-link-refusal-state: refused links bind no aliases');
|
|
113
|
+
select is((select epoch from _sid_results where label = 'holder'), 0::bigint,
|
|
114
|
+
'EC-SIDSCHEMA-1-live-epoch: matching holder keeps its epoch');
|
|
115
|
+
select is((select secret from _sid_results where label = 'holder'), null::text,
|
|
116
|
+
'EC-SIDSCHEMA-1-live-secret: non-rotating mint never returns the secret');
|
|
117
|
+
select is((select count(*) from _sid_results), 2::bigint,
|
|
118
|
+
'EC-SIDSCHEMA-1-live-state: refusing attempts write no result');
|
|
119
|
+
|
|
120
|
+
select set_config('request.jwt.claim.role', 'authenticated', true);
|
|
121
|
+
select set_config('request.jwt.claim.sub', (select auth_id::text from _sid_fixture where label = 'codex'), true);
|
|
122
|
+
select set_config('request.jwt.claims', '{}', true);
|
|
123
|
+
select set_config('request.jwt.claim.identity_lease_epoch', '', true);
|
|
124
|
+
set local role authenticated;
|
|
125
|
+
select is(agent_board.current_agent_id(), null::uuid, 'EC-SIDSCHEMA-1-epoch-absent: a session identity requires an epoch');
|
|
126
|
+
select set_config('request.jwt.claim.identity_lease_epoch', 'garbage', true);
|
|
127
|
+
select is(agent_board.current_agent_id(), null::uuid, 'EC-SIDSCHEMA-1-epoch-malformed: malformed epoch denies without casting');
|
|
128
|
+
select set_config('request.jwt.claim.identity_lease_epoch', '0', true);
|
|
129
|
+
select is(agent_board.current_agent_id(), (select agent_id from _sid_fixture where label = 'codex'),
|
|
130
|
+
'EC-SIDSCHEMA-1-epoch-scalar: scalar JWT GUC resolves the current epoch');
|
|
131
|
+
select set_config('request.jwt.claim.identity_lease_epoch', '', true);
|
|
132
|
+
select set_config('request.jwt.claims', '{"identity_lease_epoch":"0"}', true);
|
|
133
|
+
select is(agent_board.current_agent_id(), (select agent_id from _sid_fixture where label = 'codex'),
|
|
134
|
+
'EC-SIDSCHEMA-1-epoch-json: Realtime JSON JWT GUC resolves the current epoch');
|
|
135
|
+
reset role;
|
|
136
|
+
update agent_board.session_identities set last_heartbeat = now() - interval '60 seconds'
|
|
137
|
+
where agent_id = (select agent_id from _sid_fixture where label = 'codex');
|
|
138
|
+
set local role authenticated;
|
|
139
|
+
select lives_ok($q$select agent_board.heartbeat_session_identity((select agent_id from _sid_fixture where label = 'codex'))$q$,
|
|
140
|
+
'EC-SIDSCHEMA-1-heartbeat-live: current holder refreshes a live lease');
|
|
141
|
+
reset role;
|
|
142
|
+
select is((select last_heartbeat from agent_board.session_identities where agent_id = (select agent_id from _sid_fixture where label = 'codex')), now(),
|
|
143
|
+
'EC-SIDSCHEMA-1-heartbeat-state: live heartbeat updates the stored time');
|
|
144
|
+
update agent_board.session_identities set last_heartbeat = now() - interval '180 seconds'
|
|
145
|
+
where agent_id = (select agent_id from _sid_fixture where label = 'codex');
|
|
146
|
+
set local role authenticated;
|
|
147
|
+
select throws_ok(format('select pg_temp.sid_refuse(%L)', $q$select agent_board.heartbeat_session_identity((select agent_id from _sid_fixture where label = 'codex'))$q$),
|
|
148
|
+
'42501', 'identity_lease_lapsed', 'EC-SIDSCHEMA-1-heartbeat-lapsed: heartbeat cannot revive a lapsed lease');
|
|
149
|
+
reset role;
|
|
150
|
+
select is((select last_heartbeat from agent_board.session_identities where agent_id = (select agent_id from _sid_fixture where label = 'codex')), now() - interval '180 seconds',
|
|
151
|
+
'EC-SIDSCHEMA-1-heartbeat-refused-state: refused heartbeat writes nothing');
|
|
152
|
+
select set_config('request.jwt.claim.role', 'service_role', true);
|
|
153
|
+
set local role service_role;
|
|
154
|
+
select throws_ok((select format('select agent_board.link_session_alias(%L, %L, %L)', agent_id, '0', 'bound-codex') from _sid_fixture where label = 'codex'),
|
|
155
|
+
'42501', 'identity_lease_lapsed', 'EC-SIDSCHEMA-1-link-lapsed: link never reclaims a lapsed lease');
|
|
156
|
+
select throws_ok(format('select pg_temp.sid_refuse(%L)', $q$select pg_temp.sid_mint('codex', 'discontinuous', null, 'new-codex-id')$q$),
|
|
157
|
+
'42501', 'identity_continuity_refused', 'EC-SIDSCHEMA-1-reclaim-nonclaude: new non-Claude id is not continuity');
|
|
158
|
+
select lives_ok($q$select pg_temp.sid_mint('codex', 'reclaimed', null, 'bound-codex')$q$,
|
|
159
|
+
'EC-SIDSCHEMA-1-reclaim: exactly 180 seconds old can be reclaimed with PoP and a bound alias');
|
|
160
|
+
reset role;
|
|
161
|
+
select is((select epoch from _sid_results where label = 'reclaimed'), 1::bigint,
|
|
162
|
+
'EC-SIDSCHEMA-1-reclaim-epoch: reclaim increments the epoch');
|
|
163
|
+
select ok((select secret is not null and secret <> (select secret from _sid_results where label = 'first') from _sid_results where label = 'reclaimed'),
|
|
164
|
+
'EC-SIDSCHEMA-1-reclaim-secret: reclaim rotates the secret');
|
|
165
|
+
select set_config('request.jwt.claim.role', 'authenticated', true);
|
|
166
|
+
select set_config('request.jwt.claims', '{"identity_lease_epoch":"0"}', true);
|
|
167
|
+
set local role authenticated;
|
|
168
|
+
select is(agent_board.current_agent_id(), null::uuid, 'EC-SIDSCHEMA-1-epoch-stale: reclaim fences the old credential immediately');
|
|
169
|
+
select throws_ok(format('select pg_temp.sid_refuse(%L)', $q$select agent_board.heartbeat_session_identity((select agent_id from _sid_fixture where label = 'codex'))$q$),
|
|
170
|
+
'42501', 'identity_lease_not_held', 'EC-SIDSCHEMA-1-heartbeat-stale: old holder cannot heartbeat after reclaim');
|
|
171
|
+
reset role;
|
|
172
|
+
select set_config('request.jwt.claim.role', 'service_role', true);
|
|
173
|
+
set local role service_role;
|
|
174
|
+
select throws_ok(format('select pg_temp.sid_refuse(%L)', $q$select pg_temp.sid_mint('codex', 'former', (select secret from _sid_results where label = 'first'), 'bound-codex')$q$),
|
|
175
|
+
'42501', 'identity_lease_not_held', 'EC-SIDSCHEMA-1-former-secret: same key with the former secret cannot reclaim a live lease');
|
|
176
|
+
select lives_ok($q$select pg_temp.sid_mint('claude', 'claude-first', null, 'bound-claude')$q$,
|
|
177
|
+
'EC-SIDSCHEMA-1-claude-first: Claude initial reserved alias claims');
|
|
178
|
+
select set_config('request.jwt.claim.sub', (select auth_id::text from _sid_fixture where label = 'claude'), true);
|
|
179
|
+
reset role;
|
|
180
|
+
select is((select count(*) from _sid_results where label = 'claude-first'), 1::bigint,
|
|
181
|
+
'EC-SIDSCHEMA-1-claude-first-state: first Claude claim returns once');
|
|
182
|
+
set local role service_role;
|
|
183
|
+
select lives_ok((select format('select agent_board.link_session_alias(%L, %L, %L)', agent_id, '0', 'linked-claude') from _sid_fixture where label = 'claude'),
|
|
184
|
+
'EC-SIDSCHEMA-1-link-live: verified live Claude holder binds a new continuity alias');
|
|
185
|
+
reset role;
|
|
186
|
+
select is((select count(*) from agent_board.session_id_aliases where identity_agent_id = (select agent_id from _sid_fixture where label = 'claude') and session_id = 'linked-claude'), 1::bigint,
|
|
187
|
+
'EC-SIDSCHEMA-1-link-state: allowed link writes exactly its new alias');
|
|
188
|
+
set local role service_role;
|
|
189
|
+
select lives_ok((select format('select agent_board.link_session_alias(%L, %L, %L)', agent_id, '0', 'linked-claude') from _sid_fixture where label = 'claude'),
|
|
190
|
+
'EC-SIDSCHEMA-1-link-idempotent: an already-bound live alias can be linked again');
|
|
191
|
+
reset role;
|
|
192
|
+
select is((select count(*) from agent_board.session_id_aliases where identity_agent_id = (select agent_id from _sid_fixture where label = 'claude') and session_id = 'linked-claude'), 1::bigint,
|
|
193
|
+
'EC-SIDSCHEMA-1-link-idempotent-state: repeated link preserves exactly one alias');
|
|
194
|
+
update agent_board.session_identities set last_heartbeat = now() - interval '180 seconds'
|
|
195
|
+
where agent_id = (select agent_id from _sid_fixture where label = 'claude');
|
|
196
|
+
set local role service_role;
|
|
197
|
+
select lives_ok($q$select pg_temp.sid_mint('claude', 'claude-reclaim', null, 'continued-claude')$q$,
|
|
198
|
+
'EC-SIDSCHEMA-1-claude-reclaim: trusted Claude new-id continuity reclaims a lapsed lease');
|
|
199
|
+
reset role;
|
|
200
|
+
select is((select count(*) from agent_board.session_id_aliases where identity_agent_id = (select agent_id from _sid_fixture where label = 'claude') and session_id = 'continued-claude'), 1::bigint,
|
|
201
|
+
'EC-SIDSCHEMA-1-claude-alias: reclaim binds its new alias');
|
|
202
|
+
select is((select epoch from _sid_results where label = 'claude-reclaim'), 1::bigint,
|
|
203
|
+
'EC-SIDSCHEMA-1-claude-epoch: new-alias reclaim also fences the prior holder');
|
|
204
|
+
select throws_ok(format('select pg_temp.sid_refuse(%L)', $q$update agent_board.session_identities set identity_kind = 'legacy', harness = null where agent_id = (select agent_id from _sid_fixture where label = 'codex')$q$),
|
|
205
|
+
'42501', 'session identity kind is immutable', 'EC-SIDSCHEMA-1-kind: session identity kind cannot be changed');
|
|
206
|
+
select throws_ok(format('select pg_temp.sid_refuse(%L)', $q$update agent_board.session_identities set holder_secret_hash = null where agent_id = (select agent_id from _sid_fixture where label = 'codex')$q$),
|
|
207
|
+
'42501', 'a claimed session lease cannot be cleared', 'EC-SIDSCHEMA-1-lease-preserved: claimed lease cannot be nulled to bypass fencing');
|
|
208
|
+
select set_config('request.jwt.claim.role', 'authenticated', true);
|
|
209
|
+
select set_config('request.jwt.claim.sub', (select auth_id::text from _sid_fixture where label = 'legacy'), true);
|
|
210
|
+
select set_config('request.jwt.claims', '{}', true);
|
|
211
|
+
select set_config('request.jwt.claim.identity_lease_epoch', '', true);
|
|
212
|
+
set local role authenticated;
|
|
213
|
+
select is(agent_board.current_agent_id(), (select agent_id from _sid_fixture where label = 'legacy'),
|
|
214
|
+
'EC-SIDSCHEMA-1-legacy: legacy identity still resolves without an epoch');
|
|
215
|
+
reset role;
|
|
216
|
+
select set_config('request.jwt.claim.role', 'service_role', true);
|
|
217
|
+
set local role service_role;
|
|
218
|
+
select lives_ok($q$select pg_temp.sid_mint('legacy', 'legacy', null, null)$q$,
|
|
219
|
+
'EC-SIDSCHEMA-1-legacy-mint: legacy key PoP still mints without session context');
|
|
220
|
+
reset role;
|
|
221
|
+
select is((select epoch from _sid_results where label = 'legacy'), null::bigint,
|
|
222
|
+
'EC-SIDSCHEMA-1-legacy-epoch: legacy mint introduces no lease epoch');
|
|
223
|
+
select is((select secret from _sid_results where label = 'legacy'), null::text,
|
|
224
|
+
'EC-SIDSCHEMA-1-legacy-secret: legacy mint introduces no holder secret');
|
|
225
|
+
select is(has_function_privilege('service_role', 'agent_board._mint_fleet_agent_session_legacy(uuid,text,uuid,uuid,integer)', 'EXECUTE'), false,
|
|
226
|
+
'EC-SIDSCHEMA-1-private-token: renamed implementation cannot bypass the fenced mint');
|
|
227
|
+
select is(to_regprocedure('agent_board._mint_delegated_child_legacy(uuid,text,jsonb,text,interval,boolean)'), null::regprocedure,
|
|
228
|
+
'EC-SIDSCHEMA-1-private-child: no legacy child implementation can bypass the single session child mint');
|
|
229
|
+
select ok((select bool_and(expires_at <= now() + interval '900 seconds') from _sid_results),
|
|
230
|
+
'EC-SIDSCHEMA-6-token-cap: every session token stays within 900 seconds');
|
|
231
|
+
select ok((select count(*) > 0 and bool_and(s.expires_at <= now() + interval '900 seconds')
|
|
232
|
+
from agent_board.nonfleet_sessions s join _sid_fixture f on f.agent_id = s.agent_id),
|
|
233
|
+
'EC-SIDSCHEMA-6-token-stored-cap: issued fixture tokens exist and their stored expiry also stays within 900 seconds');
|
|
234
|
+
select finish();
|
|
235
|
+
rollback;
|
|
@@ -0,0 +1,230 @@
|
|
|
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 authenticated, service_role;
|
|
5
|
+
select plan(45);
|
|
6
|
+
create temp table _sid_lifetime_root(root_id uuid, root_auth uuid);
|
|
7
|
+
create temp table _sid_lifetime_children(label text primary key, agent_id uuid, key_id uuid, expires_at timestamptz);
|
|
8
|
+
create temp table _sid_lifetime_tokens(label text, expires_at timestamptz, epoch bigint);
|
|
9
|
+
do $$
|
|
10
|
+
declare v_root uuid := gen_random_uuid(); v_auth uuid;
|
|
11
|
+
begin
|
|
12
|
+
v_auth := public.ensure_github_user('sid-life-' || v_root, 'sid-life-' || v_root || '@example.test', 'sid-life-' || v_root, 'Lifetime root');
|
|
13
|
+
insert into agent_board.agents(id, agent_type, handle, display_name, status, auth_user_id, owner_user_id)
|
|
14
|
+
values (v_root, 'agent', 'sid-life-' || v_root, 'Lifetime root', 'active', v_auth, v_auth);
|
|
15
|
+
insert into agent_board.session_identities(agent_id, root_agent_id, depth, current_risky_digest, approved_risky_digest)
|
|
16
|
+
values (v_root, v_root, 0, 'approved', 'approved');
|
|
17
|
+
insert into _sid_lifetime_root values (v_root, v_auth);
|
|
18
|
+
end;
|
|
19
|
+
$$;
|
|
20
|
+
grant select on _sid_lifetime_root, _sid_lifetime_children to authenticated, service_role;
|
|
21
|
+
grant insert on _sid_lifetime_children, _sid_lifetime_tokens to service_role;
|
|
22
|
+
create function pg_temp.sid_lifetime_mint(p_label text, p_parent text default null)
|
|
23
|
+
returns void language plpgsql as $$
|
|
24
|
+
declare v_parent uuid; v_session boolean := p_label <> 'legacy';
|
|
25
|
+
begin
|
|
26
|
+
v_parent := coalesce((select agent_id from _sid_lifetime_children where label = p_parent), (select root_id from _sid_lifetime_root));
|
|
27
|
+
insert into _sid_lifetime_children
|
|
28
|
+
select p_label, m.child_agent_id, m.signing_key_id, m.grant_expires_at
|
|
29
|
+
from agent_board.mint_delegated_child(v_parent, 'sid-life-' || p_label || '-' || substr(v_parent::text, 1, 8), '[]',
|
|
30
|
+
public.test_pubkey(p_label || v_parent), interval '30 days', false,
|
|
31
|
+
case when v_session then 'claude' end, case when v_session then 'life-' || p_label end,
|
|
32
|
+
case when p_parent is not null then '0' end, '[]',
|
|
33
|
+
case when v_session then '{"resolution":"resolved","risky_digest":"approved"}'::jsonb end) m;
|
|
34
|
+
end;
|
|
35
|
+
$$;
|
|
36
|
+
create function pg_temp.sid_lifetime_token(p_label text)
|
|
37
|
+
returns void language plpgsql as $$
|
|
38
|
+
declare v_child record; v_challenge record;
|
|
39
|
+
begin
|
|
40
|
+
select * into v_child from _sid_lifetime_children where label = p_label;
|
|
41
|
+
select * into v_challenge from agent_board.issue_signing_key_challenge(v_child.agent_id, 'delegated-ephemeral');
|
|
42
|
+
insert into _sid_lifetime_tokens
|
|
43
|
+
select p_label, m.expires_at, m.identity_lease_epoch from agent_board.mint_fleet_agent_session(
|
|
44
|
+
v_challenge.challenge_id, split_part(v_challenge.challenge, ':', 3), v_child.key_id, v_child.agent_id, 900, null, 'life-' || p_label) m;
|
|
45
|
+
end;
|
|
46
|
+
$$;
|
|
47
|
+
create function pg_temp.sid_lifetime_refuse(p_call text)
|
|
48
|
+
returns void language plpgsql as $$
|
|
49
|
+
begin
|
|
50
|
+
execute p_call;
|
|
51
|
+
raise exception 'expected refusal but RPC succeeded' using errcode = 'P0001';
|
|
52
|
+
end;
|
|
53
|
+
$$;
|
|
54
|
+
grant execute on function pg_temp.sid_lifetime_mint(text, text), pg_temp.sid_lifetime_token(text), pg_temp.sid_lifetime_refuse(text) to service_role, authenticated;
|
|
55
|
+
select set_config('request.jwt.claim.role', 'service_role', true);
|
|
56
|
+
set local role service_role;
|
|
57
|
+
select lives_ok($q$select agent_board.upsert_delegation_policy((select root_id from _sid_lifetime_root), 'root_minted', '[]', 200, interval '30 days', true)$q$,
|
|
58
|
+
'EC-SIDSCHEMA-4-policy-control: operator configures its legacy lifetime latitude');
|
|
59
|
+
reset role;
|
|
60
|
+
select is((select operator_child_ttl_ceiling from agent_board.delegation_policies where root_agent_id = (select root_id from _sid_lifetime_root)), interval '30 days',
|
|
61
|
+
'EC-SIDSCHEMA-4-policy-state: operator ceiling is stored');
|
|
62
|
+
set local role service_role;
|
|
63
|
+
select lives_ok($q$select pg_temp.sid_lifetime_mint('parent'); select pg_temp.sid_lifetime_mint('child', 'parent'); select pg_temp.sid_lifetime_mint('legacy')$q$,
|
|
64
|
+
'EC-SIDSCHEMA-4-mint-control: attested and legacy mints enter the real issuer');
|
|
65
|
+
reset role;
|
|
66
|
+
select is((select count(*) from _sid_lifetime_children), 3::bigint, 'EC-SIDSCHEMA-4-mint-state: all three distinct principals exist');
|
|
67
|
+
select is((select expires_at from _sid_lifetime_children where label = 'parent'), now() + interval '7 days',
|
|
68
|
+
'EC-SIDSCHEMA-4-mint-attested: approved session mint has a seven-day ceiling');
|
|
69
|
+
select is((select expires_at from _sid_lifetime_children where label = 'legacy'), now() + interval '30 days',
|
|
70
|
+
'EC-SIDSCHEMA-4-mint-legacy: operator legacy mint preserves its latitude');
|
|
71
|
+
set local role service_role;
|
|
72
|
+
select lives_ok($q$select pg_temp.sid_lifetime_token('parent'); select pg_temp.sid_lifetime_token('child')$q$,
|
|
73
|
+
'EC-SIDSCHEMA-4-token-control: real challenge and key gates claim both session leases');
|
|
74
|
+
reset role;
|
|
75
|
+
select is((select count(*) from _sid_lifetime_tokens), 2::bigint, 'EC-SIDSCHEMA-4-token-state: both claims produced epoch-fenced tokens');
|
|
76
|
+
select set_config('request.jwt.claim.sub', (select auth_user_id::text from agent_board.agents where id = (select agent_id from _sid_lifetime_children where label = 'parent')), true);
|
|
77
|
+
select set_config('request.jwt.claim.identity_lease_epoch', '0', true);
|
|
78
|
+
update agent_board.delegation_grants set grant_expires_at = now() + interval '1 hour' where child_agent_id = (select agent_id from _sid_lifetime_children where label = 'parent');
|
|
79
|
+
set local role service_role;
|
|
80
|
+
select lives_ok($q$select agent_board.renew_session_identity((select agent_id from _sid_lifetime_children where label = 'parent'), 'approved')$q$,
|
|
81
|
+
'EC-SIDSCHEMA-4-renew-control: forwarded current-epoch holder renews an unchanged approved fingerprint');
|
|
82
|
+
reset role;
|
|
83
|
+
select is((select grant_expires_at from agent_board.delegation_grants where child_agent_id = (select agent_id from _sid_lifetime_children where label = 'parent')), now() + interval '7 days',
|
|
84
|
+
'EC-SIDSCHEMA-4-renew-attested: renewal actually extends the same principal grant up to seven days');
|
|
85
|
+
select is((select credential_epoch from agent_board.session_identities where agent_id = (select agent_id from _sid_lifetime_children where label = 'parent')), 0::bigint,
|
|
86
|
+
'EC-SIDSCHEMA-4-renew-stable: ordinary renewal preserves the identity and holder epoch');
|
|
87
|
+
set local role service_role;
|
|
88
|
+
select lives_ok($q$select agent_board.renew_delegated_grant((select agent_id from _sid_lifetime_children where label = 'parent'), interval '30 days')$q$,
|
|
89
|
+
'EC-SIDSCHEMA-4-renew-legacy-attested-control: the approved-session legacy entry point reaches its own TTL ceiling');
|
|
90
|
+
reset role;
|
|
91
|
+
select is((select grant_expires_at from agent_board.delegation_grants where child_agent_id = (select agent_id from _sid_lifetime_children where label = 'parent')), now() + interval '7 days',
|
|
92
|
+
'EC-SIDSCHEMA-4-renew-legacy-attested: a longer legacy entry-point request cannot bypass the attested seven-day ceiling');
|
|
93
|
+
set local role service_role;
|
|
94
|
+
select throws_ok(format('select pg_temp.sid_lifetime_refuse(%L)', $q$select agent_board.renew_session_identity((select agent_id from _sid_lifetime_children where label = 'parent'), 'changed')$q$),
|
|
95
|
+
'42501', 'identity_fingerprint_unapproved', 'EC-SIDSCHEMA-4-renew-fresh: mismatched risky attestation cannot extend a grant');
|
|
96
|
+
reset role;
|
|
97
|
+
update agent_board.session_identities set current_risky_digest = 'changed' where agent_id = (select agent_id from _sid_lifetime_children where label = 'parent');
|
|
98
|
+
set local role service_role;
|
|
99
|
+
select throws_ok(format('select pg_temp.sid_lifetime_refuse(%L)', $q$select agent_board.renew_session_identity((select agent_id from _sid_lifetime_children where label = 'parent'), 'approved')$q$),
|
|
100
|
+
'42501', 'identity_fingerprint_unapproved', 'EC-SIDSCHEMA-4-renew-stored: quoting the old approved digest cannot hide a recorded risky change');
|
|
101
|
+
select throws_ok(format('select pg_temp.sid_lifetime_refuse(%L)', $q$select agent_board.renew_delegated_grant((select agent_id from _sid_lifetime_children where label = 'parent'), interval '30 days')$q$),
|
|
102
|
+
'42501', 'identity_fingerprint_unapproved', 'EC-SIDSCHEMA-4-legacy-door: legacy renewal cannot bypass a session fingerprint gate');
|
|
103
|
+
reset role;
|
|
104
|
+
update agent_board.session_identities set current_risky_digest = 'approved' where agent_id = (select agent_id from _sid_lifetime_children where label = 'parent');
|
|
105
|
+
update agent_board.delegation_policies set child_ttl_ceiling = interval '30 days', operator_child_ttl_ceiling = interval '2 hours' where root_agent_id = (select root_id from _sid_lifetime_root);
|
|
106
|
+
set local role service_role;
|
|
107
|
+
select lives_ok($q$select agent_board.renew_delegated_grant((select agent_id from _sid_lifetime_children where label = 'legacy'), interval '30 days')$q$,
|
|
108
|
+
'EC-SIDSCHEMA-4-legacy-renew-control: legacy service renewal remains usable');
|
|
109
|
+
reset role;
|
|
110
|
+
select is((select grant_expires_at from agent_board.delegation_grants where child_agent_id = (select agent_id from _sid_lifetime_children where label = 'legacy')), now() + interval '2 hours',
|
|
111
|
+
'EC-SIDSCHEMA-4-legacy-operator: legacy renewal consumes the durable tighter operator ceiling');
|
|
112
|
+
set local role service_role;
|
|
113
|
+
select lives_ok($q$select agent_board.renew_session_identity((select agent_id from _sid_lifetime_children where label = 'parent'), 'approved')$q$,
|
|
114
|
+
'EC-SIDSCHEMA-4-renew-operator-control: approved renewal honors a newly tightened operator policy');
|
|
115
|
+
reset role;
|
|
116
|
+
select is((select grant_expires_at from agent_board.delegation_grants where child_agent_id = (select agent_id from _sid_lifetime_children where label = 'parent')), now() + interval '2 hours',
|
|
117
|
+
'EC-SIDSCHEMA-4-renew-operator: attested renewal also consumes the tighter operator ceiling');
|
|
118
|
+
select set_config('request.jwt.claim.sub', (select auth_user_id::text from agent_board.agents where id = (select agent_id from _sid_lifetime_children where label = 'child')), true);
|
|
119
|
+
update agent_board.delegation_policies set operator_child_ttl_ceiling = interval '30 days' where root_agent_id = (select root_id from _sid_lifetime_root);
|
|
120
|
+
set local role service_role;
|
|
121
|
+
select lives_ok($q$select agent_board.renew_session_identity((select agent_id from _sid_lifetime_children where label = 'child'), 'approved')$q$,
|
|
122
|
+
'EC-SIDSCHEMA-4-renew-ancestor-control: depth-two renewal enters the same bounded writer');
|
|
123
|
+
reset role;
|
|
124
|
+
select is((select grant_expires_at from agent_board.delegation_grants where child_agent_id = (select agent_id from _sid_lifetime_children where label = 'child')), now() + interval '2 hours',
|
|
125
|
+
'EC-SIDSCHEMA-4-renew-ancestor: renewal cannot outlive its current parent grant');
|
|
126
|
+
update agent_board.delegation_grants set grant_expires_at = now() - interval '1 second' where child_agent_id = (select agent_id from _sid_lifetime_children where label = 'parent');
|
|
127
|
+
set local role service_role;
|
|
128
|
+
select throws_ok(format('select pg_temp.sid_lifetime_refuse(%L)', $q$select agent_board.renew_session_identity((select agent_id from _sid_lifetime_children where label = 'child'), 'approved')$q$),
|
|
129
|
+
'42501', 'identity_parent_inactive', 'EC-SIDSCHEMA-4-renew-expired-ancestor: expired parent refuses depth-two renewal');
|
|
130
|
+
select throws_ok(format('select pg_temp.sid_lifetime_refuse(%L)', $q$select pg_temp.sid_lifetime_token('child')$q$),
|
|
131
|
+
'42501', 'identity_parent_inactive', 'EC-SIDSCHEMA-2-token-ancestor-expiry: token issuer checks every ancestor grant');
|
|
132
|
+
reset role;
|
|
133
|
+
update agent_board.delegation_grants set grant_expires_at = now() + interval '2 hours', revoked_at = now() where child_agent_id = (select agent_id from _sid_lifetime_children where label = 'parent');
|
|
134
|
+
set local role service_role;
|
|
135
|
+
select throws_ok(format('select pg_temp.sid_lifetime_refuse(%L)', $q$select pg_temp.sid_lifetime_token('child')$q$),
|
|
136
|
+
'42501', 'identity_parent_inactive', 'EC-SIDSCHEMA-2-token-ancestor-revoke: revoked ancestor refuses token issuance');
|
|
137
|
+
reset role;
|
|
138
|
+
update agent_board.delegation_grants set revoked_at = null where child_agent_id = (select agent_id from _sid_lifetime_children where label = 'parent');
|
|
139
|
+
update agent_board.session_identities set resolution_state = 'frozen' where agent_id = (select root_id from _sid_lifetime_root);
|
|
140
|
+
set local role service_role;
|
|
141
|
+
select throws_ok(format('select pg_temp.sid_lifetime_refuse(%L)', $q$select pg_temp.sid_lifetime_token('child')$q$),
|
|
142
|
+
'42501', 'identity_fingerprint_unapproved', 'EC-SIDSCHEMA-2-token-ancestor-freeze: frozen root refuses depth-two token issuance');
|
|
143
|
+
reset role;
|
|
144
|
+
update agent_board.session_identities set resolution_state = 'active' where agent_id = (select root_id from _sid_lifetime_root);
|
|
145
|
+
select set_config('request.jwt.claim.role', 'authenticated', true);
|
|
146
|
+
select set_config('request.jwt.claim.sub', (select root_auth::text from _sid_lifetime_root), true);
|
|
147
|
+
set local role authenticated;
|
|
148
|
+
select lives_ok($q$select agent_board.upsert_delegation_policy((select root_id from _sid_lifetime_root), 'root_minted', '[]', 200, interval '24 hours', false)$q$,
|
|
149
|
+
'EC-SIDSCHEMA-6-policy-raised: self-service may choose the accepted 200-child root limit');
|
|
150
|
+
reset role;
|
|
151
|
+
select is((select max_active_children from agent_board.delegation_policies where root_agent_id = (select root_id from _sid_lifetime_root)), 200,
|
|
152
|
+
'EC-SIDSCHEMA-6-policy-raised-state: the old 25-child clamp is gone');
|
|
153
|
+
select is((select max_depth from agent_board.delegation_policies where root_agent_id = (select root_id from _sid_lifetime_root)), 2,
|
|
154
|
+
'EC-SIDSCHEMA-6-policy-depth: policy supports the accepted depth two');
|
|
155
|
+
set local role authenticated;
|
|
156
|
+
select throws_ok($q$select agent_board.upsert_delegation_policy((select root_id from _sid_lifetime_root), 'root_minted', '[]', 201)$q$,
|
|
157
|
+
'22023', null, 'EC-SIDSCHEMA-6-policy-upper: self-service cannot exceed 200');
|
|
158
|
+
select throws_ok($q$select agent_board.upsert_delegation_policy((select root_id from _sid_lifetime_root), 'root_minted', '[]', 200, interval '7 days')$q$,
|
|
159
|
+
'22023', null, 'EC-SIDSCHEMA-4-policy-legacy: unattributed self-service policy retains its 24-hour cap');
|
|
160
|
+
reset role;
|
|
161
|
+
select is((select operator_child_ttl_ceiling from agent_board.delegation_policies where root_agent_id = (select root_id from _sid_lifetime_root)), interval '30 days',
|
|
162
|
+
'EC-SIDSCHEMA-4-policy-durable: self-service preserves the operator ceiling');
|
|
163
|
+
select is(has_function_privilege('authenticated', 'agent_board.renew_session_identity(uuid,text)', 'EXECUTE'), false,
|
|
164
|
+
'EC-SIDSCHEMA-4-renew-trust: caller-asserted fresh fingerprint is service-role-only until SIDSERVER binds it');
|
|
165
|
+
select is((select count(*) from agent_board.session_identities where root_agent_id = (select root_id from _sid_lifetime_root)), 4::bigint,
|
|
166
|
+
'EC-SIDSCHEMA-4-stable-count: renewals and refusals create no new identities');
|
|
167
|
+
update agent_board.delegation_grants set grant_expires_at = now() + interval '120 seconds' where child_agent_id = (select agent_id from _sid_lifetime_children where label = 'parent');
|
|
168
|
+
update agent_board.session_identities set last_heartbeat = now() - interval '181 seconds' where agent_id = (select agent_id from _sid_lifetime_children where label = 'child');
|
|
169
|
+
select set_config('request.jwt.claim.role', 'service_role', true);
|
|
170
|
+
set local role service_role;
|
|
171
|
+
select lives_ok($q$select pg_temp.sid_lifetime_token('child')$q$,
|
|
172
|
+
'EC-SIDSCHEMA-4-token-short-ancestor-control: a valid short-lived parent permits the real reclaim issuer');
|
|
173
|
+
reset role;
|
|
174
|
+
select is((select min(expires_at) from _sid_lifetime_tokens where label = 'child'), now() + interval '120 seconds',
|
|
175
|
+
'EC-SIDSCHEMA-4-token-short-ancestor: returned token cannot outlive current ancestor authority');
|
|
176
|
+
select is((select min(expires_at) from agent_board.nonfleet_sessions where agent_id = (select agent_id from _sid_lifetime_children where label = 'child')), now() + interval '120 seconds',
|
|
177
|
+
'EC-SIDSCHEMA-4-token-short-stored: the stored credential has the same effective ceiling');
|
|
178
|
+
update agent_board.delegation_grants set grant_expires_at = now() + interval '60 seconds' where child_agent_id = (select agent_id from _sid_lifetime_children where label = 'child');
|
|
179
|
+
update agent_board.session_identities set last_heartbeat = now() - interval '181 seconds' where agent_id = (select agent_id from _sid_lifetime_children where label = 'child');
|
|
180
|
+
set local role service_role;
|
|
181
|
+
select lives_ok($q$select pg_temp.sid_lifetime_token('child')$q$,
|
|
182
|
+
'EC-SIDSCHEMA-4-token-short-own-control: the actual issuer permits a valid shorter child grant');
|
|
183
|
+
reset role;
|
|
184
|
+
select is((select min(expires_at) from _sid_lifetime_tokens where label = 'child'), now() + interval '60 seconds',
|
|
185
|
+
'EC-SIDSCHEMA-4-token-short-own: returned token also consumes its own remaining grant');
|
|
186
|
+
select is((select min(expires_at) from agent_board.nonfleet_sessions where agent_id = (select agent_id from _sid_lifetime_children where label = 'child')), now() + interval '60 seconds',
|
|
187
|
+
'EC-SIDSCHEMA-4-token-short-own-stored: stored token consumes the shorter own grant too');
|
|
188
|
+
|
|
189
|
+
-- Derive the direct grant-expiry writers from the catalog. The population pin
|
|
190
|
+
-- names any new member until it has its own positive path and mutation receipt.
|
|
191
|
+
-- The scanner recognizes literal SQL writes, not dynamic SQL/variable callees;
|
|
192
|
+
-- literals/comments cannot masquerade as executable ceiling code.
|
|
193
|
+
create temp view _sid_expiry_writers as
|
|
194
|
+
select p.oid, p.proname, b.body
|
|
195
|
+
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
|
196
|
+
cross join lateral (select agent_board_test.stripped_body(p.oid) as body) b
|
|
197
|
+
where n.nspname = 'agent_board'
|
|
198
|
+
and b.body ~ agent_board_test.col_write_re('delegation_grants', 'grant_expires_at');
|
|
199
|
+
select is((select string_agg(proname, ', ' order by proname) from _sid_expiry_writers),
|
|
200
|
+
'mint_delegated_child, readmit_session_identity, renew_delegated_grant',
|
|
201
|
+
'EC-SIDSCHEMA-4-expiry-population: every catalog-derived expiry writer has a positive path and independent ceiling mutation');
|
|
202
|
+
create temp view _sid_unsafe_expiry as
|
|
203
|
+
select * from _sid_expiry_writers
|
|
204
|
+
where body !~ agent_board_test.re(agent_board_test.col_frag('v_ceiling') || '\s*:=\s*' || agent_board_test.ci_re('least') || '\(\s*v_policy.child_ttl_ceiling\s*,\s*v_policy.operator_child_ttl_ceiling')
|
|
205
|
+
or body !~ agent_board_test.re(agent_board_test.ci_re('least') || '\(\s*' || agent_board_test.ci_re('now') || '\(\)\s*\+\s*' || agent_board_test.ci_re('least') || '\(')
|
|
206
|
+
or body !~ agent_board_test.re(agent_board_test.col_frag('v_ancestor_expires'));
|
|
207
|
+
create temp view _sid_unsafe_expiry_class as
|
|
208
|
+
select distinct c.root_oid, c.root_name
|
|
209
|
+
from agent_board_test.fn_call_closure() c join _sid_unsafe_expiry u on u.oid = c.reached_oid;
|
|
210
|
+
select is((select coalesce(string_agg(root_name, ', ' order by root_name), '') from _sid_unsafe_expiry_class), '',
|
|
211
|
+
'EC-SIDSCHEMA-4-expiry-class: every discovered expiry writer and caller reaches an executable current-policy operator and ancestor ceiling');
|
|
212
|
+
select is((select count(*) from _sid_expiry_writers), 3::bigint,
|
|
213
|
+
'EC-SIDSCHEMA-4-expiry-nonempty: the expiry class is not vacuous');
|
|
214
|
+
create function agent_board.sid_expiry_unsafe_probe(p_child uuid)
|
|
215
|
+
returns void language sql security definer set search_path = '' as $probe$
|
|
216
|
+
/* v_ceiling := least(v_policy.child_ttl_ceiling, v_policy.operator_child_ttl_ceiling);
|
|
217
|
+
least(now() + least(extend, ceiling), v_ancestor_expires) */
|
|
218
|
+
UPDATE agent_board.delegation_grants SET grant_expires_at = now() + interval '100 days' WHERE child_agent_id = p_child;
|
|
219
|
+
$probe$;
|
|
220
|
+
create function agent_board.sid_expiry_wrapper_probe(p_child uuid)
|
|
221
|
+
returns void language sql security definer set search_path = '' as $probe$
|
|
222
|
+
SELECT agent_board.sid_expiry_unsafe_probe(/* wrapper/case/comment canary */ p_child);
|
|
223
|
+
$probe$;
|
|
224
|
+
select is((select string_agg(root_name, ', ' order by root_name) from _sid_unsafe_expiry_class where root_name in ('sid_expiry_unsafe_probe', 'sid_expiry_wrapper_probe')),
|
|
225
|
+
'sid_expiry_unsafe_probe, sid_expiry_wrapper_probe',
|
|
226
|
+
'EC-SIDSCHEMA-4-expiry-canary: unsafe uppercase writer and commented wrapper name themselves in the derived class');
|
|
227
|
+
select is((select proname from _sid_unsafe_expiry where proname = 'sid_expiry_unsafe_probe'), 'sid_expiry_unsafe_probe',
|
|
228
|
+
'EC-SIDSCHEMA-4-expiry-decoy: a ceiling in a comment never exempts an unsafe production body');
|
|
229
|
+
select finish();
|
|
230
|
+
rollback;
|