carlos / platform Public

hostclaim: Commit queues the source and staging parks and covers the source region (§7.9, K8 groundwork)

Composes QueueLocationPark, QueueStagingPark and CoverRegionPredecessors in one
claim write. The source park-local is queued only when the source box lists
paths of the journalled source stamp (H5b). The holder is untouched (H3).
phaseLandingCommitted is exported as PhaseCommitted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev b0952e0a357f8e5698e470286e5ccf6675b64e5e parent dc96144
2 files changed, +202 −2
  • internal/hostclaim/move.go +55 −2
  • internal/hostclaim/move_test.go +147 −0
diff --git a/internal/hostclaim/move.go b/internal/hostclaim/move.go
index 761b82618..c13d31df1 100644
--- a/internal/hostclaim/move.go
+++ b/internal/hostclaim/move.go
@@ -421,7 +421,7 @@ func queueLocalPark(c *Claim, opID, box string, newID func() string) {
// Claim states in which a region's whole location is retired (§6.4, §7.8):
// the landing after the commit, and a rollback from the target's release.
const (
- phaseLandingCommitted = "committed"
+ PhaseCommitted = "committed"
phaseRestoringReleased = "released"
phaseRestoringClaimed = "claimed"
)
@@ -441,7 +441,7 @@ const (
func requireRetiredRegion(c *Claim, region, box string) error {
to := c.Op.Intent["to"]
switch {
- case c.Op.Kind == KindLanding && c.Op.Phase == phaseLandingCommitted:
+ case c.Op.Kind == KindLanding && c.Op.Phase == PhaseCommitted:
if to != "" && region != to && (box == "" || box == c.Op.Intent["source"]) {
return nil
}
@@ -561,6 +561,59 @@ func CompleteLocalPark(opID, box string, earlierStamp uint64) func(*Claim) error
}
}
+// Commit is the claim half of the record's commit (§7.5, §7.9 step 1): the
+// landing goes to committed and, in the same write, the parks the commit owes
+// are queued through the Plan 2 helpers: the source region's whole location
+// (QueueLocationPark, which holds the lifecycle and exact-location checks),
+// the source box's park-local, and the target region's staging lane
+// (QueueStagingPark). The source's park-local is queued only when the source
+// box lists paths of the journalled source stamp S (InventoryHasStamp, H5b);
+// otherwise none is. CoverRegionPredecessors then covers the source region's
+// admission records. holder.stamp, session and generation are not touched
+// (R10-3, H3).
+//
+// Idempotent under the op: a landing already committed returns nil and queues
+// nothing. A refused commit leaves the claim as it found it.
+func Commit(opID, sourceRegion, sourceBox, targetRegion string, sourceScope, stagingScope ParkScope, newID func() string) func(*Claim) error {
+ return func(c *Claim) error {
+ if err := RequireOp(opID)(c); err != nil {
+ return err
+ }
+ if c.Op.Kind != KindLanding {
+ return fmt.Errorf("%w: kind %q", ErrWrongState, c.Op.Kind)
+ }
+ if c.Op.Phase == PhaseCommitted {
+ return nil
+ }
+ if c.Op.Phase != "probing" && c.Op.Phase != "probed" {
+ return fmt.Errorf("%w: phase %q", ErrWrongState, c.Op.Phase)
+ }
+ if len(c.Op.Participants) != 2 || c.Op.Exec != c.Op.Participants[1] {
+ return fmt.Errorf("%w: exec %q is not the target", ErrWrongState, c.Op.Exec)
+ }
+ if sourceBox != c.Op.Participants[0] {
+ return fmt.Errorf("%w: %q is not the move's source", ErrWrongState, sourceBox)
+ }
+ parkBox := ""
+ if stamp, err := strconv.ParseUint(c.Op.Journal[JournalSourceStamp], 10, 64); err == nil && InventoryHasStamp(c, sourceBox, stamp) {
+ parkBox = sourceBox
+ }
+ phase, obligations := c.Op.Phase, len(c.Obligations)
+ c.Op.Phase = PhaseCommitted
+ for _, step := range []func(*Claim) error{
+ QueueLocationPark(opID, sourceRegion, parkBox, sourceScope, newID),
+ QueueStagingPark(opID, targetRegion, stagingScope, newID),
+ CoverRegionPredecessors(opID, PhaseCommitted, sourceRegion),
+ } {
+ if err := step(c); err != nil {
+ c.Op.Phase, c.Obligations = phase, c.Obligations[:obligations]
+ return err
+ }
+ }
+ return nil
+ }
+}
+
// QueueResidueParks queues one park-generation per listed generation for a
// landing's earlier-stay residue in the target region: RootGeneration for
// the origin, the generation name for the rest, all scoped to the same
diff --git a/internal/hostclaim/move_test.go b/internal/hostclaim/move_test.go
index 19d375a39..20768fd83 100644
--- a/internal/hostclaim/move_test.go
+++ b/internal/hostclaim/move_test.go
@@ -1155,3 +1155,150 @@ func TestParkQueueHelpersPreserveTheRestOfTheClaim(t *testing.T) {
})
}
}
+
+// probedLanding drives the claim to landing/probed the way the landing does:
+// the flip, then every record phase mirrored through SetMovePhase. The
+// target's probe admission is on the holder, its predecessor in region ie.
+func probedLanding(t *testing.T) *Claim {
+ t.Helper()
+ c := movingToSealed(t)
+ c.Gen = 7
+ if err := Flip(moveA, "uk-box", 3, "gen-7")(c); err != nil {
+ t.Fatal(err)
+ }
+ for _, step := range [][2]string{{"checking", "parking"}, {"parking", "building"}, {"building", "published"}, {"published", "local"}, {"local", "configured"}, {"configured", "restored"}, {"restored", "probing"}, {"probing", "probed"}} {
+ if err := SetMovePhase(moveA, KindLanding, step[0], step[1])(c); err != nil {
+ t.Fatal(err)
+ }
+ }
+ c.Holder.Session = 9
+ c.Holder.Admissions = []AdmissionRecord{{Region: "uk", PredecessorRegion: "ie", Session: 9, Box: "uk-box", Generation: moveA, Predecessor: "gen-7", Mode: "cold", Coverage: "sealed"}}
+ return c
+}
+
+func commitScopes(c *Claim) (src, stg ParkScope) {
+ at := time.Date(2026, 10, 6, 10, 0, 0, 0, time.UTC)
+ src = ParkScope{Location: c.Holder.Location, Incarnation: c.Holder.Incarnation, EvidenceAt: at, RequestLimit: 120 * time.Second}
+ stg = ParkScope{Location: "moves/" + c.Host + "/" + moveA, Incarnation: c.Holder.Incarnation, EvidenceAt: at, RequestLimit: 120 * time.Second}
+ return src, stg
+}
+
+func obligationKinds(c *Claim) map[string]int {
+ kinds := map[string]int{}
+ for _, o := range c.Obligations {
+ kinds[o.Kind+"/"+o.Region+o.Box]++
+ }
+ return kinds
+}
+
+// wCommitCoverage, wCommitReplayed, H3, H5(b).
+func TestCommitQueuesObligationsAndCoversTheSourceRegion(t *testing.T) {
+ for _, from := range []string{"probing", "probed"} {
+ c := probedLanding(t)
+ c.Op.Phase = from
+ // The source box holds paths of the stay the move leaves: stamp S = 3.
+ c.Inventory = map[string]InventoryEntry{"ie-box": {Layout: "flat", Paths: map[string]uint64{"/data/a": 3}}}
+ stamp, session, gen := c.Holder.Stamp, c.Holder.Session, c.Holder.Generation
+ ids := 0
+ newID := func() string { ids++; return fmt.Sprintf("id%d", ids) }
+ src, stg := commitScopes(c)
+ if err := Commit(moveA, "ie", "ie-box", "uk", src, stg, newID)(c); err != nil {
+ t.Fatalf("from %s: %v", from, err)
+ }
+ if c.Op.Kind != KindLanding || c.Op.Phase != PhaseCommitted || c.Op.Exec != "uk-box" {
+ t.Fatalf("after commit: %+v", c.Op)
+ }
+ // H3: the holder's stamp, session and generation are the flip's, untouched.
+ if c.Holder.Stamp != stamp || c.Holder.Session != session || c.Holder.Generation != gen {
+ t.Fatalf("commit touched the holder: %+v", c.Holder)
+ }
+ k := obligationKinds(c)
+ if k["park-replica/ie"] != 1 || k["park-local/ie-box"] != 1 || k["park-replica/uk"] != 1 || len(c.Obligations) != 3 {
+ t.Fatalf("obligations: %+v", c.Obligations)
+ }
+ for _, o := range c.Obligations {
+ if o.Kind == "park-replica" && o.Region == "ie" && (o.Park == nil || o.Park.Location != c.Holder.Location || o.Owner != "region:ie") {
+ t.Fatalf("source park: %+v", o)
+ }
+ if o.Kind == "park-replica" && o.Region == "uk" && (o.Park == nil || o.Park.Location != stg.Location) {
+ t.Fatalf("staging park: %+v", o)
+ }
+ }
+ if !c.Holder.Admissions[0].Covered {
+ t.Fatal("the source-region predecessor is not covered (wCommitCoverage)")
+ }
+ if err := c.Validate(); err != nil {
+ t.Fatalf("the committed claim must validate: %v", err)
+ }
+ // Replay: nothing doubles and no ids are minted (wCommitReplayed).
+ before, usedIDs := mustJSON(t, c), ids
+ if err := Commit(moveA, "ie", "ie-box", "uk", src, stg, newID)(c); err != nil || ids != usedIDs || string(mustJSON(t, c)) != string(before) {
+ t.Fatalf("replay: %v ids %d->%d", err, usedIDs, ids)
+ }
+ }
+}
+
+// H5(b): the source's park-local is queued only when the source box lists
+// paths of the journalled source stamp S.
+func TestCommitQueuesTheSourceParkLocalOnlyForTheSourceStamp(t *testing.T) {
+ for name, inv := range map[string]map[string]InventoryEntry{
+ "no inventory": nil,
+ "other stamp only": {"ie-box": {Layout: "flat", Paths: map[string]uint64{"/data/a": 2}}},
+ "stamp on another box": {"uk-box": {Layout: "flat", Paths: map[string]uint64{"/data/a": 3}}},
+ } {
+ c := probedLanding(t)
+ c.Inventory = inv
+ ids := 0
+ newID := func() string { ids++; return fmt.Sprintf("id%d", ids) }
+ src, stg := commitScopes(c)
+ if err := Commit(moveA, "ie", "ie-box", "uk", src, stg, newID)(c); err != nil {
+ t.Fatalf("%s: %v", name, err)
+ }
+ k := obligationKinds(c)
+ if k["park-replica/ie"] != 1 || k["park-replica/uk"] != 1 || len(c.Obligations) != 2 {
+ t.Fatalf("%s: obligations %+v", name, c.Obligations)
+ }
+ if !c.Holder.Admissions[0].Covered {
+ t.Fatalf("%s: the source region is not covered", name)
+ }
+ }
+}
+
+// wCommitInterrupted: a commit that is refused leaves the claim as it was.
+func TestCommitRefusesAnythingButAProbingTargetLanding(t *testing.T) {
+ newID := func() string { return "x" }
+ type commitFn = func(c *Claim, src, stg ParkScope) func(*Claim) error
+ std := func(op, region, box string) commitFn {
+ return func(c *Claim, src, stg ParkScope) func(*Claim) error {
+ return Commit(op, region, box, "uk", src, stg, newID)
+ }
+ }
+ for name, tc := range map[string]struct {
+ mutate func(c *Claim, src, stg *ParkScope)
+ fn commitFn
+ want error
+ }{
+ "serving claim": {func(c *Claim, _, _ *ParkScope) { c.Op = Op{Kind: KindServing, Exec: "ie-box"} }, std(moveA, "ie", "ie-box"), ErrStaleOp},
+ "another op": {func(*Claim, *ParkScope, *ParkScope) {}, std(moveB, "ie", "ie-box"), ErrStaleOp},
+ "moving": {func(c *Claim, _, _ *ParkScope) { c.Op.Kind, c.Op.Phase, c.Op.Exec = KindMoving, PhaseSealed, "" }, std(moveA, "ie", "ie-box"), ErrWrongState},
+ "landing before probing": {func(c *Claim, _, _ *ParkScope) { c.Op.Phase = "restored" }, std(moveA, "ie", "ie-box"), ErrWrongState},
+ "exec not the target": {func(c *Claim, _, _ *ParkScope) { c.Op.Exec = "ie-box" }, std(moveA, "ie", "ie-box"), ErrWrongState},
+ "exec none": {func(c *Claim, _, _ *ParkScope) { c.Op.Exec = "" }, std(moveA, "ie", "ie-box"), ErrWrongState},
+ "source box is not the source": {func(*Claim, *ParkScope, *ParkScope) {}, std(moveA, "ie", "uk-box"), ErrWrongState},
+ "source region is the target": {func(*Claim, *ParkScope, *ParkScope) {}, std(moveA, "uk", "ie-box"), ErrWrongState},
+ "source park is not the holder's location": {func(_ *Claim, s, _ *ParkScope) { s.Location = "instances/a.go.example.com/other" }, std(moveA, "ie", "ie-box"), ErrMalformed},
+ "staging park is another lane": {func(c *Claim, _, g *ParkScope) { g.Location = "moves/" + c.Host + "/" + moveB }, std(moveA, "ie", "ie-box"), ErrMalformed},
+ } {
+ c := probedLanding(t)
+ c.Inventory = map[string]InventoryEntry{"ie-box": {Layout: "flat", Paths: map[string]uint64{"/data/a": 3}}}
+ src, stg := commitScopes(c)
+ tc.mutate(c, &src, &stg)
+ before := mustJSON(t, c)
+ if err := tc.fn(c, src, stg)(c); !errors.Is(err, tc.want) {
+ t.Fatalf("%s: got %v, want %v", name, err, tc.want)
+ }
+ if after := mustJSON(t, c); string(after) != string(before) {
+ t.Fatalf("%s mutated the claim:\nbefore %s\nafter %s", name, before, after)
+ }
+ }
+}