@consiliency/agent-board-schema 1.50.0 → 1.52.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 (45) hide show
  1. package/fixtures/already-applied.json +4 -0
  2. package/migrations/20261002000100_agent_board_session_challenge_supersede.sql +383 -0
  3. package/package.json +1 -1
  4. package/schema-manifest.json +12 -3
  5. package/src/tooling.test.mjs +10 -2
  6. package/tests/agent_board_agent_liveness.test.sql +3 -1
  7. package/tests/agent_board_atomic_fleet_onboarding.test.sql +33 -36
  8. package/tests/agent_board_authbound_boundary.test.sql +4 -3
  9. package/tests/agent_board_board_claim_policy.test.sql +17 -6
  10. package/tests/agent_board_claim_subject_gate.test.sql +7 -8
  11. package/tests/agent_board_claimkey.test.sql +9 -6
  12. package/tests/agent_board_claimpolicy_race.test.sql +1466 -342
  13. package/tests/agent_board_consent_emission.test.sql +5 -4
  14. package/tests/agent_board_consent_gating.test.sql +20 -9
  15. package/tests/agent_board_enroll_resumption.test.sql +1 -1
  16. package/tests/agent_board_enrollment_audit_content.test.sql +5 -4
  17. package/tests/agent_board_federated_identity_injective_slug.test.sql +3 -1
  18. package/tests/agent_board_federated_lock_order_class.test.sql +69 -52
  19. package/tests/agent_board_fedsession.test.sql +3 -1
  20. package/tests/agent_board_fedsession_provider_absence_gate.test.sql +9 -4
  21. package/tests/agent_board_fedsession_stored_identity_gate.test.sql +18 -6
  22. package/tests/agent_board_fk_lock_edges.test.sql +84 -5
  23. package/tests/agent_board_fleet_domain_reclaim.test.sql +2 -4
  24. package/tests/agent_board_fleet_machine_hijack.test.sql +2 -4
  25. package/tests/agent_board_fleet_machine_reactivation.test.sql +5 -1
  26. package/tests/agent_board_fleet_machine_reclaim.test.sql +2 -4
  27. package/tests/agent_board_invite_no_downgrade.test.sql +24 -18
  28. package/tests/agent_board_ledger_authority.test.sql +29 -21
  29. package/tests/agent_board_mint_harden.test.sql +5 -5
  30. package/tests/agent_board_outbox_wake_ordering.test.sql +4 -3
  31. package/tests/agent_board_ownerless_renew_quota.test.sql +7 -2
  32. package/tests/agent_board_portal_offline_invariant.test.sql +5 -4
  33. package/tests/agent_board_receipt_subject_gate.test.sql +7 -8
  34. package/tests/agent_board_rolereset.test.sql +5 -3
  35. package/tests/agent_board_rowid.test.sql +34 -15
  36. package/tests/agent_board_signer_lifecycle.test.sql +24 -13
  37. package/tests/agent_board_signing_challenge_pending_cap.test.sql +391 -36
  38. package/tests/agent_board_source_strip_helper.test.sql +2 -1
  39. package/tests/agent_board_task_event_origin_posture.test.sql +21 -9
  40. package/tests/agent_board_wake_recovery.test.sql +53 -26
  41. package/tests/agent_board_write_enforcement_class.test.sql +130 -12
  42. package/tests/catalog/function_execute_privileges.json +1923 -0
  43. package/tests/fixtures/test-prereqs.sql +644 -237
  44. package/tests/isolation/expected/challenge_supersede_lock_order.out +213 -0
  45. package/tests/isolation/specs/challenge_supersede_lock_order.spec +183 -0
