@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,156 @@
|
|
|
1
|
+
-- Real service-form mint/claim and resolver-form holder/owner calls. Frozen
|
|
2
|
+
-- fixtures retain matching digests, isolating the freeze gate from mismatch.
|
|
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(30);
|
|
8
|
+
create temp table _sid_action_root(root_id uuid, owner_auth uuid, suffix text);
|
|
9
|
+
create temp table _sid_action_children(label text primary key, agent_id uuid, key_id uuid, auth_id uuid);
|
|
10
|
+
create temp table _sid_action_tokens(label text, epoch bigint, secret text);
|
|
11
|
+
do $$
|
|
12
|
+
declare v_root uuid := gen_random_uuid(); v_owner uuid; v_auth uuid;
|
|
13
|
+
begin
|
|
14
|
+
v_owner := public.ensure_github_user('sid-action-owner-' || v_root, 'sid-action-owner-' || v_root || '@example.test', 'sid-action-owner-' || v_root, 'Action owner');
|
|
15
|
+
v_auth := public.ensure_github_user('sid-action-root-' || v_root, 'sid-action-root-' || v_root || '@example.test', 'sid-action-root-' || v_root, 'Action root');
|
|
16
|
+
insert into agent_board.agents(agent_type, handle, display_name, status, auth_user_id)
|
|
17
|
+
values ('human', 'sid-action-owner-' || v_root, 'Action owner', 'active', v_owner);
|
|
18
|
+
insert into agent_board.agents(id, agent_type, handle, display_name, status, auth_user_id, owner_user_id)
|
|
19
|
+
values (v_root, 'agent', 'sid-action-root-' || v_root, 'Action root', 'active', v_auth, v_owner);
|
|
20
|
+
insert into agent_board.session_identities(agent_id, root_agent_id, depth, approved_risky_digest, current_risky_digest)
|
|
21
|
+
values (v_root, v_root, 0, 'approved', 'approved');
|
|
22
|
+
insert into _sid_action_root values (v_root, v_owner, substr(v_root::text, 1, 8));
|
|
23
|
+
end;
|
|
24
|
+
$$;
|
|
25
|
+
grant select on _sid_action_root, _sid_action_children to authenticated, service_role;
|
|
26
|
+
grant insert on _sid_action_children to service_role;
|
|
27
|
+
grant select, insert on _sid_action_tokens to service_role;
|
|
28
|
+
create function pg_temp.sid_action_mint(p_label text, p_parent text default null, p_alias text default null, p_fork text default null)
|
|
29
|
+
returns void language plpgsql as $$
|
|
30
|
+
declare v_root record; v_parent uuid; v_result record;
|
|
31
|
+
begin
|
|
32
|
+
select * into v_root from _sid_action_root;
|
|
33
|
+
v_parent := coalesce((select agent_id from _sid_action_children where label = p_parent), v_root.root_id);
|
|
34
|
+
select * into v_result from agent_board.mint_delegated_child(v_parent, 'sid-action-' || p_label || '-' || v_root.suffix,
|
|
35
|
+
'["read"]', public.test_pubkey(v_root.suffix || p_label), interval '8 hours', false, 'claude', coalesce(p_alias, 'action-' || p_label),
|
|
36
|
+
case when p_parent is null then null else '0' end, '[]',
|
|
37
|
+
'{"resolution":"resolved","risky_digest":"approved"}',
|
|
38
|
+
(select agent_id from _sid_action_children where label = p_fork));
|
|
39
|
+
insert into _sid_action_children select p_label, v_result.child_agent_id, v_result.signing_key_id, a.auth_user_id
|
|
40
|
+
from agent_board.agents a where a.id = v_result.child_agent_id;
|
|
41
|
+
end;
|
|
42
|
+
$$;
|
|
43
|
+
create function pg_temp.sid_action_token(p_result text, p_first boolean default false)
|
|
44
|
+
returns void language plpgsql as $$
|
|
45
|
+
declare v_child record; v_challenge record;
|
|
46
|
+
begin
|
|
47
|
+
select * into v_child from _sid_action_children where label = 'parent';
|
|
48
|
+
select * into v_challenge from agent_board.issue_signing_key_challenge(v_child.agent_id, 'delegated-ephemeral');
|
|
49
|
+
insert into _sid_action_tokens select p_result, m.identity_lease_epoch, m.holder_secret
|
|
50
|
+
from agent_board.mint_fleet_agent_session(v_challenge.challenge_id, split_part(v_challenge.challenge, ':', 3),
|
|
51
|
+
v_child.key_id, v_child.agent_id, 900,
|
|
52
|
+
case when p_first then null else (select secret from _sid_action_tokens where label = 'first') end,
|
|
53
|
+
'action-parent') m;
|
|
54
|
+
end;
|
|
55
|
+
$$;
|
|
56
|
+
create function pg_temp.sid_action_refuse(p_call text)
|
|
57
|
+
returns void language plpgsql as $$
|
|
58
|
+
begin
|
|
59
|
+
execute p_call;
|
|
60
|
+
raise exception 'expected refusal but RPC succeeded' using errcode = 'P0001';
|
|
61
|
+
end;
|
|
62
|
+
$$;
|
|
63
|
+
grant execute on function pg_temp.sid_action_mint(text,text,text,text), pg_temp.sid_action_token(text,boolean) to service_role;
|
|
64
|
+
grant execute on function pg_temp.sid_action_refuse(text) to authenticated, service_role;
|
|
65
|
+
select set_config('request.jwt.claims', '{}', true);
|
|
66
|
+
select set_config('request.jwt.claim.role', 'service_role', true);
|
|
67
|
+
set local role service_role;
|
|
68
|
+
select lives_ok($q$select agent_board.upsert_delegation_policy((select root_id from _sid_action_root), 'root_minted', '["read"]', 200, interval '7 days', true)$q$,
|
|
69
|
+
'EC-SIDSCHEMA-5-action-policy: trusted root configures policy');
|
|
70
|
+
reset role;
|
|
71
|
+
select is((select count(*) from agent_board.delegation_policies where root_agent_id = (select root_id from _sid_action_root)), 1::bigint,
|
|
72
|
+
'EC-SIDSCHEMA-5-action-policy-state: one policy exists');
|
|
73
|
+
set local role service_role;
|
|
74
|
+
select lives_ok($q$select pg_temp.sid_action_mint('parent'); select pg_temp.sid_action_token('first', true)$q$,
|
|
75
|
+
'EC-SIDSCHEMA-5-action-fixture: real approved mint and key challenge create the current holder');
|
|
76
|
+
reset role;
|
|
77
|
+
select ok((select epoch = 0 and secret is not null from _sid_action_tokens where label = 'first'),
|
|
78
|
+
'EC-SIDSCHEMA-5-action-fixture-state: first claim returned its epoch and holder secret');
|
|
79
|
+
set local role service_role;
|
|
80
|
+
select lives_ok($q$select pg_temp.sid_action_token('holder')$q$, 'EC-SIDSCHEMA-5-token-control: unfrozen holder can mint a token');
|
|
81
|
+
reset role;
|
|
82
|
+
select is((select count(*) from _sid_action_tokens where label = 'holder'), 1::bigint, 'EC-SIDSCHEMA-5-token-control-state: one token result was returned');
|
|
83
|
+
set local role service_role;
|
|
84
|
+
select lives_ok($q$select pg_temp.sid_action_mint('child', 'parent')$q$, 'EC-SIDSCHEMA-5-child-control: unfrozen depth-one parent mints a child');
|
|
85
|
+
reset role;
|
|
86
|
+
select is((select depth from agent_board.session_identities where agent_id = (select agent_id from _sid_action_children where label = 'child')), 2::smallint,
|
|
87
|
+
'EC-SIDSCHEMA-5-child-control-state: real child is depth two');
|
|
88
|
+
select set_config('request.jwt.claim.sub', (select auth_id::text from _sid_action_children where label = 'parent'), true);
|
|
89
|
+
select set_config('request.jwt.claim.identity_lease_epoch', '0', true);
|
|
90
|
+
set local role service_role;
|
|
91
|
+
select lives_ok($q$select agent_board.link_session_alias((select agent_id from _sid_action_children where label = 'parent'), '0', 'action-linked')$q$,
|
|
92
|
+
'EC-SIDSCHEMA-5-link-control: verified unfrozen holder links an alias');
|
|
93
|
+
reset role;
|
|
94
|
+
select is((select count(*) from agent_board.session_id_aliases where identity_agent_id = (select agent_id from _sid_action_children where label = 'parent')), 2::bigint,
|
|
95
|
+
'EC-SIDSCHEMA-5-link-control-state: exactly one alias was added');
|
|
96
|
+
set local role service_role;
|
|
97
|
+
select lives_ok($q$select agent_board.renew_session_identity((select agent_id from _sid_action_children where label = 'parent'), 'approved')$q$,
|
|
98
|
+
'EC-SIDSCHEMA-5-renew-control: verified unfrozen holder renews');
|
|
99
|
+
reset role;
|
|
100
|
+
select is((select grant_expires_at from agent_board.delegation_grants where child_agent_id = (select agent_id from _sid_action_children where label = 'parent')), now() + interval '7 days',
|
|
101
|
+
'EC-SIDSCHEMA-5-renew-control-state: same grant reaches the approved effective ceiling');
|
|
102
|
+
update agent_board.session_identities set last_heartbeat = now() - interval '60 seconds'
|
|
103
|
+
where agent_id = (select agent_id from _sid_action_children where label = 'parent');
|
|
104
|
+
select set_config('request.jwt.claim.role', 'authenticated', true);
|
|
105
|
+
set local role authenticated;
|
|
106
|
+
select lives_ok($q$select agent_board.heartbeat_session_identity((select agent_id from _sid_action_children where label = 'parent'))$q$,
|
|
107
|
+
'EC-SIDSCHEMA-5-heartbeat-control: unfrozen current epoch refreshes the live lease');
|
|
108
|
+
reset role;
|
|
109
|
+
select is((select last_heartbeat from agent_board.session_identities where agent_id = (select agent_id from _sid_action_children where label = 'parent')), now(),
|
|
110
|
+
'EC-SIDSCHEMA-5-heartbeat-control-state: heartbeat changed');
|
|
111
|
+
update agent_board.session_identities set resolution_state = 'frozen', last_heartbeat = now() - interval '60 seconds'
|
|
112
|
+
where agent_id = (select agent_id from _sid_action_children where label = 'parent');
|
|
113
|
+
select ok((select resolution_state = 'frozen' and approved_risky_digest = current_risky_digest
|
|
114
|
+
from agent_board.session_identities where agent_id = (select agent_id from _sid_action_children where label = 'parent')),
|
|
115
|
+
'EC-SIDSCHEMA-5-frozen-fixture: explicit freeze is isolated from digest mismatch');
|
|
116
|
+
select set_config('request.jwt.claim.role', 'service_role', true);
|
|
117
|
+
set local role service_role;
|
|
118
|
+
select throws_ok(format('select pg_temp.sid_action_refuse(%L)', $q$select pg_temp.sid_action_token('frozen')$q$),
|
|
119
|
+
'42501', 'identity_fingerprint_unapproved', 'EC-SIDSCHEMA-5-frozen-token: frozen holder cannot mint a token');
|
|
120
|
+
select throws_ok(format('select pg_temp.sid_action_refuse(%L)', $q$select pg_temp.sid_action_mint('frozen-child', 'parent')$q$),
|
|
121
|
+
'42501', 'identity_fingerprint_unapproved', 'EC-SIDSCHEMA-5-frozen-child: frozen parent cannot mint a child');
|
|
122
|
+
select throws_ok(format('select pg_temp.sid_action_refuse(%L)', $q$select agent_board.link_session_alias((select agent_id from _sid_action_children where label = 'parent'), '0', 'action-frozen')$q$),
|
|
123
|
+
'42501', 'identity_fingerprint_unapproved', 'EC-SIDSCHEMA-5-frozen-link: frozen holder cannot link');
|
|
124
|
+
select throws_ok(format('select pg_temp.sid_action_refuse(%L)', $q$select agent_board.renew_session_identity((select agent_id from _sid_action_children where label = 'parent'), 'approved')$q$),
|
|
125
|
+
'42501', 'identity_fingerprint_unapproved', 'EC-SIDSCHEMA-5-frozen-renew: frozen holder cannot renew');
|
|
126
|
+
reset role;
|
|
127
|
+
select set_config('request.jwt.claim.role', 'authenticated', true);
|
|
128
|
+
set local role authenticated;
|
|
129
|
+
select throws_ok(format('select pg_temp.sid_action_refuse(%L)', $q$select agent_board.heartbeat_session_identity((select agent_id from _sid_action_children where label = 'parent'))$q$),
|
|
130
|
+
'42501', 'identity_fingerprint_unapproved', 'EC-SIDSCHEMA-5-frozen-heartbeat: frozen holder cannot heartbeat');
|
|
131
|
+
reset role;
|
|
132
|
+
select is((select count(*) from _sid_action_tokens), 2::bigint, 'EC-SIDSCHEMA-5-frozen-token-state: refusals returned no further token result');
|
|
133
|
+
select is((select count(*) from _sid_action_children), 2::bigint, 'EC-SIDSCHEMA-5-frozen-child-state: refusal left no child');
|
|
134
|
+
select is((select count(*) from agent_board.session_id_aliases where identity_agent_id = (select agent_id from _sid_action_children where label = 'parent')), 2::bigint,
|
|
135
|
+
'EC-SIDSCHEMA-5-frozen-link-state: refusal left aliases unchanged');
|
|
136
|
+
select is((select last_heartbeat from agent_board.session_identities where agent_id = (select agent_id from _sid_action_children where label = 'parent')), now() - interval '60 seconds',
|
|
137
|
+
'EC-SIDSCHEMA-5-frozen-lease-state: neither renewal nor heartbeat refreshed the lease');
|
|
138
|
+
select set_config('request.jwt.claim.sub', (select owner_auth::text from _sid_action_root), true);
|
|
139
|
+
set local role authenticated;
|
|
140
|
+
select lives_ok($q$select agent_board.revoke_session_identity((select agent_id from _sid_action_children where label = 'parent'))$q$,
|
|
141
|
+
'EC-SIDSCHEMA-5-tombstone-control: human owner revokes the selected frozen identity');
|
|
142
|
+
reset role;
|
|
143
|
+
select is((select count(*) from agent_board.session_identities where agent_id in (select agent_id from _sid_action_children) and tombstoned_at is not null), 2::bigint,
|
|
144
|
+
'EC-SIDSCHEMA-5-tombstone-control-state: parent and descendant are tombstoned');
|
|
145
|
+
select set_config('request.jwt.claim.role', 'service_role', true);
|
|
146
|
+
set local role service_role;
|
|
147
|
+
select throws_ok(format('select pg_temp.sid_action_refuse(%L)', $q$select pg_temp.sid_action_mint('remint', null, 'action-parent')$q$),
|
|
148
|
+
'42501', 'identity_tombstoned', 'EC-SIDSCHEMA-5-tombstone-remint: tombstoned alias cannot re-mint a principal');
|
|
149
|
+
select throws_ok(format('select pg_temp.sid_action_refuse(%L)', $q$select pg_temp.sid_action_mint('fork', null, null, 'parent')$q$),
|
|
150
|
+
'42501', 'identity_tombstoned', 'EC-SIDSCHEMA-5-tombstone-fork: tombstoned identity cannot supply fork ancestry');
|
|
151
|
+
reset role;
|
|
152
|
+
select is((select count(*) from _sid_action_children), 2::bigint, 'EC-SIDSCHEMA-5-tombstone-refusal-state: neither refusal created a new identity');
|
|
153
|
+
select is((select credential_epoch from agent_board.session_identities where agent_id = (select agent_id from _sid_action_children where label = 'parent')), 1::bigint,
|
|
154
|
+
'EC-SIDSCHEMA-5-tombstone-epoch: revocation fenced the old holder');
|
|
155
|
+
select finish();
|
|
156
|
+
rollback;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
-- Fork siblings are outside the legacy grant subtree. Each legacy entrypoint
|
|
2
|
+
-- must still close their identities, aliases and every principal credential.
|
|
3
|
+
create extension if not exists pgtap with schema extensions;
|
|
4
|
+
set search_path = extensions, public, agent_board;
|
|
5
|
+
begin;
|
|
6
|
+
select plan(74);
|
|
7
|
+
grant usage on schema extensions to service_role, authenticated;
|
|
8
|
+
create temp table _sid_revoke_roots(route text primary key, root_id uuid, owner_auth uuid);
|
|
9
|
+
create temp table _sid_revoke_children(route text, label text, agent_id uuid, key_id uuid, primary key(route,label));
|
|
10
|
+
create function pg_temp.sid_revoke_fixture(p_route text)
|
|
11
|
+
returns void language plpgsql security definer set search_path = pg_temp, pg_catalog as $$
|
|
12
|
+
declare r uuid := gen_random_uuid(); u uuid; m record; item record; parent uuid; fork uuid;
|
|
13
|
+
begin
|
|
14
|
+
u := public.ensure_github_user('sid-revoke-' || r, 'sid-revoke-' || r || '@example.test', 'sid-revoke-' || r, 'Revoke owner');
|
|
15
|
+
if p_route='hosted-instance' then
|
|
16
|
+
u := agent_board._ensure_agent_auth_user(agent_board._federated_identity_slug('https://sid-revoke.example.test',r::text));
|
|
17
|
+
end if;
|
|
18
|
+
insert into agent_board.agents(agent_type, handle, display_name, status, auth_user_id)
|
|
19
|
+
values ('human', 'sid-revoke-human-' || r, 'Revoke owner', 'active', u);
|
|
20
|
+
insert into agent_board.agents(id, agent_type, handle, display_name, status, owner_user_id,is_delegate)
|
|
21
|
+
values (r, 'agent', 'sid-revoke-root-' || r, 'Revoke root', 'active', u,p_route='hosted-instance');
|
|
22
|
+
insert into agent_board.session_identities(agent_id, root_agent_id, depth, current_risky_digest, approved_risky_digest)
|
|
23
|
+
values (r, r, 0, 'approved', 'approved');
|
|
24
|
+
insert into _sid_revoke_roots values (p_route,r,u);
|
|
25
|
+
perform agent_board.upsert_delegation_policy(r, 'root_minted', '["read"]', 200, interval '7 days', true);
|
|
26
|
+
for item in select * from (values ('source',null::text,null::text), ('fork',null,'source'),
|
|
27
|
+
('transitive-fork',null,'fork'), ('descendant','fork',null)) v(label,parent_label,fork_label)
|
|
28
|
+
loop
|
|
29
|
+
parent := coalesce((select agent_id from _sid_revoke_children where route=p_route and label=item.parent_label),r);
|
|
30
|
+
fork := (select agent_id from _sid_revoke_children where route=p_route and label=item.fork_label);
|
|
31
|
+
select * into m from agent_board.mint_delegated_child(parent,
|
|
32
|
+
'sid-revoke-' || item.label || '-' || substr(r::text,1,8), '["read"]',
|
|
33
|
+
public.test_pubkey(r::text || item.label), interval '1 hour', false, 'claude',
|
|
34
|
+
p_route || '-' || item.label, case when parent=r then null else '0' end, '[]',
|
|
35
|
+
'{"resolution":"resolved","risky_digest":"approved"}',fork);
|
|
36
|
+
insert into _sid_revoke_children values (p_route,item.label,m.child_agent_id,m.signing_key_id);
|
|
37
|
+
perform * from agent_board._issue_agent_credential(m.child_agent_id,'closure-fixture');
|
|
38
|
+
end loop;
|
|
39
|
+
if p_route='hosted-instance' then
|
|
40
|
+
insert into agent_board.delegate_instances(instance_id,owner_user_id,owner_issuer,owner_subject,agent_id)
|
|
41
|
+
values(r::text,u,'https://sid-revoke.example.test',r::text,r);
|
|
42
|
+
insert into agent_board.boards(id,slug,name,created_by_agent_id,visibility,status)
|
|
43
|
+
values(r,'sid-revoke-board-' || r,'Revoke board',r,'private','active');
|
|
44
|
+
insert into agent_board.board_memberships(board_id,agent_id,role,membership_state,created_by_agent_id)
|
|
45
|
+
values(r,r,'member','joined',r);
|
|
46
|
+
end if;
|
|
47
|
+
end $$;
|
|
48
|
+
create function pg_temp.sid_revoke_call(p_route text)
|
|
49
|
+
returns void language plpgsql as $$
|
|
50
|
+
declare r record; target uuid;
|
|
51
|
+
begin
|
|
52
|
+
select * into r from _sid_revoke_roots where route=p_route;
|
|
53
|
+
select agent_id into target from _sid_revoke_children where route=p_route and label='source';
|
|
54
|
+
perform set_config('request.jwt.claim.sub',r.owner_auth::text,true);
|
|
55
|
+
case p_route
|
|
56
|
+
when 'principal' then perform agent_board._revoke_agent_principal(target,r.root_id,'closure fixture');
|
|
57
|
+
when 'agent-subtree' then perform * from agent_board.revoke_agent_subtree(target,r.root_id,'closure fixture');
|
|
58
|
+
when 'human-subtree' then perform * from agent_board.revoke_human_subtree(r.owner_auth,r.root_id,'closure fixture');
|
|
59
|
+
when 'fleet-agent' then perform * from agent_board.revoke_fleet_subject('agent',target,r.root_id,'closure fixture');
|
|
60
|
+
when 'admin-agent' then perform * from agent_board.revoke_agent(target,'closure fixture');
|
|
61
|
+
when 'hosted-instance' then perform * from agent_board.tombstone_delegate_instance(r.root_id::text,
|
|
62
|
+
'https://sid-revoke.example.test',r.root_id::text,'owner_revoked');
|
|
63
|
+
when 'session-owner' then
|
|
64
|
+
perform set_config('request.jwt.claim.sub',r.owner_auth::text,true);
|
|
65
|
+
perform agent_board.revoke_session_identity(target);
|
|
66
|
+
end case;
|
|
67
|
+
end $$;
|
|
68
|
+
grant execute on function pg_temp.sid_revoke_fixture(text), pg_temp.sid_revoke_call(text) to service_role;
|
|
69
|
+
grant execute on function pg_temp.sid_revoke_call(text) to authenticated;
|
|
70
|
+
grant select,insert on _sid_revoke_roots,_sid_revoke_children to service_role;
|
|
71
|
+
grant select on _sid_revoke_roots,_sid_revoke_children to authenticated;
|
|
72
|
+
select set_config('request.jwt.claim.role','service_role',true);
|
|
73
|
+
set local role service_role;
|
|
74
|
+
select lives_ok(format('select pg_temp.sid_revoke_fixture(%L)',route),
|
|
75
|
+
'EC-SIDSCHEMA-5-closure-fixture-' || route || ': real minted fork, fork-of-fork, descendant and legacy credentials')
|
|
76
|
+
from unnest(array['principal','agent-subtree','human-subtree','fleet-agent','admin-agent','session-owner','hosted-instance']) route;
|
|
77
|
+
reset role;
|
|
78
|
+
select is((select count(*) from _sid_revoke_children where route=r.route),4::bigint,
|
|
79
|
+
'EC-SIDSCHEMA-5-closure-fixture-state-' || route || ': four real children exist') from _sid_revoke_roots r order by route;
|
|
80
|
+
set local role service_role;
|
|
81
|
+
select lives_ok(format('select pg_temp.sid_revoke_call(%L)',route),
|
|
82
|
+
'EC-SIDSCHEMA-5-closure-call-' || route || ': authorized entrypoint executes') from _sid_revoke_roots where route <> 'session-owner' order by route;
|
|
83
|
+
reset role;
|
|
84
|
+
select set_config('request.jwt.claim.role','authenticated',true);
|
|
85
|
+
select set_config('request.jwt.claim.sub',(select owner_auth::text from _sid_revoke_roots where route='session-owner'),true);
|
|
86
|
+
set local role authenticated;
|
|
87
|
+
select lives_ok($$select pg_temp.sid_revoke_call('session-owner')$$,
|
|
88
|
+
'EC-SIDSCHEMA-5-closure-call-session-owner: authenticated human owner executes the session entrypoint');
|
|
89
|
+
reset role;
|
|
90
|
+
select is((select count(*) from _sid_revoke_children c join agent_board.session_identities si on si.agent_id=c.agent_id
|
|
91
|
+
where c.route=r.route and si.tombstoned_at is not null),4::bigint,
|
|
92
|
+
'EC-SIDSCHEMA-5-closure-tombstones-' || route || ': source, transitive forks and descendant are tombstoned') from _sid_revoke_roots r order by route;
|
|
93
|
+
select is((select count(*) from _sid_revoke_children c join agent_board.session_identities si on si.agent_id=c.agent_id
|
|
94
|
+
where c.route=r.route and si.credential_epoch=1),4::bigint,
|
|
95
|
+
'EC-SIDSCHEMA-5-closure-epochs-' || route || ': each identity epoch is fenced exactly once') from _sid_revoke_roots r order by route;
|
|
96
|
+
select is((select count(*) from _sid_revoke_children c join agent_board.agents a on a.id=c.agent_id
|
|
97
|
+
join agent_board.agent_signing_keys k on k.id=c.key_id join agent_board.delegation_grants g on g.child_agent_id=c.agent_id
|
|
98
|
+
where c.route=r.route and a.status='revoked' and k.status='revoked' and k.proof_state='revoked' and g.revoked_at is not null),4::bigint,
|
|
99
|
+
'EC-SIDSCHEMA-5-closure-principals-' || route || ': status, keys and grants write through') from _sid_revoke_roots r order by route;
|
|
100
|
+
select is((select count(*) from _sid_revoke_children c join agent_board.agent_credentials ac on ac.agent_id=c.agent_id
|
|
101
|
+
where c.route=r.route and ac.revoked_at is not null),4::bigint,
|
|
102
|
+
'EC-SIDSCHEMA-5-closure-credentials-' || route || ': legacy credential issuer state writes through') from _sid_revoke_roots r order by route;
|
|
103
|
+
select is((select count(*) from _sid_revoke_children c join agent_board.session_id_aliases a on a.identity_agent_id=c.agent_id
|
|
104
|
+
join agent_board.session_identities si on si.agent_id=a.identity_agent_id
|
|
105
|
+
where c.route=r.route and si.tombstoned_at is not null),4::bigint,
|
|
106
|
+
'EC-SIDSCHEMA-5-closure-aliases-' || route || ': all reserved alias targets are tombstoned') from _sid_revoke_roots r order by route;
|
|
107
|
+
select is((select count(*) from _sid_revoke_children c join agent_board.session_identities si on si.agent_id=c.agent_id
|
|
108
|
+
where c.route=r.route and c.label in ('fork','transitive-fork') and si.tombstoned_at is not null),2::bigint,
|
|
109
|
+
'EC-SIDSCHEMA-5-closure-forks-' || route || ': both fork generations are independently tombstoned') from _sid_revoke_roots r order by route;
|
|
110
|
+
select is((select count(*) from _sid_revoke_children c join agent_board.session_identities si on si.agent_id=c.agent_id
|
|
111
|
+
where c.route=r.route and c.label='descendant' and si.tombstoned_at is not null),1::bigint,
|
|
112
|
+
'EC-SIDSCHEMA-5-closure-descendants-' || route || ': descendant tombstone is checked independently of forks') from _sid_revoke_roots r order by route;
|
|
113
|
+
select set_config('request.jwt.claim.role','service_role',true);
|
|
114
|
+
set local role service_role;
|
|
115
|
+
select throws_ok($$select agent_board.mint_delegated_child((select root_id from _sid_revoke_roots where route='principal'),
|
|
116
|
+
'sid-revoked-alias-' || gen_random_uuid(),'["read"]',public.test_pubkey('revoked-alias'),interval '1 hour',false,
|
|
117
|
+
'claude','principal-source',null,'[]','{"resolution":"resolved","risky_digest":"approved"}')$$,
|
|
118
|
+
'42501','identity_tombstoned','EC-SIDSCHEMA-5-closure-alias-refusal: a legacy principal revoke reserves its tombstoned session alias against re-mint');
|
|
119
|
+
reset role;
|
|
120
|
+
select is((select count(*) from agent_board.session_identities where root_agent_id=(select root_id from _sid_revoke_roots where route='principal')),5::bigint,
|
|
121
|
+
'EC-SIDSCHEMA-5-closure-alias-refusal-state: alias refusal leaves only the root and four original identities');
|
|
122
|
+
select is((select membership_state || '/' || role from agent_board.board_memberships
|
|
123
|
+
where board_id=(select root_id from _sid_revoke_roots where route='hosted-instance')
|
|
124
|
+
and agent_id=(select root_id from _sid_revoke_roots where route='hosted-instance')),
|
|
125
|
+
'left/member','EC-SIDSCHEMA-5-instance-withdrawal-state: the real verified-instance tombstone withdraws its membership without elevating its role');
|
|
126
|
+
select is((select count(*) from agent_board.board_membership_events
|
|
127
|
+
where board_id=(select root_id from _sid_revoke_roots where route='hosted-instance')
|
|
128
|
+
and event_kind='operator_removal'),1::bigint,
|
|
129
|
+
'EC-SIDSCHEMA-5-instance-withdrawal-event: withdrawal writes exactly one append-only operator removal for this board');
|
|
130
|
+
select finish();
|
|
131
|
+
rollback;
|
|
@@ -0,0 +1,107 @@
|
|
|
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(18);
|
|
6
|
+
|
|
7
|
+
create temp table _owner_lease_fixture(label text primary key, agent_id uuid, auth_id uuid);
|
|
8
|
+
do $$
|
|
9
|
+
declare v_label text; v_id uuid; v_auth uuid; v_root uuid; v_root_auth uuid; v_a uuid; v_b uuid; v_live uuid;
|
|
10
|
+
begin
|
|
11
|
+
foreach v_label in array array['root','branch-a','branch-b','unclaimed','live','lapsed','boundary','cross-parent-fork'] loop
|
|
12
|
+
v_id := gen_random_uuid();
|
|
13
|
+
v_auth := public.ensure_github_user('owner-lease-' || v_id, v_id || '@example.test', v_id::text, v_label);
|
|
14
|
+
if v_label='root' then v_root := v_id; v_root_auth := v_auth; end if;
|
|
15
|
+
if v_label='branch-a' then v_a := v_id; end if;
|
|
16
|
+
if v_label='branch-b' then v_b := v_id; end if;
|
|
17
|
+
if v_label='live' then v_live := v_id; end if;
|
|
18
|
+
insert into agent_board.agents(id, agent_type, handle, display_name, status, auth_user_id, owner_user_id, parent_agent_id, root_agent_id)
|
|
19
|
+
values(v_id,'agent','owner-lease-' || v_id,v_label,'active',v_auth,v_root_auth,
|
|
20
|
+
case when v_label='root' then null when v_label in ('branch-a','branch-b') then v_root
|
|
21
|
+
when v_label in ('boundary','cross-parent-fork') then v_b else v_a end,
|
|
22
|
+
case when v_label='root' then null else v_root end);
|
|
23
|
+
insert into agent_board.session_identities(agent_id,parent_agent_id,root_agent_id,depth,identity_kind,harness,
|
|
24
|
+
holder_secret_hash,last_heartbeat,credential_epoch,current_risky_digest,approved_risky_digest,recorded_fingerprint,forked_from)
|
|
25
|
+
values(v_id,case when v_label='root' then null when v_label in ('branch-a','branch-b') then v_root
|
|
26
|
+
when v_label in ('boundary','cross-parent-fork') then v_b else v_a end,v_root,
|
|
27
|
+
case when v_label='root' then 0 when v_label in ('branch-a','branch-b') then 1 else 2 end,
|
|
28
|
+
case when v_label in ('root','branch-a','branch-b') then 'legacy' else 'session' end,
|
|
29
|
+
case when v_label in ('root','branch-a','branch-b') then null else 'codex' end,
|
|
30
|
+
case when v_label in ('live','lapsed','boundary') then digest(v_label,'sha256') else null end,
|
|
31
|
+
case when v_label='live' then now()-interval '179 seconds'
|
|
32
|
+
when v_label='lapsed' then now()-interval '181 seconds'
|
|
33
|
+
when v_label='boundary' then now()-interval '180 seconds' else null end,
|
|
34
|
+
7,repeat('a',64),repeat('b',64),jsonb_build_object('resolution','resolved','recorded',jsonb_build_object('risky_inputs','{}'::jsonb)),
|
|
35
|
+
case when v_label='cross-parent-fork' then v_live else null end);
|
|
36
|
+
insert into _owner_lease_fixture values(v_label,v_id,v_auth);
|
|
37
|
+
end loop;
|
|
38
|
+
end;
|
|
39
|
+
$$;
|
|
40
|
+
set constraints all immediate;
|
|
41
|
+
set constraints all deferred;
|
|
42
|
+
grant select on _owner_lease_fixture to authenticated;
|
|
43
|
+
create temp table _owner_lease_before as
|
|
44
|
+
select jsonb_agg(to_jsonb(si) order by si.agent_id) as rows from agent_board.session_identities si
|
|
45
|
+
join _owner_lease_fixture f on f.agent_id=si.agent_id;
|
|
46
|
+
grant select on _owner_lease_before to authenticated;
|
|
47
|
+
select set_config('request.jwt.claims','{}',true);
|
|
48
|
+
select set_config('request.jwt.claim.sub',(select auth_id::text from _owner_lease_fixture where label='root'),true);
|
|
49
|
+
select set_config('request.jwt.claim.role','authenticated',true);
|
|
50
|
+
select set_config('request.jwt.claim.agent_credential_id','',true);
|
|
51
|
+
select set_config('request.jwt.claim.identity_lease_epoch','',true);
|
|
52
|
+
set local role authenticated;
|
|
53
|
+
create temp table _owner_lease_states as select * from agent_board.list_session_children((select agent_id from _owner_lease_fixture where label='root')) with no data;
|
|
54
|
+
select lives_ok($$insert into _owner_lease_states select * from agent_board.list_session_children((select agent_id from _owner_lease_fixture where label='root'))$$,
|
|
55
|
+
'SIDOWNER-lease-control: root enters reader with all lease states');
|
|
56
|
+
select is((select lease_state from _owner_lease_states where agent_id=(select agent_id from _owner_lease_fixture where label='live')),
|
|
57
|
+
'live','SIDOWNER-lease-live: 179-second heartbeat is live');
|
|
58
|
+
select is((select lease_state from _owner_lease_states where agent_id=(select agent_id from _owner_lease_fixture where label='lapsed')),
|
|
59
|
+
'lapsed','SIDOWNER-lease-lapsed: 181-second heartbeat is lapsed');
|
|
60
|
+
select is((select lease_state from _owner_lease_states where agent_id=(select agent_id from _owner_lease_fixture where label='boundary')),
|
|
61
|
+
'lapsed','SIDOWNER-lease-boundary: exactly 180 seconds is lapsed');
|
|
62
|
+
select is((select lease_state from _owner_lease_states where agent_id=(select agent_id from _owner_lease_fixture where label='unclaimed')),
|
|
63
|
+
'unclaimed','SIDOWNER-lease-unclaimed: reserved identity has no claimed holder');
|
|
64
|
+
select is((select lease_state from _owner_lease_states where agent_id=(select agent_id from _owner_lease_fixture where label='branch-a')),
|
|
65
|
+
'not_applicable','SIDOWNER-lease-legacy: legacy child has no session lease');
|
|
66
|
+
select is((select harness from _owner_lease_states where agent_id=(select agent_id from _owner_lease_fixture where label='live')),
|
|
67
|
+
'codex','SIDOWNER-harness: normalized projection preserves session harness');
|
|
68
|
+
create temp table _owner_lease_branch as select * from agent_board.list_session_children((select agent_id from _owner_lease_fixture where label='branch-a')) with no data;
|
|
69
|
+
select lives_ok($$insert into _owner_lease_branch select * from agent_board.list_session_children((select agent_id from _owner_lease_fixture where label='branch-a'))$$,
|
|
70
|
+
'SIDOWNER-fork-control: ancestor enters branch reader');
|
|
71
|
+
select is((select count(*) from _owner_lease_branch),3::bigint,
|
|
72
|
+
'SIDOWNER-branch-subtree: selected branch contains every strict child');
|
|
73
|
+
select is((select count(*) from _owner_lease_branch where agent_id=(select agent_id from _owner_lease_fixture where label='cross-parent-fork')),0::bigint,
|
|
74
|
+
'SIDOWNER-fork-edge: fork provenance never traverses another parent');
|
|
75
|
+
select is((select current_risky_digest from _owner_lease_states where agent_id=(select agent_id from _owner_lease_fixture where label='live')),
|
|
76
|
+
repeat('a',64),'SIDOWNER-current-digest: current digest is not replaced by approval');
|
|
77
|
+
select is((select approved_risky_digest from _owner_lease_states where agent_id=(select agent_id from _owner_lease_fixture where label='live')),
|
|
78
|
+
repeat('b',64),'SIDOWNER-approved-digest: distinct stored approval is preserved');
|
|
79
|
+
select is((select recorded_fingerprint from _owner_lease_states where agent_id=(select agent_id from _owner_lease_fixture where label='live')),
|
|
80
|
+
jsonb_build_object('resolution','resolved','recorded',jsonb_build_object('risky_inputs','{}'::jsonb)),
|
|
81
|
+
'SIDOWNER-reading-projection: state returns only the stored normalized reading');
|
|
82
|
+
reset role;
|
|
83
|
+
select is((select jsonb_agg(to_jsonb(si) order by si.agent_id) from agent_board.session_identities si
|
|
84
|
+
join _owner_lease_fixture f on f.agent_id=si.agent_id),(select rows from _owner_lease_before),
|
|
85
|
+
'SIDOWNER-full-row-read-only: readers leave every stored identity field byte-equivalent');
|
|
86
|
+
select set_config('request.jwt.claim.sub',(select auth_id::text from _owner_lease_fixture where label='live'),true);
|
|
87
|
+
select set_config('request.jwt.claim.identity_lease_epoch','7',true);
|
|
88
|
+
set local role authenticated;
|
|
89
|
+
select lives_ok($$select * from agent_board.get_session_identity_state((select agent_id from _owner_lease_fixture where label='live'))$$,
|
|
90
|
+
'SIDOWNER-session-self-control: current-epoch session reads its own state');
|
|
91
|
+
select set_config('request.jwt.claim.identity_lease_epoch','6',true);
|
|
92
|
+
select throws_ok($$select * from agent_board.get_session_identity_state((select agent_id from _owner_lease_fixture where label='live'))$$,
|
|
93
|
+
'42501','identity_read_forbidden','SIDOWNER-stale-epoch: stale session credential cannot read its own state');
|
|
94
|
+
reset role;
|
|
95
|
+
select set_config('request.jwt.claim.sub',(select auth_id::text from _owner_lease_fixture where label='root'),true);
|
|
96
|
+
select set_config('request.jwt.claim.identity_lease_epoch','',true);
|
|
97
|
+
update agent_board.session_identities set tombstoned_at=now(),tombstone_reason='revoked'
|
|
98
|
+
where agent_id=(select agent_id from _owner_lease_fixture where label='live');
|
|
99
|
+
set local role authenticated;
|
|
100
|
+
create temp table _owner_lease_tombstone as select * from agent_board.get_session_identity_state((select agent_id from _owner_lease_fixture where label='live')) with no data;
|
|
101
|
+
select lives_ok($$insert into _owner_lease_tombstone select * from agent_board.get_session_identity_state((select agent_id from _owner_lease_fixture where label='live'))$$,
|
|
102
|
+
'SIDOWNER-tombstone-lease-control: ancestor reads claimed tombstone');
|
|
103
|
+
select is((select lease_state from _owner_lease_tombstone),'lapsed',
|
|
104
|
+
'SIDOWNER-tombstone-lease: tombstoned claimed identity follows the existing not-live helper');
|
|
105
|
+
reset role;
|
|
106
|
+
select * from finish();
|
|
107
|
+
rollback;
|
|
@@ -0,0 +1,97 @@
|
|
|
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;
|
|
5
|
+
select plan(15);
|
|
6
|
+
create temp table _sidowner_n1(label text primary key, agent_id uuid, auth_id uuid, before_state jsonb);
|
|
7
|
+
do $$
|
|
8
|
+
declare v_label text; v_id uuid; v_auth uuid; v_human uuid; v_inactive uuid; v_gotrue uuid;
|
|
9
|
+
begin
|
|
10
|
+
v_id := gen_random_uuid();
|
|
11
|
+
v_human := public.ensure_github_user('n1-human-' || v_id, v_id || '@example.test', v_id::text, 'N1 human');
|
|
12
|
+
insert into agent_board.agents(agent_type,handle,display_name,status,auth_user_id)
|
|
13
|
+
values ('human','n1-human-' || v_id,'N1 human','active',v_human);
|
|
14
|
+
v_id := gen_random_uuid();
|
|
15
|
+
v_inactive := public.ensure_github_user('n1-inactive-' || v_id, v_id || '@example.test', v_id::text, 'N1 inactive human');
|
|
16
|
+
insert into agent_board.agents(agent_type,handle,display_name,status,auth_user_id)
|
|
17
|
+
values ('human','n1-inactive-' || v_id,'N1 inactive human','suspended',v_inactive);
|
|
18
|
+
v_gotrue := gen_random_uuid();
|
|
19
|
+
insert into auth.users(id,email) values (v_gotrue,v_gotrue || '@example.test');
|
|
20
|
+
foreach v_label in array array['human-owned','self-owned','gotrue-owned','inactive-human'] loop
|
|
21
|
+
v_id := gen_random_uuid();
|
|
22
|
+
v_auth := public.ensure_github_user('n1-root-' || v_id, v_id || '@example.test', v_id::text, 'N1 root');
|
|
23
|
+
insert into agent_board.agents(id,agent_type,handle,display_name,status,auth_user_id,owner_user_id)
|
|
24
|
+
values(v_id,'agent','n1-root-' || v_id,'N1 root','active',v_auth,
|
|
25
|
+
case v_label when 'human-owned' then v_human when 'self-owned' then v_auth
|
|
26
|
+
when 'gotrue-owned' then v_gotrue else v_inactive end);
|
|
27
|
+
insert into _sidowner_n1 values(v_label,v_id,v_auth,null);
|
|
28
|
+
end loop;
|
|
29
|
+
end;
|
|
30
|
+
$$;
|
|
31
|
+
set constraints all immediate;
|
|
32
|
+
update _sidowner_n1 f set before_state = (select to_jsonb(si) from agent_board.session_identities si where si.agent_id=f.agent_id);
|
|
33
|
+
grant select on _sidowner_n1 to service_role;
|
|
34
|
+
create function pg_temp.sidowner_n1_subject(p_label text) returns void language plpgsql as $$
|
|
35
|
+
begin
|
|
36
|
+
perform set_config('request.jwt.claims','{}',true);
|
|
37
|
+
perform set_config('request.jwt.claim.role','service_role',true);
|
|
38
|
+
perform set_config('request.jwt.claim.sub',(select auth_id::text from _sidowner_n1 where label=p_label),true);
|
|
39
|
+
end;
|
|
40
|
+
$$;
|
|
41
|
+
grant execute on function pg_temp.sidowner_n1_subject(text) to service_role;
|
|
42
|
+
|
|
43
|
+
select has_function('agent_board','_session_identity_has_human_owner',array['uuid'],'SIDOWNER-N1-owner-existence: SQL owner-existence guard exists');
|
|
44
|
+
select ok(has_function_privilege('service_role','agent_board._session_identity_has_human_owner(uuid)','EXECUTE')
|
|
45
|
+
and not has_function_privilege('public','agent_board._session_identity_has_human_owner(uuid)','EXECUTE')
|
|
46
|
+
and not has_function_privilege('anon','agent_board._session_identity_has_human_owner(uuid)','EXECUTE')
|
|
47
|
+
and not has_function_privilege('authenticated','agent_board._session_identity_has_human_owner(uuid)','EXECUTE')
|
|
48
|
+
and obj_description('agent_board._session_identity_has_human_owner(uuid)'::regprocedure,'pg_proc') is not null,
|
|
49
|
+
'SIDOWNER-N1-private-guard: SQL helper is service-only and documented');
|
|
50
|
+
select ok(has_function_privilege('service_role','agent_board.record_session_identity_state(uuid,jsonb)','EXECUTE')
|
|
51
|
+
and not has_function_privilege('public','agent_board.record_session_identity_state(uuid,jsonb)','EXECUTE')
|
|
52
|
+
and not has_function_privilege('anon','agent_board.record_session_identity_state(uuid,jsonb)','EXECUTE')
|
|
53
|
+
and not has_function_privilege('authenticated','agent_board.record_session_identity_state(uuid,jsonb)','EXECUTE'),
|
|
54
|
+
'SIDOWNER-N1-private-recording: recorder remains service-only');
|
|
55
|
+
select is(agent_board._session_identity_has_human_owner((select agent_id from _sidowner_n1 where label='human-owned')),true,
|
|
56
|
+
'SIDOWNER-N1-human-existence: active human bound to the root owner exists');
|
|
57
|
+
select is(agent_board._session_identity_has_human_owner((select agent_id from _sidowner_n1 where label='self-owned')),false,
|
|
58
|
+
'SIDOWNER-N1-self-existence: a self-owned agent is not a human owner');
|
|
59
|
+
select is(agent_board._session_identity_has_human_owner((select agent_id from _sidowner_n1 where label='gotrue-owned')),false,
|
|
60
|
+
'SIDOWNER-N1-gotrue-existence: GoTrue user without a human agent is not a human owner');
|
|
61
|
+
select is(agent_board._session_identity_has_human_owner((select agent_id from _sidowner_n1 where label='inactive-human')),false,
|
|
62
|
+
'SIDOWNER-N1-active-existence: inactive human is not an active human owner');
|
|
63
|
+
set local role service_role;
|
|
64
|
+
select pg_temp.sidowner_n1_subject('human-owned');
|
|
65
|
+
select lives_ok($q$select agent_board.record_session_identity_state((select agent_id from _sidowner_n1 where label='human-owned'),
|
|
66
|
+
'{"resolution":"resolved","risky_digest":"n1-control","recorded":{}}')$q$,
|
|
67
|
+
'SIDOWNER-N1-human-owned-control: human-owned root records through the actual SQL recorder');
|
|
68
|
+
reset role;
|
|
69
|
+
select ok((select resolution_state='frozen' and current_risky_digest='n1-control' and recorded_fingerprint->>'resolution'='resolved'
|
|
70
|
+
from agent_board.session_identities where agent_id=(select agent_id from _sidowner_n1 where label='human-owned')),
|
|
71
|
+
'SIDOWNER-N1-human-owned-state: allowed recording really stores its reading and freeze');
|
|
72
|
+
set local role service_role;
|
|
73
|
+
select pg_temp.sidowner_n1_subject('self-owned');
|
|
74
|
+
select throws_ok($q$select agent_board.record_session_identity_state((select agent_id from _sidowner_n1 where label='self-owned'),
|
|
75
|
+
'{"resolution":"resolved","risky_digest":"n1-probe","recorded":{}}')$q$,'42501','identity_human_owner_required',
|
|
76
|
+
'SIDOWNER-N1-self-owned-refusal: self-owned root without human owner is refused before recording');
|
|
77
|
+
reset role;
|
|
78
|
+
select is((select to_jsonb(si) from agent_board.session_identities si where agent_id=(select agent_id from _sidowner_n1 where label='self-owned')),
|
|
79
|
+
(select before_state from _sidowner_n1 where label='self-owned'),'SIDOWNER-N1-self-owned-unchanged: no reading or freeze is stored');
|
|
80
|
+
set local role service_role;
|
|
81
|
+
select pg_temp.sidowner_n1_subject('gotrue-owned');
|
|
82
|
+
select throws_ok($q$select agent_board.record_session_identity_state((select agent_id from _sidowner_n1 where label='gotrue-owned'),
|
|
83
|
+
'{"resolution":"resolved","risky_digest":"n1-probe","recorded":{}}')$q$,'42501','identity_human_owner_required',
|
|
84
|
+
'SIDOWNER-N1-gotrue-refusal: GoTrue-owned root without human agent is refused before recording');
|
|
85
|
+
reset role;
|
|
86
|
+
select is((select to_jsonb(si) from agent_board.session_identities si where agent_id=(select agent_id from _sidowner_n1 where label='gotrue-owned')),
|
|
87
|
+
(select before_state from _sidowner_n1 where label='gotrue-owned'),'SIDOWNER-N1-gotrue-unchanged: no reading or freeze is stored');
|
|
88
|
+
set local role service_role;
|
|
89
|
+
select pg_temp.sidowner_n1_subject('inactive-human');
|
|
90
|
+
select throws_ok($q$select agent_board.record_session_identity_state((select agent_id from _sidowner_n1 where label='inactive-human'),
|
|
91
|
+
'{"resolution":"resolved","risky_digest":"n1-probe","recorded":{}}')$q$,'42501','identity_human_owner_required',
|
|
92
|
+
'SIDOWNER-N1-inactive-refusal: inactive human cannot enable recording');
|
|
93
|
+
reset role;
|
|
94
|
+
select is((select to_jsonb(si) from agent_board.session_identities si where agent_id=(select agent_id from _sidowner_n1 where label='inactive-human')),
|
|
95
|
+
(select before_state from _sidowner_n1 where label='inactive-human'),'SIDOWNER-N1-inactive-unchanged: no reading or freeze is stored');
|
|
96
|
+
select * from finish();
|
|
97
|
+
rollback;
|