package host import ( "context" "crypto/ed25519" "crypto/sha256" "encoding/hex" "encoding/json" "errors" "net" "os" "os/exec" "path/filepath" "testing" "time" "github.com/carlosframework/platform/internal/cgroup" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/hostguard" "github.com/carlosframework/platform/internal/instancectl" "github.com/carlosframework/platform/internal/provision" "github.com/carlosframework/platform/internal/release" "github.com/carlosframework/platform/internal/rootstate" "github.com/carlosframework/platform/internal/safefile" ) type generationDisk struct { *rollbackDisk fail int } func (s *generationDisk) Write(k string, b []byte) error { if err := s.rollbackDisk.Write(k, b); err != nil { return err } if s.fail > 0 { s.fail-- return os.ErrPermission } return nil } func generationLaunchFixture(t *testing.T) (*launchFixture, *generationDisk, provision.Record) { return generationLaunchFixtureHost(t, "shop.example") } func generationLaunchFixtureHost(t *testing.T, host string) (*launchFixture, *generationDisk, provision.Record) { t.Helper() f := newLaunchFixtureHost(t, servingOp(), host) store := release.NewDirStore(t.TempDir()) rec := provision.Record{Host: f.target.Host, Account: "bab", App: "shop", CreatedAt: time.Date(2026, 10, 1, 0, 0, 0, 0, time.UTC)} plan14PutRecord(t, store, rec) inc, err := hostclaim.Incarnation(rec.Account, rec.App, rec.Host, rec.CreatedAt) if err != nil { t.Fatal(err) } previous, err := f.journal.Read(t.Context(), f.target) if err != nil { t.Fatal(err) } f.target.Incarnation = inc f.req.Reservation.Target = f.target previous.Target = f.target previous.Groups[0].RelativePath = filepath.Join("system.slice/carlos-edge.service", cgroup.ExecName(claimContainmentName(f.target))) if err := f.journal.Prelaunch(t.Context(), previous); err != nil { t.Fatal(err) } f.claims.set(func(c *hostclaim.Claim) { c.Holder.Incarnation = inc c.Holder.Account = rec.Account c.Holder.App = rec.App c.Holder.Key = provision.RecordKey(rec.Account, rec.App, rec.Host) c.Holder.Admissions = []hostclaim.AdmissionRecord{{Box: f.target.Box, Region: f.target.Region, Session: f.target.Session, Generation: c.Holder.Generation}} }) f.root.Guard.Root = hostguard.NewRootAuthority(&rollbackDisk{dir: t.TempDir()}) in := hostguard.RootInstallation{Artifacts: hostguard.InstallationArtifacts{Schema: 1, StateDirectory: true}, Host: rec.Host, Account: rec.Account, App: rec.App, Record: provision.RecordKey(rec.Account, rec.App, rec.Host), Incarnation: inc, Stamp: f.target.Stamp, Box: f.target.Box, Enrollment: f.target.Enrollment, Migration: 1, InventoryRevision: 1} if err := f.root.Guard.EstablishRoot(t.Context(), in, func(*hostclaim.Claim) error { return nil }); err != nil { t.Fatal(err) } history := &release.RootHistory{Store: &rollbackDisk{dir: t.TempDir()}} if err := history.RecordCreation(release.HistoryBinding{Host: rec.Host, Account: rec.Account, App: rec.App}, rootHistoryInstallation(in)); err != nil { t.Fatal(err) } f.root.history = func(ctx context.Context, fn func(*release.RootHistory) error) error { return fn(history) } f.root.installedProgram = func(context.Context, hostbox.RoleLaunchRequest, instancectl.ClaimUnitStarted, ClaimLaunchObserved) (string, error) { return "observed/program", nil } opt := t.TempDir() body := []byte("the signed build") if err := release.StageArtifact(opt, rec.Account, "v1", rec.App, body); err != nil { t.Fatal(err) } path := release.VersionPath(opt, rec.Account, "v1", rec.App) file, err := os.Open(path) if err != nil { t.Fatal(err) } id, err := rootstate.Identity(file) file.Close() if err != nil { t.Fatal(err) } sum := sha256.Sum256(body) f.kernel.path = path f.kernel.digest = hex.EncodeToString(sum[:]) f.kernel.want = hostbox.LaunchFile{Device: id.Device, Inode: id.Inode} f.kernel.observed.Executable = f.kernel.want f.kernel.observed.Running = f.kernel.want f.req.Executable = path f.req.Reservation.Executable = f.kernel.want f.req.Spec.Executable = path f.req.Spec.Digest = f.kernel.digest digest, err := f.req.Spec.Hash() if err != nil { t.Fatal(err) } f.req.Reservation.Inputs = digest f.claims.set(func(c *hostclaim.Claim) { a := *c.Holder.WakeRetry.Attempt a.LaunchInputs = digest r := *c.Holder.WakeRetry r.Attempt = &a c.Holder.WakeRetry = &r }) pub, priv, err := ed25519.GenerateKey(nil) if err != nil { t.Fatal(err) } m := release.Manifest{App: rec.App, Version: "v1", Kind: "binary", Artifacts: map[string]release.Artifact{release.AdoptPlatform(): {}}} if err := release.Ship(t.Context(), store, rec.Account, m, map[string][]byte{release.AdoptPlatform(): body}); err != nil { t.Fatal(err) } if err := release.Promote(t.Context(), store, priv, rec.Account, rec.App, "canary/console", "v1"); err != nil { t.Fatal(err) } disk := &generationDisk{rollbackDisk: &rollbackDisk{dir: t.TempDir()}} f.root.generations = &rootGenerationSource{store: store, optDir: opt, pub: func() (ed25519.PublicKey, error) { return pub, nil }, open: func() (rootstate.Store, error) { return disk, nil }, openFile: func(p string) (*os.File, error) { return safefile.OpenFile(p, os.O_RDONLY, 0) }} return f, disk, rec } func readGenerationLaunch(t *testing.T, d *generationDisk) release.GenerationReservation { t.Helper() names, err := d.Names() if err != nil || len(names) != 1 { t.Fatalf("reservations: %v %v", names, err) } raw, err := d.Read(names[0]) if err != nil { t.Fatal(err) } var r release.GenerationReservation if err := json.Unmarshal(raw, &r); err != nil { t.Fatal(err) } return r } func TestPlan14GenerationReservedBeforeClaimSpawn(t *testing.T) { f, d, _ := generationLaunchFixture(t) checked := false f.units.beforeCheck = func() { r := readGenerationLaunch(t, d) if r.ID == "" || r.Binding.Path != f.req.Executable || r.Binding.Digest != f.req.Spec.Digest || r.Binding.Binding.Account != "bab" || r.Binding.Binding.App != "shop" || r.Process.PID != 0 { t.Fatalf("pre-spawn record: %+v", r) } checked = true } if _, err := f.root.Serve(t.Context(), f.peer, f.req); err != nil { t.Fatal(err) } if !checked { t.Fatal("spawn boundary was not checked") } r := readGenerationLaunch(t, d) if r.Process.PID != f.units.started.MainPID || r.Process.TenantPID != f.kernel.observed.TenantPID || r.Process.GroupIdentity.Inode != f.kernel.observed.Group.Inode { t.Fatalf("missing process evidence: %+v", r) } if err := f.root.stop(t.Context(), f.peer, *f.req.Reservation); err != nil { t.Fatal(err) } if after := readGenerationLaunch(t, d); after != r { t.Fatal("unit settlement retired the generation pin") } } func TestPlan14GenerationLaunchRefusals(t *testing.T) { for _, attack := range []string{"open", "sync", "no-lock", "unsigned", "wrong-account", "wrong-digest", "tree-replaced", "record-changed", "reservation-erased"} { t.Run(attack, func(t *testing.T) { f, d, rec := generationLaunchFixture(t) switch attack { case "open": f.root.generations.open = func() (rootstate.Store, error) { return nil, os.ErrPermission } case "sync": d.fail = 1 case "no-lock": f.root.history = nil case "unsigned": f.root.generations.pub = func() (ed25519.PublicKey, error) { p, _, e := ed25519.GenerateKey(nil); return p, e } case "wrong-account": rec.Account = "bap" plan14PutRecord(t, f.root.generations.store, rec) if err := f.root.generations.store.Delete(t.Context(), provision.RecordKey("bab", rec.App, rec.Host)); err != nil { t.Fatal(err) } case "wrong-digest": if err := os.WriteFile(f.req.Executable, []byte("replaced"), 0755); err != nil { t.Fatal(err) } case "tree-replaced": f.units.beforeCheck = func() { dir := filepath.Dir(f.req.Executable) if err := os.Rename(dir, dir+".old"); err != nil { t.Fatal(err) } if err := os.Mkdir(dir, 0755); err != nil { t.Fatal(err) } if err := os.Rename(filepath.Join(dir+".old", filepath.Base(f.req.Executable)), f.req.Executable); err != nil { t.Fatal(err) } } case "record-changed": f.units.beforeCheck = func() { rec.Environment = "changed"; plan14PutRecord(t, f.root.generations.store, rec) } case "reservation-erased": f.units.beforeCheck = func() { names, err := d.Names() if err != nil { t.Fatal(err) } for _, n := range names { if err := d.Remove(n); err != nil { t.Fatal(err) } } } } if _, err := f.root.Serve(t.Context(), f.peer, f.req); err == nil { t.Fatal("unreserved launch succeeded") } if n, _ := f.units.counts(); n != 0 { t.Fatalf("refused launch started %d units", n) } }) } } func TestPlan14GenerationStartedSyncFailureRetainsPin(t *testing.T) { f, d, _ := generationLaunchFixture(t) f.kernel.observe = func(instancectl.ClaimUnitStarted) (ClaimLaunchObserved, error) { d.fail = 1 return f.kernel.observed, nil } if _, err := f.root.Serve(t.Context(), f.peer, f.req); err == nil { t.Fatal("unsynced launch reported success") } if n, stops := f.units.counts(); n != 1 || stops != 1 { t.Fatalf("starts=%d stops=%d", n, stops) } r := readGenerationLaunch(t, d) if r.ID == "" || r.Binding.Path != f.req.Executable { t.Fatal("lost failed launch pin") } d.fail = 0 if _, err := (&release.RootGenerations{Store: d}).Read(r.Binding); err != nil { t.Fatal(err) } } func TestPlan14GenerationReservationProcessCrash(t *testing.T) { if boundary := os.Getenv("PLAN14_GENERATION_CRASH"); boundary != "" { f, d, _ := generationLaunchFixture(t) d.dir = os.Getenv("PLAN14_GENERATION_DIR") d.afterWrite = func(_ string, raw []byte) { var r release.GenerationReservation if err := json.Unmarshal(raw, &r); err != nil { t.Fatal(err) } if boundary == "reserved" && r.Revision == 1 || boundary == "observed" && r.Revision == 2 { os.Exit(73) } } if boundary == "before-spawn" { f.units.beforeCheck = func() { os.Exit(73) } } _, err := f.root.Serve(t.Context(), f.peer, f.req) t.Fatal("crash boundary not reached", err) } for _, boundary := range []string{"reserved", "before-spawn", "observed"} { t.Run(boundary, func(t *testing.T) { dir := t.TempDir() cmd := exec.Command(os.Args[0], "-test.run=^TestPlan14GenerationReservationProcessCrash$", "-test.count=1") cmd.Env = append(os.Environ(), "PLAN14_GENERATION_CRASH="+boundary, "PLAN14_GENERATION_DIR="+dir) out, err := cmd.CombinedOutput() var exit *exec.ExitError if !errors.As(err, &exit) || exit.ExitCode() != 73 { t.Fatalf("child: %v %s", err, out) } d := &generationDisk{rollbackDisk: &rollbackDisk{dir: dir}} r := readGenerationLaunch(t, d) got, err := (&release.RootGenerations{Store: d}).Read(r.Binding) if err != nil || got != r || r.ID == "" || r.Binding.Executable.Inode == 0 || r.Binding.TreeIdentity.Inode == 0 { t.Fatalf("crash lost pin: %+v %v", got, err) } if (r.Process.PID > 0) != (boundary == "observed") { t.Fatalf("wrong process evidence: %+v", r) } }) } } func TestPlan14GenerationProductionWiring(t *testing.T) { t.Setenv("PATH", t.TempDir()) oldOpen, oldShapes := openClaimUnitBindings, productionClaimUnitShapes oldMaterial := openClaimUnitMaterializations openClaimUnitBindings = func() (rootstate.Store, error) { return &rollbackMemory{files: map[string][]byte{}}, nil } openClaimUnitMaterializations = func() (rootstate.Store, error) { return &rollbackMemory{files: map[string][]byte{}}, nil } productionClaimUnitShapes = func() (map[string]claimUnitShape, error) { return map[string]claimUnitShape{}, nil } t.Cleanup(func() { openClaimUnitBindings, productionClaimUnitShapes = oldOpen, oldShapes openClaimUnitMaterializations = oldMaterial }) f, _, _ := generationLaunchFixture(t) root := ClaimRootHTTP{Launches: f.journal, Stop: ClaimStopHTTP{Authenticate: func(context.Context, net.Conn) (StopPeer, error) { return f.peer, nil }, Coordinator: ClaimStopCoordinator{Kernel: stopKernelFunc(nil), Guard: f.root.Guard}}} assembly := &ClaimAssembly{Claims: f.root.generations.store, ManagedRoots: map[string]string{"opt": f.root.generations.optDir, "data": "/data"}, Local: f.root.Local} ctx := context.WithValue(t.Context(), claimEntryKey{}, claimEntry{Enabled: true, FlagRead: true, Assembly: assembly}) handler, err := WithProductionClaimLaunch(ctx, root) if err != nil { t.Fatal(err) } launch := handler.(ClaimRootWithLaunch).Launch if _, ok := launch.Units.(claimUnitDispatch); !ok || launch.unitBindings == nil { t.Fatal("production unit dispatch missing") } if err := assembly.Close(t.Context()); err != nil { t.Fatal(err) } if launch.generations == nil || launch.generations.open == nil || launch.generations.openFile == nil || launch.generations.pub == nil || launch.generations.store != assembly.Claims || launch.history == nil { t.Fatal("production launch lacks a mandatory root reservation") } }