carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

Console release defences, Plan 2 of 4: D2 (refuse the wrong program)

> For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Shipping or adopting a binary whose Go main package differs from the app's previous release is refused unless the change is declared with --program-change. A box also lets a build back in if its route has run that program before.

Architecture: One package-level helper set in internal/release (program.go) reads Go build info from artifact bytes or a staged file. The two ship doors (direct-bucket cli.go, API internal/console/apicli.go) call one CheckShipProgram before release.Ship. adoptHost checks the staged binary against the route's program history (programhistory.go, in the bake directory) before pointing it live, and writes a Plan 1 refusal when it refuses.

Tech Stack: Go stdlib debug/buildinfo; the existing release, console and CLI packages.

Spec: docs/superpowers/specs/2026-10-06-console-release-defences-design.md (rev 5): §3 (D2) and §7's D2 tests. Depends on Plan 1's Task 1 (release.WriteRefusal, Refusal). Land Plan 1 first, or cherry-pick that task.

Global Constraints

  • program is the build info's main package path (buildinfo.BuildInfo.Path), additive to manifest schema 1, omitempty (spec §3.1).
  • program_change: true is written only when --program-change was passed (spec §3.1).
  • No previous release with a program, a non-Go artifact, a static kind, or a first release: ship as today (spec §3.1).
  • All artifacts of one release must report the same program, or ship refuses (spec §3.1).
  • "Previous" = the newest of the app's 20 newest releases (release.VersionCandidates, as carlos releases uses) whose manifest declares a program. This plan picks that over "the newest release": a release from an older client has no program and must not switch the check off.
  • On the box the check reads the staged bytes, never only the manifest (spec §3.2), and passes for any program in the route's program history (spec §3.2, codex 7).
  • Gate with GOFLAGS=-buildvcs=false and, in this sandbox, GOCACHE=$TMPDIR/gocache per command.

Review Focus

  1. A release shipped by a v0.24.1 or older client right after this lands (no program in its manifest). Expect: the ship-side check skips it as a predecessor, and the box still checks its bytes. Test in Task 3 (TestBoxRefusesWrongProgramWithNoManifestField).
  2. A legitimate move of the console to a new main package (a refactor). Expect: --program-change ships it, the box accepts it, and a later rollback to the old program is accepted because the route ran it. Tests in Tasks 1 and 3.
  3. A non-Go binary app (Rust, a shell wrapper). Expect: no refusal ever, at ship or on the box. Tests in Tasks 1 and 3.
  4. Multi-arch releases (linux-arm64 and linux-amd64 built from different main packages by mistake). Expect: ship refuses with both names. Test in Task 1.
  5. A box upgraded to this release with a route already live and no history. Expect: the history is seeded from the live binary, so the next mismatching build is refused, not trusted. Test in Task 3 (TestBoxSeedsHistoryFromTheLiveBinary).

---

Task 1: Program detection and the ship-side rule

Files:

  • Create: internal/release/program.go
  • Modify: internal/release/release.go (Manifest, after Notes: two fields)
  • Test: internal/release/program_test.go

Interfaces:

  • Produces: - Manifest.Program string \json:"program,omitempty"\`, Manifest.ProgramChange bool \json:"program_change,omitempty"\ - func ArtifactProgram(b []byte) string — "" for non-Go - func FileProgram(path string) string — "" for non-Go or unreadable - func ReleaseProgram(files map[string][]byte) (string, error) — error when artifacts disagree - func PreviousProgram(ctx context.Context, s Store, acct, app string) (program, version string, err error) - type ProgramMismatchError struct { App, Got, Want, WantVersion string } with Error() - func CheckShipProgram(ctx context.Context, s Store, acct string, m *Manifest, files map[string][]byte, allowChange bool) error, which sets m.Program and m.ProgramChange`
  • [ ] Step 1: Write the test helper and failing tests
package release

import (
	"context"
	"errors"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"
)

