@consiliency/agent-board-schema 1.60.0 → 1.64.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.
Files changed (100) hide show
  1. package/README.md +86 -0
  2. package/fixtures/already-applied.json +16 -0
  3. package/migrations/20261011000100_agent_board_session_identities.sql +1964 -0
  4. package/migrations/20261012000100_agent_board_sidowner.sql +209 -0
  5. package/migrations/20261014000100_agent_board_grant_target_role.sql +218 -0
  6. package/migrations/20261014000200_agent_board_last_owner.sql +239 -0
  7. package/package.json +1 -1
  8. package/schema-manifest.json +39 -3
  9. package/src/tooling.test.mjs +10 -2
  10. package/tests/agent_board_atomic_fleet_onboarding.test.sql +15 -4
  11. package/tests/agent_board_atomic_fleet_onboarding_runtime.test.sql +7 -0
  12. package/tests/agent_board_consent_emission.test.sql +30 -11
  13. package/tests/agent_board_consent_gating.test.sql +16 -2
  14. package/tests/agent_board_delegate_role_class.test.sql +1 -1
  15. package/tests/agent_board_delegation_cap_ceiling.test.sql +26 -15
  16. package/tests/agent_board_delegation_enforcement.test.sql +1 -1
  17. package/tests/agent_board_enroll_resumption.test.sql +22 -4
  18. package/tests/agent_board_enroll_token_single_use.test.sql +18 -3
  19. package/tests/agent_board_federated_lock_order_class.test.sql +2 -2
  20. package/tests/agent_board_fk_lock_edges.test.sql +77 -16
  21. package/tests/agent_board_grant_target_role.test.sql +1118 -0
  22. package/tests/agent_board_hosted_delegates.test.sql +5 -3
  23. package/tests/agent_board_invite_no_downgrade.test.sql +15 -11
  24. package/tests/agent_board_ledger_authority.test.sql +77 -12
  25. package/tests/agent_board_membership_current_row_class.test.sql +9 -1
  26. package/tests/agent_board_mint_fleet_agent_session.test.sql +2 -2
  27. package/tests/agent_board_mint_harden.test.sql +38 -27
  28. package/tests/agent_board_role_guard.test.sql +319 -0
  29. package/tests/agent_board_rolereset.test.sql +25 -29
  30. package/tests/agent_board_roomdeleg.test.sql +8 -0
  31. package/tests/agent_board_roomdeleg_lane_b.test.sql +6 -6
  32. package/tests/agent_board_roomown.test.sql +3 -1
  33. package/tests/agent_board_rowid.test.sql +7 -2
  34. package/tests/agent_board_session_identity_approval_revocation.test.sql +334 -0
  35. package/tests/agent_board_session_identity_backfill.test.sql +82 -0
  36. package/tests/agent_board_session_identity_backfill_refusal.test.sql +14 -0
  37. package/tests/agent_board_session_identity_behavior_classes.test.sql +176 -0
  38. package/tests/agent_board_session_identity_class.test.sql +118 -0
  39. package/tests/agent_board_session_identity_fingerprint.test.sql +271 -0
  40. package/tests/agent_board_session_identity_labels_limits.test.sql +205 -0
  41. package/tests/agent_board_session_identity_lease.test.sql +235 -0
  42. package/tests/agent_board_session_identity_lifetime.test.sql +230 -0
  43. package/tests/agent_board_session_identity_limits.test.sql +208 -0
  44. package/tests/agent_board_session_identity_lineage.test.sql +64 -0
  45. package/tests/agent_board_session_identity_link.test.sql +201 -0
  46. package/tests/agent_board_session_identity_mint.test.sql +233 -0
  47. package/tests/agent_board_session_identity_panel_r1.test.sql +194 -0
  48. package/tests/agent_board_session_identity_panel_r2.test.sql +174 -0
  49. package/tests/agent_board_session_identity_protected_actions.test.sql +156 -0
  50. package/tests/agent_board_session_identity_revoke_closure.test.sql +131 -0
  51. package/tests/agent_board_sidowner_leases.test.sql +107 -0
  52. package/tests/agent_board_sidowner_owner_required.test.sql +97 -0
  53. package/tests/agent_board_sidowner_read_class.test.sql +171 -0
  54. package/tests/agent_board_sidowner_reads.test.sql +136 -0
  55. package/tests/agent_board_signer_lifecycle.test.sql +26 -4
  56. package/tests/agent_board_signing_challenge_pending_cap.test.sql +1 -1
  57. package/tests/agent_board_write_enforcement_class.test.sql +74 -15
  58. package/tests/catalog/function_execute_privileges.json +227 -2
  59. package/tests/catalog/p_metadata_parameters.json +102 -2
  60. package/tests/isolation/expected/grant_target_role_race.out +348 -0
  61. package/tests/isolation/expected/last_owner_departure.out +562 -0
  62. package/tests/isolation/expected/session_identity_limits.out +165 -0
  63. package/tests/isolation/expected/session_identity_links.out +235 -0
  64. package/tests/isolation/expected/session_identity_reclaim.out +253 -0
  65. package/tests/isolation/expected/session_identity_renew_revoke.out +115 -0
  66. package/tests/isolation/expected/session_identity_renew_revoke_key_gate.out +138 -0
  67. package/tests/isolation/expected/session_identity_tombstone_onboarding.out +247 -0
  68. package/tests/isolation/specs/atomic_fleet_authority_races.spec +8 -3
  69. package/tests/isolation/specs/atomic_fleet_binding_revoke.spec +0 -3
  70. package/tests/isolation/specs/atomic_fleet_board_archive.spec +0 -3
  71. package/tests/isolation/specs/atomic_fleet_human_fresh_revoke.spec +0 -3
  72. package/tests/isolation/specs/atomic_fleet_identity_disposition.spec +0 -3
  73. package/tests/isolation/specs/atomic_fleet_lifecycle_races.spec +0 -3
  74. package/tests/isolation/specs/atomic_fleet_subtree_lock_order.spec +0 -3
  75. package/tests/isolation/specs/ceiling_setter_lock_before_upsert.spec +0 -3
  76. package/tests/isolation/specs/claimpolicy_acquire_revert_race.spec +0 -2
  77. package/tests/isolation/specs/claimpolicy_board_scope.spec +0 -2
  78. package/tests/isolation/specs/claimpolicy_claimrow_lock_order.spec +0 -2
  79. package/tests/isolation/specs/claimpolicy_isolation_refusal.spec +0 -2
  80. package/tests/isolation/specs/claimpolicy_renew_revert_race.spec +0 -2
  81. package/tests/isolation/specs/claimpolicy_writer_keyshare.spec +0 -2
  82. package/tests/isolation/specs/consent_approval_lock.spec +0 -1
  83. package/tests/isolation/specs/consent_emit_actual_state.spec +0 -1
  84. package/tests/isolation/specs/grant_target_role_race.spec +206 -0
  85. package/tests/isolation/specs/hosted_delegate_add_tombstone_race.spec +0 -1
  86. package/tests/isolation/specs/hosted_delegate_tombstone_revoke_race.spec +0 -1
  87. package/tests/isolation/specs/hosted_delegate_withdraw_evict_race.spec +0 -1
  88. package/tests/isolation/specs/last_owner_departure.spec +137 -0
  89. package/tests/isolation/specs/ledger_authority_revoke.spec +0 -3
  90. package/tests/isolation/specs/onboarding_ceiling_lock_order.spec +0 -3
  91. package/tests/isolation/specs/outbox_wake_ordering.spec +0 -1
  92. package/tests/isolation/specs/session_identity_limits.spec +81 -0
  93. package/tests/isolation/specs/session_identity_links.spec +82 -0
  94. package/tests/isolation/specs/session_identity_reclaim.spec +125 -0
  95. package/tests/isolation/specs/session_identity_renew_revoke.spec +84 -0
  96. package/tests/isolation/specs/session_identity_renew_revoke_key_gate.spec +94 -0
  97. package/tests/isolation/specs/session_identity_tombstone_onboarding.spec +107 -0
  98. package/tests/isolation/specs/signed_grant_onboarding_deadlock.spec +0 -1
  99. package/tests/isolation/specs/signer_lifecycle_revoke.spec +0 -1
  100. package/tests/seeds/20261011000100_agent_board_session_identities.sql.pre.sql +47 -0
