@consiliency/agent-board-schema 1.59.0 → 1.63.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -0
- package/fixtures/already-applied.json +16 -0
- package/migrations/20261010000100_agent_board_own_post_not_delivered.sql +637 -0
- package/migrations/20261011000100_agent_board_session_identities.sql +1964 -0
- package/migrations/20261012000100_agent_board_sidowner.sql +209 -0
- package/migrations/20261014000100_agent_board_grant_target_role.sql +218 -0
- package/package.json +1 -1
- package/schema-manifest.json +39 -3
- package/src/tooling.test.mjs +9 -2
- package/tests/agent_board_addressing.test.sql +3 -1
- package/tests/agent_board_atomic_fleet_onboarding.test.sql +13 -4
- package/tests/agent_board_consent_emission.test.sql +30 -11
- package/tests/agent_board_consent_gating.test.sql +16 -2
- package/tests/agent_board_delegate_role_class.test.sql +1 -1
- package/tests/agent_board_delegation_cap_ceiling.test.sql +26 -15
- package/tests/agent_board_delegation_enforcement.test.sql +1 -1
- package/tests/agent_board_enroll_resumption.test.sql +22 -4
- package/tests/agent_board_enroll_token_single_use.test.sql +18 -3
- package/tests/agent_board_federated_lock_order_class.test.sql +2 -2
- package/tests/agent_board_fk_lock_edges.test.sql +74 -14
- package/tests/agent_board_grant_target_role.test.sql +1118 -0
- package/tests/agent_board_inbox_rpcs.test.sql +5 -2
- package/tests/agent_board_invite_no_downgrade.test.sql +10 -8
- package/tests/agent_board_ledger_authority.test.sql +70 -12
- package/tests/agent_board_membership_current_row_class.test.sql +9 -1
- package/tests/agent_board_mint_fleet_agent_session.test.sql +2 -2
- package/tests/agent_board_mint_harden.test.sql +38 -27
- package/tests/agent_board_own_post_not_delivered.test.sql +490 -0
- package/tests/agent_board_rolereset.test.sql +8 -2
- package/tests/agent_board_roomdeleg_lane_b.test.sql +6 -6
- package/tests/agent_board_rowid.test.sql +3 -1
- package/tests/agent_board_session_identity_approval_revocation.test.sql +334 -0
- package/tests/agent_board_session_identity_backfill.test.sql +82 -0
- package/tests/agent_board_session_identity_backfill_refusal.test.sql +14 -0
- package/tests/agent_board_session_identity_behavior_classes.test.sql +176 -0
- package/tests/agent_board_session_identity_class.test.sql +118 -0
- package/tests/agent_board_session_identity_fingerprint.test.sql +271 -0
- package/tests/agent_board_session_identity_labels_limits.test.sql +205 -0
- package/tests/agent_board_session_identity_lease.test.sql +235 -0
- package/tests/agent_board_session_identity_lifetime.test.sql +230 -0
- package/tests/agent_board_session_identity_limits.test.sql +208 -0
- package/tests/agent_board_session_identity_lineage.test.sql +64 -0
- package/tests/agent_board_session_identity_link.test.sql +201 -0
- package/tests/agent_board_session_identity_mint.test.sql +233 -0
- package/tests/agent_board_session_identity_panel_r1.test.sql +194 -0
- package/tests/agent_board_session_identity_panel_r2.test.sql +174 -0
- package/tests/agent_board_session_identity_protected_actions.test.sql +156 -0
- package/tests/agent_board_session_identity_revoke_closure.test.sql +131 -0
- package/tests/agent_board_sidowner_leases.test.sql +107 -0
- package/tests/agent_board_sidowner_owner_required.test.sql +97 -0
- package/tests/agent_board_sidowner_read_class.test.sql +171 -0
- package/tests/agent_board_sidowner_reads.test.sql +136 -0
- package/tests/agent_board_signer_lifecycle.test.sql +26 -4
- package/tests/agent_board_signing_challenge_pending_cap.test.sql +1 -1
- package/tests/agent_board_v2_invariants.test.sql +4 -4
- package/tests/agent_board_write_enforcement_class.test.sql +74 -15
- package/tests/catalog/function_execute_privileges.json +218 -2
- package/tests/catalog/p_metadata_parameters.json +98 -2
- package/tests/isolation/expected/grant_target_role_race.out +348 -0
- package/tests/isolation/expected/session_identity_limits.out +165 -0
- package/tests/isolation/expected/session_identity_links.out +235 -0
- package/tests/isolation/expected/session_identity_reclaim.out +253 -0
- package/tests/isolation/expected/session_identity_renew_revoke.out +115 -0
- package/tests/isolation/expected/session_identity_renew_revoke_key_gate.out +138 -0
- package/tests/isolation/expected/session_identity_tombstone_onboarding.out +247 -0
- package/tests/isolation/specs/grant_target_role_race.spec +207 -0
- package/tests/isolation/specs/session_identity_limits.spec +81 -0
- package/tests/isolation/specs/session_identity_links.spec +80 -0
- package/tests/isolation/specs/session_identity_reclaim.spec +125 -0
- package/tests/isolation/specs/session_identity_renew_revoke.spec +84 -0
- package/tests/isolation/specs/session_identity_renew_revoke_key_gate.spec +94 -0
- package/tests/isolation/specs/session_identity_tombstone_onboarding.spec +108 -0
- package/tests/seeds/20261011000100_agent_board_session_identities.sql.pre.sql +47 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
-- SIDOWNER read surface: catalog-derived projection and ACL claims.
|
|
2
|
+
create extension if not exists pgtap with schema extensions;
|
|
3
|
+
set search_path = extensions, public, agent_board;
|
|
4
|
+
begin;
|
|
5
|
+
select plan(24);
|
|
6
|
+
|
|
7
|
+
select has_function('agent_board', 'list_session_children', array['uuid'],
|
|
8
|
+
'EC-SIDOWNER-1: scoped children reader exists');
|
|
9
|
+
select has_function('agent_board', 'get_session_identity_state', array['uuid'],
|
|
10
|
+
'EC-SIDOWNER-1: scoped state reader exists');
|
|
11
|
+
select ok(has_function_privilege('authenticated', 'agent_board.list_session_children(uuid)', 'EXECUTE')
|
|
12
|
+
and has_function_privilege('service_role', 'agent_board.list_session_children(uuid)', 'EXECUTE')
|
|
13
|
+
and not has_function_privilege('public', 'agent_board.list_session_children(uuid)', 'EXECUTE')
|
|
14
|
+
and not has_function_privilege('anon', 'agent_board.list_session_children(uuid)', 'EXECUTE'),
|
|
15
|
+
'EC-SIDOWNER-1: children reader is authenticated/service-only');
|
|
16
|
+
select ok(has_function_privilege('authenticated', 'agent_board.get_session_identity_state(uuid)', 'EXECUTE')
|
|
17
|
+
and has_function_privilege('service_role', 'agent_board.get_session_identity_state(uuid)', 'EXECUTE')
|
|
18
|
+
and not has_function_privilege('public', 'agent_board.get_session_identity_state(uuid)', 'EXECUTE')
|
|
19
|
+
and not has_function_privilege('anon', 'agent_board.get_session_identity_state(uuid)', 'EXECUTE'),
|
|
20
|
+
'EC-SIDOWNER-1: state reader is authenticated/service-only');
|
|
21
|
+
select is(has_table_privilege('authenticated', 'agent_board.session_identities', 'SELECT'), false,
|
|
22
|
+
'EC-SIDOWNER-1: readers do not relax identity table ACL');
|
|
23
|
+
select is(has_table_privilege('authenticated', 'agent_board.session_id_aliases', 'SELECT'), false,
|
|
24
|
+
'EC-SIDOWNER-1: readers do not expose aliases');
|
|
25
|
+
select ok(agent_board_test.stripped_body('agent_board.list_session_children(uuid)'::regprocedure, true)
|
|
26
|
+
like '%parent_agent_id%' and agent_board_test.stripped_body('agent_board.list_session_children(uuid)'::regprocedure, true)
|
|
27
|
+
not like '%forked_from%',
|
|
28
|
+
'EC-SIDOWNER-1: children traversal uses only parent lineage');
|
|
29
|
+
select ok(pg_get_function_result('agent_board.get_session_identity_state(uuid)'::regprocedure)
|
|
30
|
+
!~ 'holder_secret_hash|credential_epoch|session_id_aliases|agent_credentials',
|
|
31
|
+
'EC-SIDOWNER-1: state projection excludes secrets and credential material');
|
|
32
|
+
select ok(agent_board_test.stripped_body('agent_board._session_identity_read_allowed(uuid)'::regprocedure, true)
|
|
33
|
+
like '%_session_identity_owner%' and agent_board_test.stripped_body('agent_board._session_identity_read_allowed(uuid)'::regprocedure, true)
|
|
34
|
+
like '%current_agent_id%',
|
|
35
|
+
'EC-SIDOWNER-1: reader authorizes verified owner or lineage actor');
|
|
36
|
+
select is(
|
|
37
|
+
(select coalesce(string_agg(p.oid::regprocedure::text, ', ' order by p.oid::regprocedure::text), '')
|
|
38
|
+
from pg_proc p
|
|
39
|
+
where p.pronamespace = 'agent_board'::regnamespace
|
|
40
|
+
and p.proname in ('list_session_children', 'get_session_identity_state')
|
|
41
|
+
and (has_function_privilege('public', p.oid, 'EXECUTE') or has_function_privilege('anon', p.oid, 'EXECUTE'))),
|
|
42
|
+
'', 'EC-SIDOWNER-1: catalog reader class has no public or anon route');
|
|
43
|
+
select ok((select count(*) = 2 from pg_proc p where p.pronamespace = 'agent_board'::regnamespace
|
|
44
|
+
and p.proname in ('list_session_children', 'get_session_identity_state')),
|
|
45
|
+
'EC-SIDOWNER-1: catalog reader population is exact');
|
|
46
|
+
select ok((select count(*) = 2 from pg_proc p where p.pronamespace = 'agent_board'::regnamespace
|
|
47
|
+
and p.proname in ('list_session_children', 'get_session_identity_state') and obj_description(p.oid, 'pg_proc') is not null),
|
|
48
|
+
'EC-SIDOWNER-1: every public SIDOWNER reader has a catalog contract');
|
|
49
|
+
|
|
50
|
+
-- Discover table users and literal callers by effect, including private helpers.
|
|
51
|
+
-- current_agent_id exposes only the existing fenced principal UUID, not readings;
|
|
52
|
+
-- its positive classification is asserted below, not exempted from discovery.
|
|
53
|
+
create temp table _owner_fns as
|
|
54
|
+
select p.*, agent_board_test.stripped_body(p.oid) as body,
|
|
55
|
+
agent_board_test.stripped_body(p.oid,true) as literals
|
|
56
|
+
from pg_proc p where p.pronamespace='agent_board'::regnamespace and p.prokind='f';
|
|
57
|
+
create temp table _owner_calls as select * from agent_board_test.fn_call_closure();
|
|
58
|
+
create temp view _owner_direct as
|
|
59
|
+
select f.* from _owner_fns f where f.body ~ agent_board_test.re(
|
|
60
|
+
agent_board_test.qname_frag('session_identities',agent_board_test.bare_ok(f.oid)) || '|'
|
|
61
|
+
|| agent_board_test.qname_frag('session_id_aliases',agent_board_test.bare_ok(f.oid)));
|
|
62
|
+
create temp table _owner_dml as
|
|
63
|
+
select distinct f.oid from _owner_fns f join pg_class t on t.relnamespace='agent_board'::regnamespace
|
|
64
|
+
and t.relkind in ('r','p') where f.body ~ agent_board_test.re(
|
|
65
|
+
agent_board_test.qname_frag(t.relname,agent_board_test.bare_ok(f.oid)))
|
|
66
|
+
and f.body ~ agent_board_test.re(
|
|
67
|
+
agent_board_test.write_frag(t.relname,agent_board_test.bare_ok(f.oid)));
|
|
68
|
+
create temp view _owner_mutators as
|
|
69
|
+
select distinct c.root_oid as oid from _owner_calls c
|
|
70
|
+
join _owner_dml w on w.oid=c.reached_oid join _owner_fns f on f.oid=c.root_oid
|
|
71
|
+
where not has_function_privilege('authenticated',f.oid,'EXECUTE')
|
|
72
|
+
or pg_get_function_result(f.oid) not in ('json','jsonb','record');
|
|
73
|
+
create temp view _owner_principal as
|
|
74
|
+
select f.oid from _owner_direct f where f.proname='current_agent_id'
|
|
75
|
+
and pg_get_function_result(f.oid)='uuid'
|
|
76
|
+
and f.body ~ agent_board_test.call_re('auth.uid',false)
|
|
77
|
+
and f.literals like '%identity_kind is distinct from ''session'' or c.lease_epoch = si.credential_epoch::text%';
|
|
78
|
+
create temp view _owner_internal as
|
|
79
|
+
select f.oid from _owner_direct f
|
|
80
|
+
where pg_get_function_result(f.oid) in ('boolean','void','trigger','timestamp with time zone')
|
|
81
|
+
and not has_function_privilege('authenticated',f.oid,'EXECUTE')
|
|
82
|
+
and not has_function_privilege('public',f.oid,'EXECUTE')
|
|
83
|
+
and not has_function_privilege('anon',f.oid,'EXECUTE');
|
|
84
|
+
create temp view _owner_scoped as
|
|
85
|
+
select f.oid from _owner_fns f where f.proname in ('get_session_identity_state','list_session_children')
|
|
86
|
+
and f.prosecdef and f.provolatile='s' and f.proconfig @> array['search_path=""']
|
|
87
|
+
and f.body ~ agent_board_test.call_re('_session_identity_read_allowed')
|
|
88
|
+
and f.literals like '%raise exception ''identity_read_forbidden'' using errcode = ''42501''%';
|
|
89
|
+
create temp view _owner_unclassified_direct as
|
|
90
|
+
select f.oid,f.proname from _owner_direct f
|
|
91
|
+
where f.oid not in (select oid from _owner_mutators union select oid from _owner_principal
|
|
92
|
+
union select oid from _owner_internal union select oid from _owner_scoped);
|
|
93
|
+
create temp view _owner_routes as
|
|
94
|
+
select distinct f.oid,f.proname from _owner_fns f join _owner_calls c on c.root_oid=f.oid
|
|
95
|
+
join _owner_direct d on d.oid=c.reached_oid
|
|
96
|
+
where d.oid not in (select oid from _owner_principal);
|
|
97
|
+
create temp view _owner_unclassified_routes as
|
|
98
|
+
select f.* from _owner_routes f
|
|
99
|
+
where f.oid not in (select oid from _owner_mutators union select oid from _owner_internal union select oid from _owner_scoped);
|
|
100
|
+
select is((select count(*) from _owner_principal),1::bigint,
|
|
101
|
+
'SIDOWNER-principal-positive: existing resolver returns only the session-epoch-fenced actor UUID');
|
|
102
|
+
select ok((select count(*)>=20 from _owner_direct),
|
|
103
|
+
'SIDOWNER-reader-population: catalog discovery includes existing table users, not just two names');
|
|
104
|
+
select is((select coalesce(string_agg(proname,', ' order by proname),'') from _owner_unclassified_direct),'',
|
|
105
|
+
'SIDOWNER-reader-class: every direct table user is positively classified; a new reader names itself');
|
|
106
|
+
select is((select coalesce(string_agg(proname,', ' order by proname),'') from _owner_unclassified_routes),'',
|
|
107
|
+
'SIDOWNER-reader-route-class: every sensitive literal caller is positively classified; a new wrapper names itself');
|
|
108
|
+
select is((select coalesce(string_agg(proname,', ' order by proname),'') from _owner_routes
|
|
109
|
+
where has_function_privilege('public',oid,'EXECUTE') or has_function_privilege('anon',oid,'EXECUTE')),'',
|
|
110
|
+
'SIDOWNER-reader-route-acl: discovered sensitive routes have no public or anonymous execution');
|
|
111
|
+
select is((select count(*) from _owner_scoped),2::bigint,
|
|
112
|
+
'SIDOWNER-scoped-positive: both scoped readers are stable definers with empty paths and named authorization');
|
|
113
|
+
select is((select coalesce(string_agg(distinct c.root_name,', ' order by c.root_name),'')
|
|
114
|
+
from _owner_calls c join _owner_dml d on d.oid=c.reached_oid
|
|
115
|
+
where c.root_name in ('get_session_identity_state','list_session_children')),'',
|
|
116
|
+
'SIDOWNER-select-only: neither scoped reader nor any literal callee writes any board table');
|
|
117
|
+
select ok((select count(*)>=5 from _owner_internal),
|
|
118
|
+
'SIDOWNER-internal-positive: internal table readers return nonprojecting scalar/trigger results and are not authenticated callable');
|
|
119
|
+
create function agent_board.sidowner_unclassified_reader() returns jsonb
|
|
120
|
+
language sql security definer set search_path='' as $probe$
|
|
121
|
+
SELECT to_jsonb(si) FROM agent_board.session_identities si LIMIT 1;
|
|
122
|
+
$probe$;
|
|
123
|
+
revoke all on function agent_board.sidowner_unclassified_reader() from public,anon;
|
|
124
|
+
grant execute on function agent_board.sidowner_unclassified_reader() to authenticated,service_role;
|
|
125
|
+
create function agent_board.sidowner_unclassified_wrapper() returns jsonb
|
|
126
|
+
language sql security definer set search_path='' as $probe$
|
|
127
|
+
SELECT agent_board.sidowner_unclassified_reader(/* no direct table reference */);
|
|
128
|
+
$probe$;
|
|
129
|
+
revoke all on function agent_board.sidowner_unclassified_wrapper() from public,anon;
|
|
130
|
+
grant execute on function agent_board.sidowner_unclassified_wrapper() to authenticated,service_role;
|
|
131
|
+
create function agent_board.sidowner_unclassified_mutating_reader() returns jsonb
|
|
132
|
+
language plpgsql security definer set search_path='' as $probe$
|
|
133
|
+
begin
|
|
134
|
+
UPDATE agent_board.presence SET availability=availability WHERE false;
|
|
135
|
+
return (SELECT to_jsonb(si) FROM agent_board.session_identities si LIMIT 1);
|
|
136
|
+
end;
|
|
137
|
+
$probe$;
|
|
138
|
+
revoke all on function agent_board.sidowner_unclassified_mutating_reader() from public,anon;
|
|
139
|
+
grant execute on function agent_board.sidowner_unclassified_mutating_reader() to authenticated,service_role;
|
|
140
|
+
-- Refresh from the actual catalog after adding the canaries, including their
|
|
141
|
+
-- transitive call graph. Caching avoids repeatedly stripping every routine.
|
|
142
|
+
insert into _owner_fns
|
|
143
|
+
select p.*, agent_board_test.stripped_body(p.oid) as body,
|
|
144
|
+
agent_board_test.stripped_body(p.oid,true) as literals
|
|
145
|
+
from pg_proc p where p.oid in ('agent_board.sidowner_unclassified_reader()'::regprocedure,
|
|
146
|
+
'agent_board.sidowner_unclassified_wrapper()'::regprocedure,
|
|
147
|
+
'agent_board.sidowner_unclassified_mutating_reader()'::regprocedure);
|
|
148
|
+
truncate _owner_calls;
|
|
149
|
+
insert into _owner_calls select * from agent_board_test.fn_call_closure();
|
|
150
|
+
truncate _owner_dml;
|
|
151
|
+
insert into _owner_dml
|
|
152
|
+
select distinct f.oid from _owner_fns f join pg_class t on t.relnamespace='agent_board'::regnamespace
|
|
153
|
+
and t.relkind in ('r','p') where f.body ~ agent_board_test.re(
|
|
154
|
+
agent_board_test.qname_frag(t.relname,agent_board_test.bare_ok(f.oid)))
|
|
155
|
+
and f.body ~ agent_board_test.re(
|
|
156
|
+
agent_board_test.write_frag(t.relname,agent_board_test.bare_ok(f.oid)));
|
|
157
|
+
select is((select string_agg(proname,', ' order by proname) from _owner_unclassified_direct
|
|
158
|
+
where proname='sidowner_unclassified_reader'),'sidowner_unclassified_reader',
|
|
159
|
+
'SIDOWNER-reader-canary: new authenticated uppercase reader names itself without a PUBLIC grant');
|
|
160
|
+
select is((select string_agg(proname,', ' order by proname) from _owner_unclassified_routes
|
|
161
|
+
where proname in ('sidowner_unclassified_reader','sidowner_unclassified_wrapper')),
|
|
162
|
+
'sidowner_unclassified_reader, sidowner_unclassified_wrapper',
|
|
163
|
+
'SIDOWNER-wrapper-canary: indirect reader also names itself in literal-call closure');
|
|
164
|
+
select is((select count(*) from _owner_mutators where oid in (
|
|
165
|
+
'agent_board.sidowner_unclassified_reader()'::regprocedure,'agent_board.sidowner_unclassified_wrapper()'::regprocedure)),0::bigint,
|
|
166
|
+
'SIDOWNER-positive-classification-canary: a SELECT-only secret reader is not mislabeled as a lifecycle writer');
|
|
167
|
+
select is((select proname::text from _owner_unclassified_direct
|
|
168
|
+
where proname='sidowner_unclassified_mutating_reader'),'sidowner_unclassified_mutating_reader',
|
|
169
|
+
'SIDOWNER-unrelated-write-canary: unrelated no-op DML cannot classify an authenticated JSON reader as safe');
|
|
170
|
+
select * from finish();
|
|
171
|
+
rollback;
|
|
@@ -0,0 +1,136 @@
|
|
|
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(31);
|
|
6
|
+
create temp table _owner_fixture(label text primary key, agent_id uuid, auth_id uuid);
|
|
7
|
+
do $$
|
|
8
|
+
declare v_owner uuid; v_root uuid; v_parent uuid; v_id uuid; v_auth uuid; v_label text;
|
|
9
|
+
begin
|
|
10
|
+
foreach v_label in array array['owner','other-owner','root','parent','sibling','grandchild','other-root'] loop
|
|
11
|
+
v_id := gen_random_uuid();
|
|
12
|
+
v_auth := public.ensure_github_user('sidowner-' || v_id, v_id || '@example.test', v_id::text, v_label);
|
|
13
|
+
if v_label = 'owner' then v_owner := v_auth; end if;
|
|
14
|
+
if v_label = 'root' then v_root := v_id; end if;
|
|
15
|
+
if v_label = 'parent' then v_parent := v_id; end if;
|
|
16
|
+
insert into agent_board.agents(id, agent_type, handle, display_name, status, auth_user_id, owner_user_id, parent_agent_id, root_agent_id)
|
|
17
|
+
values (v_id, case when v_label in ('owner','other-owner') then 'human' else 'agent' end,
|
|
18
|
+
'sidowner-' || v_id, v_label, 'active', v_auth,
|
|
19
|
+
case when v_label in ('root','parent','sibling','grandchild') then v_owner else v_auth end,
|
|
20
|
+
case when v_label in ('parent','sibling') then v_root when v_label = 'grandchild' then v_parent else null end,
|
|
21
|
+
case when v_label in ('parent','sibling','grandchild') then v_root else null end);
|
|
22
|
+
insert into _owner_fixture values (v_label, v_id, v_auth);
|
|
23
|
+
end loop;
|
|
24
|
+
end;
|
|
25
|
+
$$;
|
|
26
|
+
set constraints all immediate;
|
|
27
|
+
set constraints all deferred;
|
|
28
|
+
grant select on _owner_fixture to authenticated, service_role;
|
|
29
|
+
create function pg_temp.owner_actor(p_label text) returns void language plpgsql as $$
|
|
30
|
+
begin
|
|
31
|
+
perform set_config('request.jwt.claims', '{}', true);
|
|
32
|
+
perform set_config('request.jwt.claim.sub', coalesce((select auth_id::text from _owner_fixture where label=p_label), ''), true);
|
|
33
|
+
perform set_config('request.jwt.claim.role', 'authenticated', true);
|
|
34
|
+
perform set_config('request.jwt.claim.agent_credential_id', '', true);
|
|
35
|
+
end;
|
|
36
|
+
$$;
|
|
37
|
+
select pg_temp.owner_actor('owner');
|
|
38
|
+
set local role authenticated;
|
|
39
|
+
create temp table _owner_children as select * from agent_board.list_session_children((select agent_id from _owner_fixture where label='root')) with no data;
|
|
40
|
+
select lives_ok($$insert into _owner_children select * from agent_board.list_session_children((select agent_id from _owner_fixture where label='root'))$$,
|
|
41
|
+
'SIDOWNER-owner-control: human owner enters scoped children reader');
|
|
42
|
+
select is((select count(*) from _owner_children), 3::bigint,
|
|
43
|
+
'SIDOWNER-subtree: all strict descendants at both depths returned');
|
|
44
|
+
select is((select array_agg(depth order by ctid) from _owner_children), array[1,1,2]::smallint[],
|
|
45
|
+
'SIDOWNER-order: children ordered by depth and identity');
|
|
46
|
+
select is((select array_agg(agent_id order by ctid) from _owner_children),
|
|
47
|
+
(select array_agg(agent_id order by depth,agent_id) from _owner_children),
|
|
48
|
+
'SIDOWNER-identity-order: emitted child rows retain depth then UUID ordering');
|
|
49
|
+
select is((select count(*) from _owner_children where agent_id=(select agent_id from _owner_fixture where label='root')), 0::bigint,
|
|
50
|
+
'SIDOWNER-parent-excluded: selected parent is never included');
|
|
51
|
+
select is((select count(*) from _owner_children where root_agent_id<>(select agent_id from _owner_fixture where label='root')), 0::bigint,
|
|
52
|
+
'SIDOWNER-root-filter: unrelated root is never returned');
|
|
53
|
+
select is((select string_agg(attname, ',' order by attnum) from pg_attribute where attrelid='_owner_children'::regclass and attnum>0 and not attisdropped),
|
|
54
|
+
'agent_id,parent_agent_id,root_agent_id,handle,depth,harness,lease_state,resolution_state,fingerprint_resolution,current_risky_digest,approved_risky_digest,recorded_fingerprint,tombstoned_at,tombstone_reason',
|
|
55
|
+
'SIDOWNER-safe-columns: exact normalized projection excludes credential material');
|
|
56
|
+
create temp table _owner_root as select * from agent_board.get_session_identity_state((select agent_id from _owner_fixture where label='root')) with no data;
|
|
57
|
+
select lives_ok($$insert into _owner_root select * from agent_board.get_session_identity_state((select agent_id from _owner_fixture where label='root'))$$,
|
|
58
|
+
'SIDOWNER-owner-state-control: owner inspects root');
|
|
59
|
+
select is((select string_agg(attname, ',' order by attnum) from pg_attribute where attrelid='_owner_root'::regclass and attnum>0 and not attisdropped),
|
|
60
|
+
'agent_id,parent_agent_id,root_agent_id,handle,depth,harness,lease_state,resolution_state,fingerprint_resolution,current_risky_digest,approved_risky_digest,recorded_fingerprint,tombstoned_at,tombstone_reason',
|
|
61
|
+
'SIDOWNER-state-safe-columns: single-state reader has the same exact safe projection');
|
|
62
|
+
select is((select lease_state from _owner_root), 'not_applicable', 'SIDOWNER-legacy-lease: root is not a session lease');
|
|
63
|
+
select is((select fingerprint_resolution from _owner_root), 'unresolved', 'SIDOWNER-missing-reading: backfill reading is unresolved');
|
|
64
|
+
reset role;
|
|
65
|
+
select pg_temp.owner_actor('root');
|
|
66
|
+
set local role authenticated;
|
|
67
|
+
select lives_ok($$select * from agent_board.list_session_children((select agent_id from _owner_fixture where label='parent'))$$,
|
|
68
|
+
'SIDOWNER-ancestor-control: strict ancestor reads parent subtree');
|
|
69
|
+
select lives_ok($$select * from agent_board.get_session_identity_state((select agent_id from _owner_fixture where label='grandchild'))$$,
|
|
70
|
+
'SIDOWNER-ancestor-state: ancestor reads depth two');
|
|
71
|
+
reset role;
|
|
72
|
+
select pg_temp.owner_actor('parent');
|
|
73
|
+
set local role authenticated;
|
|
74
|
+
select lives_ok($$select * from agent_board.list_session_children((select agent_id from _owner_fixture where label='parent'))$$,
|
|
75
|
+
'SIDOWNER-self-control: parent reads own children');
|
|
76
|
+
select throws_ok($$select * from agent_board.list_session_children((select agent_id from _owner_fixture where label='sibling'))$$,
|
|
77
|
+
'42501','identity_read_forbidden','SIDOWNER-sibling: same-root sibling cannot read subtree');
|
|
78
|
+
select throws_ok($$select * from agent_board.get_session_identity_state((select agent_id from _owner_fixture where label='root'))$$,
|
|
79
|
+
'42501','identity_read_forbidden','SIDOWNER-descendant: descendant cannot read ancestor');
|
|
80
|
+
reset role;
|
|
81
|
+
update agent_board.session_identities set resolution_state='frozen' where agent_id=(select agent_id from _owner_fixture where label='parent');
|
|
82
|
+
set local role authenticated;
|
|
83
|
+
select lives_ok($$select * from agent_board.list_session_children((select agent_id from _owner_fixture where label='parent'))$$,
|
|
84
|
+
'SIDOWNER-frozen-reader: freeze does not deny diagnosis');
|
|
85
|
+
reset role;
|
|
86
|
+
update agent_board.session_identities set tombstoned_at=now(), tombstone_reason='revoked'
|
|
87
|
+
where agent_id=(select agent_id from _owner_fixture where label='grandchild');
|
|
88
|
+
set local role authenticated;
|
|
89
|
+
create temp table _owner_tombstone as select * from agent_board.list_session_children((select agent_id from _owner_fixture where label='parent')) with no data;
|
|
90
|
+
select lives_ok($$insert into _owner_tombstone select * from agent_board.list_session_children((select agent_id from _owner_fixture where label='parent'))$$,
|
|
91
|
+
'SIDOWNER-tombstone-control: parent reads tombstone');
|
|
92
|
+
select is((select tombstone_reason from _owner_tombstone), 'revoked', 'SIDOWNER-tombstone: tombstoned descendants remain visible');
|
|
93
|
+
reset role;
|
|
94
|
+
select pg_temp.owner_actor('other-owner');
|
|
95
|
+
set local role authenticated;
|
|
96
|
+
select throws_ok($$select * from agent_board.list_session_children((select agent_id from _owner_fixture where label='root'))$$,
|
|
97
|
+
'42501','identity_read_forbidden','SIDOWNER-owner-filter: unrelated human cannot inspect root');
|
|
98
|
+
select throws_ok($$select * from agent_board.get_session_identity_state((select agent_id from _owner_fixture where label='grandchild'))$$,
|
|
99
|
+
'42501','identity_read_forbidden','SIDOWNER-other-owner-state: unrelated human cannot inspect state');
|
|
100
|
+
reset role;
|
|
101
|
+
select pg_temp.owner_actor('other-root');
|
|
102
|
+
set local role authenticated;
|
|
103
|
+
select throws_ok($$select * from agent_board.list_session_children((select agent_id from _owner_fixture where label='root'))$$,
|
|
104
|
+
'42501','identity_read_forbidden','SIDOWNER-other-root: unrelated agent cannot inspect subtree');
|
|
105
|
+
create temp table _owner_empty as select * from agent_board.list_session_children((select agent_id from _owner_fixture where label='other-root')) with no data;
|
|
106
|
+
select lives_ok($$insert into _owner_empty select * from agent_board.list_session_children((select agent_id from _owner_fixture where label='other-root'))$$,
|
|
107
|
+
'SIDOWNER-empty-control: leaf is an authorized parent');
|
|
108
|
+
select is((select count(*) from _owner_empty), 0::bigint,'SIDOWNER-empty: authorized leaf returns an empty array');
|
|
109
|
+
select throws_ok($$select * from agent_board.list_session_children('00000000-0000-0000-0000-000000000000')$$,
|
|
110
|
+
'42501','identity_read_forbidden','SIDOWNER-missing: missing parent uses same refusal');
|
|
111
|
+
select throws_ok($$select * from agent_board.get_session_identity_state('00000000-0000-0000-0000-000000000000')$$,
|
|
112
|
+
'42501','identity_read_forbidden','SIDOWNER-missing-state: missing state uses same refusal');
|
|
113
|
+
reset role;
|
|
114
|
+
select pg_temp.owner_actor('owner');
|
|
115
|
+
select set_config('request.jwt.claim.agent_credential_id', gen_random_uuid()::text, true);
|
|
116
|
+
set local role authenticated;
|
|
117
|
+
select throws_ok($$select * from agent_board.list_session_children((select agent_id from _owner_fixture where label='root'))$$,
|
|
118
|
+
'42501','identity_read_forbidden','SIDOWNER-invalid-owner-credential: owner predicate cannot bypass invalid credential');
|
|
119
|
+
reset role;
|
|
120
|
+
select pg_temp.owner_actor('parent');
|
|
121
|
+
update agent_board.agents set status='revoked' where id=(select agent_id from _owner_fixture where label='parent');
|
|
122
|
+
set local role authenticated;
|
|
123
|
+
select throws_ok($$select * from agent_board.list_session_children((select agent_id from _owner_fixture where label='parent'))$$,
|
|
124
|
+
'42501','identity_read_forbidden','SIDOWNER-revoked-reader: revoked parent cannot inspect children');
|
|
125
|
+
reset role;
|
|
126
|
+
select pg_temp.owner_actor('none');
|
|
127
|
+
set local role authenticated;
|
|
128
|
+
select throws_ok($$select * from agent_board.get_session_identity_state((select agent_id from _owner_fixture where label='root'))$$,
|
|
129
|
+
'42501','identity_read_forbidden','SIDOWNER-null-actor: missing actor cannot read state');
|
|
130
|
+
reset role;
|
|
131
|
+
select is((select count(*) from agent_board.session_identities where root_agent_id=(select agent_id from _owner_fixture where label='root')), 4::bigint,
|
|
132
|
+
'SIDOWNER-read-only: reads preserve identity population');
|
|
133
|
+
select is((select resolution_state from agent_board.session_identities where agent_id=(select agent_id from _owner_fixture where label='parent')), 'frozen',
|
|
134
|
+
'SIDOWNER-read-state: diagnosis preserves freeze');
|
|
135
|
+
select * from finish();
|
|
136
|
+
rollback;
|
|
@@ -38,7 +38,7 @@ set search_path = extensions, public, agent_board;
|
|
|
38
38
|
|
|
39
39
|
begin;
|
|
40
40
|
|
|
41
|
-
select plan(
|
|
41
|
+
select plan(50);
|
|
42
42
|
|
|
43
43
|
create function pg_temp.claims(p_sub text) returns void language plpgsql as $$
|
|
44
44
|
begin
|
|
@@ -693,7 +693,25 @@ select ok(
|
|
|
693
693
|
-- would silently drop a site, which is the direction that hides a defect.
|
|
694
694
|
-- ===========================================================================
|
|
695
695
|
|
|
696
|
-
|
|
696
|
+
create temp view _sid_instance_withdrawal as
|
|
697
|
+
select p.oid,agent_board_test.stripped_body(p.oid,true) as b
|
|
698
|
+
from pg_proc p where p.oid='agent_board.tombstone_delegate_instance(text,text,text,text)'::regprocedure;
|
|
699
|
+
select ok((select b like '%perform agent_board.update_board_membership(v_board, v_row.agent_id, ''left'');%'
|
|
700
|
+
and b like '%if v_row.owner_user_id is distinct from v_owner then%'
|
|
701
|
+
and b like '%where di.instance_id = p_instance_id%'
|
|
702
|
+
and (select count(*) from regexp_matches(b,agent_board_test.call_re('update_board_membership'),'g'))=1
|
|
703
|
+
from _sid_instance_withdrawal),
|
|
704
|
+
'EC-SIDSCHEMA-5-instance-withdrawal-positive: the verified-instance revoker writes only left memberships for its bound principal, never a grant or admission; real closure tests check state and append-only provenance');
|
|
705
|
+
select ok((select strpos(b, 'perform agent_board._lock_board_authority_ceiling(v_board);') > 0
|
|
706
|
+
and strpos(b, 'perform agent_board._lock_board_authority_ceiling(v_board);')
|
|
707
|
+
< strpos(b, 'v_owner := agent_board._ensure_agent_auth_user(v_owner_slug);')
|
|
708
|
+
and strpos(b, 'perform agent_board._lock_board_authority_ceiling(v_board);')
|
|
709
|
+
< strpos(b, 'insert into agent_board.delegate_instances')
|
|
710
|
+
and b like '%select b.id from agent_board.boards b order by b.id%'
|
|
711
|
+
from _sid_instance_withdrawal
|
|
712
|
+
where oid='agent_board.tombstone_delegate_instance(text,text,text,text)'::regprocedure),
|
|
713
|
+
'SID-INTEGRITY-lock-order: tombstone_delegate_instance locks the board universe in UUID order before owner/instance and identity-family locks');
|
|
714
|
+
select is( -- pgtap-allow-raise-checked: catalog text comparison only; no RPC executes in this value expression.
|
|
697
715
|
(with recursive fns as (
|
|
698
716
|
select p.oid, p.proname, agent_board_test.stripped_body(p.oid) as b
|
|
699
717
|
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
|
@@ -736,10 +754,14 @@ select is(
|
|
|
736
754
|
-- assertion deliberately requires mutual exclusion, which survives a lock being added
|
|
737
755
|
-- downstream, and NOT a re-derived order, which does not.
|
|
738
756
|
'grant_board_role => serialised by the board advisory lock',
|
|
757
|
+
'link_session_alias => serialised by the board advisory lock',
|
|
758
|
+
'mint_delegated_child => serialised by the board advisory lock',
|
|
739
759
|
'onboard_federated_fleet_agent => serialised by the board advisory lock',
|
|
740
|
-
'
|
|
760
|
+
'readmit_session_identity => serialised by the board advisory lock',
|
|
761
|
+
'signed_grant_board_role => serialised by the board advisory lock',
|
|
762
|
+
'tombstone_delegate_instance => serialised by the board advisory lock'
|
|
741
763
|
]::text[],
|
|
742
|
-
'every
|
|
764
|
+
'every discovered agent+membership locker takes the board advisory lock (transitive; a new one names itself). FK-implied locks are covered by agent_board_fk_lock_edges.test.sql');
|
|
743
765
|
|
|
744
766
|
select finish();
|
|
745
767
|
|
|
@@ -56,7 +56,7 @@ select has_function(
|
|
|
56
56
|
'agent_board', 'issue_signing_key_challenge', array['uuid', 'text', 'interval'],
|
|
57
57
|
'issue_signing_key_challenge exists');
|
|
58
58
|
select has_function(
|
|
59
|
-
'agent_board', 'mint_fleet_agent_session', array['uuid', 'text', 'uuid', 'uuid', 'integer'],
|
|
59
|
+
'agent_board', 'mint_fleet_agent_session', array['uuid', 'text', 'uuid', 'uuid', 'integer', 'text', 'text'],
|
|
60
60
|
'mint_fleet_agent_session exists');
|
|
61
61
|
|
|
62
62
|
-- Seed one agent + key, then fill the key's sixteen pending slots.
|
|
@@ -323,8 +323,8 @@ select *
|
|
|
323
323
|
|
|
324
324
|
select is(
|
|
325
325
|
(select delivered_recipient_count from v2_board_delivery),
|
|
326
|
-
|
|
327
|
-
'post_board_message only reaches active joined or subscribed board members (
|
|
326
|
+
2,
|
|
327
|
+
'post_board_message only reaches active joined or subscribed board members, not the author (#608: the author is the seeded owner-creator)'
|
|
328
328
|
);
|
|
329
329
|
|
|
330
330
|
select is(
|
|
@@ -369,8 +369,8 @@ select *
|
|
|
369
369
|
|
|
370
370
|
select is(
|
|
371
371
|
(select delivered_recipient_count from v2_tasking_delivery),
|
|
372
|
-
|
|
373
|
-
'tasking counts explicit and board fan-out attempts before duplicate collapse (
|
|
372
|
+
3,
|
|
373
|
+
'tasking counts explicit and board fan-out attempts before duplicate collapse, not the creator (#608: the creator is the seeded owner-creator)'
|
|
374
374
|
);
|
|
375
375
|
|
|
376
376
|
select is(
|