// tinyProgram builds a stripped linux/arm64 Go binary whose main package is
// modPath, exactly as the console is built (-s -w), at test time. Nothing is
// checked in.
func tinyProgram(t *testing.T, modPath string) []byte {
	t.Helper()
	if testing.Short() {
		t.Skip("builds a binary")
	}
	dir := t.TempDir()
	must := func(err error) {
		t.Helper()
		if err != nil {
			t.Fatal(err)
		}
	}
	must(os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module "+modPath+"\n\ngo 1.22\n"), 0o644))
	must(os.WriteFile(filepath.Join(dir, "main.go"), []byte("package main\nfunc main() {}\n"), 0o644))
	out := filepath.Join(dir, "bin")
	cmd := exec.Command("go", "build", "-trimpath", "-ldflags=-s -w", "-o", out, ".")
	cmd.Dir = dir
	cmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH=arm64", "CGO_ENABLED=0", "GOFLAGS=-buildvcs=false")
	if b, err := cmd.CombinedOutput(); err != nil {
		t.Fatalf("build %s: %v\n%s", modPath, err, b)
	}
	b, err := os.ReadFile(out)
	must(err)
	return b
}

func TestArtifactProgram(t *testing.T) {
	if got := ArtifactProgram(tinyProgram(t, "example.com/console")); got != "example.com/console" {
		t.Fatalf("program = %q", got)
	}
	if got := ArtifactProgram([]byte("#!/bin/sh\necho hi\n")); got != "" {
		t.Fatalf("non-Go program = %q, want empty", got)
	}
}

func TestReleaseProgramDisagreement(t *testing.T) {
	_, err := ReleaseProgram(map[string][]byte{
		"linux-arm64": tinyProgram(t, "example.com/console"),
		"linux-amd64": tinyProgram(t, "example.com/carlos"),
	})
	if err == nil || !strings.Contains(err.Error(), "example.com/console") || !strings.Contains(err.Error(), "example.com/carlos") {
		t.Fatalf("err = %v, want both programs named", err)
	}
}

func shipWithProgram(t *testing.T, s Store, ver string, art []byte, allow bool) error {
	t.Helper()
	files := map[string][]byte{adoptPlatform: art}
	m := Manifest{Schema: 1, App: "console", Version: ver, Kind: "binary",
		Artifacts: map[string]Artifact{adoptPlatform: {}}} // Ship fills file, sha256, size
	if err := CheckShipProgram(context.Background(), s, "bab", &m, files, allow); err != nil {
		return err
	}
	return Ship(context.Background(), s, "bab", m, files)
}

func TestCheckShipProgram(t *testing.T) {
	s := NewDirStore(t.TempDir())
	console := tinyProgram(t, "example.com/console")
	cli := tinyProgram(t, "example.com/carlos")
	if err := shipWithProgram(t, s, "v1", console, false); err != nil {
		t.Fatalf("first release: %v", err)
	}
	err := shipWithProgram(t, s, "v2", cli, false)
	var pm *ProgramMismatchError
	if !errors.As(err, &pm) || pm.Got != "example.com/carlos" || pm.Want != "example.com/console" {
		t.Fatalf("wrong program ship = %v, want ProgramMismatchError", err)
	}
	if !strings.Contains(err.Error(), "--program-change") {
		t.Fatalf("refusal %q does not name the override", err)
	}
	if err := shipWithProgram(t, s, "v3", cli, true); err != nil {
		t.Fatalf("--program-change ship: %v", err)
	}
	m, err := GetManifest(context.Background(), s, "bab", "console", "v3")
	if err != nil || m.Program != "example.com/carlos" || !m.ProgramChange {
		t.Fatalf("v3 manifest = %+v, %v; want program recorded and program_change true", m, err)
	}
	if err := shipWithProgram(t, s, "v4", []byte("#!/bin/sh\n"), false); err != nil {
		t.Fatalf("non-Go artifact refused: %v", err)
	}
}

// A release from an old client has no program and must not switch the check
// off for the next ship.
func TestPreviousProgramSkipsReleasesWithoutOne(t *testing.T) {
	s := NewDirStore(t.TempDir())
	if err := shipWithProgram(t, s, "v1", tinyProgram(t, "example.com/console"), false); err != nil {
		t.Fatal(err)
	}
	shipRelease(t, s, "bab", "console", "v2", adoptPlatform, []byte("old client, no program field"))
	got, ver, err := PreviousProgram(context.Background(), s, "bab", "console")
	if err != nil || got != "example.com/console" || ver != "v1" {
		t.Fatalf("PreviousProgram = %q %q %v, want example.com/console from v1", got, ver, err)
	}
}

This mirrors shipRelease (internal/release/release_test.go:39), which passes empty Artifact values and lets Ship fill them.

  • [ ] Step 2: Run them to verify they fail

Run: GOCACHE=$TMPDIR/gocache GOFLAGS=-buildvcs=false go test ./internal/release -run 'Program' -count=1 Expected: FAIL, undefined: ArtifactProgram.

  • [ ] Step 3: Implement

In Manifest, after Notes:

	// Program is the Go main package path of the release's binaries
	// (debug/buildinfo), and ProgramChange records an intended change of it
	// (`carlos ship --program-change`). ADDITIVE to schema 1 like Label:
	// omitted when empty, ignored by old binaries. Console release defences
	// D2 (program.go).
	Program       string `json:"program,omitempty"`
	ProgramChange bool   `json:"program_change,omitempty"`

internal/release/program.go:

// The wrong-program check — D2 of the console release defences (spec
// 2026-10-06-console-release-defences-design.md §3).
//
// On 2026-10-06 the public carlos CLI binary was shipped as the console. Its
// architecture and its version stamp were both right, so nothing objected;
// what was wrong was the program. A Go binary records its main package path
// in its build info, readable from stripped bytes for any architecture
// without running them, so a ship can compare it with the app's previous
// release and a box can compare it with what its route has run.
package release

import (
	"bytes"
	"context"
	"debug/buildinfo"
	"fmt"
	"sort"
	"strings"
)

func ArtifactProgram(b []byte) string {
	bi, err := buildinfo.Read(bytes.NewReader(b))
	if err != nil {
		return ""
	}
	return bi.Path
}

func FileProgram(path string) string {
	bi, err := buildinfo.ReadFile(path)
	if err != nil {
		return ""
	}
	return bi.Path
}

// ReleaseProgram is the one program every Go artifact in files reports, ""
// when none is Go.
func ReleaseProgram(files map[string][]byte) (string, error) {
	seen := map[string][]string{}
	for name, b := range files {
		if p := ArtifactProgram(b); p != "" {
			seen[p] = append(seen[p], name)
		}
	}
	if len(seen) > 1 {
		var parts []string
		for p, names := range seen {
			sort.Strings(names)
			parts = append(parts, fmt.Sprintf("%s is %s", strings.Join(names, ", "), p))
		}
		sort.Strings(parts)
		return "", fmt.Errorf("this release's artifacts are different programs: %s", strings.Join(parts, "; "))
	}
	for p := range seen {
		return p, nil
	}
	return "", nil
}

// PreviousProgram is the program of the newest release (among the 20 newest)
// whose manifest declares one, and that release's version.
func PreviousProgram(ctx context.Context, s Store, acct, app string) (string, string, error) {
	vers, _, err := VersionCandidates(ctx, s, acct, app, 20)
	if err != nil {
		return "", "", err
	}
	var best *Manifest
	for _, v := range vers {
		m, err := GetManifest(ctx, s, acct, app, v)
		if err != nil || m == nil || m.Program == "" {
			continue
		}
		if best == nil || m.BuiltAt > best.BuiltAt {
			best = m
		}
	}
	if best == nil {
		return "", "", nil
	}
	return best.Program, best.Version, nil
}

type ProgramMismatchError struct{ App, Got, Want, WantVersion string }

func (e *ProgramMismatchError) Error() string {
	return fmt.Sprintf("this artifact is %s; %s's releases are %s (%s). If the app really is changing program, ship again with --program-change",
		e.Got, e.App, e.Want, e.WantVersion)
}

// CheckShipProgram is the one ship-side rule both ship doors call before
// release.Ship. It stamps m.Program (and m.ProgramChange when allowed).
func CheckShipProgram(ctx context.Context, s Store, acct string, m *Manifest, files map[string][]byte, allowChange bool) error {
	if m.Kind != "binary" {
		return nil
	}
	prog, err := ReleaseProgram(files)
	if err != nil || prog == "" {
		return err
	}
	m.Program = prog
	prev, prevVer, err := PreviousProgram(ctx, s, acct, m.App)
	if err != nil {
		return fmt.Errorf("reading %s's previous releases: %w", m.App, err)
	}
	if prev == "" || prev == prog {
		return nil
	}
	if !allowChange {
		return &ProgramMismatchError{App: m.App, Got: prog, Want: prev, WantVersion: prevVer}
	}
	m.ProgramChange = true
	return nil
}

BuiltAt is RFC 3339 UTC in every writer (cli.go and apicli.go both use time.Now().UTC().Format(time.RFC3339)), so string comparison orders it.

  • [ ] Step 4: Run the tests

Run: GOCACHE=$TMPDIR/gocache GOFLAGS=-buildvcs=false go test ./internal/release -run 'Program' -count=1 Expected: PASS (builds four tiny binaries; about 10 s).

  • [ ] Step 5: Commit
git add internal/release/program.go internal/release/program_test.go internal/release/release.go
git commit -m "release: detect a release's Go program and refuse a change of it at ship (console defences D2)"

---

Task 2: Both ship doors call the rule

Files:

  • Modify: cli.go: runShip's flag set (add --program-change), the direct-mode path just before release.Ship (~2192), the API-mode sess.ship(...) call
  • Modify: apimode.go: ship (3953) gains programChange bool; meta JSON gains ProgramChange bool \json:"program_change,omitempty"\``
  • Modify: internal/console/apicli.go: meta struct (~1715) gains ProgramChange bool \json:"program_change,omitempty"\`; call release.CheckShipProgram after m is built (~1969), mapping *ProgramMismatchError` to 409
  • Test: internal/console/apicli_test.go (append, beside the existing ship-door tests)

Interfaces:

  • Consumes: Task 1's CheckShipProgram, ProgramMismatchError.
  • [ ] Step 1: Write the failing API-door test

Copy the nearest existing successful ship test in internal/console/apicli_test.go (grep func TestAPIShip) as TestAPIShipRefusesAProgramChange. Ship a tinyProgram(t, "example.com/console") artifact as v1, then a tinyProgram(t, "example.com/carlos") artifact as v2. Assert:

	if rec.Code != http.StatusConflict || !strings.Contains(rec.Body.String(), "--program-change") {
		t.Fatalf("second ship = %d %s, want 409 naming --program-change", rec.Code, rec.Body.String())
	}

Then ship v3 with the meta part carrying "program_change": true, and assert

  1. tinyProgram lives in package release's tests. Copy it into internal/console as tinyProgramForConsole in the test file; test helpers cannot be imported across packages.
  • [ ] Step 2: Run it to verify it fails

Run: GOCACHE=$TMPDIR/gocache GOFLAGS=-buildvcs=false go test ./internal/console -run 'TestAPIShipRefusesAProgramChange' -count=1 Expected: FAIL. The second ship returns 200.

  • [ ] Step 3: Implement

internal/console/apicli.go, right after the m := release.Manifest{...} literal and before the context/timeout lines:

	if err := release.CheckShipProgram(r.Context(), s.cfg.Releases, acct.Sqid, &m, files, meta.ProgramChange); err != nil {
		var pm *release.ProgramMismatchError
		if errors.As(err, &pm) {
			jsonError(w, http.StatusConflict, err.Error())
			return
		}
		jsonError(w, http.StatusBadRequest, err.Error())
		return
	}

cli.go, in runShip's flags:

	programChange := fs.Bool("program-change", false, "accept that this release's Go main package differs from the app's previous release (recorded in the manifest)")

Direct mode, just before release.Ship(context.Background(), st, acct, m, files):

	if err := release.CheckShipProgram(context.Background(), st, acct, &m, files, *programChange); err != nil {
		log.Fatal(err)
	}

API mode: pass *programChange through sess.ship(...) to the new programChange parameter. In apimode.go's meta struct literal add ProgramChange bool \json:"program_change,omitempty"\` and the value. carlos deploy also calls sess.ship; pass false` there, so a deploy never changes program silently.

  • [ ] Step 4: Run the tests and the build

Run: GOCACHE=$TMPDIR/gocache GOFLAGS=-buildvcs=false go test ./internal/console -run 'APIShip' -count=1 && GOCACHE=$TMPDIR/gocache GOFLAGS=-buildvcs=false go build ./... Expected: PASS, and the build succeeds.

  • [ ] Step 5: Commit
git add cli.go apimode.go internal/console/apicli.go internal/console/apicli_test.go
git commit -m "ship: both doors refuse a change of program without --program-change"

---

Task 3: The box checks staged bytes against the route's program history

Files:

  • Create: internal/release/programhistory.go
  • Modify: internal/release/adopt.go, in adoptHost after ensureStaged succeeds and before the capability converge; and after PointLive succeeds
  • Test: internal/release/programhistory_test.go

Interfaces:

  • Consumes: Task 1's FileProgram; Plan 1's WriteRefusal/Refusal; VersionPath, RouteAccount, passConfig.bakeDir.
  • Produces: - func ReadPrograms(bakeDir, host string) []string - func RecordProgram(bakeDir, host, program string) error
  • [ ] Step 1: Write the failing tests
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)
	}
}

