carlos / platform Public
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
programis the build info's main package path (buildinfo.BuildInfo.Path), additive to manifest schema 1,omitempty(spec §3.1).program_change: trueis written only when--program-changewas passed (spec §3.1).- No previous release with a
program, a non-Go artifact, astatickind, 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, ascarlos releasesuses) whose manifest declares aprogram. This plan picks that over "the newest release": a release from an older client has noprogramand 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=falseand, in this sandbox,GOCACHE=$TMPDIR/gocacheper command.
Review Focus
- A release shipped by a v0.24.1 or older client right after this lands (no
programin its manifest). Expect: the ship-side check skips it as a predecessor, and the box still checks its bytes. Test in Task 3 (TestBoxRefusesWrongProgramWithNoManifestField). - A legitimate move of the console to a new main package (a refactor). Expect:
--program-changeships 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. - 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.
- Multi-arch releases (
linux-arm64andlinux-amd64built from different main packages by mistake). Expect: ship refuses with both names. Test in Task 1. - 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, afterNotes: 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 }withError()-func CheckShipProgram(ctx context.Context, s Store, acct string, m *Manifest, files map[string][]byte, allowChange bool) error, which setsm.Programandm.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 beforerelease.Ship(~2192), the API-modesess.ship(...)call - Modify:
apimode.go:ship(3953) gainsprogramChange bool; meta JSON gainsProgramChange bool \json:"program_change,omitempty"\`` - Modify:
internal/console/apicli.go: meta struct (~1715) gainsProgramChange bool \json:"program_change,omitempty"\`; callrelease.CheckShipProgramaftermis 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
tinyProgramlives in packagerelease's tests. Copy it intointernal/consoleastinyProgramForConsolein 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, inadoptHostafterensureStagedsucceeds and before the capability converge; and afterPointLivesucceeds - Test:
internal/release/programhistory_test.go
Interfaces:
- Consumes: Task 1's
FileProgram; Plan 1'sWriteRefusal/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"