@consiliency/agent-board-schema 1.64.0 → 1.74.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 +19 -0
- package/fixtures/already-applied.json +44 -4
- package/migrations/20261015000100_agent_board_session_provenance.sql +367 -0
- package/migrations/20261015000200_agent_board_request_preserve_removed.sql +87 -0
- package/migrations/20261015000300_agent_board_rls_correlation.sql +132 -0
- package/migrations/20261015000400_agent_board_resolution_authority.sql +146 -0
- package/migrations/20261026000100_agent_board_thread_association.sql +1287 -0
- package/migrations/20261101000100_agent_board_session_measured_mint.sql +145 -0
- package/migrations/20261104065658_agent_board_reply_delivery.sql +659 -0
- package/migrations/20261104170000_agent_board_tasking_replay_scope.sql +320 -0
- package/migrations/20261104180000_agent_board_delivery_writer_execute.sql +51 -0
- package/migrations/20261104190000_agent_board_message_replay_scope.sql +373 -0
- package/package.json +1 -1
- package/schema-manifest.json +93 -3
- package/src/deployment-drift-main.test.mjs +156 -0
- package/src/deployment-drift-script.test.mjs +14 -1
- package/src/policy-comment.test.mjs +17 -0
- package/src/tooling.test.mjs +12 -2
- package/tests/agent_board_661_reply_delivery.test.sql +185 -0
- package/tests/agent_board_667_boundary.test.sql +162 -0
- package/tests/agent_board_667_conversion_branches.test.sql +91 -0
- package/tests/agent_board_667_conversion_profile.test.sql +112 -0
- package/tests/agent_board_667_history_class.test.sql +785 -0
- package/tests/agent_board_667_native_cast_effect.test.sql +88 -0
- package/tests/agent_board_667_realtime_apply_behavior.test.sql +117 -0
- package/tests/agent_board_667_realtime_apply_controls.test.sql +143 -0
- package/tests/agent_board_667_realtime_apply_profile.test.sql +61 -0
- package/tests/agent_board_667_retention.test.sql +201 -0
- package/tests/agent_board_667_routing.test.sql +294 -0
- package/tests/agent_board_667_sender_effects.test.sql +100 -0
- package/tests/agent_board_667_sender_profile_controls.test.sql +29 -0
- package/tests/agent_board_667_storage_delimiter_behavior.test.sql +84 -0
- package/tests/agent_board_667_storage_delimiter_controls.test.sql +23 -0
- package/tests/agent_board_667_storage_delimiter_profile.test.sql +61 -0
- package/tests/agent_board_667_storage_search_behavior.test.sql +93 -0
- package/tests/agent_board_667_storage_search_controls.test.sql +22 -0
- package/tests/agent_board_667_storage_search_profile.test.sql +60 -0
- package/tests/agent_board_667_storage_timestamp_behavior.test.sql +58 -0
- package/tests/agent_board_667_storage_timestamp_controls.test.sql +22 -0
- package/tests/agent_board_667_storage_timestamp_profile.test.sql +54 -0
- package/tests/agent_board_667_writers.test.sql +281 -0
- package/tests/agent_board_717_message_replay_scope.test.sql +828 -0
- package/tests/agent_board_717_tasking_replay_scope.test.sql +661 -0
- package/tests/agent_board_addressing.test.sql +8 -5
- package/tests/agent_board_claim_subject_gate.test.sql +7 -3
- package/tests/agent_board_claimpolicy_race.test.sql +39 -11
- package/tests/agent_board_consent_gating.test.sql +4 -4
- package/tests/agent_board_delegate_role_class.test.sql +33 -15
- package/tests/agent_board_delivery_writer_execute.test.sql +371 -0
- package/tests/agent_board_fk_lock_edges.test.sql +3 -0
- package/tests/agent_board_gatenull_membstate.test.sql +7 -4
- package/tests/agent_board_hosted_delegates.test.sql +5 -2
- package/tests/agent_board_inbox_rpcs.test.sql +5 -3
- package/tests/agent_board_obsgate.test.sql +8 -4
- package/tests/agent_board_obsgate4.test.sql +2 -2
- package/tests/agent_board_outbox_wake_ordering.test.sql +25 -2
- package/tests/agent_board_own_post_not_delivered.test.sql +11 -9
- package/tests/agent_board_policy_correlation.test.sql +189 -0
- package/tests/agent_board_request_removed.test.sql +96 -0
- package/tests/agent_board_resolution_authority.test.sql +190 -0
- package/tests/agent_board_session_identity_behavior_classes.test.sql +2 -2
- package/tests/agent_board_session_identity_limits.test.sql +1 -1
- package/tests/agent_board_session_identity_measured_mint.test.sql +342 -0
- package/tests/agent_board_session_identity_predicate_isolation.test.sql +146 -0
- package/tests/agent_board_session_provenance.test.sql +106 -0
- package/tests/agent_board_sidserver_authority.test.sql +69 -0
- package/tests/agent_board_sidserver_catalog.test.sql +47 -0
- package/tests/agent_board_signed_write_gateway.test.sql +4 -2
- package/tests/agent_board_task_thread_lock_order.test.sql +67 -42
- package/tests/agent_board_v2_invariants.test.sql +5 -2
- package/tests/agent_board_write_enforcement_class.test.sql +3 -3
- package/tests/catalog/function_execute_privileges.json +43 -7
- package/tests/catalog/p_metadata_parameters.json +16 -0
- package/tests/fixtures/agent_board_667_apply_rls_guard.sql +195 -0
- package/tests/fixtures/agent_board_667_apply_rls_provenance.md +125 -0
- package/tests/fixtures/agent_board_667_apply_rls_reference.sql +1903 -0
- package/tests/fixtures/agent_board_667_conversion_guard.sql +162 -0
- package/tests/fixtures/agent_board_667_conversion_reference.md +23 -0
- package/tests/fixtures/agent_board_667_conversion_reference.sql +5325 -0
- package/tests/fixtures/agent_board_667_delimiter_guard.sql +92 -0
- package/tests/fixtures/agent_board_667_delimiter_provenance.md +116 -0
- package/tests/fixtures/agent_board_667_delimiter_reference.sql +10150 -0
- package/tests/fixtures/agent_board_667_search_guard.sql +92 -0
- package/tests/fixtures/agent_board_667_search_provenance.md +141 -0
- package/tests/fixtures/agent_board_667_search_reference.sql +4779 -0
- package/tests/fixtures/agent_board_667_sender_guard.sql +102 -0
- package/tests/fixtures/agent_board_667_sender_reference.md +20 -0
- package/tests/fixtures/agent_board_667_sender_reference.sql +875 -0
- package/tests/fixtures/agent_board_667_timestamp_graph.sql +84 -0
- package/tests/fixtures/agent_board_667_timestamp_guard.sql +94 -0
- package/tests/fixtures/agent_board_667_timestamp_provenance.md +99 -0
- package/tests/fixtures/agent_board_667_timestamp_reference.sql +2938 -0
- package/tests/isolation/expected/association_artifact_anchor_order.out +108 -0
- package/tests/isolation/expected/association_board_vs_board.out +71 -0
- package/tests/isolation/expected/association_post_wait_authority.out +77 -0
- package/tests/isolation/expected/association_strong_history_writers.out +203 -0
- package/tests/isolation/expected/association_strong_isolation.out +185 -0
- package/tests/isolation/expected/association_vs_artifact_claim_escalation.out +199 -0
- package/tests/isolation/expected/association_vs_heartbeat.out +78 -0
- package/tests/isolation/expected/association_vs_multi_anchor_artifact.out +67 -0
- package/tests/isolation/expected/association_vs_private_message.out +67 -0
- package/tests/isolation/expected/association_vs_task_and_conflict.out +133 -0
- package/tests/isolation/expected/association_vs_taskful_cross_thread.out +133 -0
- package/tests/isolation/expected/consent_approval_lock.out +1 -1
- package/tests/isolation/expected/message_replay_scope.out +209 -0
- package/tests/isolation/expected/tasking_replay_scope.out +125 -0
- package/tests/isolation/specs/association_artifact_anchor_order.spec +46 -0
- package/tests/isolation/specs/association_board_vs_board.spec +37 -0
- package/tests/isolation/specs/association_post_wait_authority.spec +48 -0
- package/tests/isolation/specs/association_strong_history_writers.spec +61 -0
- package/tests/isolation/specs/association_strong_isolation.spec +50 -0
- package/tests/isolation/specs/association_vs_artifact_claim_escalation.spec +41 -0
- package/tests/isolation/specs/association_vs_heartbeat.spec +47 -0
- package/tests/isolation/specs/association_vs_multi_anchor_artifact.spec +38 -0
- package/tests/isolation/specs/association_vs_private_message.spec +33 -0
- package/tests/isolation/specs/association_vs_task_and_conflict.spec +38 -0
- package/tests/isolation/specs/association_vs_taskful_cross_thread.spec +42 -0
- package/tests/isolation/specs/consent_approval_lock.spec +13 -7
- package/tests/isolation/specs/message_replay_scope.spec +77 -0
- package/tests/isolation/specs/tasking_replay_scope.spec +72 -0
- package/tests/mutations/717-f1/M0_prefix_main_acl_and_comments.sql +11 -0
- package/tests/mutations/717-f1/M10_wrapper_stops_delegating.sql +7 -0
- package/tests/mutations/717-f1/M11_transitive_recipient_content.sql +8 -0
- package/tests/mutations/717-f1/M12_transitive_recipient_visibility.sql +8 -0
- package/tests/mutations/717-f1/M13_transitive_worker_content.sql +15 -0
- package/tests/mutations/717-f1/M1a_regrant_upsert_delivery_recipient.sql +1 -0
- package/tests/mutations/717-f1/M1b_regrant_upsert_inbox_item.sql +1 -0
- package/tests/mutations/717-f1/M1c_regrant_queue_inbox_callbacks.sql +1 -0
- package/tests/mutations/717-f1/M1d_regrant_queue_board_post_callbacks.sql +1 -0
- package/tests/mutations/717-f1/M1e_regrant_next_thread_message_sequence.sql +1 -0
- package/tests/mutations/717-f1/M2_blanket_schema_grant.sql +1 -0
- package/tests/mutations/717-f1/M3_new_overload_of_sink.sql +10 -0
- package/tests/mutations/717-f1/M4_new_route_calling_sink.sql +7 -0
- package/tests/mutations/717-f1/M5_actor_trusts_supplied_id.sql +5 -0
- package/tests/mutations/717-f1/M6_signed_route_granted_to_authenticated.sql +1 -0
- package/tests/mutations/717-f1/M7_worker_granted_to_authenticated.sql +1 -0
- package/tests/mutations/717-f1/M8_recipient_state_rewrites_visibility.sql +5 -0
- package/tests/mutations/717-f1/M9_public_route_revoked_from_service_role.sql +1 -0
- package/tests/mutations/717-f1/outbox_wake_ordering/W1_regrant_queue_inbox_callbacks.sql +1 -0
- package/tests/mutations/717-f1/outbox_wake_ordering/W2_revoke_worker_from_service_role.sql +1 -0
- package/tests/mutations/717-f1/outbox_wake_ordering/W3_invoker_caller_of_sink.sql +1 -0
- package/tests/mutations/717-f1/outbox_wake_ordering/W4_public_regrant_board_post_sink.sql +1 -0
- package/tests/mutations/717-f1/run.sh +17 -0
- package/tests/mutations/717-f2b/C1_new_delivery_route.sql +4 -0
- package/tests/mutations/717-f2b/C2_new_replay_lookup.sql +3 -0
- package/tests/mutations/717-f2b/C3_internal_regranted.sql +2 -0
- package/tests/mutations/717-f2b/C4_wrapper_writes_delivery.sql +9 -0
- package/tests/mutations/717-f2b/C5_class_member_dynamic_sql.sql +9 -0
- package/tests/mutations/717-f2b/C6_drop_board_scope_constraint.sql +2 -0
- package/tests/mutations/717-f2b/C7_state_private_helper_content.sql +14 -0
- package/tests/mutations/717-f2b/L1_dm_lock_split_by_recipient.sql +8 -0
- package/tests/mutations/717-f2b/L2_dm_lock_removed.sql +12 -0
- package/tests/mutations/717-f2b/L3_mc_lock_split_by_recipients.sql +8 -0
- package/tests/mutations/717-f2b/L4_mc_lock_removed.sql +12 -0
- package/tests/mutations/717-f2b/M0_prefix_definitions.sql +17 -0
- package/tests/mutations/717-f2b/M10_mc_lookup_any_author.sql +15 -0
- package/tests/mutations/717-f2b/M11_dm_swallow_refusal.sql +8 -0
- package/tests/mutations/717-f2b/M12_mc_swallow_refusal.sql +8 -0
- package/tests/mutations/717-f2b/M13_signed_dm_swallows_refusal.sql +32 -0
- package/tests/mutations/717-f2b/M14_signed_mc_swallows_refusal.sql +20 -0
- package/tests/mutations/717-f2b/M15_mc_any_source.sql +7 -0
- package/tests/mutations/717-f2b/M1_dm_drop_recipient_proof.sql +16 -0
- package/tests/mutations/717-f2b/M2_mc_drop_recipient_proof.sql +16 -0
- package/tests/mutations/717-f2b/M3_dm_metadata_as_proof.sql +12 -0
- package/tests/mutations/717-f2b/M4_mc_metadata_as_proof.sql +16 -0
- package/tests/mutations/717-f2b/M5_dm_any_mode.sql +11 -0
- package/tests/mutations/717-f2b/M6_dm_board_delivery_counts.sql +11 -0
- package/tests/mutations/717-f2b/M7_mc_any_mode.sql +11 -0
- package/tests/mutations/717-f2b/M8_dm_thread_stamp_as_proof.sql +12 -0
- package/tests/mutations/717-f2b/M9_dm_lookup_any_author.sql +15 -0
- package/tests/mutations/717-f2b/run.sh +82 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
-- SIDSERVER blocker fix (Consiliency/message-board#653 BLOCKs 1 and 2): the MEASURED mint
|
|
2
|
+
-- agent_board.mint_fleet_agent_session_measured, and its parity with every gate of the
|
|
3
|
+
-- seven-argument trusted mint_fleet_agent_session it is meant to replace for the relay. Allowed paths use lives_ok then a separate state
|
|
4
|
+
-- check (AGENTS.md rule 3). Refusal probes that must COMMIT their recording are asserted
|
|
5
|
+
-- with lives_ok (a raise would roll the freeze back), then by state.
|
|
6
|
+
create extension if not exists pgtap with schema extensions;
|
|
7
|
+
set search_path = extensions, public, agent_board;
|
|
8
|
+
begin;
|
|
9
|
+
grant usage on schema extensions to authenticated, service_role;
|
|
10
|
+
select plan(63);
|
|
11
|
+
|
|
12
|
+
create temp table _mm_fixture(label text primary key, root_id uuid, agent_id uuid, key_id uuid);
|
|
13
|
+
create temp table _mm_results(label text, session_id text, expires_at timestamptz, epoch bigint, secret text, refusal text);
|
|
14
|
+
do $$
|
|
15
|
+
declare v_label text; v_root uuid; v_agent uuid; v_key uuid; v_auth uuid; v_alias uuid; v_harness text;
|
|
16
|
+
begin
|
|
17
|
+
foreach v_label in array array['codex', 'codex-b', 'claude', 'claude-lapse', 'legacy', 'recorded', 'unresolved',
|
|
18
|
+
'tomb', 'unproven', 'unclaimed', 'ancestor', 'replay'] loop
|
|
19
|
+
v_root := gen_random_uuid(); v_agent := gen_random_uuid(); v_key := gen_random_uuid();
|
|
20
|
+
v_harness := case when v_label like 'claude%' then 'claude' when v_label = 'legacy' then null else 'codex' end;
|
|
21
|
+
v_auth := public.ensure_github_user('mm-' || v_agent, 'mm-' || v_agent || '@example.test', 'mm-' || v_agent, 'MM');
|
|
22
|
+
insert into agent_board.agents(id, agent_type, handle, display_name, status)
|
|
23
|
+
values (v_root, 'agent', 'mm-root-' || v_root, 'MM root', 'active');
|
|
24
|
+
insert into agent_board.agents(id, agent_type, handle, display_name, status, auth_user_id, parent_agent_id, root_agent_id)
|
|
25
|
+
values (v_agent, 'agent', 'mm-' || v_agent, 'MM', 'active', v_auth, v_root, v_root);
|
|
26
|
+
insert into agent_board.agent_signing_keys(id, agent_id, key_id, key_version, public_key, public_key_format, fingerprint, status, proof_state)
|
|
27
|
+
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');
|
|
28
|
+
insert into agent_board.session_identities(agent_id, root_agent_id, depth) values (v_root, v_root, 0);
|
|
29
|
+
insert into agent_board.session_identities(agent_id, parent_agent_id, root_agent_id, depth, identity_kind, harness, approved_risky_digest, current_risky_digest)
|
|
30
|
+
values (v_agent, v_root, v_root, 1, case when v_label = 'legacy' then 'legacy' else 'session' end, v_harness, 'approved', 'approved');
|
|
31
|
+
if v_harness is not null then
|
|
32
|
+
insert into agent_board.session_id_aliases(identity_agent_id, root_agent_id, harness, session_id)
|
|
33
|
+
values (v_agent, v_root, v_harness, 'bound-' || v_label) returning id into v_alias;
|
|
34
|
+
update agent_board.session_identities set lease_alias_id = v_alias where agent_id = v_agent;
|
|
35
|
+
end if;
|
|
36
|
+
insert into _mm_fixture values (v_label, v_root, v_agent, v_key);
|
|
37
|
+
end loop;
|
|
38
|
+
update agent_board.session_identities set tombstoned_at = now(), tombstone_reason = 'revoked'
|
|
39
|
+
where agent_id = (select agent_id from _mm_fixture where label = 'tomb');
|
|
40
|
+
update agent_board.session_identities set resolution_state = 'frozen'
|
|
41
|
+
where agent_id = (select root_id from _mm_fixture where label = 'ancestor');
|
|
42
|
+
end;
|
|
43
|
+
$$;
|
|
44
|
+
grant select on _mm_fixture to service_role;
|
|
45
|
+
grant select, insert on _mm_results to service_role;
|
|
46
|
+
|
|
47
|
+
-- INVOKER, so the real RPC runs as service_role. p_digest is the MEASURED risky digest.
|
|
48
|
+
create function pg_temp.mm(p_label text, p_result text, p_digest text, p_alias text, p_continuity text,
|
|
49
|
+
p_predecessor uuid default null, p_secret text default null, p_resolution text default 'resolved', p_recorded jsonb default '{}')
|
|
50
|
+
returns void language plpgsql as $$
|
|
51
|
+
declare v_fx record; v_issued record;
|
|
52
|
+
begin
|
|
53
|
+
select * into v_fx from _mm_fixture where label = p_label;
|
|
54
|
+
select * into v_issued from agent_board.issue_signing_key_challenge(v_fx.agent_id, 'primary');
|
|
55
|
+
insert into _mm_results
|
|
56
|
+
select p_result, m.session_id, m.expires_at, m.identity_lease_epoch, m.holder_secret, m.refusal
|
|
57
|
+
from agent_board.mint_fleet_agent_session_measured(v_issued.challenge_id, split_part(v_issued.challenge, ':', 3), v_fx.key_id, v_fx.agent_id,
|
|
58
|
+
900, p_secret, p_alias, jsonb_build_object('resolution', p_resolution, 'risky_digest', p_digest, 'recorded', p_recorded),
|
|
59
|
+
p_continuity, p_predecessor) m;
|
|
60
|
+
end;
|
|
61
|
+
$$;
|
|
62
|
+
-- A fixed challenge, so a second use can prove the inherited single-use gate.
|
|
63
|
+
create temp table _mm_challenge(label text primary key, challenge_id uuid, nonce text);
|
|
64
|
+
grant select, insert on _mm_challenge to service_role;
|
|
65
|
+
create function pg_temp.mm_issue(p_label text) returns void language plpgsql as $$
|
|
66
|
+
declare v_fx record; v_issued record;
|
|
67
|
+
begin
|
|
68
|
+
select * into v_fx from _mm_fixture where label = p_label;
|
|
69
|
+
select * into v_issued from agent_board.issue_signing_key_challenge(v_fx.agent_id, 'primary');
|
|
70
|
+
insert into _mm_challenge values (p_label, v_issued.challenge_id, split_part(v_issued.challenge, ':', 3));
|
|
71
|
+
end;
|
|
72
|
+
$$;
|
|
73
|
+
create function pg_temp.mm_fixed(p_label text, p_result text) returns void language plpgsql as $$
|
|
74
|
+
declare v_fx record; v_ch record;
|
|
75
|
+
begin
|
|
76
|
+
select * into v_fx from _mm_fixture where label = p_label;
|
|
77
|
+
select * into v_ch from _mm_challenge where label = p_label;
|
|
78
|
+
insert into _mm_results
|
|
79
|
+
select p_result, m.session_id, m.expires_at, m.identity_lease_epoch, m.holder_secret, m.refusal
|
|
80
|
+
from agent_board.mint_fleet_agent_session_measured(v_ch.challenge_id, v_ch.nonce, v_fx.key_id, v_fx.agent_id, 900, null,
|
|
81
|
+
'bound-' || p_label, '{"resolution":"resolved","risky_digest":"approved"}'::jsonb, 'same_alias', null) m;
|
|
82
|
+
end;
|
|
83
|
+
$$;
|
|
84
|
+
grant execute on function pg_temp.mm_issue(text), pg_temp.mm_fixed(text, text) to service_role;
|
|
85
|
+
grant execute on function pg_temp.mm(text, text, text, text, text, uuid, text, text, jsonb) to service_role;
|
|
86
|
+
-- The trusted seven-argument form, for the legacy-key relay exemption's positive footprint.
|
|
87
|
+
create function pg_temp.mm_trusted(p_label text, p_alias text)
|
|
88
|
+
returns void language plpgsql as $$
|
|
89
|
+
declare v_fx record; v_issued record;
|
|
90
|
+
begin
|
|
91
|
+
select * into v_fx from _mm_fixture where label = p_label;
|
|
92
|
+
select * into v_issued from agent_board.issue_signing_key_challenge(v_fx.agent_id, 'primary');
|
|
93
|
+
perform * 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, null, p_alias);
|
|
94
|
+
end;
|
|
95
|
+
$$;
|
|
96
|
+
grant execute on function pg_temp.mm_trusted(text, text) to service_role;
|
|
97
|
+
create function pg_temp.mm_refuse(p_call text)
|
|
98
|
+
returns void language plpgsql as $$
|
|
99
|
+
begin
|
|
100
|
+
execute p_call;
|
|
101
|
+
raise exception 'expected refusal but RPC succeeded' using errcode = 'P0001';
|
|
102
|
+
end;
|
|
103
|
+
$$;
|
|
104
|
+
grant execute on function pg_temp.mm_refuse(text) to service_role;
|
|
105
|
+
create function pg_temp.mm_state(p_label text)
|
|
106
|
+
returns table(resolution_state text, current_risky_digest text, approved_risky_digest text, epoch bigint, holder boolean, recorded jsonb)
|
|
107
|
+
language sql as $$
|
|
108
|
+
select si.resolution_state, si.current_risky_digest, si.approved_risky_digest, si.credential_epoch, si.holder_secret_hash is not null, si.recorded_fingerprint
|
|
109
|
+
from agent_board.session_identities si join _mm_fixture f on f.agent_id = si.agent_id where f.label = p_label;
|
|
110
|
+
$$;
|
|
111
|
+
grant execute on function pg_temp.mm_state(text) to service_role;
|
|
112
|
+
-- Fixture clock: the runner's own role ages the lease (service_role has no UPDATE).
|
|
113
|
+
create function pg_temp.mm_lapse(p_label text) returns void language sql security definer as $$
|
|
114
|
+
update agent_board.session_identities set last_heartbeat = now() - interval '181 seconds'
|
|
115
|
+
where agent_id = (select agent_id from _mm_fixture where label = p_label);
|
|
116
|
+
$$;
|
|
117
|
+
create function pg_temp.mm_alias_count(p_label text, p_session text) returns bigint language sql as $$
|
|
118
|
+
select count(*) from agent_board.session_id_aliases a join _mm_fixture f on f.agent_id = a.identity_agent_id
|
|
119
|
+
where f.label = p_label and a.session_id = p_session;
|
|
120
|
+
$$;
|
|
121
|
+
|
|
122
|
+
-- Catalog: the mint class, enumerated from pg_proc, never from a written list.
|
|
123
|
+
select is((select string_agg(p.proname || '(' || pg_get_function_identity_arguments(p.oid) || ')', ' | ' order by p.proname)
|
|
124
|
+
from pg_proc p where p.pronamespace = 'agent_board'::regnamespace and p.proname in ('mint_fleet_agent_session', 'mint_fleet_agent_session_measured')),
|
|
125
|
+
'mint_fleet_agent_session(p_challenge_id uuid, p_nonce text, p_signing_key_id uuid, p_agent_id uuid, p_max_ttl_seconds integer, p_holder_secret text, p_session_id text)'
|
|
126
|
+
|| ' | mint_fleet_agent_session_measured(p_challenge_id uuid, p_nonce text, p_signing_key_id uuid, p_agent_id uuid, p_max_ttl_seconds integer, p_holder_secret text, p_session_id text, p_fingerprint jsonb, p_continuity text, p_predecessor_agent_id uuid)',
|
|
127
|
+
'SIDSERVER-MM-CAT-01: exactly one trusted and one measured mint exist; any added overload names itself');
|
|
128
|
+
select ok((select bool_and(p.prosecdef and p.proconfig @> array['search_path=agent_board, public']
|
|
129
|
+
and has_function_privilege('service_role', p.oid, 'EXECUTE')
|
|
130
|
+
and not has_function_privilege('anon', p.oid, 'EXECUTE') and not has_function_privilege('authenticated', p.oid, 'EXECUTE')
|
|
131
|
+
and not exists (select 1 from aclexplode(coalesce(p.proacl, acldefault('f', p.proowner))) a where a.grantee = 0 and a.privilege_type = 'EXECUTE')
|
|
132
|
+
and obj_description(p.oid, 'pg_proc') is not null)
|
|
133
|
+
from pg_proc p where p.pronamespace = 'agent_board'::regnamespace and p.proname in ('mint_fleet_agent_session', 'mint_fleet_agent_session_measured')),
|
|
134
|
+
'SIDSERVER-MM-CAT-02: both mints are pinned-search_path definers, service_role-only (no PUBLIC/anon/authenticated) and commented');
|
|
135
|
+
select is((select coalesce(string_agg(distinct c.root_name, ', ' order by c.root_name), '')
|
|
136
|
+
from agent_board_test.fn_call_closure() c join pg_proc p on p.oid = c.reached_oid
|
|
137
|
+
where p.pronamespace = 'agent_board'::regnamespace and p.proname in ('mint_fleet_agent_session', 'mint_fleet_agent_session_measured')
|
|
138
|
+
and c.root_oid <> c.reached_oid), '',
|
|
139
|
+
'SIDSERVER-MM-CAT-03: no SQL function calls either mint; the relay is the only caller class (repo side: check-sidserver-measured-mint-callers)');
|
|
140
|
+
select ok(agent_board_test.stripped_body('agent_board.mint_fleet_agent_session_measured(uuid,text,uuid,uuid,integer,text,text,jsonb,text,uuid)'::regprocedure, true)
|
|
141
|
+
like '%_mint_fleet_agent_session_legacy(%',
|
|
142
|
+
'SIDSERVER-MM-CAT-04: the measured mint validates and consumes the challenge through the private legacy issuer');
|
|
143
|
+
-- Gate parity, catalog half: every function the trusted form reaches (derived from pg_catalog,
|
|
144
|
+
-- not listed here) is reached by the measured form, so no inherited gate can be dropped by
|
|
145
|
+
-- omitting its call. The behavioural half is SIDSERVER-MM-GATE-* below.
|
|
146
|
+
select is((select coalesce(string_agg(distinct t.reached_oid::regprocedure::text, ', '), '')
|
|
147
|
+
from agent_board_test.fn_call_closure() t
|
|
148
|
+
where t.root_oid = 'agent_board.mint_fleet_agent_session(uuid,text,uuid,uuid,integer,text,text)'::regprocedure
|
|
149
|
+
and t.reached_oid <> t.root_oid
|
|
150
|
+
and not exists (select 1 from agent_board_test.fn_call_closure() m
|
|
151
|
+
where m.root_oid = 'agent_board.mint_fleet_agent_session_measured(uuid,text,uuid,uuid,integer,text,text,jsonb,text,uuid)'::regprocedure
|
|
152
|
+
and m.reached_oid = t.reached_oid)), '',
|
|
153
|
+
'SIDSERVER-MM-PARITY-01: the measured mint reaches every function the trusted mint reaches');
|
|
154
|
+
select ok((select count(distinct t.reached_oid) >= 5 from agent_board_test.fn_call_closure() t
|
|
155
|
+
where t.root_oid = 'agent_board.mint_fleet_agent_session(uuid,text,uuid,uuid,integer,text,text)'::regprocedure and t.reached_oid <> t.root_oid),
|
|
156
|
+
'SIDSERVER-MM-PARITY-01-nonvacuous: the trusted closure is populated (legacy issuer, ceiling, limits, lease, service-role gate)');
|
|
157
|
+
|
|
158
|
+
set local role service_role;
|
|
159
|
+
select set_config('request.jwt.claim.role', 'service_role', true);
|
|
160
|
+
|
|
161
|
+
-- BLOCK 1 control: a first claim measuring the approved digest issues a token.
|
|
162
|
+
select lives_ok($q$select pg_temp.mm('codex', 'first-a', 'approved', 'bound-codex', 'same_alias')$q$,
|
|
163
|
+
'SIDSERVER-MM-FP-control: a first claim measuring the approved digest enters the measured mint');
|
|
164
|
+
select is((select (refusal is null and session_id is not null and epoch is not null and secret ~ '^[0-9a-f]{64}$') from _mm_results where label = 'first-a'),
|
|
165
|
+
true, 'SIDSERVER-MM-FP-control-state: the approved reading claims with a token, epoch and holder secret');
|
|
166
|
+
select is((select row(resolution_state, current_risky_digest)::text from pg_temp.mm_state('codex')), '(active,approved)',
|
|
167
|
+
'SIDSERVER-MM-FP-control-recorded: the approved reading is recorded and stays active');
|
|
168
|
+
|
|
169
|
+
-- BLOCK 1: a live holder re-minting with a CHANGED measured digest is refused AND frozen.
|
|
170
|
+
select lives_ok(format($q$select pg_temp.mm('codex', 'remint-b', 'changed-b', 'bound-codex', 'same_alias', null, %L)$q$,
|
|
171
|
+
(select secret from _mm_results where label = 'first-a')),
|
|
172
|
+
'SIDSERVER-MM-FP-remint-returns: a changed re-mint RETURNS its refusal so the freeze commits');
|
|
173
|
+
select is((select row(refusal, session_id, epoch, secret)::text from _mm_results where label = 'remint-b'),
|
|
174
|
+
'(identity_fingerprint_unapproved,,,)', 'SIDSERVER-MM-FP-remint-refused: a changed re-mint gets no session, epoch or holder secret');
|
|
175
|
+
select is((select row(resolution_state, current_risky_digest, approved_risky_digest)::text from pg_temp.mm_state('codex')),
|
|
176
|
+
'(frozen,changed-b,approved)', 'SIDSERVER-MM-FP-remint-frozen: the measured digest B is recorded and the identity frozen');
|
|
177
|
+
select is((select count(*) from agent_board.nonfleet_sessions s join _mm_fixture f on f.agent_id = s.agent_id
|
|
178
|
+
where f.label = 'codex' and s.revoked_at is null and s.session_id <> (select session_id from _mm_results where label = 'first-a')), 0::bigint,
|
|
179
|
+
'SIDSERVER-MM-FP-remint-session-revoked: the refused mint leaves no unrevoked session row');
|
|
180
|
+
select is((select count(*) from agent_board.agent_signing_key_challenges c join _mm_fixture f on f.agent_id = c.agent_id
|
|
181
|
+
where f.label = 'codex' and c.consumed_at is null), 0::bigint,
|
|
182
|
+
'SIDSERVER-MM-FP-remint-challenge-consumed: the refused proof is single-use');
|
|
183
|
+
select lives_ok(format($q$select pg_temp.mm('codex', 'after-freeze-a', 'approved', 'bound-codex', 'same_alias', null, %L)$q$,
|
|
184
|
+
(select secret from _mm_results where label = 'first-a')),
|
|
185
|
+
'SIDSERVER-MM-FP-sticky-returns: an approved reading after the freeze still enters the measured mint');
|
|
186
|
+
select is((select refusal from _mm_results where label = 'after-freeze-a'), 'identity_fingerprint_unapproved',
|
|
187
|
+
'SIDSERVER-MM-FP-sticky: the freeze is sticky; restoring the reading does not unfreeze');
|
|
188
|
+
|
|
189
|
+
-- BLOCK 1: a FIRST claim measuring digest B is refused and frozen, never claimed.
|
|
190
|
+
select lives_ok($q$select pg_temp.mm('codex-b', 'first-b', 'changed-b', 'bound-codex-b', 'same_alias')$q$,
|
|
191
|
+
'SIDSERVER-MM-FP-first-returns: a changed first claim RETURNS its refusal so the freeze commits');
|
|
192
|
+
select is((select row(refusal, session_id, secret)::text from _mm_results where label = 'first-b'),
|
|
193
|
+
'(identity_fingerprint_unapproved,,)', 'SIDSERVER-MM-FP-first-refused: a changed first claim gets no session or holder secret');
|
|
194
|
+
select is((select row(resolution_state, current_risky_digest, holder)::text from pg_temp.mm_state('codex-b')),
|
|
195
|
+
'(frozen,changed-b,f)', 'SIDSERVER-MM-FP-first-frozen: digest B is recorded, frozen, and the lease is never claimed');
|
|
196
|
+
|
|
197
|
+
-- Unresolved never approves; recorded-only changes do not freeze.
|
|
198
|
+
-- The digest equals the approved one, so only the resolution term can freeze this reading.
|
|
199
|
+
select lives_ok($q$select pg_temp.mm('unresolved', 'unresolved', 'approved', 'bound-unresolved', 'same_alias', null, null, 'unresolved')$q$,
|
|
200
|
+
'SIDSERVER-MM-FP-unresolved-returns: an unresolved reading enters and returns');
|
|
201
|
+
select is((select row(r.refusal, s.resolution_state)::text from _mm_results r, pg_temp.mm_state('unresolved') s where r.label = 'unresolved'),
|
|
202
|
+
'(identity_fingerprint_unapproved,frozen)', 'SIDSERVER-MM-FP-unresolved: an unresolved reading freezes and refuses');
|
|
203
|
+
select lives_ok($q$select pg_temp.mm('recorded', 'recorded', 'approved', 'bound-recorded', 'same_alias', null, null, 'resolved', '{"model":"m2"}')$q$,
|
|
204
|
+
'SIDSERVER-MM-FP-recorded-only-control: a recorded-only change enters the measured mint');
|
|
205
|
+
select is((select row(r.refusal is null, s.resolution_state, s.recorded ->> 'model')::text from _mm_results r, pg_temp.mm_state('recorded') s where r.label = 'recorded'),
|
|
206
|
+
'(t,active,m2)', 'SIDSERVER-MM-FP-recorded-only: a recorded-only change is stored without freezing and still claims');
|
|
207
|
+
|
|
208
|
+
-- Measurement shape is mandatory on the measured form.
|
|
209
|
+
select throws_ok($q$select pg_temp.mm_refuse('select * from agent_board.mint_fleet_agent_session_measured(gen_random_uuid(), ''n'', gen_random_uuid(), gen_random_uuid(), 900, null, ''s'', null::jsonb, ''same_alias'', null)')$q$,
|
|
210
|
+
'22023', 'invalid identity fingerprint reading', 'SIDSERVER-MM-FP-reading-required: the measured form refuses a missing reading');
|
|
211
|
+
select throws_ok($q$select pg_temp.mm_refuse('select pg_temp.mm(''legacy'', ''legacy'', ''approved'', ''x'', ''same_alias'')')$q$,
|
|
212
|
+
'22023', 'measured mint requires a session identity', 'SIDSERVER-MM-FP-legacy-refused: a legacy identity cannot enter the measured form');
|
|
213
|
+
|
|
214
|
+
-- BLOCK 2: lapsed Claude reclaim through an UNFAMILIAR alias.
|
|
215
|
+
select lives_ok($q$select pg_temp.mm('claude', 'claude-first', 'approved', 'bound-claude', 'same_alias')$q$,
|
|
216
|
+
'SIDSERVER-MM-CONT-control-first: the Claude initial reserved alias claims through the measured form');
|
|
217
|
+
select pg_temp.mm_lapse('claude');
|
|
218
|
+
select throws_ok($q$select pg_temp.mm_refuse('select pg_temp.mm(''claude'', ''x'', ''approved'', ''unseen-same'', ''same_alias'')')$q$,
|
|
219
|
+
'42501', 'identity_continuity_refused', 'SIDSERVER-MM-CONT-same-alias: a lapsed Claude same_alias claim cannot bind an unseen id');
|
|
220
|
+
select is(pg_temp.mm_alias_count('claude', 'unseen-same'), 0::bigint, 'SIDSERVER-MM-CONT-same-alias-no-row: no alias row is written');
|
|
221
|
+
select throws_ok($q$select pg_temp.mm_refuse('select pg_temp.mm(''claude'', ''x'', ''approved'', ''unseen-new'', ''new'')')$q$,
|
|
222
|
+
'42501', 'identity_continuity_refused', 'SIDSERVER-MM-CONT-new: a lapsed Claude "new" claim cannot bind an unseen id');
|
|
223
|
+
select is(pg_temp.mm_alias_count('claude', 'unseen-new'), 0::bigint, 'SIDSERVER-MM-CONT-new-no-row: no alias row is written');
|
|
224
|
+
select is((select epoch from pg_temp.mm_state('claude')), (select epoch from _mm_results where label = 'claude-first'),
|
|
225
|
+
'SIDSERVER-MM-CONT-epoch-unchanged: refused unfamiliar-alias claims never rotate the epoch');
|
|
226
|
+
select throws_ok(format($q$select pg_temp.mm_refuse(%L)$q$, format($r$select pg_temp.mm('claude', 'x', 'approved', 'unseen-other', 'preserved_uuids', %L)$r$,
|
|
227
|
+
(select agent_id from _mm_fixture where label = 'codex'))),
|
|
228
|
+
'42501', 'identity_continuity_refused', 'SIDSERVER-MM-CONT-predecessor-other: preserved UUIDs naming another identity are refused');
|
|
229
|
+
select is(pg_temp.mm_alias_count('claude', 'unseen-other'), 0::bigint, 'SIDSERVER-MM-CONT-predecessor-other-no-row: no alias row is written');
|
|
230
|
+
select throws_ok(format($q$select pg_temp.mm_refuse(%L)$q$, format($r$select pg_temp.mm('claude', 'x', 'approved', 'bound-claude', 'same_alias', %L)$r$,
|
|
231
|
+
(select agent_id from _mm_fixture where label = 'claude'))),
|
|
232
|
+
'42501', 'identity_continuity_refused', 'SIDSERVER-MM-CONT-predecessor-without-preserved: only preserved UUIDs may name a predecessor');
|
|
233
|
+
reset role;
|
|
234
|
+
insert into agent_board.session_id_aliases(identity_agent_id, root_agent_id, harness, session_id)
|
|
235
|
+
select root_id, root_id, 'claude', 'taken-claude' from _mm_fixture where label = 'claude';
|
|
236
|
+
set local role service_role;
|
|
237
|
+
select throws_ok(format($q$select pg_temp.mm_refuse(%L)$q$, format($r$select pg_temp.mm('claude', 'x', 'approved', 'taken-claude', 'preserved_uuids', %L)$r$,
|
|
238
|
+
(select agent_id from _mm_fixture where label = 'claude'))),
|
|
239
|
+
'42501', 'identity_continuity_refused', 'SIDSERVER-MM-GATE-alias-collision: an id already bound under the same root and harness is never rebound');
|
|
240
|
+
select lives_ok(format($q$select pg_temp.mm('claude', 'claude-resumed', 'approved', 'resumed-claude', 'preserved_uuids', %L)$q$,
|
|
241
|
+
(select agent_id from _mm_fixture where label = 'claude')),
|
|
242
|
+
'SIDSERVER-MM-CONT-preserved: measured preserved-UUID continuity of this identity reclaims through a new id');
|
|
243
|
+
select is((select row(r.refusal is null, r.secret ~ '^[0-9a-f]{64}$', r.epoch - (select epoch from _mm_results where label = 'claude-first'),
|
|
244
|
+
pg_temp.mm_alias_count('claude', 'resumed-claude'))::text from _mm_results r where r.label = 'claude-resumed'),
|
|
245
|
+
'(t,t,1,1)', 'SIDSERVER-MM-CONT-preserved-state: the resumed id is bound, a new holder issued and the epoch fenced');
|
|
246
|
+
|
|
247
|
+
-- Own-alias reclaim and the live-lease refusal are unchanged.
|
|
248
|
+
select pg_temp.mm_lapse('claude');
|
|
249
|
+
select lives_ok($q$select pg_temp.mm('claude', 'claude-own', 'approved', 'bound-claude', 'same_alias')$q$,
|
|
250
|
+
'SIDSERVER-MM-CONT-own-alias: a lapsed holder reclaims through its own bound alias');
|
|
251
|
+
select is((select r.epoch - (select epoch from _mm_results where label = 'claude-resumed') from _mm_results r where r.label = 'claude-own'), 1::bigint,
|
|
252
|
+
'SIDSERVER-MM-CONT-own-alias-epoch: own-alias reclaim fences the prior holder');
|
|
253
|
+
select throws_ok($q$select pg_temp.mm_refuse('select pg_temp.mm(''claude'', ''x'', ''approved'', ''bound-claude'', ''same_alias'')')$q$,
|
|
254
|
+
'42501', 'duplicate_live_session', 'SIDSERVER-MM-CONT-live-duplicate: the identical live session is still refused');
|
|
255
|
+
select throws_ok($q$select pg_temp.mm_refuse('select pg_temp.mm(''claude'', ''x'', ''approved'', ''resumed-claude'', ''same_alias'')')$q$,
|
|
256
|
+
'42501', 'identity_lease_live', 'SIDSERVER-MM-CONT-live-lease: another id cannot take a live lease');
|
|
257
|
+
|
|
258
|
+
-- Codex never binds a new alias, whatever it measures ('recorded' is a claimed Codex identity).
|
|
259
|
+
select pg_temp.mm_lapse('recorded');
|
|
260
|
+
select throws_ok($q$select pg_temp.mm_refuse('select pg_temp.mm(''recorded'', ''x'', ''approved'', ''unseen-codex'', ''same_alias'')')$q$,
|
|
261
|
+
'42501', 'identity_continuity_refused', 'SIDSERVER-MM-CONT-codex-same-alias: a lapsed Codex identity cannot bind an unseen id');
|
|
262
|
+
select throws_ok(format($q$select pg_temp.mm_refuse(%L)$q$, format($r$select pg_temp.mm('recorded', 'x', 'approved', 'unseen-codex', 'preserved_uuids', %L)$r$,
|
|
263
|
+
(select agent_id from _mm_fixture where label = 'recorded'))),
|
|
264
|
+
'42501', 'identity_continuity_refused', 'SIDSERVER-MM-CONT-codex-preserved: a lapsed Codex identity cannot bind an unseen id even claiming preserved UUIDs');
|
|
265
|
+
select is(pg_temp.mm_alias_count('recorded', 'unseen-codex'), 0::bigint, 'SIDSERVER-MM-CONT-codex-no-row: no Codex alias row is written');
|
|
266
|
+
|
|
267
|
+
-- Behavioural half of gate parity: one refusal per gate of the trusted form, entered through the
|
|
268
|
+
-- measured mint with every other gate satisfied.
|
|
269
|
+
select throws_ok($q$select pg_temp.mm_refuse('select pg_temp.mm(''tomb'', ''x'', ''approved'', ''bound-tomb'', ''same_alias'')')$q$,
|
|
270
|
+
'42501', 'identity_tombstoned', 'SIDSERVER-MM-GATE-tombstoned: a tombstoned identity is refused');
|
|
271
|
+
-- The challenge is issued while the key is still active and proven (issuance refuses an inactive
|
|
272
|
+
-- key); the key then loses its proof before the mint, so only the mint's own gate can refuse it.
|
|
273
|
+
select pg_temp.mm_issue('unproven');
|
|
274
|
+
reset role;
|
|
275
|
+
update agent_board.agent_signing_keys set status = 'revoked', revoked_at = now(), proof_state = 'unproven'
|
|
276
|
+
where id = (select key_id from _mm_fixture where label = 'unproven');
|
|
277
|
+
set local role service_role;
|
|
278
|
+
select throws_ok($q$select pg_temp.mm_refuse('select pg_temp.mm_fixed(''unproven'', ''x'')')$q$,
|
|
279
|
+
'42501', 'key_unproven: session key requires proof', 'SIDSERVER-MM-GATE-key-unproven: an unproven session key is refused before the challenge is used');
|
|
280
|
+
select throws_ok($q$select pg_temp.mm_refuse('select pg_temp.mm(''unclaimed'', ''x'', ''approved'', ''not-reserved'', ''same_alias'')')$q$,
|
|
281
|
+
'42501', 'identity_continuity_refused', 'SIDSERVER-MM-GATE-first-claim-alias: a first claim must use the reserved alias');
|
|
282
|
+
select lives_ok($q$select pg_temp.mm('ancestor', 'ancestor', 'approved', 'bound-ancestor', 'same_alias')$q$,
|
|
283
|
+
'SIDSERVER-MM-GATE-frozen-ancestor-returns: a frozen ancestor refusal is returned, not raised');
|
|
284
|
+
select is((select row(r.refusal, r.secret, s.resolution_state)::text from _mm_results r, pg_temp.mm_state('ancestor') s where r.label = 'ancestor'),
|
|
285
|
+
'(identity_fingerprint_unapproved,,active)', 'SIDSERVER-MM-GATE-frozen-ancestor: a frozen ancestor refuses an approved child without freezing it');
|
|
286
|
+
select pg_temp.mm_issue('replay');
|
|
287
|
+
select lives_ok($q$select pg_temp.mm_fixed('replay', 'replay-first')$q$, 'SIDSERVER-MM-GATE-replay-control: the fixed challenge mints once');
|
|
288
|
+
reset role;
|
|
289
|
+
select pg_temp.mm_lapse('replay');
|
|
290
|
+
set local role service_role;
|
|
291
|
+
select throws_ok($q$select pg_temp.mm_refuse('select pg_temp.mm_fixed(''replay'', ''replay-again'')')$q$,
|
|
292
|
+
'23505', null, 'SIDSERVER-MM-GATE-nonce-replay: the inherited challenge gate refuses a second use');
|
|
293
|
+
select throws_ok(format($q$select pg_temp.mm_refuse(%L)$q$, format($r$select pg_temp.mm('claude', 'x', 'approved', 'resumed-claude', 'same_alias', null, %L)$r$,
|
|
294
|
+
repeat('0', 64))),
|
|
295
|
+
'42501', 'identity_lease_not_held', 'SIDSERVER-MM-GATE-holder-mismatch: a live lease with the wrong holder secret is refused');
|
|
296
|
+
|
|
297
|
+
-- Claimant-chosen holder secret (#653 re-panel BLOCK B): a claim stores the presented secret, so
|
|
298
|
+
-- a claimant whose response was lost refreshes its own live lease by re-presenting it, while any
|
|
299
|
+
-- other claimant of the identical session id is still refused.
|
|
300
|
+
select pg_temp.mm_lapse('claude');
|
|
301
|
+
select lives_ok(format($q$select pg_temp.mm('claude', 'claimant-claim', 'approved', 'bound-claude', 'same_alias', null, %L)$q$, repeat('a', 64)),
|
|
302
|
+
'SIDSERVER-MM-CLAIMANT-claim-control: a lapsed holder reclaims presenting its own secret');
|
|
303
|
+
select lives_ok(format($q$select pg_temp.mm('claude', 'claimant-retry', 'approved', 'bound-claude', 'same_alias', null, %L)$q$, repeat('a', 64)),
|
|
304
|
+
'SIDSERVER-MM-CLAIMANT-lost-response: re-presenting the claimed secret after a lost response is not refused');
|
|
305
|
+
select is((select row(c.secret = repeat('a', 64), r.refusal is null, r.secret is null, r.epoch = c.epoch)::text
|
|
306
|
+
from _mm_results c, _mm_results r where c.label = 'claimant-claim' and r.label = 'claimant-retry'),
|
|
307
|
+
'(t,t,t,t)', 'SIDSERVER-MM-CLAIMANT-stored: the claim stored the presented secret and the retry refreshed it at the same epoch');
|
|
308
|
+
select throws_ok(format($q$select pg_temp.mm_refuse(%L)$q$, format($r$select pg_temp.mm('claude', 'x', 'approved', 'bound-claude', 'same_alias', null, %L)$r$,
|
|
309
|
+
repeat('b', 64))),
|
|
310
|
+
'42501', 'duplicate_live_session', 'SIDSERVER-MM-CLAIMANT-other: another claimant of the identical live session id is refused whatever it presents');
|
|
311
|
+
select throws_ok(format($q$select pg_temp.mm_refuse(%L)$q$, $r$select pg_temp.mm('claude', 'x', 'approved', 'bound-claude', 'same_alias', null, 'not-a-secret')$r$),
|
|
312
|
+
'22023', 'invalid holder secret', 'SIDSERVER-MM-CLAIMANT-malformed: a presented secret that is not 64 lower hex is refused before it could be stored');
|
|
313
|
+
|
|
314
|
+
-- Positive footprint of the legacy-key relay exemption (deps.ts five-key call): the trusted
|
|
315
|
+
-- form WITHOUT a session id can never claim a session identity.
|
|
316
|
+
select lives_ok($q$select pg_temp.mm('claude-lapse', 'cl-first', 'approved', 'bound-claude-lapse', 'same_alias')$q$,
|
|
317
|
+
'SIDSERVER-MM-EXEMPT-control: a Claude fixture claims before its lease lapses');
|
|
318
|
+
select pg_temp.mm_lapse('claude-lapse');
|
|
319
|
+
select throws_ok($q$select pg_temp.mm_refuse('select pg_temp.mm_trusted(''claude-lapse'', null)')$q$,
|
|
320
|
+
'42501', 'identity_continuity_refused', 'SIDSERVER-MM-EXEMPT-legacy-key-lapsed: the trusted form without a session id refuses a lapsed session identity');
|
|
321
|
+
select throws_ok($q$select pg_temp.mm_refuse('select pg_temp.mm_trusted(''codex-b'', null)')$q$,
|
|
322
|
+
'42501', 'identity_fingerprint_unapproved', 'SIDSERVER-MM-EXEMPT-legacy-key-frozen: the trusted form refuses the identity frozen by a measured mint');
|
|
323
|
+
reset role;
|
|
324
|
+
update agent_board.session_identities set resolution_state = 'active', current_risky_digest = 'approved'
|
|
325
|
+
where agent_id = (select agent_id from _mm_fixture where label = 'unresolved');
|
|
326
|
+
set local role service_role;
|
|
327
|
+
select throws_ok($q$select pg_temp.mm_refuse('select pg_temp.mm_trusted(''unresolved'', null)')$q$,
|
|
328
|
+
'42501', 'identity_continuity_refused', 'SIDSERVER-MM-EXEMPT-legacy-key-unclaimed: the trusted form without a session id refuses an unclaimed session identity');
|
|
329
|
+
-- And a direct non-service caller is refused by both overloads.
|
|
330
|
+
reset role;
|
|
331
|
+
select set_config('request.jwt.claim.role', 'authenticated', true);
|
|
332
|
+
set local role authenticated;
|
|
333
|
+
select throws_ok($q$select * from agent_board.mint_fleet_agent_session_measured(gen_random_uuid(), 'n', gen_random_uuid(), gen_random_uuid(), 900, null, 's', '{"resolution":"resolved","risky_digest":"approved"}'::jsonb, 'same_alias', null)$q$,
|
|
334
|
+
'42501', null, 'SIDSERVER-MM-CAT-05: authenticated cannot execute the measured mint');
|
|
335
|
+
reset role;
|
|
336
|
+
-- Inherited service-role gate, entered by a caller that CAN execute it (the owner) without the
|
|
337
|
+
-- service_role claim, so the grant cannot mask the in-body gate.
|
|
338
|
+
select throws_ok($q$select pg_temp.mm_refuse('select * from agent_board.mint_fleet_agent_session_measured(gen_random_uuid(), ''n'', gen_random_uuid(), gen_random_uuid(), 900, null, ''s'', ''{"resolution":"resolved","risky_digest":"approved"}''::jsonb, ''same_alias'', null)')$q$,
|
|
339
|
+
'42501', 'session mint is service_role-only', 'SIDSERVER-MM-GATE-service-role: a non-service claim is refused in the body, not only by the grant');
|
|
340
|
+
|
|
341
|
+
select * from finish();
|
|
342
|
+
rollback;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
-- SIDSERVER SL-5 acceptance-audit closure (plan amendment 2026-11-01, decision 2): RPC-level predicate isolation for
|
|
2
|
+
-- the matrix rows `mismatch-renewed` (row 17) and `ancestor-digest-bypassed` (row 42). Every allow path is lives_ok
|
|
3
|
+
-- followed by a separate state check. Where another guard would mask a predicate, the assertion isolates it with
|
|
4
|
+
-- every other gate satisfied. renew_session_identity calls renew_delegated_grant, and that function repeats the
|
|
5
|
+
-- refusal, so these assertions pin the function that RAISED as well as the SQLSTATE and message. States the public
|
|
6
|
+
-- writers cannot produce (active but drifted, active with a null approval) are constructed inside this rolled-back
|
|
7
|
+
-- transaction, only to isolate a predicate. They are never an approved test parent.
|
|
8
|
+
create extension if not exists pgtap with schema extensions;
|
|
9
|
+
set search_path = extensions, public, agent_board;
|
|
10
|
+
begin;
|
|
11
|
+
grant usage on schema extensions to authenticated, service_role;
|
|
12
|
+
select plan(21);
|
|
13
|
+
create temp table _sid_iso_root(root_id uuid, root_auth uuid);
|
|
14
|
+
create temp table _sid_iso_children(label text primary key, agent_id uuid, key_id uuid, expires_at timestamptz);
|
|
15
|
+
create temp table _sid_iso_tokens(label text, expires_at timestamptz, epoch bigint);
|
|
16
|
+
create temp table _sid_iso_results(label text primary key, value boolean);
|
|
17
|
+
do $$
|
|
18
|
+
declare v_root uuid := gen_random_uuid(); v_auth uuid;
|
|
19
|
+
begin
|
|
20
|
+
v_auth := public.ensure_github_user('sid-iso-' || v_root, 'sid-iso-' || v_root || '@example.test', 'sid-iso-' || v_root, 'Isolation root');
|
|
21
|
+
insert into agent_board.agents(id, agent_type, handle, display_name, status, auth_user_id, owner_user_id)
|
|
22
|
+
values (v_root, 'agent', 'sid-iso-' || v_root, 'Isolation root', 'active', v_auth, v_auth);
|
|
23
|
+
insert into agent_board.session_identities(agent_id, root_agent_id, depth, current_risky_digest, approved_risky_digest)
|
|
24
|
+
values (v_root, v_root, 0, 'approved', 'approved');
|
|
25
|
+
insert into _sid_iso_root values (v_root, v_auth);
|
|
26
|
+
end;
|
|
27
|
+
$$;
|
|
28
|
+
grant select on _sid_iso_root, _sid_iso_children to authenticated, service_role;
|
|
29
|
+
grant insert on _sid_iso_children, _sid_iso_tokens, _sid_iso_results to service_role;
|
|
30
|
+
create function pg_temp.sid_iso_mint(p_label text, p_parent text default null)
|
|
31
|
+
returns void language plpgsql as $$
|
|
32
|
+
declare v_parent uuid;
|
|
33
|
+
begin
|
|
34
|
+
v_parent := coalesce((select agent_id from _sid_iso_children where label = p_parent), (select root_id from _sid_iso_root));
|
|
35
|
+
insert into _sid_iso_children
|
|
36
|
+
select p_label, m.child_agent_id, m.signing_key_id, m.grant_expires_at
|
|
37
|
+
from agent_board.mint_delegated_child(v_parent, 'sid-iso-' || p_label || '-' || substr(v_parent::text, 1, 8), '[]',
|
|
38
|
+
public.test_pubkey(p_label || v_parent), interval '30 days', false, 'claude', 'iso-' || p_label,
|
|
39
|
+
case when p_parent is not null then '0' end, '[]', '{"resolution":"resolved","risky_digest":"approved"}'::jsonb) m;
|
|
40
|
+
end;
|
|
41
|
+
$$;
|
|
42
|
+
create function pg_temp.sid_iso_token(p_label text)
|
|
43
|
+
returns void language plpgsql as $$
|
|
44
|
+
declare v_child record; v_challenge record;
|
|
45
|
+
begin
|
|
46
|
+
select * into v_child from _sid_iso_children where label = p_label;
|
|
47
|
+
select * into v_challenge from agent_board.issue_signing_key_challenge(v_child.agent_id, 'delegated-ephemeral');
|
|
48
|
+
insert into _sid_iso_tokens
|
|
49
|
+
select p_label, m.expires_at, m.identity_lease_epoch from agent_board.mint_fleet_agent_session(
|
|
50
|
+
v_challenge.challenge_id, split_part(v_challenge.challenge, ':', 3), v_child.key_id, v_child.agent_id, 900, null, 'iso-' || p_label) m;
|
|
51
|
+
end;
|
|
52
|
+
$$;
|
|
53
|
+
-- Never raises: returns '<sqlstate>|<message>|<name of the function that raised>' for a refusal, or 'no refusal'.
|
|
54
|
+
create function pg_temp.sid_iso_refusal(p_call text)
|
|
55
|
+
returns text language plpgsql as $$
|
|
56
|
+
declare v_state text; v_message text; v_context text;
|
|
57
|
+
begin
|
|
58
|
+
execute p_call;
|
|
59
|
+
return 'no refusal';
|
|
60
|
+
exception when others then
|
|
61
|
+
get stacked diagnostics v_state = returned_sqlstate, v_message = message_text, v_context = pg_exception_context;
|
|
62
|
+
return v_state || '|' || v_message || '|'
|
|
63
|
+
|| coalesce(substring(split_part(v_context, E'\n', 1) from 'function (?:[a-z_]+\.)?([a-z_]+)\('), 'unknown');
|
|
64
|
+
end;
|
|
65
|
+
$$;
|
|
66
|
+
grant execute on function pg_temp.sid_iso_mint(text, text), pg_temp.sid_iso_token(text), pg_temp.sid_iso_refusal(text) to service_role, authenticated;
|
|
67
|
+
select set_config('request.jwt.claim.role', 'service_role', true);
|
|
68
|
+
set local role service_role;
|
|
69
|
+
select lives_ok($q$select agent_board.upsert_delegation_policy((select root_id from _sid_iso_root), 'root_minted', '[]', 200, interval '30 days', true)$q$,
|
|
70
|
+
'SIDSERVER-FP-03/isolation-policy-control: operator configures the root delegation policy');
|
|
71
|
+
select lives_ok($q$select pg_temp.sid_iso_mint('parent'); select pg_temp.sid_iso_mint('target', 'parent')$q$,
|
|
72
|
+
'SIDSERVER-FP-03/isolation-mint-control: approved parent and target session children enter the real issuer');
|
|
73
|
+
reset role;
|
|
74
|
+
select is((select count(*) from _sid_iso_children), 2::bigint, 'SIDSERVER-FP-03/isolation-mint-state: both principals exist');
|
|
75
|
+
select is((select array[approved_risky_digest, current_risky_digest, resolution_state] from agent_board.session_identities
|
|
76
|
+
where agent_id = (select agent_id from _sid_iso_children where label = 'parent')), array['approved', 'approved', 'active'],
|
|
77
|
+
'SIDSERVER-FP-03/isolation-parent-state: the parent is approved, unchanged and active');
|
|
78
|
+
set local role service_role;
|
|
79
|
+
select lives_ok($q$select pg_temp.sid_iso_token('parent')$q$,
|
|
80
|
+
'SIDSERVER-FP-03/isolation-token-control: real challenge and key gates claim the parent lease');
|
|
81
|
+
reset role;
|
|
82
|
+
select is((select count(*) from _sid_iso_tokens where label = 'parent'), 1::bigint, 'SIDSERVER-FP-03/isolation-token-state: the parent holds an epoch-fenced lease');
|
|
83
|
+
select set_config('request.jwt.claim.sub', (select auth_user_id::text from agent_board.agents where id = (select agent_id from _sid_iso_children where label = 'parent')), true);
|
|
84
|
+
select set_config('request.jwt.claim.identity_lease_epoch', (select epoch::text from _sid_iso_tokens where label = 'parent'), true);
|
|
85
|
+
update agent_board.delegation_grants set grant_expires_at = now() + interval '1 hour' where child_agent_id = (select agent_id from _sid_iso_children where label = 'parent');
|
|
86
|
+
set local role service_role;
|
|
87
|
+
select lives_ok($q$select agent_board.renew_session_identity((select agent_id from _sid_iso_children where label = 'parent'), 'approved')$q$,
|
|
88
|
+
'SIDSERVER-FP-03/isolation-renew-control: the approved live holder renews through renew_session_identity');
|
|
89
|
+
reset role;
|
|
90
|
+
select is((select grant_expires_at from agent_board.delegation_grants where child_agent_id = (select agent_id from _sid_iso_children where label = 'parent')), now() + interval '7 days',
|
|
91
|
+
'SIDSERVER-FP-03/isolation-renew-state: that renewal extends the grant to the seven-day ceiling');
|
|
92
|
+
update agent_board.delegation_grants set grant_expires_at = now() + interval '1 hour' where child_agent_id = (select agent_id from _sid_iso_children where label = 'parent');
|
|
93
|
+
set local role service_role;
|
|
94
|
+
select lives_ok($q$select agent_board.renew_delegated_grant((select agent_id from _sid_iso_children where label = 'parent'), interval '2 hours')$q$,
|
|
95
|
+
'SIDSERVER-FP-03/isolation-delegated-control: the approved live holder renews through renew_delegated_grant');
|
|
96
|
+
reset role;
|
|
97
|
+
select is((select grant_expires_at from agent_board.delegation_grants where child_agent_id = (select agent_id from _sid_iso_children where label = 'parent')), now() + interval '2 hours',
|
|
98
|
+
'SIDSERVER-FP-03/isolation-delegated-state: that renewal extends the grant by the requested interval');
|
|
99
|
+
|
|
100
|
+
-- Row 17, renew_session_identity/current-vs-approved: active, approved = supplied, current drifted.
|
|
101
|
+
update agent_board.session_identities set current_risky_digest = 'changed' where agent_id = (select agent_id from _sid_iso_children where label = 'parent');
|
|
102
|
+
set local role service_role;
|
|
103
|
+
select is(pg_temp.sid_iso_refusal($q$select agent_board.renew_session_identity((select agent_id from _sid_iso_children where label = 'parent'), 'approved')$q$),
|
|
104
|
+
'42501|identity_fingerprint_unapproved|renew_session_identity',
|
|
105
|
+
'SIDSERVER-FP-03/mismatch/renew_session_identity/current-vs-approved: renew_session_identity itself refuses a drifted current reading before the delegated grant gate');
|
|
106
|
+
reset role;
|
|
107
|
+
-- Row 17, renew_session_identity/approved-null and renew_delegated_grant/approved-null: active, no approval, no reading.
|
|
108
|
+
update agent_board.session_identities set current_risky_digest = null, approved_risky_digest = null where agent_id = (select agent_id from _sid_iso_children where label = 'parent');
|
|
109
|
+
select is((select array[coalesce(approved_risky_digest, '<null>'), coalesce(current_risky_digest, '<null>'), resolution_state] from agent_board.session_identities
|
|
110
|
+
where agent_id = (select agent_id from _sid_iso_children where label = 'parent')), array['<null>', '<null>', 'active'],
|
|
111
|
+
'SIDSERVER-FP-03/isolation-null-state: the constructed identity is active with neither approval nor reading');
|
|
112
|
+
set local role service_role;
|
|
113
|
+
select is(pg_temp.sid_iso_refusal($q$select agent_board.renew_session_identity((select agent_id from _sid_iso_children where label = 'parent'), null)$q$),
|
|
114
|
+
'42501|identity_fingerprint_unapproved|renew_session_identity',
|
|
115
|
+
'SIDSERVER-FP-03/mismatch/renew_session_identity/approved-null: renew_session_identity itself refuses an identity without an approval before the delegated grant gate');
|
|
116
|
+
select is(pg_temp.sid_iso_refusal($q$select agent_board.renew_delegated_grant((select agent_id from _sid_iso_children where label = 'parent'), interval '3 hours')$q$),
|
|
117
|
+
'42501|identity_fingerprint_unapproved|renew_delegated_grant',
|
|
118
|
+
'SIDSERVER-FP-03/mismatch/renew_delegated_grant/approved-null: renew_delegated_grant refuses a session identity without an approval');
|
|
119
|
+
reset role;
|
|
120
|
+
select is((select grant_expires_at from agent_board.delegation_grants where child_agent_id = (select agent_id from _sid_iso_children where label = 'parent')), now() + interval '2 hours',
|
|
121
|
+
'SIDSERVER-FP-03/isolation-null-no-renewal: neither refused renewal extended the grant');
|
|
122
|
+
update agent_board.session_identities set current_risky_digest = 'approved', approved_risky_digest = 'approved' where agent_id = (select agent_id from _sid_iso_children where label = 'parent');
|
|
123
|
+
|
|
124
|
+
-- Row 42: _session_identity_ancestor_actor with the parent as the verified current actor and the target's reading approved.
|
|
125
|
+
set local role service_role;
|
|
126
|
+
select lives_ok($q$insert into _sid_iso_results select 'ancestor-control', agent_board._session_identity_ancestor_actor((select agent_id from _sid_iso_children where label = 'target'))$q$,
|
|
127
|
+
'SIDSERVER-APPROVE-01/ancestor-digest/isolation-control: the predicate is evaluated for the approved live parent');
|
|
128
|
+
reset role;
|
|
129
|
+
select is((select value from _sid_iso_results where label = 'ancestor-control'), true,
|
|
130
|
+
'SIDSERVER-APPROVE-01/ancestor-digest/isolation-state: the approved live parent is the target''s ancestor approver');
|
|
131
|
+
update agent_board.session_identities set current_risky_digest = 'drifted' where agent_id = (select agent_id from _sid_iso_children where label = 'parent');
|
|
132
|
+
set local role service_role;
|
|
133
|
+
select lives_ok($q$insert into _sid_iso_results select 'ancestor-current', agent_board._session_identity_ancestor_actor((select agent_id from _sid_iso_children where label = 'target'))$q$,
|
|
134
|
+
'SIDSERVER-APPROVE-01/ancestor-digest/current-control: the predicate is evaluated for an active parent whose reading drifted');
|
|
135
|
+
reset role;
|
|
136
|
+
select is((select value from _sid_iso_results where label = 'ancestor-current'), false,
|
|
137
|
+
'SIDSERVER-APPROVE-01/ancestor-digest/current: an active parent whose own current reading drifted from its approval cannot approve the target');
|
|
138
|
+
update agent_board.session_identities set current_risky_digest = null, approved_risky_digest = null where agent_id = (select agent_id from _sid_iso_children where label = 'parent');
|
|
139
|
+
set local role service_role;
|
|
140
|
+
select lives_ok($q$insert into _sid_iso_results select 'ancestor-null', agent_board._session_identity_ancestor_actor((select agent_id from _sid_iso_children where label = 'target'))$q$,
|
|
141
|
+
'SIDSERVER-APPROVE-01/ancestor-digest/non-null-control: the predicate is evaluated for an active parent without any approval');
|
|
142
|
+
reset role;
|
|
143
|
+
select is((select value from _sid_iso_results where label = 'ancestor-null'), false,
|
|
144
|
+
'SIDSERVER-APPROVE-01/ancestor-digest/non-null: an active parent without any approval cannot approve the target');
|
|
145
|
+
select * from finish();
|
|
146
|
+
rollback;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
create extension if not exists pgtap with schema extensions;
|
|
2
|
+
set search_path = extensions, public, agent_board;
|
|
3
|
+
begin;
|
|
4
|
+
select plan(32);
|
|
5
|
+
|
|
6
|
+
create function pg_temp.a(n integer) returns uuid language sql immutable as $$
|
|
7
|
+
select ('66500000-0000-4000-8000-' || lpad(n::text, 12, '0'))::uuid
|
|
8
|
+
$$;
|
|
9
|
+
create function pg_temp.claim(n integer) returns void language plpgsql as $$
|
|
10
|
+
begin
|
|
11
|
+
perform set_config('request.jwt.claims', json_build_object('role', 'authenticated', 'sub', pg_temp.a(n))::text, true);
|
|
12
|
+
end
|
|
13
|
+
$$;
|
|
14
|
+
insert into auth.users(id, instance_id, aud, role, email)
|
|
15
|
+
select pg_temp.a(n), '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated', 'prov665-' || n || '@test.invalid'
|
|
16
|
+
from generate_series(1, 15) n;
|
|
17
|
+
insert into agent_board.agents(id, agent_type, handle, display_name, status, auth_user_id)
|
|
18
|
+
select pg_temp.a(n), 'agent', 'prov665-' || n, 'Provenance ' || n, 'active', pg_temp.a(n)
|
|
19
|
+
from generate_series(1, 15) n;
|
|
20
|
+
insert into agent_board.boards(id, slug, name, created_by_agent_id, visibility)
|
|
21
|
+
values (pg_temp.a(100), 'prov665', 'Provenance', pg_temp.a(1), 'open');
|
|
22
|
+
insert into agent_board.board_memberships(board_id, agent_id, membership_state, role, created_by_agent_id)
|
|
23
|
+
values (pg_temp.a(100), pg_temp.a(1), 'joined', 'owner', pg_temp.a(1)),
|
|
24
|
+
(pg_temp.a(100), pg_temp.a(5), 'requested', 'member', pg_temp.a(5)),
|
|
25
|
+
(pg_temp.a(100), pg_temp.a(6), 'requested', 'member', pg_temp.a(6)),
|
|
26
|
+
(pg_temp.a(100), pg_temp.a(6), 'joined', 'member', pg_temp.a(6)),
|
|
27
|
+
(pg_temp.a(100), pg_temp.a(10), 'requested', 'member', pg_temp.a(10)),
|
|
28
|
+
(pg_temp.a(100), pg_temp.a(15), 'requested', 'member', pg_temp.a(15)),
|
|
29
|
+
(pg_temp.a(100), pg_temp.a(15), 'joined', 'member', pg_temp.a(15));
|
|
30
|
+
-- Subject-authored requests establish the consent an approval needs (#82).
|
|
31
|
+
do $$ declare a integer; begin
|
|
32
|
+
for a in 13..14 loop
|
|
33
|
+
perform pg_temp.claim(a);
|
|
34
|
+
perform agent_board.request_join_board(pg_temp.a(100), pg_temp.a(a));
|
|
35
|
+
end loop;
|
|
36
|
+
end $$;
|
|
37
|
+
create temp table active_before as
|
|
38
|
+
select id, created_by_agent_id from agent_board.board_memberships
|
|
39
|
+
where board_id = pg_temp.a(100) and agent_id = pg_temp.a(15) and membership_state = 'joined';
|
|
40
|
+
|
|
41
|
+
-- The population is catalog-derived. No exemption list or hard-coded RPC scan.
|
|
42
|
+
create temp table provenance_sites as
|
|
43
|
+
select p.oid, p.oid::regprocedure::text as signature,
|
|
44
|
+
agent_board_test.stripped_body(p.oid) as body
|
|
45
|
+
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
|
46
|
+
where n.nspname = 'agent_board'
|
|
47
|
+
and 'p_created_by_agent_id' = any(p.proargnames)
|
|
48
|
+
and has_function_privilege('authenticated', p.oid, 'EXECUTE')
|
|
49
|
+
and agent_board_test.stripped_body(p.oid) ~ agent_board_test.call_re('agent_board._upsert_board_membership_state');
|
|
50
|
+
select is((select string_agg(p.proname::text, ',' order by p.proname) from provenance_sites s join pg_proc p on p.oid = s.oid),
|
|
51
|
+
'invite_board_member,join_board,resolve_board_request', 'PROV-1 membership provenance class population');
|
|
52
|
+
select is((select coalesce(string_agg(signature, ', '), '') from provenance_sites where body ~ agent_board_test.ident_re('p_created_by_agent_id')),
|
|
53
|
+
'', 'PROV-2 no membership RPC consumes caller-asserted provenance; offenders named');
|
|
54
|
+
select is((select coalesce(string_agg(signature, ', '), '') from provenance_sites
|
|
55
|
+
where has_function_privilege('anon', oid, 'EXECUTE') or not has_function_privilege('service_role', oid, 'EXECUTE')),
|
|
56
|
+
'', 'PROV-3 membership provenance class has restricted ACLs');
|
|
57
|
+
select is((select coalesce(string_agg(signature, ', '), '') from provenance_sites where obj_description(oid, 'pg_proc') is null),
|
|
58
|
+
'', 'PROV-4 membership provenance class documents attribution');
|
|
59
|
+
|
|
60
|
+
select pg_temp.claim(1);
|
|
61
|
+
select lives_ok($$select agent_board.invite_board_member(pg_temp.a(100), pg_temp.a(3), pg_temp.a(2))$$, 'PROV-5 invite accepts compatibility attribution');
|
|
62
|
+
select is((select created_by_agent_id from agent_board.board_memberships where board_id = pg_temp.a(100) and agent_id = pg_temp.a(3)), pg_temp.a(1), 'PROV-6 invite attribution is session actor');
|
|
63
|
+
select lives_ok($$select agent_board.invite_board_member(pg_temp.a(100), pg_temp.a(4), null)$$, 'PROV-7 invite accepts NULL attribution');
|
|
64
|
+
select is((select created_by_agent_id from agent_board.board_memberships where board_id = pg_temp.a(100) and agent_id = pg_temp.a(4)), pg_temp.a(1), 'PROV-8 NULL invite attribution is session actor');
|
|
65
|
+
select lives_ok($$select agent_board.resolve_board_request(pg_temp.a(100), pg_temp.a(5), 'removed', pg_temp.a(2))$$, 'PROV-9 resolution accepts compatibility attribution');
|
|
66
|
+
select is((select created_by_agent_id from agent_board.board_memberships where board_id = pg_temp.a(100) and agent_id = pg_temp.a(5) and membership_state = 'left'), pg_temp.a(1), 'PROV-10 resolution attribution is session actor');
|
|
67
|
+
select lives_ok($$select agent_board.resolve_board_request(pg_temp.a(100), pg_temp.a(6), 'joined', pg_temp.a(2))$$, 'PROV-11 redundant pending retirement succeeds');
|
|
68
|
+
select is((select created_by_agent_id from agent_board.board_memberships where board_id = pg_temp.a(100) and agent_id = pg_temp.a(6) and membership_state = 'left'), pg_temp.a(1), 'PROV-12 retirement attribution is session actor');
|
|
69
|
+
select is((select created_by_agent_id from agent_board.board_memberships where board_id = pg_temp.a(100) and agent_id = pg_temp.a(6) and membership_state = 'joined'), pg_temp.a(6), 'PROV-13 no-op preserves active row attribution');
|
|
70
|
+
|
|
71
|
+
select pg_temp.claim(7);
|
|
72
|
+
select lives_ok($$select agent_board.join_board(pg_temp.a(100), pg_temp.a(7), 'joined', pg_temp.a(2))$$, 'PROV-14 self-join accepts compatibility attribution');
|
|
73
|
+
select is((select created_by_agent_id from agent_board.board_memberships where board_id = pg_temp.a(100) and agent_id = pg_temp.a(7)), pg_temp.a(7), 'PROV-15 join attribution is session actor');
|
|
74
|
+
select pg_temp.claim(8);
|
|
75
|
+
select lives_ok($$select agent_board.join_board(pg_temp.a(100), pg_temp.a(8), 'joined', null)$$, 'PROV-16 self-join accepts NULL attribution');
|
|
76
|
+
select is((select created_by_agent_id from agent_board.board_memberships where board_id = pg_temp.a(100) and agent_id = pg_temp.a(8)), pg_temp.a(8), 'PROV-17 NULL join attribution is session actor');
|
|
77
|
+
|
|
78
|
+
select set_config('request.jwt.claims', '{"role":"service_role"}', true);
|
|
79
|
+
select lives_ok($$select agent_board.invite_board_member(pg_temp.a(100), pg_temp.a(9), pg_temp.a(2))$$, 'PROV-18 unresolved service invitation succeeds');
|
|
80
|
+
select is((select created_by_agent_id from agent_board.board_memberships where board_id = pg_temp.a(100) and agent_id = pg_temp.a(9)), null::uuid, 'PROV-19 service invitation does not invent agent attribution');
|
|
81
|
+
-- #668 (owner-confirmed 2026-10-26): resolution requires a RESOLVED same-board owner/admin, so an
|
|
82
|
+
-- unresolved service-role session is refused and the pending row keeps its state and provenance.
|
|
83
|
+
select throws_ok($$select agent_board.resolve_board_request(pg_temp.a(100), pg_temp.a(10), 'removed', pg_temp.a(2))$$, '42501', null, 'PROV-20 unresolved service resolution refused');
|
|
84
|
+
select is((select string_agg(membership_state || ':' || coalesce(created_by_agent_id::text, 'null'), ',') from agent_board.board_memberships where board_id = pg_temp.a(100) and agent_id = pg_temp.a(10)),
|
|
85
|
+
'requested:' || pg_temp.a(10)::text, 'PROV-21 refused service resolution leaves the pending row and its provenance unchanged');
|
|
86
|
+
select lives_ok($$select agent_board.join_board(pg_temp.a(100), pg_temp.a(11), 'joined', pg_temp.a(2))$$, 'PROV-22 unresolved service join succeeds');
|
|
87
|
+
select is((select created_by_agent_id from agent_board.board_memberships where board_id = pg_temp.a(100) and agent_id = pg_temp.a(11)), null::uuid, 'PROV-23 service join does not invent agent attribution');
|
|
88
|
+
select set_config('request.jwt.claims', '{"role":"authenticated"}', true);
|
|
89
|
+
select throws_ok($$select agent_board.invite_board_member(pg_temp.a(100), pg_temp.a(12), pg_temp.a(1))$$, '42501', null, 'PROV-24 unresolved authenticated invite denied');
|
|
90
|
+
select is((select count(*)::integer from agent_board.board_memberships where board_id = pg_temp.a(100) and agent_id = pg_temp.a(12)), 0, 'PROV-25 denied invitation writes nothing');
|
|
91
|
+
|
|
92
|
+
-- A RESOLVED service-role owner session: attribution is that session's agent, never the forged
|
|
93
|
+
-- compatibility argument (pg_temp.a(2) is not even a member).
|
|
94
|
+
select set_config('request.jwt.claims', json_build_object('role', 'service_role', 'sub', pg_temp.a(1))::text, true);
|
|
95
|
+
select lives_ok($$select agent_board.resolve_board_request(pg_temp.a(100), pg_temp.a(13), 'joined', pg_temp.a(2))$$, 'PROV-26 resolved service owner approves with forged attribution');
|
|
96
|
+
select is((select string_agg(membership_state || ':' || coalesce(created_by_agent_id::text, 'null'), ',') from agent_board.board_memberships where board_id = pg_temp.a(100) and agent_id = pg_temp.a(13)),
|
|
97
|
+
'joined:' || pg_temp.a(1)::text, 'PROV-27 service owner approval attribution is the session actor');
|
|
98
|
+
select lives_ok($$select agent_board.resolve_board_request(pg_temp.a(100), pg_temp.a(14), 'removed', pg_temp.a(2))$$, 'PROV-28 resolved service owner rejects with forged attribution');
|
|
99
|
+
select is((select string_agg(membership_state || ':' || coalesce(created_by_agent_id::text, 'null'), ',') from agent_board.board_memberships where board_id = pg_temp.a(100) and agent_id = pg_temp.a(14)),
|
|
100
|
+
'left:' || pg_temp.a(1)::text, 'PROV-29 service owner rejection attribution is the session actor');
|
|
101
|
+
select lives_ok($$select agent_board.resolve_board_request(pg_temp.a(100), pg_temp.a(15), 'joined', pg_temp.a(2))$$, 'PROV-30 resolved service owner no-op retires the redundant request');
|
|
102
|
+
select is((select created_by_agent_id from agent_board.board_memberships where board_id = pg_temp.a(100) and agent_id = pg_temp.a(15) and membership_state = 'left'), pg_temp.a(1), 'PROV-31 service owner retirement attribution is the session actor');
|
|
103
|
+
select is((select array_agg(row(id, created_by_agent_id)::text) from agent_board.board_memberships where board_id = pg_temp.a(100) and agent_id = pg_temp.a(15) and membership_state = 'joined'),
|
|
104
|
+
(select array_agg(row(id, created_by_agent_id)::text) from active_before), 'PROV-32 no-op keeps the active row identity and attribution');
|
|
105
|
+
select * from finish();
|
|
106
|
+
rollback;
|