shipRelease(t, s, acct, app, ver, platform, bytes) (release_test.go:39) writes a manifest with no program, which is exactly an old client's ship.

  • [ ] Step 2: Run them to verify they fail

Run: GOCACHE=$TMPDIR/gocache GOFLAGS=-buildvcs=false go test ./internal/release -run 'TestBox' -count=1 Expected: FAIL, undefined: ReadPrograms.

  • [ ] Step 3: Implement programhistory.go
// 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)
}
  • [ ] Step 4: Wire it into adoptHost

Right after m, err := ensureStaged(...) and its error branch (before the capability converge comment block), insert:

	// 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
			}
			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}); err != nil {
				fmt.Printf("  %s: writing refusal record: %v\n", rt.Host, err)
			}
			return
		}
	}

and right after PointLive succeeds (after its error branch, before "The accepted window"):

	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)
		}
	}

Add "slices" to adopt.go's imports if it is not already there.

  • [ ] Step 5: Run the release suite

Run: GOCACHE=$TMPDIR/gocache GOFLAGS=-buildvcs=false go test ./internal/release -count=1 Expected: PASS. Existing fixtures ship non-Go bytes ("good build"), so their behaviour is unchanged.

  • [ ] Step 6: Commit
git add internal/release/programhistory.go internal/release/programhistory_test.go internal/release/adopt.go
git commit -m "release: a box refuses a staged binary its route has never run the program of"

