carlos / platform Public

WIP claim Plan 14 track E checkpoint 16 (not for merge): E1 fresh-install evidence

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev 8b7291f87d7649bf1cc6146cfa558b4ce9d24572 parent d5459b2
18 files changed, +925 −18
  • internal/host/claimlaunch_production.go +3 −0
  • internal/host/claimlaunch_root.go +63 −0
  • internal/host/claimretire_transport.go +1 −0
  • internal/host/claimunit.go +1 −1
  • internal/host/claimunit_test.go +1 −1
  • internal/host/plan14_fresh_install_process_test.go +100 −0
  • internal/host/plan14_fresh_install_test.go +271 −0
  • internal/host/roothistory.go +1 −0
  • internal/host/roothistory_creation.go +84 −0
  • internal/host/roothistory_launch.go +55 −0
  • internal/host/rootinventory.go +23 −4
  • internal/provision/plan14_e_write_census_test.go +3 −0
  • internal/provision/testdata/plan14-root-reads-e.tsv +24 −6
  • internal/release/adopt.go +9 −2
  • internal/release/plan14_fresh_install_test.go +215 −0
  • internal/release/roothistory.go +9 −1
  • internal/release/roothistory_creation.go +50 −0
  • internal/release/roothistory_pass.go +12 −3