@@ -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"
@@ -0,0 +1,94 @@
1
+ # A third session holds only the signing key. Revoke acquires root/identity
2
+ # then waits for that key, pinning renewal blocking BEFORE its state reread.
3
+ # Renewal and owner revocation of a real minted/claimed identity, both commit
4
+ # orders. The loser waits on the identity row; revoke-after-renew fences its
5
+ # result, and renew-after-revoke must re-read the tombstone rather than revive it.
6
+ setup
7
+ {
8
+ grant usage on schema extensions to service_role, authenticated;
9
+ insert into auth.users(id) values ('81000000-0000-4000-8000-000000000001');
10
+ insert into agent_board.agents(id, agent_type, handle, display_name, status, auth_user_id, owner_user_id)
11
+ values ('82000000-0000-4000-8000-000000000001', 'human', 'sid-renew-owner', 'Renew owner', 'active',
12
+ '81000000-0000-4000-8000-000000000001', null),
13
+ ('82000000-0000-4000-8000-000000000002', 'agent', 'sid-renew-root', 'Renew root', 'active', null,
14
+ '81000000-0000-4000-8000-000000000001');
15
+ insert into agent_board.session_identities(agent_id, root_agent_id, depth, current_risky_digest, approved_risky_digest)
16
+ values ('82000000-0000-4000-8000-000000000002', '82000000-0000-4000-8000-000000000002', 0, 'approved', 'approved');
17
+ BEGIN;
18
+ SET LOCAL ROLE service_role;
19
+ SET LOCAL "request.jwt.claim.role" = 'service_role';
20
+ select count(*) from agent_board.upsert_delegation_policy('82000000-0000-4000-8000-000000000002', 'root_minted', '[]', 200, interval '7 days', true);
21
+ do $$ declare m record; c record; begin
22
+ select * into m from agent_board.mint_delegated_child('82000000-0000-4000-8000-000000000002', 'sid-renew-child', '[]',
23
+ public.test_pubkey('sid-renew-child'), interval '1 hour', false, 'claude', 'renew-original', null, '[]',
24
+ jsonb_build_object('resolution', 'resolved', 'risky_digest', 'approved'));
25
+ select * into c from agent_board.issue_signing_key_challenge(m.child_agent_id, 'delegated-ephemeral');
26
+ perform * from agent_board.mint_fleet_agent_session(c.challenge_id, split_part(c.challenge, ':', 3),
27
+ m.signing_key_id, m.child_agent_id, 900, null, 'renew-original');
28
+ end $$;
29
+ COMMIT;
30
+ }
31
+
32
+ teardown
33
+ {
34
+ do $$ declare v_child uuid; v_auth uuid; begin
35
+ select id, auth_user_id into v_child, v_auth from agent_board.agents where handle = 'sid-renew-child';
36
+ update agent_board.session_identities set lease_alias_id = null where agent_id = v_child;
37
+ delete from agent_board.session_id_aliases where identity_agent_id = v_child;
38
+ delete from agent_board.session_identities where agent_id = v_child;
39
+ delete from agent_board.nonfleet_sessions where agent_id = v_child;
40
+ delete from agent_board.agents where id = v_child;
41
+ delete from auth.users where id = v_auth;
42
+ delete from agent_board.session_identities where agent_id = '82000000-0000-4000-8000-000000000002';
43
+ delete from agent_board.agents where id in ('82000000-0000-4000-8000-000000000001', '82000000-0000-4000-8000-000000000002');
44
+ delete from auth.users where id = '81000000-0000-4000-8000-000000000001';
45
+ end $$;
46
+ }
47
+
48
+ session "renew"
49
+ step "renew_begin" {
50
+ BEGIN;
51
+ 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;
52
+ SET LOCAL ROLE service_role;
53
+ SET LOCAL "request.jwt.claim.role" = 'service_role';
54
+ SET LOCAL "request.jwt.claim.identity_lease_epoch" = '0';
55
+ }
56
+ step "renew" {
57
+ select agent_board.renew_session_identity((select id from agent_board.agents where handle = 'sid-renew-child'), 'approved');
58
+ }
59
+ step "renew_commit" { COMMIT; }
60
+
61
+ session "revoke"
62
+ step "revoke_begin" {
63
+ BEGIN;
64
+ SET LOCAL ROLE authenticated;
65
+ SET LOCAL "request.jwt.claim.role" = 'authenticated';
66
+ SET LOCAL "request.jwt.claim.sub" = '81000000-0000-4000-8000-000000000001';
67
+ }
68
+ step "revoke" {
69
+ select agent_board.revoke_session_identity((select id from agent_board.agents where handle = 'sid-renew-child'));
70
+ }
71
+ step "revoke_commit" { COMMIT; }
72
+
73
+ session "check"
74
+ step "check" {
75
+ select si.tombstone_reason, si.credential_epoch, a.status, g.revoked_at is not null as grant_revoked,
76
+ g.last_renewed_at is not null as renewal_preceded_revoke,
77
+ (select count(*) from agent_board.fleet_revocation_events e where e.subject_id = a.id) as permanent_revocations,
78
+ (select count(*) from agent_board.agent_signing_keys k where k.agent_id = a.id and k.status <> 'revoked') as active_keys
79
+ from agent_board.agents a join agent_board.session_identities si on si.agent_id = a.id
80
+ join agent_board.delegation_grants g on g.child_agent_id = a.id where a.handle = 'sid-renew-child';
81
+ select bool_and(expires_at <= issued_at + interval '900 seconds') as bounded_pre_revoke_tokens
82
+ from agent_board.nonfleet_sessions where agent_id = (select id from agent_board.agents where handle = 'sid-renew-child');
83
+ }
84
+
85
+ session "key_gate"
86
+ step "key_begin" { BEGIN; }
87
+ step "key_hold" {
88
+ select 1 as held from agent_board.agent_signing_keys
89
+ where agent_id = (select id from agent_board.agents where handle = 'sid-renew-child') for update;
90
+ }
91
+ step "key_commit" { COMMIT; }
92
+
93
+ permutation "key_begin" "key_hold" "revoke_begin" "revoke" "renew_begin" "renew" "key_commit" "revoke_commit" "renew_commit" "check"
94
+ permutation "key_begin" "key_hold" "renew_begin" "renew" "revoke_begin" "revoke" "key_commit" "renew_commit" "revoke_commit" "check"
@@ -0,0 +1,107 @@
1
+ # SIDSCHEMA integrity repair: real verified instance withdrawal, with a real
2
+ # minted session descendant, against onboarding's cycle-relevant acquisitions.
3
+ # The onboarding session is a lock-equivalent stand-in (the established
4
+ # signed_grant_onboarding_deadlock.spec pattern), NOT a full onboarding replay.
5
+ # It takes the actual board mutex, agents FOR UPDATE and memberships FOR UPDATE
6
+ # in onboard_federated_fleet_agent's installed order and modes. Invite/bounds
7
+ # locks before that mutex cannot cycle: withdrawal never accesses those rows.
8
+ # All other onboarding locks follow the mutex; omission cannot create or hide a
9
+ # shared-row cycle while withdrawal waits before its owner/instance/family rows.
10
+ # No timing sleeps. Both orders and the mutex-alone interleaving must complete.
11
+ # The separately mutated catalog class names withdrawal if its mutex is absent;
12
+ # the order pin rejects a mutex moved after the instance. Withdrawal is NOT an
13
+ # exemption based on the membership state it writes.
14
+
15
+ setup
16
+ {
17
+ grant usage on schema extensions to service_role;
18
+ insert into agent_board.agents(id,agent_type,handle,display_name,status,owner_user_id,is_delegate)
19
+ values ('85000000-0000-4000-8000-000000000001','agent','sid-ti-root','Integrity delegate','active',
20
+ agent_board._ensure_agent_auth_user(agent_board._federated_identity_slug('urn:sid-ti:issuer','sid-ti-owner')),true);
21
+ insert into agent_board.session_identities(agent_id,root_agent_id,depth,current_risky_digest,approved_risky_digest)
22
+ values ('85000000-0000-4000-8000-000000000001','85000000-0000-4000-8000-000000000001',0,'approved','approved');
23
+ insert into agent_board.delegate_instances(instance_id,owner_user_id,owner_issuer,owner_subject,agent_id)
24
+ select 'sid_ti_instance_00001',owner_user_id,'urn:sid-ti:issuer','sid-ti-owner',id
25
+ from agent_board.agents where id='85000000-0000-4000-8000-000000000001';
26
+ insert into agent_board.boards(id,slug,name,created_by_agent_id,visibility,status)
27
+ values ('85000000-0000-4000-8000-000000000002','sid-ti-board','Integrity board',
28
+ '85000000-0000-4000-8000-000000000001','private','active');
29
+ insert into agent_board.board_memberships(board_id,agent_id,role,membership_state,created_by_agent_id)
30
+ values ('85000000-0000-4000-8000-000000000002','85000000-0000-4000-8000-000000000001',
31
+ 'member','joined','85000000-0000-4000-8000-000000000001');
32
+ BEGIN;
33
+ SET LOCAL ROLE service_role;
34
+ SET LOCAL "request.jwt.claim.role" = 'service_role';
35
+ select count(*) from agent_board.upsert_delegation_policy('85000000-0000-4000-8000-000000000001',
36
+ 'root_minted','["read"]',200,interval '7 days',true);
37
+ do $$ declare m record; begin
38
+ select * into m from agent_board.mint_delegated_child('85000000-0000-4000-8000-000000000001',
39
+ 'sid-ti-child','["read"]',public.test_pubkey('sid-ti-child'),interval '1 hour',false,
40
+ 'claude','sid-ti-session',null,'[]',jsonb_build_object('resolution','resolved','risky_digest','approved'));
41
+ perform * from agent_board._issue_agent_credential(m.child_agent_id,'sid-ti-fixture');
42
+ end $$;
43
+ COMMIT;
44
+ }
45
+
46
+ teardown
47
+ {
48
+ do $$ declare v_auth_ids uuid[]; begin
49
+ select array_agg(auth_user_id) into v_auth_ids from agent_board.agents
50
+ where root_agent_id='85000000-0000-4000-8000-000000000001' and auth_user_id is not null;
51
+ v_auth_ids := array_append(v_auth_ids,(select owner_user_id from agent_board.agents
52
+ where id='85000000-0000-4000-8000-000000000001'));
53
+ delete from agent_board.fleet_revocation_events where revoked_by_agent_id='85000000-0000-4000-8000-000000000001';
54
+ delete from agent_board.board_membership_events where board_id='85000000-0000-4000-8000-000000000002';
55
+ delete from agent_board.boards where id='85000000-0000-4000-8000-000000000002';
56
+ delete from agent_board.delegate_instances where instance_id='sid_ti_instance_00001';
57
+ delete from agent_board.session_id_aliases where root_agent_id='85000000-0000-4000-8000-000000000001';
58
+ delete from agent_board.session_identities where root_agent_id='85000000-0000-4000-8000-000000000001';
59
+ delete from agent_board.agents where root_agent_id='85000000-0000-4000-8000-000000000001';
60
+ delete from agent_board.agents where id='85000000-0000-4000-8000-000000000001';
61
+ delete from auth.users where id=any(v_auth_ids);
62
+ end $$;
63
+ }
64
+
65
+ session "tomb"
66
+ step "t_begin" { BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role'; }
67
+ step "t_tomb" {
68
+ select first from agent_board.tombstone_delegate_instance(
69
+ 'sid_ti_instance_00001','urn:sid-ti:issuer','sid-ti-owner','owner_revoked');
70
+ }
71
+ step "t_commit" { COMMIT; }
72
+
73
+ session "onboard"
74
+ step "o_begin" { BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role'; }
75
+ step "o_advisory" {
76
+ select agent_board._lock_board_authority_ceiling('85000000-0000-4000-8000-000000000002') is null as advisory_taken;
77
+ }
78
+ step "o_agents" {
79
+ select count(*)::int as locked_agents from (
80
+ select 1 from agent_board.agents where id='85000000-0000-4000-8000-000000000001' for update) s;
81
+ }
82
+ step "o_membership" {
83
+ select count(*)::int as locked_memberships from (
84
+ select 1 from agent_board.board_memberships
85
+ where board_id='85000000-0000-4000-8000-000000000002'
86
+ and agent_id='85000000-0000-4000-8000-000000000001' for update) s;
87
+ }
88
+ step "o_commit" { COMMIT; }
89
+
90
+ session "check"
91
+ step "check" {
92
+ select tombstoned_at is not null as instance_tombstoned from agent_board.delegate_instances
93
+ where instance_id='sid_ti_instance_00001';
94
+ select status from agent_board.agents where id='85000000-0000-4000-8000-000000000001';
95
+ select membership_state from agent_board.board_memberships
96
+ where board_id='85000000-0000-4000-8000-000000000002'
97
+ and agent_id='85000000-0000-4000-8000-000000000001';
98
+ select si.tombstoned_at is not null as child_tombstoned,si.credential_epoch,
99
+ (select count(*) from agent_board.agent_credentials ac where ac.agent_id=si.agent_id and ac.revoked_at is null) as live_credentials
100
+ from agent_board.session_identities si join agent_board.agents a on a.id=si.agent_id where a.handle='sid-ti-child';
101
+ select count(*) as withdrawal_events from agent_board.board_membership_events
102
+ where board_id='85000000-0000-4000-8000-000000000002' and event_kind='operator_removal';
103
+ }
104
+
105
+ permutation "o_begin" "o_advisory" "o_agents" "t_begin" "t_tomb" "o_membership" "o_commit" "t_commit" "check"
106
+ permutation "t_begin" "t_tomb" "o_begin" "o_advisory" "t_commit" "o_agents" "o_membership" "o_commit" "check"
107
+ permutation "o_begin" "o_advisory" "t_begin" "t_tomb" "o_agents" "o_membership" "o_commit" "t_commit" "check"
@@ -74,7 +74,6 @@ teardown
74
74
  delete from agent_board.signature_evidence
75
75
  where agent_id in (select id from agent_board.agents where handle like 'f4iso-%');
76
76
  delete from agent_board.board_authority_ceilings where board_id='f4950000-0000-4000-8000-000000000001';
77
- delete from agent_board.board_memberships where board_id='f4950000-0000-4000-8000-000000000001';
78
77
  delete from agent_board.boards where id='f4950000-0000-4000-8000-000000000001';
79
78
  delete from agent_board.agent_signing_keys
80
79
  where agent_id in (select id from agent_board.agents where handle like 'f4iso-%');
@@ -84,7 +84,6 @@ teardown
84
84
  where agent_id in (select id from agent_board.agents where handle like 'k269iso-%')
85
85
  or revoked_by_agent_id in (select id from agent_board.agents where handle like 'k269iso-%');
86
86
  delete from agent_board.board_authority_ceilings where board_id='26950000-0000-4000-8000-000000000001';
87
- delete from agent_board.board_memberships where board_id='26950000-0000-4000-8000-000000000001';
88
87
  delete from agent_board.boards where id='26950000-0000-4000-8000-000000000001';
89
88
  delete from agent_board.agent_signing_keys
90
89
  where agent_id in (select id from agent_board.agents where handle like 'k269iso-%');
@@ -0,0 +1,47 @@
1
+ -- SIDSCHEMA backfill fixture. These principals exist before the durable
2
+ -- session-identity tables and must retain their original lineage afterwards.
3
+ insert into auth.users(id) values ('61100000-0000-0000-0000-000000000010');
4
+ insert into auth.users(id) values ('61100000-0000-0000-0000-000000000014');
5
+ insert into agent_board.agents (
6
+ id, agent_type, kind, handle, display_name, status, root_agent_id, auth_user_id
7
+ ) values (
8
+ '61100000-0000-0000-0000-000000000001',
9
+ 'agent', 'agent', 'sid-backfill-root', 'SID Backfill Root', 'active', null,
10
+ '61100000-0000-0000-0000-000000000014'
11
+ );
12
+
13
+ update agent_board.agents set owner_user_id='61100000-0000-0000-0000-000000000010',
14
+ created_at='2026-01-01 00:00:00+00', metadata='{"sid_backfill":"historical"}'
15
+ where id='61100000-0000-0000-0000-000000000001';
16
+ insert into agent_board.agent_signing_keys(id,agent_id,key_id,key_version,public_key,public_key_format,
17
+ fingerprint,status,proof_state,created_at,metadata)
18
+ values ('61100000-0000-0000-0000-000000000011','61100000-0000-0000-0000-000000000001',
19
+ 'historical-backfill',1,public.test_pubkey('sid-historical-backfill'),'raw_ed25519_base64url',
20
+ agent_board._raw_key_fingerprint(public.test_pubkey('sid-historical-backfill')),'pending_proof','unproven',
21
+ '2026-01-01 00:00:00+00','{"sid_backfill":"historical-key"}');
22
+ insert into agent_board.boards(id,slug,name,created_by_agent_id,created_at)
23
+ values ('61100000-0000-0000-0000-000000000012','sid-historical-backfill','Historical backfill',
24
+ '61100000-0000-0000-0000-000000000001','2026-01-01 00:00:00+00');
25
+ insert into agent_board.board_membership_events(id,board_id,agent_id,event_kind,result_state,provenance,created_at)
26
+ values ('61100000-0000-0000-0000-000000000013','61100000-0000-0000-0000-000000000012',
27
+ '61100000-0000-0000-0000-000000000001','historical-placement','joined','operator-designated','2026-01-01 00:00:00+00');
28
+
29
+ insert into agent_board.agents (
30
+ id, agent_type, kind, handle, display_name, status, parent_agent_id, root_agent_id, owner_user_id
31
+ ) values (
32
+ '61100000-0000-0000-0000-000000000002',
33
+ 'agent', 'agent', 'sid-backfill-child', 'SID Backfill Child', 'active',
34
+ '61100000-0000-0000-0000-000000000001',
35
+ '61100000-0000-0000-0000-000000000001',
36
+ '61100000-0000-0000-0000-000000000010'
37
+ );
38
+
39
+ insert into agent_board.agents (
40
+ id, agent_type, kind, handle, display_name, status, parent_agent_id, root_agent_id, owner_user_id
41
+ ) values (
42
+ '61100000-0000-0000-0000-000000000003',
43
+ 'agent', 'agent', 'sid-backfill-grandchild', 'SID Backfill Grandchild', 'active',
44
+ '61100000-0000-0000-0000-000000000002',
45
+ '61100000-0000-0000-0000-000000000001',
46
+ '61100000-0000-0000-0000-000000000010'
47
+ );