package host import ( "context" "crypto/ed25519" "crypto/sha256" "encoding/hex" "fmt" "os" "path/filepath" "strings" "testing" "time" "github.com/carlosframework/platform/internal/registry" "github.com/carlosframework/platform/internal/release" ) type proofRootEnv struct { root proofRoot store release.Store bake string keyFile string pub ed25519.PublicKey pubB64 string sha string now time.Time logs []string } func provePolicy(keyFile string) string { return fmt.Sprintf(`{"schema":1,"prove":{"apps":["console"],"prover":"dev","after":"15m","key_file":%q}}`, keyFile) } // newProofRootEnv: one named-unit console route, bound, running its live // build for 20 minutes, ready. policy builds the policy file from the key // file's path; nil writes no policy file. func newProofRootEnv(t *testing.T, policy func(keyFile string) string) *proofRootEnv { t.Helper() opt, bake, etc := t.TempDir(), t.TempDir(), t.TempDir() liveTo(t, opt, observedHost, "v1") reg := restartTestRegistry(t) rt := registry.Route{Host: observedHost, Kind: "instance", App: "console", Channel: "canary/console", Unit: "console.service", Status: "ready"} if err := reg.Upsert(rt); err != nil { t.Fatal(err) } seed, pubB64, err := release.GenerateKey() if err != nil { t.Fatal(err) } pub, err := release.ParsePublicKey(pubB64, "test") if err != nil { t.Fatal(err) } keyFile := filepath.Join(etc, "proving.key") if err := os.WriteFile(keyFile, []byte(seed+"\n"), 0o600); err != nil { t.Fatal(err) } policyPath := filepath.Join(etc, "proof-policy.json") if policy != nil { if err := os.WriteFile(policyPath, []byte(policy(keyFile)), 0o644); err != nil { t.Fatal(err) } } staged := filepath.Join(opt, "versions", "v1", "console") sha, err := release.FileSHA256(staged) if err != nil { t.Fatal(err) } e := &proofRootEnv{store: release.NewDirStore(t.TempDir()), bake: bake, keyFile: keyFile, pub: pub, pubB64: pubB64, sha: sha, now: time.Date(2026, 10, 6, 10, 0, 0, 0, time.UTC)} units := &fakeUnits{shows: []unitProps{{ActiveState: "active", InvocationID: "i1", MainPID: 7, ExecPath: release.LiveLink(opt, observedHost), ActiveEnterTimestamp: e.now.Add(-20 * time.Minute)}}} e.root = proofRoot{ mintCensus: func() (bool, error) { return true, nil }, obs: proofObserver{sys: units, procExe: func(int) string { return staged }, optDir: opt, now: func() time.Time { return e.now }, resolve: proofFixtureResolver(rt), ready: func(registry.Route) func() error { return func() error { return nil } }}, reg: reg, store: e.store, bakeDir: bake, policyPath: policyPath, readKey: readProvingKey, logf: func(f string, a ...any) { e.logs = append(e.logs, fmt.Sprintf(f, a...)) }, } return e } func (e *proofRootEnv) proof(t *testing.T) *release.Proof { t.Helper() p, err := release.GetProof(context.Background(), e.store, "bab", "console", e.sha, "dev") if err != nil { return nil } return p } // signed is a proof for the env's live build, signed with its proving key. func (e *proofRootEnv) signed(t *testing.T, provenAt time.Time) *release.Proof { t.Helper() seed, err := os.ReadFile(e.keyFile) if err != nil { t.Fatal(err) } key, err := release.ParseSigningKey(string(seed), "test") if err != nil { t.Fatal(err) } p, err := release.SignProof(key, release.Proof{App: "console", Platform: release.AdoptPlatform(), SHA256: e.sha, Version: "v1", Prover: "dev", ActiveSince: e.now.Add(-time.Hour), ProvenAt: provenAt}) if err != nil { t.Fatal(err) } return p } func TestProofRootMintsWhenEveryConditionHolds(t *testing.T) { t.Parallel() e := newProofRootEnv(t, provePolicy) e.root.run(context.Background()) p := e.proof(t) if p == nil { t.Fatalf("no proof minted; logs: %v", e.logs) } if err := p.Check(e.pub, "console", release.AdoptPlatform(), e.sha, "dev"); err != nil { t.Fatalf("minted proof: %v", err) } if p.Version != "v1" || !p.ActiveSince.Equal(e.now.Add(-20*time.Minute)) || !p.ProvenAt.Equal(e.now) { t.Fatalf("proof = %+v", p) } } func TestProofRootDoesNotMintARefusedBuild(t *testing.T) { e := newProofRootEnv(t, provePolicy) if err := release.WriteRefusal(e.bake, release.Refusal{Host: observedHost, App: "console", Version: "v1", SHA256: e.sha}); err != nil { t.Fatal(err) } e.root.run(context.Background()) if e.proof(t) != nil { t.Fatal("minted a proof for a build a refusal record names") } } // D3-held fix round 1: a move's held refusal (MoveOp set) is scoped to // that move's held stage and never stops this box minting a proof for a // build it runs. Since final review finding 3 it lives in the held // namespace, which minting never reads. func TestProofRootMintsPastAMovesHeldRefusal(t *testing.T) { e := newProofRootEnv(t, provePolicy) if err := release.WriteRefusal(release.HeldRefusalBase(e.bake), release.Refusal{Host: "held.example.com", App: "console", Version: "v1", SHA256: e.sha, Kind: release.RefusalNotProven, MoveOp: "mv_abcdefghijklmnopqrstuvwxyz"}); err != nil { t.Fatal(err) } e.root.run(context.Background()) if e.proof(t) == nil { t.Fatalf("a move's held refusal stopped the mint; logs: %v", e.logs) } } func TestProofRootNeverOverwritesAProof(t *testing.T) { t.Parallel() e := newProofRootEnv(t, provePolicy) first := e.signed(t, e.now.Add(-time.Minute)) if err := release.PutProof(context.Background(), e.store, "bab", first); err != nil { t.Fatal(err) } e.root.run(context.Background()) if p := e.proof(t); p == nil || !p.ProvenAt.Equal(first.ProvenAt) { t.Fatalf("proof = %+v, want the first one untouched", p) } } // racedStore hides one key from the first Get, as if another pass minted it // between this pass's existence check and its write. type racedStore struct { release.Store hidden string gets int } func (s *racedStore) Get(ctx context.Context, key string) ([]byte, string, error) { if key == s.hidden { s.gets++ if s.gets == 1 { return nil, "", fmt.Errorf("raced: %s: %w", key, os.ErrNotExist) } } return s.Store.Get(ctx, key) } // PutProof is create-only: a second mint of one build carries a different // proven_at and conflicts. That conflict means "already minted", not an error. func TestProofRootTreatsAMintRaceAsAlreadyMinted(t *testing.T) { e := newProofRootEnv(t, provePolicy) first := e.signed(t, e.now.Add(-time.Minute)) if err := release.PutProof(context.Background(), e.store, "bab", first); err != nil { t.Fatal(err) } e.root.store = &racedStore{Store: e.store, hidden: release.ProofKey("bab", "console", e.sha, "dev")} e.root.run(context.Background()) logs := strings.Join(e.logs, "\n") if strings.Contains(logs, "writing the proof") { t.Fatalf("a mint race was logged as a write failure: %v", e.logs) } if strings.Contains(logs, "proof: minted") { t.Fatalf("a mint race was logged as this pass's mint: %v", e.logs) } if p := e.proof(t); p == nil || !p.ProvenAt.Equal(first.ProvenAt) { t.Fatalf("proof = %+v, want the first one untouched", p) } } func TestProofRootRefusesAKeyOthersCanRead(t *testing.T) { e := newProofRootEnv(t, provePolicy) if err := os.Chmod(e.keyFile, 0o644); err != nil { t.Fatal(err) } e.root.run(context.Background()) if e.proof(t) != nil { t.Fatal("minted with a world-readable key") } if !strings.Contains(strings.Join(e.logs, "\n"), "readable by root only") { t.Fatalf("logs = %v, want the key-mode refusal", e.logs) } } // Task 2's carry: a policy file that does not parse is closed. A proving box // with a typo mints nothing, and says so on every pass. func TestProofRootBrokenPolicyMintsNothingAndSaysSoEveryPass(t *testing.T) { e := newProofRootEnv(t, func(keyFile string) string { return strings.Replace(provePolicy(keyFile), `"schema":1`, `"schema":1,"requier":[]`, 1) }) for pass := 0; pass < 2; pass++ { e.logs = nil e.root.hourly = pass == 1 e.root.run(context.Background()) if e.proof(t) != nil { t.Fatalf("pass %d minted under a policy that does not parse", pass) } if !strings.Contains(strings.Join(e.logs, "\n"), "nothing is minted until it parses") { t.Fatalf("pass %d logs = %v, want the policy error", pass, e.logs) } } } type noReadStore struct { release.Store t *testing.T } func (s noReadStore) Get(ctx context.Context, key string) ([]byte, string, error) { s.t.Errorf("bucket GET %s with no policy file", key) return s.Store.Get(ctx, key) } // Review focus 3's source: with no policy file at all, the build already // live enters the healthy history, and no proof is read or minted. func TestProofRootRecordsTheLiveBuildAsHealthy(t *testing.T) { e := newProofRootEnv(t, nil) e.root.store = noReadStore{Store: e.store, t: t} e.root.run(context.Background()) h := release.ReadHealthy(e.bake, "bab", "console") if len(h) != 1 || h[0].SHA256 != e.sha || h[0].How != "live" || h[0].Version != "v1" { t.Fatalf("history = %+v, want the live build", h) } if e.proof(t) != nil { t.Fatal("a proof was minted with no policy file") } } // A box that never had the requirement (Tito, a self-hoster) has nothing // switched off and must stay quiet; one that had it and lost it logs hourly. func TestProofRootHourlyLineOnlyWhereTheRequirementWasOnce(t *testing.T) { e := newProofRootEnv(t, func(string) string { return `{"schema":1}` }) e.root.hourly = true e.root.run(context.Background()) if strings.Contains(strings.Join(e.logs, "\n"), "requirement is off") { t.Fatalf("a box that never required a proof logged that the requirement is off: %v", e.logs) } } func TestProofRootHourlyLineWhenRequirementIsOff(t *testing.T) { e := newProofRootEnv(t, func(string) string { return `{"schema":1}` }) marker := filepath.Join(e.root.bakeDir, "proof-required", "console") if err := os.MkdirAll(filepath.Dir(marker), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(marker, nil, 0o644); err != nil { t.Fatal(err) } e.root.hourly = true e.root.run(context.Background()) if !strings.Contains(strings.Join(e.logs, "\n"), "requirement is off for console") { t.Fatalf("logs = %v, want the hourly requirement-off line", e.logs) } e.logs = nil e.root.hourly = false e.root.run(context.Background()) if strings.Contains(strings.Join(e.logs, "\n"), "requirement is off") { t.Fatalf("a stage-only pass printed the hourly line: %v", e.logs) } } // The marker is written by any root pass that reads a require entry, not // only the hourly one, so a requirement switched on and off between two // hourly passes still leaves its trail. Deleting the whole file (the other // break glass) is "off" too. func TestProofRootRequireEntryLeavesTheMarker(t *testing.T) { t.Parallel() e := newProofRootEnv(t, nil) if err := os.WriteFile(e.root.policyPath, []byte(fmt.Sprintf( `{"schema":1,"require":[{"app":"console","prover":"dev","key":%q}]}`, e.pubB64)), 0o644); err != nil { t.Fatal(err) } marker := filepath.Join(e.bake, "proof-required", "console") e.root.run(context.Background()) if _, err := os.Stat(marker); err != nil { t.Fatalf("a stage-only pass that read a require entry left no marker: %v", err) } if strings.Contains(strings.Join(e.logs, "\n"), "requirement is off") { t.Fatalf("logged the requirement off while it is on: %v", e.logs) } if err := os.Remove(e.root.policyPath); err != nil { t.Fatal(err) } e.logs = nil e.root.hourly = true e.root.run(context.Background()) if !strings.Contains(strings.Join(e.logs, "\n"), "requirement is off for console") { t.Fatalf("logs = %v, want the hourly requirement-off line once the file is gone", e.logs) } } // Final review H4: the first adoption after a requirement is switched on. // A has run well for two hours with an empty healthy history; B is promoted // with a valid dev proof. One RunAdopt-shaped run (proofRoot.around a real // release.Pass) adopts B, and A, the build B replaced, is in the history // too, so a later rollback to A does not wait for a dev proof. B enters it // through the settle hook. func TestProofRootRecordsTheReplacedBuildBeforeAdoption(t *testing.T) { ctx := context.Background() opt, bake, etc := t.TempDir(), t.TempDir(), t.TempDir() s := release.NewDirStore(t.TempDir()) plat := release.AdoptPlatform() pub, priv, err := ed25519.GenerateKey(nil) if err != nil { t.Fatal(err) } for ver, body := range map[string]string{"a": "build a", "b": "build b"} { m := release.Manifest{App: "console", Version: ver, Kind: "binary", Artifacts: map[string]release.Artifact{plat: {}}} if err := release.Ship(ctx, s, "bab", m, map[string][]byte{plat: []byte(body)}); err != nil { t.Fatalf("Ship %s: %v", ver, err) } } if err := release.StageArtifact(opt, "bab", "a", "console", []byte("build a")); err != nil { t.Fatal(err) } if err := release.PointLive(opt, observedHost, "bab", "a", "console"); err != nil { t.Fatal(err) } if err := release.Promote(ctx, s, priv, "bab", "console", "canary/console", "b"); err != nil { t.Fatal(err) } reg := restartTestRegistry(t) rt := registry.Route{Host: observedHost, Kind: "instance", App: "console", Channel: "canary/console", Unit: "console.service", Status: "ready"} if err := reg.Upsert(rt); err != nil { t.Fatal(err) } shaA := sha256Hex("build a") shaB := sha256Hex("build b") seed, proverPub, err := release.GenerateKey() if err != nil { t.Fatal(err) } proverKey, err := release.ParseSigningKey(seed, "test") if err != nil { t.Fatal(err) } now := time.Date(2026, 10, 6, 10, 0, 0, 0, time.UTC) p, err := release.SignProof(proverKey, release.Proof{App: "console", Platform: plat, SHA256: shaB, Version: "b", Prover: "dev", ActiveSince: now.Add(-time.Hour), ProvenAt: now}) if err != nil { t.Fatal(err) } if err := release.PutProof(ctx, s, "bab", p); err != nil { t.Fatal(err) } policyPath := filepath.Join(etc, "proof-policy.json") if err := os.WriteFile(policyPath, []byte(fmt.Sprintf( `{"schema":1,"require":[{"app":"console","prover":"dev","key":%q}]}`, proverPub)), 0o644); err != nil { t.Fatal(err) } link := release.LiveLink(opt, observedHost) units := &fakeUnits{shows: []unitProps{{ActiveState: "active", InvocationID: "a1", MainPID: 7, ExecPath: link, ActiveEnterTimestamp: now.Add(-2 * time.Hour)}}} procExe := func(int) string { return link } // the process runs whatever the link names clock := func() time.Time { return now } root := proofRoot{ obs: proofObserver{sys: units, procExe: procExe, optDir: opt, now: clock, resolve: proofFixtureResolver(rt), ready: func(registry.Route) func() error { return func() error { return nil } }}, reg: reg, store: s, bakeDir: bake, policyPath: policyPath, readKey: readProvingKey, logf: func(f string, a ...any) { t.Logf(f, a...) }, } // The restart starts B's process and settles, as checkedUnitRestart // would, and its settled hook records what it runs. restart := func(host string) error { units.shows, units.n = []unitProps{{ActiveState: "active", InvocationID: "b1", MainPID: 8, ExecPath: link, ActiveEnterTimestamp: now}}, 0 if err := root.obs.recordSettled(bake)(rt); err != nil { return err } return nil } if err := root.around(ctx, func() error { return release.Pass(ctx, reg, s, pub, opt, restart, release.BakeDir(bake), release.WithProofGate(release.NewProofGate(policyPath, bake))) }); err != nil { t.Fatal(err) } if v, _ := release.LiveVersion(opt, observedHost); v != "b" { t.Fatalf("live = %q, want b adopted on its proof", v) } got := map[string]string{} for _, h := range release.ReadHealthy(bake, "bab", "console") { got[h.SHA256] = h.How } if got[shaA] != "live" { t.Fatalf("history = %+v, want a (sha256 %s) recorded live before the pass replaced it", got, shaA) } if got[shaB] != "settle" { t.Fatalf("history = %+v, want b (sha256 %s) recorded by the settle hook", got, shaB) } } // A route the pass did not touch is observed once: the second half reuses // the first half's look when the link and the process are unchanged, and // looks again when the process was restarted under the same link. func TestProofRootReusesAnUnchangedObservation(t *testing.T) { e := newProofRootEnv(t, provePolicy) units := e.root.obs.sys.(*fakeUnits) obs := e.root.recordLive(context.Background()) if o := obs.byHost[observedHost]; o.err != nil { t.Fatalf("first half: %v", o.err) } rt := registry.Route{Host: observedHost, App: "console", Unit: "console.service"} if !e.root.stillLive(rt, obs.byHost[observedHost]) { t.Fatal("an untouched route is not still live") } units.shows = []unitProps{{ActiveState: "active", InvocationID: "i2", MainPID: 9, ExecPath: units.shows[0].ExecPath, ActiveEnterTimestamp: e.now}} units.n = 0 if e.root.stillLive(rt, obs.byHost[observedHost]) { t.Fatal("a restarted process under the same link counts as still live") } e.root.mintAndLog(context.Background(), obs) if e.proof(t) != nil { t.Fatal("minted from the first half's look at a process that has since restarted") } } func sha256Hex(s string) string { h := sha256.Sum256([]byte(s)) return hex.EncodeToString(h[:]) } func TestPlan14NoCensusProofKeepsMain(t *testing.T) { for _, flag := range []string{"", "1"} { t.Run("flag="+flag, func(t *testing.T) { t.Setenv("CARLOS_HOST_CLAIM", flag) e := newProofRootEnv(t, provePolicy) e.root.mintCensus = nil before, _ := os.ReadDir(e.bake) history, err := configureRootProof(&e.root, rootRestartChecks{legacy: func() (bool, error) { return true, nil }}, func() (*release.RootHistoryPass, error) { t.Fatal("no-census history creation") return nil, os.ErrPermission }) if err != nil || history != nil || e.root.recordProgram != nil || e.root.refusals != nil || e.root.mintCensus != nil { t.Fatal("new root policy on legacy box", err) } after, _ := os.ReadDir(e.bake) if len(before) != len(after) { t.Fatal("configuration wrote state") } e.root.run(t.Context()) if e.proof(t) == nil { t.Fatal("legacy proving box stopped minting", e.logs) } }) } }