carlos / platform Public

zdd-4 third fix wave: pins and the restage check compare fully resolved real paths (N4, R19 item 5)

BoxPins also pins filepath.EvalSymlinks of every live chain's end (not
existing ends the chain as before; any other error fails closed), and
ensureStagedChecked asks StagedPaths.Refers, which compares the
candidate's real path as well as its name. A symlinked opt dir with
live/h -> ../current -> versions/v1/app (two relative hops) no longer
lets a re-shipped v1 be restaged over the running copy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev e3b4f58c977799e81ce5bfb6c99d374fbfd93e0a parent 5a2d352
4 files changed, +107 −5
  • internal/release/adopt.go +7 −1
  • internal/release/pins.go +9 −3
  • internal/release/pins_test.go +51 −0
  • internal/release/stage_reuse_test.go +40 −1
diff --git a/internal/release/adopt.go b/internal/release/adopt.go
index 800b76e85..53daf3fce 100644
--- a/internal/release/adopt.go
+++ b/internal/release/adopt.go
@@ -1799,7 +1799,13 @@ func ensureStagedChecked(ctx context.Context, s Store, optDir, acct, app, ver, h
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) {
+ // By the copy's real path as well as its name, so a symlinked opt dir
+ // cannot make a pinned copy look unpinned (Astra r3 N4).
+ referred, rerr := pins.Refers(path)
+ if rerr != 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, rerr)
+ }
+ if referred {
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)
diff --git a/internal/release/pins.go b/internal/release/pins.go
index bc86e2e52..ebbb3472a 100644
--- a/internal/release/pins.go
+++ b/internal/release/pins.go
@@ -83,9 +83,9 @@ func (f PinSourceFunc) StagedPins(ctx context.Context, optDir string) (StagedPat
// swap's <host>.tmp included, as LivePins includes it), so a live link that
// reaches a staged copy through another link still pins that copy (a
// relative hop pinned at the real path the OS reaches, and at its lexical
-// join too when the two differ), and the
-// staged path of every row's Version and
-// PrevVersion. routes is every registry row, unfiltered: a hibernating route,
+// join too when the two differ), plus the fully resolved real path of the
+// chain's end when it exists (StagedPaths.addReal), and the staged path of
+// every row's Version and PrevVersion. routes is every registry row, unfiltered: a hibernating route,
// an expired canary and a mid-provision row all still refer to their builds.
//
// An entry that cannot be read is an error, never a skipped entry: a pin set
@@ -108,6 +108,12 @@ func BoxPins(optDir string, routes []registry.Route) (StagedPaths, error) {
for _, h := range aliases {
pins.add(h)
}
+ // And the fully resolved real path of where the chain ends (Astra r3
+ // N4, ruling R19 item 5): a hop written through a symlinked directory
+ // names the copy under another spelling than VersionPath's.
+ if err := pins.addReal(hops[len(hops)-1]); err != nil {
+ return nil, fmt.Errorf("live link %s: %w", filepath.Join(dir, e.Name()), err)
+ }
}
for _, rt := range routes {
if rt.App == "" {
diff --git a/internal/release/pins_test.go b/internal/release/pins_test.go
index 7fd86386d..1a1ef766b 100644
--- a/internal/release/pins_test.go
+++ b/internal/release/pins_test.go
@@ -289,3 +289,54 @@ func TestBoxPinsRelativeLinkUnderASymlinkedOptDir(t *testing.T) {
}
}
}
+
+// Ruling R19 item 5: an absolute final hop reached through a symlinked
+// directory is pinned at the fully resolved real path too, the copy's
+// VersionPath here.
+func TestBoxPinsPinsTheRealPathOfTheChainsEnd(t *testing.T) {
+ opt := t.TempDir()
+ if err := StageArtifact(opt, "bab", "v1", "km", []byte("v1")); err != nil {
+ t.Fatal(err)
+ }
+ staged := VersionPath(opt, "bab", "v1", "km")
+ alias := filepath.Join(t.TempDir(), "alias")
+ if err := os.Symlink(filepath.Dir(staged), alias); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.MkdirAll(filepath.Join(opt, "live"), 0o755); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.Symlink(filepath.Join(alias, "km"), LiveLink(opt, "h.example.com")); err != nil {
+ t.Fatal(err)
+ }
+ pins, err := BoxPins(opt, nil)
+ if err != nil {
+ t.Fatalf("BoxPins: %v", err)
+ }
+ for _, p := range []string{filepath.Join(alias, "km"), staged} {
+ if !pins.Has(p) {
+ t.Errorf("BoxPins = %v, missing %s", pins, p)
+ }
+ }
+}
+
+// Refers compares a candidate's real path; a candidate that does not exist
+// is compared as written.
+func TestStagedPathsRefersComparesTheRealPath(t *testing.T) {
+ real := t.TempDir()
+ opt := filepath.Join(t.TempDir(), "opt")
+ if err := os.Symlink(real, opt); err != nil {
+ t.Fatal(err)
+ }
+ if err := StageArtifact(opt, "bab", "v1", "km", []byte("v1")); err != nil {
+ t.Fatal(err)
+ }
+ pins := StagedPaths{}
+ pins.add(VersionPath(real, "bab", "v1", "km"))
+ if ok, err := pins.Refers(VersionPath(opt, "bab", "v1", "km")); err != nil || !ok {
+ t.Fatalf("Refers(the copy under the symlinked opt) = %v, %v; want true", ok, err)
+ }
+ if ok, err := pins.Refers(VersionPath(opt, "bab", "v9", "km")); err != nil || ok {
+ t.Fatalf("Refers(a missing copy) = %v, %v; want false, nil", ok, err)
+ }
+}
diff --git a/internal/release/stage_reuse_test.go b/internal/release/stage_reuse_test.go
index 7a370bf1a..3d45f7fa2 100644
--- a/internal/release/stage_reuse_test.go
+++ b/internal/release/stage_reuse_test.go
@@ -27,9 +27,15 @@ type reuseFixture struct {
}
func newReuseFixture(t *testing.T) reuseFixture {
+ t.Helper()
+ return newReuseFixtureAt(t, t.TempDir())
+}
+
+// newReuseFixtureAt is newReuseFixture with the opt dir given.
+func newReuseFixtureAt(t *testing.T, opt string) reuseFixture {
t.Helper()
ctx := context.Background()
- f := reuseFixture{s: NewDirStore(t.TempDir()), opt: t.TempDir(), reg: testRegistry(t)}
+ f := reuseFixture{s: NewDirStore(t.TempDir()), opt: opt, 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 {
@@ -308,3 +314,36 @@ func TestStageHeldNeverRestagesADifferingCopy(t *testing.T) {
t.Fatalf("staged v1 = %q, %v; want it untouched", b, rerr)
}
}
+
+// Astra r3 N4, ruling R19 item 5: the opt dir is itself a symlink
+// (/opt/carlos -> /srv/carlos), and live/h2 reaches the stale v2 through two
+// relative hops: live/h2 -> ../current, current -> versions/v2/bab/myapp.
+// The hops resolve under the real dir, so no pin is spelt the way VersionPath
+// spells the copy under the opt dir's own name. The restage's membership
+// check compares the copy's real path too, and refuses.
+func TestStaleCopyTwoRelativeHopsUnderASymlinkedOptDirNameIsNotOverwritten(t *testing.T) {
+ real := t.TempDir()
+ opt := filepath.Join(t.TempDir(), "opt")
+ if err := os.Symlink(real, opt); err != nil {
+ t.Fatal(err)
+ }
+ f := newReuseFixtureAt(t, opt)
+ rel, err := filepath.Rel(opt, VersionPath(opt, "bab", "v2", "myapp"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := os.Symlink(rel, filepath.Join(opt, "current")); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.Symlink(filepath.Join("..", "current"), LiveLink(opt, "h2.example.com")); err != nil {
+ t.Fatal(err)
+ }
+ pins, err := BoxPins(opt, nil)
+ if err != nil {
+ t.Fatalf("BoxPins: %v", err)
+ }
+ if pins.Has(VersionPath(opt, "bab", "v2", "myapp")) {
+ t.Fatalf("setup: BoxPins = %v already names the copy lexically; the test would not exercise the real-path check", pins)
+ }
+ f.assertRefused(t, f.pass(t), ErrVersionReused.Error())
+}