carlos / platform Public

host: the moves evidence provider answers for a bare-location holder

A move cuts only a holder at the activator's own lease location,
instances/<host> (ruling T5-lease), so every moved host's holder is
there. The provider refused that form, which would have blocked every
moved host's lane sweep (controller finding, Plan 12 review).

The holder may now be at the bare or the incarnation location, the two
forms the claim allows. VerifyRetirement judges residue outside the live
location by park.Decide's own partition: a 64-hex child directory is its
own incarnation location, everything else is the bare one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 94a8d9fbf7f1711f558572ba341b69cc6b8c5114 parent c8a04d7
2 files changed, +66 −7
  • internal/host/movesevidence.go +25 −4
  • internal/host/movesevidence_test.go +41 −3
diff --git a/internal/host/movesevidence.go b/internal/host/movesevidence.go
index 5393648f3..5f9d83bf2 100644
--- a/internal/host/movesevidence.go
+++ b/internal/host/movesevidence.go
@@ -37,6 +37,7 @@ package host
import (
"context"
+ "encoding/hex"
"errors"
"fmt"
"strings"
@@ -99,15 +100,32 @@ func (e movesRetirementEvidence) VerifyRetirement(ctx context.Context, o hostcla
if err != nil {
return "", time.Time{}, err
}
- live := r.location + "/"
for _, v := range versions {
- if strings.HasPrefix(v.Key, "instances/"+r.host+"/") && !strings.HasPrefix(v.Key, live) {
+ loc, ok := movesInstancesLocation(r.host, v.Key)
+ if ok && loc != r.location {
return "", time.Time{}, &ClaimDependencyError{Contracts: []string{movesLegacyCensus}}
}
}
return r.incarnation, r.at, nil
}
+// movesInstancesLocation is the location park.Decide files an
+// instances/<host>/ key under (park's locationOf): a 64-hex first segment
+// with a child is its own incarnation location, and everything else is the
+// bare instances/<host>. ok is false outside instances/<host>/.
+func movesInstancesLocation(host, key string) (string, bool) {
+ base := "instances/" + host
+ tail, ok := strings.CutPrefix(key, base+"/")
+ if !ok {
+ return "", false
+ }
+ first, _, child := strings.Cut(tail, "/")
+ if _, err := hex.DecodeString(first); child && err == nil && len(first) == 64 {
+ return base + "/" + first, true
+ }
+ return base, true
+}
+
// retired proves the newest retired move from the claim observation, the
// holder's record and the complete census, or refuses.
func (e movesRetirementEvidence) retired(ctx context.Context, o hostclaim.Observed, versions []lease.ObjectVersion) (movesRetired, error) {
@@ -132,9 +150,12 @@ func (e movesRetirementEvidence) retired(ctx context.Context, o hostclaim.Observ
if h.Family != "instance" || h.Key != provision.RecordKey(h.Account, h.App, host) {
return refuse(false, "the holder is not an instance record")
}
+ // A moved holder is at the bare location, the activator's own lease
+ // (ruling T5-lease, movescut.go writerLease); a scoped holder is at its
+ // incarnation's. Anything else is a location the census cannot place.
location := strings.TrimSuffix(h.Location, "/")
- if location != "instances/"+host+"/"+h.Incarnation {
- return movesRetired{}, &ClaimDependencyError{Contracts: []string{movesLegacyCensus}}
+ if location != "instances/"+host && location != "instances/"+host+"/"+h.Incarnation {
+ return refuse(true, "the holder is at %s, neither the bare nor the incarnation location", h.Location)
}
rec, err := e.Records(ctx, h.Account, h.App, host)
if err != nil {
diff --git a/internal/host/movesevidence_test.go b/internal/host/movesevidence_test.go
index a3d7afc9d..14a0f8bef 100644
--- a/internal/host/movesevidence_test.go
+++ b/internal/host/movesevidence_test.go
@@ -233,10 +233,11 @@ func TestVerifyRetirementRefusesLegacyResidue(t *testing.T) {
}
})
}
- t.Run("a holder in the legacy location form", func(t *testing.T) {
+ t.Run("an incarnation directory beside a bare holder", func(t *testing.T) {
f := newEvFixture(t)
- f.setClaim(t, func(c *hostclaim.Claim) { c.Holder.Location = "instances/" + evHost })
- f.bucket.Put("data", "instances/"+evHost+"/0000/db", []byte("live"))
+ f.bare(t)
+ f.bucket.Put("data", "instances/"+evHost+"/g-live/0000/db", []byte("live"))
+ f.bucket.Put("data", "instances/"+evHost+"/"+f.inc+"/g-old/0000/db", []byte("scoped"))
_, _, err := f.provider().VerifyRetirement(t.Context(), f.observe(t), f.census(t))
var dep *ClaimDependencyError
if !errors.As(err, &dep) {
@@ -245,6 +246,43 @@ func TestVerifyRetirementRefusesLegacyResidue(t *testing.T) {
})
}
+// bare puts the holder at the activator's own lease location,
+// instances/<host>: the only holder a move cuts (ruling T5-lease,
+// movescut.go writerLease), so every moved host's holder.
+func (f *evFixture) bare(t *testing.T) {
+ t.Helper()
+ f.location = "instances/" + evHost
+ f.setClaim(t, func(c *hostclaim.Claim) { c.Holder.Location = f.location })
+}
+
+// TestMovedHostAtABareLocationIsSwept (controller finding, Plan 12 review):
+// a moved host's holder is at the bare location, and its objects there are
+// its live location's, not residue outside it. The provider answers, and
+// the worker queues the moves/<host> lane chore.
+func TestMovedHostAtABareLocationIsSwept(t *testing.T) {
+ f := newEvFixture(t)
+ f.bare(t)
+ f.lane(evMoveID, "move.json")
+ f.lane(evMoveID, "replica/0000/db")
+ f.bucket.Put("data", f.location+"/g-live/0000/db", []byte("live"))
+ obs, census := f.observe(t), f.census(t)
+ inc, at, err := f.provider().VerifyRetirement(t.Context(), obs, census)
+ if err != nil || inc != f.inc || !at.Equal(evAt) {
+ t.Fatalf("VerifyRetirement = %q, %v, %v; want %q, %v", inc, at, err, f.inc, evAt)
+ }
+ proof, err := f.provider().VerifyMovesSweep(t.Context(), obs, census)
+ if err != nil || proof.Incarnation != f.inc || !proof.RetiredAt.Equal(evAt) {
+ t.Fatalf("proof = %+v, %v", proof, err)
+ }
+ if err := f.worker(t, evNow).Run(t.Context()); !errors.Is(err, park.ErrPending) {
+ t.Fatalf("the lane chore waits for its horizon: %v", err)
+ }
+ moves, ok := f.chores(t)["moves/"+evHost]
+ if !ok || moves.Incarnation != f.inc || !moves.EvidenceAt.Equal(evNow) {
+ t.Fatalf("chores = %+v; want the moves/<host> chore at the listing's end", f.chores(t))
+ }
+}
+
// TestMovesSweepProofRefusesALiveMove (K10, M8): no proof while a move op
// is on the claim, while the record carries a live move, or while the
// claim was released by a move the record has not closed.