---

Task 4: Gate and operator note

Files:

  • Modify: README.md, the same operator-notes area Plan 1 Task 9 used
  • [ ] Step 1: Add the note
**Wrong-program check (console release defences, D2).** `carlos ship` now
records a binary's Go main package in the manifest and refuses a release whose
program differs from the app's previous one. Pass `--program-change` when the
change is intended. Boxes check the staged bytes too, so a release shipped by
an older client is caught at adoption. A build a route has run before is
always accepted, so rolling back across an intended change works.

It is README prose, so invoke the copy-review skill on it before committing, and apply Paul's edits verbatim.

  • [ ] Step 2: Run the full gate
GOCACHE=$TMPDIR/gocache GOFLAGS=-buildvcs=false nice -n 10 go vet ./... && \
GOCACHE=$TMPDIR/gocache GOFLAGS=-buildvcs=false nice -n 10 go test ./... -count=1 && \
GOOS=darwin GOARCH=arm64 GOCACHE=$TMPDIR/gocache GOFLAGS=-buildvcs=false go build ./... && \
GOOS=windows GOARCH=amd64 GOCACHE=$TMPDIR/gocache GOFLAGS=-buildvcs=false go build ./...

Expected: all pass.

  • [ ] Step 3: Commit
git add README.md
git commit -m "README: operator note for the wrong-program check"