sim: a dirty return ends the source's files protection, and its install parks the pre-move generations, only when the target admitted the move (Astra joint round 4 item 2)
eaa08175 treated every dirty return as having a target admission, but src_covered false makes a return dirty when the target never admitted (releaseTarget's seal-return). movesinstall.go's targetAdmitted (1b-next) decides from the claim's recorded admissions alone: with none of the move's generation, holder.generation is still the pre-move one, the source's live state, and the return's install parks nothing. The model mirrors it: targetAdmitted (an admission whose generation is the move's op id); dirtyReturned requires it in filesHome and in the oracle (servableRegions reads the admissions itself, whatever the rules), and the install-park step parks nothing without it. New rule dirty-return-needs-admission drops the check on the protocol side; witness wDirtyReturnWithoutAdmission (the source's write unreplicated through the seal, the target stalled before its probe, the landing rolled back dirty) holds with the rule and violates INV-30 without it. TestFilesWitnessesStuckWithoutINV30 skips the new witness: its loss has no liveness consequence in the scenario, so INV-30 is its only oracle. One-box COUNTERS and traces unchanged; 300-seed two-box COUNTERS unchanged; unmutated 3,000 two-box seeds, 0 failed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 files changed,
+94
−8
internal/sim/hostlife/moves_test.go+9 −0internal/sim/hostlife/regionha_completion_witness_test.go+44 −0internal/sim/hostlife/regionha_test.go+33 −7internal/sim/hostlife/regionha_witness_test.go+5 −1internal/sim/hostlife/rules_test.go+1 −0internal/sim/hostlife/witness_test.go+2 −0
CI passed — run details
CI log
=== gate ===
go: downloading github.com/aws/aws-sdk-go-v2/service/sns v1.43.0
go: downloading github.com/oschwald/maxminddb-golang v1.13.1
go: downloading github.com/klauspost/compress v1.18.0
cd launchctx && go vet ./... && go test ./... -count=1
ok amadan.net/carlos/platform.git/launchctx 0.011s
? amadan.net/carlos/platform.git/launchctx/controlname [no test files]
? amadan.net/carlos/platform.git/launchctx/wireid [no test files]
go vet ./...
gofmt -l .: clean
go test ./... -count=1 -timeout 30m
ok github.com/carlosframework/platform 320.993s
? github.com/carlosframework/platform/cmd/changelog [no test files]
ok github.com/carlosframework/platform/cmd/console 3.066s
ok github.com/carlosframework/platform/cmd/moveprobe 92.244s
ok github.com/carlosframework/platform/internal/activator 208.925s
ok github.com/carlosframework/platform/internal/agentsock 0.020s
ok github.com/carlosframework/platform/internal/alerting 0.003s
ok github.com/carlosframework/platform/internal/analytics 0.019s
ok github.com/carlosframework/platform/internal/appconfig 2.251s
ok github.com/carlosframework/platform/internal/apperr 0.077s
ok github.com/carlosframework/platform/internal/applog 0.140s
ok github.com/carlosframework/platform/internal/awsinv 0.006s
ok github.com/carlosframework/platform/internal/boxchoice 0.005s
ok github.com/carlosframework/platform/internal/boxid 0.995s
? github.com/carlosframework/platform/internal/boxid/boxidtest [no test files]
? github.com/carlosframework/platform/internal/boxid/internal/signtest [no test files]
ok github.com/carlosframework/platform/internal/boxvolumes 0.008s
ok github.com/carlosframework/platform/internal/caps 0.012s
ok github.com/carlosframework/platform/internal/certwarm 0.002s
ok github.com/carlosframework/platform/internal/cgroup 0.193s
ok github.com/carlosframework/platform/internal/changelog 0.260s
ok github.com/carlosframework/platform/internal/clamd 0.054s
? github.com/carlosframework/platform/internal/cliflag [no test files]
ok github.com/carlosframework/platform/internal/console 356.205s
? github.com/carlosframework/platform/internal/controlname [no test files]
? github.com/carlosframework/platform/internal/controlsock [no test files]
ok github.com/carlosframework/platform/internal/dbdoor 5.554s
ok github.com/carlosframework/platform/internal/dbdoor/dbdoortest 0.014s
ok github.com/carlosframework/platform/internal/dbverify 0.014s
ok github.com/carlosframework/platform/internal/dnsrec 0.014s
ok github.com/carlosframework/platform/internal/docsite 0.014s
ok github.com/carlosframework/platform/internal/domains 1.974s
ok github.com/carlosframework/platform/internal/doorkey 0.014s
ok github.com/carlosframework/platform/internal/economics 0.014s
ok github.com/carlosframework/platform/internal/edge 25.768s
? github.com/carlosframework/platform/internal/edgeproof [no test files]
ok github.com/carlosframework/platform/internal/edgesfile 0.014s
ok github.com/carlosframework/platform/internal/fleetapi 0.128s
ok github.com/carlosframework/platform/internal/gate 1.655s
ok github.com/carlosframework/platform/internal/genselect 0.015s
ok github.com/carlosframework/platform/internal/geo 0.019s
ok github.com/carlosframework/platform/internal/geoip 0.033s
ok github.com/carlosframework/platform/internal/host 54.726s
ok github.com/carlosframework/platform/internal/hostactors 0.572s
ok github.com/carlosframework/platform/internal/hostbox 1.283s
ok github.com/carlosframework/platform/internal/hostclaim 0.274s
ok github.com/carlosframework/platform/internal/hostevidence 0.019s
ok github.com/carlosframework/platform/internal/hostexec 0.309s
ok github.com/carlosframework/platform/internal/hostfence 0.046s
ok github.com/carlosframework/platform/internal/hostfiles 4.136s
ok github.com/carlosframework/platform/internal/hostfloor 0.024s
ok github.com/carlosframework/platform/internal/hostguard 0.146s
ok github.com/carlosframework/platform/internal/hostops 1.370s
? github.com/carlosframework/platform/internal/hostops/fencerelease [no test files]
ok github.com/carlosframework/platform/internal/hostops/internal/fencerelease 0.017s
ok github.com/carlosframework/platform/internal/hostquarantine 0.002s
ok github.com/carlosframework/platform/internal/hostresolve 0.151s
ok github.com/carlosframework/platform/internal/hostwrite 0.003s
ok github.com/carlosframework/platform/internal/infraview 0.002s
ok github.com/carlosframework/platform/internal/instancectl 1.703s
ok github.com/carlosframework/platform/internal/instanceexec 2.696s
ok github.com/carlosframework/platform/internal/instancefs 0.003s
ok github.com/carlosframework/platform/internal/instancekey 0.002s
ok github.com/carlosframework/platform/internal/instanceupdates 0.013s
ok github.com/carlosframework/platform/internal/layout 1.327s
ok github.com/carlosframework/platform/internal/lease 9.432s
ok github.com/carlosframework/platform/internal/lease/leasetest 0.007s
ok github.com/carlosframework/platform/internal/ledger 0.011s
ok github.com/carlosframework/platform/internal/mail 0.148s
ok github.com/carlosframework/platform/internal/maintenance 0.079s
ok github.com/carlosframework/platform/internal/maintenanceexec 5.525s
ok github.com/carlosframework/platform/internal/maintenanceguard 0.068s
ok github.com/carlosframework/platform/internal/metrics 0.005s
ok github.com/carlosframework/platform/internal/moves 21.007s
ok github.com/carlosframework/platform/internal/moves/archive 0.734s
ok github.com/carlosframework/platform/internal/objstore 0.158s
ok github.com/carlosframework/platform/internal/ops 0.155s
ok github.com/carlosframework/platform/internal/park 0.188s
ok github.com/carlosframework/platform/internal/privatefile 0.003s
ok github.com/carlosframework/platform/internal/provision 3.127s
ok github.com/carlosframework/platform/internal/publicrelease 0.011s
? github.com/carlosframework/platform/internal/regionname [no test files]
ok github.com/carlosframework/platform/internal/registry 3.674s
ok github.com/carlosframework/platform/internal/relay 7.226s
ok github.com/carlosframework/platform/internal/release 19.472s
ok github.com/carlosframework/platform/internal/rollout 3.224s
ok github.com/carlosframework/platform/internal/rolloutwords 0.003s
ok github.com/carlosframework/platform/internal/rootaws 0.007s
ok github.com/carlosframework/platform/internal/s3compat 0.005s
ok github.com/carlosframework/platform/internal/safefile 0.004s
ok github.com/carlosframework/platform/internal/schedule 2.206s
ok github.com/carlosframework/platform/internal/sim/hostlife 8.332s
ok github.com/carlosframework/platform/internal/sim/litestreamfacts 4.157s
ok github.com/carlosframework/platform/internal/status 0.035s
ok github.com/carlosframework/platform/internal/steering 0.006s
ok github.com/carlosframework/platform/internal/stopevidence 0.122s
ok github.com/carlosframework/platform/internal/svcrole 0.002s
ok github.com/carlosframework/platform/internal/telemetry 0.004s
ok github.com/carlosframework/platform/internal/termfence 0.004s
ok github.com/carlosframework/platform/internal/tunnelclient 0.004s
ok github.com/carlosframework/platform/internal/unixdial 0.002s
ok github.com/carlosframework/platform/internal/updatewords 0.002s
ok github.com/carlosframework/platform/internal/usage 0.004s
ok github.com/carlosframework/platform/internal/version 0.001s
ok github.com/carlosframework/platform/internal/vet 1.546s
=== cross ===
--- shard 0 of 5 ---
cross-compiling darwin/arm64
--- shard 1 of 5 ---
cross-compiling darwin/amd64
--- shard 2 of 5 ---
cross-compiling linux/arm64
--- shard 3 of 5 ---
cross-compiling windows/amd64
--- shard 4 of 5 ---
cross-compiling windows/arm64
=== minio-contract ===
minio contract: arm64, minio RELEASE.2025-09-07T16-13-09Z, mc RELEASE.2025-08-13T08-35-41Z
minio: cached, digest verified
mc: cached, digest verified
minio healthy on 127.0.0.1:20999
Added `local` successfully.
Bucket created successfully `local/contract-bucket`.
Bucket created successfully `local/denied-bucket`.
Created policy `contract-only` successfully.
Added user `scopeduser` successfully.
Attached Policies: [contract-only]
To User: scopeduser
buckets and scoped user ready
=== RUN TestDeliveredContractMinio
=== RUN TestDeliveredContractMinio/put
=== RUN TestDeliveredContractMinio/get_round-trips_the_body
=== RUN TestDeliveredContractMinio/list_shows_the_key
=== RUN TestDeliveredContractMinio/head_and_list_agree_on_the_version
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_its_own_object
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed
=== RUN TestDeliveredContractMinio/delete_removes_it
=== RUN TestDeliveredContractMinio/cross-bucket_denial
--- PASS: TestDeliveredContractMinio (0.02s)
--- PASS: TestDeliveredContractMinio/put (0.00s)
--- PASS: TestDeliveredContractMinio/get_round-trips_the_body (0.00s)
--- PASS: TestDeliveredContractMinio/list_shows_the_key (0.00s)
--- PASS: TestDeliveredContractMinio/head_and_list_agree_on_the_version (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route (0.01s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed (0.00s)
--- PASS: TestDeliveredContractMinio/delete_removes_it (0.00s)
--- PASS: TestDeliveredContractMinio/cross-bucket_denial (0.00s)
PASS
ok github.com/carlosframework/platform/internal/objstore 0.022s
=== RUN TestT4StorageProbeMinio
2026/10/12 20:03:33 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18DDE0D0BA8F9893, HostID: 7c420053e0fbcd7f41653fdd92ffa2802c903d05303b1aa15adc2e8bb65d183f, api error SignatureDoesNotMatch: The request signature we calculated does not match the signature you provided. Check your key and signing method.
--- PASS: TestT4StorageProbeMinio (0.01s)
PASS
ok github.com/carlosframework/platform/internal/host 0.020s
minio contract: PASS
diff --git a/internal/sim/hostlife/moves_test.go b/internal/sim/hostlife/moves_test.go| index 56ae4313f..86d143936 100644 |
| --- a/internal/sim/hostlife/moves_test.go |
| +++ b/internal/sim/hostlife/moves_test.go |
| @@ -1627,6 +1627,15 @@ func (w *world) recoverSource(b *box, c *claim) { |
| if !w.evidence(b, op.RollbackID) { |
| return |
| } |
| + if on("dirty-return-needs-admission") && !targetAdmitted(c) { |
| + // movesinstall.go's parking (fix rounds 3 and 4): the target never |
| + // admitted this move, so holder.generation is still the pre-move |
| + // one, the source's live state; there is no residue, and nothing |
| + // is parked. The mutation parks as if the target had admitted |
| + w.stats["rollback: dirty return parks nothing, the target never admitted"]++ |
| + w.cas(c, func(n *claim) bool { n.Op.SrcPhase = "install-copy"; return sameOp(n.Op.ID, op.ID) }) |
| + return |
| + } |
| if w.parkStep(b, c.Holder.Location, fmt.Sprintf("superseded/%s/%s/return/", op.ID, host), op.StopAt+horizonH, w.opScope(c, c.Holder.Location), w.keepFilesLive(b.Region, confine(c.Holder.Location))) { |
| w.cas(c, func(n *claim) bool { n.Op.SrcPhase = "install-copy"; return sameOp(n.Op.ID, op.ID) }) |
| } |
diff --git a/internal/sim/hostlife/regionha_completion_witness_test.go b/internal/sim/hostlife/regionha_completion_witness_test.go| index 166577cbc..38e2bdba7 100644 |
| --- a/internal/sim/hostlife/regionha_completion_witness_test.go |
| +++ b/internal/sim/hostlife/regionha_completion_witness_test.go |
| @@ -724,3 +724,47 @@ func TestDirtyReturnParksPreMoveFiles(t *testing.T) { |
| w.finish(t) |
| w.mustHold(t) |
| } |
| + |
| +// wDirtyReturnWithoutAdmission (dirty-return-needs-admission, Astra joint |
| +// round 4 item 2): the source's last write is not yet replicated at the |
| +// cut, so the seal does not cover its replica (src_covered false), and the |
| +// move's target stalls before its probe is admitted, so the landing rolls |
| +// back. The return is dirty (the seal is returned) although the target |
| +// never admitted: holder.generation is still the pre-move one, the source |
| +// keeps the files home, INV-30 counts the source region, and the return's |
| +// install parks nothing (movesinstall.go's targetAdmitted). Without the |
| +// admission check the install park retires the manifest the source's next |
| +// install would pin (INV-30). |
| +func wDirtyReturnWithoutAdmission(t *testing.T, w *world) { |
| + w.twoBox() |
| + src, sib := w.boxes[0], w.boxes[3] |
| + w.serve(src, 1, app0) |
| + w.running(t, src) |
| + w.published(t, src) |
| + w.appWrite(src) // not replicated before the cut: the seal is all that holds it |
| + loc := w.claim.Holder.Location |
| + w.holdKey(loc) // the source's uploads stay in flight through the seal |
| + w.moveTo(1) |
| + if w.claim.Op.Kind != "moving" { |
| + t.Fatalf("witness: the move did not start: %s", w.state()) |
| + } |
| + tgt := w.boxByID(w.claim.Op.Tgt) |
| + if !w.reach(t, 600, "the flip, before the target's probe", func() bool { return w.claim.Op.Kind == "landing" }, func() { w.worker(src); w.reconcile(src); w.worker(tgt); w.reconcile(tgt); w.runConsole() }) { |
| + return |
| + } |
| + w.hold = nil |
| + w.completeHeld(loc) |
| + if targetAdmitted(w.claim) || w.claim.Op.SrcCovered { |
| + t.Fatalf("witness: not the scenario (target admitted %v, src_covered %v): %s", targetAdmitted(w.claim), w.claim.Op.SrcCovered, w.state()) |
| + } |
| + // the target stalls: the landing's deadline passes with no probe |
| + w.drive(w.claim.Op.CutDeadline+12-w.now, never, w.act(sib), w.runConsole, w.recoveryPass, w.operator) |
| + if !w.reach(t, 1500, "the dirty return's install past its park", func() bool { |
| + c := w.claim |
| + return c.Op.Kind == "restoring" && c.Op.Dirty && c.Op.SrcPhase != "waiting" && c.Op.SrcPhase != "claimed" && c.Op.SrcPhase != "install-park" || c.Op.Kind == "serving" |
| + }, w.actAll(), w.recoveryPass, w.runConsole) { |
| + return |
| + } |
| + w.reach(t, 1500, "the return serving on the source", func() bool { c := w.claim; return c.Op.Kind == "serving" && c.Op.Exec == src.ID }, w.actAll(), w.recoveryPass, w.runConsole, func() { w.wake(src) }) |
| + w.finish(t) |
| +} |
diff --git a/internal/sim/hostlife/regionha_test.go b/internal/sim/hostlife/regionha_test.go| index 1b815e678..b8bc19a8e 100644 |
| --- a/internal/sim/hostlife/regionha_test.go |
| +++ b/internal/sim/hostlife/regionha_test.go |
| @@ -2165,7 +2165,7 @@ func (w *world) filesLiveSet(c *claim, r int, k string) bool { |
| // commit, and in a rollback's released phase, a park-generation queued |
| // during the move archives the live manifest in the source region. |
| func (w *world) filesHome(c *claim, r int) bool { |
| - if dirtyReturned(c) && r == c.Op.SrcRegion && on("files-home-move-phases") { |
| + if dirtyReturned(c, on("dirty-return-needs-admission")) && r == c.Op.SrcRegion && on("files-home-move-phases") { |
| return false // D6: the dirty return's install parks the pre-move generations |
| } |
| if e := w.execBox(c); e != nil && e.Region == r { |
| @@ -2207,10 +2207,13 @@ type pinWatch struct { |
| // op.SrcGen again there); a rollback's source region. Creating, |
| // deleting, parking and purging have no executor and name none. |
| func (w *world) servableRegions(c *claim) []int { |
| - if c == nil || c.Holder == nil || dirtyReturned(c) { |
| - // after a dirty return the source's next install comes from the |
| - // return lane, never from a pinned manifest of the pre-move |
| - // generations, which D6 retires by design (moves a5e8c13f) |
| + if c == nil || c.Holder == nil || dirtyReturned(c, true) { |
| + // after a dirty return of a move its target admitted, the source's |
| + // next install comes from the return lane, never from a pinned |
| + // manifest of the pre-move generations, which D6 retires by design |
| + // (moves a5e8c13f). The oracle reads the claim's admissions itself, |
| + // whatever the rules say: a dirty return the target never admitted |
| + // keeps the pre-move generation as the source's live state |
| return nil |
| } |
| var out []int |
| @@ -2637,6 +2640,29 @@ func (w *world) relocJournalRelease(b *box, c *claim, pending bool) { |
| // admission decides (moves' movesAuthoritative, a5e8c13f): the target's, |
| // so the source region is not authoritative, and the return's install |
| // park retires the pre-move generations (D6). |
| -func dirtyReturned(c *claim) bool { |
| - return c != nil && c.Holder != nil && c.Op.Kind == "restoring" && c.Op.Dirty && c.Op.SrcPhase != "waiting" && c.Holder.Gen != c.Op.RollbackID |
| +func dirtyReturned(c *claim, needAdmission bool) bool { |
| + if c == nil || c.Holder == nil || c.Op.Kind != "restoring" || !c.Op.Dirty || c.Op.SrcPhase == "waiting" || c.Holder.Gen == c.Op.RollbackID { |
| + return false |
| + } |
| + return !needAdmission || targetAdmitted(c) |
| +} |
| + |
| +// targetAdmitted is movesinstall.go's targetAdmitted (Astra round 4, item |
| +// 2): the claim's recorded admissions hold one of this move's generation |
| +// (the landing's, installGen: the move's op id). A dirty return can come |
| +// without one: src_covered false makes the return dirty even when the |
| +// target never admitted (releaseTarget's seal-return), and then |
| +// holder.generation is still the pre-move one, the source's live state, |
| +// which the return's install keeps and parks nothing of. Decided from the |
| +// claim's admissions only, never from the landing phase. |
| +func targetAdmitted(c *claim) bool { |
| + if c == nil || c.Holder == nil { |
| + return false |
| + } |
| + for _, a := range c.Holder.Admissions { |
| + if a.Gen == c.Op.ID { |
| + return true |
| + } |
| + } |
| + return false |
| } |
diff --git a/internal/sim/hostlife/regionha_witness_test.go b/internal/sim/hostlife/regionha_witness_test.go| index 5b089d40d..f2a017aa7 100644 |
| --- a/internal/sim/hostlife/regionha_witness_test.go |
| +++ b/internal/sim/hostlife/regionha_witness_test.go |
| @@ -1262,7 +1262,11 @@ func TestFilesWitnessesStuckWithoutINV30(t *testing.T) { |
| defer func() { mutated = saved }() |
| n := 0 |
| for _, x := range witnesses { |
| - if x.want != "INV-30" { |
| + if x.want != "INV-30" || x.rule == "dirty-return-needs-admission" { |
| + // dirty-return-needs-admission's loss has no liveness consequence |
| + // in its scenario (the dirty return serves from the returned |
| + // seal): INV-30, the independent preservation invariant, is its |
| + // only oracle (Astra joint round 4) |
| continue |
| } |
| n++ |
diff --git a/internal/sim/hostlife/rules_test.go b/internal/sim/hostlife/rules_test.go| index 5d1ebacee..9cb452f4c 100644 |
| --- a/internal/sim/hostlife/rules_test.go |
| +++ b/internal/sim/hostlife/rules_test.go |
| @@ -222,6 +222,7 @@ var allRules = []string{ |
| "reloc-completed-catchup-hold", // finding 3, ruling 85 (hostbox completedHold, 1b-next a092ddc2): a source whose reconcile runs only after the target completed holds its row for the completed op until its park-local retires it; the mutation leaves the row its release left ready (INV-33; wDelayedSourceHoldsItsRow) |
| "reloc-completion-fenced-exempt", // ruling 86 (1b-next ca282378: StartGuard's fencedCompletion, ProductionAuthority.completionFence): a delete or purge whose caller verified the completion box's termination fence passes the barrier, as a failover with its fenced source does; the mutation verifies nothing, and a completion box terminated before its discharge while a purge holds admission closed blocks both the purge and the failover for ever (INV-3; wPurgeOverAFencedCompletionBox) |
| "reloc-probe-stop-flush", // ruling 87 (1b-next 6a4eadde, EndProbe through flushStopped): the probe stop stops the writers, flushes the database and files and records the positions before the activator's fence and root's stop, and with another owner's fence it refuses after the flush; a restart before the record has no replicator, so it refuses until ruling 67's deadline; the mutation stops with nothing flushed, and the first serving wake declares writes the probe session acknowledged (INV-34; wProbeStopFlushes) |
| + "dirty-return-needs-admission", // Astra joint round 4 item 2 (moves movesinstall.go's targetAdmitted, movessources.go's restoring case): a dirty return ends the source region's files protection, and its install parks the pre-move generations, only when the claim records an admission of this move's generation by the target; src_covered false makes a return dirty without one, and then the pre-move generation stays the source's live state and nothing is parked; the mutation drops the admission check, and the install park retires the manifest the source's next install would pin (INV-30; wDirtyReturnWithoutAdmission) |
| "reloc-admits-probe-only", // L18 joint round 2 finding 3 (hostclaim relocatingAdmission): a relocating claim admits only op.exec's cold probe in probing, so between StopWriters' launch-journal closure and Collect's admission fence no wake of the source admits a writer; the mutation admits the source's wake in starting and releasing, as the model did before (TestNoAdmissionBetweenStopAndFence) |
| "reloc-c1-failed-release-refused", // finding 4, claim ask C1 open (QueueRelocationRelease requires the source in releasing): a live failed target cannot release in probe-failed and reaches stuck at the release deadline, and the operator's resolve stops it; the mutation releases as if C1 had landed (a fidelity guard: TestFailedTargetCannotReleaseWithoutC1) |
| "reloc-c11-later-attempt-refused", // finding 4, claim ask C11 open (Preflight's ErrLaterAttemptUnlaunchable, ruling 58): after P's opening a later cold probe attempt is refused before it is counted, which is probe-failed; the mutation counts and makes it as if C11 had landed (a fidelity guard: TestLaterAttemptRefusedWithoutC11) |
diff --git a/internal/sim/hostlife/witness_test.go b/internal/sim/hostlife/witness_test.go| index 33c36dc7f..ebdce19c6 100644 |
| --- a/internal/sim/hostlife/witness_test.go |
| +++ b/internal/sim/hostlife/witness_test.go |
| @@ -291,6 +291,8 @@ var witnesses = []witness{ |
| "finding 3, ruling 85: the source releases, its reconcile is delayed until the target completed, and its next reconcile holds its row for the completed op until its park-local retires it; without the catch-up hold the row its release left ready stays ready", wDelayedSourceHoldsItsRow}, |
| {"reloc-probe-stop-flush", "reloc-probe-stop-flush", "INV-34", |
| "ruling 87: the probe session acknowledges a write its replicator has not shipped; the probe stop flushes it before root's stop, so the first serving wake, cold, declares nothing the probe session acknowledged; stopped with nothing flushed, that wake declares the write lost", wProbeStopFlushes}, |
| + {"dirty-return-needs-admission", "dirty-return-needs-admission", "INV-30", |
| + "Astra joint round 4: the source's unreplicated write makes the seal src_covered false and the target stalls before its probe, so the landing rolls back dirty with no target admission; the source keeps the files home and its install parks nothing; without the admission check the install park retires the manifest the source's next install would pin", wDirtyReturnWithoutAdmission}, |
| {"reloc-completion-fenced-exempt", "reloc-completion-fenced-exempt", "INV-3", |
| "ruling 86, seed 2224: a purge closes the app's admission and the completion box is terminated before its discharge; once its fence record exists the purge verifies it, starts over the unmarked completion and frees the host; without the exemption the barrier refuses the purge and the closed admission the failover, for ever", wPurgeOverAFencedCompletionBox}, |
| } |