@consiliency/agent-board-schema 1.58.0 → 1.59.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 +4 -0
- package/migrations/20261009000100_agent_board_hosted_followups.sql +665 -0
- package/package.json +1 -1
- package/schema-manifest.json +12 -3
- package/src/tooling.test.mjs +6 -2
- package/tests/agent_board_hosted_delegates.test.sql +148 -4
- package/tests/catalog/function_execute_privileges.json +9 -0
- package/tests/catalog/p_metadata_parameters.json +4 -0
- package/tests/isolation/expected/hosted_delegate_add_tombstone_race.out +105 -0
- package/tests/isolation/expected/hosted_delegate_exchange_agent_revoke_race.out +66 -0
- package/tests/isolation/expected/hosted_delegate_tombstone_revoke_race.out +170 -0
- package/tests/isolation/specs/hosted_delegate_add_tombstone_race.spec +102 -0
- package/tests/isolation/specs/hosted_delegate_exchange_agent_revoke_race.spec +92 -0
- package/tests/isolation/specs/hosted_delegate_tombstone_revoke_race.spec +158 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# HOSTED follow-ups (Consiliency/message-board#591, item 3b): add_delegate_to_board racing
|
|
2
|
+
# tombstone_delegate_instance of the same instance.
|
|
3
|
+
#
|
|
4
|
+
# add_delegate_to_board reads the instance row FOR SHARE (after the board advisory lock and the
|
|
5
|
+
# owner's agents row) and refuses a tombstoned one; the tombstone holds that row FOR UPDATE while
|
|
6
|
+
# it tombstones the instance and ends every active membership it then lists. The instance row is
|
|
7
|
+
# the one lock the two contend on, so they serialise, and a tombstoned instance is never left with
|
|
8
|
+
# an active membership:
|
|
9
|
+
#
|
|
10
|
+
# P1 the owner's add first: the tombstone waits on the instance row, then lists the new
|
|
11
|
+
# membership and ends it (`left`). Tombstoned, revoked, no active membership.
|
|
12
|
+
# P2 the tombstone first: the add waits on the instance row, then reads the tombstone and is
|
|
13
|
+
# refused. Tombstoned, revoked, no membership at all.
|
|
14
|
+
#
|
|
15
|
+
# Without the FOR SHARE (measured, mutation T4 in plans/evidence/hosted/mutation-receipts.md) both
|
|
16
|
+
# orders leave a tombstoned instance joined to the board: in P1 the tombstone no longer waits and
|
|
17
|
+
# lists no membership to end; in P2 the add reads the instance before the tombstone commits and
|
|
18
|
+
# writes its membership without waiting (the sink's foreign-key FOR KEY SHARE on the delegate's
|
|
19
|
+
# agents row does not conflict with the tombstone's FOR NO KEY UPDATE). Removing the tombstone's
|
|
20
|
+
# own FOR UPDATE alone is NOT caught (T5): its UPDATE of the instance row takes the same lock
|
|
21
|
+
# before it lists memberships.
|
|
22
|
+
|
|
23
|
+
setup
|
|
24
|
+
{
|
|
25
|
+
insert into agent_board.agents(id, agent_type, kind, handle, display_name, status)
|
|
26
|
+
values ('16591100-0000-4000-8000-000000000001', 'agent', 'agent', 'iso-at-admin', 'Iso At Admin', 'active');
|
|
27
|
+
insert into agent_board.identity_providers(
|
|
28
|
+
issuer, audience, supported, policy_approved, allowed_scopes, allowed_board_scopes, allowed_agent_kinds
|
|
29
|
+
) values (
|
|
30
|
+
'https://iso-at.test', 'message-board', true, true, ARRAY[]::text[], ARRAY[]::text[], ARRAY['human']
|
|
31
|
+
);
|
|
32
|
+
select count(*) from agent_board.enroll_federated_human(
|
|
33
|
+
'https://iso-at.test', 'message-board', 'iso-at-owner',
|
|
34
|
+
jsonb_build_object('sub', 'iso-at-owner'), 'Iso At Owner');
|
|
35
|
+
select count(*) from agent_board.exchange_delegate_credential(jsonb_build_object(
|
|
36
|
+
'iss', 'https://iso-at.test', 'sub', 'iso-at-owner', 'slot_resource', null,
|
|
37
|
+
'agent_board_delegate_instance', jsonb_build_object(
|
|
38
|
+
'instance_id', 'iso_at_instance_00001', 'grant_id', 'iso_grant_0000000001', 'generation', 1,
|
|
39
|
+
'owner_sub', 'iso-at-owner', 'client_id', 'iso-client', 'status', 'active', 'client_change', null)));
|
|
40
|
+
insert into agent_board.boards(id, slug, name, created_by_agent_id, visibility, status)
|
|
41
|
+
values ('16591300-0000-4000-8000-000000000001', 'iso-at-board', 'Iso At Board',
|
|
42
|
+
'16591100-0000-4000-8000-000000000001', 'private', 'active');
|
|
43
|
+
insert into agent_board.board_memberships(board_id, agent_id, role, membership_state, created_by_agent_id)
|
|
44
|
+
select '16591300-0000-4000-8000-000000000001'::uuid, a.id, 'member', 'joined', '16591100-0000-4000-8000-000000000001'::uuid
|
|
45
|
+
from agent_board.agents a where a.handle = agent_board._federated_identity_slug('https://iso-at.test', 'iso-at-owner');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
teardown
|
|
49
|
+
{
|
|
50
|
+
delete from agent_board.board_membership_events where board_id = '16591300-0000-4000-8000-000000000001';
|
|
51
|
+
delete from agent_board.board_memberships where board_id = '16591300-0000-4000-8000-000000000001';
|
|
52
|
+
delete from agent_board.boards where id = '16591300-0000-4000-8000-000000000001';
|
|
53
|
+
delete from agent_board.agent_credentials
|
|
54
|
+
where agent_id in (select agent_id from agent_board.delegate_instances where instance_id like 'iso\_at\_%');
|
|
55
|
+
delete from agent_board.delegate_instances where instance_id like 'iso\_at\_%';
|
|
56
|
+
delete from agent_board.enrollment_federation_audit where issuer = 'https://iso-at.test';
|
|
57
|
+
delete from agent_board.agents
|
|
58
|
+
where handle like 'delegate-%' or handle like 'fed-human-%' or id = '16591100-0000-4000-8000-000000000001';
|
|
59
|
+
delete from agent_board.identity_providers where issuer = 'https://iso-at.test';
|
|
60
|
+
delete from auth.identities where provider_id like 'delegate-%' or provider_id like 'fed-human-%';
|
|
61
|
+
delete from auth.users where email like 'agent+delegate-%' or email like 'agent+fed-human-%';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
session "owner"
|
|
65
|
+
step "o_begin" {
|
|
66
|
+
BEGIN;
|
|
67
|
+
select set_config('request.jwt.claims', json_build_object('role', 'authenticated', 'sub',
|
|
68
|
+
(select auth_user_id from agent_board.agents
|
|
69
|
+
where handle = agent_board._federated_identity_slug('https://iso-at.test', 'iso-at-owner')))::text, true) is not null as owner_session;
|
|
70
|
+
}
|
|
71
|
+
step "o_add" {
|
|
72
|
+
select membership_state from agent_board.add_delegate_to_board(
|
|
73
|
+
(select agent_id from agent_board.delegate_instances where instance_id = 'iso_at_instance_00001'),
|
|
74
|
+
'16591300-0000-4000-8000-000000000001');
|
|
75
|
+
}
|
|
76
|
+
step "o_commit" { COMMIT; }
|
|
77
|
+
|
|
78
|
+
session "tomb"
|
|
79
|
+
step "t_begin" { BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role'; }
|
|
80
|
+
step "t_tomb" {
|
|
81
|
+
select first from agent_board.tombstone_delegate_instance(
|
|
82
|
+
'iso_at_instance_00001', 'https://iso-at.test', 'iso-at-owner', 'owner_revoked');
|
|
83
|
+
}
|
|
84
|
+
step "t_commit" { COMMIT; }
|
|
85
|
+
|
|
86
|
+
session "check"
|
|
87
|
+
step "check" {
|
|
88
|
+
select (select (di.tombstoned_at is not null)::text from agent_board.delegate_instances di
|
|
89
|
+
where di.instance_id = 'iso_at_instance_00001') as tombstoned,
|
|
90
|
+
(select a.status from agent_board.agents a
|
|
91
|
+
join agent_board.delegate_instances di on di.agent_id = a.id
|
|
92
|
+
where di.instance_id = 'iso_at_instance_00001') as delegate_status,
|
|
93
|
+
(select string_agg(bm.membership_state, ',' order by bm.created_at, bm.id) from agent_board.board_memberships bm
|
|
94
|
+
join agent_board.delegate_instances di on di.agent_id = bm.agent_id
|
|
95
|
+
where di.instance_id = 'iso_at_instance_00001') as delegate_memberships,
|
|
96
|
+
(select count(*) from agent_board.board_memberships bm
|
|
97
|
+
join agent_board.delegate_instances di on di.agent_id = bm.agent_id
|
|
98
|
+
where di.tombstoned_at is not null and bm.membership_state in ('joined', 'subscribed')) as tombstoned_and_active;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
permutation "o_begin" "o_add" "t_begin" "t_tomb" "o_commit" "t_commit" "check"
|
|
102
|
+
permutation "t_begin" "t_tomb" "o_begin" "o_add" "t_commit" "o_commit" "check"
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# HOSTED follow-ups (Consiliency/message-board#591, the class of item 8): an exchange that
|
|
2
|
+
# supersedes a generation racing revoke_agent_subtree of the same delegate.
|
|
3
|
+
#
|
|
4
|
+
# The exchange revokes every lower-generation credential (an UPDATE that locks those rows) and
|
|
5
|
+
# then inserts the new credential, whose foreign key takes FOR KEY SHARE on the delegate's agents
|
|
6
|
+
# row. _revoke_agent_principal takes that agents row FOR UPDATE and then the credentials: ABBA,
|
|
7
|
+
# the same shape as the tombstone's (hosted_delegate_tombstone_revoke_race P2). The exchange now
|
|
8
|
+
# takes the FOR KEY SHARE when it reads the agent's status, before any credential.
|
|
9
|
+
#
|
|
10
|
+
# The "hold" session parks the exchange mid-UPDATE deterministically: it holds the delegate's
|
|
11
|
+
# newest credential FOR UPDATE, so the exchange's superseding UPDATE locks the older one and waits.
|
|
12
|
+
#
|
|
13
|
+
# P1 Unfixed: the revocation takes the agents row, revokes the agent, and waits on the
|
|
14
|
+
# credential the exchange holds; released, the exchange's insert waits on the agents row:
|
|
15
|
+
# deadlock, the exchange is the victim (its deadlock_timeout is the shorter). Fixed: the
|
|
16
|
+
# revocation waits on the agents row; the exchange commits its generation-2 credential, and
|
|
17
|
+
# the revocation then revokes the agent and every credential, that one included.
|
|
18
|
+
|
|
19
|
+
setup
|
|
20
|
+
{
|
|
21
|
+
insert into agent_board.agents(id, agent_type, kind, handle, display_name, status)
|
|
22
|
+
values ('16591400-0000-4000-8000-000000000001', 'agent', 'agent', 'iso-xa-revoker', 'Iso Xa Revoker', 'active');
|
|
23
|
+
select count(*) from agent_board.exchange_delegate_credential(jsonb_build_object(
|
|
24
|
+
'iss', 'https://iso-xa.test', 'sub', 'iso-xa-owner', 'slot_resource', null,
|
|
25
|
+
'agent_board_delegate_instance', jsonb_build_object(
|
|
26
|
+
'instance_id', 'iso_xa_instance_00001', 'grant_id', 'iso_grant_0000000001', 'generation', 1,
|
|
27
|
+
'owner_sub', 'iso-xa-owner', 'client_id', 'iso-client', 'status', 'active', 'client_change', null)));
|
|
28
|
+
select count(*) from agent_board.exchange_delegate_credential(jsonb_build_object(
|
|
29
|
+
'iss', 'https://iso-xa.test', 'sub', 'iso-xa-owner', 'slot_resource', null,
|
|
30
|
+
'agent_board_delegate_instance', jsonb_build_object(
|
|
31
|
+
'instance_id', 'iso_xa_instance_00001', 'grant_id', 'iso_grant_0000000001', 'generation', 1,
|
|
32
|
+
'owner_sub', 'iso-xa-owner', 'client_id', 'iso-client', 'status', 'active', 'client_change', null)));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
teardown
|
|
36
|
+
{
|
|
37
|
+
delete from agent_board.fleet_revocation_events where revoked_by_agent_id = '16591400-0000-4000-8000-000000000001';
|
|
38
|
+
delete from agent_board.agent_credentials
|
|
39
|
+
where agent_id in (select agent_id from agent_board.delegate_instances where instance_id like 'iso\_xa\_%');
|
|
40
|
+
delete from agent_board.delegate_instances where instance_id like 'iso\_xa\_%';
|
|
41
|
+
delete from agent_board.agents where handle like 'delegate-%' or id = '16591400-0000-4000-8000-000000000001';
|
|
42
|
+
delete from auth.identities where provider_id like 'delegate-%' or provider_id like 'fed-human-%';
|
|
43
|
+
delete from auth.users where email like 'agent+delegate-%' or email like 'agent+fed-human-%';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
session "hold"
|
|
47
|
+
step "h_begin" {
|
|
48
|
+
BEGIN;
|
|
49
|
+
select (ac.id is not null) as held
|
|
50
|
+
from agent_board.agent_credentials ac
|
|
51
|
+
join agent_board.delegate_instances di on di.agent_id = ac.agent_id
|
|
52
|
+
where di.instance_id = 'iso_xa_instance_00001'
|
|
53
|
+
order by ac.created_at desc, ac.id desc
|
|
54
|
+
limit 1
|
|
55
|
+
for update of ac;
|
|
56
|
+
}
|
|
57
|
+
step "h_commit" { COMMIT; }
|
|
58
|
+
|
|
59
|
+
session "exch"
|
|
60
|
+
setup { SET deadlock_timeout = '100ms'; }
|
|
61
|
+
step "x_begin" { BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role'; }
|
|
62
|
+
step "x_gen2" {
|
|
63
|
+
select count(*) as issued from agent_board.exchange_delegate_credential(jsonb_build_object(
|
|
64
|
+
'iss', 'https://iso-xa.test', 'sub', 'iso-xa-owner', 'slot_resource', null,
|
|
65
|
+
'agent_board_delegate_instance', jsonb_build_object(
|
|
66
|
+
'instance_id', 'iso_xa_instance_00001', 'grant_id', 'iso_grant_0000000002', 'generation', 2,
|
|
67
|
+
'owner_sub', 'iso-xa-owner', 'client_id', 'iso-client', 'status', 'active', 'client_change', null)));
|
|
68
|
+
}
|
|
69
|
+
step "x_commit" { COMMIT; }
|
|
70
|
+
|
|
71
|
+
session "revoke"
|
|
72
|
+
setup { SET deadlock_timeout = '60s'; }
|
|
73
|
+
step "r_begin" { BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role'; }
|
|
74
|
+
step "r_agent" {
|
|
75
|
+
select revoked_agent_count from agent_board.revoke_agent_subtree(
|
|
76
|
+
(select agent_id from agent_board.delegate_instances where instance_id = 'iso_xa_instance_00001'),
|
|
77
|
+
'16591400-0000-4000-8000-000000000001', 'iso exchange revoke race');
|
|
78
|
+
}
|
|
79
|
+
step "r_commit" { COMMIT; }
|
|
80
|
+
|
|
81
|
+
session "check"
|
|
82
|
+
step "check" {
|
|
83
|
+
select a.status as delegate_status,
|
|
84
|
+
(select count(*) from agent_board.agent_credentials ac where ac.agent_id = a.id) as credentials,
|
|
85
|
+
(select count(*) from agent_board.agent_credentials ac where ac.agent_id = a.id and ac.generation = 2) as generation_2,
|
|
86
|
+
(select count(*) from agent_board.agent_credentials ac where ac.agent_id = a.id and ac.revoked_at is null) as live
|
|
87
|
+
from agent_board.agents a
|
|
88
|
+
join agent_board.delegate_instances di on di.agent_id = a.id
|
|
89
|
+
where di.instance_id = 'iso_xa_instance_00001';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
permutation "h_begin" "x_begin" "x_gen2" "r_begin" "r_agent" "h_commit" "x_commit" "r_commit" "check"
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# HOSTED follow-ups (Consiliency/message-board#591, items 8 and 4): tombstone_delegate_instance
|
|
2
|
+
# racing the revokers of the same delegate.
|
|
3
|
+
#
|
|
4
|
+
# The tombstone used to take the delegate's credentials first (its revoke UPDATE) and the
|
|
5
|
+
# delegate's agents row last (the membership event's foreign key, then its status UPDATE).
|
|
6
|
+
# _revoke_agent_principal -- the per-agent step of revoke_human_subtree and revoke_agent_subtree --
|
|
7
|
+
# takes the agents row FOR UPDATE first and the credentials after: ABBA. The tombstone now does its
|
|
8
|
+
# status UPDATE of the agents row (FOR NO KEY UPDATE) right after the instance row, before any
|
|
9
|
+
# credential. It takes NO lock on the owner's auth.users row: it reaches boards, and a board+owner
|
|
10
|
+
# route must take the board first (agent_board_federated_lock_order_class EC-3).
|
|
11
|
+
#
|
|
12
|
+
# P1 and P2 park the tombstone mid-function deterministically: the "hold" session holds the
|
|
13
|
+
# delegate's board_memberships row FOR UPDATE, which update_board_membership (called by the
|
|
14
|
+
# tombstone after it has revoked the credentials) waits for. The revoker is started while the
|
|
15
|
+
# tombstone is parked, then the hold is released.
|
|
16
|
+
#
|
|
17
|
+
# P1 revoke_human_subtree of the owner. Unfixed: the revocation takes the delegate's agents row
|
|
18
|
+
# and waits on the credentials the tombstone holds; released, the tombstone waits on that
|
|
19
|
+
# agents row: deadlock, the tombstone is the victim (its deadlock_timeout is the shorter) and
|
|
20
|
+
# rolls back. Fixed: the revocation waits on the agents row, and both commit: the instance
|
|
21
|
+
# is tombstoned AND the delegate revoked.
|
|
22
|
+
# P2 revoke_agent_subtree of the delegate itself. The same deadlock unfixed; the same wait fixed.
|
|
23
|
+
# P3 RECORDS A STATED RESIDUAL (AB15), it does not pin a fix: a pre-exchange tombstone (a NEW
|
|
24
|
+
# instance) racing revoke_human_subtree of an owner with no other evidence. The revocation
|
|
25
|
+
# does not wait for the tombstone, reads delegate_instances before the insert commits, and
|
|
26
|
+
# writes no marker (markers = 0). Reachable only for an owner whose auth user exists with no
|
|
27
|
+
# audit row, no human agents row and no recorded pair -- i.e. pre-1.59.0 evidence only -- so
|
|
28
|
+
# setup provisions the auth user directly. Closing it needs an owner-row lock in the
|
|
29
|
+
# tombstone, which EC-3 forbids (it would deadlock against enroll_federated_human). If a
|
|
30
|
+
# change makes this read 1, the residual closed: update AB15 and this golden together.
|
|
31
|
+
#
|
|
32
|
+
# What each permutation discriminates (measured, plans/evidence/hosted/mutation-receipts.md,
|
|
33
|
+
# section "#591 follow-ups"): P1 and P2 deadlock with the status UPDATE moved back after the
|
|
34
|
+
# memberships.
|
|
35
|
+
|
|
36
|
+
setup
|
|
37
|
+
{
|
|
38
|
+
insert into agent_board.agents(id, agent_type, kind, handle, display_name, status)
|
|
39
|
+
values ('16591000-0000-4000-8000-000000000001', 'agent', 'agent', 'iso-tr-revoker', 'Iso Tr Revoker', 'active');
|
|
40
|
+
insert into agent_board.identity_providers(
|
|
41
|
+
issuer, audience, supported, policy_approved, allowed_scopes, allowed_board_scopes, allowed_agent_kinds
|
|
42
|
+
) values (
|
|
43
|
+
'https://iso-tr.test', 'message-board', true, true, ARRAY[]::text[], ARRAY[]::text[], ARRAY['human']
|
|
44
|
+
);
|
|
45
|
+
select count(*) from agent_board.enroll_federated_human(
|
|
46
|
+
'https://iso-tr.test', 'message-board', 'iso-tr-owner',
|
|
47
|
+
jsonb_build_object('sub', 'iso-tr-owner'), 'Iso Tr Owner');
|
|
48
|
+
select count(*) from agent_board.exchange_delegate_credential(jsonb_build_object(
|
|
49
|
+
'iss', 'https://iso-tr.test', 'sub', 'iso-tr-owner', 'slot_resource', null,
|
|
50
|
+
'agent_board_delegate_instance', jsonb_build_object(
|
|
51
|
+
'instance_id', 'iso_tr_instance_00001', 'grant_id', 'iso_grant_0000000001', 'generation', 1,
|
|
52
|
+
'owner_sub', 'iso-tr-owner', 'client_id', 'iso-client', 'status', 'active', 'client_change', null)));
|
|
53
|
+
insert into agent_board.boards(id, slug, name, created_by_agent_id, visibility, status)
|
|
54
|
+
values ('16591200-0000-4000-8000-000000000001', 'iso-tr-board', 'Iso Tr Board',
|
|
55
|
+
'16591000-0000-4000-8000-000000000001', 'private', 'active');
|
|
56
|
+
insert into agent_board.board_memberships(board_id, agent_id, role, membership_state, created_by_agent_id)
|
|
57
|
+
select '16591200-0000-4000-8000-000000000001'::uuid, a.id, 'member', 'joined', '16591000-0000-4000-8000-000000000001'::uuid
|
|
58
|
+
from agent_board.agents a where a.handle = agent_board._federated_identity_slug('https://iso-tr.test', 'iso-tr-owner');
|
|
59
|
+
do $$
|
|
60
|
+
begin
|
|
61
|
+
perform set_config('request.jwt.claims', json_build_object('role', 'authenticated', 'sub',
|
|
62
|
+
(select auth_user_id from agent_board.agents
|
|
63
|
+
where handle = agent_board._federated_identity_slug('https://iso-tr.test', 'iso-tr-owner')))::text, true);
|
|
64
|
+
perform agent_board.add_delegate_to_board(
|
|
65
|
+
(select agent_id from agent_board.delegate_instances where instance_id = 'iso_tr_instance_00001'),
|
|
66
|
+
'16591200-0000-4000-8000-000000000001');
|
|
67
|
+
end $$;
|
|
68
|
+
-- P3's owner: an auth user and nothing else (see the header).
|
|
69
|
+
select agent_board._ensure_agent_auth_user(agent_board._federated_identity_slug('https://iso-tr.test', 'iso-tr-bare')) is not null as bare_owner;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
teardown
|
|
73
|
+
{
|
|
74
|
+
delete from agent_board.fleet_revocation_events where revoked_by_agent_id = '16591000-0000-4000-8000-000000000001';
|
|
75
|
+
delete from agent_board.fleet_human_revocations where issuer = 'https://iso-tr.test';
|
|
76
|
+
delete from agent_board.board_membership_events where board_id = '16591200-0000-4000-8000-000000000001';
|
|
77
|
+
delete from agent_board.board_memberships where board_id = '16591200-0000-4000-8000-000000000001';
|
|
78
|
+
delete from agent_board.boards where id = '16591200-0000-4000-8000-000000000001';
|
|
79
|
+
delete from agent_board.agent_credentials
|
|
80
|
+
where agent_id in (select agent_id from agent_board.delegate_instances where instance_id like 'iso\_tr\_%');
|
|
81
|
+
delete from agent_board.delegate_instances where instance_id like 'iso\_tr\_%';
|
|
82
|
+
delete from agent_board.enrollment_federation_audit where issuer = 'https://iso-tr.test';
|
|
83
|
+
delete from agent_board.agents
|
|
84
|
+
where handle like 'delegate-%' or handle like 'fed-human-%' or id = '16591000-0000-4000-8000-000000000001';
|
|
85
|
+
delete from agent_board.identity_providers where issuer = 'https://iso-tr.test';
|
|
86
|
+
delete from auth.identities where provider_id like 'delegate-%' or provider_id like 'fed-human-%';
|
|
87
|
+
delete from auth.users where email like 'agent+delegate-%' or email like 'agent+fed-human-%';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
session "hold"
|
|
91
|
+
step "h_begin" {
|
|
92
|
+
BEGIN;
|
|
93
|
+
select (bm.id is not null) as held from agent_board.board_memberships bm
|
|
94
|
+
join agent_board.delegate_instances di on di.agent_id = bm.agent_id
|
|
95
|
+
where di.instance_id = 'iso_tr_instance_00001' and bm.membership_state = 'joined'
|
|
96
|
+
for update of bm;
|
|
97
|
+
}
|
|
98
|
+
step "h_commit" { COMMIT; }
|
|
99
|
+
|
|
100
|
+
session "tomb"
|
|
101
|
+
setup { SET deadlock_timeout = '100ms'; }
|
|
102
|
+
step "t_begin" { BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role'; }
|
|
103
|
+
step "t_tomb" {
|
|
104
|
+
select first from agent_board.tombstone_delegate_instance(
|
|
105
|
+
'iso_tr_instance_00001', 'https://iso-tr.test', 'iso-tr-owner', 'owner_revoked');
|
|
106
|
+
}
|
|
107
|
+
step "t_tomb_bare" {
|
|
108
|
+
select first from agent_board.tombstone_delegate_instance(
|
|
109
|
+
'iso_tr_bare_instance_01', 'https://iso-tr.test', 'iso-tr-bare', 'owner_revoked');
|
|
110
|
+
}
|
|
111
|
+
step "t_commit" { COMMIT; }
|
|
112
|
+
|
|
113
|
+
session "revoke"
|
|
114
|
+
setup { SET deadlock_timeout = '60s'; }
|
|
115
|
+
step "r_begin" { BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role'; }
|
|
116
|
+
step "r_human" {
|
|
117
|
+
select revoked_agent_count from agent_board.revoke_human_subtree(
|
|
118
|
+
(select auth_user_id from agent_board.agents
|
|
119
|
+
where handle = agent_board._federated_identity_slug('https://iso-tr.test', 'iso-tr-owner')),
|
|
120
|
+
'16591000-0000-4000-8000-000000000001', 'iso tombstone revoke race');
|
|
121
|
+
}
|
|
122
|
+
step "r_agent" {
|
|
123
|
+
select revoked_agent_count from agent_board.revoke_agent_subtree(
|
|
124
|
+
(select agent_id from agent_board.delegate_instances where instance_id = 'iso_tr_instance_00001'),
|
|
125
|
+
'16591000-0000-4000-8000-000000000001', 'iso tombstone revoke race');
|
|
126
|
+
}
|
|
127
|
+
step "r_human_bare" {
|
|
128
|
+
select revoked_agent_count from agent_board.revoke_human_subtree(
|
|
129
|
+
(select id from auth.users
|
|
130
|
+
where email = 'agent+' || agent_board._federated_identity_slug('https://iso-tr.test', 'iso-tr-bare') || '@agent-board.local'),
|
|
131
|
+
'16591000-0000-4000-8000-000000000001', 'iso tombstone revoke race');
|
|
132
|
+
}
|
|
133
|
+
step "r_commit" { COMMIT; }
|
|
134
|
+
|
|
135
|
+
session "check"
|
|
136
|
+
step "check" {
|
|
137
|
+
select (select (di.tombstoned_at is not null)::text from agent_board.delegate_instances di
|
|
138
|
+
where di.instance_id = 'iso_tr_instance_00001') as tombstoned,
|
|
139
|
+
(select a.status from agent_board.agents a
|
|
140
|
+
join agent_board.delegate_instances di on di.agent_id = a.id
|
|
141
|
+
where di.instance_id = 'iso_tr_instance_00001') as delegate_status,
|
|
142
|
+
(select count(*) from agent_board.agent_credentials ac
|
|
143
|
+
join agent_board.delegate_instances di on di.agent_id = ac.agent_id
|
|
144
|
+
where di.instance_id = 'iso_tr_instance_00001' and ac.revoked_at is null) as live_credentials,
|
|
145
|
+
(select count(*) from agent_board.board_memberships bm
|
|
146
|
+
join agent_board.delegate_instances di on di.agent_id = bm.agent_id
|
|
147
|
+
where di.instance_id = 'iso_tr_instance_00001' and bm.membership_state in ('joined', 'subscribed')) as active_memberships;
|
|
148
|
+
}
|
|
149
|
+
step "check_bare" {
|
|
150
|
+
select (select count(*) from agent_board.delegate_instances where instance_id = 'iso_tr_bare_instance_01'
|
|
151
|
+
and tombstoned_at is not null and owner_subject = 'iso-tr-bare') as tombstoned_recorded,
|
|
152
|
+
(select count(*) from agent_board.fleet_human_revocations
|
|
153
|
+
where issuer = 'https://iso-tr.test' and subject = 'iso-tr-bare') as markers;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
permutation "h_begin" "t_begin" "t_tomb" "r_begin" "r_human" "h_commit" "t_commit" "r_commit" "check"
|
|
157
|
+
permutation "h_begin" "t_begin" "t_tomb" "r_begin" "r_agent" "h_commit" "t_commit" "r_commit" "check"
|
|
158
|
+
permutation "t_begin" "t_tomb_bare" "r_begin" "r_human_bare" "t_commit" "r_commit" "check_bare"
|