carlos / platform Public

release: a box refuses a staged binary its route has never run the program of

adoptHost checks the staged bytes' Go program against the route's history in the bake dir (seeded from the live binary) and writes a refusal record on a mismatch unless the manifest carries ProgramChange. The history is recorded after PointLive.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev a8f8578dac2ca90625526eaaf85d22f414bdfa16 parent d9722d0
3 files changed, +205 −0
  • internal/release/adopt.go +34 −0
  • internal/release/programhistory.go +60 −0
  • internal/release/programhistory_test.go +111 −0
diff --git a/internal/release/adopt.go b/internal/release/adopt.go
index b4958aa7d..27401c497 100644
--- a/internal/release/adopt.go
+++ b/internal/release/adopt.go
@@ -13,6 +13,7 @@ import (
"io"
"os"
"runtime"
+ "slices"
"strings"
"syscall"
"time"
@@ -1010,6 +1011,34 @@ func adoptHost(ctx context.Context, reg *registry.Registry, s Store, optDir stri
return
}
+ // D2 (programhistory.go): the staged bytes, not the manifest, decide.
+ // Skipped for non-Go binaries and when this pass has no bake dir.
+ newProg := FileProgram(VersionPath(optDir, RouteAccount(rt), ver, rt.App))
+ if cfg.bakeDir != "" && newProg != "" {
+ hist := ReadPrograms(cfg.bakeDir, rt.Host)
+ if len(hist) == 0 && cur != "" {
+ if seed := FileProgram(VersionPath(optDir, RouteAccount(rt), cur, rt.App)); seed != "" {
+ _ = RecordProgram(cfg.bakeDir, rt.Host, seed)
+ hist = []string{seed}
+ }
+ }
+ if len(hist) > 0 && !slices.Contains(hist, newProg) && !(m != nil && m.ProgramChange) {
+ fmt.Printf(" %s: %s is %s, but this route has only run %s — not pointed live (ship with --program-change if this is intended)\n",
+ rt.Host, ver, newProg, strings.Join(hist, ", "))
+ sha := ""
+ if m != nil {
+ sha = m.Artifacts[adoptPlatform].SHA256
+ }
+ // Nothing was restarted, so the box is still on its old build.
+ if err := WriteRefusal(cfg.bakeDir, Refusal{Host: rt.Host, App: rt.App, Version: ver, SHA256: sha,
+ Platform: adoptPlatform, PointerPromotedAt: ptr.PromotedAt, RefusedAt: now.UTC(),
+ Reason: "wrong program: " + newProg, Recovered: true}); err != nil {
+ fmt.Printf(" %s: writing refusal record: %v\n", rt.Host, err)
+ }
+ return
+ }
+ }
+
// The capability converge for the NEW version, BEFORE PointLive (spec
// §4d). m is the manifest ensureStaged has in hand — never a re-read of
// the staged sibling, whose write is best-effort: a sibling that failed
@@ -1103,6 +1132,11 @@ func adoptHost(ctx context.Context, reg *registry.Registry, s Store, optDir stri
}
return
}
+ if cfg.bakeDir != "" && newProg != "" {
+ if err := RecordProgram(cfg.bakeDir, rt.Host, newProg); err != nil {
+ fmt.Printf(" %s: recording program history: %v\n", rt.Host, err)
+ }
+ }
// The accepted window, made visible (2026-08-03 ruling). A pass with no
// WithCapabilities — the in-process adoptLoop, and above all the 2-3
diff --git a/internal/release/programhistory.go b/internal/release/programhistory.go
new file mode 100644
index 000000000..e9ac293a6
--- /dev/null
+++ b/internal/release/programhistory.go
@@ -0,0 +1,60 @@
+// Program history — the box half of D2 (spec §3.2). Every Go main package
+// path this box has pointed live for a route, in the bake directory. A build
+// whose program is in its route's history is never "the wrong program", which
+// is what lets a deliberate A -> B change be rolled back to A.
+package release
+
+import (
+ "encoding/json"
+ "os"
+ "path/filepath"
+ "slices"
+ "strings"
+)
+
+func programPath(bakeDir, host string) string {
+ return filepath.Join(bakeDir, "programs", host+".json")
+}
+
+func ReadPrograms(bakeDir, host string) []string {
+ if strings.ContainsAny(host, `/\`) {
+ return nil
+ }
+ b, err := os.ReadFile(programPath(bakeDir, host))
+ if err != nil {
+ return nil
+ }
+ var rec struct {
+ Programs []string `json:"programs"`
+ }
+ if json.Unmarshal(b, &rec) != nil {
+ return nil
+ }
+ return rec.Programs
+}
+
+func RecordProgram(bakeDir, host, program string) error {
+ if program == "" || strings.ContainsAny(host, `/\`) {
+ return nil
+ }
+ have := ReadPrograms(bakeDir, host)
+ if slices.Contains(have, program) {
+ return nil
+ }
+ b, err := json.Marshal(struct {
+ Schema int `json:"schema"`
+ Programs []string `json:"programs"`
+ }{1, append(have, program)})
+ if err != nil {
+ return err
+ }
+ p := programPath(bakeDir, host)
+ if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
+ return err
+ }
+ tmp := p + ".tmp"
+ if err := os.WriteFile(tmp, b, 0o644); err != nil {
+ return err
+ }
+ return os.Rename(tmp, p)
+}
diff --git a/internal/release/programhistory_test.go b/internal/release/programhistory_test.go
new file mode 100644
index 000000000..24aff87e7
--- /dev/null
+++ b/internal/release/programhistory_test.go
@@ -0,0 +1,111 @@
+package release
+
+import (
+ "bytes"
+ "context"
+ "strings"
+ "testing"
+)
+
+// The route is live on "old" (a console build). The channel is promoted to
+// "new" (a CLI build) shipped by an OLD client, so its manifest has no
+// program field and only the bytes can tell.
+func TestBoxRefusesWrongProgramWithNoManifestField(t *testing.T) {
+ host := "console.example.com"
+ s := NewDirStore(t.TempDir())
+ ctx := context.Background()
+ priv, pub := testKeys(t)
+ opt, bake := t.TempDir(), t.TempDir()
+ reg := testRegistry(t)
+ good, bad := tinyProgram(t, "example.com/console"), tinyProgram(t, "example.com/carlos")
+ shipRelease(t, s, "bab", "console", "old", adoptPlatform, good)
+ shipRelease(t, s, "bab", "console", "new", adoptPlatform, bad) // old client: no program
+ if err := StageArtifact(opt, "bab", "old", "console", good); err != nil {
+ t.Fatal(err)
+ }
+ addRoute(t, reg, host, "console", "canary/x", false)
+ if err := PointLive(opt, host, "bab", "old", "console"); err != nil {
+ t.Fatal(err)
+ }
+ if err := Promote(ctx, s, priv, "bab", "console", "canary/x", "new"); err != nil {
+ t.Fatal(err)
+ }
+ restart := func(h string) error { t.Fatalf("restart consulted for a wrong-program build on %s", h); return nil }
+ var out bytes.Buffer
+ withCapturedStdout(t, &out, func() {
+ if err := Pass(ctx, reg, s, pub, opt, restart, BakeDir(bake)); err != nil {
+ t.Fatalf("Pass: %v", err)
+ }
+ })
+ if v, _ := LiveVersion(opt, host); v != "old" {
+ t.Fatalf("live = %q, want old", v)
+ }
+ if !strings.Contains(out.String(), "example.com/carlos") || !strings.Contains(out.String(), "not pointed live") {
+ t.Fatalf("printed %q, want the program refusal", out.String())
+ }
+ if r, _ := ReadRefusal(bake, host); r == nil || r.Version != "new" {
+ t.Fatalf("refusal = %+v, want one for new", r)
+ }
+}
+
+func TestBoxSeedsHistoryFromTheLiveBinary(t *testing.T) {
+ bake := t.TempDir()
+ if got := ReadPrograms(bake, "console.example.com"); len(got) != 0 {
+ t.Fatalf("fresh history = %v", got)
+ }
+ // The refusal test above starts with no history: passing it proves the
+ // seed. Pin the file shape directly too.
+ if err := RecordProgram(bake, "console.example.com", "example.com/console"); err != nil {
+ t.Fatal(err)
+ }
+ if err := RecordProgram(bake, "console.example.com", "example.com/console"); err != nil {
+ t.Fatal(err)
+ }
+ if got := ReadPrograms(bake, "console.example.com"); len(got) != 1 || got[0] != "example.com/console" {
+ t.Fatalf("history = %v, want one entry (no duplicates)", got)
+ }
+}
+
+func TestBoxAcceptsAProgramTheRouteRanBefore(t *testing.T) {
+ host := "console.example.com"
+ s := NewDirStore(t.TempDir())
+ ctx := context.Background()
+ priv, pub := testKeys(t)
+ opt, bake := t.TempDir(), t.TempDir()
+ reg := testRegistry(t)
+ a, b := tinyProgram(t, "example.com/a"), tinyProgram(t, "example.com/b")
+ shipRelease(t, s, "bab", "console", "va", adoptPlatform, a)
+ shipRelease(t, s, "bab", "console", "vb", adoptPlatform, b)
+ if err := StageArtifact(opt, "bab", "vb", "console", b); err != nil {
+ t.Fatal(err)
+ }
+ addRoute(t, reg, host, "console", "canary/x", false)
+ if err := PointLive(opt, host, "bab", "vb", "console"); err != nil {
+ t.Fatal(err)
+ }
+ for _, p := range []string{"example.com/a", "example.com/b"} {
+ if err := RecordProgram(bake, host, p); err != nil {
+ t.Fatal(err)
+ }
+ }
+ if err := Promote(ctx, s, priv, "bab", "console", "canary/x", "va"); err != nil {
+ t.Fatal(err)
+ }
+ if err := Pass(ctx, reg, s, pub, opt, func(string) error { return nil }, BakeDir(bake)); err != nil {
+ t.Fatalf("Pass: %v", err)
+ }
+ if v, _ := LiveVersion(opt, host); v != "va" {
+ t.Fatalf("live = %q, want va: rolling back across a deliberate change must work (codex 7)", v)
+ }
+}
+
+func TestBoxIgnoresNonGoBinaries(t *testing.T) {
+ host := "x.example.com"
+ ctx, reg, s, pub, opt := startUnitFixture(t, host) // "good build"/"bad build" are not Go
+ if err := Pass(ctx, reg, s, pub, opt, func(string) error { return nil }, BakeDir(t.TempDir())); err != nil {
+ t.Fatalf("Pass: %v", err)
+ }
+ if v, _ := LiveVersion(opt, host); v != "new" {
+ t.Fatalf("live = %q, want new: non-Go bytes are never refused", v)
+ }
+}