@consiliency/agent-board-schema 1.59.0 → 1.63.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -0
- package/fixtures/already-applied.json +16 -0
- package/migrations/20261010000100_agent_board_own_post_not_delivered.sql +637 -0
- package/migrations/20261011000100_agent_board_session_identities.sql +1964 -0
- package/migrations/20261012000100_agent_board_sidowner.sql +209 -0
- package/migrations/20261014000100_agent_board_grant_target_role.sql +218 -0
- package/package.json +1 -1
- package/schema-manifest.json +39 -3
- package/src/tooling.test.mjs +9 -2
- package/tests/agent_board_addressing.test.sql +3 -1
- package/tests/agent_board_atomic_fleet_onboarding.test.sql +13 -4
- package/tests/agent_board_consent_emission.test.sql +30 -11
- package/tests/agent_board_consent_gating.test.sql +16 -2
- package/tests/agent_board_delegate_role_class.test.sql +1 -1
- package/tests/agent_board_delegation_cap_ceiling.test.sql +26 -15
- package/tests/agent_board_delegation_enforcement.test.sql +1 -1
- package/tests/agent_board_enroll_resumption.test.sql +22 -4
- package/tests/agent_board_enroll_token_single_use.test.sql +18 -3
- package/tests/agent_board_federated_lock_order_class.test.sql +2 -2
- package/tests/agent_board_fk_lock_edges.test.sql +74 -14
- package/tests/agent_board_grant_target_role.test.sql +1118 -0
- package/tests/agent_board_inbox_rpcs.test.sql +5 -2
- package/tests/agent_board_invite_no_downgrade.test.sql +10 -8
- package/tests/agent_board_ledger_authority.test.sql +70 -12
- package/tests/agent_board_membership_current_row_class.test.sql +9 -1
- package/tests/agent_board_mint_fleet_agent_session.test.sql +2 -2
- package/tests/agent_board_mint_harden.test.sql +38 -27
- package/tests/agent_board_own_post_not_delivered.test.sql +490 -0
- package/tests/agent_board_rolereset.test.sql +8 -2
- package/tests/agent_board_roomdeleg_lane_b.test.sql +6 -6
- package/tests/agent_board_rowid.test.sql +3 -1
- package/tests/agent_board_session_identity_approval_revocation.test.sql +334 -0
- package/tests/agent_board_session_identity_backfill.test.sql +82 -0
- package/tests/agent_board_session_identity_backfill_refusal.test.sql +14 -0
- package/tests/agent_board_session_identity_behavior_classes.test.sql +176 -0
- package/tests/agent_board_session_identity_class.test.sql +118 -0
- package/tests/agent_board_session_identity_fingerprint.test.sql +271 -0
- package/tests/agent_board_session_identity_labels_limits.test.sql +205 -0
- package/tests/agent_board_session_identity_lease.test.sql +235 -0
- package/tests/agent_board_session_identity_lifetime.test.sql +230 -0
- package/tests/agent_board_session_identity_limits.test.sql +208 -0
- package/tests/agent_board_session_identity_lineage.test.sql +64 -0
- package/tests/agent_board_session_identity_link.test.sql +201 -0
- package/tests/agent_board_session_identity_mint.test.sql +233 -0
- package/tests/agent_board_session_identity_panel_r1.test.sql +194 -0
- package/tests/agent_board_session_identity_panel_r2.test.sql +174 -0
- package/tests/agent_board_session_identity_protected_actions.test.sql +156 -0
- package/tests/agent_board_session_identity_revoke_closure.test.sql +131 -0
- package/tests/agent_board_sidowner_leases.test.sql +107 -0
- package/tests/agent_board_sidowner_owner_required.test.sql +97 -0
- package/tests/agent_board_sidowner_read_class.test.sql +171 -0
- package/tests/agent_board_sidowner_reads.test.sql +136 -0
- package/tests/agent_board_signer_lifecycle.test.sql +26 -4
- package/tests/agent_board_signing_challenge_pending_cap.test.sql +1 -1
- package/tests/agent_board_v2_invariants.test.sql +4 -4
- package/tests/agent_board_write_enforcement_class.test.sql +74 -15
- package/tests/catalog/function_execute_privileges.json +218 -2
- package/tests/catalog/p_metadata_parameters.json +98 -2
- package/tests/isolation/expected/grant_target_role_race.out +348 -0
- package/tests/isolation/expected/session_identity_limits.out +165 -0
- package/tests/isolation/expected/session_identity_links.out +235 -0
- package/tests/isolation/expected/session_identity_reclaim.out +253 -0
- package/tests/isolation/expected/session_identity_renew_revoke.out +115 -0
- package/tests/isolation/expected/session_identity_renew_revoke_key_gate.out +138 -0
- package/tests/isolation/expected/session_identity_tombstone_onboarding.out +247 -0
- package/tests/isolation/specs/grant_target_role_race.spec +207 -0
- package/tests/isolation/specs/session_identity_limits.spec +81 -0
- package/tests/isolation/specs/session_identity_links.spec +80 -0
- package/tests/isolation/specs/session_identity_reclaim.spec +125 -0
- package/tests/isolation/specs/session_identity_renew_revoke.spec +84 -0
- package/tests/isolation/specs/session_identity_renew_revoke_key_gate.spec +94 -0
- package/tests/isolation/specs/session_identity_tombstone_onboarding.spec +108 -0
- package/tests/seeds/20261011000100_agent_board_session_identities.sql.pre.sql +47 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# #674 (plans/plan-674-grant-target-role.md §5.4): grant_board_role reads the target's
|
|
2
|
+
# CURRENT role in ONE step-6 statement that locks the target's active row
|
|
3
|
+
# FOR NO KEY UPDATE OF t and LEFT JOINs the grantor's active row unlocked. Every session
|
|
4
|
+
# uses explicit BEGIN/COMMIT (or ROLLBACK); claims are transaction-local, so they are set
|
|
5
|
+
# in the begin step. Authenticated grants pass no p_grantor_agent_id (the host shape).
|
|
6
|
+
#
|
|
7
|
+
# Board B (created by O). Roles at setup:
|
|
8
|
+
# O owner, A1 admin, M member, V observer, G member.
|
|
9
|
+
# One admin-preset invite link on B, minted by O through create_invite_link (max_uses 5).
|
|
10
|
+
#
|
|
11
|
+
# I-1 M redeems the admin link (rank-max lifts M to admin, row held) while A1 grants
|
|
12
|
+
# M -> observer. A1 BLOCKS at step 6; after M commits, the locked read sees admin,
|
|
13
|
+
# and C-B refuses (D6b). M stays admin. (pins M7)
|
|
14
|
+
# I-2 O removes M (update_board_membership, held) while A1 grants M -> observer. A1
|
|
15
|
+
# blocks; after O commits, A1 gets P0002 (no all-null row).
|
|
16
|
+
# I-3 O demotes A1 to member (held) while A1 grants M -> observer. A1 blocks on the
|
|
17
|
+
# board advisory lock; after O commits, step 6 sees A1 as member and C-A refuses
|
|
18
|
+
# (D6a). Reverse order: both succeed.
|
|
19
|
+
# I-4 O posts on B (post_board_message fan-out inserts delivery_recipients naming M's
|
|
20
|
+
# membership: FOR KEY SHARE on M's row, held) while O's second session grants
|
|
21
|
+
# M -> observer. The grant completes WITHOUT blocking. (pins M7b)
|
|
22
|
+
# I-5 G redeems the admin link (G's own row raised to admin, held) while G's second
|
|
23
|
+
# session grants V -> member. It does NOT block (g is unlocked), reads G's committed
|
|
24
|
+
# role member, and C-A refuses (D6a). After G commits, a retry succeeds.
|
|
25
|
+
|
|
26
|
+
setup
|
|
27
|
+
{
|
|
28
|
+
insert into auth.users(id) values
|
|
29
|
+
('67410000-0000-4000-8000-000000000001'),
|
|
30
|
+
('67410000-0000-4000-8000-000000000002'),
|
|
31
|
+
('67410000-0000-4000-8000-000000000003'),
|
|
32
|
+
('67410000-0000-4000-8000-000000000004'),
|
|
33
|
+
('67410000-0000-4000-8000-000000000005');
|
|
34
|
+
insert into agent_board.agents(id, agent_type, handle, display_name, status, auth_user_id) values
|
|
35
|
+
('67420000-0000-4000-8000-000000000001', 'agent', 'g674-iso-o', 'G674 O', 'active', '67410000-0000-4000-8000-000000000001'),
|
|
36
|
+
('67420000-0000-4000-8000-000000000002', 'agent', 'g674-iso-a1', 'G674 A1', 'active', '67410000-0000-4000-8000-000000000002'),
|
|
37
|
+
('67420000-0000-4000-8000-000000000003', 'agent', 'g674-iso-m', 'G674 M', 'active', '67410000-0000-4000-8000-000000000003'),
|
|
38
|
+
('67420000-0000-4000-8000-000000000004', 'agent', 'g674-iso-v', 'G674 V', 'active', '67410000-0000-4000-8000-000000000004'),
|
|
39
|
+
('67420000-0000-4000-8000-000000000005', 'agent', 'g674-iso-g', 'G674 G', 'active', '67410000-0000-4000-8000-000000000005');
|
|
40
|
+
insert into agent_board.boards(id, slug, name, created_by_agent_id, visibility)
|
|
41
|
+
values ('67430000-0000-4000-8000-000000000001', 'g674-iso-board', 'G674 isolation board',
|
|
42
|
+
'67420000-0000-4000-8000-000000000001', 'private');
|
|
43
|
+
insert into agent_board.board_memberships(board_id, agent_id, membership_state, role, created_by_agent_id) values
|
|
44
|
+
('67430000-0000-4000-8000-000000000001', '67420000-0000-4000-8000-000000000001', 'joined', 'owner', '67420000-0000-4000-8000-000000000001'),
|
|
45
|
+
('67430000-0000-4000-8000-000000000001', '67420000-0000-4000-8000-000000000002', 'joined', 'admin', '67420000-0000-4000-8000-000000000001'),
|
|
46
|
+
('67430000-0000-4000-8000-000000000001', '67420000-0000-4000-8000-000000000003', 'joined', 'member', '67420000-0000-4000-8000-000000000001'),
|
|
47
|
+
('67430000-0000-4000-8000-000000000001', '67420000-0000-4000-8000-000000000004', 'joined', 'observer', '67420000-0000-4000-8000-000000000001'),
|
|
48
|
+
('67430000-0000-4000-8000-000000000001', '67420000-0000-4000-8000-000000000005', 'joined', 'member', '67420000-0000-4000-8000-000000000001');
|
|
49
|
+
insert into agent_board.threads(id, created_by_agent_id, subject, board_id)
|
|
50
|
+
values ('67440000-0000-4000-8000-000000000001', '67420000-0000-4000-8000-000000000001', 'g674 iso thread',
|
|
51
|
+
'67430000-0000-4000-8000-000000000001');
|
|
52
|
+
-- The admin-preset link, minted by O through the real RPC (service path, O supplied).
|
|
53
|
+
select count(*) from agent_board.create_invite_link('67430000-0000-4000-8000-000000000001', 'admin', 'joined', 5,
|
|
54
|
+
null, '67420000-0000-4000-8000-000000000001', jsonb_build_object());
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
teardown
|
|
58
|
+
{
|
|
59
|
+
delete from agent_board.board_invite_redemptions where invite_id in
|
|
60
|
+
(select id from agent_board.board_invite_tokens where board_id = '67430000-0000-4000-8000-000000000001');
|
|
61
|
+
delete from agent_board.board_invite_tokens where board_id = '67430000-0000-4000-8000-000000000001';
|
|
62
|
+
delete from agent_board.delivery_recipients where board_id = '67430000-0000-4000-8000-000000000001';
|
|
63
|
+
delete from agent_board.inbox_items where board_id = '67430000-0000-4000-8000-000000000001';
|
|
64
|
+
delete from agent_board.callback_outbox_events where board_id = '67430000-0000-4000-8000-000000000001';
|
|
65
|
+
delete from agent_board.messages where thread_id = '67440000-0000-4000-8000-000000000001';
|
|
66
|
+
delete from agent_board.threads where id = '67440000-0000-4000-8000-000000000001';
|
|
67
|
+
delete from agent_board.board_membership_events where board_id = '67430000-0000-4000-8000-000000000001';
|
|
68
|
+
delete from agent_board.board_memberships where board_id = '67430000-0000-4000-8000-000000000001';
|
|
69
|
+
delete from agent_board.boards where id = '67430000-0000-4000-8000-000000000001';
|
|
70
|
+
delete from agent_board.agents where id::text like '67420000-0000-4000-8000-00000000000_';
|
|
71
|
+
delete from auth.users where id::text like '67410000-0000-4000-8000-00000000000_';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# M, redeeming the admin link (I-1).
|
|
75
|
+
session "m"
|
|
76
|
+
step "m_begin" {
|
|
77
|
+
BEGIN;
|
|
78
|
+
select set_config('g674.code', (select code from agent_board.board_invite_tokens
|
|
79
|
+
where board_id = '67430000-0000-4000-8000-000000000001' and preset_role = 'admin'), true) is not null as have_code;
|
|
80
|
+
SET LOCAL ROLE authenticated;
|
|
81
|
+
SET LOCAL "request.jwt.claim.role" = 'authenticated';
|
|
82
|
+
SET LOCAL "request.jwt.claim.sub" = '67410000-0000-4000-8000-000000000003';
|
|
83
|
+
}
|
|
84
|
+
step "m_redeem" { select role, membership_state from agent_board.redeem_invite_link(current_setting('g674.code')); }
|
|
85
|
+
step "m_commit" { COMMIT; }
|
|
86
|
+
|
|
87
|
+
# A1, the admin grantor (I-1, I-2, I-3).
|
|
88
|
+
session "a"
|
|
89
|
+
step "a_begin" {
|
|
90
|
+
BEGIN;
|
|
91
|
+
SET LOCAL ROLE authenticated;
|
|
92
|
+
SET LOCAL "request.jwt.claim.role" = 'authenticated';
|
|
93
|
+
SET LOCAL "request.jwt.claim.sub" = '67410000-0000-4000-8000-000000000002';
|
|
94
|
+
}
|
|
95
|
+
step "a_grant_m_observer" {
|
|
96
|
+
select role from agent_board.grant_board_role('67430000-0000-4000-8000-000000000001',
|
|
97
|
+
'67420000-0000-4000-8000-000000000003', 'observer');
|
|
98
|
+
}
|
|
99
|
+
step "a_commit" { COMMIT; }
|
|
100
|
+
step "a_rollback" { ROLLBACK; }
|
|
101
|
+
|
|
102
|
+
# O, the owner (I-2 eviction, I-3 demotion of A1, I-4 grantor).
|
|
103
|
+
session "o"
|
|
104
|
+
step "o_begin" {
|
|
105
|
+
BEGIN;
|
|
106
|
+
SET LOCAL ROLE authenticated;
|
|
107
|
+
SET LOCAL "request.jwt.claim.role" = 'authenticated';
|
|
108
|
+
SET LOCAL "request.jwt.claim.sub" = '67410000-0000-4000-8000-000000000001';
|
|
109
|
+
}
|
|
110
|
+
step "o_remove_m" {
|
|
111
|
+
select membership_state from agent_board.update_board_membership('67430000-0000-4000-8000-000000000001',
|
|
112
|
+
'67420000-0000-4000-8000-000000000003', 'removed');
|
|
113
|
+
}
|
|
114
|
+
step "o_demote_a" {
|
|
115
|
+
select role from agent_board.grant_board_role('67430000-0000-4000-8000-000000000001',
|
|
116
|
+
'67420000-0000-4000-8000-000000000002', 'member');
|
|
117
|
+
}
|
|
118
|
+
step "o_grant_m_observer" {
|
|
119
|
+
select role from agent_board.grant_board_role('67430000-0000-4000-8000-000000000001',
|
|
120
|
+
'67420000-0000-4000-8000-000000000003', 'observer');
|
|
121
|
+
}
|
|
122
|
+
step "o_commit" { COMMIT; }
|
|
123
|
+
|
|
124
|
+
# O's second session, posting on B (I-4): the fan-out FK-inserts delivery_recipients rows
|
|
125
|
+
# naming each recipient's membership, which takes FOR KEY SHARE on M's row.
|
|
126
|
+
session "p"
|
|
127
|
+
step "p_begin" {
|
|
128
|
+
BEGIN;
|
|
129
|
+
SET LOCAL ROLE authenticated;
|
|
130
|
+
SET LOCAL "request.jwt.claim.role" = 'authenticated';
|
|
131
|
+
SET LOCAL "request.jwt.claim.sub" = '67410000-0000-4000-8000-000000000001';
|
|
132
|
+
}
|
|
133
|
+
step "p_post" {
|
|
134
|
+
select count(*) > 0 as posted from agent_board.post_board_message('67440000-0000-4000-8000-000000000001',
|
|
135
|
+
'67420000-0000-4000-8000-000000000001', '67430000-0000-4000-8000-000000000001', 'g674 iso', 'fan-out to M');
|
|
136
|
+
}
|
|
137
|
+
# RESET ROLE (still inside the open transaction, which keeps every lock it took) so this
|
|
138
|
+
# count reads M's uncommitted delivery row past the recipient-scoped RLS O is under.
|
|
139
|
+
step "p_holds_m_fk" {
|
|
140
|
+
RESET ROLE;
|
|
141
|
+
select count(*) as m_delivery_rows from agent_board.delivery_recipients dr
|
|
142
|
+
join agent_board.board_memberships bm on bm.id = dr.board_membership_id
|
|
143
|
+
where bm.board_id = '67430000-0000-4000-8000-000000000001' and bm.agent_id = '67420000-0000-4000-8000-000000000003';
|
|
144
|
+
}
|
|
145
|
+
step "p_commit" { COMMIT; }
|
|
146
|
+
|
|
147
|
+
# G, redeeming the admin link for its OWN row (I-5).
|
|
148
|
+
session "g"
|
|
149
|
+
step "g_begin" {
|
|
150
|
+
BEGIN;
|
|
151
|
+
select set_config('g674.code', (select code from agent_board.board_invite_tokens
|
|
152
|
+
where board_id = '67430000-0000-4000-8000-000000000001' and preset_role = 'admin'), true) is not null as have_code;
|
|
153
|
+
SET LOCAL ROLE authenticated;
|
|
154
|
+
SET LOCAL "request.jwt.claim.role" = 'authenticated';
|
|
155
|
+
SET LOCAL "request.jwt.claim.sub" = '67410000-0000-4000-8000-000000000005';
|
|
156
|
+
}
|
|
157
|
+
step "g_redeem" { select role, membership_state from agent_board.redeem_invite_link(current_setting('g674.code')); }
|
|
158
|
+
step "g_commit" { COMMIT; }
|
|
159
|
+
|
|
160
|
+
# G's second session, the grantor in I-5.
|
|
161
|
+
session "g2"
|
|
162
|
+
step "g2_begin" {
|
|
163
|
+
BEGIN;
|
|
164
|
+
SET LOCAL ROLE authenticated;
|
|
165
|
+
SET LOCAL "request.jwt.claim.role" = 'authenticated';
|
|
166
|
+
SET LOCAL "request.jwt.claim.sub" = '67410000-0000-4000-8000-000000000005';
|
|
167
|
+
}
|
|
168
|
+
step "g2_grant_v_member" {
|
|
169
|
+
select role from agent_board.grant_board_role('67430000-0000-4000-8000-000000000001',
|
|
170
|
+
'67420000-0000-4000-8000-000000000004', 'member');
|
|
171
|
+
}
|
|
172
|
+
step "g2_rollback" { ROLLBACK; }
|
|
173
|
+
step "g2_retry_begin" {
|
|
174
|
+
BEGIN;
|
|
175
|
+
SET LOCAL ROLE authenticated;
|
|
176
|
+
SET LOCAL "request.jwt.claim.role" = 'authenticated';
|
|
177
|
+
SET LOCAL "request.jwt.claim.sub" = '67410000-0000-4000-8000-000000000005';
|
|
178
|
+
}
|
|
179
|
+
step "g2_retry_grant_v_member" {
|
|
180
|
+
select role from agent_board.grant_board_role('67430000-0000-4000-8000-000000000001',
|
|
181
|
+
'67420000-0000-4000-8000-000000000004', 'member');
|
|
182
|
+
}
|
|
183
|
+
step "g2_commit" { COMMIT; }
|
|
184
|
+
|
|
185
|
+
session "check"
|
|
186
|
+
step "check" {
|
|
187
|
+
select a.handle, bm.role, bm.membership_state
|
|
188
|
+
from agent_board.board_memberships bm join agent_board.agents a on a.id = bm.agent_id
|
|
189
|
+
where bm.board_id = '67430000-0000-4000-8000-000000000001'
|
|
190
|
+
order by a.handle, bm.membership_state;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
# I-1: the target is promoted through the product while it is being demoted.
|
|
194
|
+
permutation "m_begin" "m_redeem" "a_begin" "a_grant_m_observer" "m_commit" "a_rollback" "check"
|
|
195
|
+
# I-2: eviction during a grant.
|
|
196
|
+
permutation "o_begin" "o_remove_m" "a_begin" "a_grant_m_observer" "o_commit" "a_rollback" "check"
|
|
197
|
+
# I-3: two grants on one board, advisory-serialised. Demotion first: the grantor's role is
|
|
198
|
+
# read AFTER the advisory wait (C-A refuses). Grant first: both succeed.
|
|
199
|
+
permutation "o_begin" "o_demote_a" "a_begin" "a_grant_m_observer" "o_commit" "a_rollback" "check"
|
|
200
|
+
permutation "a_begin" "a_grant_m_observer" "o_begin" "o_demote_a" "a_commit" "o_commit" "check"
|
|
201
|
+
# I-4: FK-share non-interference -- the grant completes without blocking. The poster commits
|
|
202
|
+
# BEFORE the grantor so that a grant which DID block (M7b, FOR UPDATE) is released and the
|
|
203
|
+
# golden diff shows `<waiting ...>`/`<... completed>` instead of a truncated run.
|
|
204
|
+
permutation "p_begin" "p_post" "p_holds_m_fk" "o_begin" "o_grant_m_observer" "p_commit" "o_commit" "check"
|
|
205
|
+
# I-5: a concurrent raise of the grantor is seen as the older, lower rank (refusal), never
|
|
206
|
+
# a wrongful allow; the retry after commit succeeds.
|
|
207
|
+
permutation "g_begin" "g_redeem" "g2_begin" "g2_grant_v_member" "g2_rollback" "g_commit" "g2_retry_begin" "g2_retry_grant_v_member" "g2_commit" "check"
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Concurrent mints from a 59-row mixed rolling-hour census share the true root
|
|
2
|
+
# lock. Expired legacy grants and revoked session mints still consume the hour.
|
|
3
|
+
setup
|
|
4
|
+
{
|
|
5
|
+
grant usage on schema extensions to service_role;
|
|
6
|
+
insert into auth.users(id) values ('77000000-0000-4000-8000-000000000001');
|
|
7
|
+
insert into agent_board.agents(id, agent_type, handle, display_name, status, owner_user_id)
|
|
8
|
+
values ('78000000-0000-4000-8000-000000000001', 'agent', 'sid-limits-root', 'SID limits root', 'active',
|
|
9
|
+
'77000000-0000-4000-8000-000000000001');
|
|
10
|
+
insert into agent_board.session_identities(agent_id, root_agent_id, depth)
|
|
11
|
+
values ('78000000-0000-4000-8000-000000000001', '78000000-0000-4000-8000-000000000001', 0);
|
|
12
|
+
insert into agent_board.delegation_policies(id, root_agent_id, mode, max_active_children, allowed_scopes, child_ttl_ceiling)
|
|
13
|
+
values ('79000000-0000-4000-8000-000000000001', '78000000-0000-4000-8000-000000000001', 'root_minted', 200, '[]', interval '8 hours');
|
|
14
|
+
do $$ declare n integer; child uuid; begin
|
|
15
|
+
for n in 1..59 loop
|
|
16
|
+
child := gen_random_uuid();
|
|
17
|
+
insert into agent_board.agents(id, agent_type, handle, display_name, status, owner_user_id, parent_agent_id, root_agent_id)
|
|
18
|
+
values (child, 'agent', 'sid-limits-history-' || n, 'SID limits history', case when n <= 30 then 'active' else 'revoked' end,
|
|
19
|
+
'77000000-0000-4000-8000-000000000001', '78000000-0000-4000-8000-000000000001', '78000000-0000-4000-8000-000000000001');
|
|
20
|
+
insert into agent_board.session_identities(agent_id, parent_agent_id, root_agent_id, depth, identity_kind, harness, tombstoned_at, tombstone_reason)
|
|
21
|
+
values (child, '78000000-0000-4000-8000-000000000001', '78000000-0000-4000-8000-000000000001', 1,
|
|
22
|
+
case when n <= 30 then 'legacy' else 'session' end, case when n <= 30 then null else 'codex' end,
|
|
23
|
+
case when n <= 30 then null else now() end, case when n <= 30 then null else 'revoked' end);
|
|
24
|
+
insert into agent_board.delegation_grants(child_agent_id, root_agent_id, parent_agent_id, delegation_policy_id, granted_scopes, grant_expires_at, revoked_at)
|
|
25
|
+
values (child, '78000000-0000-4000-8000-000000000001', '78000000-0000-4000-8000-000000000001',
|
|
26
|
+
'79000000-0000-4000-8000-000000000001', '[]', now() - interval '1 hour', case when n <= 30 then null else now() end);
|
|
27
|
+
end loop;
|
|
28
|
+
end $$;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
teardown
|
|
32
|
+
{
|
|
33
|
+
do $$ declare v_auth_ids uuid[]; begin
|
|
34
|
+
select array_agg(auth_user_id) into v_auth_ids from agent_board.agents
|
|
35
|
+
where root_agent_id = '78000000-0000-4000-8000-000000000001' and auth_user_id is not null;
|
|
36
|
+
delete from agent_board.session_id_aliases where root_agent_id = '78000000-0000-4000-8000-000000000001';
|
|
37
|
+
delete from agent_board.session_identities where root_agent_id = '78000000-0000-4000-8000-000000000001';
|
|
38
|
+
delete from agent_board.agents where root_agent_id = '78000000-0000-4000-8000-000000000001';
|
|
39
|
+
delete from agent_board.agents where id = '78000000-0000-4000-8000-000000000001';
|
|
40
|
+
delete from auth.users where id = any(v_auth_ids) or id = '77000000-0000-4000-8000-000000000001';
|
|
41
|
+
end $$;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
session "a"
|
|
45
|
+
step "a_begin" { BEGIN; SET LOCAL ROLE service_role; SET LOCAL "request.jwt.claim.role" = 'service_role'; }
|
|
46
|
+
step "a_mint" {
|
|
47
|
+
do $$ begin
|
|
48
|
+
perform * from agent_board.mint_delegated_child('78000000-0000-4000-8000-000000000001', 'sid-limit-a', '[]',
|
|
49
|
+
public.test_pubkey('sid-limit-a'), interval '1 hour', false, 'codex', 'limit-a');
|
|
50
|
+
end $$;
|
|
51
|
+
}
|
|
52
|
+
step "a_commit" { COMMIT; }
|
|
53
|
+
|
|
54
|
+
session "b"
|
|
55
|
+
step "b_begin" { BEGIN; SET LOCAL ROLE service_role; SET LOCAL "request.jwt.claim.role" = 'service_role'; }
|
|
56
|
+
step "b_mint" {
|
|
57
|
+
do $$ begin
|
|
58
|
+
perform * from agent_board.mint_delegated_child('78000000-0000-4000-8000-000000000001', 'sid-limit-b', '[]',
|
|
59
|
+
public.test_pubkey('sid-limit-b'), interval '1 hour', false, 'codex', 'limit-b');
|
|
60
|
+
end $$;
|
|
61
|
+
}
|
|
62
|
+
step "b_duplicate" {
|
|
63
|
+
do $$ begin
|
|
64
|
+
perform * from agent_board.mint_delegated_child('78000000-0000-4000-8000-000000000001', 'sid-limit-b', '[]',
|
|
65
|
+
public.test_pubkey('sid-limit-b'), interval '1 hour', false, 'codex', 'limit-a');
|
|
66
|
+
end $$;
|
|
67
|
+
}
|
|
68
|
+
step "b_commit" { COMMIT; }
|
|
69
|
+
|
|
70
|
+
session "check"
|
|
71
|
+
step "check" {
|
|
72
|
+
select count(*) as mixed_hourly, count(*) filter (where revoked_at is not null) as tombstoned_hourly
|
|
73
|
+
from agent_board.delegation_grants where root_agent_id = '78000000-0000-4000-8000-000000000001';
|
|
74
|
+
select count(*) as new_principals from agent_board.agents where handle in ('sid-limit-a', 'sid-limit-b');
|
|
75
|
+
select count(*) as aliases from agent_board.session_id_aliases where root_agent_id = '78000000-0000-4000-8000-000000000001';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
permutation "a_begin" "a_mint" "b_begin" "b_mint" "a_commit" "b_commit" "check"
|
|
79
|
+
permutation "b_begin" "b_mint" "a_begin" "a_mint" "b_commit" "a_commit" "check"
|
|
80
|
+
permutation "a_begin" "a_mint" "b_begin" "b_duplicate" "a_commit" "b_commit" "check"
|
|
81
|
+
permutation "b_begin" "b_duplicate" "a_begin" "a_mint" "b_commit" "a_commit" "check"
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Two verified service-role links serialize on one live Claude identity. Both
|
|
2
|
+
# commit orders and idempotent/different aliases are observable without sleeps.
|
|
3
|
+
setup
|
|
4
|
+
{
|
|
5
|
+
insert into auth.users(id) values ('71000000-0000-4000-8000-000000000001');
|
|
6
|
+
insert into agent_board.agents(id, agent_type, handle, display_name, status, auth_user_id, parent_agent_id, root_agent_id)
|
|
7
|
+
values
|
|
8
|
+
('72000000-0000-4000-8000-000000000001', 'agent', 'sid-links-root', 'SID links root', 'active', null, null, null),
|
|
9
|
+
('72000000-0000-4000-8000-000000000002', 'agent', 'sid-links-child', 'SID links child', 'active', '71000000-0000-4000-8000-000000000001',
|
|
10
|
+
'72000000-0000-4000-8000-000000000001', '72000000-0000-4000-8000-000000000001');
|
|
11
|
+
insert into agent_board.session_identities(agent_id, parent_agent_id, root_agent_id, depth, identity_kind, harness,
|
|
12
|
+
approved_risky_digest, current_risky_digest, holder_secret_hash, last_heartbeat)
|
|
13
|
+
values
|
|
14
|
+
('72000000-0000-4000-8000-000000000001', null, '72000000-0000-4000-8000-000000000001', 0, 'legacy', null, 'approved', 'approved', null, null),
|
|
15
|
+
('72000000-0000-4000-8000-000000000002', '72000000-0000-4000-8000-000000000001', '72000000-0000-4000-8000-000000000001', 1, 'session', 'claude',
|
|
16
|
+
'approved', 'approved', extensions.digest('isolation-holder', 'sha256'), now());
|
|
17
|
+
insert into agent_board.session_id_aliases(id, identity_agent_id, root_agent_id, harness, session_id)
|
|
18
|
+
values ('73000000-0000-4000-8000-000000000001', '72000000-0000-4000-8000-000000000002', '72000000-0000-4000-8000-000000000001', 'claude', 'original');
|
|
19
|
+
update agent_board.session_identities set lease_alias_id = '73000000-0000-4000-8000-000000000001'
|
|
20
|
+
where agent_id = '72000000-0000-4000-8000-000000000002';
|
|
21
|
+
insert into agent_board.delegation_policies(id, root_agent_id, mode, allowed_scopes)
|
|
22
|
+
values ('74000000-0000-4000-8000-000000000001', '72000000-0000-4000-8000-000000000001', 'root_minted', '["read","write"]');
|
|
23
|
+
insert into agent_board.delegation_grants(child_agent_id, root_agent_id, parent_agent_id, delegation_policy_id, granted_scopes, grant_expires_at)
|
|
24
|
+
values ('72000000-0000-4000-8000-000000000002', '72000000-0000-4000-8000-000000000001',
|
|
25
|
+
'72000000-0000-4000-8000-000000000001', '74000000-0000-4000-8000-000000000001', '["read","write"]', now() + interval '8 hours');
|
|
26
|
+
insert into agent_board.boards(id, slug, name, created_by_agent_id)
|
|
27
|
+
values ('74000000-0000-4000-8000-000000000002', 'sid-links-room', 'SID links room', '72000000-0000-4000-8000-000000000001');
|
|
28
|
+
insert into agent_board.board_memberships(board_id, agent_id, membership_state, role) values
|
|
29
|
+
('74000000-0000-4000-8000-000000000002', '72000000-0000-4000-8000-000000000001', 'joined', 'owner'),
|
|
30
|
+
('74000000-0000-4000-8000-000000000002', '72000000-0000-4000-8000-000000000002', 'joined', 'admin');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
teardown
|
|
34
|
+
{
|
|
35
|
+
delete from agent_board.board_membership_events where board_id = '74000000-0000-4000-8000-000000000002';
|
|
36
|
+
delete from agent_board.board_memberships where board_id = '74000000-0000-4000-8000-000000000002';
|
|
37
|
+
delete from agent_board.boards where id = '74000000-0000-4000-8000-000000000002';
|
|
38
|
+
delete from agent_board.delegation_grants where root_agent_id = '72000000-0000-4000-8000-000000000001';
|
|
39
|
+
delete from agent_board.delegation_policies where root_agent_id = '72000000-0000-4000-8000-000000000001';
|
|
40
|
+
update agent_board.session_identities set lease_alias_id = null where root_agent_id = '72000000-0000-4000-8000-000000000001';
|
|
41
|
+
delete from agent_board.session_id_aliases where root_agent_id = '72000000-0000-4000-8000-000000000001';
|
|
42
|
+
delete from agent_board.session_identities where root_agent_id = '72000000-0000-4000-8000-000000000001';
|
|
43
|
+
delete from agent_board.agents where id = '72000000-0000-4000-8000-000000000002';
|
|
44
|
+
delete from agent_board.agents where id = '72000000-0000-4000-8000-000000000001';
|
|
45
|
+
delete from auth.users where id = '71000000-0000-4000-8000-000000000001';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
session "a"
|
|
49
|
+
step "a_begin" { BEGIN; SET LOCAL ROLE service_role; SET LOCAL "request.jwt.claim.role" = 'service_role'; SET LOCAL "request.jwt.claim.sub" = '71000000-0000-4000-8000-000000000001'; }
|
|
50
|
+
step "a_link" { select agent_board.link_session_alias('72000000-0000-4000-8000-000000000002', '0', 'continued-a'); }
|
|
51
|
+
step "a_commit" { COMMIT; }
|
|
52
|
+
step "a_rebegin" { BEGIN; SET LOCAL ROLE service_role; SET LOCAL "request.jwt.claim.role" = 'service_role'; SET LOCAL "request.jwt.claim.sub" = '71000000-0000-4000-8000-000000000001'; }
|
|
53
|
+
step "a_relink" { select agent_board.link_session_alias('72000000-0000-4000-8000-000000000002', '0', 'continued-a'); }
|
|
54
|
+
step "a_recommit" { COMMIT; }
|
|
55
|
+
|
|
56
|
+
session "b"
|
|
57
|
+
step "b_begin" { BEGIN; SET LOCAL ROLE service_role; SET LOCAL "request.jwt.claim.role" = 'service_role'; SET LOCAL "request.jwt.claim.sub" = '71000000-0000-4000-8000-000000000001'; }
|
|
58
|
+
step "b_same" { select agent_board.link_session_alias('72000000-0000-4000-8000-000000000002', '0', 'continued-a'); }
|
|
59
|
+
step "b_other" { select agent_board.link_session_alias('72000000-0000-4000-8000-000000000002', '0', 'continued-b'); }
|
|
60
|
+
step "b_narrow" {
|
|
61
|
+
select role from agent_board.grant_board_role('74000000-0000-4000-8000-000000000002', '72000000-0000-4000-8000-000000000001', 'observer', '72000000-0000-4000-8000-000000000001');
|
|
62
|
+
select allowed_scopes from agent_board.upsert_delegation_policy('72000000-0000-4000-8000-000000000001', 'root_minted', '["read"]', 200, interval '8 hours', true);
|
|
63
|
+
}
|
|
64
|
+
step "b_commit" { COMMIT; }
|
|
65
|
+
|
|
66
|
+
session "check"
|
|
67
|
+
step "check" {
|
|
68
|
+
select count(*) as aliases, count(distinct identity_agent_id) as identities
|
|
69
|
+
from agent_board.session_id_aliases where root_agent_id = '72000000-0000-4000-8000-000000000001';
|
|
70
|
+
select credential_epoch, holder_secret_hash = extensions.digest('isolation-holder', 'sha256') as unchanged_holder
|
|
71
|
+
from agent_board.session_identities where agent_id = '72000000-0000-4000-8000-000000000002';
|
|
72
|
+
select granted_scopes, role from agent_board.delegation_grants g join agent_board.board_memberships bm on bm.agent_id = g.child_agent_id
|
|
73
|
+
where g.child_agent_id = '72000000-0000-4000-8000-000000000002' and bm.board_id = '74000000-0000-4000-8000-000000000002';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
permutation "a_begin" "a_link" "b_begin" "b_same" "a_commit" "b_commit" "check"
|
|
77
|
+
permutation "b_begin" "b_same" "a_begin" "a_link" "b_commit" "a_commit" "check"
|
|
78
|
+
permutation "a_begin" "a_link" "b_begin" "b_other" "a_commit" "b_commit" "check"
|
|
79
|
+
permutation "a_begin" "a_link" "b_begin" "b_narrow" "a_commit" "b_commit" "a_rebegin" "a_relink" "a_recommit" "check"
|
|
80
|
+
permutation "b_begin" "b_narrow" "a_begin" "a_link" "b_commit" "a_commit" "check"
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Two verified key holders reclaim one lapsed Claude identity. Distinct keys
|
|
2
|
+
# avoid challenge/key serialization masking the identity-row race. Both commit
|
|
3
|
+
# orders must produce one epoch bump, one token, one rotated holder and the named
|
|
4
|
+
# duplicate_live_session refusal; the old epoch cannot resolve afterward.
|
|
5
|
+
setup
|
|
6
|
+
{
|
|
7
|
+
grant usage on schema extensions to service_role;
|
|
8
|
+
insert into auth.users(id, instance_id, aud, role, email)
|
|
9
|
+
values ('74000000-0000-4000-8000-000000000001', '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated', 'sid-reclaim@test.invalid');
|
|
10
|
+
insert into agent_board.agents(id, agent_type, handle, display_name, status, auth_user_id, parent_agent_id, root_agent_id)
|
|
11
|
+
values ('75000000-0000-4000-8000-000000000001', 'agent', 'sid-reclaim-root', 'Reclaim root', 'active', null, null, null),
|
|
12
|
+
('75000000-0000-4000-8000-000000000002', 'agent', 'sid-reclaim-child', 'Reclaim child', 'active', '74000000-0000-4000-8000-000000000001',
|
|
13
|
+
'75000000-0000-4000-8000-000000000001', '75000000-0000-4000-8000-000000000001');
|
|
14
|
+
insert into agent_board.agent_signing_keys(id, agent_id, key_id, key_version, public_key, public_key_format, fingerprint, status, proof_state)
|
|
15
|
+
values ('76000000-0000-4000-8000-000000000001', '75000000-0000-4000-8000-000000000002', 'holder-a', 1,
|
|
16
|
+
public.test_pubkey('sid-reclaim-a'), 'raw_ed25519_base64url', public.test_key_fingerprint('sid-reclaim-a'), 'active', 'proven'),
|
|
17
|
+
('76000000-0000-4000-8000-000000000002', '75000000-0000-4000-8000-000000000002', 'holder-b', 2,
|
|
18
|
+
public.test_pubkey('sid-reclaim-b'), 'raw_ed25519_base64url', public.test_key_fingerprint('sid-reclaim-b'), 'active', 'proven');
|
|
19
|
+
insert into agent_board.session_identities(agent_id, parent_agent_id, root_agent_id, depth, identity_kind, harness,
|
|
20
|
+
approved_risky_digest, current_risky_digest, holder_secret_hash, last_heartbeat)
|
|
21
|
+
values ('75000000-0000-4000-8000-000000000001', null, '75000000-0000-4000-8000-000000000001', 0, 'legacy', null, 'approved', 'approved', null, null),
|
|
22
|
+
('75000000-0000-4000-8000-000000000002', '75000000-0000-4000-8000-000000000001', '75000000-0000-4000-8000-000000000001', 1, 'session', 'claude',
|
|
23
|
+
'approved', 'approved', extensions.digest('old-holder', 'sha256'), now() - interval '181 seconds');
|
|
24
|
+
insert into agent_board.session_id_aliases(id, identity_agent_id, root_agent_id, harness, session_id)
|
|
25
|
+
values ('77000000-0000-4000-8000-000000000001', '75000000-0000-4000-8000-000000000002', '75000000-0000-4000-8000-000000000001', 'claude', 'original');
|
|
26
|
+
update agent_board.session_identities set lease_alias_id = '77000000-0000-4000-8000-000000000001'
|
|
27
|
+
where agent_id = '75000000-0000-4000-8000-000000000002';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
teardown
|
|
31
|
+
{
|
|
32
|
+
update agent_board.session_identities set lease_alias_id = null where root_agent_id = '75000000-0000-4000-8000-000000000001';
|
|
33
|
+
delete from agent_board.session_id_aliases where root_agent_id = '75000000-0000-4000-8000-000000000001';
|
|
34
|
+
delete from agent_board.session_identities where root_agent_id = '75000000-0000-4000-8000-000000000001';
|
|
35
|
+
delete from agent_board.nonfleet_sessions where agent_id in
|
|
36
|
+
(select id from agent_board.agents where root_agent_id = '75000000-0000-4000-8000-000000000001');
|
|
37
|
+
delete from agent_board.agents where root_agent_id = '75000000-0000-4000-8000-000000000001';
|
|
38
|
+
delete from agent_board.agents where id = '75000000-0000-4000-8000-000000000001';
|
|
39
|
+
delete from auth.users where id = '74000000-0000-4000-8000-000000000001';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
session "a"
|
|
43
|
+
step "a_begin" { BEGIN; SET LOCAL ROLE service_role; SET LOCAL "request.jwt.claim.role" = 'service_role'; }
|
|
44
|
+
step "a_reclaim" {
|
|
45
|
+
do $$ declare c record; begin
|
|
46
|
+
select * into c from agent_board.issue_signing_key_challenge('75000000-0000-4000-8000-000000000002', 'holder-a');
|
|
47
|
+
perform * from agent_board.mint_fleet_agent_session(c.challenge_id, split_part(c.challenge, ':', 3),
|
|
48
|
+
'76000000-0000-4000-8000-000000000001', '75000000-0000-4000-8000-000000000002', 900, null, 'resumed');
|
|
49
|
+
end; $$;
|
|
50
|
+
}
|
|
51
|
+
step "a_first_claim" {
|
|
52
|
+
do $$ declare a uuid; k uuid; c record; begin
|
|
53
|
+
select id into a from agent_board.agents where handle = 'sid-stale-old';
|
|
54
|
+
select id into k from agent_board.agent_signing_keys where agent_id = a;
|
|
55
|
+
select id, challenge_nonce into c from agent_board.agent_signing_key_challenges where agent_id = a order by created_at desc limit 1;
|
|
56
|
+
perform * from agent_board.mint_fleet_agent_session(c.id, c.challenge_nonce, k, a, 900, null, 'stale-reservation');
|
|
57
|
+
end $$;
|
|
58
|
+
}
|
|
59
|
+
step "a_commit" { COMMIT; }
|
|
60
|
+
|
|
61
|
+
session "b"
|
|
62
|
+
step "b_begin" { BEGIN; SET LOCAL ROLE service_role; SET LOCAL "request.jwt.claim.role" = 'service_role'; }
|
|
63
|
+
step "b_reclaim" {
|
|
64
|
+
do $$ declare c record; begin
|
|
65
|
+
select * into c from agent_board.issue_signing_key_challenge('75000000-0000-4000-8000-000000000002', 'holder-b');
|
|
66
|
+
perform * from agent_board.mint_fleet_agent_session(c.challenge_id, split_part(c.challenge, ':', 3),
|
|
67
|
+
'76000000-0000-4000-8000-000000000002', '75000000-0000-4000-8000-000000000002', 900, null, 'resumed');
|
|
68
|
+
end; $$;
|
|
69
|
+
}
|
|
70
|
+
step "b_supersede" {
|
|
71
|
+
do $$ begin
|
|
72
|
+
perform * from agent_board.mint_delegated_child('75000000-0000-4000-8000-000000000001', 'sid-stale-new', '[]',
|
|
73
|
+
public.test_pubkey('sid-stale-new'), interval '1 hour', false, 'claude', 'stale-reservation', null, '[]',
|
|
74
|
+
jsonb_build_object('resolution', 'resolved', 'risky_digest', 'approved'));
|
|
75
|
+
end $$;
|
|
76
|
+
}
|
|
77
|
+
step "b_commit" { COMMIT; }
|
|
78
|
+
|
|
79
|
+
session "check"
|
|
80
|
+
step "prepare_stale" {
|
|
81
|
+
update agent_board.agents set owner_user_id = '74000000-0000-4000-8000-000000000001'
|
|
82
|
+
where id = '75000000-0000-4000-8000-000000000001';
|
|
83
|
+
BEGIN;
|
|
84
|
+
SET LOCAL ROLE service_role;
|
|
85
|
+
SET LOCAL "request.jwt.claim.role" = 'service_role';
|
|
86
|
+
select count(*) from agent_board.upsert_delegation_policy('75000000-0000-4000-8000-000000000001', 'root_minted', '[]', 200, interval '8 hours', true);
|
|
87
|
+
do $$ declare m record; begin
|
|
88
|
+
select * into m from agent_board.mint_delegated_child('75000000-0000-4000-8000-000000000001', 'sid-stale-old', '[]',
|
|
89
|
+
public.test_pubkey('sid-stale-old'), interval '1 hour', false, 'claude', 'stale-reservation', null, '[]',
|
|
90
|
+
jsonb_build_object('resolution', 'resolved', 'risky_digest', 'approved'));
|
|
91
|
+
perform * from agent_board.issue_signing_key_challenge(m.child_agent_id, 'delegated-ephemeral');
|
|
92
|
+
end $$;
|
|
93
|
+
COMMIT;
|
|
94
|
+
update agent_board.session_identities set created_at = now() - interval '181 seconds'
|
|
95
|
+
where agent_id = (select id from agent_board.agents where handle = 'sid-stale-old');
|
|
96
|
+
}
|
|
97
|
+
step "check_stale" {
|
|
98
|
+
select a.status, si.tombstone_reason, si.credential_epoch, si.holder_secret_hash is not null as claimed,
|
|
99
|
+
(select count(*) from agent_board.fleet_revocation_events e where e.subject_id = a.id) as permanent_revocations
|
|
100
|
+
from agent_board.agents a join agent_board.session_identities si on si.agent_id = a.id where a.handle = 'sid-stale-old';
|
|
101
|
+
select a.handle as bound_principal from agent_board.session_id_aliases al join agent_board.agents a on a.id = al.identity_agent_id
|
|
102
|
+
where al.root_agent_id = '75000000-0000-4000-8000-000000000001' and al.session_id = 'stale-reservation';
|
|
103
|
+
select count(*) as successor_principals from agent_board.agents where handle = 'sid-stale-new';
|
|
104
|
+
}
|
|
105
|
+
step "check" {
|
|
106
|
+
select credential_epoch, holder_secret_hash <> extensions.digest('old-holder', 'sha256') as rotated_holder,
|
|
107
|
+
a.session_id as bound_alias
|
|
108
|
+
from agent_board.session_identities si join agent_board.session_id_aliases a on a.id = si.lease_alias_id
|
|
109
|
+
where si.agent_id = '75000000-0000-4000-8000-000000000002';
|
|
110
|
+
select count(*) as tokens from agent_board.nonfleet_sessions where agent_id = '75000000-0000-4000-8000-000000000002';
|
|
111
|
+
BEGIN;
|
|
112
|
+
SET LOCAL ROLE authenticated;
|
|
113
|
+
SET LOCAL "request.jwt.claim.role" = 'authenticated';
|
|
114
|
+
SET LOCAL "request.jwt.claim.sub" = '74000000-0000-4000-8000-000000000001';
|
|
115
|
+
SET LOCAL "request.jwt.claim.identity_lease_epoch" = '0';
|
|
116
|
+
select agent_board.current_agent_id() is null as old_epoch_fenced;
|
|
117
|
+
SET LOCAL "request.jwt.claim.identity_lease_epoch" = '1';
|
|
118
|
+
select agent_board.current_agent_id() = '75000000-0000-4000-8000-000000000002'::uuid as new_epoch_resolves;
|
|
119
|
+
COMMIT;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
permutation "a_begin" "a_reclaim" "b_begin" "b_reclaim" "a_commit" "b_commit" "check"
|
|
123
|
+
permutation "b_begin" "b_reclaim" "a_begin" "a_reclaim" "b_commit" "a_commit" "check"
|
|
124
|
+
permutation "prepare_stale" "a_begin" "a_first_claim" "b_begin" "b_supersede" "a_commit" "b_commit" "check_stale"
|
|
125
|
+
permutation "prepare_stale" "b_begin" "b_supersede" "a_begin" "a_first_claim" "b_commit" "a_commit" "check_stale"
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Renewal and owner revocation of a real minted/claimed identity, both commit
|
|
2
|
+
# orders. The loser waits on the identity row; revoke-after-renew fences its
|
|
3
|
+
# result, and renew-after-revoke must re-read the tombstone rather than revive it.
|
|
4
|
+
setup
|
|
5
|
+
{
|
|
6
|
+
grant usage on schema extensions to service_role, authenticated;
|
|
7
|
+
insert into auth.users(id) values ('81000000-0000-4000-8000-000000000001');
|
|
8
|
+
insert into agent_board.agents(id, agent_type, handle, display_name, status, auth_user_id, owner_user_id)
|
|
9
|
+
values ('82000000-0000-4000-8000-000000000001', 'human', 'sid-renew-owner', 'Renew owner', 'active',
|
|
10
|
+
'81000000-0000-4000-8000-000000000001', null),
|
|
11
|
+
('82000000-0000-4000-8000-000000000002', 'agent', 'sid-renew-root', 'Renew root', 'active', null,
|
|
12
|
+
'81000000-0000-4000-8000-000000000001');
|
|
13
|
+
insert into agent_board.session_identities(agent_id, root_agent_id, depth, current_risky_digest, approved_risky_digest)
|
|
14
|
+
values ('82000000-0000-4000-8000-000000000002', '82000000-0000-4000-8000-000000000002', 0, 'approved', 'approved');
|
|
15
|
+
BEGIN;
|
|
16
|
+
SET LOCAL ROLE service_role;
|
|
17
|
+
SET LOCAL "request.jwt.claim.role" = 'service_role';
|
|
18
|
+
select count(*) from agent_board.upsert_delegation_policy('82000000-0000-4000-8000-000000000002', 'root_minted', '[]', 200, interval '7 days', true);
|
|
19
|
+
do $$ declare m record; c record; begin
|
|
20
|
+
select * into m from agent_board.mint_delegated_child('82000000-0000-4000-8000-000000000002', 'sid-renew-child', '[]',
|
|
21
|
+
public.test_pubkey('sid-renew-child'), interval '1 hour', false, 'claude', 'renew-original', null, '[]',
|
|
22
|
+
jsonb_build_object('resolution', 'resolved', 'risky_digest', 'approved'));
|
|
23
|
+
select * into c from agent_board.issue_signing_key_challenge(m.child_agent_id, 'delegated-ephemeral');
|
|
24
|
+
perform * from agent_board.mint_fleet_agent_session(c.challenge_id, split_part(c.challenge, ':', 3),
|
|
25
|
+
m.signing_key_id, m.child_agent_id, 900, null, 'renew-original');
|
|
26
|
+
end $$;
|
|
27
|
+
COMMIT;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
teardown
|
|
31
|
+
{
|
|
32
|
+
do $$ declare v_child uuid; v_auth uuid; begin
|
|
33
|
+
select id, auth_user_id into v_child, v_auth from agent_board.agents where handle = 'sid-renew-child';
|
|
34
|
+
update agent_board.session_identities set lease_alias_id = null where agent_id = v_child;
|
|
35
|
+
delete from agent_board.session_id_aliases where identity_agent_id = v_child;
|
|
36
|
+
delete from agent_board.session_identities where agent_id = v_child;
|
|
37
|
+
delete from agent_board.nonfleet_sessions where agent_id = v_child;
|
|
38
|
+
delete from agent_board.agents where id = v_child;
|
|
39
|
+
delete from auth.users where id = v_auth;
|
|
40
|
+
delete from agent_board.session_identities where agent_id = '82000000-0000-4000-8000-000000000002';
|
|
41
|
+
delete from agent_board.agents where id in ('82000000-0000-4000-8000-000000000001', '82000000-0000-4000-8000-000000000002');
|
|
42
|
+
delete from auth.users where id = '81000000-0000-4000-8000-000000000001';
|
|
43
|
+
end $$;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
session "renew"
|
|
47
|
+
step "renew_begin" {
|
|
48
|
+
BEGIN;
|
|
49
|
+
select set_config('request.jwt.claim.sub', (select auth_user_id::text from agent_board.agents where handle = 'sid-renew-child'), true) is not null as holder_forwarded;
|
|
50
|
+
SET LOCAL ROLE service_role;
|
|
51
|
+
SET LOCAL "request.jwt.claim.role" = 'service_role';
|
|
52
|
+
SET LOCAL "request.jwt.claim.identity_lease_epoch" = '0';
|
|
53
|
+
}
|
|
54
|
+
step "renew" {
|
|
55
|
+
select agent_board.renew_session_identity((select id from agent_board.agents where handle = 'sid-renew-child'), 'approved');
|
|
56
|
+
}
|
|
57
|
+
step "renew_commit" { COMMIT; }
|
|
58
|
+
|
|
59
|
+
session "revoke"
|
|
60
|
+
step "revoke_begin" {
|
|
61
|
+
BEGIN;
|
|
62
|
+
SET LOCAL ROLE authenticated;
|
|
63
|
+
SET LOCAL "request.jwt.claim.role" = 'authenticated';
|
|
64
|
+
SET LOCAL "request.jwt.claim.sub" = '81000000-0000-4000-8000-000000000001';
|
|
65
|
+
}
|
|
66
|
+
step "revoke" {
|
|
67
|
+
select agent_board.revoke_session_identity((select id from agent_board.agents where handle = 'sid-renew-child'));
|
|
68
|
+
}
|
|
69
|
+
step "revoke_commit" { COMMIT; }
|
|
70
|
+
|
|
71
|
+
session "check"
|
|
72
|
+
step "check" {
|
|
73
|
+
select si.tombstone_reason, si.credential_epoch, a.status, g.revoked_at is not null as grant_revoked,
|
|
74
|
+
g.last_renewed_at is not null as renewal_preceded_revoke,
|
|
75
|
+
(select count(*) from agent_board.fleet_revocation_events e where e.subject_id = a.id) as permanent_revocations,
|
|
76
|
+
(select count(*) from agent_board.agent_signing_keys k where k.agent_id = a.id and k.status <> 'revoked') as active_keys
|
|
77
|
+
from agent_board.agents a join agent_board.session_identities si on si.agent_id = a.id
|
|
78
|
+
join agent_board.delegation_grants g on g.child_agent_id = a.id where a.handle = 'sid-renew-child';
|
|
79
|
+
select bool_and(expires_at <= issued_at + interval '900 seconds') as bounded_pre_revoke_tokens
|
|
80
|
+
from agent_board.nonfleet_sessions where agent_id = (select id from agent_board.agents where handle = 'sid-renew-child');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
permutation "renew_begin" "renew" "revoke_begin" "revoke" "renew_commit" "revoke_commit" "check"
|
|
84
|
+
permutation "revoke_begin" "revoke" "renew_begin" "renew" "revoke_commit" "renew_commit" "check"
|