carlos / platform Public

host: a restart is this landing's only with proof of descent (Plan 3 Task 5 fix round 2, finding 8)

A marker's generation counts as a restart of this landing only when its
chain of cold admissions on this box and region reaches the move's
prepared generation. An earlier stay's generation, though its cold
admission on this box and region is still in the history, is superseded
by the prepared marker by If-Match and never treated as a restart. The
earlier-stay test now carries that admission; a genuine restart after a
crash is still recognised.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev ba7aa3fb45a506377c4e03399d284244e688b466 parent 1436fb0
2 files changed, +127 −5
  • internal/host/movesprobeowners.go +38 −4
  • internal/host/movesprobeowners_fix1_test.go +89 −1
diff --git a/internal/host/movesprobeowners.go b/internal/host/movesprobeowners.go
index e3d31deda..3e03f9b76 100644
--- a/internal/host/movesprobeowners.go
+++ b/internal/host/movesprobeowners.go
@@ -307,9 +307,11 @@ func manifestPin(m moves.InstallManifest) genselect.Pin {
}
// landingMarker reports whether the location's current marker already
-// names this landing: the move id's verified prepared marker, or the fresh
-// generation of a later admission on this box under it. Anything else (an
-// earlier stay's, another incarnation's) is superseded by the prepared one.
+// names this landing: the move id's verified prepared marker, or a fresh
+// generation proved to descend from the move's admission (descendsFromMove).
+// Anything else (an earlier stay's, though its cold admission on this box
+// and region is still in the history; another incarnation's) is superseded
+// by the prepared one, never taken for a restart (fix round 2, finding 8).
func (m *movesProbeOwners) landingMarker(marker *genselect.Marker, host string, v probeLanding, digest string) bool {
h := v.holder
if marker == nil || marker.Incarnation != h.Incarnation || marker.Host != host || marker.Box != m.Box {
@@ -318,8 +320,40 @@ func (m *movesProbeOwners) landingMarker(marker *genselect.Marker, host string,
if marker.Op == v.op {
return marker.Pin == manifestPin(v.manifest) && marker.ManifestSHA256 == digest
}
+ return m.descendsFromMove(h, marker.Op, v.op)
+}
+
+// descendsFromMove is positive proof that generation g is a restart of this
+// landing: walking back from g, each step a cold admission on this box and
+// region into the current generation, its predecessor chain reaches the
+// move's prepared generation (the admission into the move id itself, which
+// names the op). A chain that leaves this box or region, loops, or is
+// trimmed before the move id is no proof.
+func (m *movesProbeOwners) descendsFromMove(h *hostclaim.Holder, g, op string) bool {
+ seen := map[string]bool{}
+ for g != "" && g != op && !seen[g] {
+ seen[g] = true
+ found := false
+ for i := len(h.Admissions) - 1; i >= 0; i-- {
+ a := h.Admissions[i]
+ if a.Generation != g || a.Mode != "cold" {
+ continue
+ }
+ if a.Box != m.Box || a.Region != m.Region {
+ return false
+ }
+ g, found = a.Predecessor, true
+ break
+ }
+ if !found {
+ return false
+ }
+ }
+ if g != op {
+ return false
+ }
for _, a := range h.Admissions {
- if a.Generation == marker.Op && a.Box == m.Box && a.Region == m.Region && a.Mode == "cold" {
+ if a.Generation == op && a.Box == m.Box && a.Region == m.Region && a.Mode == "cold" {
return true
}
}
diff --git a/internal/host/movesprobeowners_fix1_test.go b/internal/host/movesprobeowners_fix1_test.go
index 012cebb04..e481df618 100644
--- a/internal/host/movesprobeowners_fix1_test.go
+++ b/internal/host/movesprobeowners_fix1_test.go
@@ -16,6 +16,7 @@ import (
"github.com/carlosframework/platform/internal/hostwrite"
"github.com/carlosframework/platform/internal/moves"
"github.com/carlosframework/platform/internal/provision"
+ "github.com/superfly/ltx"
)
// fenceWork is an admitted spawn selection whose fence can be closed: once
@@ -237,10 +238,22 @@ func TestMovesProbeOwnersChangedConfigThroughThePolicy(t *testing.T) {
// Finding 8, ruled order: admit, publish the verified prepared marker under
// the writer's fence (superseding the earlier stay's marker by If-Match),
-// then the start check, then the spawn.
+// then the start check, then the spawn. Fix round 2: the earlier stay is
+// really in the admission history, a cold admission on this box and region
+// that does not descend from the move's admission; it is still an earlier
+// stay, superseded, never taken for a restart of this landing.
func TestMovesProbeOwnersSupersedeTheEarlierStaysMarker(t *testing.T) {
f := newPOFixture(t)
ctx := t.Context()
+ c := f.poFlipClaim()
+ c.Holder.Admissions = []hostclaim.AdmissionRecord{
+ {Session: 5, Box: "uk-box", Generation: "earlier", Predecessor: "", Region: "uk", Mode: "cold"},
+ {Session: 15, Box: "ie-box", Generation: "src", Predecessor: "earlier", Region: "ie", PredecessorRegion: "uk", Mode: "cold"},
+ }
+ if err := c.Validate(); err != nil {
+ t.Fatal(err)
+ }
+ f.putClaim(c)
f.bucket.Put("data", f.markerKey(), mustJSON(t, genselect.Marker{Op: "earlier", Incarnation: f.inc, Host: poHost, Box: "uk-box", InstalledAt: time.Unix(1, 0).UTC(), Pin: genselect.Pin{SnapshotTXID: 3}}))
admitted, in := f.selectAndAdmit()
if err := f.owners.Materialize(ctx, admitted, in, newFenceWork()); err != nil {
@@ -249,6 +262,81 @@ func TestMovesProbeOwnersSupersedeTheEarlierStaysMarker(t *testing.T) {
if m, _ := f.currentMarker(); m.Op != poOp {
t.Fatalf("the earlier stay's marker was not superseded: %+v", m)
}
+ if err := f.owners.Publish(ctx, admitted, in); err != nil {
+ t.Fatalf("Group D's prepared publication: %v", err)
+ }
+ if err := f.owners.PublicationEvidence(ctx, admitted); err != nil {
+ t.Fatalf("publication evidence: %v", err)
+ }
+}
+
+// Fix round 2: a genuine restart after a crash is still this landing's. The
+// first attempt published the move id; the restarted attempt fresh1
+// published its own marker after its root and crashed. The next attempt's
+// baseline is the certified remote from fresh1, whose chain reaches the move
+// id, and fresh1's marker is kept, not superseded by the prepared one. (That
+// attempt then stops at Task 4's files rule: .files-gen names the move id,
+// two cold admissions back.)
+func TestMovesProbeOwnersRecogniseARestartAfterACrash(t *testing.T) {
+ f := newPOFixture(t)
+ ctx := t.Context()
+ first, in := f.selectAndAdmit()
+ if err := f.owners.Materialize(ctx, first, in, newFenceWork()); err != nil {
+ t.Fatal(err)
+ }
+ again, err := f.owners.Select(ctx, f.route, first, poProbe)
+ if err != nil {
+ t.Fatal(err)
+ }
+ restarted := f.admit(first, again)
+ fresh := again.Admission.NewGeneration
+ f.bucket.Put("data", "instances/"+poHost+"/g-"+fresh+"/0009/"+ltx.FormatFilename(1, 44), f.ltxHeader(1, 44))
+ pub := ClaimPublication{Sync: func(context.Context) error { return nil }, Check: func(context.Context) error { return nil }}
+ if _, err := f.owners.PublishFenced(ctx, restarted, again, pub); err != nil {
+ t.Fatal(err)
+ }
+ // The crash: the next record attempt admits again over fresh1.
+ next, err := f.owners.Select(ctx, f.route, restarted, poProbe)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if next.Baseline.Generation != fresh || next.Baseline.Marker == nil || next.Baseline.Marker.Op != fresh || next.Baseline.MaxTXID != 44 {
+ t.Fatalf("the restart's baseline is not the certified remote of %s: %+v", fresh, next.Baseline)
+ }
+ admitted := f.admit(restarted, next)
+ f.writeLocal(poOp, admitted.Claim.Holder.Generation, f.inc)
+ written := f.bucket.LatestID("data", f.markerKey())
+ err = f.owners.Materialize(ctx, admitted, next, newFenceWork())
+ if m, _ := f.currentMarker(); m.Op != fresh || f.bucket.LatestID("data", f.markerKey()) != written {
+ t.Fatalf("the restart's marker was superseded: %+v", m)
+ }
+ if !errors.Is(err, moves.ErrStartFiles) {
+ t.Fatalf("the second restart: %v", err)
+ }
+}
+
+// Fix round 2, the predicate itself: a generation is this landing's only
+// with positive proof, the chain of cold admissions on this box and region
+// back from it reaching the move id.
+func TestMovesProbeOwnersLandingMarkerNeedsDescent(t *testing.T) {
+ f := newPOFixture(t)
+ v := probeLanding{op: poOp, manifest: f.manifest, holder: &hostclaim.Holder{Incarnation: f.inc, Admissions: []hostclaim.AdmissionRecord{
+ {Session: 5, Box: "uk-box", Generation: "earlier", Region: "uk", Mode: "cold"},
+ {Session: 15, Box: "ie-box", Generation: "src", Predecessor: "earlier", Region: "ie", Mode: "cold"},
+ {Session: 20, Box: "uk-box", Generation: poOp, Predecessor: "src", Region: "uk", Mode: "cold", Coverage: "sealed"},
+ {Session: 21, Box: "uk-box", Generation: "f1", Predecessor: poOp, Region: "uk", Mode: "cold"},
+ {Session: 22, Box: "uk-box", Generation: "f2", Predecessor: "f1", Region: "uk", Mode: "cold"},
+ {Session: 23, Box: "uk-box", Generation: "orphan", Predecessor: "gone", Region: "uk", Mode: "cold"},
+ {Session: 24, Box: "other-box", Generation: "f3", Predecessor: "f2", Region: "uk", Mode: "cold"},
+ }}}
+ marker := func(op string) *genselect.Marker {
+ return &genselect.Marker{Op: op, Incarnation: f.inc, Host: poHost, Box: "uk-box"}
+ }
+ for op, want := range map[string]bool{"f1": true, "f2": true, "earlier": false, "src": false, "orphan": false, "f3": false, "unknown": false} {
+ if got := f.owners.landingMarker(marker(op), poHost, v, ""); got != want {
+ t.Errorf("%s: %v, want %v", op, got, want)
+ }
+ }
}
// Finding 8: a history trimmed so that the admitted generation's ancestry