carlos / platform Public

zdd-4 task 6: root staging checks a reused staged copy's digest and never overwrites a referenced one

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 1cb3b09f7c4d2cfeb87872cd52cb58676e5d0eae parent 8cf3efd
2 files changed, +459 −56
  • internal/release/adopt.go +184 −56
  • internal/release/stage_reuse_test.go +275 −0
diff --git a/internal/release/adopt.go b/internal/release/adopt.go
index 5fade535d..ee19ae754 100644
--- a/internal/release/adopt.go
+++ b/internal/release/adopt.go
@@ -84,6 +84,15 @@ type passConfig struct {
// proof is D3's gate (WithProofGate, proofgate.go). nil, the default for
// every existing caller and test, means no proof is read or required.
proof *ProofGate
+
+ // pins is WithPinSource's provider: staged copies that something beyond
+ // the registry rows, the live links and the generations file still
+ // refers to. nil adds nothing.
+ pins PinSource
+
+ // stage is what ensureStagedChecked needs to judge a staged copy it is
+ // about to reuse (spec §4.2). PassFilter sets it once per pass.
+ stage stageCheck
}
// NagExpiredCanaries opts a Pass/PassFilter call into spec §5's nag: one
@@ -279,6 +288,13 @@ func WithProofGate(g *ProofGate) PassOption {
return func(c *passConfig) { c.proof = g }
}
+// WithPinSource adds src's references to the pin set a reused staged copy is
+// checked against (spec §4.2), on top of the live links, the registry rows
+// and the generations file, which every pass with a data dir reads itself.
+func WithPinSource(src PinSource) PassOption {
+ return func(c *passConfig) { c.pins = src }
+}
+
// stageBlocked reports whether err is the version store refusing a write to
// THIS uid, rather than any of the many other ways staging fails. errors.Is
// unwraps the *os.PathError that mkdir/openat return.
@@ -366,6 +382,48 @@ func PassFilter(ctx context.Context, reg *registry.Registry, s Store, pub ed2551
}
cfg.selected = selected
+ // The reuse check's view of what refers to a staged copy (spec §4.2). It
+ // is asked only when a staged copy turns out to differ from its canonical
+ // release, so a healthy pass never pays for it, and it reads fresh then,
+ // so a host this pass has already repointed counts.
+ cfg.stage = stageCheck{
+ verified: map[string]bool{},
+ refs: func() (StagedPaths, error) {
+ rows, err := reg.List()
+ if err != nil {
+ return nil, fmt.Errorf("list routes: %w", err)
+ }
+ pins, err := BoxPins(optDir, rows)
+ if err != nil {
+ return nil, err
+ }
+ // Live and pending generations, from the file plan 5's agent
+ // writes into the data dir (index amendments). Every production
+ // pass passes the data dir as BakeDir: root's RunAdopt, the
+ // agent's loops, and cold materialization. A pass with no data
+ // dir (tests) has no agent beside it to ask.
+ if cfg.bakeDir != "" {
+ gen, err := GenerationPins(GenerationsPath(cfg.bakeDir), func() ([]registry.Route, error) { return rows, nil }).StagedPins(ctx, optDir)
+ if err != nil {
+ return nil, err
+ }
+ for p := range gen {
+ pins.add(p)
+ }
+ }
+ if cfg.pins != nil {
+ more, err := cfg.pins.StagedPins(ctx, optDir)
+ if err != nil {
+ return nil, err
+ }
+ for p := range more {
+ pins.add(p)
+ }
+ }
+ return pins, nil
+ },
+ }
+
// Resolve every distinct (Account, App, Channel) once. Verification
// failures kill the group (nil entry), not the pass.
//
@@ -1089,7 +1147,7 @@ func adoptHost(ctx context.Context, work hostwrite.Work, reg *registry.Registry,
if !admitted {
return
}
- m, err := ensureStaged(ctx, s, optDir, RouteAccount(rt), rt.App, ver, rt.Host)
+ m, err := ensureStagedChecked(ctx, s, optDir, RouteAccount(rt), rt.App, ver, rt.Host, cfg.stage)
if err != nil {
fmt.Printf(" %s: %v\n", rt.Host, err)
// A version this uid cannot write is the one staging failure a
@@ -1579,6 +1637,18 @@ func capabilityPlan(rt registry.Route, m *Manifest, canRestart bool) (caps.Plan,
}, nil
}
+// stageCheck is what ensureStagedChecked needs to judge a staged copy it is
+// about to reuse (spec §4.2).
+type stageCheck struct {
+ // refs reports the staged copies something on this box still refers to.
+ // nil, or an error, means unknown, and unknown never permits a restage.
+ refs func() (StagedPaths, error)
+ // verified holds staged paths whose bytes already matched their canonical
+ // release this pass, so N hosts moving onto one build hash it once. nil
+ // remembers nothing.
+ verified map[string]bool
+}
+
// ensureStaged makes ver's build of app available in the version store,
// downloading and sha256-verifying it if needed. Idempotent across hosts in
// one pass. The manifest supplies the sha; the bytes are refused unless they
@@ -1590,9 +1660,10 @@ func capabilityPlan(rt registry.Route, m *Manifest, canRestart bool) (caps.Plan,
// current: a fresh download writes the sibling alongside the binary, and a
// binary that is ALREADY staged but missing its sibling gets one written
// without a re-download — the back-fill for every version staged before this
-// sibling existed. That sibling is a deliberately LOSSY re-marshal of the
-// bucket's manifest (see StageManifest's doc); the bucket object stays
-// canonical.
+// sibling existed. For copies staged by this binary or later the sibling is
+// the canonical manifest's exact bytes; only a copy an older binary staged
+// carries a lossy re-marshal (see ReadStagedManifest). The bucket object stays
+// canonical either way.
//
// The back-fill's own fetch failure is NOT fatal when the binary is already
// staged (2026-08-03 review, I2): this function's whole promise is "the
@@ -1615,74 +1686,131 @@ func capabilityPlan(rt registry.Route, m *Manifest, canRestart bool) (caps.Plan,
// and adoptHost turns that into a refusal when — and only when — capabilities
// are armed for the route. Every other caller keeps today's contract exactly:
// an error means "the binary is not on disk", nil means it is.
+//
+// A version directory that is already staged is no longer trusted by name
+// (zero-downtime deploys spec §4.2). A version name can be shipped again with
+// different bytes once retention has pruned it, and the staged path is keyed
+// by name alone. So a staged binary is hashed and compared with the canonical
+// manifest's digest for this platform:
+// - a match reuses it, as before;
+// - a mismatch restages over it only when nothing on the box refers to it
+// (chk.refs: live links, Version, PrevVersion, the generations file,
+// and whatever WithPinSource adds); otherwise the version is refused with
+// ErrVersionReused, so a copy under another instance's build never
+// changes underneath it;
+// - a canonical manifest that cannot be read keeps today's answer: the copy
+// is used as staged, because a box cannot compare what it cannot read,
+// and the agent runs any attempt onto it in hold (CanonicalDemand).
+//
+// ensureStaged is ensureStagedChecked with the pin set this package can read
+// with no registry in hand: the live links. The tests that stage one version
+// directly call it.
func ensureStaged(ctx context.Context, s Store, optDir, acct, app, ver, host string) (*Manifest, error) {
- staged := false
- if _, err := os.Stat(VersionPath(optDir, acct, ver, app)); err == nil {
- staged = true
- if _, err := os.Stat(StagedManifestPath(optDir, acct, ver, app)); err == nil {
- // Binary and sibling both present: the sibling IS this function's
- // manifest in hand on this path — read once, locally, with no
- // bucket round-trip, which is the whole point of staging it. This
- // is not the re-read the doc comment above forbids: it happens
- // only where the file is known to exist, and a read that fails or
- // refuses (a rolled-back platform binary meeting a capability
- // block a newer one wrote) falls through to the fetch below
- // rather than answering "no capabilities".
- m, err := ReadStagedManifest(optDir, acct, ver, app)
- if err == nil && m != nil {
- return m, nil
- }
- if err != nil {
- fmt.Printf(" %s: staged manifest for %s: %v — re-fetching it\n", host, ver, err)
- }
- }
- // Binary staged, sibling missing: fall through to fetch the manifest
- // and write ONLY the sibling. This is the one path where an
- // already-staged version's manifest is re-fetched — kept cheap by
- // only ever taking it when the sibling itself is absent.
- }
+ return ensureStagedChecked(ctx, s, optDir, acct, app, ver, host, stageCheck{
+ refs: func() (StagedPaths, error) { return BoxPins(optDir, nil) },
+ })
+}
+
+// ensureStagedChecked is ensureStaged with the reuse check's references
+// supplied by chk; see ensureStaged's doc for the whole contract.
+func ensureStagedChecked(ctx context.Context, s Store, optDir, acct, app, ver, host string, chk stageCheck) (*Manifest, error) {
+ path := VersionPath(optDir, acct, ver, app)
+ _, statErr := os.Stat(path)
+ staged := statErr == nil
m, raw, err := GetManifestRaw(ctx, s, acct, app, ver)
if err != nil {
- if staged {
- // The binary IS staged — this function's whole promise — so a
- // fetch failure here is only ever worth a log line; see the
- // doc comment above and backfillManifest's per-pass retry.
- fmt.Printf(" %s: back-fill manifest for %s: %v\n", host, ver, err)
- return nil, nil
+ if !staged {
+ return nil, fmt.Errorf("manifest for %s/%s: %w", app, ver, err)
}
- return nil, fmt.Errorf("manifest for %s/%s: %w", app, ver, err)
+ // The binary IS staged, which is this function's whole promise, and
+ // the canonical manifest cannot be read to compare it with. A sibling
+ // already on disk is the manifest in hand, exactly as before this
+ // check existed. Without one, a fetch failure is only worth a line;
+ // backfillManifest retries on every pass.
+ sib, serr := ReadStagedManifest(optDir, acct, ver, app)
+ if serr == nil && sib != nil {
+ return sib, nil
+ }
+ if serr != nil {
+ fmt.Printf(" %s: staged manifest for %s: %v\n", host, ver, serr)
+ }
+ fmt.Printf(" %s: back-fill manifest for %s: %v\n", host, ver, err)
+ return nil, nil
}
- if !staged {
- art, ok := m.Artifacts[adoptPlatform]
- if !ok {
- // A box facing a release with no build for its platform: skip, never
- // install the wrong arch. The next release ships it and the box
- // catches up.
- return nil, fmt.Errorf("release %s has no %s build — skipping", ver, adoptPlatform)
+ art, hasBuild := m.Artifacts[adoptPlatform]
+ if staged {
+ if chk.verified[path] {
+ repairSibling(optDir, acct, app, ver, host, raw)
+ return m, nil
}
- body, _, err := GetQualified(ctx, s, ArtifactKey(acct, app, ver, art.File))
- if err != nil {
- return nil, fmt.Errorf("download %s: %w", ver, err)
+ sum, herr := FileSHA256(path)
+ if herr != nil {
+ return nil, fmt.Errorf("staged %s/%s: %w", app, ver, herr)
+ }
+ if hasBuild && strings.EqualFold(sum, art.SHA256) {
+ if chk.verified != nil {
+ chk.verified[path] = true
+ }
+ repairSibling(optDir, acct, app, ver, host, raw)
+ return m, nil
}
- if got := etagOf(body); got != art.SHA256 {
- return nil, fmt.Errorf("download %s: sha256 mismatch (refusing to stage)", ver)
+ // Different bytes under the same name. Overwrite only a copy nothing
+ // refers to: overwriting a referenced one would change the bytes under
+ // another instance's build (spec §4.2).
+ if chk.refs == nil {
+ return nil, fmt.Errorf("%s/%s: the staged copy differs from the release and what refers to it is unknown, so it was not restaged", app, ver)
}
- if err := StageArtifact(optDir, acct, ver, app, body); err != nil {
- return nil, err
+ pins, perr := chk.refs()
+ if perr != nil {
+ return nil, fmt.Errorf("%s/%s: the staged copy differs from the release and what refers to it is unknown (%v), so it was not restaged", app, ver, perr)
+ }
+ if pins.Has(path) {
+ return nil, fmt.Errorf("%s/%s: %w", app, ver, ErrVersionReused)
}
+ fmt.Printf(" %s: staged %s differs from the release in the store and nothing on this box refers to it; restaging\n", host, ver)
+ }
+ if !hasBuild {
+ // A box facing a release with no build for its platform: skip, never
+ // install the wrong arch. The next release ships it and the box
+ // catches up.
+ return nil, fmt.Errorf("release %s has no %s build — skipping", ver, adoptPlatform)
}
- // The sibling is the CANONICAL bytes (spec §4.2): what the bucket holds,
- // unknown fields and all, never a re-marshal by whichever binary staged
- // it. The write is best-effort: a failure here is not fatal to staging the
- // binary. A binary with an unreadable sibling is the leave-alone state,
- // which is safe (ReadStagedManifest's nil-nil-on-absence contract), and
- // backfillManifest gets another chance at it on every later pass.
+ body, _, err := GetQualified(ctx, s, ArtifactKey(acct, app, ver, art.File))
+ if err != nil {
+ return nil, fmt.Errorf("download %s: %w", ver, err)
+ }
+ if got := etagOf(body); got != art.SHA256 {
+ return nil, fmt.Errorf("download %s: sha256 mismatch (refusing to stage)", ver)
+ }
+ if err := StageArtifact(optDir, acct, ver, app, body); err != nil {
+ return nil, err
+ }
+ if chk.verified != nil {
+ chk.verified[path] = true
+ }
+ // The sibling is the CANONICAL bytes (spec §4.2), and a restage replaces
+ // whatever sibling the stale copy had. Best-effort: a failure here is not
+ // fatal to staging the binary, and backfillManifest gets another chance
+ // at it on every later pass.
if err := StageManifest(optDir, acct, ver, app, raw); err != nil {
fmt.Printf(" %s: stage manifest sibling for %s: %v\n", host, ver, err)
}
return m, nil
}
+// repairSibling writes the canonical bytes beside a reused binary whose
+// sibling is absent or does not read: the back-fill for versions staged
+// before the sibling existed, and the repair for one a different binary
+// wrote. A sibling that reads is left alone, as before. Best-effort.
+func repairSibling(optDir, acct, app, ver, host string, raw []byte) {
+ if sib, err := ReadStagedManifest(optDir, acct, ver, app); err == nil && sib != nil {
+ return
+ }
+ if err := StageManifest(optDir, acct, ver, app, raw); err != nil {
+ fmt.Printf(" %s: stage manifest sibling for %s: %v\n", host, ver, err)
+ }
+}
+
// backfillManifest is adoptHost's per-pass repair for a LIVE version's
// staged sibling (2026-08-03 review, C1). ensureStaged's own back-fill only
// fires from the call inside adoptHost that sits behind the cur == ver
diff --git a/internal/release/stage_reuse_test.go b/internal/release/stage_reuse_test.go
new file mode 100644
index 000000000..6316dd364
--- /dev/null
+++ b/internal/release/stage_reuse_test.go
@@ -0,0 +1,275 @@
+package release
+
+import (
+ "bytes"
+ "context"
+ "crypto/ed25519"
+ "errors"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/carlosframework/platform/internal/registry"
+)
+
+// reuseFixture: h1 follows canary/x and serves v1. Then v2 is shipped as
+// "new v2" while a stale copy of v2 ("old v2", from an earlier release under
+// the same name that retention has since pruned) is already staged, and the
+// channel moves to v2.
+type reuseFixture struct {
+ s Store
+ priv ed25519.PrivateKey
+ pub ed25519.PublicKey
+ opt string
+ reg *registry.Registry
+}
+
+func newReuseFixture(t *testing.T) reuseFixture {
+ t.Helper()
+ ctx := context.Background()
+ f := reuseFixture{s: NewDirStore(t.TempDir()), opt: t.TempDir(), reg: testRegistry(t)}
+ f.priv, f.pub = testKeys(t)
+ shipRelease(t, f.s, "bab", "myapp", "v1", adoptPlatform, []byte("v1"))
+ if err := Promote(ctx, f.s, f.priv, "bab", "myapp", "canary/x", "v1"); err != nil {
+ t.Fatal(err)
+ }
+ addRoute(t, f.reg, "h1.example.com", "myapp", "canary/x", false)
+ if err := Pass(ctx, f.reg, f.s, f.pub, f.opt, func(string) error { return nil }); err != nil {
+ t.Fatal(err)
+ }
+ if v, _ := LiveVersion(f.opt, "h1.example.com"); v != "v1" {
+ t.Fatalf("setup: live = %q, want v1", v)
+ }
+ if err := StageArtifact(f.opt, "bab", "v2", "myapp", []byte("old v2")); err != nil {
+ t.Fatal(err)
+ }
+ shipRelease(t, f.s, "bab", "myapp", "v2", adoptPlatform, []byte("new v2"))
+ if err := Promote(ctx, f.s, f.priv, "bab", "myapp", "canary/x", "v2"); err != nil {
+ t.Fatal(err)
+ }
+ return f
+}
+
+func (f reuseFixture) pass(t *testing.T, opts ...PassOption) string {
+ t.Helper()
+ var out bytes.Buffer
+ withCapturedStdout(t, &out, func() {
+ if err := Pass(context.Background(), f.reg, f.s, f.pub, f.opt, func(string) error { return nil }, opts...); err != nil {
+ t.Fatalf("Pass: %v", err)
+ }
+ })
+ return out.String()
+}
+
+func (f reuseFixture) stagedV2(t *testing.T) string {
+ t.Helper()
+ b, err := os.ReadFile(VersionPath(f.opt, "bab", "v2", "myapp"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ return string(b)
+}
+
+// assertRefused: h1 stays on v1, the stale copy is untouched, and the pass
+// said why.
+func (f reuseFixture) assertRefused(t *testing.T, out, why string) {
+ t.Helper()
+ if got := f.stagedV2(t); got != "old v2" {
+ t.Fatalf("the referenced copy was overwritten: it now holds %q", got)
+ }
+ if v, _ := LiveVersion(f.opt, "h1.example.com"); v != "v1" {
+ t.Fatalf("h1 live = %q, want v1: the reused name must not install", v)
+ }
+ if rt, _ := f.reg.Get("h1.example.com"); rt == nil || rt.Version != "v1" {
+ t.Fatalf("h1 row = %+v, want Version v1", rt)
+ }
+ if !strings.Contains(out, why) {
+ t.Fatalf("pass printed %q, want it to contain %q", out, why)
+ }
+}
+
+// Spec §12 "Restage refusal": an unreferenced stale copy is restaged.
+func TestUnreferencedStaleCopyIsRestaged(t *testing.T) {
+ f := newReuseFixture(t)
+ out := f.pass(t)
+ if got := f.stagedV2(t); got != "new v2" {
+ t.Fatalf("staged v2 = %q, want the canonical bytes", got)
+ }
+ if v, _ := LiveVersion(f.opt, "h1.example.com"); v != "v2" {
+ t.Fatalf("h1 live = %q, want v2", v)
+ }
+ raw, _, err := f.s.Get(context.Background(), ManifestKey("bab", "myapp", "v2"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ sib, err := os.ReadFile(StagedManifestPath(f.opt, "bab", "v2", "myapp"))
+ if err != nil || !bytes.Equal(sib, raw) {
+ t.Fatalf("sibling = %s, %v; want the canonical manifest bytes", sib, err)
+ }
+ if !strings.Contains(out, "restaging") {
+ t.Fatalf("pass printed %q; want a line saying the copy was restaged", out)
+ }
+}
+
+// A row's PrevVersion is a reference (spec §4.2's pin set).
+func TestStaleCopyARegistryRowNamesIsNotOverwritten(t *testing.T) {
+ f := newReuseFixture(t)
+ // h2 is mid-provision, so no pass adopts it, but its row still names v2.
+ if err := f.reg.Upsert(registry.Route{Host: "h2.example.com", Kind: "instance", App: "myapp", Channel: "canary/x", Status: "creating"}); err != nil {
+ t.Fatal(err)
+ }
+ if err := f.reg.SetVersions("h2.example.com", "v1", "v2"); err != nil {
+ t.Fatal(err)
+ }
+ f.assertRefused(t, f.pass(t), ErrVersionReused.Error())
+}
+
+func TestStaleCopyALiveLinkNamesIsNotOverwritten(t *testing.T) {
+ f := newReuseFixture(t)
+ if err := PointLive(f.opt, "h2.example.com", "bab", "v2", "myapp"); err != nil {
+ t.Fatal(err)
+ }
+ f.assertRefused(t, f.pass(t), ErrVersionReused.Error())
+}
+
+// GoodVersion and live generations arrive through a PinSource (plan 5).
+func TestStaleCopyAPinSourceNamesIsNotOverwritten(t *testing.T) {
+ f := newReuseFixture(t)
+ src := PinSourceFunc(func(context.Context, string) (StagedPaths, error) {
+ return StagedPaths{VersionPath(f.opt, "bab", "v2", "myapp"): true}, nil
+ })
+ f.assertRefused(t, f.pass(t, WithPinSource(src)), ErrVersionReused.Error())
+}
+
+// A generation running v2 on another host, as plan 5's agent records it.
+func TestAGenerationsFileEntryProtectsAStagedCopy(t *testing.T) {
+ f := newReuseFixture(t)
+ if err := f.reg.Upsert(registry.Route{Host: "h2.example.com", Kind: "instance", App: "myapp", Channel: "canary/x", Status: "creating"}); err != nil {
+ t.Fatal(err)
+ }
+ dataDir := t.TempDir()
+ if err := WriteGenerations(GenerationsPath(dataDir), Generations{Hosts: map[string][]string{"h2.example.com": {"v2"}}}); err != nil {
+ t.Fatal(err)
+ }
+ f.assertRefused(t, f.pass(t, BakeDir(dataDir)), ErrVersionReused.Error())
+}
+
+// Review Focus 5: a generations file root cannot read is an incomplete pin
+// set, and no restage happens that pass.
+func TestAnUnreadableGenerationsFileNeverPermitsARestage(t *testing.T) {
+ f := newReuseFixture(t)
+ dataDir := t.TempDir()
+ path := GenerationsPath(dataDir)
+ if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.WriteFile(path, []byte(`{"schema":1,"hosts":{"h2.exa`), 0o644); err != nil {
+ t.Fatal(err)
+ }
+ f.assertRefused(t, f.pass(t, BakeDir(dataDir)), "what refers to it is unknown")
+}
+
+// Every box before plan 5: no file is no generations, and an unreferenced
+// stale copy is restaged as usual.
+func TestAnAbsentGenerationsFileIsNoGenerations(t *testing.T) {
+ f := newReuseFixture(t)
+ f.pass(t, BakeDir(t.TempDir()))
+ if got := f.stagedV2(t); got != "new v2" {
+ t.Fatalf("staged v2 = %q, want it restaged", got)
+ }
+}
+
+func TestUnknownReferencesNeverPermitARestage(t *testing.T) {
+ f := newReuseFixture(t)
+ src := PinSourceFunc(func(context.Context, string) (StagedPaths, error) {
+ return nil, errors.New("generation report unreadable")
+ })
+ f.assertRefused(t, f.pass(t, WithPinSource(src)), "what refers to it is unknown")
+}
+
+// Two hosts moving onto one stale build in one pass: restaged once, both move.
+func TestOneRestageServesEveryHostInThePass(t *testing.T) {
+ f := newReuseFixture(t)
+ addRoute(t, f.reg, "h3.example.com", "myapp", "canary/x", false)
+ out := f.pass(t)
+ if n := strings.Count(out, "restaging"); n != 1 {
+ t.Fatalf("restaged %d times, want once:\n%s", n, out)
+ }
+ for _, h := range []string{"h1.example.com", "h3.example.com"} {
+ if v, _ := LiveVersion(f.opt, h); v != "v2" {
+ t.Fatalf("%s live = %q, want v2", h, v)
+ }
+ }
+}
+
+// Unchanged behaviour: a reused copy that matches is not downloaded again.
+func TestMatchingStagedCopyIsReusedWithoutADownload(t *testing.T) {
+ s := NewDirStore(t.TempDir())
+ ctx := context.Background()
+ priv, pub := testKeys(t)
+ opt := t.TempDir()
+ reg := testRegistry(t)
+ shipRelease(t, s, "bab", "myapp", "v2", adoptPlatform, []byte("v2 bytes"))
+ if err := Promote(ctx, s, priv, "bab", "myapp", "canary/x", "v2"); err != nil {
+ t.Fatal(err)
+ }
+ if err := StageArtifact(opt, "bab", "v2", "myapp", []byte("v2 bytes")); err != nil {
+ t.Fatal(err)
+ }
+ old := time.Unix(1_600_000_000, 0)
+ bin := VersionPath(opt, "bab", "v2", "myapp")
+ if err := os.Chtimes(bin, old, old); err != nil {
+ t.Fatal(err)
+ }
+ addRoute(t, reg, "h1.example.com", "myapp", "canary/x", false)
+ if err := Pass(ctx, reg, s, pub, opt, func(string) error { return nil }); err != nil {
+ t.Fatal(err)
+ }
+ if v, _ := LiveVersion(opt, "h1.example.com"); v != "v2" {
+ t.Fatalf("live = %q, want v2", v)
+ }
+ if info, err := os.Stat(bin); err != nil || !info.ModTime().Equal(old) {
+ t.Fatalf("the matching copy was rewritten (mtime %v, %v)", info.ModTime(), err)
+ }
+}
+
+// Review Focus 3: when the bucket cannot answer for the manifest, a staged
+// copy keeps today's answer. It is used, never restaged, and nothing claims a
+// mismatch the box could not establish.
+func TestUnreadableManifestKeepsTodaysAnswerForAStagedCopy(t *testing.T) {
+ dir := t.TempDir()
+ s := NewDirStore(dir)
+ ctx := context.Background()
+ priv, pub := testKeys(t)
+ opt := t.TempDir()
+ reg := testRegistry(t)
+ shipRelease(t, s, "bab", "myapp", "v2", adoptPlatform, []byte("new v2"))
+ if err := Promote(ctx, s, priv, "bab", "myapp", "canary/x", "v2"); err != nil {
+ t.Fatal(err)
+ }
+ if err := StageArtifact(opt, "bab", "v2", "myapp", []byte("old v2")); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.Remove(filepath.Join(dir, filepath.FromSlash(ManifestKey("bab", "myapp", "v2")))); err != nil {
+ t.Fatal(err)
+ }
+ addRoute(t, reg, "h1.example.com", "myapp", "canary/x", false)
+ var out bytes.Buffer
+ withCapturedStdout(t, &out, func() {
+ if err := Pass(ctx, reg, s, pub, opt, func(string) error { return nil }); err != nil {
+ t.Fatal(err)
+ }
+ })
+ if v, _ := LiveVersion(opt, "h1.example.com"); v != "v2" {
+ t.Fatalf("live = %q, want v2 (today's answer)", v)
+ }
+ b, _ := os.ReadFile(VersionPath(opt, "bab", "v2", "myapp"))
+ if string(b) != "old v2" {
+ t.Fatalf("staged v2 = %q: nothing may be restaged from a manifest the box could not read", b)
+ }
+ if strings.Contains(out.String(), "restaging") || strings.Contains(out.String(), ErrVersionReused.Error()) {
+ t.Fatalf("pass claimed a mismatch it could not establish:\n%s", out.String())
+ }
+}