diff --git a/internal/host/claimlaunch_production.go b/internal/host/claimlaunch_production.go
index 041037098..f661ca022 100644
--- a/internal/host/claimlaunch_production.go
+++ b/internal/host/claimlaunch_production.go
@@ -64,6 +64,7 @@ func WithProductionClaimLaunch(ctx context.Context, handler http.Handler) (http.
return nil, &ClaimDependencyError{Contracts: []string{"claim Plan 11 isolated root launch: production claim assembly"}}
}
a := entry.Assembly
+ root.history = productionCreationHistory
wrapped, err := claimLaunchFor(root, a.Identity.Verify, a.ManagedRoots)
if err != nil {
return nil, err
@@ -72,6 +73,8 @@ func WithProductionClaimLaunch(ctx context.Context, handler http.Handler) (http.
return nil, ErrClaimLaunchUnavailable
}
wrapped.Launch.Local = a.Local
+ wrapped.Launch.history = root.history
+ wrapped.Launch.installedProgram = wrapped.Launch.rootInstalledProgram
return wrapped, nil
}
diff --git a/internal/host/claimlaunch_root.go b/internal/host/claimlaunch_root.go
index 1d5b20e31..8870c832c 100644
--- a/internal/host/claimlaunch_root.go
+++ b/internal/host/claimlaunch_root.go
@@ -18,6 +18,7 @@ import (
execlease "github.com/carlosframework/platform/internal/hostexec"
"github.com/carlosframework/platform/internal/hostguard"
"github.com/carlosframework/platform/internal/instancectl"
+ "github.com/carlosframework/platform/internal/release"
"github.com/carlosframework/platform/internal/rootstate"
)
@@ -59,6 +60,8 @@ type ClaimLaunchObserved struct {
// roster identity, its local launch journal and its own view of the kernel.
// A request only names which reservation to run.
type ClaimLaunchRoot struct {
+ history rootHistoryAccess
+ installedProgram func(context.Context, hostbox.RoleLaunchRequest, instancectl.ClaimUnitStarted, ClaimLaunchObserved) (string, error)
executionState rootstate.Store
ValidateExecutable func(host, path, digest string) error
Local hostbox.LocalAuthority
@@ -159,6 +162,19 @@ func (h *ClaimLaunchRoot) launchRecord(ctx context.Context, peer StopPeer, t hos
}
func (h *ClaimLaunchRoot) start(ctx context.Context, peer StopPeer, q hostbox.RoleLaunchRequest) (hostbox.RoleLaunchGrant, error) {
+ if h.history == nil {
+ return h.startRecorded(ctx, peer, q, nil)
+ }
+ var out hostbox.RoleLaunchGrant
+ err := h.history(ctx, func(history *release.RootHistory) error {
+ var err error
+ out, err = h.startRecorded(ctx, peer, q, history)
+ return err
+ })
+ return out, err
+}
+
+func (h *ClaimLaunchRoot) startRecorded(ctx context.Context, peer StopPeer, q hostbox.RoleLaunchRequest, history *release.RootHistory) (hostbox.RoleLaunchGrant, error) {
r := *q.Reservation
t := r.Target
if t.BootID != peer.BootID || t.ControllerID != peer.ControllerID {
@@ -178,13 +194,60 @@ func (h *ClaimLaunchRoot) start(ctx context.Context, peer StopPeer, q hostbox.Ro
return hostbox.RoleLaunchGrant{}, err
}
var grant hostbox.RoleLaunchGrant
+ var binding release.HistoryBinding
+ if history != nil {
+ if h.Guard.Root == nil || h.installedProgram == nil {
+ return grant, ErrClaimLaunchUnavailable
+ }
+ view, err := h.Guard.Root.View(ctx, t.Host, hostguard.Captured{Incarnation: t.Incarnation, Stamp: t.Stamp})
+ if err != nil {
+ return grant, err
+ }
+ in := view.Installation
+ if in.Box != t.Box || in.Enrollment != t.Enrollment || in.InventoryRevision == 0 {
+ return grant, hostbox.ErrChanged
+ }
+ binding = release.HistoryBinding{Host: in.Host, Account: in.Account, App: in.App}
+ }
check := func(c *hostclaim.Claim) error { return h.authorize(ctx, peer, q, c) }
// The fence, the claim check, the journal intention, the unit start and
// the started record all happen under the host guard a root fence also
// takes, so no fence or second launch interleaves with them.
err = h.Guard.With(ctx, t.Host, hostguard.Captured{Incarnation: t.Incarnation, Stamp: t.Stamp}, check, func() error {
+ if history != nil {
+ if err := history.ConsumeCreation(binding); err != nil {
+ return err
+ }
+ }
var e error
grant, e = h.launchLocked(ctx, peer, q, r)
+ if e == nil && history != nil {
+ started := instancectl.ClaimUnitStarted{Unit: grant.Unit, InvocationID: grant.UnitInvocationID, MainPID: grant.PID, ControlGroup: grant.Group.RelativePath}
+ observed, err := h.waitReady(ctx, started, r.Executable)
+ if err != nil {
+ return err
+ }
+ if observed.ProcessStart != grant.ProcessStart {
+ return hostbox.ErrChanged
+ }
+ program, err := h.installedProgram(ctx, q, started, observed)
+ if err != nil {
+ return err
+ }
+ fresh, err := h.Guard.Claims.Read(ctx, t.Host)
+ if err != nil {
+ return err
+ }
+ if fresh.Absent {
+ return hostbox.ErrChanged
+ }
+ if err := h.authorize(ctx, peer, q, fresh.Claim); err != nil {
+ return err
+ }
+ if err := history.RecordInstalledProgram(binding, program); err != nil {
+ return err
+ }
+ }
return e
})
return grant, err
diff --git a/internal/host/claimretire_transport.go b/internal/host/claimretire_transport.go
index 7cec4edf0..0061339f2 100644
--- a/internal/host/claimretire_transport.go
+++ b/internal/host/claimretire_transport.go
@@ -22,6 +22,7 @@ import (
)
type ClaimRootHTTP struct {
+ history rootHistoryAccess
executionRuntime *claimExecutionRuntime
Execution *execlease.RootBroker
InventoryActor func(context.Context) (hostbox.Actor, error)
diff --git a/internal/host/claimunit.go b/internal/host/claimunit.go
index 85e9f9cb3..e9e48fb39 100644
--- a/internal/host/claimunit.go
+++ b/internal/host/claimunit.go
@@ -96,7 +96,7 @@ func claimUnitContent(name string, content []byte, cfg claimUnitSettings) []byte
}
return strconv.Quote(strings.ReplaceAll(p, "%", "%%"))
}
- roots := []string{"/run/carlos-control", "/run/carlos", "/var/lib/carlos-claims", "/run/systemd/system", cfg.data, cfg.opt, "/etc/systemd/system", "/sys/fs/cgroup"}
+ roots := []string{"/run/carlos-control", "/run/carlos", "/var/lib/carlos-claims", rootReleasePrivate, "/run/systemd/system", cfg.data, cfg.opt, "/etc/systemd/system", "/sys/fs/cgroup"}
for i, p := range roots {
roots[i] = quote(p)
}
diff --git a/internal/host/claimunit_test.go b/internal/host/claimunit_test.go
index 55c5ae40d..4477e43f6 100644
--- a/internal/host/claimunit_test.go
+++ b/internal/host/claimunit_test.go
@@ -79,7 +79,7 @@ func TestClaimControlUnitInstallers(t *testing.T) {
"EnvironmentFile": {"-/etc/carlos/host.env"},
"StateDirectory": {"carlos-claims"},
"StateDirectoryMode": {"0700"},
- "ReadWritePaths": {"/run/carlos-control /run/carlos /var/lib/carlos-claims /run/systemd/system /data /opt/carlos /etc/systemd/system /sys/fs/cgroup"},
+ "ReadWritePaths": {"/run/carlos-control /run/carlos /var/lib/carlos-claims /var/lib/carlos-release/private /run/systemd/system /data /opt/carlos /etc/systemd/system /sys/fs/cgroup"},
"RestrictAddressFamilies": {"AF_UNIX AF_INET AF_INET6"},
"ProtectSystem": {"strict"},
"ProtectHome": {"yes"},
diff --git a/internal/host/plan14_fresh_install_process_test.go b/internal/host/plan14_fresh_install_process_test.go
new file mode 100644
index 000000000..3e445d694
--- /dev/null
+++ b/internal/host/plan14_fresh_install_process_test.go
@@ -0,0 +1,100 @@
+package host
+
+import (
+ "context"
+ "errors"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "testing"
+
+ "github.com/carlosframework/platform/internal/hostbox"
+ "github.com/carlosframework/platform/internal/hostclaim"
+ "github.com/carlosframework/platform/internal/hostguard"
+ "github.com/carlosframework/platform/internal/provision"
+ "github.com/carlosframework/platform/internal/release"
+)
+
+func TestPlan14FreshInstallProcessCrash(t *testing.T) {
+ if boundary := os.Getenv("PLAN14_CREATION_CRASH"); boundary != "" {
+ dir := os.Getenv("PLAN14_CREATION_DIR")
+ root, disk := plan14InventoryProcessRoot(t, dir)
+ hs := &rollbackDisk{dir: filepath.Join(dir, "history")}
+ h := &release.RootHistory{Store: hs}
+ root.history = func(_ context.Context, fn func(*release.RootHistory) error) error { return fn(h) }
+ count := 0
+ disk.afterWrite = func(string, []byte) {
+ count++
+ if boundary == "intent" && count == 1 || boundary == "committed" && count == 2 {
+ os.Exit(73)
+ }
+ }
+ hs.afterWrite = func(string, []byte) {
+ if boundary == "creation" {
+ os.Exit(73)
+ }
+ }
+ obs, err := hostclaim.Read(t.Context(), root.Stop.Coordinator.Evidence, recoveryHost)
+ if err != nil {
+ t.Fatal(err)
+ }
+ holder := obs.Claim.Holder
+ _, etag, err := root.Stop.Coordinator.Evidence.Get(t.Context(), holder.Key)
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, client := hostbox.RemoteJournals(root.commitRootInventory)
+ _, err = client.WriteExpected(t.Context(), hostbox.StopInventory{Complete: true, Host: obs.Host, Incarnation: holder.Incarnation, InstallationStamp: holder.Stamp, Box: "box-1", Enrollment: "en", DirectoryRevision: "1"}, hostbox.InventoryExpectation{RecordKey: holder.Key, RecordETag: etag, Target: hostguard.FenceTarget{Host: obs.Host, Incarnation: holder.Incarnation, Stamp: holder.Stamp, Session: holder.Session, Box: "box-1", Enrollment: "en", ClaimETag: obs.ETag, Op: obs.Claim.Op.ID, Phase: obs.Claim.Op.Phase}})
+ t.Fatal("boundary not reached", boundary, err)
+ }
+ for _, boundary := range []string{"intent", "creation", "committed"} {
+ t.Run(boundary, func(t *testing.T) {
+ dir := t.TempDir()
+ for _, name := range []string{"root", "data", "opt", "units", "locks", "child", "history"} {
+ if err := os.Mkdir(filepath.Join(dir, name), 0700); err != nil {
+ t.Fatal(err)
+ }
+ }
+ f := newRecoveryFixture(t)
+ obs := f.read()
+ holder := obs.Claim.Holder
+ plan14PutRecord(t, f.claims, provision.Record{Schema: 1, Host: obs.Host, Account: holder.Account, App: holder.App, Channel: "stable", CreatedAt: f.now})
+ store := release.NewDirStore(filepath.Join(dir, "deployment"))
+ keys, err := f.claims.List(t.Context(), "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, key := range keys {
+ raw, _, err := f.claims.Get(t.Context(), key)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err = store.Put(t.Context(), key, raw, ""); err != nil {
+ t.Fatal(err)
+ }
+ }
+ cmd := exec.Command(os.Args[0], "-test.run=^TestPlan14FreshInstallProcessCrash$")
+ cmd.Env = append(os.Environ(), "PLAN14_CREATION_CRASH="+boundary, "PLAN14_CREATION_DIR="+dir, "TMPDIR="+filepath.Join(dir, "child"))
+ output, err := cmd.CombinedOutput()
+ var exit *exec.ExitError
+ if !errors.As(err, &exit) || exit.ExitCode() != 73 {
+ t.Fatalf("child: %v %s", err, output)
+ }
+ root, _ := plan14InventoryProcessRoot(t, dir)
+ h := &release.RootHistory{Store: &rollbackDisk{dir: filepath.Join(dir, "history")}}
+ root.history = func(_ context.Context, fn func(*release.RootHistory) error) error { return fn(h) }
+ if err := root.recoverRootInventory(t.Context()); err != nil {
+ t.Fatal(err)
+ }
+ view, err := root.Stop.Coordinator.Guard.Root.View(t.Context(), obs.Host, hostguard.Captured{Incarnation: holder.Incarnation, Stamp: holder.Stamp})
+ if err != nil {
+ t.Fatal(err)
+ }
+ b := release.HistoryBinding{Host: obs.Host, Account: holder.Account, App: holder.App}
+ fresh, err := h.FreshInstallation(b, rootHistoryInstallation(view.Installation))
+ if err != nil || !fresh || view.Installation.InventoryRevision != 1 {
+ t.Fatalf("recovered: %+v fresh=%v: %v", view, fresh, err)
+ }
+ })
+ }
+}
diff --git a/internal/host/plan14_fresh_install_test.go b/internal/host/plan14_fresh_install_test.go
new file mode 100644
index 000000000..e8db1e5f5
--- /dev/null
+++ b/internal/host/plan14_fresh_install_test.go
@@ -0,0 +1,271 @@
+package host
+
+import (
+ "context"
+ "crypto/sha256"
+ "encoding/hex"
+ "github.com/carlosframework/platform/internal/rootparticipation"
+ "github.com/carlosframework/platform/internal/rootstate"
+ "io"
+ "os"
+ "strings"
+ "testing"
+ "time"
+
+ "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"
+)
+
+func TestPlan14FreshInstallRegistration(t *testing.T) {
+ for _, mode := range []string{"new", "writer", "relocation", "landing", "store-failure", "replay", "deleted-history"} {
+ t.Run(mode, func(t *testing.T) {
+ kind, phase := hostclaim.KindServing, ""
+ if mode == "relocation" {
+ kind, phase = hostclaim.KindRelocating, hostclaim.PhaseInstalling
+ }
+ if mode == "landing" {
+ kind, phase = hostclaim.KindLanding, provision.LandChecking
+ }
+ f, root, q := plan14InstallRegistration(t, kind, phase)
+ if mode == "writer" {
+ o, err := hostclaim.Update(t.Context(), f.claims, f.read(), func(c *hostclaim.Claim) error { c.Holder.WriterBox = "box-1"; c.Holder.Session = 1; return nil })
+ if err != nil {
+ t.Fatal(err)
+ }
+ q.Target.ClaimETag, q.Target.Session = o.ETag, 1
+ q = plan14PreparedInventory(t, root, q)
+ }
+ disk := &rollbackDisk{dir: t.TempDir()}
+ history := &release.RootHistory{Store: disk}
+ calls := 0
+ root.history = func(ctx context.Context, fn func(*release.RootHistory) error) error {
+ calls++
+ if mode == "store-failure" {
+ return os.ErrPermission
+ }
+ return fn(history)
+ }
+ out, err := root.commitRootInventory(t.Context(), q)
+ if mode == "store-failure" {
+ if err == nil {
+ t.Fatal("registration ignored history failure")
+ }
+ if _, err = root.Stop.Coordinator.Guard.Capture(q.Target.Host); err == nil {
+ t.Fatal("registered before release exclusion")
+ }
+ return
+ }
+ if err != nil {
+ t.Fatal(err)
+ }
+ if out.Revision != 1 || calls != 1 {
+ t.Fatalf("out=%+v calls=%d", out, calls)
+ }
+ in, err := root.Stop.Coordinator.Guard.Root.View(t.Context(), q.Target.Host, hostguard.Captured{Incarnation: q.Target.Incarnation, Stamp: q.Target.Stamp})
+ if err != nil {
+ t.Fatal(err)
+ }
+ b := release.HistoryBinding{Host: in.Installation.Host, Account: in.Installation.Account, App: in.Installation.App}
+ id := rootHistoryInstallation(in.Installation)
+ if mode == "replay" || mode == "deleted-history" {
+ if mode == "deleted-history" {
+ names, e := disk.Names()
+ if e != nil {
+ t.Fatal(e)
+ }
+ for _, name := range names {
+ if e = disk.Remove(name); e != nil {
+ t.Fatal(e)
+ }
+ }
+ }
+ if _, err = root.commitRootInventory(t.Context(), q); err != nil {
+ t.Fatal(err)
+ }
+ }
+ fresh, err := history.FreshInstallation(b, id)
+ want := mode == "new" || mode == "replay"
+ if want && (err != nil || !fresh) {
+ t.Fatalf("new creation not recorded: %v %v", fresh, err)
+ }
+ if !want && err == nil && fresh {
+ t.Fatal("old installation became fresh")
+ }
+ })
+ }
+}
+
+func TestPlan14FreshInstallLaunchObservation(t *testing.T) {
+ for _, mode := range []string{"ready", "not-ready", "history-failure", "record-failure"} {
+ t.Run(mode, func(t *testing.T) {
+ f := newLaunchFixture(t, servingOp())
+ f.claims.set(func(c *hostclaim.Claim) {
+ c.Holder.Account = "bab"
+ c.Holder.App = "app"
+ c.Holder.Key = "record"
+ 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{Host: f.target.Host, Account: "bab", App: "app", Record: "record", Incarnation: f.target.Incarnation, 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)
+ }
+ h := &release.RootHistory{Store: &rollbackDisk{dir: t.TempDir()}}
+ b := release.HistoryBinding{Host: in.Host, Account: in.Account, App: in.App}
+ if err := h.RecordCreation(b, rootHistoryInstallation(in)); err != nil {
+ t.Fatal(err)
+ }
+ f.root.history = func(ctx context.Context, fn func(*release.RootHistory) error) error {
+ if mode == "history-failure" {
+ return os.ErrPermission
+ }
+ return fn(h)
+ }
+ observed := 0
+ f.root.installedProgram = func(context.Context, hostbox.RoleLaunchRequest, instancectl.ClaimUnitStarted, ClaimLaunchObserved) (string, error) {
+ observed++
+ if mode == "record-failure" {
+ return "", os.ErrPermission
+ }
+ return "actual/program", nil
+ }
+ if mode == "not-ready" {
+ f.kernel.observeErr = os.ErrPermission
+ }
+ _, err := f.root.Serve(t.Context(), f.peer, f.req)
+ if (err == nil) != (mode == "ready") {
+ t.Fatalf("launch: %v", err)
+ }
+ programs, e := h.Programs(b)
+ if e != nil {
+ t.Fatal(e)
+ }
+ if mode == "ready" {
+ if len(programs) != 1 || programs[0] != "actual/program" {
+ t.Fatalf("programs: %v", programs)
+ }
+ } else if len(programs) != 0 {
+ t.Fatal("unobserved program recorded", programs)
+ }
+ fresh, e := h.FreshInstallation(b, rootHistoryInstallation(in))
+ if e != nil || fresh != (mode == "history-failure") {
+ t.Fatalf("fresh after attempt: %v %v", fresh, e)
+ }
+ if mode == "not-ready" && observed != 0 {
+ t.Fatal("recorded unready launch")
+ }
+ })
+ }
+}
+
+func TestPlan14InstalledLaunchProgramEvidence(t *testing.T) {
+ path, err := os.Executable()
+ if err != nil {
+ t.Fatal(err)
+ }
+ file, err := os.Open(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ id, err := rootstate.Identity(file)
+ if err != nil {
+ t.Fatal(err)
+ }
+ raw, err := io.ReadAll(file)
+ file.Close()
+ if err != nil {
+ t.Fatal(err)
+ }
+ digest := sha256.Sum256(raw)
+ for _, mode := range []string{"observed", "staged-only", "digest", "inode", "process", "unit", "authorization"} {
+ t.Run(mode, func(t *testing.T) {
+ f := newLaunchFixture(t, servingOp())
+ f.req.Executable = path
+ f.req.Spec.Digest = hex.EncodeToString(digest[:])
+ f.req.Reservation.Executable = hostbox.LaunchFile{Device: id.Device, Inode: id.Inode}
+ seen := f.kernel.observed
+ seen.Executable = f.req.Reservation.Executable
+ seen.Running = seen.Executable
+ f.kernel.observed = seen
+ switch mode {
+ case "staged-only":
+ seen.TenantPID = 0
+ f.kernel.observed = seen
+ case "digest":
+ f.req.Spec.Digest = strings.Repeat("0", 64)
+ case "inode":
+ f.req.Reservation.Executable.Inode++
+ case "process":
+ f.kernel.observed.TenantStart = "successor"
+ case "unit":
+ f.units.sameErr = os.ErrPermission
+ case "authorization":
+ f.root.ValidateExecutable = func(string, string, string) error { return os.ErrPermission }
+ }
+ program, err := f.root.rootInstalledProgram(t.Context(), f.req, f.units.started, seen)
+ if mode == "observed" {
+ if err != nil || program != release.FileProgram(path) {
+ t.Fatalf("program=%q: %v", program, err)
+ }
+ } else if err == nil {
+ t.Fatal("accepted", mode)
+ }
+ })
+ }
+}
+
+func TestPlan14FreshInstallCanonicalIdentity(t *testing.T) {
+ for _, mode := range []string{"canonical", "account", "app", "host", "record", "census", "inventory", "service-files"} {
+ t.Run(mode, func(t *testing.T) {
+ f := newRestartConsumptionFixture(t)
+ rt, err := f.e.root.reg.Get(f.claim.Host)
+ if err != nil {
+ t.Fatal(err)
+ }
+ id, err := f.p.source.creation(t.Context(), *rt)
+ if err != nil || id == "" {
+ t.Fatal(id, err)
+ }
+ switch mode {
+ case "account":
+ rt.Account = "bap"
+ case "app":
+ rt.App = "other"
+ case "host":
+ rt.Host = "other.example"
+ case "record":
+ rec, err := provision.GetRecord(t.Context(), f.e.store, rt.Account, rt.App, rt.Host)
+ if err != nil {
+ t.Fatal(err)
+ }
+ rec.CreatedAt = rec.CreatedAt.Add(time.Second)
+ plan14PutRecord(t, f.e.store, *rec)
+ case "census":
+ f.p.source.census = func() (rootparticipation.Census, error) { return rootparticipation.Census{}, os.ErrPermission }
+ case "inventory":
+ if err := f.p.source.state.Remove(rt.Host + ".json"); err != nil {
+ t.Fatal(err)
+ }
+ case "service-files":
+ if err := f.e.root.reg.Delete(rt.Host); err != nil {
+ t.Fatal(err)
+ }
+ rt.Caps = "host-role"
+ rt.GoodVersion = "invented"
+ rt.Unit = "forged.service"
+ }
+ got, err := f.p.source.creation(t.Context(), *rt)
+ if mode == "canonical" || mode == "service-files" {
+ if err != nil || got != id {
+ t.Fatalf("canonical identity changed: %q %v", got, err)
+ }
+ } else if err == nil {
+ t.Fatal("accepted", mode)
+ }
+ })
+ }
+}
diff --git a/internal/host/roothistory.go b/internal/host/roothistory.go
index 420c29bcb..8e889a821 100644
--- a/internal/host/roothistory.go
+++ b/internal/host/roothistory.go
@@ -22,6 +22,7 @@ func openRootHistory(store release.Store, optDir string) (*release.RootHistoryPa
}
source := productionProofBindingSource(store, systemdUnits{}, optDir)
h := boundRootHistory(&release.RootHistory{Store: s}, source.resolve, productionProofObserver(optDir))
+ h.Creation = rootHistoryCreation(store)
m := rootHistoryMaintenance{history: h.History, store: store, source: source,
checks: productionRootRestartChecks(systemdUnits{}, optDir), now: time.Now,
readback: func(ctx context.Context, b release.HistoryBinding, snapshot func(context.Context) (release.HistoryMigrationStatus, error)) error {
diff --git a/internal/host/roothistory_creation.go b/internal/host/roothistory_creation.go
new file mode 100644
index 000000000..4ead4a102
--- /dev/null
+++ b/internal/host/roothistory_creation.go
@@ -0,0 +1,84 @@
+package host
+
+import (
+ "context"
+ "crypto/sha256"
+ "encoding/hex"
+ "encoding/json"
+ "os"
+
+ "github.com/carlosframework/platform/internal/hostclaim"
+ "github.com/carlosframework/platform/internal/hostguard"
+ "github.com/carlosframework/platform/internal/provision"
+ "github.com/carlosframework/platform/internal/registry"
+ "github.com/carlosframework/platform/internal/release"
+ "github.com/carlosframework/platform/internal/rootparticipation"
+ "github.com/carlosframework/platform/internal/rootstate"
+)
+
+type rootHistoryAccess func(context.Context, func(*release.RootHistory) error) error
+
+func productionCreationHistory(ctx context.Context, effect func(*release.RootHistory) error) error {
+ legacy, err := legacyRootRestart()
+ if err != nil {
+ return err
+ }
+ if legacy {
+ return os.ErrPermission
+ }
+ return withClaimRootRelease(ctx, true, func() (rootstate.Store, error) {
+ return rootstate.Open(rootReleasePrivate, true, false)
+ }, func() error {
+ s, err := rootstate.Open(release.RootHistoryDir, true, false)
+ if err != nil {
+ return err
+ }
+ defer s.Close()
+ return effect(&release.RootHistory{Store: s})
+ })
+}
+
+// Inventory additions do not change the identity of the installation.
+func rootHistoryInstallation(in hostguard.RootInstallation) string {
+ in.Empty = false
+ in.InventoryRevision = 0
+ in.Registration = hostguard.InstallationRegistration{}
+ raw, _ := json.Marshal(in)
+ sum := sha256.Sum256(raw)
+ return hex.EncodeToString(sum[:])
+}
+
+func rootHistoryCreation(store release.Store) func(context.Context, registry.Route) (string, error) {
+ return func(ctx context.Context, rt registry.Route) (string, error) {
+ census, err := rootparticipation.Open()
+ if err != nil {
+ return "", err
+ }
+ defer census.State.Close()
+ state, err := rootstate.Open("/var/lib/carlos-claims/authority/fences", false, false)
+ if err != nil {
+ return "", err
+ }
+ defer state.Close()
+ return (rootRestartRequestSource{state: state, store: store, census: census.Census}).creation(ctx, rt)
+ }
+}
+
+func (s rootRestartRequestSource) creation(ctx context.Context, rt registry.Route) (string, error) {
+ in, err := s.installation(ctx, rt.Host)
+ if err != nil {
+ return "", err
+ }
+ if in.Host != rt.Host || in.Account != rt.Account || in.App != rt.App || in.Record != provision.RecordKey(in.Account, in.App, in.Host) {
+ return "", os.ErrPermission
+ }
+ rec, _, err := provision.GetRecordETag(ctx, s.store, in.Account, in.App, in.Host)
+ if err != nil {
+ return "", err
+ }
+ inc, err := hostclaim.Incarnation(rec.Account, rec.App, rec.Host, rec.CreatedAt)
+ if err != nil || inc != in.Incarnation {
+ return "", os.ErrPermission
+ }
+ return rootHistoryInstallation(in), nil
+}
diff --git a/internal/host/roothistory_launch.go b/internal/host/roothistory_launch.go
new file mode 100644
index 000000000..a53d0892e
--- /dev/null
+++ b/internal/host/roothistory_launch.go
@@ -0,0 +1,55 @@
+package host
+
+import (
+ "context"
+ "crypto/sha256"
+ "debug/buildinfo"
+ "encoding/hex"
+ "io"
+ "os"
+
+ "github.com/carlosframework/platform/internal/hostbox"
+ "github.com/carlosframework/platform/internal/instancectl"
+ "github.com/carlosframework/platform/internal/rootstate"
+ "github.com/carlosframework/platform/internal/safefile"
+)
+
+func (h *ClaimLaunchRoot) rootInstalledProgram(ctx context.Context, q hostbox.RoleLaunchRequest, started instancectl.ClaimUnitStarted, observed ClaimLaunchObserved) (string, error) {
+ if q.Reservation == nil || q.Spec == nil || observed.TenantPID <= 0 || observed.TenantStart == "" || observed.Running != q.Reservation.Executable || observed.Executable != observed.Running {
+ return "", os.ErrPermission
+ }
+ if err := h.ValidateExecutable(q.Reservation.Target.Host, q.Executable, q.Spec.Digest); err != nil {
+ return "", err
+ }
+ f, err := safefile.OpenFile(q.Executable, os.O_RDONLY, 0)
+ if err != nil {
+ return "", err
+ }
+ defer f.Close()
+ id, err := rootstate.Identity(f)
+ if err != nil {
+ return "", err
+ }
+ if id.Device != observed.Running.Device || id.Inode != observed.Running.Inode {
+ return "", os.ErrPermission
+ }
+ digest := sha256.New()
+ if _, err := io.Copy(digest, f); err != nil {
+ return "", err
+ }
+ if hex.EncodeToString(digest.Sum(nil)) != q.Spec.Digest {
+ return "", os.ErrPermission
+ }
+ program := ""
+ if info, err := buildinfo.Read(f); err == nil {
+ program = info.Path
+ }
+ again, err := h.Kernel.Observe(started)
+ if err != nil || again != observed {
+ return "", os.ErrPermission
+ }
+ if err := h.Units.Same(ctx, started); err != nil {
+ return "", err
+ }
+ return program, nil
+}
diff --git a/internal/host/rootinventory.go b/internal/host/rootinventory.go
index dc06ae586..c9c06951b 100644
--- a/internal/host/rootinventory.go
+++ b/internal/host/rootinventory.go
@@ -18,14 +18,28 @@ import (
"github.com/carlosframework/platform/internal/hostclaim"
"github.com/carlosframework/platform/internal/hostguard"
"github.com/carlosframework/platform/internal/registry"
+ "github.com/carlosframework/platform/internal/release"
"github.com/carlosframework/platform/internal/rootstate"
"github.com/carlosframework/platform/internal/safefile"
)
func (h ClaimRootHTTP) commitRootInventory(ctx context.Context, q hostbox.JournalRequest) (hostbox.JournalResponse, error) {
- return h.commitRootInventoryPending(ctx, q, false)
+ return h.withRootInventoryHistory(ctx, q, false)
}
-func (h ClaimRootHTTP) commitRootInventoryPending(ctx context.Context, q hostbox.JournalRequest, pending bool) (hostbox.JournalResponse, error) {
+func (h ClaimRootHTTP) withRootInventoryHistory(ctx context.Context, q hostbox.JournalRequest, pending bool) (hostbox.JournalResponse, error) {
+ if h.history == nil {
+ return h.commitRootInventoryPending(ctx, q, pending, nil)
+ }
+ var out hostbox.JournalResponse
+ err := h.history(ctx, func(history *release.RootHistory) error {
+ var err error
+ out, err = h.commitRootInventoryPending(ctx, q, pending, history)
+ return err
+ })
+ return out, err
+}
+
+func (h ClaimRootHTTP) commitRootInventoryPending(ctx context.Context, q hostbox.JournalRequest, pending bool, history *release.RootHistory) (hostbox.JournalResponse, error) {
var out hostbox.JournalResponse
if !pending && (q.V == 2 || q.V == 3) {
now := time.Now()
@@ -252,7 +266,12 @@ func (h ClaimRootHTTP) commitRootInventoryPending(ctx context.Context, q hostbox
}
}
return nil
- }, func(hostguard.RootInstallation) error {
+ }, func(prior hostguard.RootInstallation) error {
+ if history != nil && q.Revision == 0 && prior.InventoryRevision == 0 && observed.Claim.Op.Kind == hostclaim.KindServing && holder.Session == 0 && holder.WriterBox == "" {
+ if err := history.RecordCreation(release.HistoryBinding{Host: in.Host, Account: holder.Account, App: holder.App}, rootHistoryInstallation(installation)); err != nil {
+ return err
+ }
+ }
if firstLanding && currentRow == nil {
if err := h.Reconcile.Registry.Upsert(canonical); err != nil {
return err
@@ -346,7 +365,7 @@ func (h ClaimRootHTTP) recoverRootInventory(ctx context.Context) error {
if err = json.Unmarshal(raw, &q); err != nil {
return err
}
- if _, err = h.commitRootInventoryPending(ctx, q, true); err != nil {
+ if _, err = h.withRootInventoryHistory(ctx, q, true); err != nil {
if errors.Is(err, hostbox.ErrChanged) || errors.Is(err, hostbox.ErrJournalUncertain) {
err = errors.Join(err, h.Stop.Coordinator.Guard.AbandonRootInventory(ctx, q.Target.Host, raw, func(c *hostclaim.Claim) bool {
fresh, readErr := hostclaim.Read(ctx, h.Stop.Coordinator.Evidence, q.Target.Host)
diff --git a/internal/provision/plan14_e_write_census_test.go b/internal/provision/plan14_e_write_census_test.go
index 53839474c..95b7c3819 100644
--- a/internal/provision/plan14_e_write_census_test.go
+++ b/internal/provision/plan14_e_write_census_test.go
@@ -2,6 +2,9 @@ package provision
// Track E overlays the reviewed Part 1 write census without changing its baseline.
var plan14EWriteCensusOverlay = []censusEntry{
+ {"internal/host/rootinventory.go", "ClaimRootHTTP.commitRootInventoryPending", "key-site", "key:RecordKey", "cbc0c1fa8be968c6dd18a42ff200fe5765f25d35c07ed3ece3b18ad4d619d64c"},
+ {"internal/host/rootinventory.go", "ClaimRootHTTP.recoverRootInventory", "key-site", "key:RecordKey", "a828684ad31a409d3e1fc306216d907681f514cb813302eb965a040e5084feda"},
+ {"internal/host/roothistory_creation.go", "rootRestartRequestSource.creation", "key-site", "key:RecordKey", "af79a66b746b0f42388010b38ea8130dca6a8d29c4ba5623bfc9c3cc40dd9601"},
{"internal/host/rootrestart_requests.go", "rootRestartRequests.consume", "key-site", "key:RecordKey", "0a9fbbe6f201077e276298b7e666e1ee2a345f1c420335290fec38a676b1ad84"},
{"internal/host/rootrestart_requests_source.go", "rootRestartRequestSource.inventory", "key-site", "key:ParseSidecarKey,key:RecordKey", "1ae4bd07fdbd2b0fccd29cc032b0de4a6c53bc52da7fcbc186a11f8b65b394f9"},
{"internal/host/rootrestart_requests_source.go", "rootRestartRequestSource.target", "key-site", "key:RecordKey", "301b62efbf0281f713cbed7fb15cd244b7d2c479882f4ad930c8d482b51177ff"},
diff --git a/internal/provision/testdata/plan14-root-reads-e.tsv b/internal/provision/testdata/plan14-root-reads-e.tsv
index bd9342e77..9261e1ab8 100644
--- a/internal/provision/testdata/plan14-root-reads-e.tsv
+++ b/internal/provision/testdata/plan14-root-reads-e.tsv
@@ -11,7 +11,7 @@ internal/host/proofroot.go proofRoot.mint authority B3,J1,J4,E1 E1 production us
internal/host/proofroot.go proofRoot.observeAndRecord authority B3,J1,J4,E1 E13: signed recovery allows unknown history but preserves usable refusals, including retained originals; verified later promotion clears only D1. Health recording is independent of program recording; minting requires a census. Fixtures: TestPlan14RecoveryUnknownHistory, TestPlan14RecoveryKnownRefusal, TestPlan14RecoveryOriginalRefusalWithUnknownHistory, TestPlan14RootHistoryVerifiedRetryBeforeAttempt, TestPlan14ProgramFailureStillRecordsHealth, TestPlan14RootHistoryMintCensus. file:Readlink@108,registry:Account@335,registry:App@360,registry:Host@151,registry:Host@549,registry:Host@714,whole:Route@186,whole:Route@345,whole:Route@370 6a3a5e5bd6c0163567e9d2604eb090affeb1058f64922474a81aaa1ca589b578
internal/host/recoveryrefusals.go rootRestartChecks.mintRefusals authority B3,E1 Exact signed recovery retirement binds namespace, complete outcome digest and a later root-verified success; pending originals and changed evidence remain restrictive. Independent settlement publishes status without controlling effects. Fixtures: TestPlan14HistoryExactRecoveryRetirement, TestPlan14HistoryRecoveryRetirementAuthority, TestPlan14HistoryRetirementVisibleSync, TestPlan14HistoryRetirementProcessCrash, TestPlan14RecoveryRetirementProductionCallback, TestPlan14IndependentRecoveryPublishesStatus. 8087848fae0c92cc94908a7565019249aac203dbb3efbc313fafdf4b1a55b49d
internal/host/roothistory.go boundRootHistory authority E1 Exact signed recovery retirement binds namespace, complete outcome digest and a later root-verified success; pending originals and changed evidence remain restrictive. Independent settlement publishes status without controlling effects. Fixtures: TestPlan14HistoryExactRecoveryRetirement, TestPlan14HistoryRecoveryRetirementAuthority, TestPlan14HistoryRetirementVisibleSync, TestPlan14HistoryRetirementProcessCrash, TestPlan14RecoveryRetirementProductionCallback, TestPlan14IndependentRecoveryPublishesStatus. file:Open@1061,file:Read@1379,registry:Account@613,registry:App@635,registry:Channel@456,registry:Channel@470,registry:Host@590,whole:Route@1469,whole:Route@377,whole:Route@472,whole:Route@592,whole:Route@615,whole:Route@637,whole:Route@846 fe46269101116374044658bbb87c1bd44317525dcadf227d4515b47845238be2
-internal/host/roothistory.go openRootHistory authority E1 Challenge-bound release-history readback authenticates the deployment request and active enrolled box key, rereads private history with original recovery restrictions, and rechecks roster/fence state. No status grants completeness or changes service effects. Fixtures: TestPlan14HistoryReadback, TestPlan14HistoryReadbackAuthority, TestPlan14HistoryReadbackReadRace, TestPlan14HistoryReadbackReenrollment, TestPlan14HistoryReadbackRequestRefusal, TestPlan14HistoryReadbackProduction, TestPlan14HistoryReadbackProcessCrash. file:Open@104,file:Open@937 92ba5735531ee5fc8b7f907030104a190462d31a7210a81508b01bfa2698fc98
+internal/host/roothistory.go openRootHistory authority E1 E16: root first-registration creation evidence under release exclusion; exact installation identity is required for first-program adoption and is consumed before an attempt. Installed-program history comes from the verified claim launch. Fixtures: TestPlan14FreshInstallRegistration, TestPlan14FreshInstallProgramGate, TestPlan14FreshInstallCanonicalIdentity, TestPlan14FreshInstallLaunchObservation, TestPlan14InstalledLaunchProgramEvidence, TestPlan14FreshInstallProcessCrash, TestPlan14FreshInstallVisibleSync, TestClaimControlUnitInstallers. file:Open@104,file:Open@978 a9cd1c2ff83ec5fe9eec63fd65ef4a9a6fe845e59a0ac98f98652b767837b3a3
internal/host/roothistory.go recordRootProgram authority E1 Exact signed recovery retirement binds namespace, complete outcome digest and a later root-verified success; pending originals and changed evidence remain restrictive. Independent settlement publishes status without controlling effects. Fixtures: TestPlan14HistoryExactRecoveryRetirement, TestPlan14HistoryRecoveryRetirementAuthority, TestPlan14HistoryRetirementVisibleSync, TestPlan14HistoryRetirementProcessCrash, TestPlan14RecoveryRetirementProductionCallback, TestPlan14IndependentRecoveryPublishesStatus. whole:Route@151,whole:Route@297,whole:Route@398 fbbc9451c39309090580486d75da6120b02e1ba054157159846bea057ae0c80f
internal/host/roothistory_recovery.go historyRestartAttempt authority B3,E1 Exact signed recovery retirement binds namespace, complete outcome digest and a later root-verified success; pending originals and changed evidence remain restrictive. Independent settlement publishes status without controlling effects. Fixtures: TestPlan14HistoryExactRecoveryRetirement, TestPlan14HistoryRecoveryRetirementAuthority, TestPlan14HistoryRetirementVisibleSync, TestPlan14HistoryRetirementProcessCrash, TestPlan14RecoveryRetirementProductionCallback, TestPlan14IndependentRecoveryPublishesStatus. registry:Host@187,registry:Host@361,whole:Route@189,whole:Route@363 1c2af7b90cb56053c1d58c10ccaa851c3aa25b0be9e595bf8e90a013ad0bae66
internal/host/roothistory_recovery.go linkRootHistory authority B3,E1 Exact signed recovery retirement binds namespace, complete outcome digest and a later root-verified success; pending originals and changed evidence remain restrictive. Independent settlement publishes status without controlling effects. Fixtures: TestPlan14HistoryExactRecoveryRetirement, TestPlan14HistoryRecoveryRetirementAuthority, TestPlan14HistoryRetirementVisibleSync, TestPlan14HistoryRetirementProcessCrash, TestPlan14RecoveryRetirementProductionCallback, TestPlan14IndependentRecoveryPublishesStatus. file:Prepare@121,registry:Account@374,registry:App@412,whole:Route@376,whole:Route@414 a60324796d2b25b3b130bb0439bbd9a415b679e6b51bc92a44f23f77e2d93e1e
@@ -28,7 +28,7 @@ internal/host/rootrollback.go settleRollbackAttempt authority B3,E1 Independent
internal/host/rootrollback_resume.go recoverRootRestarts authority B3 Recover every attempt independently, skip settled writes and remove leftover capabilities. file:Read@819 0eb3221e553183869f907660c74bae544d48da57f1e5592b92400377f5769a0e
internal/host/rootrollback_resume.go resumeRootRestart authority B3,J1,E1 Signed named-unit recovery is independent of release and health history. Root rechecks canonical source, unit, process, signed target and trusted live pointer; captures attempt authority before publication; verifies readiness and resumes without repeated effects. Fixtures: TestPlan14SignedActiveFixIgnoresHistory, TestPlan14SignedActiveFixAuthority, TestPlan14SignedFixSupersedesOlderAttempt, TestPlan14ActiveRecoveryProcessCrash, TestPlan14SignedRestartWithoutHealthHistory, TestPlan14CapturedRollbackWithoutHealthHistory, TestPlan14SignedRestartRequiresTrustedLiveLink. registry:Account@1419,registry:Account@2125,registry:App@1447,registry:App@2153,registry:Unit@1225,whole:Route@1227,whole:Route@1421,whole:Route@1449,whole:Route@2127,whole:Route@2155 f69e185f75b5758b439be86809c2a83dd300e2e1235879c4860b70cfb5cb39ba
internal/release/adopt.go PassFilter authority B3,E3,J1,E1 Signed named-unit recovery is independent of release and health history. Root rechecks canonical source, unit, process, signed target and trusted live pointer; captures attempt authority before publication; verifies readiness and resumes without repeated effects. Fixtures: TestPlan14SignedActiveFixIgnoresHistory, TestPlan14SignedActiveFixAuthority, TestPlan14SignedFixSupersedesOlderAttempt, TestPlan14ActiveRecoveryProcessCrash, TestPlan14SignedRestartWithoutHealthHistory, TestPlan14CapturedRollbackWithoutHealthHistory, TestPlan14SignedRestartRequiresTrustedLiveLink. E7 applies merge ruling 4: unbound no-census routes use the legacy pass and restart; ordinary candidates make no binding store call. Fixtures: TestR2UnguardedERecoveryUpdate, TestR2UnguardedERecoveryIncidentRecipe, TestPlan14ERecoveryBindingRefusalKeepsPromotedFix. registry:App@26180,registry:App@28637,registry:App@29129,registry:App@29366,registry:App@30773,registry:App@5975,registry:App@9000,registry:App@9141,registry:Hibernate@29012,registry:Host@1055,registry:Host@2746,registry:Host@2813,registry:Host@2909,registry:Host@29182,registry:Host@2944,registry:Host@30098,registry:Host@30826,registry:Host@690,registry:UpdateSettings@8878,whole:Route@1090,whole:Route@1251,whole:Route@2360,whole:Route@2384,whole:Route@2415,whole:Route@2472,whole:Route@2558,whole:Route@2580,whole:Route@26175,whole:Route@2680,whole:Route@28632,whole:Route@28890,whole:Route@28912,whole:Route@28959,whole:Route@28977,whole:Route@28987,whole:Route@29030,whole:Route@29051,whole:Route@29064,whole:Route@29124,whole:Route@29150,whole:Route@29224,whole:Route@29263,whole:Route@29279,whole:Route@29307,whole:Route@29361,whole:Route@29387,whole:Route@30140,whole:Route@30194,whole:Route@30210,whole:Route@30717,whole:Route@30768,whole:Route@30794,whole:Route@30868,whole:Route@30903,whole:Route@30919,whole:Route@3665,whole:Route@4312,whole:Route@579,whole:Route@5897,whole:Route@5919,whole:Route@5970,whole:Route@5996,whole:Route@617,whole:Route@675,whole:Route@793,whole:Route@817,whole:Route@8829,whole:Route@8943,whole:Route@8988,whole:Route@9136 49427f5f398cf7df2b0ac2f63bfacfee3d3c630a253b176493ad87e0f7a49f00
-internal/release/adopt.go adoptHost authority B3,E1,E2,E3,E4,J1 E13: signed recovery allows unknown history but preserves usable refusals, including retained originals; verified later promotion clears only D1. Health recording is independent of program recording; minting requires a census. Fixtures: TestPlan14RecoveryUnknownHistory, TestPlan14RecoveryKnownRefusal, TestPlan14RecoveryOriginalRefusalWithUnknownHistory, TestPlan14RootHistoryVerifiedRetryBeforeAttempt, TestPlan14ProgramFailureStillRecordsHealth, TestPlan14RootHistoryMintCensus. registry:App@10895,registry:App@11055,registry:App@12084,registry:App@12808,registry:App@14370,registry:App@19529,registry:App@25005,registry:App@3114,registry:App@32764,registry:App@6717,registry:App@8018,registry:App@8212,registry:Hibernate@33625,registry:Host@10775,registry:Host@10808,registry:Host@10908,registry:Host@11068,registry:Host@11170,registry:Host@11202,registry:Host@11267,registry:Host@11717,registry:Host@11850,registry:Host@12280,registry:Host@12406,registry:Host@12898,registry:Host@13674,registry:Host@14142,registry:Host@14356,registry:Host@14698,registry:Host@15288,registry:Host@19019,registry:Host@19127,registry:Host@19497,registry:Host@20317,registry:Host@20475,registry:Host@2075,registry:Host@20937,registry:Host@20989,registry:Host@21136,registry:Host@21223,registry:Host@22329,registry:Host@22573,registry:Host@2264,registry:Host@23233,registry:Host@2345,registry:Host@23618,registry:Host@23759,registry:Host@24973,registry:Host@25100,registry:Host@26442,registry:Host@27092,registry:Host@30325,registry:Host@30943,registry:Host@31268,registry:Host@3127,registry:Host@31342,registry:Host@31589,registry:Host@31740,registry:Host@32936,registry:Host@33103,registry:Host@33210,registry:Host@33438,registry:Host@33581,registry:Host@34326,registry:Host@34452,registry:Host@34540,registry:Host@4428,registry:Host@4542,registry:Host@5505,registry:Host@5610,registry:Host@6829,registry:Host@6877,registry:Host@6994,registry:Host@7026,registry:Host@7904,registry:Host@8072,registry:Host@8198,registry:Host@8317,registry:Host@8795,registry:Host@9182,registry:Host@9408,registry:Host@9504,registry:Host@9595,registry:Kind@552,registry:Unit@31488,registry:Unit@577,whole:Route@10032,whole:Route@10890,whole:Route@11050,whole:Route@12074,whole:Route@12798,whole:Route@14338,whole:Route@14903,whole:Route@15264,whole:Route@16487,whole:Route@16790,whole:Route@16946,whole:Route@19276,whole:Route@19420,whole:Route@19519,whole:Route@2176,whole:Route@22058,whole:Route@22457,whole:Route@24995,whole:Route@29896,whole:Route@3109,whole:Route@32912,whole:Route@3554,whole:Route@4306,whole:Route@6712 c62d48c2f006b81d24e2778b83482475d5f2c1fda1c86d0985ebd9c3cf390a61
+internal/release/adopt.go adoptHost authority B3,E1,E2,E3,E4,J1 E16: root first-registration creation evidence under release exclusion; exact installation identity is required for first-program adoption and is consumed before an attempt. Installed-program history comes from the verified claim launch. Fixtures: TestPlan14FreshInstallRegistration, TestPlan14FreshInstallProgramGate, TestPlan14FreshInstallCanonicalIdentity, TestPlan14FreshInstallLaunchObservation, TestPlan14InstalledLaunchProgramEvidence, TestPlan14FreshInstallProcessCrash, TestPlan14FreshInstallVisibleSync, TestClaimControlUnitInstallers. registry:App@10895,registry:App@11055,registry:App@12084,registry:App@13010,registry:App@14592,registry:App@19751,registry:App@25227,registry:App@3114,registry:App@32986,registry:App@6717,registry:App@8018,registry:App@8212,registry:Hibernate@33847,registry:Host@10775,registry:Host@10808,registry:Host@10908,registry:Host@11068,registry:Host@11170,registry:Host@11202,registry:Host@11267,registry:Host@11717,registry:Host@11850,registry:Host@12297,registry:Host@12406,registry:Host@12608,registry:Host@13100,registry:Host@13886,registry:Host@14364,registry:Host@14578,registry:Host@14920,registry:Host@15510,registry:Host@19241,registry:Host@19349,registry:Host@19719,registry:Host@20539,registry:Host@20697,registry:Host@2075,registry:Host@21159,registry:Host@21211,registry:Host@21358,registry:Host@21445,registry:Host@22551,registry:Host@2264,registry:Host@22795,registry:Host@2345,registry:Host@23455,registry:Host@23840,registry:Host@23981,registry:Host@25195,registry:Host@25322,registry:Host@26664,registry:Host@27314,registry:Host@30547,registry:Host@31165,registry:Host@3127,registry:Host@31490,registry:Host@31564,registry:Host@31811,registry:Host@31962,registry:Host@33158,registry:Host@33325,registry:Host@33432,registry:Host@33660,registry:Host@33803,registry:Host@34548,registry:Host@34674,registry:Host@34762,registry:Host@4428,registry:Host@4542,registry:Host@5505,registry:Host@5610,registry:Host@6829,registry:Host@6877,registry:Host@6994,registry:Host@7026,registry:Host@7904,registry:Host@8072,registry:Host@8198,registry:Host@8317,registry:Host@8795,registry:Host@9182,registry:Host@9408,registry:Host@9504,registry:Host@9595,registry:Kind@552,registry:Unit@31710,registry:Unit@577,whole:Route@10032,whole:Route@10890,whole:Route@11050,whole:Route@12074,whole:Route@13000,whole:Route@14560,whole:Route@15125,whole:Route@15486,whole:Route@16709,whole:Route@17012,whole:Route@17168,whole:Route@19498,whole:Route@19642,whole:Route@19741,whole:Route@2176,whole:Route@22280,whole:Route@22679,whole:Route@25217,whole:Route@30118,whole:Route@3109,whole:Route@33134,whole:Route@3554,whole:Route@4306,whole:Route@6712 5a5aba93df8af6fa4cda8fc5db5fc7ef9c0ef9f05fd400555eb68b5b06679dd7
internal/release/rollback.go writeGroupRefusals authority E1 E1 group refusal writer persists root denial before its diagnostic projection; pending attempt remains restrictive. registry:App@801,whole:Route@783,whole:Route@882,whole:Route@888,whole:Route@952 301386c9d57f9f13db3c31a6cf3a26a44c7ca53ff8d98d910c10a7e8530b8795
internal/release/rootbake.go RootHistory.LoadBake authority E1 E1 strict root bake and app completeness revisions; backward clocks refuse and visible records are durably recommitted. file:Read@261 bd188ea6f2e76d277e1274043bf0f2a447391b1dc540aa400b36ad2573ccd84e
internal/release/roothistory.go RootHistory.read authority E1 Root per-binding refusal/program completeness and pending attempt state; strict decode and exact durable recommit before authority reads. E1 production integration is selected but incomplete. 4af99259d080d2b4c4ffee7cf2bc324e665cb07416031a495692421bd21878b4
@@ -39,12 +39,12 @@ internal/release/roothistory_pass.go RootHistoryPass.CompleteAttempt authority B
internal/release/roothistory_pass.go RootHistoryPass.begin authority E1 Root history intent is linked to captured canonical rollback authority before live effects; verified recovery settles only its exact intent. Fixtures: TestPlan14HistoryRollbackLink, TestPlan14HistoryRecoveryRefusesChanges, TestPlan14HistoryRecoveryProcessCrash, TestPlan14HistorySettlementRetriesVisibleSync. E1 migration and restart consumption remain incomplete. file:Prepare@607,file:Prepare@638,file:Read@292,whole:Route@218 dde605d1d382309a8546c7fb4a78e276909bc7741a83b93c93d0f2ec9c783004
internal/release/roothistory_pass.go RootHistoryPass.complete authority E1 E1 canonical binding selection and recheck; durable attempt precedes effects; only independent installed observation settles and retires the exact target refusal. whole:Route@205,whole:Route@278,whole:Route@350 10a2d72856ad28919399878857ee8d40e60877b86f76ba12d6f98d456001ccd2
internal/release/roothistory_pass.go RootHistoryPass.recheck authority E1 E1 canonical binding selection and recheck; durable attempt precedes effects; only independent installed observation settles and retires the exact target refusal. whole:Route@133 b335ffd791175d39f69451d7f55b9fb59674cacd48ff6221ad3edf9719208038
-internal/release/roothistory_pass.go RootHistoryPass.resolve authority E1 E1 canonical binding selection and recheck; durable attempt precedes effects; only independent installed observation settles and retires the exact target refusal. registry:App@394,registry:Host@341,whole:Route@240,whole:Route@378 7a0d3ca9f00fedcb219693d9ee98a68d03ae67e83b0efd42a943eccebadc7105
+internal/release/roothistory_pass.go RootHistoryPass.resolve authority E1 E16: root first-registration creation evidence under release exclusion; exact installation identity is required for first-program adoption and is consumed before an attempt. Installed-program history comes from the verified claim launch. Fixtures: TestPlan14FreshInstallRegistration, TestPlan14FreshInstallProgramGate, TestPlan14FreshInstallCanonicalIdentity, TestPlan14FreshInstallLaunchObservation, TestPlan14InstalledLaunchProgramEvidence, TestPlan14FreshInstallProcessCrash, TestPlan14FreshInstallVisibleSync, TestClaimControlUnitInstallers. registry:App@394,registry:Host@341,whole:Route@240,whole:Route@378,whole:Route@526 5fa39e1cad81603db8562b5f9a07f992085529b2a10ac13d6a76cc0a92e1766c
internal/release/roothistory_pass.go RootHistoryPass.selectRoute authority E1 E1 canonical binding selection and recheck; durable attempt precedes effects; only independent installed observation settles and retires the exact target refusal. whole:Route@134,whole:Route@370 83fabcbe7eea1c210666501b217ede898c0f64d22ff88db92b6c0a5790736dcd
internal/release/roothistory_pass.go passConfig.bindProof authority E1 E1 canonical binding selection and recheck; durable attempt precedes effects; only independent installed observation settles and retires the exact target refusal. whole:Route@214,whole:Route@277,whole:Route@353 395b72a300204af57355f0d6d037438b1210798f17c26efa9cd3dbef1f098806
internal/release/roothistory_pass.go passConfig.recheckHistory authority E1 E1 canonical binding selection and recheck; durable attempt precedes effects; only independent installed observation settles and retires the exact target refusal. registry:Host@184,whole:Route@162 2a7c830d3889757864bf69ddc17a9ee1cff2151dd27c90e69c9b6e4f85854332
internal/release/roothistory_pass.go passConfig.recordRefusal authority E1 E1 canonical binding selection and recheck; durable attempt precedes effects; only independent installed observation settles and retires the exact target refusal. registry:Host@151,whole:Route@241 298ca88f7dc02be7779aaa527990b4e4548c1fe5498fba0272a6e7041b236354
-internal/release/roothistory_pass.go type RootHistoryPass authority B3,E1 Root history intent is linked to captured canonical rollback authority before live effects; verified recovery settles only its exact intent. Fixtures: TestPlan14HistoryRollbackLink, TestPlan14HistoryRecoveryRefusesChanges, TestPlan14HistoryRecoveryProcessCrash, TestPlan14HistorySettlementRetriesVisibleSync. E1 migration and restart consumption remain incomplete. file:Prepare@215 c90889be3f234547b634149feceb82515c03de58598912758152f39604f23274
+internal/release/roothistory_pass.go type RootHistoryPass authority B3,E1 E16: root first-registration creation evidence under release exclusion; exact installation identity is required for first-program adoption and is consumed before an attempt. Installed-program history comes from the verified claim launch. Fixtures: TestPlan14FreshInstallRegistration, TestPlan14FreshInstallProgramGate, TestPlan14FreshInstallCanonicalIdentity, TestPlan14FreshInstallLaunchObservation, TestPlan14InstalledLaunchProgramEvidence, TestPlan14FreshInstallProcessCrash, TestPlan14FreshInstallVisibleSync, TestClaimControlUnitInstallers. file:Prepare@282 6bc3106de363145e70f591b2938a4546d7b1afaf51b0e6ab2cca35ea6bb5caef
internal/release/roothistory_reconstruct.go RootHistory.Reconstruct authority E1 Signed replacement binds the exact pending digest and current sources/revisions; old intended refusals and hold bounds remain restrictive. Fixtures: TestPlan14ReconstructionReplacement, TestPlan14ReconstructionReplacementCrash. f5deeb978cef7024adf551c9ac6ef2fce4de88b2d5392affdf3313bc82df0d16
internal/release/roothistory_reconstruct.go RootHistory.ReconstructionStatus authority E1 Exact completion binds the accepted authorization digest from durable private history to enrolled-root signed readback. Pending originals, changed evidence and canonical sources cannot supply retirement completion. Fixtures: TestPlan14HistoryReconstructionCompletion, TestPlan14HistoryRetirementCompletion, TestPlan14ReadReconstructionCompletion, TestPlan14ReadRetirementCompletion, TestPlan14RecoveryRetirementProductionCallback, TestPlan14RootHistoryReconstructionCrash, TestPlan14HistoryRetirementProcessCrash. file:Read@189 611a42b0f940b753e22a99266e6aae3384173233f717f3bdef0f38c80dcb00d5
internal/release/roothistory_reconstruct.go RootHistory.reconstructionImages authority E1 E1 signed root-history reconstruction; exact namespace identity, revisions and canonical sources; journaled completion retains denies and stronger bounds. Tests: TestPlan14RootHistoryReconstruction, TestPlan14RootHistoryReconstructionCrash, TestPlan14RootHistoryReconstructionDeliveryPass. file:Read@1190,file:Read@226,file:Read@2946,file:Read@629 cb47b84dd9bbb94a253aca2fa2c95ceaa75ddfa6453c78ece1c1b432e977e79a
@@ -54,8 +54,8 @@ internal/release/roothistory_replace.go replacementReconstruction authority E1 S
internal/release/signedhead.go StageSignedHead authority E1 Canonical route is supplied by the root unit verifier. Verifies signed channel, digest ratchet and manifest/artifact before staging; unavailable pin census never authorizes restaging differing bytes. Fixtures: TestPlan14Incident20261006WithLedger and TestPlan14SignedNamedRecoveryAuthority. registry:Account@202,registry:Account@302,registry:Account@403,registry:App@214,registry:App@415,registry:Channel@222,registry:Host@434 ca02f04e55556ba0233dfa0848b0c533a9d9aa06cad6abd33cfd2968b3bd838a
internal/rootstate/link_linux.go ReadTrustedLink authority A,E1 Checks the full root-owned parent chain before descriptor-based live-link reading. Fixture: TestPlan14TrustedLiveLink; privileged acceptance remains required. file:Open@156 90f8e8de4e2c71f07b5d749cbe4bb1c1dfa426576a82138823449f56fc9ec5ab
internal/rootstate/link_linux.go directory.readLink authority A,E1 Pins the symlink inode with O_PATH and checks owner/type/link count before readlinkat. Fixture: TestPlan14TrustedLiveLink. file:Fstat@356,file:Openat@183,file:Readlinkat@554 a8559fe63dce48830916004ecfe7fbbe5d419568c7daf7a3073cf1b5bb12c3d7
-@coverage internal/host b2bc360b179563924a103a527049fe2bfd013f3979f43d5585cf281059a0ce39
-@coverage internal/release 41c455979764d29fd7801abfa4700e0d941341a954e37bbe1de00665ae081ec2
+@coverage internal/host 602c4df9eb0b2143825502d87198495d19f4a4bfcc378a925b7816d474507560
+@coverage internal/release a69c59eb1ff514d78b138292d8fe617887993c0be43bd9132394a95243ac8d3c
@coverage internal/rootstate 6338f74a6b2059cf77cf478bd6111b37b441eb9d65a309464c378359d4bdac05
internal/release/roothistory_status.go RootHistory.MigrationStatus authority E1 Exact completion binds the accepted authorization digest from durable private history to enrolled-root signed readback. Pending originals, changed evidence and canonical sources cannot supply retirement completion. Fixtures: TestPlan14HistoryReconstructionCompletion, TestPlan14HistoryRetirementCompletion, TestPlan14ReadReconstructionCompletion, TestPlan14ReadRetirementCompletion, TestPlan14RecoveryRetirementProductionCallback, TestPlan14RootHistoryReconstructionCrash, TestPlan14HistoryRetirementProcessCrash. file:Read@1062,file:Read@3891 f0ab7ad2d2d70f8c22ebfd575f5a1c6931f7eba30ccdba80ad43d0efffc88154
internal/host/legacyrestart.go restartEffectFailed authority B3 E8 merge ruling 5: optional identity failures preserve the same restart outcome; no second restart; real not-up takes main rollback. Fixtures: TestPlan14ERecoveryRestartOutcome, TestPlan14ERecoveryBindingRefusalKeepsPromotedFix, TestPlan14ERecoveryNotUpPrecedesBinding. b68bb65884a14fe0cf78a65f56ded4ea41584bba99af9195827d1ea191b340a9
@@ -162,3 +162,21 @@ internal/host/rootrestart_requests_source.go rootRestartRequestSource.inventory
internal/host/rootrestart_requests_source.go rootRestartRequestSource.target authority E1 E15: census-only root unit-restart consumption uses retained installations, canonical records, exact requests, invocation evidence and admission after durable intent under release exclusion. No-census effects use the original consumer. Fixtures: TestPlan14RootRestartProductionConsumption, TestPlan14RootRestartProductionHeldRollover, TestPlan14RootRestartProductionAdmissionAfterIntent, TestPlan14RootRestartProductionUnknown, TestPlan14RootRestartProductionSyncFailure, TestPlan14RootRestartProductionCrash, TestPlan14RootRestartRequestCensusGate. registry:Unit@1211,whole:Route@891 301b62efbf0281f713cbed7fb15cd244b7d2c479882f4ad930c8d482b51177ff
internal/host/rootrestart_requests_source.go type rootRestartRequestSource authority E1 E15: census-only root unit-restart consumption uses retained installations, canonical records, exact requests, invocation evidence and admission after durable intent under release exclusion. No-census effects use the original consumer. Fixtures: TestPlan14RootRestartProductionConsumption, TestPlan14RootRestartProductionHeldRollover, TestPlan14RootRestartProductionAdmissionAfterIntent, TestPlan14RootRestartProductionUnknown, TestPlan14RootRestartProductionSyncFailure, TestPlan14RootRestartProductionCrash, TestPlan14RootRestartRequestCensusGate. db7ff5c46b6db3fe88657fddf8b4e17324b896fce9b21042d7f0f63140c1d776
internal/release/rootrestart_history.go RootRestarts.Bindings authority E1 E15: census-only root unit-restart consumption uses retained installations, canonical records, exact requests, invocation evidence and admission after durable intent under release exclusion. No-census effects use the original consumer. Fixtures: TestPlan14RootRestartProductionConsumption, TestPlan14RootRestartProductionHeldRollover, TestPlan14RootRestartProductionAdmissionAfterIntent, TestPlan14RootRestartProductionUnknown, TestPlan14RootRestartProductionSyncFailure, TestPlan14RootRestartProductionCrash, TestPlan14RootRestartRequestCensusGate. file:Read@324 d22b7e6b5710ca0025639395101933bec5ce0bd38768ebcf44a0805f1f159b08
+internal/host/claimlaunch_production.go WithProductionClaimLaunch authority E1 E16: root first-registration creation evidence under release exclusion; exact installation identity is required for first-program adoption and is consumed before an attempt. Installed-program history comes from the verified claim launch. Fixtures: TestPlan14FreshInstallRegistration, TestPlan14FreshInstallProgramGate, TestPlan14FreshInstallCanonicalIdentity, TestPlan14FreshInstallLaunchObservation, TestPlan14InstalledLaunchProgramEvidence, TestPlan14FreshInstallProcessCrash, TestPlan14FreshInstallVisibleSync, TestClaimControlUnitInstallers. eddb5f39afb0a4a7e94f8063955159d105f38574e600f15fda8d39c6837183a0
+internal/host/claimlaunch_root.go ClaimLaunchRoot.start authority E1 E16: root first-registration creation evidence under release exclusion; exact installation identity is required for first-program adoption and is consumed before an attempt. Installed-program history comes from the verified claim launch. Fixtures: TestPlan14FreshInstallRegistration, TestPlan14FreshInstallProgramGate, TestPlan14FreshInstallCanonicalIdentity, TestPlan14FreshInstallLaunchObservation, TestPlan14InstalledLaunchProgramEvidence, TestPlan14FreshInstallProcessCrash, TestPlan14FreshInstallVisibleSync, TestClaimControlUnitInstallers. fe4bea8c5fff3656ca8e1c42eec41e0d2eedd3a4a3b2f00e1203740cb8ebc4f7
+internal/host/claimlaunch_root.go ClaimLaunchRoot.startRecorded authority E1 E16: root first-registration creation evidence under release exclusion; exact installation identity is required for first-program adoption and is consumed before an attempt. Installed-program history comes from the verified claim launch. Fixtures: TestPlan14FreshInstallRegistration, TestPlan14FreshInstallProgramGate, TestPlan14FreshInstallCanonicalIdentity, TestPlan14FreshInstallLaunchObservation, TestPlan14InstalledLaunchProgramEvidence, TestPlan14FreshInstallProcessCrash, TestPlan14FreshInstallVisibleSync, TestClaimControlUnitInstallers. file:Read@2599 1cdb9e6359ed4994f2a7ebd12390f84f3f85c944e2c003cc6911ceae93f201d9
+internal/host/claimlaunch_root.go type ClaimLaunchRoot authority E1 E16: root first-registration creation evidence under release exclusion; exact installation identity is required for first-program adoption and is consumed before an attempt. Installed-program history comes from the verified claim launch. Fixtures: TestPlan14FreshInstallRegistration, TestPlan14FreshInstallProgramGate, TestPlan14FreshInstallCanonicalIdentity, TestPlan14FreshInstallLaunchObservation, TestPlan14InstalledLaunchProgramEvidence, TestPlan14FreshInstallProcessCrash, TestPlan14FreshInstallVisibleSync, TestClaimControlUnitInstallers. b01a2842411ca0ef5d5b0d5b189dc6300e1ac8c62e9a5b907a1afe79ca2e66c7
+internal/host/claimretire_transport.go type ClaimRootHTTP authority E1 E16: root first-registration creation evidence under release exclusion; exact installation identity is required for first-program adoption and is consumed before an attempt. Installed-program history comes from the verified claim launch. Fixtures: TestPlan14FreshInstallRegistration, TestPlan14FreshInstallProgramGate, TestPlan14FreshInstallCanonicalIdentity, TestPlan14FreshInstallLaunchObservation, TestPlan14InstalledLaunchProgramEvidence, TestPlan14FreshInstallProcessCrash, TestPlan14FreshInstallVisibleSync, TestClaimControlUnitInstallers. e87bb168975b9a7c4334de1c1fc027c556d69597122a6134770e770c719a45be
+internal/host/claimunit.go claimUnitContent authority E1 E16: root first-registration creation evidence under release exclusion; exact installation identity is required for first-program adoption and is consumed before an attempt. Installed-program history comes from the verified claim launch. Fixtures: TestPlan14FreshInstallRegistration, TestPlan14FreshInstallProgramGate, TestPlan14FreshInstallCanonicalIdentity, TestPlan14FreshInstallLaunchObservation, TestPlan14InstalledLaunchProgramEvidence, TestPlan14FreshInstallProcessCrash, TestPlan14FreshInstallVisibleSync, TestClaimControlUnitInstallers. 38dcfeb754659895bc28b024bc3186c6b12edeb17d087546507fa1299eb8e331
+internal/host/roothistory_creation.go productionCreationHistory authority E1 E16: root first-registration creation evidence under release exclusion; exact installation identity is required for first-program adoption and is consumed before an attempt. Installed-program history comes from the verified claim launch. Fixtures: TestPlan14FreshInstallRegistration, TestPlan14FreshInstallProgramGate, TestPlan14FreshInstallCanonicalIdentity, TestPlan14FreshInstallLaunchObservation, TestPlan14InstalledLaunchProgramEvidence, TestPlan14FreshInstallProcessCrash, TestPlan14FreshInstallVisibleSync, TestClaimControlUnitInstallers. file:Open@295,file:Open@374 b4f4f82422cf0b1d874be7a0d9ee4a2032a4704b022bf73b8264097c2a53705c
+internal/host/roothistory_creation.go rootHistoryCreation authority E1 E16: root first-registration creation evidence under release exclusion; exact installation identity is required for first-program adoption and is consumed before an attempt. Installed-program history comes from the verified claim launch. Fixtures: TestPlan14FreshInstallRegistration, TestPlan14FreshInstallProgramGate, TestPlan14FreshInstallCanonicalIdentity, TestPlan14FreshInstallLaunchObservation, TestPlan14InstalledLaunchProgramEvidence, TestPlan14FreshInstallProcessCrash, TestPlan14FreshInstallVisibleSync, TestClaimControlUnitInstallers. file:Open@190,file:Open@300,whole:Route@535 49c28b2886b2ef03b818b2ad7d9a7f6fec65637f86e4d2f94e54394cac032bcc
+internal/host/roothistory_creation.go rootHistoryInstallation authority E1 E16: root first-registration creation evidence under release exclusion; exact installation identity is required for first-program adoption and is consumed before an attempt. Installed-program history comes from the verified claim launch. Fixtures: TestPlan14FreshInstallRegistration, TestPlan14FreshInstallProgramGate, TestPlan14FreshInstallCanonicalIdentity, TestPlan14FreshInstallLaunchObservation, TestPlan14InstalledLaunchProgramEvidence, TestPlan14FreshInstallProcessCrash, TestPlan14FreshInstallVisibleSync, TestClaimControlUnitInstallers. 0b2eb471c2bb877739f75172ef05b4fd2f4f3ea0a11b84c0b72b35461a6d98fb
+internal/host/roothistory_creation.go rootRestartRequestSource.creation authority E1 E16: root first-registration creation evidence under release exclusion; exact installation identity is required for first-program adoption and is consumed before an attempt. Installed-program history comes from the verified claim launch. Fixtures: TestPlan14FreshInstallRegistration, TestPlan14FreshInstallProgramGate, TestPlan14FreshInstallCanonicalIdentity, TestPlan14FreshInstallLaunchObservation, TestPlan14InstalledLaunchProgramEvidence, TestPlan14FreshInstallProcessCrash, TestPlan14FreshInstallVisibleSync, TestClaimControlUnitInstallers. registry:Account@219,registry:App@243,registry:Host@133,registry:Host@194 af79a66b746b0f42388010b38ea8130dca6a8d29c4ba5623bfc9c3cc40dd9601
+internal/host/roothistory_creation.go type rootHistoryAccess authority E1 E16: root first-registration creation evidence under release exclusion; exact installation identity is required for first-program adoption and is consumed before an attempt. Installed-program history comes from the verified claim launch. Fixtures: TestPlan14FreshInstallRegistration, TestPlan14FreshInstallProgramGate, TestPlan14FreshInstallCanonicalIdentity, TestPlan14FreshInstallLaunchObservation, TestPlan14InstalledLaunchProgramEvidence, TestPlan14FreshInstallProcessCrash, TestPlan14FreshInstallVisibleSync, TestClaimControlUnitInstallers. 9c97bcd7878d79022ed942d8e6c00e944a00e0fa27a1bc5c732b4f4c10f4e9dd
+internal/host/roothistory_launch.go ClaimLaunchRoot.rootInstalledProgram authority E1 E16: root first-registration creation evidence under release exclusion; exact installation identity is required for first-program adoption and is consumed before an attempt. Installed-program history comes from the verified claim launch. Fixtures: TestPlan14FreshInstallRegistration, TestPlan14FreshInstallProgramGate, TestPlan14FreshInstallCanonicalIdentity, TestPlan14FreshInstallLaunchObservation, TestPlan14InstalledLaunchProgramEvidence, TestPlan14FreshInstallProcessCrash, TestPlan14FreshInstallVisibleSync, TestClaimControlUnitInstallers. file:OpenFile@541,file:Read@1043 e8d530dd1b8f613dfbd41d34a1b08b19aa3f74e0522c8f41db768ccd4e9e80d2
+internal/host/rootinventory.go ClaimRootHTTP.commitRootInventoryPending authority C,E1 E16: root first-registration creation evidence under release exclusion; exact installation identity is required for first-program adoption and is consumed before an attempt. Installed-program history comes from the verified claim launch. Fixtures: TestPlan14FreshInstallRegistration, TestPlan14FreshInstallProgramGate, TestPlan14FreshInstallCanonicalIdentity, TestPlan14FreshInstallLaunchObservation, TestPlan14InstalledLaunchProgramEvidence, TestPlan14FreshInstallProcessCrash, TestPlan14FreshInstallVisibleSync, TestClaimControlUnitInstallers. file:Read@1383,file:Read@3414,registry:Account@4778,registry:Account@4793,registry:App@4814,registry:App@4825,registry:ClaimIncarnation@4284,registry:ClaimOwner@4334,registry:ClaimOwner@5317,registry:ClaimOwnership@4876,registry:ClaimOwnership@4898,registry:ClaimOwnership@5631,registry:ClaimOwnership@7632,registry:ClaimRowID@4498,registry:ClaimRowID@4575,registry:ClaimStamp@4312,registry:DBPath@4842,registry:DBPath@4856,registry:Host@4748,registry:Host@4760,whole:ClaimOwnership@4880,whole:ClaimOwnership@4908,whole:ClaimOwnership@5641,whole:ClaimOwnership@7643,whole:Route@3606,whole:Route@3989,whole:Route@4005,whole:Route@4060,whole:Route@4711,whole:Route@4724,whole:Route@4733,whole:Route@4971,whole:Route@5203,whole:Route@5879,whole:Route@6015,whole:Route@6239,whole:Route@7476,whole:Route@8883,whole:Route@8944 cbc0c1fa8be968c6dd18a42ff200fe5765f25d35c07ed3ece3b18ad4d619d64c
+internal/release/roothistory_creation.go RootHistory.ConsumeCreation authority E1 E16: root first-registration creation evidence under release exclusion; exact installation identity is required for first-program adoption and is consumed before an attempt. Installed-program history comes from the verified claim launch. Fixtures: TestPlan14FreshInstallRegistration, TestPlan14FreshInstallProgramGate, TestPlan14FreshInstallCanonicalIdentity, TestPlan14FreshInstallLaunchObservation, TestPlan14InstalledLaunchProgramEvidence, TestPlan14FreshInstallProcessCrash, TestPlan14FreshInstallVisibleSync, TestClaimControlUnitInstallers. 7ec65199b5afc894ae4aefded6a40f485086d0f8a4c86391eca193f473dca2a1
+internal/release/roothistory_creation.go RootHistory.FreshInstallation authority E1 E16: root first-registration creation evidence under release exclusion; exact installation identity is required for first-program adoption and is consumed before an attempt. Installed-program history comes from the verified claim launch. Fixtures: TestPlan14FreshInstallRegistration, TestPlan14FreshInstallProgramGate, TestPlan14FreshInstallCanonicalIdentity, TestPlan14FreshInstallLaunchObservation, TestPlan14InstalledLaunchProgramEvidence, TestPlan14FreshInstallProcessCrash, TestPlan14FreshInstallVisibleSync, TestClaimControlUnitInstallers. 71b326a36350139c9c592b0b5b4b91409831fcffdd0c61d68cf5ec2bf54bbd5b
+internal/release/roothistory_creation.go RootHistory.RecordCreation authority E1 E16: root first-registration creation evidence under release exclusion; exact installation identity is required for first-program adoption and is consumed before an attempt. Installed-program history comes from the verified claim launch. Fixtures: TestPlan14FreshInstallRegistration, TestPlan14FreshInstallProgramGate, TestPlan14FreshInstallCanonicalIdentity, TestPlan14FreshInstallLaunchObservation, TestPlan14InstalledLaunchProgramEvidence, TestPlan14FreshInstallProcessCrash, TestPlan14FreshInstallVisibleSync, TestClaimControlUnitInstallers. 0b545ab63902eb8f2c8988991786c964a2af53d65eff46d8e53a0747da492b6d
+internal/host/rootinventory.go ClaimRootHTTP.recoverRootInventory authority C,E1 E16: root first-registration creation evidence under release exclusion; exact installation identity is required for first-program adoption and is consumed before an attempt. Installed-program history comes from the verified claim launch. Fixtures: TestPlan14FreshInstallRegistration, TestPlan14FreshInstallProgramGate, TestPlan14FreshInstallCanonicalIdentity, TestPlan14FreshInstallLaunchObservation, TestPlan14InstalledLaunchProgramEvidence, TestPlan14FreshInstallProcessCrash, TestPlan14FreshInstallVisibleSync, TestClaimControlUnitInstallers. file:Read@1371,file:Read@707 a828684ad31a409d3e1fc306216d907681f514cb813302eb965a040e5084feda
+internal/host/rootinventory.go ClaimRootHTTP.withRootInventoryHistory authority E1 E16: root first-registration creation evidence under release exclusion; exact installation identity is required for first-program adoption and is consumed before an attempt. Installed-program history comes from the verified claim launch. Fixtures: TestPlan14FreshInstallRegistration, TestPlan14FreshInstallProgramGate, TestPlan14FreshInstallCanonicalIdentity, TestPlan14FreshInstallLaunchObservation, TestPlan14InstalledLaunchProgramEvidence, TestPlan14FreshInstallProcessCrash, TestPlan14FreshInstallVisibleSync, TestClaimControlUnitInstallers. 81bc65e0a2018fcdaa8933231d44579429801c2265e6c87c274e9fd8e096c202
diff --git a/internal/release/adopt.go b/internal/release/adopt.go
index d2307d834..5f167e8f0 100644
--- a/internal/release/adopt.go
+++ b/internal/release/adopt.go
@@ -1505,12 +1505,19 @@ func adoptHost(ctx context.Context, work hostwrite.Work, reg *registry.Registry,
newProg := FileProgram(VersionPath(optDir, RouteAccount(rt), ver, rt.App))
if (cfg.bakeDir != "" || cfg.requireRootHistory) && newProg != "" {
var hist []string
+ fresh := false
if cfg.requireRootHistory {
hist, err = cfg.rootHistory.History.Programs(cfg.historySelected[rt.Host].binding)
if err != nil {
cfg.historyError(err)
return
}
+ sel := cfg.historySelected[rt.Host]
+ fresh, err = cfg.rootHistory.History.FreshInstallation(sel.binding, sel.creation)
+ if err != nil {
+ cfg.historyError(err)
+ return
+ }
} else {
hist = ReadPrograms(cfg.bakeDir, rt.Host)
}
@@ -1524,7 +1531,7 @@ func adoptHost(ctx context.Context, work hostwrite.Work, reg *registry.Registry,
hist = append(slices.Clone(hist), live)
}
}
- if (len(hist) > 0 || cfg.requireRootHistory) && !slices.Contains(hist, newProg) && m == nil {
+ if (len(hist) > 0 || cfg.requireRootHistory && !fresh) && !slices.Contains(hist, newProg) && m == nil {
// ensureStaged has the binary but could not obtain its manifest
// (no sibling, and the store did not answer), so whether the
// release carries --program-change is unknown. Unknown is not a
@@ -1540,7 +1547,7 @@ func adoptHost(ctx context.Context, work hostwrite.Work, reg *registry.Registry,
if cfg.requireRootHistory {
programChange = programChange && ptr.DigestOf(ver) != ""
}
- if (len(hist) > 0 || cfg.requireRootHistory) && !slices.Contains(hist, newProg) && !programChange {
+ if (len(hist) > 0 || cfg.requireRootHistory && !fresh) && !slices.Contains(hist, newProg) && !programChange {
fmt.Printf(" %s: %s is %s, but this route has only run %s — not pointed live (ship a new version with carlos ship --program-change and promote it, if the change is intended)\n",
rt.Host, ver, newProg, strings.Join(hist, ", "))
sha := m.Artifacts[adoptPlatform].SHA256
diff --git a/internal/release/plan14_fresh_install_test.go b/internal/release/plan14_fresh_install_test.go
new file mode 100644
index 000000000..2dbe60af0
--- /dev/null
+++ b/internal/release/plan14_fresh_install_test.go
@@ -0,0 +1,215 @@
+package release
+
+import (
+ "context"
+ "encoding/json"
+ "github.com/carlosframework/platform/internal/registry"
+ "os"
+ "path/filepath"
+ "testing"
+ "time"
+)
+
+func TestPlan14FreshInstallEvidence(t *testing.T) {
+ for _, state := range []string{"fresh", "empty", "observed", "refused", "attempted", "changed", "corrupt", "sync"} {
+ t.Run(state, func(t *testing.T) {
+ h, s, b := rootHistoryFixture(t)
+ if err := os.Remove(filepath.Join(s.dir, historyKey(b))); err != nil {
+ t.Fatal(err)
+ }
+ if state == "empty" {
+ if err := h.RecordInstalledProgram(b, ""); err != nil {
+ t.Fatal(err)
+ }
+ } else {
+ if err := h.RecordCreation(b, "installation"); err != nil {
+ t.Fatal(err)
+ }
+ }
+ switch state {
+ case "observed":
+ if err := h.RecordInstalledProgram(b, ""); err != nil {
+ t.Fatal(err)
+ }
+ case "refused":
+ if err := h.AddRefusal(b, Refusal{Host: b.Host, App: b.App, Version: "v1", RefusedAt: time.Now()}); err != nil {
+ t.Fatal(err)
+ }
+ case "attempted":
+ if err := h.Begin(b, HistoryAttempt{ID: "attempt", Version: "v1", Source: "record", Started: time.Now()}); err != nil {
+ t.Fatal(err)
+ }
+ case "corrupt":
+ if err := os.WriteFile(filepath.Join(s.dir, historyKey(b)), []byte("{"), 0600); err != nil {
+ t.Fatal(err)
+ }
+ case "sync":
+ s.fail = 1
+ }
+ identity := "installation"
+ if state == "changed" {
+ identity = "successor"
+ }
+ fresh, err := h.FreshInstallation(b, identity)
+ if state == "fresh" {
+ if err != nil || !fresh {
+ t.Fatalf("fresh=%v: %v", fresh, err)
+ }
+ } else if err == nil && fresh {
+ t.Fatal("invented fresh installation", state)
+ }
+ })
+ }
+}
+
+func TestPlan14CreationKeepsHistory(t *testing.T) {
+ h, _, b := rootHistoryFixture(t)
+ if err := h.RecordCreation(b, "installation"); err != nil {
+ t.Fatal(err)
+ }
+ p, err := h.Programs(b)
+ if err != nil || len(p) != 1 || p[0] != "old/program" {
+ t.Fatalf("lost history: %v %v", p, err)
+ }
+ if fresh, err := h.FreshInstallation(b, "installation"); err != nil || fresh {
+ t.Fatalf("old history became fresh: %v %v", fresh, err)
+ }
+}
+
+func TestPlan14FreshInstallProgramGate(t *testing.T) {
+ binary := tinyProgram(t, "first/program")
+ for _, mode := range []string{"fresh", "unproven", "changed", "consumed", "missing-provider"} {
+ t.Run(mode, func(t *testing.T) {
+ h, s, b := rootHistoryFixture(t)
+ if err := os.Remove(filepath.Join(s.dir, historyKey(b))); err != nil {
+ t.Fatal(err)
+ }
+ if mode == "unproven" {
+ if err := h.RecordInstalledProgram(b, ""); err != nil {
+ t.Fatal(err)
+ }
+ r, err := h.read(b)
+ if err != nil {
+ t.Fatal(err)
+ }
+ r.Refusals.Complete = true
+ if err = h.write(r); err != nil {
+ t.Fatal(err)
+ }
+ } else if err := h.RecordCreation(b, "installation"); err != nil {
+ t.Fatal(err)
+ }
+ if mode == "consumed" {
+ if err := h.ConsumeCreation(b); err != nil {
+ t.Fatal(err)
+ }
+ }
+ seedRootBake(t, h, b, time.Now().UTC().Add(-time.Hour))
+ st := NewDirStore(t.TempDir())
+ priv, pub := testKeys(t)
+ reg := testRegistry(t)
+ opt := t.TempDir()
+ addRoute(t, reg, b.Host, b.App, "canary/x", false)
+ shipRelease(t, st, b.Account, b.App, "first", adoptPlatform, binary)
+ if err := Promote(t.Context(), st, priv, b.Account, b.App, "canary/x", "first"); err != nil {
+ t.Fatal(err)
+ }
+ calls, effects := 0, 0
+ deps := &RootHistoryPass{History: h, Resolve: func(context.Context, registry.Route) (HistoryBinding, string, error) { return b, "record", nil }, Installed: func(context.Context, registry.Route, string) (string, error) { return "first/program", nil }, Creation: func(context.Context, registry.Route) (string, error) {
+ calls++
+ if mode == "changed" && calls >= 3 {
+ return "successor", nil
+ }
+ return "installation", nil
+ }}
+ if mode == "missing-provider" {
+ deps.Creation = nil
+ }
+ err := Pass(t.Context(), reg, st, pub, opt, func(string) error { effects++; return nil }, WithRootHistory(deps))
+ v, _ := LiveVersion(opt, b.Host)
+ if mode == "fresh" {
+ if err != nil || v != "first" || effects != 1 {
+ t.Fatalf("fresh install: %s %d %v", v, effects, err)
+ }
+ } else if v != "" || effects != 0 {
+ t.Fatalf("%s installed: %s %d %v", mode, v, effects, err)
+ }
+ })
+ }
+}
+
+func TestPlan14FreshInstallVisibleSync(t *testing.T) {
+ h, s, b := rootHistoryFixture(t)
+ if err := os.Remove(filepath.Join(s.dir, historyKey(b))); err != nil {
+ t.Fatal(err)
+ }
+ s.fail = 2
+ if err := h.RecordCreation(b, "installation"); err == nil {
+ t.Fatal("unsynced creation succeeded")
+ }
+ if fresh, err := h.FreshInstallation(b, "installation"); err == nil || fresh {
+ t.Fatal("visible unsynced creation granted")
+ }
+ if fresh, err := h.FreshInstallation(b, "installation"); err != nil || !fresh {
+ t.Fatalf("durability repair: %v %v", fresh, err)
+ }
+ s.fail = 3
+ if err := h.ConsumeCreation(b); err == nil {
+ t.Fatal("unsynced read allowed attempt")
+ }
+ if err := h.ConsumeCreation(b); err == nil {
+ t.Fatal("unsynced read allowed attempt")
+ }
+ if err := h.ConsumeCreation(b); err == nil {
+ t.Fatal("unsynced read allowed attempt")
+ }
+ if err := h.ConsumeCreation(b); err != nil {
+ t.Fatal(err)
+ }
+ if fresh, err := h.FreshInstallation(b, "installation"); err != nil || fresh {
+ t.Fatalf("attempt rearmed: %v %v", fresh, err)
+ }
+}
+
+type creationSyncFailure struct {
+ *healthyRetryStore
+ remaining int
+}
+
+func (s *creationSyncFailure) Write(name string, raw []byte) error {
+ var r historyRecord
+ if err := json.Unmarshal(raw, &r); err != nil {
+ return err
+ }
+ if !r.Fresh && s.remaining > 0 {
+ s.fail = 1
+ s.remaining--
+ }
+ return s.healthyRetryStore.Write(name, raw)
+}
+
+func TestPlan14FreshInstallConsumptionSync(t *testing.T) {
+ h, s, b := rootHistoryFixture(t)
+ if err := os.Remove(filepath.Join(s.dir, historyKey(b))); err != nil {
+ t.Fatal(err)
+ }
+ if err := h.RecordCreation(b, "installation"); err != nil {
+ t.Fatal(err)
+ }
+ h.Store = &creationSyncFailure{healthyRetryStore: s, remaining: 2}
+ if err := h.ConsumeCreation(b); err == nil {
+ t.Fatal("visible unsynced attempt permitted launch")
+ }
+ if fresh, err := h.FreshInstallation(b, "installation"); err == nil || fresh {
+ t.Fatal("unrepaired consumption read succeeded")
+ }
+ if fresh, err := h.FreshInstallation(b, "installation"); err != nil || fresh {
+ t.Fatalf("consumption not recovered: %v %v", fresh, err)
+ }
+ if err := h.RecordCreation(b, "successor"); err != nil {
+ t.Fatal(err)
+ }
+ if fresh, err := h.FreshInstallation(b, "successor"); err != nil || fresh {
+ t.Fatalf("replacement erased retained history: %v %v", fresh, err)
+ }
+}
diff --git a/internal/release/roothistory.go b/internal/release/roothistory.go
index ceef97f4e..e2dce29ef 100644
--- a/internal/release/roothistory.go
+++ b/internal/release/roothistory.go
@@ -57,6 +57,8 @@ type historyRefusals struct {
}
type historyRecord struct {
+ Creation string `json:"creation,omitempty"`
+ Fresh bool `json:"fresh,omitempty"`
Schema int `json:"schema"`
Binding HistoryBinding `json:"binding"`
Revision uint64 `json:"revision"`
@@ -81,6 +83,9 @@ func (b HistoryBinding) valid() bool {
}
func validHistory(r historyRecord, b HistoryBinding) bool {
+ if len(r.Creation) > 4096 || (r.Fresh && (r.Creation == "" || !r.Programs.Complete || !r.Refusals.Complete || len(r.Programs.Values) != 0 || len(r.Refusals.Values) != 0 || r.Attempt != nil)) {
+ return false
+ }
if !b.valid() || r.Schema != 1 || r.Binding != b || r.Revision == 0 ||
r.Programs.Revision == 0 || r.Programs.Revision > r.Revision || r.Refusals.Revision == 0 || r.Refusals.Revision > r.Revision ||
len(r.Programs.Values) > historyLimit || len(r.Refusals.Values) > historyLimit {
@@ -171,7 +176,7 @@ func (h *RootHistory) RecordInstalledProgram(b HistoryBinding, program string) e
} else if err != nil {
return err
}
- if r.Programs.Complete && (program == "" || slices.Contains(r.Programs.Values, program)) {
+ if !r.Fresh && r.Programs.Complete && (program == "" || slices.Contains(r.Programs.Values, program)) {
return nil
}
if r.Revision == ^uint64(0) {
@@ -181,6 +186,7 @@ func (h *RootHistory) RecordInstalledProgram(b HistoryBinding, program string) e
r.Programs.Values = append(r.Programs.Values, program)
}
r.Revision++
+ r.Fresh = false
r.Programs.Complete, r.Programs.Revision = true, r.Revision
return h.write(r)
}
@@ -229,6 +235,7 @@ func (h *RootHistory) AddRefusal(b HistoryBinding, v Refusal) error {
return os.ErrPermission
}
r.Revision++
+ r.Fresh = false
r.Refusals.Revision = r.Revision
next := r
next.Refusals.Values = append(slices.Clone(r.Refusals.Values), v)
@@ -272,6 +279,7 @@ func (h *RootHistory) Begin(b HistoryBinding, a HistoryAttempt) error {
return os.ErrPermission
}
r.Revision++
+ r.Fresh = false
r.Attempt = &a
return h.write(r)
}
diff --git a/internal/release/roothistory_creation.go b/internal/release/roothistory_creation.go
new file mode 100644
index 000000000..09143109c
--- /dev/null
+++ b/internal/release/roothistory_creation.go
@@ -0,0 +1,50 @@
+package release
+
+import (
+ "errors"
+ "os"
+)
+
+// RecordCreation is called only inside root's first-registration transaction,
+// after root proves the installation is new. Existing history is preserved.
+func (h *RootHistory) RecordCreation(b HistoryBinding, installation string) error {
+ if !b.valid() || installation == "" || len(installation) > 4096 {
+ return os.ErrPermission
+ }
+ _, err := h.read(b)
+ if err == nil {
+ return nil
+ }
+ if !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+ return h.write(historyRecord{Schema: 1, Binding: b, Revision: 1, Creation: installation, Fresh: true,
+ Programs: historyPrograms{Revision: 1, Complete: true}, Refusals: historyRefusals{Revision: 1, Complete: true}})
+}
+
+func (h *RootHistory) FreshInstallation(b HistoryBinding, installation string) (bool, error) {
+ r, err := h.read(b)
+ if err != nil {
+ return false, err
+ }
+ return installation != "" && r.Creation == installation && r.Fresh, nil
+}
+
+func (h *RootHistory) ConsumeCreation(b HistoryBinding) error {
+ r, err := h.read(b)
+ if errors.Is(err, os.ErrNotExist) {
+ return nil
+ }
+ if err != nil {
+ return err
+ }
+ if !r.Fresh {
+ return nil
+ }
+ if r.Revision == ^uint64(0) {
+ return os.ErrPermission
+ }
+ r.Revision++
+ r.Fresh = false
+ return h.write(r)
+}
diff --git a/internal/release/roothistory_pass.go b/internal/release/roothistory_pass.go
index 4af39c775..2fb592030 100644
--- a/internal/release/roothistory_pass.go
+++ b/internal/release/roothistory_pass.go
@@ -13,6 +13,7 @@ import (
)
type RootHistoryPass struct {
+ Creation func(context.Context, registry.Route) (string, error)
History *RootHistory
Resolve func(context.Context, registry.Route) (HistoryBinding, string, error)
Installed func(context.Context, registry.Route, string) (string, error)
@@ -21,8 +22,9 @@ type RootHistoryPass struct {
}
type historySelection struct {
- binding HistoryBinding
- source string
+ binding HistoryBinding
+ source string
+ creation string
}
func WithRecovery(fn func(context.Context, registry.Route) (bool, error), legacy ...func() (bool, error)) PassOption {
@@ -52,7 +54,14 @@ func (h *RootHistoryPass) resolve(ctx context.Context, rt registry.Route) (histo
if !b.valid() || source == "" || b.Host != rt.Host || b.Account != RouteAccount(rt) || b.App != rt.App {
return historySelection{}, os.ErrPermission
}
- return historySelection{b, source}, nil
+ creation := ""
+ if h.Creation != nil {
+ creation, err = h.Creation(ctx, rt)
+ if err != nil {
+ return historySelection{}, err
+ }
+ }
+ return historySelection{binding: b, source: source, creation: creation}, nil
}
func (h *RootHistoryPass) selectRoute(ctx context.Context, rt registry.Route) (historySelection, error) {