@consiliency/agent-board-schema 1.44.0 → 1.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fixtures/already-applied.json +16 -0
- package/migrations/20260925000100_agent_board_ownerless_renew_ttl_cap.sql +166 -0
- package/migrations/20260926000100_agent_board_ownerless_quota_census.sql +268 -0
- package/migrations/20260927000100_agent_board_fleet_domain_no_downgrade.sql +393 -0
- package/migrations/20260928000100_agent_board_ownerless_quota_renew_convergence.sql +562 -0
- package/package.json +1 -1
- package/schema-manifest.json +39 -3
- package/src/tooling.test.mjs +35 -2
- package/tests/agent_board_fleet_machine_hijack.test.sql +270 -1
- package/tests/agent_board_fleet_machine_reclaim.test.sql +28 -2
- package/tests/agent_board_ownerless_quota_census.test.sql +400 -0
- package/tests/agent_board_ownerless_renew_quota.test.sql +551 -0
- package/tests/agent_board_ownerless_renew_ttl.test.sql +226 -0
- package/tests/catalog/p_metadata_parameters.json +12 -0
- package/tests/isolation/expected/fleet_domain_no_downgrade_demote_race.out +447 -0
- package/tests/isolation/specs/fleet_domain_no_downgrade_demote_race.spec +292 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# #397 — register_fleet_machine's no-downgrade guard (#395) against a destination DEMOTED while
|
|
2
|
+
# the move waits.
|
|
3
|
+
#
|
|
4
|
+
# THE WINDOW. The guard's rank comparison is a conjunct of the ON CONFLICT DO UPDATE's WHERE, so
|
|
5
|
+
# it is atomic against the conflicting MACHINE row. The DESTINATION domain's lifecycle is not:
|
|
6
|
+
# pinning it needs a row lock on fleet_trust_domains, which was added to this function once
|
|
7
|
+
# (PR #158, ab38805) and REVERTED for deadlocking 40P01 against reclaim_fleet_machine — #159 owns
|
|
8
|
+
# the lock order and is open. So a `service_role` demotion of the destination can commit while the
|
|
9
|
+
# upsert is blocked on the machine row, and the machine lands in a domain that is 'pending' at
|
|
10
|
+
# commit time, still 'active' itself, with its fleet_machine_agents bindings intact (they key on
|
|
11
|
+
# machine_id) — #395 again, under concurrency.
|
|
12
|
+
#
|
|
13
|
+
# WHAT WAS MEASURED, and why the obvious repair is not the one shipped. Moving the destination
|
|
14
|
+
# read INTO the upsert as a correlated subquery does NOT close this: under READ COMMITTED an
|
|
15
|
+
# upsert that waits on the conflicting row re-reads THAT ROW's latest version on resume but
|
|
16
|
+
# evaluates every other relation against the statement's ORIGINAL snapshot, so the domain is still
|
|
17
|
+
# read as of before the wait. Both the cached read and the correlated subquery were run against
|
|
18
|
+
# this interleaving and both allowed the move. What closes it is a re-check of the destination in
|
|
19
|
+
# a SEPARATE statement after the move — a fresh snapshot — which raises 42501 and unwinds it.
|
|
20
|
+
#
|
|
21
|
+
# P1 — the pin. rl locks the machine row and holds it; rm's register BLOCKS (isolationtester
|
|
22
|
+
# prints `<waiting ...>`); rl then demotes the destination to 'pending' and commits; rm resumes,
|
|
23
|
+
# its upsert applies the move against the row it now locks, and the post-move re-check — a fresh
|
|
24
|
+
# statement, fresh snapshot — sees 'pending' and RAISES 42501, aborting rm. FINAL: the machine is
|
|
25
|
+
# still in its ORIGINAL active domain.
|
|
26
|
+
#
|
|
27
|
+
# P2 — the twin. The same move with NO concurrent demotion SUCCEEDS and lands. Without it, a guard
|
|
28
|
+
# that refused every move would satisfy P1 and this spec would be scoring an over-refusal as a
|
|
29
|
+
# fix. P2 leaves the machine in the destination domain, so it runs after P1's fixture reset (each
|
|
30
|
+
# permutation re-runs setup).
|
|
31
|
+
#
|
|
32
|
+
# WHAT EACH PERMUTATION PINS, and the mutation that falsifies it. Every refusal case carries a twin
|
|
33
|
+
# so a guard that refused everything could not pass this spec.
|
|
34
|
+
#
|
|
35
|
+
# P1 / P2 destination demoted during the wait → refused; uncontended move → succeeds
|
|
36
|
+
# P3 / P3-twin a concurrent session RELOCATES the machine, then demotes → refused;
|
|
37
|
+
# the relocation alone → the equal-rank move succeeds
|
|
38
|
+
# P4 / P4-twin the LOCK HOLDER relocates, then demotes → refused; relocation alone → succeeds
|
|
39
|
+
# P5 / P5-reverse register and reclaim serialize on the machine row in both starting orders
|
|
40
|
+
# (see the rc session for what these do NOT show)
|
|
41
|
+
# P6 / P6-promote / the first-registration race: the loser reaches the conflict path with no row
|
|
42
|
+
# P6-control to lock, so it is REFUSED 40001 whether the destination is demoted (P6) or the
|
|
43
|
+
# SOURCE is promoted (P6-promote); serialized, the same two registrations
|
|
44
|
+
# succeed (P6-control)
|
|
45
|
+
#
|
|
46
|
+
# MUTATION SIGNALS, all four measured:
|
|
47
|
+
# * delete the post-move comparison → P1, P3, P4 no longer raise; the checker shows the
|
|
48
|
+
# machine 'active' in a 'pending' domain
|
|
49
|
+
# * remove the pre-read's FOR UPDATE → P3, P4 no longer raise (the stale-pre-read bypass)
|
|
50
|
+
# * delete the lost-race (40001) refusal → P6, P6-promote no longer raise
|
|
51
|
+
# * inject the #158-shaped fleet_trust_domains
|
|
52
|
+
# FOR UPDATE into register → P1, P3, P4 become `ERROR: deadlock detected`.
|
|
53
|
+
# THIS is the evidence that the machine-only lock avoids ab38805's revert; P5/P5-reverse stay
|
|
54
|
+
# GREEN under that injection and were mis-cited for it in an earlier round.
|
|
55
|
+
|
|
56
|
+
setup
|
|
57
|
+
{
|
|
58
|
+
insert into agent_board.agents(id, agent_type, handle, display_name, status) values
|
|
59
|
+
('a3970000-0000-4000-8000-000000000001', 'agent', 'nodowngrade-iso-owner', 'No-downgrade Iso Owner', 'active'),
|
|
60
|
+
('a3970000-0000-4000-8000-000000000002', 'agent', 'nodowngrade-iso-operator', 'No-downgrade Iso Operator', 'active'),
|
|
61
|
+
('a3970000-0000-4000-8000-000000000003', 'agent', 'nodowngrade-iso-newowner', 'No-downgrade Iso New Owner', 'active');
|
|
62
|
+
|
|
63
|
+
-- Two ACTIVE domains owned by that agent: the machine's current one, and the move's destination.
|
|
64
|
+
insert into agent_board.fleet_trust_domains(id, slug, display_name, domain_kind, owner_agent_id, lifecycle_state, approved_at)
|
|
65
|
+
values ('d3970000-0000-4000-8000-000000000001', 'nodowngrade-iso-current', 'No-downgrade Iso Current', 'tailnet',
|
|
66
|
+
'a3970000-0000-4000-8000-000000000001', 'active', now()),
|
|
67
|
+
('d3970000-0000-4000-8000-000000000002', 'nodowngrade-iso-dest', 'No-downgrade Iso Destination', 'tailnet',
|
|
68
|
+
'a3970000-0000-4000-8000-000000000001', 'active', now()),
|
|
69
|
+
-- X: a third ACTIVE domain the concurrent session RELOCATES the machine into (#397 round 2).
|
|
70
|
+
('d3970000-0000-4000-8000-000000000003', 'nodowngrade-iso-x', 'No-downgrade Iso X', 'tailnet',
|
|
71
|
+
'a3970000-0000-4000-8000-000000000001', 'active', now()),
|
|
72
|
+
-- The reclaim probe's destination: active, owned by the arbitrated NEW owner.
|
|
73
|
+
('d3970000-0000-4000-8000-000000000004', 'nodowngrade-iso-newdom', 'No-downgrade Iso New Owner Domain', 'tailnet',
|
|
74
|
+
'a3970000-0000-4000-8000-000000000003', 'active', now()),
|
|
75
|
+
-- P6 family: the first-registration race runs between two PENDING domains, which is the
|
|
76
|
+
-- shape the source-promotion case needs (#397 round 3, finding 1).
|
|
77
|
+
('d3970000-0000-4000-8000-000000000005', 'nodowngrade-iso-race-src', 'No-downgrade Iso Race Source', 'tailnet',
|
|
78
|
+
'a3970000-0000-4000-8000-000000000001', 'pending', null),
|
|
79
|
+
('d3970000-0000-4000-8000-000000000006', 'nodowngrade-iso-race-dest', 'No-downgrade Iso Race Destination', 'tailnet',
|
|
80
|
+
'a3970000-0000-4000-8000-000000000001', 'pending', null);
|
|
81
|
+
|
|
82
|
+
-- The bound-shaped machine the move would relocate. Inserted directly so its id is fixed.
|
|
83
|
+
insert into agent_board.fleet_machines(id, trust_domain_id, owner_agent_id, machine_label, machine_fingerprint,
|
|
84
|
+
lifecycle_state, enrollment_source, enrolled_at, metadata)
|
|
85
|
+
values ('c3970000-0000-4000-8000-000000000001', 'd3970000-0000-4000-8000-000000000001',
|
|
86
|
+
'a3970000-0000-4000-8000-000000000001', 'nodowngrade-iso-machine', 'sha256:nodowngrade-iso',
|
|
87
|
+
'active', 'portal', now(), jsonb_build_object('redacted', true, 'metadata-safe', true));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
teardown
|
|
91
|
+
{
|
|
92
|
+
delete from agent_board.fleet_machines where id = 'c3970000-0000-4000-8000-000000000001';
|
|
93
|
+
delete from agent_board.fleet_machines where machine_fingerprint = 'sha256:nodowngrade-iso-fresh';
|
|
94
|
+
delete from agent_board.fleet_trust_domains where id in (
|
|
95
|
+
'd3970000-0000-4000-8000-000000000001',
|
|
96
|
+
'd3970000-0000-4000-8000-000000000002',
|
|
97
|
+
'd3970000-0000-4000-8000-000000000003',
|
|
98
|
+
'd3970000-0000-4000-8000-000000000004',
|
|
99
|
+
'd3970000-0000-4000-8000-000000000005',
|
|
100
|
+
'd3970000-0000-4000-8000-000000000006');
|
|
101
|
+
delete from agent_board.agents where id in (
|
|
102
|
+
'a3970000-0000-4000-8000-000000000001',
|
|
103
|
+
'a3970000-0000-4000-8000-000000000002',
|
|
104
|
+
'a3970000-0000-4000-8000-000000000003');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# rl: holds the machine row (any concurrent writer of that row will do — reclaim, revoke and
|
|
108
|
+
# reactivate all take it) and demotes the destination domain before releasing it.
|
|
109
|
+
session "rl"
|
|
110
|
+
step "rl_begin" { BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role'; }
|
|
111
|
+
step "rl_lock" {
|
|
112
|
+
select id from agent_board.fleet_machines
|
|
113
|
+
where id = 'c3970000-0000-4000-8000-000000000001' for update;
|
|
114
|
+
}
|
|
115
|
+
step "rl_demote" {
|
|
116
|
+
update agent_board.fleet_trust_domains
|
|
117
|
+
set lifecycle_state = 'pending', approved_at = null, updated_at = now()
|
|
118
|
+
where id = 'd3970000-0000-4000-8000-000000000002';
|
|
119
|
+
}
|
|
120
|
+
step "rl_relocate_dest" {
|
|
121
|
+
update agent_board.fleet_machines
|
|
122
|
+
set trust_domain_id = 'd3970000-0000-4000-8000-000000000002', updated_at = now()
|
|
123
|
+
where id = 'c3970000-0000-4000-8000-000000000001';
|
|
124
|
+
}
|
|
125
|
+
step "rl_relocate_x" {
|
|
126
|
+
update agent_board.fleet_machines
|
|
127
|
+
set trust_domain_id = 'd3970000-0000-4000-8000-000000000003', updated_at = now()
|
|
128
|
+
where id = 'c3970000-0000-4000-8000-000000000001';
|
|
129
|
+
}
|
|
130
|
+
step "rl_promote_race_src" {
|
|
131
|
+
update agent_board.fleet_trust_domains
|
|
132
|
+
set lifecycle_state = 'active', approved_at = now(), updated_at = now()
|
|
133
|
+
where id = 'd3970000-0000-4000-8000-000000000005';
|
|
134
|
+
}
|
|
135
|
+
step "rl_demote_race_dest" {
|
|
136
|
+
update agent_board.fleet_trust_domains
|
|
137
|
+
set lifecycle_state = 'quarantined', approved_at = null, updated_at = now()
|
|
138
|
+
where id = 'd3970000-0000-4000-8000-000000000006';
|
|
139
|
+
}
|
|
140
|
+
step "rl_demote_current" {
|
|
141
|
+
update agent_board.fleet_trust_domains
|
|
142
|
+
set lifecycle_state = 'pending', approved_at = null, updated_at = now()
|
|
143
|
+
where id = 'd3970000-0000-4000-8000-000000000001';
|
|
144
|
+
}
|
|
145
|
+
step "rl_commit" { COMMIT; }
|
|
146
|
+
|
|
147
|
+
# rm: the owner re-registering their own fingerprint into the destination domain — the #395 move.
|
|
148
|
+
session "rm"
|
|
149
|
+
step "rm_begin" { BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role'; }
|
|
150
|
+
step "rm_move" {
|
|
151
|
+
select trust_domain_id
|
|
152
|
+
from agent_board.register_fleet_machine(
|
|
153
|
+
'a3970000-0000-4000-8000-000000000001', 'd3970000-0000-4000-8000-000000000002',
|
|
154
|
+
'nodowngrade-iso-machine-moved', 'sha256:nodowngrade-iso', 'portal',
|
|
155
|
+
jsonb_build_object('redacted', true));
|
|
156
|
+
}
|
|
157
|
+
step "rm_move_current" {
|
|
158
|
+
select trust_domain_id
|
|
159
|
+
from agent_board.register_fleet_machine(
|
|
160
|
+
'a3970000-0000-4000-8000-000000000001', 'd3970000-0000-4000-8000-000000000001',
|
|
161
|
+
'nodowngrade-iso-machine-relabelled', 'sha256:nodowngrade-iso', 'portal',
|
|
162
|
+
jsonb_build_object('redacted', true));
|
|
163
|
+
}
|
|
164
|
+
step "rm_commit" { COMMIT; }
|
|
165
|
+
|
|
166
|
+
# rc: the operator reclaim. It locks the machine row and then takes an FK KEY SHARE on the
|
|
167
|
+
# destination domain — machine ≺ domain, the SAME acquisition order register takes.
|
|
168
|
+
#
|
|
169
|
+
# WHAT P5/P5-reverse DO AND DO NOT SHOW, corrected after a review seat pointed out that the first
|
|
170
|
+
# version of this comment claimed the stronger thing. They show that register and reclaim SERIALIZE
|
|
171
|
+
# on the machine row in both starting orders and that neither wedges. They do NOT reproduce the
|
|
172
|
+
# historical domain↔machine cycle: they target different domains, and each RPC only starts after
|
|
173
|
+
# the other has RETURNED, so no session is ever mid-call holding one lock and waiting for the other.
|
|
174
|
+
# Measured, not assumed: injecting the #158-shaped fleet_trust_domains FOR UPDATE leaves BOTH of
|
|
175
|
+
# them green. The permutations that actually falsify that lock are P1/P3/P4 — see the header.
|
|
176
|
+
session "rc"
|
|
177
|
+
step "rc_begin" { BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role'; }
|
|
178
|
+
step "rc_reclaim" {
|
|
179
|
+
select owner_agent_id, trust_domain_id
|
|
180
|
+
from agent_board.reclaim_fleet_machine(
|
|
181
|
+
'c3970000-0000-4000-8000-000000000001', 'a3970000-0000-4000-8000-000000000003',
|
|
182
|
+
'a3970000-0000-4000-8000-000000000002', 'iso reclaim vs register',
|
|
183
|
+
'd3970000-0000-4000-8000-000000000004');
|
|
184
|
+
}
|
|
185
|
+
step "rc_commit" { COMMIT; }
|
|
186
|
+
|
|
187
|
+
# rf: the session that WINS a first-registration race on a fingerprint no row exists for yet.
|
|
188
|
+
# The loser's pre-read legitimately finds nothing, so v_prior_domain is null and the skip predicate
|
|
189
|
+
# has no baseline — the one conflict path the locked pre-read cannot cover.
|
|
190
|
+
session "rf"
|
|
191
|
+
step "rf_begin" { BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role'; }
|
|
192
|
+
step "rf_register" {
|
|
193
|
+
select trust_domain_id
|
|
194
|
+
from agent_board.register_fleet_machine(
|
|
195
|
+
'a3970000-0000-4000-8000-000000000001', 'd3970000-0000-4000-8000-000000000005',
|
|
196
|
+
'nodowngrade-iso-fresh-winner', 'sha256:nodowngrade-iso-fresh', 'portal',
|
|
197
|
+
jsonb_build_object('redacted', true));
|
|
198
|
+
}
|
|
199
|
+
step "rf_commit" { COMMIT; }
|
|
200
|
+
|
|
201
|
+
# rm2: the loser of that race. Its insert blocks on the unique index, then takes the CONFLICT path
|
|
202
|
+
# against a row it never read.
|
|
203
|
+
session "rm2"
|
|
204
|
+
step "rm2_begin" { BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role'; }
|
|
205
|
+
step "rm2_register" {
|
|
206
|
+
select trust_domain_id
|
|
207
|
+
from agent_board.register_fleet_machine(
|
|
208
|
+
'a3970000-0000-4000-8000-000000000001', 'd3970000-0000-4000-8000-000000000006',
|
|
209
|
+
'nodowngrade-iso-fresh-loser', 'sha256:nodowngrade-iso-fresh', 'portal',
|
|
210
|
+
jsonb_build_object('redacted', true));
|
|
211
|
+
}
|
|
212
|
+
step "rm2_commit" { COMMIT; }
|
|
213
|
+
|
|
214
|
+
# checker for the raced fingerprint.
|
|
215
|
+
session "chkf"
|
|
216
|
+
step "chkf" {
|
|
217
|
+
select fm.trust_domain_id, fm.lifecycle_state as machine_state, ftd.lifecycle_state as domain_state
|
|
218
|
+
from agent_board.fleet_machines fm
|
|
219
|
+
join agent_board.fleet_trust_domains ftd on ftd.id = fm.trust_domain_id
|
|
220
|
+
where fm.machine_fingerprint = 'sha256:nodowngrade-iso-fresh';
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
# checker: where the machine ended up, and the lifecycle of the domain it is in.
|
|
224
|
+
session "chk"
|
|
225
|
+
step "chk" {
|
|
226
|
+
select fm.trust_domain_id, fm.owner_agent_id, fm.lifecycle_state as machine_state,
|
|
227
|
+
ftd.lifecycle_state as domain_state
|
|
228
|
+
from agent_board.fleet_machines fm
|
|
229
|
+
join agent_board.fleet_trust_domains ftd on ftd.id = fm.trust_domain_id
|
|
230
|
+
where fm.id = 'c3970000-0000-4000-8000-000000000001';
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
# P1: the move blocks, the destination is demoted under it, and the post-move re-check refuses.
|
|
234
|
+
permutation "rl_begin" "rl_lock" "rm_begin" "rm_move" "rl_demote" "rl_commit" "rm_commit" "chk"
|
|
235
|
+
|
|
236
|
+
# P2 (twin): the same move with no concurrent demotion still succeeds and lands.
|
|
237
|
+
permutation "rm_begin" "rm_move" "rm_commit" "chk"
|
|
238
|
+
|
|
239
|
+
# P3 (#397 round 2, case A — the concurrent session RELOCATES the machine). rl moves the machine
|
|
240
|
+
# D1→D2 uncommitted; rm re-registers into D1, so rm's prior-read sees the COMMITTED D1 and records
|
|
241
|
+
# v_prior_domain = D1; rl then demotes D1 and commits; rm resumes against the machine row as it now
|
|
242
|
+
# is (D2) and moves it D2→D1. The destination it lands in EQUALS the stale v_prior_domain, so a
|
|
243
|
+
# skip predicate built on that pre-read reads "nothing moved" and skips the fresh-snapshot check —
|
|
244
|
+
# leaving an active machine in a pending domain. MUST refuse.
|
|
245
|
+
permutation "rl_begin" "rl_relocate_dest" "rm_begin" "rm_move_current" "rl_demote_current" "rl_commit" "rm_commit" "chk"
|
|
246
|
+
|
|
247
|
+
# P3-twin: the same relocation with NO demotion. D1 is still active, so the move back into it is a
|
|
248
|
+
# legitimate equal-rank relocation and MUST succeed — an over-refusing guard fails here.
|
|
249
|
+
permutation "rl_begin" "rl_relocate_dest" "rm_begin" "rm_move_current" "rl_commit" "rm_commit" "chk"
|
|
250
|
+
|
|
251
|
+
# P4 (#397 round 2, case B — the LOCK HOLDER relocates). rm re-registers into the machine's OWN
|
|
252
|
+
# domain D1 (a label update, so both the destination gate and the rank pass and v_prior_domain =
|
|
253
|
+
# D1); rl holds the machine row, relocates it to X, demotes D1, and commits; rm resumes and the
|
|
254
|
+
# upsert genuinely moves the machine X→D1, which is now pending. Same stale-predicate skip.
|
|
255
|
+
# rm blocks at the UPSERT, not at the prior-read — a plain SELECT does not wait, which is why the
|
|
256
|
+
# pre-read is stale rather than merely early. MUST refuse.
|
|
257
|
+
permutation "rl_begin" "rl_lock" "rm_begin" "rm_move_current" "rl_relocate_x" "rl_demote_current" "rl_commit" "rm_commit" "chk"
|
|
258
|
+
|
|
259
|
+
# P4-twin: the same, without the demotion — X→D1 is equal-rank and MUST succeed.
|
|
260
|
+
permutation "rl_begin" "rl_lock" "rm_begin" "rm_move_current" "rl_relocate_x" "rl_commit" "rm_commit" "chk"
|
|
261
|
+
|
|
262
|
+
# P5 (lock order). register's machine-row FOR UPDATE is the SAME row reclaim_fleet_machine locks,
|
|
263
|
+
# and both then reach fleet_trust_domains only through the FK — machine ≺ domain in both, so no
|
|
264
|
+
# cycle. Evidence, not assertion: reclaim holds the machine row, register blocks, reclaim commits,
|
|
265
|
+
# register resumes and is refused by #141 (the machine now belongs to the new owner). No 40P01.
|
|
266
|
+
permutation "rc_begin" "rc_reclaim" "rm_begin" "rm_move_current" "rc_commit" "rm_commit" "chk"
|
|
267
|
+
|
|
268
|
+
# P5-reverse: the other acquisition order. register takes the machine row first and holds it to
|
|
269
|
+
# commit; reclaim blocks and completes afterwards. Still no cycle.
|
|
270
|
+
permutation "rm_begin" "rm_move_current" "rc_begin" "rc_reclaim" "rm_commit" "rc_commit" "chk"
|
|
271
|
+
|
|
272
|
+
# P6 (the first-registration race — the one conflict path the locked pre-read cannot cover). Both
|
|
273
|
+
# race domains are PENDING. rf first-registers the fingerprint into SRC and holds it uncommitted;
|
|
274
|
+
# rm2 registers the SAME fingerprint into DEST and blocks on the unique index; DEST is demoted to
|
|
275
|
+
# quarantined (rank 0, below pending) and committed while rm2 waits; rf commits; rm2 resumes onto
|
|
276
|
+
# the CONFLICT path against a row it never read and never locked. There is no recoverable baseline
|
|
277
|
+
# — the domain the machine was in belonged to a row we never read, and the upsert has overwritten
|
|
278
|
+
# it — so this is REFUSED as a serialization failure (40001, retryable) rather than approximated.
|
|
279
|
+
permutation "rf_begin" "rf_register" "rm2_begin" "rm2_register" "rl_begin" "rl_demote_race_dest" "rl_commit" "rf_commit" "rm2_commit" "chkf"
|
|
280
|
+
|
|
281
|
+
# P6-promote: THE ROUND-3 PANEL'S CASE, in its own shape. Nothing is demoted — the SOURCE is
|
|
282
|
+
# PROMOTED to active while both racers are in flight, so rm2 would move the machine OUT of a
|
|
283
|
+
# now-active domain INTO a pending one. Round 2's fallback compared the cached destination rank
|
|
284
|
+
# against itself (1 >= 1) and passed this; the refusal must come from the ABSENT BASELINE, which is
|
|
285
|
+
# why it does not depend on any rank comparison at all.
|
|
286
|
+
permutation "rf_begin" "rf_register" "rm2_begin" "rm2_register" "rl_begin" "rl_promote_race_src" "rl_commit" "rf_commit" "rm2_commit" "chkf"
|
|
287
|
+
|
|
288
|
+
# P6-control (the twin): the SAME two registrations, SERIALIZED — rf commits before rm2 starts, so
|
|
289
|
+
# rm2 has a row to lock and a real baseline. Both domains are pending, so its move is equal-rank and
|
|
290
|
+
# MUST succeed and land in DEST. Without this, a guard that refused every second registration would
|
|
291
|
+
# satisfy P6 and P6-promote.
|
|
292
|
+
permutation "rf_begin" "rf_register" "rf_commit" "rm2_begin" "rm2_register" "rm2_commit" "chkf"
|