@consiliency/agent-board-schema 1.10.0 → 1.22.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/fixtures/already-applied.json +136 -0
- package/migrations/20260729000100_agent_board_fedsession_autojoin_default_board.sql +364 -0
- package/migrations/20260730000100_agent_board_gatenull_service_role_bypass.sql +369 -0
- package/migrations/20260730000200_agent_board_membstate_authz.sql +195 -0
- package/migrations/20260731000100_agent_board_agentsdir_column_scope.sql +88 -0
- package/migrations/20260801000100_agent_board_leaveidem_terminal_noop.sql +317 -0
- package/migrations/20260802000100_agent_board_rolereset_reentry_clamp.sql +237 -0
- package/migrations/20260803000100_agent_board_rowid_total_order.sql +1201 -0
- package/migrations/20260804000100_agent_board_hygiene_actor_binding_rls.sql +229 -0
- package/migrations/20260806000100_agent_board_consent_substrate.sql +339 -0
- package/migrations/20260807000100_agent_board_consent_emission.sql +619 -0
- package/migrations/20260808000100_agent_board_rejectsem.sql +145 -0
- package/migrations/20260809000100_agent_board_consent_gating.sql +492 -0
- package/migrations/20260810000100_agent_board_consent_locking.sql +485 -0
- package/migrations/20260811000100_agent_board_retention_metadata_revoke.sql +23 -0
- package/migrations/20260812000100_agent_board_pubexec_class_revoke.sql +54 -0
- package/migrations/20260813000100_agent_board_invitepolicy_block.sql +420 -0
- package/migrations/20260814000100_agent_board_obsgate_thread_post_policy.sql +239 -0
- package/migrations/20260815000100_agent_board_obsgate2_authoring_gates.sql +287 -0
- package/migrations/20260816000100_agent_board_obsgate3_task_lifecycle.sql +118 -0
- package/migrations/20260817000100_agent_board_obsgate4_claims_lifecycle.sql +250 -0
- package/migrations/20260818000100_agent_board_fleet_machines_rls_correlation.sql +43 -0
- package/migrations/20260819000100_agent_board_resolve_noop_retire.sql +166 -0
- package/migrations/20260820000100_agent_board_roomdeleg_lane_b.sql +206 -0
- package/migrations/20260821000100_agent_board_claimgate_claimkey.sql +372 -0
- package/migrations/20260822000100_agent_board_mint_harden.sql +710 -0
- package/migrations/20260822000200_agent_board_mint_harden_fingerprint_backfill.sql +207 -0
- package/migrations/20260823000100_agent_board_fleet_machine_hijack.sql +106 -0
- package/migrations/20260824000100_agent_board_claimgate_shadow.sql +443 -0
- package/migrations/20260825000100_agent_board_claimgate_enforce.sql +660 -0
- package/migrations/20260826000100_agent_board_fleet_machine_reactivation.sql +514 -0
- package/migrations/20260828000100_agent_board_delegation_cap_ceiling.sql +474 -0
- package/migrations/20260829000100_agent_board_fleet_machine_reclaim.sql +432 -0
- package/migrations/20260830000100_agent_board_delegation_sole_writer.sql +41 -0
- package/migrations/20260831000100_agent_board_fleet_domain_reclaim.sql +389 -0
- package/package.json +1 -1
- package/schema-manifest.json +309 -3
- package/src/tooling.test.mjs +123 -2
- package/tests/agent_board_actorspoof.test.sql +349 -0
- package/tests/agent_board_agentsdir.test.sql +154 -0
- package/tests/agent_board_authbound_boundary.test.sql +9 -1
- package/tests/agent_board_claimgate_enforce.test.sql +464 -0
- package/tests/agent_board_claimgate_predicate.test.sql +360 -0
- package/tests/agent_board_claimkey.test.sql +229 -0
- package/tests/agent_board_consent_backfill.test.sql +48 -0
- package/tests/agent_board_consent_emission.test.sql +601 -0
- package/tests/agent_board_consent_gating.test.sql +489 -0
- package/tests/agent_board_consent_substrate.test.sql +720 -0
- package/tests/agent_board_delegation_cap_ceiling.test.sql +303 -0
- package/tests/agent_board_delegation_enforcement.test.sql +55 -6
- package/tests/agent_board_delegation_policies.test.sql +107 -1
- package/tests/agent_board_fedsession.test.sql +6 -4
- package/tests/agent_board_fedsession_autojoin.test.sql +181 -0
- package/tests/agent_board_fleet_domain_reclaim.test.sql +419 -0
- package/tests/agent_board_fleet_machine_hijack.test.sql +190 -0
- package/tests/agent_board_fleet_machine_reactivation.test.sql +426 -0
- package/tests/agent_board_fleet_machine_reclaim.test.sql +509 -0
- package/tests/agent_board_fleet_machines_rls.test.sql +79 -0
- package/tests/agent_board_fleet_policy.test.sql +2 -2
- package/tests/agent_board_gatenull_membstate.test.sql +352 -0
- package/tests/agent_board_grants_hygiene.test.sql +408 -0
- package/tests/agent_board_identity_providers.test.sql +9 -0
- package/tests/agent_board_inbox_reads.test.sql +29 -12
- package/tests/agent_board_inbox_rpcs.test.sql +44 -34
- package/tests/agent_board_invitepolicy.test.sql +205 -0
- package/tests/agent_board_leaveidem.test.sql +194 -0
- package/tests/agent_board_mint_fleet_agent_session.test.sql +10 -10
- package/tests/agent_board_mint_harden.test.sql +303 -0
- package/tests/agent_board_mint_harden_backfill.test.sql +95 -0
- package/tests/agent_board_obsgate.test.sql +178 -0
- package/tests/agent_board_obsgate2.test.sql +153 -0
- package/tests/agent_board_obsgate3.test.sql +170 -0
- package/tests/agent_board_obsgate4.test.sql +196 -0
- package/tests/agent_board_portal_offline_invariant.test.sql +13 -2
- package/tests/agent_board_pubexec_class.test.sql +53 -0
- package/tests/agent_board_rejectsem.test.sql +271 -0
- package/tests/agent_board_resigning_refresh.test.sql +1 -1
- package/tests/agent_board_resolve_noop_retire.test.sql +71 -0
- package/tests/agent_board_retention_metadata_acl.test.sql +53 -0
- package/tests/agent_board_revoke_agent_subtree.test.sql +3 -3
- package/tests/agent_board_rolereset.test.sql +351 -0
- package/tests/agent_board_roomdeleg.test.sql +5 -3
- package/tests/agent_board_roomdeleg_lane_b.test.sql +144 -0
- package/tests/agent_board_roomlink.test.sql +25 -9
- package/tests/agent_board_rowid.test.sql +1274 -0
- package/tests/agent_board_signature_rls_reads.test.sql +3 -3
- package/tests/agent_board_signed_write_gateway.test.sql +3 -3
- package/tests/agent_board_signing_challenge_pending_cap.test.sql +2 -2
- package/tests/agent_board_signing_challenge_reaper.test.sql +2 -2
- package/tests/agent_board_signing_proof_rpcs.test.sql +4 -4
- package/tests/agent_board_signing_store.test.sql +6 -6
- package/tests/fixtures/isolation-substrate.sql +93 -0
- package/tests/fixtures/test-prereqs.sql +33 -0
- package/tests/isolation/expected/claimgate_ownerless_quota.out +36 -0
- package/tests/isolation/expected/claimkey_renew_race.out +38 -0
- package/tests/isolation/expected/consent_approval_lock.out +43 -0
- package/tests/isolation/expected/consent_emit_actual_state.out +37 -0
- package/tests/isolation/expected/domain_reclaim_revoke_race.out +40 -0
- package/tests/isolation/expected/reactivate_revoke_race.out +80 -0
- package/tests/isolation/expected/reclaim_revoke_race.out +40 -0
- package/tests/isolation/specs/claimgate_ownerless_quota.spec +77 -0
- package/tests/isolation/specs/claimkey_renew_race.spec +67 -0
- package/tests/isolation/specs/consent_approval_lock.spec +98 -0
- package/tests/isolation/specs/consent_emit_actual_state.spec +79 -0
- package/tests/isolation/specs/domain_reclaim_revoke_race.spec +88 -0
- package/tests/isolation/specs/reactivate_revoke_race.spec +115 -0
- package/tests/isolation/specs/reclaim_revoke_race.spec +94 -0
- package/tests/seeds/20260806000100_agent_board_consent_substrate.sql.pre.sql +19 -0
- package/tests/seeds/20260822000200_agent_board_mint_harden_fingerprint_backfill.sql.pre.sql +39 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
-- FEDSESSION-AUTOJOIN pgTAP: enroll_federated_human auto-joins a freshly provisioned
|
|
2
|
+
-- federated human to the provider's designated default board.
|
|
3
|
+
--
|
|
4
|
+
-- Proves: the additive default_board_id column + the service_role-only helper; a first
|
|
5
|
+
-- provision with an ACTIVE designated board yields exactly one baseline 'member'/'joined'
|
|
6
|
+
-- self-created membership tagged source=fedsession-autojoin; re-provision is idempotent (no
|
|
7
|
+
-- duplicate); a NULL default_board_id yields no membership; an INACTIVE designated board is
|
|
8
|
+
-- a non-fatal skip (the human still provisions, no membership); and a human who LEFT the
|
|
9
|
+
-- board is NOT re-joined on re-provision (first-time-only). Runs as postgres
|
|
10
|
+
-- (service_role-equivalent), mirroring agent_board_fedsession.test.sql.
|
|
11
|
+
|
|
12
|
+
create extension if not exists pgtap with schema extensions;
|
|
13
|
+
set search_path = extensions, public, agent_board;
|
|
14
|
+
|
|
15
|
+
begin;
|
|
16
|
+
|
|
17
|
+
select plan(14);
|
|
18
|
+
|
|
19
|
+
-- ---------------------------------------------------------------------------
|
|
20
|
+
-- Phase 0: metadata + privileges
|
|
21
|
+
-- ---------------------------------------------------------------------------
|
|
22
|
+
-- 1
|
|
23
|
+
select has_column('agent_board', 'identity_providers', 'default_board_id',
|
|
24
|
+
'identity_providers.default_board_id exists');
|
|
25
|
+
-- 2
|
|
26
|
+
select has_function('agent_board', '_fedsession_autojoin_default_board',
|
|
27
|
+
array['agent_board.identity_providers', 'uuid'],
|
|
28
|
+
'_fedsession_autojoin_default_board(identity_providers,uuid) exists');
|
|
29
|
+
-- 3
|
|
30
|
+
select function_privs_are('agent_board', '_fedsession_autojoin_default_board',
|
|
31
|
+
array['agent_board.identity_providers', 'uuid'], 'service_role', array['EXECUTE'],
|
|
32
|
+
'service_role may EXECUTE _fedsession_autojoin_default_board');
|
|
33
|
+
-- 4
|
|
34
|
+
select function_privs_are('agent_board', '_fedsession_autojoin_default_board',
|
|
35
|
+
array['agent_board.identity_providers', 'uuid'], 'authenticated', array[]::text[],
|
|
36
|
+
'authenticated may NOT EXECUTE _fedsession_autojoin_default_board');
|
|
37
|
+
|
|
38
|
+
-- ---------------------------------------------------------------------------
|
|
39
|
+
-- Fixtures + provisioning
|
|
40
|
+
-- ---------------------------------------------------------------------------
|
|
41
|
+
create temp table _aj (
|
|
42
|
+
board_active uuid, board_inactive uuid,
|
|
43
|
+
agent_h1 uuid, agent_h2 uuid, agent_h3 uuid,
|
|
44
|
+
iss1 text, sub1 text
|
|
45
|
+
) on commit drop;
|
|
46
|
+
grant all on table _aj to authenticated, service_role;
|
|
47
|
+
|
|
48
|
+
do $$
|
|
49
|
+
declare
|
|
50
|
+
s text := replace(substr(gen_random_uuid()::text, 1, 8), '-', '');
|
|
51
|
+
v_owner_agent uuid;
|
|
52
|
+
v_board_active uuid; v_board_inactive uuid;
|
|
53
|
+
v_iss1 text; v_iss2 text; v_iss3 text;
|
|
54
|
+
v_sub1 text := 'aj-user1-';
|
|
55
|
+
v_h1 uuid; v_h2 uuid; v_h3 uuid;
|
|
56
|
+
begin
|
|
57
|
+
v_iss1 := 'https://aj1-' || s || '.test';
|
|
58
|
+
v_iss2 := 'https://aj2-' || s || '.test';
|
|
59
|
+
v_iss3 := 'https://aj3-' || s || '.test';
|
|
60
|
+
v_sub1 := 'aj-user1-' || s;
|
|
61
|
+
|
|
62
|
+
-- A board-owner agent (created_by for the seeded boards).
|
|
63
|
+
insert into agent_board.agents(agent_type, kind, handle, display_name, status)
|
|
64
|
+
values ('system', 'system'::agent_board.agent_kind, 'aj-owner-' || s, 'AJ Owner', 'active')
|
|
65
|
+
returning id into v_owner_agent;
|
|
66
|
+
|
|
67
|
+
-- An ACTIVE default board and an ARCHIVED (inactive) board.
|
|
68
|
+
insert into agent_board.boards(slug, name, created_by_agent_id, visibility, status)
|
|
69
|
+
values ('aj-active-' || s, 'AJ Active', v_owner_agent, 'private', 'active')
|
|
70
|
+
returning id into v_board_active;
|
|
71
|
+
insert into agent_board.boards(slug, name, created_by_agent_id, visibility, status)
|
|
72
|
+
values ('aj-inactive-' || s, 'AJ Inactive', v_owner_agent, 'private', 'archived')
|
|
73
|
+
returning id into v_board_inactive;
|
|
74
|
+
|
|
75
|
+
-- P1: default board = ACTIVE board -> provision H1 (should auto-join).
|
|
76
|
+
insert into agent_board.identity_providers
|
|
77
|
+
(issuer, audience, supported, policy_approved, allowed_agent_kinds, default_board_id)
|
|
78
|
+
values (v_iss1, 'message-board', true, true, '{human}', v_board_active);
|
|
79
|
+
select e.agent_id into v_h1 from agent_board.enroll_federated_human(
|
|
80
|
+
v_iss1, 'message-board', v_sub1,
|
|
81
|
+
jsonb_build_object('sub', v_sub1, 'email_verified', false), 'H1') e;
|
|
82
|
+
|
|
83
|
+
-- P2: default board NULL -> provision H2 (no auto-join).
|
|
84
|
+
insert into agent_board.identity_providers
|
|
85
|
+
(issuer, audience, supported, policy_approved, allowed_agent_kinds, default_board_id)
|
|
86
|
+
values (v_iss2, 'message-board', true, true, '{human}', null);
|
|
87
|
+
select e.agent_id into v_h2 from agent_board.enroll_federated_human(
|
|
88
|
+
v_iss2, 'message-board', 'aj-user2-' || s,
|
|
89
|
+
jsonb_build_object('sub', 'aj-user2-' || s, 'email_verified', false), 'H2') e;
|
|
90
|
+
|
|
91
|
+
-- P3: default board = INACTIVE board -> provision H3 (no auto-join; provision still succeeds).
|
|
92
|
+
insert into agent_board.identity_providers
|
|
93
|
+
(issuer, audience, supported, policy_approved, allowed_agent_kinds, default_board_id)
|
|
94
|
+
values (v_iss3, 'message-board', true, true, '{human}', v_board_inactive);
|
|
95
|
+
select e.agent_id into v_h3 from agent_board.enroll_federated_human(
|
|
96
|
+
v_iss3, 'message-board', 'aj-user3-' || s,
|
|
97
|
+
jsonb_build_object('sub', 'aj-user3-' || s, 'email_verified', false), 'H3') e;
|
|
98
|
+
|
|
99
|
+
-- Idempotent re-provision of H1 (same issuer/sub) -> must NOT add a second membership.
|
|
100
|
+
perform e.agent_id from agent_board.enroll_federated_human(
|
|
101
|
+
v_iss1, 'message-board', v_sub1,
|
|
102
|
+
jsonb_build_object('sub', v_sub1, 'email_verified', false), 'H1') e;
|
|
103
|
+
|
|
104
|
+
insert into _aj values (v_board_active, v_board_inactive, v_h1, v_h2, v_h3, v_iss1, v_sub1);
|
|
105
|
+
end
|
|
106
|
+
$$;
|
|
107
|
+
|
|
108
|
+
-- H1 auto-joined the ACTIVE default board ------------------------------------------------
|
|
109
|
+
-- 5
|
|
110
|
+
select is(
|
|
111
|
+
(select count(*)::int from agent_board.board_memberships m, _aj
|
|
112
|
+
where m.board_id = _aj.board_active and m.agent_id = _aj.agent_h1),
|
|
113
|
+
1, 'H1 has exactly one membership on the active default board (idempotent after re-provision)');
|
|
114
|
+
-- 6
|
|
115
|
+
select is(
|
|
116
|
+
(select m.role from agent_board.board_memberships m, _aj
|
|
117
|
+
where m.board_id = _aj.board_active and m.agent_id = _aj.agent_h1),
|
|
118
|
+
'member', 'auto-join role is baseline member (never owner/admin)');
|
|
119
|
+
-- 7
|
|
120
|
+
select is(
|
|
121
|
+
(select m.membership_state from agent_board.board_memberships m, _aj
|
|
122
|
+
where m.board_id = _aj.board_active and m.agent_id = _aj.agent_h1),
|
|
123
|
+
'joined', 'auto-join membership_state is joined');
|
|
124
|
+
-- 8
|
|
125
|
+
select is(
|
|
126
|
+
(select m.created_by_agent_id from agent_board.board_memberships m, _aj
|
|
127
|
+
where m.board_id = _aj.board_active and m.agent_id = _aj.agent_h1),
|
|
128
|
+
(select agent_h1 from _aj), 'auto-join created_by is the fed-human itself (self-join)');
|
|
129
|
+
-- 9
|
|
130
|
+
select is(
|
|
131
|
+
(select m.metadata->>'source' from agent_board.board_memberships m, _aj
|
|
132
|
+
where m.board_id = _aj.board_active and m.agent_id = _aj.agent_h1),
|
|
133
|
+
'fedsession-autojoin', 'auto-join membership tagged source=fedsession-autojoin');
|
|
134
|
+
-- 10
|
|
135
|
+
select is(
|
|
136
|
+
(select count(distinct m.id)::int from agent_board.board_memberships m, _aj
|
|
137
|
+
where m.board_id = _aj.board_active and m.agent_id = _aj.agent_h1),
|
|
138
|
+
1, 'no duplicate membership rows after re-provision (idempotent)');
|
|
139
|
+
|
|
140
|
+
-- NULL default board -> no auto-join -----------------------------------------------------
|
|
141
|
+
-- 11
|
|
142
|
+
select is(
|
|
143
|
+
(select count(*)::int from agent_board.board_memberships m, _aj
|
|
144
|
+
where m.agent_id = _aj.agent_h2),
|
|
145
|
+
0, 'H2 (provider default_board_id NULL) got NO auto-join membership');
|
|
146
|
+
|
|
147
|
+
-- INACTIVE default board -> non-fatal skip -----------------------------------------------
|
|
148
|
+
-- 12
|
|
149
|
+
select ok((select agent_h3 is not null from _aj),
|
|
150
|
+
'H3 provisioned successfully even though its designated default board is inactive');
|
|
151
|
+
-- 13
|
|
152
|
+
select is(
|
|
153
|
+
(select count(*)::int from agent_board.board_memberships m, _aj
|
|
154
|
+
where m.board_id = _aj.board_inactive and m.agent_id = _aj.agent_h3),
|
|
155
|
+
0, 'inactive default board yields NO auto-join (non-fatal skip)');
|
|
156
|
+
|
|
157
|
+
-- Left-respect: a human who LEFT the board is NOT re-joined on re-provision ---------------
|
|
158
|
+
update agent_board.board_memberships m
|
|
159
|
+
set membership_state = 'left', left_at = now()
|
|
160
|
+
from _aj
|
|
161
|
+
where m.board_id = _aj.board_active and m.agent_id = _aj.agent_h1;
|
|
162
|
+
|
|
163
|
+
do $$
|
|
164
|
+
declare r record;
|
|
165
|
+
begin
|
|
166
|
+
select iss1, sub1 into r from _aj;
|
|
167
|
+
perform e.agent_id from agent_board.enroll_federated_human(
|
|
168
|
+
r.iss1, 'message-board', r.sub1,
|
|
169
|
+
jsonb_build_object('sub', r.sub1, 'email_verified', false), 'H1') e;
|
|
170
|
+
end
|
|
171
|
+
$$;
|
|
172
|
+
|
|
173
|
+
-- 14
|
|
174
|
+
select is(
|
|
175
|
+
(select count(*)::int from agent_board.board_memberships m, _aj
|
|
176
|
+
where m.board_id = _aj.board_active and m.agent_id = _aj.agent_h1
|
|
177
|
+
and m.membership_state = 'joined'),
|
|
178
|
+
0, 'a fed-human who LEFT the default board is NOT re-joined on re-provision (first-time-only)');
|
|
179
|
+
|
|
180
|
+
select * from finish();
|
|
181
|
+
rollback;
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
-- #153 whole-domain squatting arbitration — reassign a squatted fleet_trust_domain's owner.
|
|
2
|
+
--
|
|
3
|
+
-- reclaim_fleet_trust_domain (service_role only) is the operator arbitration path for a WHOLE
|
|
4
|
+
-- domain squatted by someone other than its rightful owner — the residual #150's machine-level
|
|
5
|
+
-- reclaim could not cover. It reassigns fleet_trust_domains.owner_agent_id to the arbitrated new
|
|
6
|
+
-- owner under a row lock; the reassignment is atomic at the domain row and post-commit every authz
|
|
7
|
+
-- site (revoke_fleet_subject('trust_domain'), register_fleet_machine, set_fleet_receive_policy) reads
|
|
8
|
+
-- the new owner. SERIALIZATION CAVEAT (#159): only revoke_fleet_subject('trust_domain') is locked
|
|
9
|
+
-- against an in-flight reclaim here; register_fleet_machine / set_fleet_receive_policy are NOT yet
|
|
10
|
+
-- serialized (tracked in #159). It writes NO fleet_revocation_events row (a trust_domain ledger row
|
|
11
|
+
-- over-denies the whole domain incl. the new owner — #150 divergence rationale), and APPENDS the
|
|
12
|
+
-- audit to metadata.reclaim_history.
|
|
13
|
+
--
|
|
14
|
+
-- These assertions pin:
|
|
15
|
+
-- * reclaim reassigns owner_agent_id (state + returned prior owner) — PIN: skip the reassign ⇒
|
|
16
|
+
-- (3)/(10)/(12)/(13) fail (owner stays squatter, authority does not move).
|
|
17
|
+
-- * NO trust_domain revocation event is written by reclaim (7) — PIN: add a revoke-event write ⇒
|
|
18
|
+
-- a row appears ⇒ (7) fails. Pins the over-deny divergence rationale.
|
|
19
|
+
-- * NON-GOAL scope: a machine in the reclaimed domain KEEPS owner=squatter (8) — PIN: also
|
|
20
|
+
-- reassign machine owners ⇒ (8) fails. Documents domain-vs-machine separation.
|
|
21
|
+
-- * AUTHORITY BRACKET (rule #2/#3): each authority probe is bracketed by a BEFORE probe under the
|
|
22
|
+
-- same authenticated principal proving current_agent_id() resolves — so an AFTER flip is a
|
|
23
|
+
-- genuine authority change, not a null-actor artifact. register_fleet_machine into the domain is
|
|
24
|
+
-- the reversible authority lever: squatter CAN before / CANNOT after; new owner CANNOT before /
|
|
25
|
+
-- CAN after (9-12). The kill switch itself: squatter's revoke('trust_domain') is REFUSED after
|
|
26
|
+
-- reclaim (13) — PIN: skip reassign ⇒ squatter still owns ⇒ not refused ⇒ (13) fails.
|
|
27
|
+
-- * reclaim is operator-only: an authenticated caller (the victim) is refused 42501 (14) — PIN:
|
|
28
|
+
-- remove the _is_service_role() body guard ⇒ (14) fails (body guard isolated from the ACL by a
|
|
29
|
+
-- temp grant to authenticated).
|
|
30
|
+
-- * reclaim validates the new owner + provenance agent exist and the domain exists (15,16,17).
|
|
31
|
+
-- * REVOKED-DOMAIN RESIDUAL (documented, positive pin): reclaim of a domain the squatter already
|
|
32
|
+
-- revoked SUCCEEDS + transfers owner (18,19) but the domain STAYS revoked (20) — no domain
|
|
33
|
+
-- un-revoke path today. Pins the boundary positively (rule #1) rather than leaving a silent gap.
|
|
34
|
+
-- * AUDIT APPEND (rule #2): a second reclaim APPENDS to reclaim_history (21) — PIN: overwrite ⇒
|
|
35
|
+
-- length stays 1 ⇒ (21) fails.
|
|
36
|
+
-- * CLASS (rule #1 / EC-4): reclaim_fleet_trust_domain is the ONLY function-writer of
|
|
37
|
+
-- fleet_trust_domains.owner_agent_id (22) — catalog-derived, so a future owner-writer names
|
|
38
|
+
-- itself. (No RPC INSERTs a domain; domains are seeded by direct table INSERT, which is not a
|
|
39
|
+
-- function and cannot match.)
|
|
40
|
+
--
|
|
41
|
+
-- Tests run as session_user=postgres ⇒ _is_service_role() is true; probes and the authz assertion
|
|
42
|
+
-- flip to role=authenticated (jwt claims) to exercise the principal / body guard.
|
|
43
|
+
|
|
44
|
+
create extension if not exists pgtap with schema extensions;
|
|
45
|
+
set search_path = extensions, public, agent_board;
|
|
46
|
+
|
|
47
|
+
begin;
|
|
48
|
+
|
|
49
|
+
select plan(22);
|
|
50
|
+
|
|
51
|
+
-- (1) Shape.
|
|
52
|
+
select has_function(
|
|
53
|
+
'agent_board', 'reclaim_fleet_trust_domain',
|
|
54
|
+
array['uuid', 'uuid', 'uuid', 'text'],
|
|
55
|
+
'reclaim_fleet_trust_domain exists'
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
create temp table _dr_fx (
|
|
59
|
+
operator uuid, -- arbitration actor (p_reclaimed_by); no auth mapping needed
|
|
60
|
+
squatter uuid, -- squats the domains; owner of dom_core/dom_revoked/dom_authz
|
|
61
|
+
new_owner uuid, -- arbitrated rightful owner
|
|
62
|
+
victim uuid, -- used for the operator-only authz probe
|
|
63
|
+
dom_core uuid, -- main reclaim + authority + kill-switch
|
|
64
|
+
dom_revoked uuid, -- revoked by the squatter BEFORE reclaim (residual pin)
|
|
65
|
+
dom_authz uuid, -- operator-only probe target
|
|
66
|
+
m_core uuid, -- a machine in dom_core (non-goal: stays owned by squatter)
|
|
67
|
+
squatter_user uuid,
|
|
68
|
+
new_owner_user uuid,
|
|
69
|
+
victim_user uuid
|
|
70
|
+
) on commit drop;
|
|
71
|
+
grant all on table _dr_fx to authenticated, service_role;
|
|
72
|
+
|
|
73
|
+
create temp table _dr_probe (label text, allowed boolean) on commit drop;
|
|
74
|
+
grant all on table _dr_probe to authenticated, service_role;
|
|
75
|
+
|
|
76
|
+
do $$
|
|
77
|
+
declare
|
|
78
|
+
v_operator uuid := gen_random_uuid();
|
|
79
|
+
v_squatter uuid := gen_random_uuid();
|
|
80
|
+
v_new_owner uuid := gen_random_uuid();
|
|
81
|
+
v_victim uuid := gen_random_uuid();
|
|
82
|
+
v_dom_core uuid;
|
|
83
|
+
v_dom_revoked uuid;
|
|
84
|
+
v_dom_authz uuid;
|
|
85
|
+
v_m_core uuid;
|
|
86
|
+
begin
|
|
87
|
+
insert into agent_board.agents(id, agent_type, handle, display_name)
|
|
88
|
+
values
|
|
89
|
+
(v_operator, 'agent', 'dr-operator', 'DR Operator'),
|
|
90
|
+
(v_squatter, 'agent', 'dr-squatter', 'DR Squatter'),
|
|
91
|
+
(v_new_owner, 'agent', 'dr-new-owner', 'DR New Owner'),
|
|
92
|
+
(v_victim, 'agent', 'dr-victim', 'DR Victim');
|
|
93
|
+
|
|
94
|
+
-- All three domains are squatted (owned by the squatter). dom_core + dom_authz active; dom_revoked
|
|
95
|
+
-- active now (revoked below via the squatter's own kill switch to exercise the residual).
|
|
96
|
+
insert into agent_board.fleet_trust_domains(slug, display_name, domain_kind, owner_agent_id, lifecycle_state, approved_at)
|
|
97
|
+
values
|
|
98
|
+
('dr-domain-core', 'DR Domain Core', 'tailnet', v_squatter, 'active', now()),
|
|
99
|
+
('dr-domain-revoked', 'DR Domain Revoked', 'tailnet', v_squatter, 'active', now()),
|
|
100
|
+
('dr-domain-authz', 'DR Domain Authz', 'tailnet', v_squatter, 'active', now());
|
|
101
|
+
select id into v_dom_core from agent_board.fleet_trust_domains where slug = 'dr-domain-core';
|
|
102
|
+
select id into v_dom_revoked from agent_board.fleet_trust_domains where slug = 'dr-domain-revoked';
|
|
103
|
+
select id into v_dom_authz from agent_board.fleet_trust_domains where slug = 'dr-domain-authz';
|
|
104
|
+
|
|
105
|
+
-- A machine the squatter owns inside dom_core (non-goal: domain reclaim must NOT reassign it).
|
|
106
|
+
select machine_id into v_m_core from agent_board.register_fleet_machine(
|
|
107
|
+
v_squatter, v_dom_core, 'dr-m-core', 'sha256:dr-m-core', 'portal', '{"redacted":true}'::jsonb);
|
|
108
|
+
|
|
109
|
+
insert into _dr_fx(operator, squatter, new_owner, victim, dom_core, dom_revoked, dom_authz, m_core)
|
|
110
|
+
values (v_operator, v_squatter, v_new_owner, v_victim, v_dom_core, v_dom_revoked, v_dom_authz, v_m_core);
|
|
111
|
+
end
|
|
112
|
+
$$;
|
|
113
|
+
|
|
114
|
+
-- Map the squatter, new owner and victim agents to auth users so the authenticated probes/authz
|
|
115
|
+
-- call resolve a principal via current_agent_id() → auth.uid().
|
|
116
|
+
do $$
|
|
117
|
+
declare
|
|
118
|
+
v_sfx text := replace(substr(gen_random_uuid()::text, 1, 8), '-', '');
|
|
119
|
+
v_squatter_user uuid;
|
|
120
|
+
v_new_owner_user uuid;
|
|
121
|
+
v_victim_user uuid;
|
|
122
|
+
begin
|
|
123
|
+
v_squatter_user := public.ensure_github_user('dr-squatter-' || v_sfx, 'dr-squatter-' || v_sfx || '@example.test', 'dr-squatter-' || v_sfx, 'DR Squatter ' || v_sfx);
|
|
124
|
+
v_new_owner_user := public.ensure_github_user('dr-newowner-' || v_sfx, 'dr-newowner-' || v_sfx || '@example.test', 'dr-newowner-' || v_sfx, 'DR New Owner ' || v_sfx);
|
|
125
|
+
v_victim_user := public.ensure_github_user('dr-victim-' || v_sfx, 'dr-victim-' || v_sfx || '@example.test', 'dr-victim-' || v_sfx, 'DR Victim ' || v_sfx);
|
|
126
|
+
|
|
127
|
+
update agent_board.agents set auth_user_id = v_squatter_user where id = (select squatter from _dr_fx);
|
|
128
|
+
update agent_board.agents set auth_user_id = v_new_owner_user where id = (select new_owner from _dr_fx);
|
|
129
|
+
update agent_board.agents set auth_user_id = v_victim_user where id = (select victim from _dr_fx);
|
|
130
|
+
|
|
131
|
+
update _dr_fx set squatter_user = v_squatter_user, new_owner_user = v_new_owner_user, victim_user = v_victim_user;
|
|
132
|
+
end
|
|
133
|
+
$$;
|
|
134
|
+
|
|
135
|
+
------------------------------------------------------------------------------------------
|
|
136
|
+
-- BEFORE authority probes (rule #2/#3 bracket): register_fleet_machine into dom_core is the
|
|
137
|
+
-- reversible authority lever (requires ftd.owner_agent_id = actor). The squatter (owner) CAN; the
|
|
138
|
+
-- new owner CANNOT — proving both principals resolve, so the AFTER flips below are genuine.
|
|
139
|
+
------------------------------------------------------------------------------------------
|
|
140
|
+
set local role authenticated;
|
|
141
|
+
select set_config('request.jwt.claim.role', 'authenticated', true);
|
|
142
|
+
select set_config('request.jwt.claim.sub', (select squatter_user::text from _dr_fx), true);
|
|
143
|
+
do $$
|
|
144
|
+
begin
|
|
145
|
+
begin
|
|
146
|
+
perform agent_board.register_fleet_machine((select squatter from _dr_fx), (select dom_core from _dr_fx),
|
|
147
|
+
'dr-m-sqbefore', 'sha256:dr-m-sqbefore', 'portal', '{"redacted":true}'::jsonb);
|
|
148
|
+
insert into _dr_probe values ('squatter_before', true);
|
|
149
|
+
exception when sqlstate '42501' then insert into _dr_probe values ('squatter_before', false);
|
|
150
|
+
end;
|
|
151
|
+
end $$;
|
|
152
|
+
select set_config('request.jwt.claim.sub', (select new_owner_user::text from _dr_fx), true);
|
|
153
|
+
do $$
|
|
154
|
+
begin
|
|
155
|
+
begin
|
|
156
|
+
perform agent_board.register_fleet_machine((select new_owner from _dr_fx), (select dom_core from _dr_fx),
|
|
157
|
+
'dr-m-nobefore', 'sha256:dr-m-nobefore', 'portal', '{"redacted":true}'::jsonb);
|
|
158
|
+
insert into _dr_probe values ('new_owner_before', true);
|
|
159
|
+
exception when sqlstate '42501' then insert into _dr_probe values ('new_owner_before', false);
|
|
160
|
+
end;
|
|
161
|
+
end $$;
|
|
162
|
+
reset role;
|
|
163
|
+
select set_config('request.jwt.claim.role', '', true);
|
|
164
|
+
select set_config('request.jwt.claim.sub', '', true);
|
|
165
|
+
|
|
166
|
+
------------------------------------------------------------------------------------------
|
|
167
|
+
-- CORE: operator reclaims dom_core from the squatter to the new owner.
|
|
168
|
+
------------------------------------------------------------------------------------------
|
|
169
|
+
-- (2) Reclaim succeeds (allow-side lives_ok + separate state reads, #113).
|
|
170
|
+
select lives_ok(
|
|
171
|
+
$$ create temp table _dr_core as
|
|
172
|
+
select * from agent_board.reclaim_fleet_trust_domain(
|
|
173
|
+
(select dom_core from _dr_fx), (select new_owner from _dr_fx),
|
|
174
|
+
(select operator from _dr_fx), 'operator arbitrated: domain belongs to new owner') $$,
|
|
175
|
+
'core: service_role reclaim of dom_core succeeds'
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
-- (3) Owner is reassigned. MUTATION PIN: skipping the owner reassignment leaves owner=squatter.
|
|
179
|
+
select is(
|
|
180
|
+
(select owner_agent_id from agent_board.fleet_trust_domains where id = (select dom_core from _dr_fx)),
|
|
181
|
+
(select new_owner from _dr_fx),
|
|
182
|
+
'core: reclaim reassigns owner_agent_id to the new owner'
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
-- (4) The returned prior owner is the squatter.
|
|
186
|
+
select is(
|
|
187
|
+
(select prior_owner_agent_id from _dr_core),
|
|
188
|
+
(select squatter from _dr_fx),
|
|
189
|
+
'core: reclaim returns the prior owner (the squatter)'
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
-- (5) Audit APPENDED to metadata.reclaim_history: last record has operator + prior owner.
|
|
193
|
+
select ok(
|
|
194
|
+
(select (metadata -> 'reclaim_history' -> -1 ->> 'reclaimed_by') = (select operator::text from _dr_fx)
|
|
195
|
+
and (metadata -> 'reclaim_history' -> -1 ->> 'prior_owner') = (select squatter::text from _dr_fx)
|
|
196
|
+
from agent_board.fleet_trust_domains where id = (select dom_core from _dr_fx)),
|
|
197
|
+
'core: reclaim audit (reclaimed_by + prior owner) appended to metadata.reclaim_history'
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
-- (6) The audit records the prior lifecycle (active here) — makes the revoked residual visible.
|
|
201
|
+
select is(
|
|
202
|
+
(select metadata -> 'reclaim_history' -> -1 ->> 'prior_lifecycle_state'
|
|
203
|
+
from agent_board.fleet_trust_domains where id = (select dom_core from _dr_fx)),
|
|
204
|
+
'active',
|
|
205
|
+
'core: reclaim audit records prior_lifecycle_state'
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
-- (7) NO trust_domain revocation event is written by reclaim (over-deny divergence rationale).
|
|
209
|
+
-- MUTATION PIN: adding a fleet_revocation_events write to reclaim makes a row appear → this flips.
|
|
210
|
+
select is(
|
|
211
|
+
(select count(*)::integer from agent_board.fleet_revocation_events
|
|
212
|
+
where subject_kind = 'trust_domain' and subject_id = (select dom_core from _dr_fx)),
|
|
213
|
+
0,
|
|
214
|
+
'core: reclaim writes NO trust_domain revocation event (no over-deny)'
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
-- (8) NON-GOAL: a machine in dom_core KEEPS owner=squatter (domain reclaim ≠ machine reclaim).
|
|
218
|
+
-- MUTATION PIN: also reassigning machine owners in reclaim flips this to new_owner.
|
|
219
|
+
select is(
|
|
220
|
+
(select owner_agent_id from agent_board.fleet_machines where id = (select m_core from _dr_fx)),
|
|
221
|
+
(select squatter from _dr_fx),
|
|
222
|
+
'non-goal: a machine in the reclaimed domain keeps owner=squatter'
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
------------------------------------------------------------------------------------------
|
|
226
|
+
-- AFTER authority probes: register authority on dom_core post-reclaim, then the kill switch.
|
|
227
|
+
------------------------------------------------------------------------------------------
|
|
228
|
+
set local role authenticated;
|
|
229
|
+
select set_config('request.jwt.claim.role', 'authenticated', true);
|
|
230
|
+
select set_config('request.jwt.claim.sub', (select squatter_user::text from _dr_fx), true);
|
|
231
|
+
do $$
|
|
232
|
+
begin
|
|
233
|
+
begin
|
|
234
|
+
perform agent_board.register_fleet_machine((select squatter from _dr_fx), (select dom_core from _dr_fx),
|
|
235
|
+
'dr-m-sqafter', 'sha256:dr-m-sqafter', 'portal', '{"redacted":true}'::jsonb);
|
|
236
|
+
insert into _dr_probe values ('squatter_after', true);
|
|
237
|
+
exception when sqlstate '42501' then insert into _dr_probe values ('squatter_after', false);
|
|
238
|
+
end;
|
|
239
|
+
end $$;
|
|
240
|
+
select set_config('request.jwt.claim.sub', (select new_owner_user::text from _dr_fx), true);
|
|
241
|
+
do $$
|
|
242
|
+
begin
|
|
243
|
+
begin
|
|
244
|
+
perform agent_board.register_fleet_machine((select new_owner from _dr_fx), (select dom_core from _dr_fx),
|
|
245
|
+
'dr-m-noafter', 'sha256:dr-m-noafter', 'portal', '{"redacted":true}'::jsonb);
|
|
246
|
+
insert into _dr_probe values ('new_owner_after', true);
|
|
247
|
+
exception when sqlstate '42501' then insert into _dr_probe values ('new_owner_after', false);
|
|
248
|
+
end;
|
|
249
|
+
end $$;
|
|
250
|
+
-- Kill switch: the squatter (no longer owner) tries revoke_fleet_subject('trust_domain', dom_core).
|
|
251
|
+
select set_config('request.jwt.claim.sub', (select squatter_user::text from _dr_fx), true);
|
|
252
|
+
do $$
|
|
253
|
+
begin
|
|
254
|
+
begin
|
|
255
|
+
perform agent_board.revoke_fleet_subject('trust_domain', (select dom_core from _dr_fx),
|
|
256
|
+
(select squatter from _dr_fx), 'squatter tries the kill switch after losing the domain',
|
|
257
|
+
'{"redacted":true}'::jsonb);
|
|
258
|
+
insert into _dr_probe values ('squatter_killswitch_denied', false); -- reached completion → NOT denied
|
|
259
|
+
exception when sqlstate '42501' then insert into _dr_probe values ('squatter_killswitch_denied', true);
|
|
260
|
+
end;
|
|
261
|
+
end $$;
|
|
262
|
+
reset role;
|
|
263
|
+
select set_config('request.jwt.claim.role', '', true);
|
|
264
|
+
select set_config('request.jwt.claim.sub', '', true);
|
|
265
|
+
|
|
266
|
+
-- (9)(10) Authority lever (register): squatter COULD before (bracket) → CANNOT after (PIN skip reassign).
|
|
267
|
+
select is((select allowed from _dr_probe where label = 'squatter_before'), true,
|
|
268
|
+
'bracket: the squatter (owner) could register into dom_core before reclaim');
|
|
269
|
+
select is((select allowed from _dr_probe where label = 'squatter_after'), false,
|
|
270
|
+
'squatter: loses domain authority (cannot register into dom_core) after reclaim');
|
|
271
|
+
-- (11)(12) New owner: CANNOT before (bracket) → CAN after (PIN skip reassign).
|
|
272
|
+
select is((select allowed from _dr_probe where label = 'new_owner_before'), false,
|
|
273
|
+
'bracket: the new owner could NOT register into dom_core before reclaim');
|
|
274
|
+
select is((select allowed from _dr_probe where label = 'new_owner_after'), true,
|
|
275
|
+
'new owner: gains domain authority (can register into dom_core) after reclaim');
|
|
276
|
+
-- (13) KILL SWITCH removed: the squatter's revoke('trust_domain') is refused after reclaim.
|
|
277
|
+
-- MUTATION PIN: skipping the owner reassign leaves the squatter owning dom_core → revoke is NOT
|
|
278
|
+
-- refused → denied=false → this flips.
|
|
279
|
+
select is((select allowed from _dr_probe where label = 'squatter_killswitch_denied'), true,
|
|
280
|
+
'kill-switch: the squatter''s revoke(trust_domain) is refused (42501) after reclaim');
|
|
281
|
+
|
|
282
|
+
------------------------------------------------------------------------------------------
|
|
283
|
+
-- AUTHZ (dom_authz): operator-only. An authenticated caller (the victim) is refused. The BODY guard
|
|
284
|
+
-- is isolated from the service_role-only grant by a temp grant to authenticated.
|
|
285
|
+
------------------------------------------------------------------------------------------
|
|
286
|
+
create temp table _dr_authz (denied boolean) on commit drop;
|
|
287
|
+
grant all on table _dr_authz to authenticated, service_role;
|
|
288
|
+
|
|
289
|
+
grant execute on function agent_board.reclaim_fleet_trust_domain(uuid, uuid, uuid, text) to authenticated;
|
|
290
|
+
|
|
291
|
+
set local role authenticated;
|
|
292
|
+
select set_config('request.jwt.claim.role', 'authenticated', true);
|
|
293
|
+
select set_config('request.jwt.claim.sub', (select victim_user::text from _dr_fx), true);
|
|
294
|
+
do $$
|
|
295
|
+
begin
|
|
296
|
+
begin
|
|
297
|
+
perform * from agent_board.reclaim_fleet_trust_domain(
|
|
298
|
+
(select dom_authz from _dr_fx), (select victim from _dr_fx),
|
|
299
|
+
(select victim from _dr_fx), 'victim self-reclaim attempt');
|
|
300
|
+
insert into _dr_authz values (false); -- reached completion → NOT denied
|
|
301
|
+
exception when sqlstate '42501' then insert into _dr_authz values (true);
|
|
302
|
+
end;
|
|
303
|
+
end
|
|
304
|
+
$$;
|
|
305
|
+
reset role;
|
|
306
|
+
select set_config('request.jwt.claim.role', '', true);
|
|
307
|
+
select set_config('request.jwt.claim.sub', '', true);
|
|
308
|
+
revoke execute on function agent_board.reclaim_fleet_trust_domain(uuid, uuid, uuid, text) from authenticated;
|
|
309
|
+
|
|
310
|
+
-- (14) MUTATION PIN: removing the _is_service_role() body guard lets the authenticated call
|
|
311
|
+
-- complete → denied=false → this flips.
|
|
312
|
+
select ok(
|
|
313
|
+
(select denied from _dr_authz),
|
|
314
|
+
'authz: authenticated caller (the victim) cannot reclaim (42501) — operator-only'
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
------------------------------------------------------------------------------------------
|
|
318
|
+
-- VALIDATION.
|
|
319
|
+
------------------------------------------------------------------------------------------
|
|
320
|
+
-- (15) unknown new owner
|
|
321
|
+
select throws_ok(
|
|
322
|
+
format($$ select * from agent_board.reclaim_fleet_trust_domain(%L::uuid, %L::uuid, %L::uuid, 'unknown new owner') $$,
|
|
323
|
+
(select dom_authz from _dr_fx), gen_random_uuid(), (select operator from _dr_fx)),
|
|
324
|
+
'22023', NULL, 'validate: reclaim to an unknown new owner raises'
|
|
325
|
+
);
|
|
326
|
+
-- (16) unknown provenance
|
|
327
|
+
select throws_ok(
|
|
328
|
+
format($$ select * from agent_board.reclaim_fleet_trust_domain(%L::uuid, %L::uuid, %L::uuid, 'unknown provenance') $$,
|
|
329
|
+
(select dom_authz from _dr_fx), (select new_owner from _dr_fx), gen_random_uuid()),
|
|
330
|
+
'22023', NULL, 'validate: reclaim with an unknown reclaimed_by agent raises'
|
|
331
|
+
);
|
|
332
|
+
-- (17) unknown domain
|
|
333
|
+
select throws_ok(
|
|
334
|
+
format($$ select * from agent_board.reclaim_fleet_trust_domain(%L::uuid, %L::uuid, %L::uuid, 'unknown domain') $$,
|
|
335
|
+
gen_random_uuid(), (select new_owner from _dr_fx), (select operator from _dr_fx)),
|
|
336
|
+
'22023', NULL, 'validate: reclaim of a non-existent domain raises'
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
------------------------------------------------------------------------------------------
|
|
340
|
+
-- REVOKED-DOMAIN RESIDUAL (documented, positive pin): the squatter revokes dom_revoked first; the
|
|
341
|
+
-- operator then reclaims it. Ownership transfers, but the domain STAYS revoked (no un-revoke path).
|
|
342
|
+
------------------------------------------------------------------------------------------
|
|
343
|
+
do $$
|
|
344
|
+
begin
|
|
345
|
+
perform agent_board.revoke_fleet_subject('trust_domain', (select dom_revoked from _dr_fx),
|
|
346
|
+
(select squatter from _dr_fx), 'squatter revoke before arbitration', '{"redacted":true}'::jsonb);
|
|
347
|
+
end
|
|
348
|
+
$$;
|
|
349
|
+
-- (18)
|
|
350
|
+
select lives_ok(
|
|
351
|
+
$$ select * from agent_board.reclaim_fleet_trust_domain(
|
|
352
|
+
(select dom_revoked from _dr_fx), (select new_owner from _dr_fx),
|
|
353
|
+
(select operator from _dr_fx), 'reclaim a revoked-then-squatted domain') $$,
|
|
354
|
+
'revoked-residual: reclaiming a revoked domain succeeds'
|
|
355
|
+
);
|
|
356
|
+
-- (19)
|
|
357
|
+
select is(
|
|
358
|
+
(select owner_agent_id from agent_board.fleet_trust_domains where id = (select dom_revoked from _dr_fx)),
|
|
359
|
+
(select new_owner from _dr_fx),
|
|
360
|
+
'revoked-residual: reclaim of a revoked domain still transfers ownership'
|
|
361
|
+
);
|
|
362
|
+
-- (20) BOUNDARY AS BEHAVIOR: the owner-transfer SUCCEEDED (owner=new_owner) AND the domain is STILL
|
|
363
|
+
-- revoked — one compound assertion so the intentional residual is pinned as behavior, not inferred
|
|
364
|
+
-- across two checks. PIN (mutation H): making reclaim revive the domain (lifecycle='active' on the
|
|
365
|
+
-- revoked branch) flips this; skipping the reassign (owner stays squatter) also flips it.
|
|
366
|
+
select ok(
|
|
367
|
+
(select owner_agent_id = (select new_owner from _dr_fx) and lifecycle_state = 'revoked'
|
|
368
|
+
from agent_board.fleet_trust_domains where id = (select dom_revoked from _dr_fx)),
|
|
369
|
+
'revoked-residual: after a successful owner-transfer the pre-revoked domain STAYS revoked (intentional boundary)'
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
------------------------------------------------------------------------------------------
|
|
373
|
+
-- AUDIT APPEND (rule #2): a SECOND reclaim of dom_core APPENDS to reclaim_history.
|
|
374
|
+
------------------------------------------------------------------------------------------
|
|
375
|
+
do $$
|
|
376
|
+
begin
|
|
377
|
+
perform * from agent_board.reclaim_fleet_trust_domain(
|
|
378
|
+
(select dom_core from _dr_fx), (select victim from _dr_fx),
|
|
379
|
+
(select operator from _dr_fx), 'second arbitration on the same domain');
|
|
380
|
+
end
|
|
381
|
+
$$;
|
|
382
|
+
-- (21) MUTATION PIN: reverting the audit to overwrite (single-element array / flat keys) leaves
|
|
383
|
+
-- reclaim_history at length 1 → this flips.
|
|
384
|
+
select is(
|
|
385
|
+
(select jsonb_array_length(metadata -> 'reclaim_history') from agent_board.fleet_trust_domains where id = (select dom_core from _dr_fx)),
|
|
386
|
+
2,
|
|
387
|
+
'audit: a repeated reclaim appends to reclaim_history (prior record preserved)'
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
------------------------------------------------------------------------------------------
|
|
391
|
+
-- CLASS (rule #1 / EC-4): catalog-derived — reclaim_fleet_trust_domain is the ONLY writer of
|
|
392
|
+
-- fleet_trust_domains.owner_agent_id. A future owner-writer appears here BY NAME. WRITER = a
|
|
393
|
+
-- function whose definition INSERTs owner_agent_id into fleet_trust_domains or UPDATEs
|
|
394
|
+
-- fleet_trust_domains assigning owner_agent_id. Reads (WHERE owner_agent_id = ...) and the lifecycle
|
|
395
|
+
-- -only revoke UPDATE (no owner_agent_id =) do not match. Aggregates excluded (prokind='f').
|
|
396
|
+
------------------------------------------------------------------------------------------
|
|
397
|
+
-- (22)
|
|
398
|
+
select is(
|
|
399
|
+
(
|
|
400
|
+
select string_agg(proname, ', ' order by proname)
|
|
401
|
+
from (
|
|
402
|
+
select p.proname
|
|
403
|
+
from pg_proc p
|
|
404
|
+
join pg_namespace n on n.oid = p.pronamespace
|
|
405
|
+
where n.nspname = 'agent_board'
|
|
406
|
+
and p.prokind = 'f'
|
|
407
|
+
and (
|
|
408
|
+
pg_get_functiondef(p.oid) ~ 'insert into agent_board\.fleet_trust_domains\s*\([^)]*owner_agent_id'
|
|
409
|
+
or pg_get_functiondef(p.oid) ~ 'update agent_board\.fleet_trust_domains[^;]*owner_agent_id\s*='
|
|
410
|
+
)
|
|
411
|
+
) writers
|
|
412
|
+
),
|
|
413
|
+
'reclaim_fleet_trust_domain',
|
|
414
|
+
'class: reclaim_fleet_trust_domain is the only writer of fleet_trust_domains.owner_agent_id (a new writer names itself)'
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
select * from finish();
|
|
418
|
+
|
|
419
|
+
rollback;
|