carlos / platform Public

release: D2 box tests from the final-review triage

The refusing pass's seed is asserted on disk (ReadPrograms after the pass). An agent-style pass (BakeDir plus an OnStageBlocked that fails the test, install tree unwritable) after root's wrong-program refusal relays nothing and prints the gate line. A -> authorised B -> A adoption runs on an exec route, a carlos-app@ route converged with capabilities, and a named-unit route, with the history built by adoption alone. The helper-only seed test is renamed TestRecordProgramKeepsOneEntryPerProgram.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev a98c66f914f96cd448ac925eae64659d34548d25 parent 0e3d558
1 file changed, +168 −3
  • internal/release/programhistory_test.go +168 −3

CI passed — run details

diff --git a/internal/release/programhistory_test.go b/internal/release/programhistory_test.go
index 812a40f07..41fb1dc2f 100644
--- a/internal/release/programhistory_test.go
+++ b/internal/release/programhistory_test.go
@@ -3,9 +3,14 @@ package release
import (
"bytes"
"context"
+ "io/fs"
+ "os"
+ "path/filepath"
"slices"
"strings"
"testing"
+
+ "github.com/carlosframework/platform/internal/registry"
)
// The route is live on "old" (a console build). The channel is promoted to
@@ -52,6 +57,11 @@ func TestBoxRefusesWrongProgramWithNoManifestField(t *testing.T) {
if r.Kind != RefusalWrongProgram || !r.Recovered || r.RestoredVersion != "old" || r.RecoveryError != "" {
t.Fatalf("refusal = %+v, want a recovered wrong-program record still on old", r)
}
+ // The history started empty; the refusing pass seeded it from the live
+ // binary, and the seed is on disk, not only in that pass's memory.
+ if got := ReadPrograms(bake, host); !slices.Equal(got, []string{"example.com/console"}) {
+ t.Fatalf("history after the refusing pass = %v, want the live binary's program persisted", got)
+ }
// The next pass reads the record at the gate: no --retry (it would loop),
// and the way out is named: a NEW version, since this one's manifest is
// immutable.
@@ -212,13 +222,168 @@ func TestBoxDefersAWrongProgramWhoseManifestItCannotRead(t *testing.T) {
}
}
-func TestBoxSeedsHistoryFromTheLiveBinary(t *testing.T) {
+// After root's pass refuses a wrong program, the agent's own passes (BakeDir
+// and OnStageBlocked, the shape internal/host's loops run) must not relay the
+// version to root again: a relay would run a root pass that refuses the same
+// bytes, every tick. The install directory is made unwritable for the agent
+// pass, as carlos-edge.service's sandbox makes it, so any attempt to stage or
+// repoint would be a write refusal that relays.
+func TestAgentPassDoesNotRelayAfterRootsWrongProgramRefusal(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
+ addRoute(t, reg, host, "console", "canary/x", false)
+ restart := func(h string) error { t.Fatalf("restart consulted for a wrong-program build on %s", h); return nil }
+ if err := Promote(ctx, s, priv, "bab", "console", "canary/x", "old"); err != nil {
+ t.Fatal(err)
+ }
+ if err := Pass(ctx, reg, s, pub, opt, func(string) error { return nil }, BakeDir(bake)); err != nil {
+ t.Fatalf("root Pass old: %v", err)
+ }
+ if err := Promote(ctx, s, priv, "bab", "console", "canary/x", "new"); err != nil {
+ t.Fatal(err)
+ }
+ // Root's pass: stages new and refuses it.
+ if err := Pass(ctx, reg, s, pub, opt, restart, BakeDir(bake)); err != nil {
+ t.Fatalf("root Pass new: %v", err)
+ }
+ if r, _ := ReadRefusal(bake, host); r == nil || r.Kind != RefusalWrongProgram {
+ t.Fatalf("refusal = %+v, want root's wrong-program record", r)
+ }
+
+ readOnlyTree(t, opt)
+ relay := func(h, ver string, err error) { t.Fatalf("agent pass relayed %s %s to root: %v", h, ver, err) }
+ var out bytes.Buffer
+ withCapturedStdout(t, &out, func() {
+ for i := 0; i < 2; i++ {
+ if err := Pass(ctx, reg, s, pub, opt, restart, BakeDir(bake), OnStageBlocked(relay)); err != nil {
+ t.Fatalf("agent Pass %d: %v", i, err)
+ }
+ }
+ })
+ if v, _ := LiveVersion(opt, host); v != "old" {
+ t.Fatalf("live = %q, want old", v)
+ }
+ if !strings.Contains(out.String(), "is the wrong program for this route") {
+ t.Fatalf("agent pass printed %q, want the gate line", out.String())
+ }
+}
+
+// readOnlyTree makes every directory under root unwritable until the test
+// ends (then restores it so the temp dir can be removed).
+func readOnlyTree(t *testing.T, root string) {
+ t.Helper()
+ var dirs []string
+ if err := filepath.WalkDir(root, func(p string, d fs.DirEntry, err error) error {
+ if err == nil && d.IsDir() {
+ dirs = append(dirs, p)
+ }
+ return err
+ }); err != nil {
+ t.Fatal(err)
+ }
+ for _, d := range dirs {
+ if err := os.Chmod(d, 0o555); err != nil {
+ t.Fatal(err)
+ }
+ }
+ t.Cleanup(func() {
+ for _, d := range dirs {
+ os.Chmod(d, 0o755)
+ }
+ })
+ if f, err := os.CreateTemp(root, "probe"); err == nil {
+ f.Close()
+ os.Remove(f.Name())
+ t.Skip("directories stay writable (running as root?)")
+ }
+}
+
+// Spec §7: a deliberate A -> B change and the rollback to A, on each route
+// kind that runs a release: an exec child, a carlos-app@ unit (converged with
+// capabilities, as root's hourly pass does) and a named unit. The history is
+// built by adoption alone.
+func TestBoxRollsBackAcrossADeliberateChangeOnEveryRouteKind(t *testing.T) {
+ for _, tc := range []struct {
+ name string
+ rt registry.Route
+ caps bool
+ }{
+ {"exec", registry.Route{}, false},
+ {"carlos-app unit", registry.Route{Backing: registry.BackingUnit}, true},
+ {"named unit", registry.Route{Unit: "console.service"}, false},
+ } {
+ t.Run(tc.name, func(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/console"), tinyProgram(t, "example.com/carlos")
+ if err := shipWithProgram(t, s, "va", a, false); err != nil {
+ t.Fatal(err)
+ }
+ if err := shipWithProgram(t, s, "vb", b, true); err != nil {
+ t.Fatal(err)
+ }
+ rt := tc.rt
+ rt.Host, rt.Kind, rt.App, rt.Channel = host, "instance", "console", "canary/x"
+ if err := reg.Upsert(rt); err != nil {
+ t.Fatal(err)
+ }
+ var restarted []string
+ opts := []PassOption{BakeDir(bake)}
+ if tc.caps {
+ o, _ := capsTestOps(t, newCapsRecorder(opt, host))
+ opts = append(opts, WithCapabilities(o))
+ }
+ restart := func(h string) error { restarted = append(restarted, h); return nil }
+ for _, step := range []struct {
+ ver string
+ hist []string
+ }{
+ {"va", []string{"example.com/console"}},
+ {"vb", []string{"example.com/console", "example.com/carlos"}},
+ {"va", []string{"example.com/console", "example.com/carlos"}},
+ } {
+ if err := Promote(ctx, s, priv, "bab", "console", "canary/x", step.ver); err != nil {
+ t.Fatal(err)
+ }
+ if err := Pass(ctx, reg, s, pub, opt, restart, opts...); err != nil {
+ t.Fatalf("Pass %s: %v", step.ver, err)
+ }
+ if v, _ := LiveVersion(opt, host); v != step.ver {
+ t.Fatalf("live = %q, want %s", v, step.ver)
+ }
+ if got := ReadPrograms(bake, host); !slices.Equal(got, step.hist) {
+ t.Fatalf("history after %s = %v, want %v", step.ver, got, step.hist)
+ }
+ if r, _ := ReadRefusal(bake, host); r != nil {
+ t.Fatalf("refusal after %s = %+v, want none", step.ver, r)
+ }
+ }
+ if len(restarted) < 2 {
+ t.Fatalf("restarted = %v, want the route cycled on each change", restarted)
+ }
+ })
+ }
+}
+
+// RecordProgram keeps one entry per program.
+func TestRecordProgramKeepsOneEntryPerProgram(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.
+ // The seed itself is asserted after a real refusing pass in
+ // TestBoxRefusesWrongProgramWithNoManifestField. Pin the file shape here.
if err := RecordProgram(bake, "console.example.com", "example.com/console"); err != nil {
t.Fatal(err)
}