carlos / platform Public

host: the prepared build re-checks the install's authority before every bucket write (Task 6 fix round 1, finding 1)

installAuthority re-reads the claim and holds it to this install (the
op at phase local with this box executing, the holder at the captured
stamp); installAuthorityStore runs it immediately before every write,
retries included, as region HA's authorityStore does, and never deletes.
A stale build whose authority moved writes nothing more and never wins
the create-only manifest.

Regression: authority moves to another executor between the first
object upload and the next write; the stale writer leaves one object
and no manifest, the step is not journalled and the record does not
move; once authority returns the installer completes, reusing that
object (one version per object).

Also the local_ header's step (6), which still said the manifest was
skipped until C8 existed (Minor).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev c92dfb1f4ad3ba99eaaa98c9044fbec29f6b4a39 parent fc07868
2 files changed, +154 −3
  • internal/host/movesinstall.go +67 −3
  • internal/host/movesland_test.go +87 −0
diff --git a/internal/host/movesinstall.go b/internal/host/movesinstall.go
index b835be64a..6a72ed6ce 100644
--- a/internal/host/movesinstall.go
+++ b/internal/host/movesinstall.go
@@ -237,6 +237,62 @@ func newInstallRun(s release.Store, w *movesWorker, rec provision.Record, src in
func (ir *installRun) cfg() moveCfg { return ir.w.cfg }
+// installAuthority is the install's authority re-check for the prepared
+// manifest's bucket writes (fix round 1, finding 1; region HA's
+// authorityStore pattern): a fresh read of the claim, the install's own
+// claim check at phase (the op at that phase with this box executing) and
+// the holder still at the captured stamp. It runs immediately before every
+// write attempt, retries included, so a delayed build whose executor,
+// phase or stamp moved writes nothing more.
+func (ir *installRun) installAuthority(ctx context.Context, captured hostguard.Captured, phase string) func() error {
+ check := ir.src.ClaimCheck(phase)
+ return func() error {
+ if err := ctx.Err(); err != nil {
+ return err
+ }
+ obs, err := hostclaim.Read(ctx, ir.s, ir.rec.Host)
+ if err != nil {
+ return err
+ }
+ if obs.Absent || obs.Claim == nil {
+ return fmt.Errorf("%w: %s has no claim", errLandingChanged, ir.rec.Host)
+ }
+ if err := check(obs.Claim); err != nil {
+ return err
+ }
+ if obs.Claim.Holder == nil || obs.Claim.Holder.Stamp != captured.Stamp {
+ return fmt.Errorf("%w: the claim's holder is not at the installation stamp %d", errLandingChanged, captured.Stamp)
+ }
+ return nil
+ }
+}
+
+// installAuthorityStore is the versioned store the prepared build writes
+// through: every write re-checks the install's authority first. Reads are
+// the store's own; the build never deletes.
+type installAuthorityStore struct {
+ lease.VersionedStore
+ check func() error
+}
+
+func (s installAuthorityStore) PutIfAbsent(ctx context.Context, bucket, key string, body []byte) (string, error) {
+ if err := s.check(); err != nil {
+ return "", err
+ }
+ return s.VersionedStore.PutIfAbsent(ctx, bucket, key, body)
+}
+
+func (s installAuthorityStore) CopyVersion(ctx context.Context, sb, sk, sv, db, dk string, createOnly bool) (string, string, error) {
+ if err := s.check(); err != nil {
+ return "", "", err
+ }
+ return s.VersionedStore.CopyVersion(ctx, sb, sk, sv, db, dk, createOnly)
+}
+
+func (installAuthorityStore) DeleteVersion(context.Context, string, string, string) error {
+ return fmt.Errorf("%w: the prepared build never deletes", errLandingChanged)
+}
+
// filesManifestBuilt reports whether the journal records the prepared
// files manifest as built: the step done with a binding, not the "skipped"
// value journals written before C8 existed, which a replay builds (ruling
@@ -648,8 +704,13 @@ func (ir *installRun) published(ctx context.Context) (bool, error) {
// finding 1; a retry clears only its staging); (4) the new paths are
// registered under the holder's stamp; (5) the timers table is the sealed
// snapshot with its registrations, the carried columns are set and the
-// restore-verification entry removed; (6) the prepared files manifest,
-// skipped with a log line until C8 exists; (7) the files directory is synced
+// restore-verification entry removed; (6) the prepared files manifest (C8,
+// Task 6): the placed tree, <host>.env and the wake state as create-only
+// objects and one create-only sequence-0 manifest under files/g-<gen>/ in
+// the location, each write re-checking the install's authority, the
+// binding journalled with the manifest's digest and exact version, and a
+// manifest already there that is not this install's refused
+// (RollbackManifestOccupied); (7) the files directory is synced
// and .files-gen written last, so its presence vouches for every step
// before it.
//
@@ -809,7 +870,10 @@ func (ir *installRun) local_(ctx context.Context) (bool, error) {
if err != nil {
return false, err
}
- pm, err := buildPreparedManifest(ctx, vs, cfg.OwnBucket, holder.Location, gen, id, captured.Stamp, dir, envPath, hostfiles.WakeStateOf(host, rows, src.NextDue, src.Replication))
+ // Every write re-checks the authority first (installAuthority): a
+ // stale build never wins the create-only manifest.
+ authority := installAuthorityStore{VersionedStore: vs, check: ir.installAuthority(ctx, captured, phase)}
+ pm, err := buildPreparedManifest(ctx, authority, cfg.OwnBucket, holder.Location, gen, id, captured.Stamp, dir, envPath, hostfiles.WakeStateOf(host, rows, src.NextDue, src.Replication))
if errors.Is(err, hostfiles.ErrManifestExists) {
log.Printf("moves: %s: the prepared files manifest of %s is not this install's: %v", host, id, err)
return ir.refuse(ctx, refuseManifestOccupied)
diff --git a/internal/host/movesland_test.go b/internal/host/movesland_test.go
index 51fede800..ece16725a 100644
--- a/internal/host/movesland_test.go
+++ b/internal/host/movesland_test.go
@@ -11,6 +11,7 @@ import (
"log/slog"
"os"
"path/filepath"
+ "slices"
"sort"
"strings"
"sync"
@@ -1449,6 +1450,92 @@ func TestLocalRefusesAForeignPreparedManifest(t *testing.T) {
}
}
+// moveAuthority hands the landing's op to exec in the claim, as a
+// replacement or a return of the executor would.
+func moveAuthority(t *testing.T, h *landingHarness, exec string) {
+ t.Helper()
+ obs, err := hostclaim.Read(context.Background(), h.s, p1Host)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := hostclaim.Update(context.Background(), h.s, obs, func(c *hostclaim.Claim) error {
+ c.Op.Exec = exec
+ if !slices.Contains(c.Op.Participants, exec) {
+ c.Op.Participants = append(c.Op.Participants, exec)
+ }
+ return nil
+ }); err != nil {
+ t.Fatal(err)
+ }
+}
+
+// The prepared build re-checks the installation's authority (the op at
+// phase local with this box executing, the holder at the captured stamp)
+// immediately before every bucket write, retries included (fix round 1,
+// finding 1; region HA's authorityStore). Authority moves to another
+// executor between the first object upload and the next write: the stale
+// writer writes nothing more, no manifest exists, and the step is not
+// journalled. Once authority returns, the installer completes, reusing
+// the one object the stale writer left.
+func TestPreparedBuildRechecksAuthorityBeforeEveryWrite(t *testing.T) {
+ h := newLandingHarness(t)
+ h.flip(t)
+ c := h.claim(t)
+ prefix := movesFilesPrefix(c.Holder.Location, p1ID)
+ moved := false
+ h.vs.deny = func(op, bucket, key string) error {
+ if op == "put-if-absent" && strings.HasPrefix(key, prefix+"objects/") && !moved {
+ moved = true
+ moveAuthority(t, h, "other-box")
+ }
+ return nil
+ }
+ h.land(t)
+ if !moved {
+ t.Fatal("the build never uploaded an object")
+ }
+ objects := 0
+ for k := range h.f.buckets["data-uk"] {
+ if strings.HasPrefix(k, prefix+"objects/") {
+ objects++
+ }
+ if k == hostfiles.ManifestKey(prefix) {
+ t.Fatal("the stale writer published the manifest")
+ }
+ }
+ if objects != 1 {
+ t.Fatalf("%d objects after authority moved, want the one uploaded before it", objects)
+ }
+ if _, ok := h.landJournal(t).Steps[landStepFilesManifest]; ok {
+ t.Fatal("the step was journalled without the manifest")
+ }
+ if m := h.read(t, p1Host).Move; m.LandingPhase != provision.LandLocal || m.Reason != "" {
+ t.Fatalf("the record moved: %+v", m)
+ }
+ moveAuthority(t, h, "uk-box")
+ h.vs.deny = nil
+ h.freshTarget()
+ h.land(t)
+ h.installed(t)
+ pm, m, _ := landedPreparedManifest(t, h)
+ if pm.Op != p1ID || m.Seq != 0 {
+ t.Fatalf("the authorized installer did not complete: %+v", pm)
+ }
+ vs, err := h.vs.ListVersions(context.Background(), "data-uk", prefix+"objects/")
+ if err != nil {
+ t.Fatal(err)
+ }
+ seen := map[string]int{}
+ for _, v := range vs {
+ seen[v.Key]++
+ }
+ for k, n := range seen {
+ if n != 1 {
+ t.Fatalf("%s has %d versions: the replay did not reuse it", k, n)
+ }
+ }
+}
+
// Nothing is written under the location's files prefix before the
// files_manifest step: the manifest follows the placed files, the env
// file, the timers and the columns (the step order of local, §7.6).