@@ -0,0 +1,213 @@
1
+ Parsed test spec with 3 sessions
2
+
3
+ starting permutation: mint_begin mint_lock_challenge issue_begin issue_at_cap mint_lock_key issue_commit mint_commit check
4
+ step mint_begin: BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role';
5
+ step mint_lock_challenge:
6
+ select count(*)::int as locked_challenges from (
7
+ select 1 from agent_board.agent_signing_key_challenges c
8
+ where c.id = 'f1c00000-0000-4000-8000-000000000001' for update) s;
9
+
10
+ locked_challenges
11
+ -----------------
12
+ 1
13
+ (1 row)
14
+
15
+ step issue_begin: BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role';
16
+ step issue_at_cap:
17
+ select c.key_id, c.key_version
18
+ from agent_board.issue_signing_key_challenge(
19
+ 'f1a00000-0000-4000-8000-000000000001', 'primary', interval '5 minutes') c;
20
+
21
+ key_id |key_version
22
+ -------+-----------
23
+ primary| 1
24
+ (1 row)
25
+
26
+ step mint_lock_key:
27
+ select count(*)::int as locked_keys from (
28
+ select 1 from agent_board.agent_signing_keys k
29
+ where k.id = 'f1b00000-0000-4000-8000-000000000001' for update) s;
30
+ <waiting ...>
31
+ step issue_commit: COMMIT;
32
+ step mint_lock_key: <... completed>
33
+ locked_keys
34
+ -----------
35
+ 1
36
+ (1 row)
37
+
38
+ step mint_commit: COMMIT;
39
+ step check:
40
+ select
41
+ (select proof_state from agent_board.agent_signing_key_challenges
42
+ where id = 'f1c00000-0000-4000-8000-000000000001') as oldest_state,
43
+ (select proof_state from agent_board.agent_signing_key_challenges
44
+ where id = 'f1c00000-0000-4000-8000-000000000002') as second_oldest_state,
45
+ (select count(*)::int from agent_board.agent_signing_key_challenges
46
+ where signing_key_id = 'f1b00000-0000-4000-8000-000000000001'
47
+ and metadata ? 'superseded_by') as evicted_rows,
48
+ (select count(*)::int from agent_board.agent_signing_key_challenges
49
+ where signing_key_id = 'f1b00000-0000-4000-8000-000000000001'
50
+ and proof_state = 'pending' and consumed_at is null
51
+ and expires_at > now()) as pending_rows;
52
+
53
+ oldest_state|second_oldest_state|evicted_rows|pending_rows
54
+ ------------+-------------------+------------+------------
55
+ pending |expired | 1| 16
56
+ (1 row)
57
+
58
+
59
+ starting permutation: issue_begin issue_at_cap mint_begin mint_lock_challenge issue_commit mint_lock_key mint_commit check
60
+ step issue_begin: BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role';
61
+ step issue_at_cap:
62
+ select c.key_id, c.key_version
63
+ from agent_board.issue_signing_key_challenge(
64
+ 'f1a00000-0000-4000-8000-000000000001', 'primary', interval '5 minutes') c;
65
+
66
+ key_id |key_version
67
+ -------+-----------
68
+ primary| 1
69
+ (1 row)
70
+
71
+ step mint_begin: BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role';
72
+ step mint_lock_challenge:
73
+ select count(*)::int as locked_challenges from (
74
+ select 1 from agent_board.agent_signing_key_challenges c
75
+ where c.id = 'f1c00000-0000-4000-8000-000000000001' for update) s;
76
+ <waiting ...>
77
+ step issue_commit: COMMIT;
78
+ step mint_lock_challenge: <... completed>
79
+ locked_challenges
80
+ -----------------
81
+ 1
82
+ (1 row)
83
+
84
+ step mint_lock_key:
85
+ select count(*)::int as locked_keys from (
86
+ select 1 from agent_board.agent_signing_keys k
87
+ where k.id = 'f1b00000-0000-4000-8000-000000000001' for update) s;
88
+
89
+ locked_keys
90
+ -----------
91
+ 1
92
+ (1 row)
93
+
94
+ step mint_commit: COMMIT;
95
+ step check:
96
+ select
97
+ (select proof_state from agent_board.agent_signing_key_challenges
98
+ where id = 'f1c00000-0000-4000-8000-000000000001') as oldest_state,
99
+ (select proof_state from agent_board.agent_signing_key_challenges
100
+ where id = 'f1c00000-0000-4000-8000-000000000002') as second_oldest_state,
101
+ (select count(*)::int from agent_board.agent_signing_key_challenges
102
+ where signing_key_id = 'f1b00000-0000-4000-8000-000000000001'
103
+ and metadata ? 'superseded_by') as evicted_rows,
104
+ (select count(*)::int from agent_board.agent_signing_key_challenges
105
+ where signing_key_id = 'f1b00000-0000-4000-8000-000000000001'
106
+ and proof_state = 'pending' and consumed_at is null
107
+ and expires_at > now()) as pending_rows;
108
+
109
+ oldest_state|second_oldest_state|evicted_rows|pending_rows
110
+ ------------+-------------------+------------+------------
111
+ expired |pending | 1| 16
112
+ (1 row)
113
+
114
+
115
+ starting permutation: issue_begin issue_at_cap issue_commit mint_begin mint_lock_challenge mint_lock_key mint_commit check
116
+ step issue_begin: BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role';
117
+ step issue_at_cap:
118
+ select c.key_id, c.key_version
119
+ from agent_board.issue_signing_key_challenge(
120
+ 'f1a00000-0000-4000-8000-000000000001', 'primary', interval '5 minutes') c;
121
+
122
+ key_id |key_version
123
+ -------+-----------
124
+ primary| 1
125
+ (1 row)
126
+
127
+ step issue_commit: COMMIT;
128
+ step mint_begin: BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role';
129
+ step mint_lock_challenge:
130
+ select count(*)::int as locked_challenges from (
131
+ select 1 from agent_board.agent_signing_key_challenges c
132
+ where c.id = 'f1c00000-0000-4000-8000-000000000001' for update) s;
133
+
134
+ locked_challenges
135
+ -----------------
136
+ 1
137
+ (1 row)
138
+
139
+ step mint_lock_key:
140
+ select count(*)::int as locked_keys from (
141
+ select 1 from agent_board.agent_signing_keys k
142
+ where k.id = 'f1b00000-0000-4000-8000-000000000001' for update) s;
143
+
144
+ locked_keys
145
+ -----------
146
+ 1
147
+ (1 row)
148
+
149
+ step mint_commit: COMMIT;
150
+ step check:
151
+ select
152
+ (select proof_state from agent_board.agent_signing_key_challenges
153
+ where id = 'f1c00000-0000-4000-8000-000000000001') as oldest_state,
154
+ (select proof_state from agent_board.agent_signing_key_challenges
155
+ where id = 'f1c00000-0000-4000-8000-000000000002') as second_oldest_state,
156
+ (select count(*)::int from agent_board.agent_signing_key_challenges
157
+ where signing_key_id = 'f1b00000-0000-4000-8000-000000000001'
158
+ and metadata ? 'superseded_by') as evicted_rows,
159
+ (select count(*)::int from agent_board.agent_signing_key_challenges
160
+ where signing_key_id = 'f1b00000-0000-4000-8000-000000000001'
161
+ and proof_state = 'pending' and consumed_at is null
162
+ and expires_at > now()) as pending_rows;
163
+
164
+ oldest_state|second_oldest_state|evicted_rows|pending_rows
165
+ ------------+-------------------+------------+------------
166
+ expired |pending | 1| 16
167
+ (1 row)
168
+
169
+
170
+ starting permutation: mint_begin mint_lock_all issue_begin issue_at_cap issue_commit mint_commit check
171
+ step mint_begin: BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role';
172
+ step mint_lock_all:
173
+ select count(*)::int as locked_challenges from (
174
+ select 1 from agent_board.agent_signing_key_challenges c
175
+ where c.signing_key_id = 'f1b00000-0000-4000-8000-000000000001'
176
+ and c.proof_state = 'pending'
177
+ and c.consumed_at is null
178
+ order by c.id
179
+ for update) s;
180
+
181
+ locked_challenges
182
+ -----------------
183
+ 16
184
+ (1 row)
185
+
186
+ step issue_begin: BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role';
187
+ step issue_at_cap:
188
+ select c.key_id, c.key_version
189
+ from agent_board.issue_signing_key_challenge(
190
+ 'f1a00000-0000-4000-8000-000000000001', 'primary', interval '5 minutes') c;
191
+
192
+ ERROR: challenge_pending_cap: too many pending signing-key challenges for this key (max 16)
193
+ step issue_commit: COMMIT;
194
+ step mint_commit: COMMIT;
195
+ step check:
196
+ select
197
+ (select proof_state from agent_board.agent_signing_key_challenges
198
+ where id = 'f1c00000-0000-4000-8000-000000000001') as oldest_state,
199
+ (select proof_state from agent_board.agent_signing_key_challenges
200
+ where id = 'f1c00000-0000-4000-8000-000000000002') as second_oldest_state,
201
+ (select count(*)::int from agent_board.agent_signing_key_challenges
202
+ where signing_key_id = 'f1b00000-0000-4000-8000-000000000001'
203
+ and metadata ? 'superseded_by') as evicted_rows,
204
+ (select count(*)::int from agent_board.agent_signing_key_challenges
205
+ where signing_key_id = 'f1b00000-0000-4000-8000-000000000001'
206
+ and proof_state = 'pending' and consumed_at is null
207
+ and expires_at > now()) as pending_rows;
208
+
209
+ oldest_state|second_oldest_state|evicted_rows|pending_rows
210
+ ------------+-------------------+------------+------------
211
+ pending |pending | 0| 16
212
+ (1 row)
213
+
@@ -0,0 +1,183 @@
1
+ # F1 (code-review-2026-09-01) — issue_signing_key_challenge must be ACYCLIC against
2
+ # mint_fleet_agent_session once it supersedes at the pending-challenge cap.
3
+ #
4
+ # THE CYCLE THIS CHANGE WOULD HAVE INTRODUCED. Superseding at the cap makes
5
+ # issue_signing_key_challenge WRITE a challenge row while it already holds the key row:
6
+ # agent_signing_keys(FOR UPDATE, 20260719000100 / this migration)
7
+ # -> agent_signing_key_challenges(UPDATE, the eviction)
8
+ # mint_fleet_agent_session takes exactly that pair in the OPPOSITE order:
9
+ # agent_signing_key_challenges(FOR UPDATE, 20260718000600:73-76)
10
+ # -> agent_signing_keys(FOR UPDATE, 20260718000600:95-100)
11
+ # That is an ABBA between two honest callers, and not a rare one: the traffic this change
12
+ # is FOR — a flood driving evictions while legitimate agents mint — is precisely the
13
+ # traffic that produces it. agent_board_fk_lock_edges.test.sql FKL-10 named the pair the
14
+ # first time this migration was written without the fix; that assertion is a static
15
+ # detector and cannot see the fix, which is why the proof lives here.
16
+ #
17
+ # THE FIX this spec pins: the eviction's row selection carries FOR UPDATE SKIP LOCKED, so
18
+ # the issuing transaction NEVER WAITS on a challenge row. A row a mint already holds is
19
+ # passed over for the next-oldest. A transaction that cannot wait cannot close a cycle.
20
+ #
21
+ # WHAT THE GOLDEN SHOWS, AND WHAT WOULD CHANGE IF THE CLAUSE WERE REMOVED. In permutation
22
+ # 1 `mint_lock_challenge` takes the OLDEST pending row; `issue` then runs at the cap and
23
+ # COMPLETES WITHOUT A `<waiting ...>` LINE, evicting the SECOND-oldest instead. Delete
24
+ # `skip locked` and that step blocks — the trace gains `<waiting ...>`, and once
25
+ # `mint_lock_key` runs the two transactions are in a cycle and the server reports
26
+ # `ERROR: deadlock detected`. Either outcome is a byte diff against this golden.
27
+ #
28
+ # THE "mint" SESSION IS A LOCK-EQUIVALENT STAND-IN, and here is the equivalence.
29
+ # * It runs mint_fleet_agent_session's two cycle-relevant acquisitions, in its order and
30
+ # its modes: the challenge row FOR UPDATE (:73-76), then the key row FOR UPDATE
31
+ # (:95-100).
32
+ # * It omits that function's other work — _agent_principal_active's reads, the
33
+ # delegation_grants lookup, the auth_user_id read, the challenge consume UPDATE and the
34
+ # nonfleet_sessions insert. None of those touches agent_signing_keys, and the consume
35
+ # UPDATE re-writes a row this session already holds, so none can add an edge to the
36
+ # pair under test.
37
+ # * A real mint call was rejected only because it needs a valid PoP challenge, an
38
+ # auth_user_id-bearing principal and a live lineage inside a setup block, and because
39
+ # every one of its rejection paths RAISES, which would abort the session mid-permutation
40
+ # and turn a lock-order question into an error-message question. The claim under test is
41
+ # a LOCK ORDER, and these are the locks.
42
+ #
43
+ # THE ISSUE SESSION IS THE REAL FUNCTION, unmodified. That side is where the fix lives, so
44
+ # a stand-in there would be testing the spec instead of the schema.
45
+
46
+ setup
47
+ {
48
+ insert into agent_board.agents(id, agent_type, kind, handle, display_name, status)
49
+ values ('f1a00000-0000-4000-8000-000000000001','agent','agent','f1iso-owner','F1 ISO Owner','active');
50
+
51
+ insert into agent_board.agent_signing_keys(
52
+ id, agent_id, key_id, key_version, public_key, public_key_format, fingerprint, status, proof_state)
53
+ values ('f1b00000-0000-4000-8000-000000000001','f1a00000-0000-4000-8000-000000000001',
54
+ 'primary', 1, public.test_pubkey('f1-iso'), 'raw_ed25519_base64url',
55
+ public.test_key_fingerprint('f1-iso'), 'active', 'proven');
56
+
57
+ -- Exactly sixteen pending challenges: the key is AT the cap, so the next issue must
58
+ -- evict. created_at is set explicitly so "oldest" and "second-oldest" are unambiguous
59
+ -- rather than depending on insert clock resolution.
60
+ insert into agent_board.agent_signing_key_challenges(
61
+ id, signing_key_id, agent_id, challenge_nonce, challenge_bytes, expires_at, created_at)
62
+ select
63
+ ('f1c00000-0000-4000-8000-0000000000' || lpad(i::text, 2, '0'))::uuid,
64
+ 'f1b00000-0000-4000-8000-000000000001',
65
+ 'f1a00000-0000-4000-8000-000000000001',
66
+ 'f1-iso-nonce-' || i,
67
+ 'agent-signing-key-proof.v1:f1-iso:' || i,
68
+ now() + interval '5 minutes',
69
+ now() - make_interval(secs => (17 - i))
70
+ from generate_series(1, 16) i;
71
+ }
72
+
73
+ teardown
74
+ {
75
+ delete from agent_board.agent_signing_key_challenges
76
+ where agent_id = 'f1a00000-0000-4000-8000-000000000001';
77
+ delete from agent_board.agent_signing_keys
78
+ where agent_id = 'f1a00000-0000-4000-8000-000000000001';
79
+ delete from agent_board.agents where handle like 'f1iso-%';
80
+ }
81
+
82
+ # mint_fleet_agent_session's two cycle-relevant locks, in its order and modes.
83
+ session "mint"
84
+ step "mint_begin" { BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role'; }
85
+ # The OLDEST pending row — the one the eviction would otherwise take.
86
+ step "mint_lock_challenge" {
87
+ select count(*)::int as locked_challenges from (
88
+ select 1 from agent_board.agent_signing_key_challenges c
89
+ where c.id = 'f1c00000-0000-4000-8000-000000000001' for update) s;
90
+ }
91
+ # ALL sixteen pending rows at once. This is the adversary for the no-evictable-row branch:
92
+ # SKIP LOCKED can only skip, so with every candidate held the eviction finds nothing.
93
+ step "mint_lock_all" {
94
+ select count(*)::int as locked_challenges from (
95
+ select 1 from agent_board.agent_signing_key_challenges c
96
+ where c.signing_key_id = 'f1b00000-0000-4000-8000-000000000001'
97
+ and c.proof_state = 'pending'
98
+ and c.consumed_at is null
99
+ order by c.id
100
+ for update) s;
101
+ }
102
+ step "mint_lock_key" {
103
+ select count(*)::int as locked_keys from (
104
+ select 1 from agent_board.agent_signing_keys k
105
+ where k.id = 'f1b00000-0000-4000-8000-000000000001' for update) s;
106
+ }
107
+ step "mint_commit" { COMMIT; }
108
+
109
+ # The real issuer, unmodified, at the cap. Only the deterministic columns are selected:
110
+ # challenge_id and the nonce inside `challenge` are fresh random values per run and must
111
+ # never reach a golden.
112
+ session "issue"
113
+ step "issue_begin" { BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role'; }
114
+ step "issue_at_cap" {
115
+ select c.key_id, c.key_version
116
+ from agent_board.issue_signing_key_challenge(
117
+ 'f1a00000-0000-4000-8000-000000000001', 'primary', interval '5 minutes') c;
118
+ }
119
+ step "issue_commit" { COMMIT; }
120
+
121
+ session "check"
122
+ # WHICH row was evicted is the observable that distinguishes skipping from waiting: with
123
+ # SKIP LOCKED the mint-held oldest row is untouched and the SECOND-oldest carries the
124
+ # marker. The pending count proves the bound still holds.
125
+ step "check" {
126
+ select
127
+ (select proof_state from agent_board.agent_signing_key_challenges
128
+ where id = 'f1c00000-0000-4000-8000-000000000001') as oldest_state,
129
+ (select proof_state from agent_board.agent_signing_key_challenges
130
+ where id = 'f1c00000-0000-4000-8000-000000000002') as second_oldest_state,
131
+ (select count(*)::int from agent_board.agent_signing_key_challenges
132
+ where signing_key_id = 'f1b00000-0000-4000-8000-000000000001'
133
+ and metadata ? 'superseded_by') as evicted_rows,
134
+ (select count(*)::int from agent_board.agent_signing_key_challenges
135
+ where signing_key_id = 'f1b00000-0000-4000-8000-000000000001'
136
+ and proof_state = 'pending' and consumed_at is null
137
+ and expires_at > now()) as pending_rows;
138
+ }
139
+
140
+ # 1. THE CYCLE, in the order that would produce the deadlock. mint holds the oldest
141
+ # challenge row; issue then runs at the cap. It must NOT wait (no `<waiting ...>`), and
142
+ # mint's key lock must then be the only thing that blocks.
143
+ permutation "mint_begin" "mint_lock_challenge" "issue_begin" "issue_at_cap" "mint_lock_key" "issue_commit" "mint_commit" "check"
144
+
145
+ # 2. The reverse arrival order: issue arrives FIRST, evicts the true oldest, and mint then
146
+ # blocks on that row and proceeds once issue commits. This is the direction in which the
147
+ # eviction DOES cost a mint a wait, and it pins that the cost is a wait-then-proceed and
148
+ # not a deadlock.
149
+ #
150
+ # `issue_commit` MUST precede `mint_lock_key`. The first version of this permutation put
151
+ # the commit last, which does not test the schema at all: mint blocks on the challenge
152
+ # row, isolationtester will not run a blocked session's next step, and nothing in the
153
+ # permutation can release the lock — so the tester sat out its own 300-second cancel and
154
+ # baked `canceling step mint_lock_challenge after 300 seconds` into the golden. That is a
155
+ # property of the spec's step order, not of the lock order, and it made every iteration
156
+ # cost five minutes.
157
+ permutation "issue_begin" "issue_at_cap" "mint_begin" "mint_lock_challenge" "issue_commit" "mint_lock_key" "mint_commit" "check"
158
+
159
+ # 3. Fully serial: issue commits before mint starts. The baseline the two concurrent
160
+ # permutations are read against.
161
+ permutation "issue_begin" "issue_at_cap" "issue_commit" "mint_begin" "mint_lock_challenge" "mint_lock_key" "mint_commit" "check"
162
+
163
+ # 4. EVERY candidate locked -- the branch that exists only because SKIP LOCKED can skip.
164
+ #
165
+ # With all sixteen pending rows held by another session the eviction's
166
+ # `for update skip locked limit 1` returns NO ROW, so `v_evicted` is null. The function
167
+ # must then REFUSE (challenge_pending_cap) rather than issue: inserting a seventeenth
168
+ # would push the pending set past the bound the cap exists to hold, and the count that
169
+ # said the set was full was correct. This is the ONE path where the pre-change refusal
170
+ # survives, and the PR describes it as "confined to a window in which sixteen mints for
171
+ # one key are in flight at once" -- a claim that was visible in the SQL and asserted
172
+ # nowhere until this permutation.
173
+ #
174
+ # Three things the golden shows, and all three matter:
175
+ # * issue_at_cap ERRORS. It does not wait -- SKIP LOCKED means no `<waiting ...>`.
176
+ # * `evicted_rows = 0`. Nothing was superseded, so no legitimate challenge was
177
+ # destroyed on the way to refusing.
178
+ # * `pending_rows = 16`. No seventeenth row was inserted.
179
+ #
180
+ # MUTATION: delete the `if v_evicted is null then raise` guard and this permutation's
181
+ # golden changes -- the refusal disappears and pending_rows becomes 17, which is the
182
+ # bound being exceeded, i.e. exactly the failure the guard is there to prevent.
183
+ permutation "mint_begin" "mint_lock_all" "issue_begin" "issue_at_cap" "issue_commit" "mint_commit" "check"