@consiliency/agent-board-schema 1.57.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 +8 -0
- package/migrations/20261008000100_agent_board_hosted_delegates.sql +3152 -0
- package/migrations/20261009000100_agent_board_hosted_followups.sql +665 -0
- package/package.json +1 -1
- package/schema-manifest.json +21 -3
- package/src/tooling.test.mjs +9 -2
- package/tests/agent_board_atomic_fleet_onboarding.test.sql +13 -0
- package/tests/agent_board_consent_emission.test.sql +4 -2
- package/tests/agent_board_delegate_role_class.test.sql +736 -0
- package/tests/agent_board_federated_identity_injective_slug.test.sql +5 -2
- package/tests/agent_board_fedsession_stored_identity_gate.test.sql +71 -14
- package/tests/agent_board_fk_lock_edges.test.sql +9 -0
- package/tests/agent_board_hosted_delegates.test.sql +1278 -0
- package/tests/agent_board_ledger_authority.test.sql +34 -5
- package/tests/agent_board_signer_lifecycle.test.sql +3 -0
- package/tests/agent_board_write_enforcement_class.test.sql +8 -3
- package/tests/catalog/function_execute_privileges.json +269 -2
- package/tests/catalog/p_metadata_parameters.json +44 -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_exchange_race.out +93 -0
- package/tests/isolation/expected/hosted_delegate_first_exchange_race.out +129 -0
- package/tests/isolation/expected/hosted_delegate_owner_provisioning_race.out +91 -0
- package/tests/isolation/expected/hosted_delegate_owner_revoke_race.out +137 -0
- package/tests/isolation/expected/hosted_delegate_revoke_race.out +87 -0
- package/tests/isolation/expected/hosted_delegate_tombstone_revoke_race.out +170 -0
- package/tests/isolation/expected/hosted_delegate_withdraw_evict_race.out +235 -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_exchange_race.spec +64 -0
- package/tests/isolation/specs/hosted_delegate_first_exchange_race.spec +72 -0
- package/tests/isolation/specs/hosted_delegate_owner_provisioning_race.spec +73 -0
- package/tests/isolation/specs/hosted_delegate_owner_revoke_race.spec +93 -0
- package/tests/isolation/specs/hosted_delegate_revoke_race.spec +59 -0
- package/tests/isolation/specs/hosted_delegate_tombstone_revoke_race.spec +158 -0
- package/tests/isolation/specs/hosted_delegate_withdraw_evict_race.spec +145 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
Parsed test spec with 4 sessions
|
|
2
|
+
|
|
3
|
+
starting permutation: h_begin t_begin t_tomb r_begin r_human h_commit t_commit r_commit check
|
|
4
|
+
bare_owner
|
|
5
|
+
----------
|
|
6
|
+
t
|
|
7
|
+
(1 row)
|
|
8
|
+
|
|
9
|
+
step h_begin:
|
|
10
|
+
BEGIN;
|
|
11
|
+
select (bm.id is not null) as held from agent_board.board_memberships bm
|
|
12
|
+
join agent_board.delegate_instances di on di.agent_id = bm.agent_id
|
|
13
|
+
where di.instance_id = 'iso_tr_instance_00001' and bm.membership_state = 'joined'
|
|
14
|
+
for update of bm;
|
|
15
|
+
|
|
16
|
+
held
|
|
17
|
+
----
|
|
18
|
+
t
|
|
19
|
+
(1 row)
|
|
20
|
+
|
|
21
|
+
step t_begin: BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role';
|
|
22
|
+
step t_tomb:
|
|
23
|
+
select first from agent_board.tombstone_delegate_instance(
|
|
24
|
+
'iso_tr_instance_00001', 'https://iso-tr.test', 'iso-tr-owner', 'owner_revoked');
|
|
25
|
+
<waiting ...>
|
|
26
|
+
step r_begin: BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role';
|
|
27
|
+
step r_human:
|
|
28
|
+
select revoked_agent_count from agent_board.revoke_human_subtree(
|
|
29
|
+
(select auth_user_id from agent_board.agents
|
|
30
|
+
where handle = agent_board._federated_identity_slug('https://iso-tr.test', 'iso-tr-owner')),
|
|
31
|
+
'16591000-0000-4000-8000-000000000001', 'iso tombstone revoke race');
|
|
32
|
+
<waiting ...>
|
|
33
|
+
step h_commit: COMMIT;
|
|
34
|
+
step t_tomb: <... completed>
|
|
35
|
+
first
|
|
36
|
+
-----
|
|
37
|
+
t
|
|
38
|
+
(1 row)
|
|
39
|
+
|
|
40
|
+
step t_commit: COMMIT;
|
|
41
|
+
step r_human: <... completed>
|
|
42
|
+
revoked_agent_count
|
|
43
|
+
-------------------
|
|
44
|
+
2
|
|
45
|
+
(1 row)
|
|
46
|
+
|
|
47
|
+
step r_commit: COMMIT;
|
|
48
|
+
step check:
|
|
49
|
+
select (select (di.tombstoned_at is not null)::text from agent_board.delegate_instances di
|
|
50
|
+
where di.instance_id = 'iso_tr_instance_00001') as tombstoned,
|
|
51
|
+
(select a.status from agent_board.agents a
|
|
52
|
+
join agent_board.delegate_instances di on di.agent_id = a.id
|
|
53
|
+
where di.instance_id = 'iso_tr_instance_00001') as delegate_status,
|
|
54
|
+
(select count(*) from agent_board.agent_credentials ac
|
|
55
|
+
join agent_board.delegate_instances di on di.agent_id = ac.agent_id
|
|
56
|
+
where di.instance_id = 'iso_tr_instance_00001' and ac.revoked_at is null) as live_credentials,
|
|
57
|
+
(select count(*) from agent_board.board_memberships bm
|
|
58
|
+
join agent_board.delegate_instances di on di.agent_id = bm.agent_id
|
|
59
|
+
where di.instance_id = 'iso_tr_instance_00001' and bm.membership_state in ('joined', 'subscribed')) as active_memberships;
|
|
60
|
+
|
|
61
|
+
tombstoned|delegate_status|live_credentials|active_memberships
|
|
62
|
+
----------+---------------+----------------+------------------
|
|
63
|
+
true |revoked | 0| 0
|
|
64
|
+
(1 row)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
starting permutation: h_begin t_begin t_tomb r_begin r_agent h_commit t_commit r_commit check
|
|
68
|
+
bare_owner
|
|
69
|
+
----------
|
|
70
|
+
t
|
|
71
|
+
(1 row)
|
|
72
|
+
|
|
73
|
+
step h_begin:
|
|
74
|
+
BEGIN;
|
|
75
|
+
select (bm.id is not null) as held from agent_board.board_memberships bm
|
|
76
|
+
join agent_board.delegate_instances di on di.agent_id = bm.agent_id
|
|
77
|
+
where di.instance_id = 'iso_tr_instance_00001' and bm.membership_state = 'joined'
|
|
78
|
+
for update of bm;
|
|
79
|
+
|
|
80
|
+
held
|
|
81
|
+
----
|
|
82
|
+
t
|
|
83
|
+
(1 row)
|
|
84
|
+
|
|
85
|
+
step t_begin: BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role';
|
|
86
|
+
step t_tomb:
|
|
87
|
+
select first from agent_board.tombstone_delegate_instance(
|
|
88
|
+
'iso_tr_instance_00001', 'https://iso-tr.test', 'iso-tr-owner', 'owner_revoked');
|
|
89
|
+
<waiting ...>
|
|
90
|
+
step r_begin: BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role';
|
|
91
|
+
step r_agent:
|
|
92
|
+
select revoked_agent_count from agent_board.revoke_agent_subtree(
|
|
93
|
+
(select agent_id from agent_board.delegate_instances where instance_id = 'iso_tr_instance_00001'),
|
|
94
|
+
'16591000-0000-4000-8000-000000000001', 'iso tombstone revoke race');
|
|
95
|
+
<waiting ...>
|
|
96
|
+
step h_commit: COMMIT;
|
|
97
|
+
step t_tomb: <... completed>
|
|
98
|
+
first
|
|
99
|
+
-----
|
|
100
|
+
t
|
|
101
|
+
(1 row)
|
|
102
|
+
|
|
103
|
+
step t_commit: COMMIT;
|
|
104
|
+
step r_agent: <... completed>
|
|
105
|
+
revoked_agent_count
|
|
106
|
+
-------------------
|
|
107
|
+
1
|
|
108
|
+
(1 row)
|
|
109
|
+
|
|
110
|
+
step r_commit: COMMIT;
|
|
111
|
+
step check:
|
|
112
|
+
select (select (di.tombstoned_at is not null)::text from agent_board.delegate_instances di
|
|
113
|
+
where di.instance_id = 'iso_tr_instance_00001') as tombstoned,
|
|
114
|
+
(select a.status from agent_board.agents a
|
|
115
|
+
join agent_board.delegate_instances di on di.agent_id = a.id
|
|
116
|
+
where di.instance_id = 'iso_tr_instance_00001') as delegate_status,
|
|
117
|
+
(select count(*) from agent_board.agent_credentials ac
|
|
118
|
+
join agent_board.delegate_instances di on di.agent_id = ac.agent_id
|
|
119
|
+
where di.instance_id = 'iso_tr_instance_00001' and ac.revoked_at is null) as live_credentials,
|
|
120
|
+
(select count(*) from agent_board.board_memberships bm
|
|
121
|
+
join agent_board.delegate_instances di on di.agent_id = bm.agent_id
|
|
122
|
+
where di.instance_id = 'iso_tr_instance_00001' and bm.membership_state in ('joined', 'subscribed')) as active_memberships;
|
|
123
|
+
|
|
124
|
+
tombstoned|delegate_status|live_credentials|active_memberships
|
|
125
|
+
----------+---------------+----------------+------------------
|
|
126
|
+
true |revoked | 0| 0
|
|
127
|
+
(1 row)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
starting permutation: t_begin t_tomb_bare r_begin r_human_bare t_commit r_commit check_bare
|
|
131
|
+
bare_owner
|
|
132
|
+
----------
|
|
133
|
+
t
|
|
134
|
+
(1 row)
|
|
135
|
+
|
|
136
|
+
step t_begin: BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role';
|
|
137
|
+
step t_tomb_bare:
|
|
138
|
+
select first from agent_board.tombstone_delegate_instance(
|
|
139
|
+
'iso_tr_bare_instance_01', 'https://iso-tr.test', 'iso-tr-bare', 'owner_revoked');
|
|
140
|
+
|
|
141
|
+
first
|
|
142
|
+
-----
|
|
143
|
+
t
|
|
144
|
+
(1 row)
|
|
145
|
+
|
|
146
|
+
step r_begin: BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role';
|
|
147
|
+
step r_human_bare:
|
|
148
|
+
select revoked_agent_count from agent_board.revoke_human_subtree(
|
|
149
|
+
(select id from auth.users
|
|
150
|
+
where email = 'agent+' || agent_board._federated_identity_slug('https://iso-tr.test', 'iso-tr-bare') || '@agent-board.local'),
|
|
151
|
+
'16591000-0000-4000-8000-000000000001', 'iso tombstone revoke race');
|
|
152
|
+
|
|
153
|
+
revoked_agent_count
|
|
154
|
+
-------------------
|
|
155
|
+
0
|
|
156
|
+
(1 row)
|
|
157
|
+
|
|
158
|
+
step t_commit: COMMIT;
|
|
159
|
+
step r_commit: COMMIT;
|
|
160
|
+
step check_bare:
|
|
161
|
+
select (select count(*) from agent_board.delegate_instances where instance_id = 'iso_tr_bare_instance_01'
|
|
162
|
+
and tombstoned_at is not null and owner_subject = 'iso-tr-bare') as tombstoned_recorded,
|
|
163
|
+
(select count(*) from agent_board.fleet_human_revocations
|
|
164
|
+
where issuer = 'https://iso-tr.test' and subject = 'iso-tr-bare') as markers;
|
|
165
|
+
|
|
166
|
+
tombstoned_recorded|markers
|
|
167
|
+
-------------------+-------
|
|
168
|
+
1| 0
|
|
169
|
+
(1 row)
|
|
170
|
+
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
Parsed test spec with 4 sessions
|
|
2
|
+
|
|
3
|
+
starting permutation: a_begin a_evict o_begin o_withdraw a_commit o_commit check o_readd check
|
|
4
|
+
step a_begin:
|
|
5
|
+
BEGIN;
|
|
6
|
+
select set_config('request.jwt.claims',
|
|
7
|
+
json_build_object('role', 'authenticated', 'sub', '16320000-0000-4000-8000-000000000001')::text, true) is not null as admin_session;
|
|
8
|
+
|
|
9
|
+
admin_session
|
|
10
|
+
-------------
|
|
11
|
+
t
|
|
12
|
+
(1 row)
|
|
13
|
+
|
|
14
|
+
step a_evict:
|
|
15
|
+
select membership_state from agent_board.update_board_membership(
|
|
16
|
+
'16322000-0000-4000-8000-000000000001',
|
|
17
|
+
(select agent_id from agent_board.delegate_instances where instance_id = 'iso_wd_instance_00001'),
|
|
18
|
+
'removed');
|
|
19
|
+
|
|
20
|
+
membership_state
|
|
21
|
+
----------------
|
|
22
|
+
removed
|
|
23
|
+
(1 row)
|
|
24
|
+
|
|
25
|
+
step o_begin:
|
|
26
|
+
BEGIN;
|
|
27
|
+
select set_config('request.jwt.claims', json_build_object('role', 'authenticated', 'sub',
|
|
28
|
+
(select auth_user_id from agent_board.agents
|
|
29
|
+
where handle = agent_board._federated_identity_slug('https://iso-wd.test', 'iso-wd-owner')))::text, true) is not null as owner_session;
|
|
30
|
+
|
|
31
|
+
owner_session
|
|
32
|
+
-------------
|
|
33
|
+
t
|
|
34
|
+
(1 row)
|
|
35
|
+
|
|
36
|
+
step o_withdraw:
|
|
37
|
+
select membership_state from agent_board.update_board_membership(
|
|
38
|
+
'16322000-0000-4000-8000-000000000001',
|
|
39
|
+
(select agent_id from agent_board.delegate_instances where instance_id = 'iso_wd_instance_00001'),
|
|
40
|
+
'left');
|
|
41
|
+
<waiting ...>
|
|
42
|
+
step a_commit: COMMIT;
|
|
43
|
+
step o_withdraw: <... completed>
|
|
44
|
+
ERROR: a principal may withdraw its delegate only from an active or pending membership
|
|
45
|
+
step o_commit: COMMIT;
|
|
46
|
+
step check:
|
|
47
|
+
select (select bm.membership_state from agent_board.board_memberships bm
|
|
48
|
+
join agent_board.delegate_instances di on di.agent_id = bm.agent_id
|
|
49
|
+
where bm.board_id = '16322000-0000-4000-8000-000000000001' and di.instance_id = 'iso_wd_instance_00001'
|
|
50
|
+
order by bm.created_at desc, bm.id desc limit 1) as delegate_state,
|
|
51
|
+
(select bm.membership_state from agent_board.board_memberships bm
|
|
52
|
+
where bm.board_id = '16322000-0000-4000-8000-000000000001'
|
|
53
|
+
and bm.agent_id = '16321000-0000-4000-8000-000000000002'
|
|
54
|
+
order by bm.created_at desc, bm.id desc limit 1) as member_state;
|
|
55
|
+
|
|
56
|
+
delegate_state|member_state
|
|
57
|
+
--------------+------------
|
|
58
|
+
removed |joined
|
|
59
|
+
(1 row)
|
|
60
|
+
|
|
61
|
+
step o_readd:
|
|
62
|
+
select set_config('request.jwt.claims', json_build_object('role', 'authenticated', 'sub',
|
|
63
|
+
(select auth_user_id from agent_board.agents
|
|
64
|
+
where handle = agent_board._federated_identity_slug('https://iso-wd.test', 'iso-wd-owner')))::text, false) is not null as owner_session;
|
|
65
|
+
select membership_state from agent_board.add_delegate_to_board(
|
|
66
|
+
(select agent_id from agent_board.delegate_instances where instance_id = 'iso_wd_instance_00001'),
|
|
67
|
+
'16322000-0000-4000-8000-000000000001');
|
|
68
|
+
|
|
69
|
+
owner_session
|
|
70
|
+
-------------
|
|
71
|
+
t
|
|
72
|
+
(1 row)
|
|
73
|
+
|
|
74
|
+
ERROR: a removed delegate stays removed; a board admin must lift the removal
|
|
75
|
+
step check:
|
|
76
|
+
select (select bm.membership_state from agent_board.board_memberships bm
|
|
77
|
+
join agent_board.delegate_instances di on di.agent_id = bm.agent_id
|
|
78
|
+
where bm.board_id = '16322000-0000-4000-8000-000000000001' and di.instance_id = 'iso_wd_instance_00001'
|
|
79
|
+
order by bm.created_at desc, bm.id desc limit 1) as delegate_state,
|
|
80
|
+
(select bm.membership_state from agent_board.board_memberships bm
|
|
81
|
+
where bm.board_id = '16322000-0000-4000-8000-000000000001'
|
|
82
|
+
and bm.agent_id = '16321000-0000-4000-8000-000000000002'
|
|
83
|
+
order by bm.created_at desc, bm.id desc limit 1) as member_state;
|
|
84
|
+
|
|
85
|
+
delegate_state|member_state
|
|
86
|
+
--------------+------------
|
|
87
|
+
removed |joined
|
|
88
|
+
(1 row)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
starting permutation: o_begin o_withdraw a_begin a_evict o_commit a_commit check o_readd check
|
|
92
|
+
step o_begin:
|
|
93
|
+
BEGIN;
|
|
94
|
+
select set_config('request.jwt.claims', json_build_object('role', 'authenticated', 'sub',
|
|
95
|
+
(select auth_user_id from agent_board.agents
|
|
96
|
+
where handle = agent_board._federated_identity_slug('https://iso-wd.test', 'iso-wd-owner')))::text, true) is not null as owner_session;
|
|
97
|
+
|
|
98
|
+
owner_session
|
|
99
|
+
-------------
|
|
100
|
+
t
|
|
101
|
+
(1 row)
|
|
102
|
+
|
|
103
|
+
step o_withdraw:
|
|
104
|
+
select membership_state from agent_board.update_board_membership(
|
|
105
|
+
'16322000-0000-4000-8000-000000000001',
|
|
106
|
+
(select agent_id from agent_board.delegate_instances where instance_id = 'iso_wd_instance_00001'),
|
|
107
|
+
'left');
|
|
108
|
+
|
|
109
|
+
membership_state
|
|
110
|
+
----------------
|
|
111
|
+
left
|
|
112
|
+
(1 row)
|
|
113
|
+
|
|
114
|
+
step a_begin:
|
|
115
|
+
BEGIN;
|
|
116
|
+
select set_config('request.jwt.claims',
|
|
117
|
+
json_build_object('role', 'authenticated', 'sub', '16320000-0000-4000-8000-000000000001')::text, true) is not null as admin_session;
|
|
118
|
+
|
|
119
|
+
admin_session
|
|
120
|
+
-------------
|
|
121
|
+
t
|
|
122
|
+
(1 row)
|
|
123
|
+
|
|
124
|
+
step a_evict:
|
|
125
|
+
select membership_state from agent_board.update_board_membership(
|
|
126
|
+
'16322000-0000-4000-8000-000000000001',
|
|
127
|
+
(select agent_id from agent_board.delegate_instances where instance_id = 'iso_wd_instance_00001'),
|
|
128
|
+
'removed');
|
|
129
|
+
<waiting ...>
|
|
130
|
+
step o_commit: COMMIT;
|
|
131
|
+
step a_evict: <... completed>
|
|
132
|
+
membership_state
|
|
133
|
+
----------------
|
|
134
|
+
removed
|
|
135
|
+
(1 row)
|
|
136
|
+
|
|
137
|
+
step a_commit: COMMIT;
|
|
138
|
+
step check:
|
|
139
|
+
select (select bm.membership_state from agent_board.board_memberships bm
|
|
140
|
+
join agent_board.delegate_instances di on di.agent_id = bm.agent_id
|
|
141
|
+
where bm.board_id = '16322000-0000-4000-8000-000000000001' and di.instance_id = 'iso_wd_instance_00001'
|
|
142
|
+
order by bm.created_at desc, bm.id desc limit 1) as delegate_state,
|
|
143
|
+
(select bm.membership_state from agent_board.board_memberships bm
|
|
144
|
+
where bm.board_id = '16322000-0000-4000-8000-000000000001'
|
|
145
|
+
and bm.agent_id = '16321000-0000-4000-8000-000000000002'
|
|
146
|
+
order by bm.created_at desc, bm.id desc limit 1) as member_state;
|
|
147
|
+
|
|
148
|
+
delegate_state|member_state
|
|
149
|
+
--------------+------------
|
|
150
|
+
removed |joined
|
|
151
|
+
(1 row)
|
|
152
|
+
|
|
153
|
+
step o_readd:
|
|
154
|
+
select set_config('request.jwt.claims', json_build_object('role', 'authenticated', 'sub',
|
|
155
|
+
(select auth_user_id from agent_board.agents
|
|
156
|
+
where handle = agent_board._federated_identity_slug('https://iso-wd.test', 'iso-wd-owner')))::text, false) is not null as owner_session;
|
|
157
|
+
select membership_state from agent_board.add_delegate_to_board(
|
|
158
|
+
(select agent_id from agent_board.delegate_instances where instance_id = 'iso_wd_instance_00001'),
|
|
159
|
+
'16322000-0000-4000-8000-000000000001');
|
|
160
|
+
|
|
161
|
+
owner_session
|
|
162
|
+
-------------
|
|
163
|
+
t
|
|
164
|
+
(1 row)
|
|
165
|
+
|
|
166
|
+
ERROR: a removed delegate stays removed; a board admin must lift the removal
|
|
167
|
+
step check:
|
|
168
|
+
select (select bm.membership_state from agent_board.board_memberships bm
|
|
169
|
+
join agent_board.delegate_instances di on di.agent_id = bm.agent_id
|
|
170
|
+
where bm.board_id = '16322000-0000-4000-8000-000000000001' and di.instance_id = 'iso_wd_instance_00001'
|
|
171
|
+
order by bm.created_at desc, bm.id desc limit 1) as delegate_state,
|
|
172
|
+
(select bm.membership_state from agent_board.board_memberships bm
|
|
173
|
+
where bm.board_id = '16322000-0000-4000-8000-000000000001'
|
|
174
|
+
and bm.agent_id = '16321000-0000-4000-8000-000000000002'
|
|
175
|
+
order by bm.created_at desc, bm.id desc limit 1) as member_state;
|
|
176
|
+
|
|
177
|
+
delegate_state|member_state
|
|
178
|
+
--------------+------------
|
|
179
|
+
removed |joined
|
|
180
|
+
(1 row)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
starting permutation: a_begin a_evict_member m_begin m_leave a_commit m_commit check
|
|
184
|
+
step a_begin:
|
|
185
|
+
BEGIN;
|
|
186
|
+
select set_config('request.jwt.claims',
|
|
187
|
+
json_build_object('role', 'authenticated', 'sub', '16320000-0000-4000-8000-000000000001')::text, true) is not null as admin_session;
|
|
188
|
+
|
|
189
|
+
admin_session
|
|
190
|
+
-------------
|
|
191
|
+
t
|
|
192
|
+
(1 row)
|
|
193
|
+
|
|
194
|
+
step a_evict_member:
|
|
195
|
+
select membership_state from agent_board.update_board_membership(
|
|
196
|
+
'16322000-0000-4000-8000-000000000001', '16321000-0000-4000-8000-000000000002', 'removed');
|
|
197
|
+
|
|
198
|
+
membership_state
|
|
199
|
+
----------------
|
|
200
|
+
removed
|
|
201
|
+
(1 row)
|
|
202
|
+
|
|
203
|
+
step m_begin:
|
|
204
|
+
BEGIN;
|
|
205
|
+
select set_config('request.jwt.claims',
|
|
206
|
+
json_build_object('role', 'authenticated', 'sub', '16320000-0000-4000-8000-000000000002')::text, true) is not null as member_session;
|
|
207
|
+
|
|
208
|
+
member_session
|
|
209
|
+
--------------
|
|
210
|
+
t
|
|
211
|
+
(1 row)
|
|
212
|
+
|
|
213
|
+
step m_leave:
|
|
214
|
+
select membership_state from agent_board.update_board_membership(
|
|
215
|
+
'16322000-0000-4000-8000-000000000001', '16321000-0000-4000-8000-000000000002', 'left');
|
|
216
|
+
<waiting ...>
|
|
217
|
+
step a_commit: COMMIT;
|
|
218
|
+
step m_leave: <... completed>
|
|
219
|
+
ERROR: self membership change not permitted; use the join/approval path
|
|
220
|
+
step m_commit: COMMIT;
|
|
221
|
+
step check:
|
|
222
|
+
select (select bm.membership_state from agent_board.board_memberships bm
|
|
223
|
+
join agent_board.delegate_instances di on di.agent_id = bm.agent_id
|
|
224
|
+
where bm.board_id = '16322000-0000-4000-8000-000000000001' and di.instance_id = 'iso_wd_instance_00001'
|
|
225
|
+
order by bm.created_at desc, bm.id desc limit 1) as delegate_state,
|
|
226
|
+
(select bm.membership_state from agent_board.board_memberships bm
|
|
227
|
+
where bm.board_id = '16322000-0000-4000-8000-000000000001'
|
|
228
|
+
and bm.agent_id = '16321000-0000-4000-8000-000000000002'
|
|
229
|
+
order by bm.created_at desc, bm.id desc limit 1) as member_state;
|
|
230
|
+
|
|
231
|
+
delegate_state|member_state
|
|
232
|
+
--------------+------------
|
|
233
|
+
joined |removed
|
|
234
|
+
(1 row)
|
|
235
|
+
|
|
@@ -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,64 @@
|
|
|
1
|
+
# HOSTED (v11) SL-2, IF-0-HOSTED-7: two concurrent exchanges of ONE instance at different
|
|
2
|
+
# generations serialise on the instance row lock (`select ... for update`), in both orders.
|
|
3
|
+
#
|
|
4
|
+
# new first: the generation-3 exchange commits; the generation-2 exchange, blocked on the
|
|
5
|
+
# row, then sees r = 3 and is refused generation_superseded.
|
|
6
|
+
# old first: the generation-2 exchange commits; the generation-3 exchange, blocked, then
|
|
7
|
+
# sees r = 2, records 3 and revokes every lower-generation credential.
|
|
8
|
+
#
|
|
9
|
+
# Either way the recorded generation is 3 and the only live credential is generation 3.
|
|
10
|
+
# Without the row lock the generation-2 exchange does not wait for the read, decides against
|
|
11
|
+
# r = 1, and the trace and the final generation both change (the mutation receipt).
|
|
12
|
+
|
|
13
|
+
setup
|
|
14
|
+
{
|
|
15
|
+
select count(*) from agent_board.exchange_delegate_credential(jsonb_build_object(
|
|
16
|
+
'iss', 'https://iso-portal.test', 'sub', 'iso-owner', 'slot_resource', null,
|
|
17
|
+
'agent_board_delegate_instance', jsonb_build_object(
|
|
18
|
+
'instance_id', 'iso_exchange_race_0001', 'grant_id', 'iso_grant_0000000001', 'generation', 1,
|
|
19
|
+
'owner_sub', 'iso-owner', 'client_id', 'iso-client', 'status', 'active', 'client_change', null)));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
teardown
|
|
23
|
+
{
|
|
24
|
+
delete from agent_board.agent_credentials
|
|
25
|
+
where agent_id in (select agent_id from agent_board.delegate_instances where instance_id like 'iso\_%');
|
|
26
|
+
delete from agent_board.delegate_instances where instance_id like 'iso\_%';
|
|
27
|
+
delete from agent_board.agents where handle like 'delegate-%';
|
|
28
|
+
delete from auth.identities where provider_id like 'delegate-%' or provider_id like 'fed-human-%';
|
|
29
|
+
delete from auth.users where email like 'agent+delegate-%' or email like 'agent+fed-human-%';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
session "new"
|
|
33
|
+
step "new_begin" { BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role'; }
|
|
34
|
+
step "new_exchange" {
|
|
35
|
+
select count(*) as issued from agent_board.exchange_delegate_credential(jsonb_build_object(
|
|
36
|
+
'iss', 'https://iso-portal.test', 'sub', 'iso-owner', 'slot_resource', null,
|
|
37
|
+
'agent_board_delegate_instance', jsonb_build_object(
|
|
38
|
+
'instance_id', 'iso_exchange_race_0001', 'grant_id', 'iso_grant_0000000003', 'generation', 3,
|
|
39
|
+
'owner_sub', 'iso-owner', 'client_id', 'iso-client', 'status', 'active', 'client_change', null)));
|
|
40
|
+
}
|
|
41
|
+
step "new_commit" { COMMIT; }
|
|
42
|
+
|
|
43
|
+
session "old"
|
|
44
|
+
step "old_begin" { BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role'; }
|
|
45
|
+
step "old_exchange" {
|
|
46
|
+
select count(*) as issued from agent_board.exchange_delegate_credential(jsonb_build_object(
|
|
47
|
+
'iss', 'https://iso-portal.test', 'sub', 'iso-owner', 'slot_resource', null,
|
|
48
|
+
'agent_board_delegate_instance', jsonb_build_object(
|
|
49
|
+
'instance_id', 'iso_exchange_race_0001', 'grant_id', 'iso_grant_0000000002', 'generation', 2,
|
|
50
|
+
'owner_sub', 'iso-owner', 'client_id', 'iso-client', 'status', 'active', 'client_change', null)));
|
|
51
|
+
}
|
|
52
|
+
step "old_commit" { COMMIT; }
|
|
53
|
+
|
|
54
|
+
session "check"
|
|
55
|
+
step "check" {
|
|
56
|
+
select di.generation as recorded_generation, di.grant_id as recorded_grant,
|
|
57
|
+
(select string_agg(ac.generation::text, ',' order by ac.generation)
|
|
58
|
+
from agent_board.agent_credentials ac where ac.agent_id = di.agent_id and ac.revoked_at is null) as live_generations,
|
|
59
|
+
(select count(*) from agent_board.agents a where a.handle like 'delegate-%') as delegate_agents
|
|
60
|
+
from agent_board.delegate_instances di where di.instance_id = 'iso_exchange_race_0001';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
permutation "new_begin" "new_exchange" "old_begin" "old_exchange" "new_commit" "old_commit" "check"
|
|
64
|
+
permutation "old_begin" "old_exchange" "new_begin" "new_exchange" "old_commit" "new_commit" "check"
|