carlos / platform Public

Merge integrated Plan 14 (305866ad) into track E + WIP checkpoint 58 (not for merge)

Enabled console passes under containment, with refusal and mutation witnesses; censuses pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev 8d7aa3b3a4b9a600a460695941c7363c91fdceda parent 66bdb47parent 305866a
29 files changed, +1310 −125
  • internal/host/claimactivation_preflight.go +65 −0
  • internal/host/claimactivation_preflight_test.go +41 −0
  • internal/host/claimconstruction_rootrunner_linux_test.go +55 −2
  • internal/host/claimunit_binding.go +4 −0
  • internal/host/claimunit_console.go +28 −0
  • internal/host/claimunit_console_test.go +40 −0
  • internal/host/claimunit_definition.go +4 −0
  • internal/host/claimunit_definition_test.go +59 −0
  • internal/host/claimunit_effective_test.go +46 −37
  • internal/host/claimunit_materialize.go +5 −0
  • internal/host/plan14_enabled_console_root_linux_test.go +185 −0
  • internal/host/plan14_fixture_nat_root_linux_test.go +136 −0
  • internal/host/plan14_generation_registry_root_linux_test.go +6 −0
  • internal/host/plan14_installed_launch_root_linux_test.go +45 −0
  • internal/host/plan14_r10_root_linux_test.go +288 −0
  • internal/host/plan14_r9_root_linux_test.go +86 −16
  • internal/host/rootmigration_bootstrap_root_linux_test.go +58 −2
  • internal/host/rootmigration_production_linux.go +17 −1
  • internal/host/rootmigration_startup.go +1 −1
  • internal/host/rootmovesfloor_source.go +1 −1
  • internal/host/rootreleaselock.go +1 −19
  • internal/host/systemapply.go +1 −1
  • internal/host/systemparticipation.go +8 −1
  • internal/hostguard/generationinventory.go +1 −1
  • internal/hostguard/retired_inventory_test.go +60 −0
  • internal/hostguard/root.go +21 −2
  • internal/hostguard/rootmigration_inventory.go +1 −1
  • internal/provision/testdata/plan14-root-reads-e.tsv +19 −17
  • internal/provision/testdata/plan14-root-reads-p3.tsv +28 −23

CI failed — run details

CI log
=== gate ===
cd launchctx && go vet ./... && go test ./... -count=1
ok  	amadan.net/carlos/platform.git/launchctx	0.011s
?   	amadan.net/carlos/platform.git/launchctx/controlname	[no test files]
?   	amadan.net/carlos/platform.git/launchctx/wireid	[no test files]
go vet ./...
gofmt -l .: clean
go test ./... -count=1 -timeout 30m
ok  	github.com/carlosframework/platform	292.579s
?   	github.com/carlosframework/platform/cmd/changelog	[no test files]
?   	github.com/carlosframework/platform/cmd/console	[no test files]
ok  	github.com/carlosframework/platform/cmd/moveprobe	92.223s
ok  	github.com/carlosframework/platform/cmd/zddprobe	0.701s
ok  	github.com/carlosframework/platform/internal/activator	251.075s
ok  	github.com/carlosframework/platform/internal/agentsock	0.015s
ok  	github.com/carlosframework/platform/internal/alerting	0.003s
ok  	github.com/carlosframework/platform/internal/analytics	0.018s
ok  	github.com/carlosframework/platform/internal/appconfig	2.445s
ok  	github.com/carlosframework/platform/internal/apperr	0.071s
ok  	github.com/carlosframework/platform/internal/applog	0.129s
ok  	github.com/carlosframework/platform/internal/awsinv	0.003s
ok  	github.com/carlosframework/platform/internal/boxchoice	0.004s
ok  	github.com/carlosframework/platform/internal/boxid	0.960s
?   	github.com/carlosframework/platform/internal/boxid/boxidtest	[no test files]
?   	github.com/carlosframework/platform/internal/boxid/internal/signtest	[no test files]
ok  	github.com/carlosframework/platform/internal/boxvolumes	0.009s
ok  	github.com/carlosframework/platform/internal/caps	0.014s
ok  	github.com/carlosframework/platform/internal/certwarm	0.003s
ok  	github.com/carlosframework/platform/internal/cgroup	0.693s
ok  	github.com/carlosframework/platform/internal/changelog	0.302s
ok  	github.com/carlosframework/platform/internal/clamd	0.054s
?   	github.com/carlosframework/platform/internal/cliflag	[no test files]
ok  	github.com/carlosframework/platform/internal/console	389.765s
2026/10/16 07:15:23 console: email doors will refuse — SES provisioner unavailable (region eu-west-1): boom: no AWS credentials on this box
2026/10/16 07:15:23 console: platform email domains will emit records for the member instead of CARLOS publishing them — Route53 DNS writer unavailable: boom: route53 unreachable
2026/10/16 07:15:24 console: CONSOLE_GEOIP_DB /tmp/TestOpenGeoIPIsOptionalAndNeverFatal1719590348/001/missing.mmdb is unusable, so the IP step of region guesses is off: verifier: exit status 2
2026/10/16 07:15:24 console: GeoIP database loaded from ../../internal/geo/testdata/GeoIP2-City-Test.mmdb
2026/10/16 07:15:24 console: GeoIP database loaded from ../../internal/geo/testdata/GeoIP2-City-Test.mmdb
2026/10/16 07:15:24 console: CONSOLE_GEOIP_DB ../../internal/geo/testdata/GeoIP2-City-Test.mmdb expired (GeoLite2, 30 days after build) and was closed. replace it with a current edition, destroy the old file, and restart
2026/10/16 07:15:24 console: CONSOLE_GEOIP_DB ../../internal/geo/testdata/GeoIP2-City-Test.mmdb is a GeoLite2 database more than 30 days old; it was not opened. replace it with a current edition and destroy the old file
2026/10/16 07:15:24 console: CONSOLE_GEOIP_DB /tmp/TestConsoleGeoReaderNeverYieldsATypedNil751091515/001/missing.mmdb is unusable, so the IP step of region guesses is off: verifier: exit status 2
2026/10/16 07:15:24 console: CARLOS_HIBERNATE_IDLE "soon": want a positive duration like 15m or 90s; the carlos db door is off
--- FAIL: TestClaimEnabledCommandStartupWithInstanceFixture (0.08s)
    --- FAIL: TestClaimEnabledCommandStartupWithInstanceFixture/untrusted-roster (0.04s)
        claim_startup_test.go:115: startup: exit status 1
            hostops: required contract unavailable
    --- FAIL: TestClaimEnabledCommandStartupWithInstanceFixture/enabled (0.04s)
        claim_startup_test.go:115: startup: exit status 1
            hostops: required contract unavailable
FAIL
FAIL	github.com/carlosframework/platform/internal/consolecmd	2.696s
?   	github.com/carlosframework/platform/internal/controlname	[no test files]
?   	github.com/carlosframework/platform/internal/controlsock	[no test files]
ok  	github.com/carlosframework/platform/internal/dbdoor	6.037s
ok  	github.com/carlosframework/platform/internal/dbdoor/dbdoortest	0.008s
ok  	github.com/carlosframework/platform/internal/dbverify	0.004s
ok  	github.com/carlosframework/platform/internal/dnsrec	0.002s
ok  	github.com/carlosframework/platform/internal/docsite	0.004s
ok  	github.com/carlosframework/platform/internal/domains	2.348s
ok  	github.com/carlosframework/platform/internal/doorkey	0.006s
ok  	github.com/carlosframework/platform/internal/economics	0.005s
ok  	github.com/carlosframework/platform/internal/edge	26.691s
?   	github.com/carlosframework/platform/internal/edgeproof	[no test files]
ok  	github.com/carlosframework/platform/internal/edgesfile	0.008s
ok  	github.com/carlosframework/platform/internal/fleetapi	0.129s
ok  	github.com/carlosframework/platform/internal/gate	1.655s
ok  	github.com/carlosframework/platform/internal/genselect	0.014s
ok  	github.com/carlosframework/platform/internal/geo	0.013s
ok  	github.com/carlosframework/platform/internal/geoip	0.029s
ok  	github.com/carlosframework/platform/internal/historyreadback	0.151s
ok  	github.com/carlosframework/platform/internal/host	173.692s
ok  	github.com/carlosframework/platform/internal/hostactors	0.529s
ok  	github.com/carlosframework/platform/internal/hostbox	2.563s
ok  	github.com/carlosframework/platform/internal/hostclaim	0.539s
ok  	github.com/carlosframework/platform/internal/hostevidence	0.014s
ok  	github.com/carlosframework/platform/internal/hostexec	4.810s
ok  	github.com/carlosframework/platform/internal/hostfence	0.029s
ok  	github.com/carlosframework/platform/internal/hostfiles	10.436s
ok  	github.com/carlosframework/platform/internal/hostfloor	0.014s
ok  	github.com/carlosframework/platform/internal/hostguard	1.043s
ok  	github.com/carlosframework/platform/internal/hostops	1.590s
?   	github.com/carlosframework/platform/internal/hostops/fencerelease	[no test files]
ok  	github.com/carlosframework/platform/internal/hostops/internal/fencerelease	0.014s
ok  	github.com/carlosframework/platform/internal/hostquarantine	0.014s
ok  	github.com/carlosframework/platform/internal/hostrelocate	0.479s
ok  	github.com/carlosframework/platform/internal/hostresolve	0.146s
ok  	github.com/carlosframework/platform/internal/hostwrite	0.016s
ok  	github.com/carlosframework/platform/internal/infraview	0.014s
ok  	github.com/carlosframework/platform/internal/instancectl	5.229s
ok  	github.com/carlosframework/platform/internal/instanceexec	2.728s
ok  	github.com/carlosframework/platform/internal/instancefs	0.015s
ok  	github.com/carlosframework/platform/internal/instancekey	0.014s
ok  	github.com/carlosframework/platform/internal/instanceupdates	0.021s
ok  	github.com/carlosframework/platform/internal/layout	1.556s
ok  	github.com/carlosframework/platform/internal/lease	9.808s
ok  	github.com/carlosframework/platform/internal/lease/leasetest	0.019s
ok  	github.com/carlosframework/platform/internal/ledger	0.012s
ok  	github.com/carlosframework/platform/internal/mail	0.184s
ok  	github.com/carlosframework/platform/internal/maintenance	0.096s
ok  	github.com/carlosframework/platform/internal/maintenanceexec	5.495s
ok  	github.com/carlosframework/platform/internal/maintenanceguard	0.075s
ok  	github.com/carlosframework/platform/internal/metrics	0.005s
ok  	github.com/carlosframework/platform/internal/moves	21.092s
ok  	github.com/carlosframework/platform/internal/moves/archive	0.781s
ok  	github.com/carlosframework/platform/internal/objstore	0.201s
ok  	github.com/carlosframework/platform/internal/ops	0.182s
ok  	github.com/carlosframework/platform/internal/park	0.200s
ok  	github.com/carlosframework/platform/internal/privatefile	0.003s
ok  	github.com/carlosframework/platform/internal/provision	32.180s
ok  	github.com/carlosframework/platform/internal/publicrelease	0.008s
?   	github.com/carlosframework/platform/internal/regionname	[no test files]
ok  	github.com/carlosframework/platform/internal/registry	5.415s
ok  	github.com/carlosframework/platform/internal/relay	7.238s
ok  	github.com/carlosframework/platform/internal/release	22.259s
ok  	github.com/carlosframework/platform/internal/rollout	3.675s
ok  	github.com/carlosframework/platform/internal/rolloutwords	0.003s
ok  	github.com/carlosframework/platform/internal/rootaws	0.006s
ok  	github.com/carlosframework/platform/internal/rootparticipation	0.063s
ok  	github.com/carlosframework/platform/internal/rootstate	0.028s
ok  	github.com/carlosframework/platform/internal/runs	0.577s
ok  	github.com/carlosframework/platform/internal/s3compat	0.004s
ok  	github.com/carlosframework/platform/internal/safefile	0.004s
ok  	github.com/carlosframework/platform/internal/schedule	2.506s
ok  	github.com/carlosframework/platform/internal/seeds	0.011s
ok  	github.com/carlosframework/platform/internal/sim/hostlife	9.406s
ok  	github.com/carlosframework/platform/internal/sim/litestreamfacts	3.821s
ok  	github.com/carlosframework/platform/internal/status	0.043s
ok  	github.com/carlosframework/platform/internal/steering	0.008s
ok  	github.com/carlosframework/platform/internal/stopevidence	0.324s
ok  	github.com/carlosframework/platform/internal/svcrole	0.002s
ok  	github.com/carlosframework/platform/internal/telemetry	0.004s
ok  	github.com/carlosframework/platform/internal/termfence	0.003s
ok  	github.com/carlosframework/platform/internal/tunnelclient	0.006s
ok  	github.com/carlosframework/platform/internal/unixdial	0.002s
ok  	github.com/carlosframework/platform/internal/updatewords	0.004s
ok  	github.com/carlosframework/platform/internal/usage	0.004s
ok  	github.com/carlosframework/platform/internal/version	0.002s
ok  	github.com/carlosframework/platform/internal/vet	1.564s
FAIL
make: *** [Makefile:18: test] Error 1

amadan: step gate failed: exit status 2

amadan: job failed at step gate — its output is above, not at the tail of this log
diff --git a/internal/host/claimactivation_preflight.go b/internal/host/claimactivation_preflight.go
index 57e677860..7e35f8bdd 100644
--- a/internal/host/claimactivation_preflight.go
+++ b/internal/host/claimactivation_preflight.go
@@ -10,7 +10,9 @@ import (
"github.com/carlosframework/platform/internal/registry"
"github.com/carlosframework/platform/internal/release"
"github.com/carlosframework/platform/internal/rootstate"
+ "io/fs"
"os"
+ "path/filepath"
"slices"
"strings"
)
@@ -68,6 +70,9 @@ func claimActivationVerified(ctx context.Context, store release.Store, r ClaimRu
}
}
}
+ if err := claimActivationLocalUnits(r); err != nil {
+ return err
+ }
if r.Mode == "agent" {
return nil
}
@@ -99,3 +104,63 @@ func claimActivationVerified(ctx context.Context, store release.Store, r ClaimRu
}
return nil
}
+
+func claimActivationLocalUnits(r ClaimRuntimeResources) error {
+ if r.Mode != "agent" && os.Geteuid() == 0 {
+ for _, dir := range []string{"/var/lib/carlos-app", "/var/lib/private/carlos-app"} {
+ entries, err := os.ReadDir(dir)
+ if errors.Is(err, os.ErrNotExist) {
+ continue
+ }
+ if err != nil {
+ return err
+ }
+ if len(entries) != 0 {
+ return hostwrite.ErrUnavailable
+ }
+ }
+ }
+ for _, dir := range []string{r.UnitsDir, "/etc/systemd/system", "/run/systemd/system", "/usr/lib/systemd/system", "/lib/systemd/system", "/etc/carlos/apps"} {
+ if dir == "" {
+ continue
+ }
+ err := filepath.WalkDir(dir, func(path string, entry fs.DirEntry, err error) error {
+ if errors.Is(err, os.ErrNotExist) && path == dir {
+ return nil
+ }
+ if err != nil {
+ return err
+ }
+ name := entry.Name()
+ if strings.HasPrefix(name, "carlos-app@") && name != "carlos-app@.service" && name != "carlos-app@.socket" {
+ return hostwrite.ErrUnavailable
+ }
+ if entry.IsDir() {
+ return nil
+ }
+ if dir == "/etc/carlos/apps" {
+ return hostwrite.ErrUnavailable
+ }
+ if !strings.HasSuffix(name, ".service") && !strings.HasSuffix(name, ".conf") {
+ return nil
+ }
+ if _, err := unitsFS.ReadFile("units/" + name); err == nil {
+ return nil
+ }
+ raw, err := os.ReadFile(path)
+ if err != nil {
+ return err
+ }
+ for _, marker := range []string{filepath.Join(r.OptDir, "live") + "/", "/opt/carlos/live/", "/etc/carlos/apps/", "User=carlos", "User = carlos"} {
+ if strings.Contains(string(raw), marker) {
+ return hostwrite.ErrUnavailable
+ }
+ }
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/internal/host/claimactivation_preflight_test.go b/internal/host/claimactivation_preflight_test.go
index 4feb386ac..0350888df 100644
--- a/internal/host/claimactivation_preflight_test.go
+++ b/internal/host/claimactivation_preflight_test.go
@@ -107,3 +107,44 @@ func TestPlan14ActivationRejectsHiddenNamedRecords(t *testing.T) {
})
}
}
+
+func TestPlan14ActivationRejectsHiddenLocalUnits(t *testing.T) {
+ for _, unit := range []string{"carlos-app@shop.example.service", "console.service", "woodstar.service.d/override.conf"} {
+ for _, attack := range []string{"omitted", "cleared"} {
+ t.Run(unit+"/"+attack, func(t *testing.T) {
+ reg := restartTestRegistry(t)
+ if attack == "cleared" {
+ if err := reg.Upsert(registry.Route{Host: "shop.example", Account: "bab", App: "shop", Socket: "/run/shop.sock"}); err != nil {
+ t.Fatal(err)
+ }
+ }
+ dir := t.TempDir()
+ units := filepath.Join(dir, "units")
+ path := filepath.Join(units, unit)
+ if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.WriteFile(path, []byte("[Service]\nExecStart=/opt/carlos/live/shop.example serve\n"), 0644); err != nil {
+ t.Fatal(err)
+ }
+ store := release.NewDirStore(filepath.Join(dir, "bucket"))
+ for _, mode := range []string{"agent", "instance-control"} {
+ p := &activationPreflightOwner{}
+ r := ClaimRuntimeResources{Mode: mode, Registry: reg, DataDir: filepath.Join(dir, "data"), OptDir: filepath.Join(dir, "opt"), UnitsDir: units}
+ if _, err := OpenProductionClaimAssembly(t.Context(), store, r, p); !errors.Is(err, hostwrite.ErrUnavailable) || p.calls != 0 {
+ t.Fatal(err, p.calls)
+ }
+ r.Mode = "instance-control"
+ if err := migrateClaimJournalsWith(t.Context(), store, r, p, true); !errors.Is(err, hostwrite.ErrUnavailable) || p.calls != 0 {
+ t.Fatal(err, p.calls)
+ }
+ for _, path := range []string{r.DataDir, r.OptDir} {
+ if _, err := os.Stat(path); !os.IsNotExist(err) {
+ t.Fatal("created state", path, err)
+ }
+ }
+ }
+ })
+ }
+ }
+}
diff --git a/internal/host/claimconstruction_rootrunner_linux_test.go b/internal/host/claimconstruction_rootrunner_linux_test.go
index 745079928..806967730 100644
--- a/internal/host/claimconstruction_rootrunner_linux_test.go
+++ b/internal/host/claimconstruction_rootrunner_linux_test.go
@@ -20,7 +20,9 @@ import (
"time"
"github.com/carlosframework/platform/internal/hostactors"
+ "github.com/carlosframework/platform/internal/hostbox"
execlease "github.com/carlosframework/platform/internal/hostexec"
+ "github.com/carlosframework/platform/internal/hostguard"
"github.com/carlosframework/platform/internal/hostops"
"github.com/carlosframework/platform/internal/release"
"github.com/carlosframework/platform/internal/rootparticipation"
@@ -73,7 +75,7 @@ func filesSeedInstanceControlChild(t *testing.T, binary, root string, listener *
}
cmd := exec.Command(binary, "-test.run=^TestRootFilesSeedInstanceControlPayload$", "-test.v", "-test.timeout=90s")
cmd.Env = append(os.Environ(), "FILES_SEED_CONTROL=1", "TMPDIR="+root)
- if os.Getenv("PLAN14_R9_PENDING") != "" {
+ if os.Getenv("PLAN14_R9_PENDING") != "" || os.Getenv("PLAN14_R10_ACQUISITION") != "" || os.Getenv("PLAN14_R10_RETIRED") != "" {
cmd.SysProcAttr = &syscall.SysProcAttr{Cloneflags: syscall.CLONE_NEWNS}
for _, path := range []string{"/run/carlos", "/run/carlos/hostguard"} {
if err := os.Mkdir(path, 0755); err == nil {
@@ -112,6 +114,15 @@ func TestRootFilesSeedInstanceControlPayload(t *testing.T) {
t.Setenv("LISTEN_FDS", "1")
t.Setenv("LISTEN_PID", strconv.Itoa(os.Getpid()))
store, resources, owners, _ := fix4JournalFixture(t)
+ if os.Getenv("PLAN14_R10_ACQUISITION") != "" || os.Getenv("PLAN14_R10_RETIRED") != "" {
+ openClaimRootAuthority = func() (rootstate.Store, error) {
+ return rootstate.Open("/var/lib/carlos-claims/authority/fences", true, false)
+ }
+ openClaimRootJournal = func() (*hostbox.LaunchJournal, error) {
+ return hostbox.OpenLaunchJournal("/var/lib/carlos-claims/journals.db")
+ }
+ }
+
resources.OptDir = generationRootRunnerDir(t)
dir := t.TempDir()
t.Setenv("CARLOS_DEPLOYMENT_DIR", dir)
@@ -144,7 +155,40 @@ func TestRootFilesSeedInstanceControlPayload(t *testing.T) {
return rootstate.Open(filepath.Join(dir, "release-exclusion"), true, false)
}}
rootLaunchStartup.Do(func() {
- rootLaunchStartup.err = rootparticipation.RegisterLaunchVerifier(func(ctx context.Context, _ uint64) error { return exclusion.Check(ctx) }, exclusion.With)
+ verify := func(ctx context.Context, _ uint64) error { return exclusion.Check(ctx) }
+ if os.Getenv("PLAN14_R10_ACQUISITION") != "" || os.Getenv("PLAN14_R10_RETIRED") != "" {
+ generation, err := rootstate.Open(release.RootGenerationsDir, true, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ generation.Close()
+ read := func(ctx context.Context) (generationInventory, error) {
+ snapshot, err := productionLocalGenerationInventory(ctx)
+ if err != nil {
+ t.Logf("production generation read: %v", err)
+ return snapshot, err
+ }
+ if os.Getenv("PLAN14_R10_RETIRED") != "" {
+ if len(snapshot.Installations) != 2 || len(snapshot.Payloads) != 2 {
+ t.Fatalf("lost retained installation or payload: %d %d", len(snapshot.Installations), len(snapshot.Payloads))
+ }
+ installs := map[string]hostguard.FenceView{}
+ for _, v := range snapshot.Installations {
+ installs[v.Host] = v
+ }
+ if err := generationPayloadBindings(snapshot.Payloads[1:], installs); err == nil {
+ t.Fatal("retirement dropped payload obligation")
+ }
+ }
+ return snapshot, nil
+ }
+ v := rootLaunchVerifier{exclude: exclusion, open: func() (rootstate.Store, error) { return rootstate.Open(release.RootGenerationsDir, false, false) }, reconcile: generationReconciler{read: read, kernel: func(context.Context) (string, string, error) {
+ boot, err := execlease.BootID()
+ return boot, floorTestDigest, err
+ }}}
+ verify = v.verify
+ }
+ rootLaunchStartup.err = rootparticipation.RegisterLaunchVerifier(verify, exclusion.With)
})
if rootLaunchStartup.err != nil {
t.Fatal(rootLaunchStartup.err)
@@ -157,6 +201,15 @@ func TestRootFilesSeedInstanceControlPayload(t *testing.T) {
}
}
}
+ if os.Getenv("PLAN14_R10_RETIRED") != "" {
+ for _, host := range []string{"sibling.example", "retired.example"} {
+ t.Setenv("PLAN14_R10_INSTALL_HOST", host)
+ filesSeedPendingConstruction(t, release.NewDirStore(dir), resources, owners.ClaimOwnerBindings, actor)
+ }
+ }
+ if os.Getenv("PLAN14_R10_ACQUISITION") != "" {
+ plan14PendingAcquisition(t, resources.OptDir, actor.ControlBucket)
+ }
RunInstanceControl(nil)
t.Fatal("claims-on instance-control returned before serving")
}
diff --git a/internal/host/claimunit_binding.go b/internal/host/claimunit_binding.go
index 5ab79797d..39d1f5d27 100644
--- a/internal/host/claimunit_binding.go
+++ b/internal/host/claimunit_binding.go
@@ -22,6 +22,7 @@ import (
type claimUnitShape struct {
Host, Account, App, Kind, Unit, Backing, Socket, Addr, DBPath string
+ Console bool `json:",omitempty"`
Program string `json:",omitempty"`
PredecessorDigest string `json:",omitempty"`
}
@@ -42,6 +43,9 @@ type claimUnitBindings struct {
}
func (s claimUnitShape) valid() bool {
+ if s.Console && (s.Unit != "console.service" || s.Backing != "" || !claimHexDigest(s.Program)) {
+ return false
+ }
if (s.Program != "" && !claimHexDigest(s.Program)) || (s.PredecessorDigest != "" && !claimHexDigest(s.PredecessorDigest)) {
return false
}
diff --git a/internal/host/claimunit_console.go b/internal/host/claimunit_console.go
new file mode 100644
index 000000000..bb7addf46
--- /dev/null
+++ b/internal/host/claimunit_console.go
@@ -0,0 +1,28 @@
+package host
+
+import (
+ "encoding/json"
+ "io"
+ "os"
+
+ "github.com/carlosframework/platform/internal/hostfloor"
+ "github.com/carlosframework/platform/internal/rootstate"
+)
+
+func verifyClaimConsoleEnrollment(b claimUnitBinding) error {
+ f, err := rootstate.OpenAuthorityFile("/etc/carlos/host-actor.json")
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ return checkClaimConsoleEnrollment(f, b)
+}
+
+func checkClaimConsoleEnrollment(r io.Reader, b claimUnitBinding) error {
+ var enrollment hostfloor.Enrollment
+ dec := json.NewDecoder(io.LimitReader(r, rootstate.MaxRecord+1))
+ if !b.valid() || !b.Shape.Console || dec.Decode(&enrollment) != nil || dec.Decode(new(any)) != io.EOF || enrollment.Box != b.Owner.Box || enrollment.Enrollment != b.Owner.Enrollment || enrollment.InstanceID == "" {
+ return os.ErrPermission
+ }
+ return nil
+}
diff --git a/internal/host/claimunit_console_test.go b/internal/host/claimunit_console_test.go
new file mode 100644
index 000000000..01a80d10e
--- /dev/null
+++ b/internal/host/claimunit_console_test.go
@@ -0,0 +1,40 @@
+package host
+
+import (
+ "encoding/json"
+ "strings"
+ "testing"
+
+ "github.com/carlosframework/platform/internal/hostfloor"
+)
+
+func TestPlan14ConsoleEnrollmentMatchesRootOwner(t *testing.T) {
+ _, owner, shape := plan14UnitBindingFixture(t, "console.service", "", "")
+ shape.Console, shape.Program = true, strings.Repeat("a", 64)
+ binding := claimUnitBinding{Schema: 1, Owner: unitBindingOwner(owner), Shape: shape}
+ good := hostfloor.Enrollment{Box: owner.Box, Enrollment: owner.Enrollment, InstanceID: "i-0123456789abcdef0"}
+ for _, change := range []string{"none", "box", "enrollment", "instance", "trailing", "malformed"} {
+ t.Run(change, func(t *testing.T) {
+ value := good
+ switch change {
+ case "box":
+ value.Box = "other"
+ case "enrollment":
+ value.Enrollment = "other"
+ case "instance":
+ value.InstanceID = ""
+ }
+ raw, _ := json.Marshal(value)
+ if change == "trailing" {
+ raw = append(raw, []byte(" {}")...)
+ }
+ if change == "malformed" {
+ raw = []byte("{")
+ }
+ err := checkClaimConsoleEnrollment(strings.NewReader(string(raw)), binding)
+ if (err == nil) != (change == "none") {
+ t.Fatal(change, err)
+ }
+ })
+ }
+}
diff --git a/internal/host/claimunit_definition.go b/internal/host/claimunit_definition.go
index 6e105c5f0..73d3f8b39 100644
--- a/internal/host/claimunit_definition.go
+++ b/internal/host/claimunit_definition.go
@@ -41,6 +41,10 @@ func claimBoundUnitDefinition(s claimUnitShape) ([]byte, error) {
}
body += "\n[Service]\nIPAddressAllow=" + host + bits + "\nSocketBindDeny=any\nSocketBindAllow=" + family + ":tcp:" + port + "\n"
}
+ if s.Console {
+ body = strings.Replace(body, "InaccessiblePaths=/etc/carlos /run/carlos-control\n", "InaccessiblePaths=/run/carlos-control\n", 1)
+ body += "\n[Service]\nTemporaryFileSystem=/etc/carlos:ro\nBindReadOnlyPaths=/etc/carlos/host-actor.json\nIPAddressAllow=169.254.169.254/32\n"
+ }
return []byte(body), nil
}
diff --git a/internal/host/claimunit_definition_test.go b/internal/host/claimunit_definition_test.go
index 8da9e6981..2fe4f586e 100644
--- a/internal/host/claimunit_definition_test.go
+++ b/internal/host/claimunit_definition_test.go
@@ -1,6 +1,7 @@
package host
import (
+ "context"
"strings"
"testing"
@@ -95,3 +96,61 @@ func TestPlan14MappedLoopbackDefinition(t *testing.T) {
t.Fatal("mapped address broadened IPv6 allowance")
}
}
+
+func TestPlan14ConsoleContainmentRequiresRootProfile(t *testing.T) {
+ _, _, shape := plan14UnitBindingFixture(t, "console.service", "", "")
+ shape.Program = strings.Repeat("a", 64)
+ ordinary, err := claimBoundUnitDefinition(shape)
+ if err != nil {
+ t.Fatal(err)
+ }
+ shape.Console = true
+ bound, err := claimBoundUnitDefinition(shape)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, setting := range []string{
+ "TemporaryFileSystem=/etc/carlos:ro",
+ "BindReadOnlyPaths=/etc/carlos/host-actor.json",
+ "IPAddressAllow=169.254.169.254/32",
+ } {
+ if !strings.Contains(string(bound), setting+"\n") || strings.Contains(string(ordinary), setting+"\n") {
+ t.Fatal("console capability not explicit", setting)
+ }
+ }
+ for _, setting := range []string{"InaccessiblePaths=/run/carlos-control", "PrivatePIDs=yes", "ProtectControlGroups=yes", "SocketBindDeny=any", "Restart=no"} {
+ if !strings.Contains(string(bound), setting+"\n") {
+ t.Fatal("lost containment", setting)
+ }
+ }
+ if strings.Contains(string(bound), "IPAddressAllow=localhost") || strings.Contains(string(bound), "BindReadOnlyPaths=/etc/carlos\n") {
+ t.Fatal("broad console capability")
+ }
+ for _, mutate := range []func(*claimUnitShape){
+ func(s *claimUnitShape) { s.Unit = "woodstar.service" },
+ func(s *claimUnitShape) { s.Unit = "carlos-app@" + s.Host + ".service"; s.Backing = "unit" },
+ func(s *claimUnitShape) { s.Program = "" },
+ } {
+ bad := shape
+ mutate(&bad)
+ if _, err := claimBoundUnitDefinition(bad); err == nil {
+ t.Fatal("console capability escaped its binding", bad)
+ }
+ }
+}
+
+func TestPlan14ConsoleProfileCannotChangeBoundShape(t *testing.T) {
+ bindings, _, shape := plan14UnitBindingFixture(t, "console.service", "", "")
+ shape.Program = strings.Repeat("a", 64)
+ bindings.canonical = func(context.Context, string) (claimUnitShape, error) { return shape, nil }
+ if _, err := bindings.establish(t.Context(), shape.Host); err != nil {
+ t.Fatal(err)
+ }
+ shape.Console = true
+ if _, err := bindings.check(t.Context(), shape.Host, nil); err == nil {
+ t.Fatal("console capability added to existing binding")
+ }
+ if _, err := bindings.establish(t.Context(), shape.Host); err == nil {
+ t.Fatal("console capability replaced existing binding")
+ }
+}
diff --git a/internal/host/claimunit_effective_test.go b/internal/host/claimunit_effective_test.go
index 71b04ee5c..76ef43263 100644
--- a/internal/host/claimunit_effective_test.go
+++ b/internal/host/claimunit_effective_test.go
@@ -10,45 +10,54 @@ import (
)
func TestPlan14EffectiveBoundUnit(t *testing.T) {
- _, _, shape := plan14UnitBindingFixture(t, "console.service", "", "")
- shape.Program = strings.Repeat("a", 64)
- definition, err := claimBoundUnitDefinition(shape)
- if err != nil {
- t.Fatal(err)
- }
- values := map[string]string{"FragmentPath": "/etc/systemd/system/console.service", "DropInPaths": "", "NeedDaemonReload": "no", "ExecStart": "{ path=/usr/bin/false ; argv[]=/usr/bin/false ; }", "IPAddressAllow": "", "IPAddressDeny": "", "Slice": instancectl.SliceName(shape.Host)}
- for _, line := range strings.Split(string(definition), "\n") {
- k, v, ok := strings.Cut(line, "=")
- if !ok {
- continue
- }
- if k == "ExecStart" {
- continue
- }
- values[k] = v
- }
- for _, attack := range []string{"none", "DropInPaths", "User", "Group", "NoNewPrivileges", "ProtectSystem", "PrivatePIDs", "IPAddressAllow", "ExecStart", "NeedDaemonReload", "FragmentPath", "SocketBindAllow", "SocketBindDeny", "BindsTo", "PartOf"} {
- t.Run(attack, func(t *testing.T) {
- ctl := func(_ context.Context, args ...string) (string, error) {
- var out strings.Builder
- for _, key := range strings.Split(args[2], ",") {
- value := values[key]
- if key == attack {
- value = "forged"
- if key == "BindsTo" || key == "PartOf" {
- value = "carlos-edge.service"
- }
- }
- out.WriteString(key + "=" + value + "\n")
- }
- return out.String(), nil
- }
- err := claimEffectiveUnit(t.Context(), shape, ctl)
- if attack == "none" && err != nil {
+ for _, profile := range []bool{false, true} {
+ t.Run(map[bool]string{false: "ordinary", true: "console"}[profile], func(t *testing.T) {
+ _, _, shape := plan14UnitBindingFixture(t, "console.service", "", "")
+ shape.Program = strings.Repeat("a", 64)
+ shape.Console = profile
+ definition, err := claimBoundUnitDefinition(shape)
+ if err != nil {
t.Fatal(err)
}
- if attack != "none" && err == nil {
- t.Fatal("effective override accepted", attack)
+ values := map[string]string{"FragmentPath": "/etc/systemd/system/console.service", "DropInPaths": "", "NeedDaemonReload": "no", "ExecStart": "{ path=/usr/bin/false ; argv[]=/usr/bin/false ; }", "IPAddressAllow": "", "IPAddressDeny": "", "Slice": instancectl.SliceName(shape.Host)}
+ for _, line := range strings.Split(string(definition), "\n") {
+ k, v, ok := strings.Cut(line, "=")
+ if !ok {
+ continue
+ }
+ if k == "ExecStart" {
+ continue
+ }
+ if k == "IPAddressAllow" || k == "IPAddressDeny" {
+ values[k] += " " + v
+ } else {
+ values[k] = v
+ }
+ }
+ for _, attack := range []string{"none", "DropInPaths", "User", "Group", "NoNewPrivileges", "ProtectSystem", "PrivatePIDs", "IPAddressAllow", "ExecStart", "NeedDaemonReload", "FragmentPath", "SocketBindAllow", "SocketBindDeny", "BindsTo", "PartOf"} {
+ t.Run(attack, func(t *testing.T) {
+ ctl := func(_ context.Context, args ...string) (string, error) {
+ var out strings.Builder
+ for _, key := range strings.Split(args[2], ",") {
+ value := values[key]
+ if key == attack {
+ value = "forged"
+ if key == "BindsTo" || key == "PartOf" {
+ value = "carlos-edge.service"
+ }
+ }
+ out.WriteString(key + "=" + value + "\n")
+ }
+ return out.String(), nil
+ }
+ err := claimEffectiveUnit(t.Context(), shape, ctl)
+ if attack == "none" && err != nil {
+ t.Fatal(err)
+ }
+ if attack != "none" && err == nil {
+ t.Fatal("effective override accepted", attack)
+ }
+ })
}
})
}
diff --git a/internal/host/claimunit_materialize.go b/internal/host/claimunit_materialize.go
index 5f8f1a616..731eb5012 100644
--- a/internal/host/claimunit_materialize.go
+++ b/internal/host/claimunit_materialize.go
@@ -143,6 +143,11 @@ func (m claimUnitMaterializer) install(ctx context.Context, b claimUnitBinding)
}
func (m claimUnitMaterializer) verify(b claimUnitBinding) error {
+ if b.Shape.Console {
+ if err := verifyClaimConsoleEnrollment(b); err != nil {
+ return err
+ }
+ }
r, err := m.load(b.Owner.Host)
if err != nil {
return err
diff --git a/internal/host/plan14_enabled_console_root_linux_test.go b/internal/host/plan14_enabled_console_root_linux_test.go
new file mode 100644
index 000000000..788c77a0b
--- /dev/null
+++ b/internal/host/plan14_enabled_console_root_linux_test.go
@@ -0,0 +1,185 @@
+//go:build linux && rootrunner
+
+package host
+
+import (
+ "crypto/ed25519"
+ "database/sql"
+ "encoding/base64"
+ "encoding/json"
+ "io"
+ "net"
+ "net/http"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+
+ "github.com/carlosframework/platform/internal/hostactors"
+ "github.com/carlosframework/platform/internal/hostfloor"
+ "github.com/carlosframework/platform/internal/hostguard"
+ "github.com/carlosframework/platform/internal/lease/leasetest"
+ "github.com/carlosframework/platform/internal/provision"
+ "github.com/carlosframework/platform/internal/release"
+ "github.com/carlosframework/platform/internal/status"
+)
+
+func TestPlan14ContainedEnabledConsoleRootRunner(t *testing.T) {
+ plan14ServiceExecutables(t)
+ plan14InstalledClaimLaunch(t, "enabled:controller:authority:binary:console:materialized:tcp")
+}
+
+func plan14ConsoleEnrollment(t *testing.T, owner hostguard.RootInstallation) {
+ t.Helper()
+ instance := status.InstanceID()
+ if instance == "" {
+ t.Fatal("live instance identity unavailable")
+ }
+ if os.Getenv("PLAN14_CONTAINED_FAILURE") == "enrollment" {
+ instance = "i-00000000000000000"
+ }
+ raw, _ := json.Marshal(hostfloor.Enrollment{Box: owner.Box, Enrollment: owner.Enrollment, InstanceID: instance})
+ if err := os.WriteFile("/etc/carlos/host-actor.json", raw, 0644); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.WriteFile("/etc/carlos/console-private", []byte("hidden"), 0644); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func plan14EnabledConsoleProgram(t *testing.T, p claimUnitProgram, check func(*http.Client)) (claimUnitProgram, func(*http.Client)) {
+ t.Helper()
+ pub, key, err := ed25519.GenerateKey(nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ const bucket = "console-runner"
+ roster := hostactors.Roster{V: 1, DeploymentBucket: bucket, Revision: 1,
+ Boxes: map[string]hostactors.Box{}, Holds: map[string]hostactors.Reservation{}, Activations: map[string]hostactors.Reservation{},
+ Consoles: map[string]hostactors.Console{"console": {Enrollment: strings.Repeat("a", 32), InstanceID: "console", Namespaces: []string{"platform"}, DeploymentBucket: bucket}}}
+ signed, err := release.SignRootEnvelope(release.RootSigner(key), roster)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if os.Getenv("PLAN14_CONTAINED_FAILURE") == "roster" {
+ signed = []byte(`{"bad":"signature"}`)
+ }
+ objects := leasetest.NewVersionedBucket()
+ objects.AddBucket(bucket)
+ objects.Put(bucket, hostactors.Key, signed)
+ s3, _ := leasetest.NewS3Server(objects)
+ listener, err := net.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ t.Fatal(err)
+ }
+ server := &http.Server{Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Query().Get("list-type") == "2" {
+ io.WriteString(w, `<ListBucketResult><IsTruncated>false</IsTruncated></ListBucketResult>`)
+ return
+ }
+ s3.ServeHTTP(w, r)
+ })}
+ go server.Serve(listener)
+ t.Cleanup(func() { server.Close() })
+ _, port, _ := net.SplitHostPort(listener.Addr().String())
+ plan14FixtureNAT(t, port)
+ endpoint := "http://192.0.2.40:" + port
+ var kept []string
+ for _, env := range p.Env {
+ if strings.HasPrefix(env, "CARLOS_DEPLOYMENT_DIR=") || strings.HasPrefix(env, "CARLOS_RELEASE_PUBKEY=") {
+ continue
+ }
+ kept = append(kept, env)
+ }
+ p.Env = append(kept, "PLAN14_CONSOLE_CONTAINMENT_CHECK=1", "CARLOS_HOST_CLAIM=1", "CARLOS_CONSOLE_ID=console", "CARLOS_DEPLOYMENT_BUCKET="+bucket,
+ "CARLOS_RELEASE_PUBKEY="+base64.StdEncoding.EncodeToString(pub), "CARLOS_RELEASE_KEY="+base64.StdEncoding.EncodeToString(key.Seed()),
+ "AWS_REGION=eu-west-1", "AWS_ACCESS_KEY_ID=runner", "AWS_SECRET_ACCESS_KEY=runner", "AWS_ENDPOINT_URL_S3="+endpoint, "AWS_ENDPOINT_URL="+endpoint,
+ "CARLOS_DATA_DIR="+provision.InstanceDir("/data", p.Host), "CARLOS_REGISTRY="+filepath.Join(provision.InstanceDir("/data", p.Host), "registry.db"))
+ return p, func(client *http.Client) {
+ check(client)
+ db, err := sql.Open("sqlite", provision.NestedDBPath("/data", p.Host))
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer db.Close()
+ var instance string
+ if err := db.QueryRow("SELECT instance FROM claim_sql_identity").Scan(&instance); err != nil || instance != status.InstanceID() {
+ t.Fatal("contained gated SQL identity", instance, err)
+ }
+ }
+}
+
+func plan14CheckConsoleContainment() error {
+ raw, err := os.ReadFile("/etc/carlos/host-actor.json")
+ if err != nil {
+ return err
+ }
+ var enrollment hostfloor.Enrollment
+ if json.Unmarshal(raw, &enrollment) != nil || enrollment.InstanceID == "" || status.InstanceID() == "" {
+ return os.ErrPermission
+ }
+ if f, err := os.OpenFile("/etc/carlos/host-actor.json", os.O_WRONLY, 0); err == nil {
+ f.Close()
+ return os.ErrPermission
+ }
+ for _, path := range []string{"/etc/carlos/console-private", "/etc/carlos/claim-units", "/run/carlos-control", "/data/instances/sibling.example"} {
+ if _, err := os.ReadDir(path); err == nil {
+ return os.ErrPermission
+ }
+ if _, err := os.ReadFile(path); err == nil {
+ return os.ErrPermission
+ }
+ }
+ return nil
+}
+
+func plan14ConsoleEnrollmentRefusals(t *testing.T, dispatch claimUnitDispatch, host string) {
+ t.Helper()
+ const path = "/etc/carlos/host-actor.json"
+ raw, err := os.ReadFile(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ restore := func() {
+ if err := os.WriteFile(path, raw, 0644); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.Chmod(path, 0644); err != nil {
+ t.Fatal(err)
+ }
+ }
+ defer restore()
+ if err := os.Chmod(path, 0666); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := dispatch.forStart(t.Context(), host); err == nil {
+ t.Fatal("writable enrollment accepted")
+ }
+ restore()
+ var enrollment hostfloor.Enrollment
+ if err := json.Unmarshal(raw, &enrollment); err != nil {
+ t.Fatal(err)
+ }
+ enrollment.Box = "foreign"
+ foreign, _ := json.Marshal(enrollment)
+ if err := os.WriteFile(path, foreign, 0644); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := dispatch.forStart(t.Context(), host); err == nil {
+ t.Fatal("foreign enrollment accepted")
+ }
+ restore()
+ if _, err := dispatch.forStart(t.Context(), host); err != nil {
+ t.Fatal("restored enrollment refused", err)
+ }
+}
+
+func TestPlan14ContainedConsoleRefusalsRootRunner(t *testing.T) {
+ for _, failure := range []string{"roster", "enrollment"} {
+ t.Run(failure, func(t *testing.T) {
+ t.Setenv("PLAN14_CONTAINED_FAILURE", failure)
+ plan14ServiceExecutables(t)
+ plan14InstalledClaimLaunch(t, "enabled:controller:authority:binary:console:materialized:tcp")
+ })
+ }
+}
diff --git a/internal/host/plan14_fixture_nat_root_linux_test.go b/internal/host/plan14_fixture_nat_root_linux_test.go
new file mode 100644
index 000000000..537c39857
--- /dev/null
+++ b/internal/host/plan14_fixture_nat_root_linux_test.go
@@ -0,0 +1,136 @@
+//go:build linux && rootrunner
+
+package host
+
+import (
+ "encoding/binary"
+ "fmt"
+ "os"
+ "strconv"
+ "syscall"
+ "testing"
+
+ "golang.org/x/sys/unix"
+)
+
+func plan14NFTAttr(kind uint16, values ...[]byte) []byte {
+ var value []byte
+ for _, v := range values {
+ value = append(value, v...)
+ }
+ n := 4 + len(value)
+ raw := make([]byte, (n+3)&^3)
+ binary.NativeEndian.PutUint16(raw, uint16(n))
+ binary.NativeEndian.PutUint16(raw[2:], kind)
+ copy(raw[4:], value)
+ return raw
+}
+func plan14NFTWord(kind uint16, value uint32) []byte {
+ raw := make([]byte, 4)
+ binary.BigEndian.PutUint32(raw, value)
+ return plan14NFTAttr(kind, raw)
+}
+func plan14NFTText(kind uint16, value string) []byte {
+ return plan14NFTAttr(kind, append([]byte(value), 0))
+}
+func plan14NFTExpression(name string, attrs ...[]byte) []byte {
+ return plan14NFTAttr(unix.NFTA_LIST_ELEM|unix.NLA_F_NESTED, plan14NFTText(unix.NFTA_EXPR_NAME, name), plan14NFTAttr(unix.NFTA_EXPR_DATA|unix.NLA_F_NESTED, attrs...))
+}
+func plan14NFTMessage(kind, flags uint16, seq uint32, attrs ...[]byte) []byte {
+ payload := []byte{unix.AF_INET, 0, 0, 0}
+ if kind == unix.NFNL_MSG_BATCH_BEGIN || kind == unix.NFNL_MSG_BATCH_END {
+ payload = []byte{unix.AF_UNSPEC, 0, 0, unix.NFNL_SUBSYS_NFTABLES}
+ }
+ for _, a := range attrs {
+ payload = append(payload, a...)
+ }
+ raw := make([]byte, unix.NLMSG_HDRLEN+len(payload))
+ binary.NativeEndian.PutUint32(raw, uint32(len(raw)))
+ binary.NativeEndian.PutUint16(raw[4:], kind)
+ binary.NativeEndian.PutUint16(raw[6:], flags)
+ binary.NativeEndian.PutUint32(raw[8:], seq)
+ copy(raw[unix.NLMSG_HDRLEN:], payload)
+ return raw
+}
+func plan14NFTTransaction(messages ...[]byte) error {
+ fd, err := unix.Socket(unix.AF_NETLINK, unix.SOCK_RAW|unix.SOCK_CLOEXEC, unix.NETLINK_NETFILTER)
+ if err != nil {
+ return err
+ }
+ defer unix.Close(fd)
+ if err := unix.Bind(fd, &unix.SockaddrNetlink{Family: unix.AF_NETLINK}); err != nil {
+ return err
+ }
+ if err := unix.SetsockoptTimeval(fd, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &unix.Timeval{Sec: 5}); err != nil {
+ return err
+ }
+ raw := plan14NFTMessage(unix.NFNL_MSG_BATCH_BEGIN, unix.NLM_F_REQUEST, 1)
+ for _, m := range messages {
+ raw = append(raw, m...)
+ }
+ raw = append(raw, plan14NFTMessage(unix.NFNL_MSG_BATCH_END, unix.NLM_F_REQUEST, uint32(len(messages)+2))...)
+ if err := unix.Sendto(fd, raw, 0, &unix.SockaddrNetlink{Family: unix.AF_NETLINK}); err != nil {
+ return err
+ }
+ acks := map[uint32]bool{}
+ for len(acks) < len(messages) {
+ buf := make([]byte, 65536)
+ n, _, err := unix.Recvfrom(fd, buf, 0)
+ if err != nil {
+ return err
+ }
+ replies, err := syscall.ParseNetlinkMessage(buf[:n])
+ if err != nil {
+ return err
+ }
+ for _, reply := range replies {
+ if reply.Header.Type != unix.NLMSG_ERROR || len(reply.Data) < 4 {
+ return os.ErrInvalid
+ }
+ code := int32(binary.NativeEndian.Uint32(reply.Data))
+ if code != 0 {
+ return fmt.Errorf("netfilter message %d: %w", reply.Header.Seq, syscall.Errno(-code))
+ }
+ acks[reply.Header.Seq] = true
+ }
+ }
+ return nil
+}
+
+func plan14FixtureNAT(t *testing.T, port string) {
+ t.Helper()
+ n, err := strconv.ParseUint(port, 10, 16)
+ if err != nil {
+ t.Fatal(err)
+ }
+ portRaw := []byte{byte(n >> 8), byte(n)}
+ table := fmt.Sprintf("plan14_e58_%d", os.Getpid())
+ flags := uint16(unix.NLM_F_REQUEST | unix.NLM_F_ACK | unix.NLM_F_CREATE)
+ message := func(kind uint16, seq uint32, attrs ...[]byte) []byte {
+ return plan14NFTMessage(unix.NFNL_SUBSYS_NFTABLES<<8|kind, flags, seq, attrs...)
+ }
+ payload := func(base, offset, size uint32) []byte {
+ return plan14NFTExpression("payload", plan14NFTWord(unix.NFTA_PAYLOAD_DREG, unix.NFT_REG_1), plan14NFTWord(unix.NFTA_PAYLOAD_BASE, base), plan14NFTWord(unix.NFTA_PAYLOAD_OFFSET, offset), plan14NFTWord(unix.NFTA_PAYLOAD_LEN, size))
+ }
+ compare := func(value []byte) []byte {
+ return plan14NFTExpression("cmp", plan14NFTWord(unix.NFTA_CMP_SREG, unix.NFT_REG_1), plan14NFTWord(unix.NFTA_CMP_OP, unix.NFT_CMP_EQ), plan14NFTAttr(unix.NFTA_CMP_DATA|unix.NLA_F_NESTED, plan14NFTAttr(unix.NFTA_DATA_VALUE, value)))
+ }
+ expressions := [][]byte{
+ payload(unix.NFT_PAYLOAD_NETWORK_HEADER, 16, 4), compare([]byte{192, 0, 2, 40}),
+ payload(unix.NFT_PAYLOAD_NETWORK_HEADER, 9, 1), compare([]byte{unix.IPPROTO_TCP}),
+ payload(unix.NFT_PAYLOAD_TRANSPORT_HEADER, 2, 2), compare(portRaw),
+ plan14NFTExpression("immediate", plan14NFTWord(unix.NFTA_IMMEDIATE_DREG, unix.NFT_REG_1), plan14NFTAttr(unix.NFTA_IMMEDIATE_DATA|unix.NLA_F_NESTED, plan14NFTAttr(unix.NFTA_DATA_VALUE, []byte{127, 0, 0, 1}))),
+ plan14NFTExpression("nat", plan14NFTWord(unix.NFTA_NAT_TYPE, unix.NFT_NAT_DNAT), plan14NFTWord(unix.NFTA_NAT_FAMILY, unix.AF_INET), plan14NFTWord(unix.NFTA_NAT_REG_ADDR_MIN, unix.NFT_REG_1)),
+ }
+ create := plan14NFTMessage(unix.NFNL_SUBSYS_NFTABLES<<8|unix.NFT_MSG_NEWTABLE, flags|unix.NLM_F_EXCL, 2, plan14NFTText(unix.NFTA_TABLE_NAME, table))
+ chain := message(unix.NFT_MSG_NEWCHAIN, 3, plan14NFTText(unix.NFTA_CHAIN_TABLE, table), plan14NFTText(unix.NFTA_CHAIN_NAME, "output"), plan14NFTText(unix.NFTA_CHAIN_TYPE, "nat"), plan14NFTAttr(unix.NFTA_CHAIN_HOOK|unix.NLA_F_NESTED, plan14NFTWord(unix.NFTA_HOOK_HOOKNUM, unix.NF_INET_LOCAL_OUT), plan14NFTWord(unix.NFTA_HOOK_PRIORITY, 0xffffff9c)))
+ rule := message(unix.NFT_MSG_NEWRULE, 4, plan14NFTText(unix.NFTA_RULE_TABLE, table), plan14NFTText(unix.NFTA_RULE_CHAIN, "output"), plan14NFTAttr(unix.NFTA_RULE_EXPRESSIONS|unix.NLA_F_NESTED, expressions...))
+ if err := plan14NFTTransaction(create, chain, rule); err != nil {
+ t.Fatal("local bucket fixture NAT", err)
+ }
+ t.Cleanup(func() {
+ if err := plan14NFTTransaction(plan14NFTMessage(unix.NFNL_SUBSYS_NFTABLES<<8|unix.NFT_MSG_DELTABLE, unix.NLM_F_REQUEST|unix.NLM_F_ACK, 2, plan14NFTText(unix.NFTA_TABLE_NAME, table))); err != nil {
+ t.Error("fixture NAT cleanup", err)
+ }
+ })
+}
diff --git a/internal/host/plan14_generation_registry_root_linux_test.go b/internal/host/plan14_generation_registry_root_linux_test.go
index ea0389855..1ccfee457 100644
--- a/internal/host/plan14_generation_registry_root_linux_test.go
+++ b/internal/host/plan14_generation_registry_root_linux_test.go
@@ -26,6 +26,12 @@ func TestMain(m *testing.M) {
if len(os.Args) > 1 {
switch os.Args[1] {
case "serve":
+ if os.Getenv("PLAN14_CONSOLE_CONTAINMENT_CHECK") == "1" {
+ if err := plan14CheckConsoleContainment(); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ }
consolecmd.Main()
return
case "plan14-woodstar":
diff --git a/internal/host/plan14_installed_launch_root_linux_test.go b/internal/host/plan14_installed_launch_root_linux_test.go
index 54d18a166..bfbed70b8 100644
--- a/internal/host/plan14_installed_launch_root_linux_test.go
+++ b/internal/host/plan14_installed_launch_root_linux_test.go
@@ -162,6 +162,8 @@ func TestPlan14IndependentControllerLaunchRootRunner(t *testing.T) {
}
func plan14InstalledClaimLaunch(t *testing.T, boundUnit string, runtimeUpdate ...bool) {
+ enabledConsole := strings.HasPrefix(boundUnit, "enabled:")
+ boundUnit = strings.TrimPrefix(boundUnit, "enabled:")
independentController := strings.HasPrefix(boundUnit, "controller:")
boundUnit = strings.TrimPrefix(boundUnit, "controller:")
lostInstalledReply := strings.HasPrefix(boundUnit, "lost-installed:")
@@ -194,6 +196,9 @@ func plan14InstalledClaimLaunch(t *testing.T, boundUnit string, runtimeUpdate ..
tcpMode := boundUnit == "tcp" || boundUnit == "backing-program"
if boundUnit == "tcp" {
boundUnit = "plan14-tcp.service"
+ if enabledConsole {
+ boundUnit = "console.service"
+ }
}
if boundUnit == "socket-program" {
boundUnit = "plan14-socket.service"
@@ -267,6 +272,9 @@ func plan14InstalledClaimLaunch(t *testing.T, boundUnit string, runtimeUpdate ..
program = plan14ConsoleBinaryProgram(t, program)
}
}
+ if enabledConsole {
+ program, checkService = plan14EnabledConsoleProgram(t, program, checkService)
+ }
if woodstar {
if len(plan14WoodstarExecutable) == 0 {
t.Fatal("real Woodstar executable missing")
@@ -516,6 +524,10 @@ func plan14InstalledClaimLaunch(t *testing.T, boundUnit string, runtimeUpdate ..
}
defer bindings.Close()
shape := claimUnitShape{Host: host, Account: old.Account, App: old.App, Kind: "instance", Unit: boundUnit, Socket: provision.NestedSocket(host), DBPath: provision.NestedDBPath("/data", host)}
+ shape.Console = enabledConsole
+ if enabledConsole {
+ plan14ConsoleEnrollment(t, old)
+ }
if strings.HasPrefix(boundUnit, "carlos-app@") {
shape.Backing = "unit"
}
@@ -663,6 +675,9 @@ func plan14InstalledClaimLaunch(t *testing.T, boundUnit string, runtimeUpdate ..
}
if materialized {
plan14BoundOverrideRefusals(t, f.root.Units.(claimUnitDispatch), host, unit, ctl)
+ if enabledConsole {
+ plan14ConsoleEnrollmentRefusals(t, f.root.Units.(claimUnitDispatch), host)
+ }
f.root.boundReady = func(ctx context.Context, host string, started instancectl.ClaimUnitStarted) error {
b, err := f.root.unitBindings.check(ctx, host, nil)
if err != nil {
@@ -820,6 +835,36 @@ func plan14InstalledClaimLaunch(t *testing.T, boundUnit string, runtimeUpdate ..
}
}
response, err := call(t.Context(), f.req)
+ if enabledConsole && os.Getenv("PLAN14_CONTAINED_FAILURE") != "" {
+ output, _ := os.ReadFile(provision.LogSocket(host, "run"))
+ want := "claim recovery"
+ if os.Getenv("PLAN14_CONTAINED_FAILURE") == "enrollment" {
+ want = "enrolled on instance"
+ }
+ if err == nil || response.Grant != nil || !strings.Contains(string(output), want) {
+ t.Fatalf("contained startup did not refuse %s: %v %s", want, err, output)
+ }
+ deadline := time.Now().Add(5 * time.Second)
+ for {
+ gone := f.root.Units.Gone(t.Context(), host, "")
+ if gone == nil {
+ break
+ }
+ if time.Now().After(deadline) {
+ state, _ := ctl(t.Context(), "show", unit, "-p", "ActiveState,SubState,MainPID,ControlPID,InvocationID,ControlGroup")
+ logs, _ := exec.Command("journalctl", "-u", unit, "--no-pager", "-n", "40").CombinedOutput()
+ t.Fatalf("refused startup retained work: %v %s\n%s", gone, state, logs)
+ }
+ time.Sleep(20 * time.Millisecond)
+ }
+ stop := f.req
+ stop.Op = hostbox.RoleLaunchStop
+ stop.Spec, stop.Executable, stop.MemoryMax = nil, "", 0
+ if _, err := call(t.Context(), stop); err != nil {
+ t.Fatal("failed startup did not settle", err)
+ }
+ return
+ }
if lostInstalledReply {
plan14RecoverInstalledReply(t, f, lostReply, err)
err = lock.With(t.Context(), func(ctx context.Context) error {
diff --git a/internal/host/plan14_r10_root_linux_test.go b/internal/host/plan14_r10_root_linux_test.go
new file mode 100644
index 000000000..02cf3ef6f
--- /dev/null
+++ b/internal/host/plan14_r10_root_linux_test.go
@@ -0,0 +1,288 @@
+//go:build linux && rootrunner
+
+package host
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "testing"
+ "time"
+
+ "github.com/carlosframework/platform/internal/hostwrite"
+ "github.com/carlosframework/platform/internal/registry"
+ "github.com/carlosframework/platform/internal/release"
+ "github.com/carlosframework/platform/internal/rootparticipation"
+ "github.com/carlosframework/platform/internal/rootstate"
+)
+
+func TestPlan14NoCensusReleaseRootRunner(t *testing.T) {
+ if _, err := os.Lstat("/var/lib/carlos-release"); !os.IsNotExist(err) {
+ t.Fatal("existing release state", err)
+ }
+ t.Cleanup(func() { os.RemoveAll("/var/lib/carlos-release") })
+ for _, scenario := range []string{"absent", "empty", "held", "aborted"} {
+ t.Run(scenario, func(t *testing.T) {
+ var s rootstate.Store
+ if scenario != "absent" {
+ var err error
+ s, err = rootstate.Open(rootReleasePrivate, true, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer s.Close()
+ }
+ if scenario == "held" || scenario == "aborted" {
+ unlock, err := s.Lock(t.Context(), "release.lock")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if scenario == "held" {
+ defer unlock()
+ } else {
+ unlock()
+ }
+ }
+ before, _ := os.ReadDir(rootReleasePrivate)
+ for _, flag := range []string{"", "1"} {
+ t.Setenv("CARLOS_HOST_CLAIM", flag)
+ plan14NoCensusCommands(t)
+ ctx, cancel := context.WithTimeout(t.Context(), time.Second)
+ called := false
+ err := withClaimRootRelease(ctx, false, func() (rootstate.Store, error) { t.Fatal("opened root release"); return nil, os.ErrPermission }, func() error { called = true; return nil }, func() (rootparticipation.Ledger, bool, error) { return rootparticipation.Ledger{}, false, nil })
+ if err != nil || !called {
+ t.Fatal("adopt", err, called)
+ }
+ for _, recover := range []bool{false, true} {
+ f := newApplyFixture(t, "canary", "v1.0.0")
+ f.a.participation = nil
+ f.a.rootMoves = rootMovesFloorIdentity
+ if err := f.a.runWithRelease(ctx, recover); err != nil {
+ t.Fatal("system", recover, err)
+ }
+ }
+ cancel()
+ }
+ after, _ := os.ReadDir(rootReleasePrivate)
+ if len(after) != len(before) {
+ t.Fatal("new release file", before, after)
+ }
+ if scenario == "absent" {
+ if _, err := os.Lstat(rootReleasePrivate); !os.IsNotExist(err) {
+ t.Fatal("created release directory", err)
+ }
+ }
+ })
+ }
+}
+
+func plan14NoCensusCommands(t *testing.T) {
+ t.Helper()
+ for _, mode := range []string{"adopt", "apply", "recover"} {
+ dir := t.TempDir()
+ ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
+ cmd := exec.CommandContext(ctx, os.Args[0], "-test.run=^TestPlan14NoCensusCommandPayload$")
+ cmd.Env = isolatedAdoptOrderEnv(t, "PLAN14_R10_COMMAND="+mode,
+ "CARLOS_HOST_CLAIM="+os.Getenv("CARLOS_HOST_CLAIM"), "CARLOS_DATA_DIR="+filepath.Join(dir, "data"),
+ "CARLOS_OPT_DIR="+filepath.Join(dir, "opt"), "CARLOS_DEPLOYMENT_BUCKET=", "CARLOS_DEPLOYMENT_DIR="+filepath.Join(dir, "bucket"), "CARLOS_REGION_HA=", "CARLOS_MOVES_PHASE1=")
+ out, err := cmd.CombinedOutput()
+ cancel()
+ if mode == "adopt" {
+ var status *exec.ExitError
+ if !errors.As(err, &status) || status.ExitCode() != 2 || !bytes.Contains(out, []byte("flag provided but not defined: -plan14-no-such-option")) {
+ t.Fatal("adopt did not reach ordinary argument handling", err, string(out))
+ }
+ } else if err != nil {
+ t.Fatal(mode, err, string(out))
+ }
+ }
+}
+
+func TestPlan14NoCensusCommandPayload(t *testing.T) {
+ switch os.Getenv("PLAN14_R10_COMMAND") {
+ case "adopt":
+ RunAdopt([]string{"-plan14-no-such-option"})
+ case "apply":
+ RunSystemApply(nil)
+ case "recover":
+ RunSystemApply([]string{"-recover"})
+ default:
+ t.Fatal("fixture only")
+ }
+}
+
+func TestPlan14PendingAcquisitionStartupRootRunner(t *testing.T) {
+ if err := os.Mkdir("/var/lib/carlos-release", 0700); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() { os.RemoveAll("/var/lib/carlos-release") })
+ for _, mode := range []string{"instance-control", "system"} {
+ t.Run(mode, func(t *testing.T) {
+ t.Setenv("PLAN14_R10_ACQUISITION", mode)
+ runServingParkPeerNamespace(t, false, false, "seed-construction")
+ })
+ }
+}
+
+func TestPlan14AcquisitionCrashPayload(t *testing.T) {
+ if os.Getenv("PLAN14_R10_CRASH") != "1" {
+ t.Fatal("fixture only")
+ }
+ s, err := rootstate.Open(rootparticipation.Namespace, false, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ l := rootparticipation.Ledger{State: s}
+ if err := l.Acquire(t.Context(), func() error { os.Exit(0); return nil }); err != nil {
+ t.Fatal(err)
+ }
+ t.Fatal("did not die at durable acquisition")
+}
+
+func plan14PendingAcquisition(t *testing.T, opt, deployment string) {
+ cmd := exec.Command(os.Args[0], "-test.run=^TestPlan14AcquisitionCrashPayload$")
+ cmd.Env = append(os.Environ(), "PLAN14_R10_CRASH=1")
+ if out, err := cmd.CombinedOutput(); err != nil {
+ t.Fatal(string(out), err)
+ }
+ s, err := rootstate.Open(rootparticipation.Namespace, false, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer s.Close()
+ l := rootparticipation.Ledger{State: s}
+ if _, err := l.Census(); !errors.Is(err, rootparticipation.ErrUnavailable) {
+ t.Fatal("pending acquisition admitted", err)
+ }
+ c, err := l.RecoveryCensus()
+ if err != nil || c.Pending != "acquisition" {
+ t.Fatal(c, err)
+ }
+ t.Setenv("CARLOS_DEPLOYMENT_BUCKET", deployment)
+ id, required, err := rootMovesFloorIdentity()
+ if err != nil || !required {
+ t.Fatal("floor identity dead end", required, err)
+ }
+ t.Setenv("CARLOS_DEPLOYMENT_BUCKET", "")
+ if os.Getenv("PLAN14_R10_ACQUISITION") == "system" {
+ f := newApplyFixture(t, "canary", "v1.0.0")
+ f.a.optDir = opt
+ f.a.dataDir = "/data"
+ f.a.rootMoves = func() (rootFloorIdentity, bool, error) { return id, true, nil }
+ f.a.participation = nil
+ if err := f.a.runWithRelease(t.Context(), true); err != nil {
+ t.Fatal("system recovery entrypoint", err)
+ }
+ if _, err := l.Census(); err != nil {
+ t.Fatal("acquisition not settled", err)
+ }
+ }
+}
+
+func TestPlan14RetiredInstallationStartupRootRunner(t *testing.T) {
+ if err := os.Mkdir("/var/lib/carlos-release", 0700); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() { os.RemoveAll("/var/lib/carlos-release") })
+ for _, mode := range []string{"complete", "interrupted"} {
+ t.Run(mode, func(t *testing.T) {
+ t.Setenv("PLAN14_R10_RETIRED", mode)
+ runServingParkPeerNamespace(t, false, false, "seed-construction")
+ })
+ }
+}
+
+func TestPlan14RetiredMigrationPayloadRootRunner(t *testing.T) {
+ testRootMigrationBootstrap(t, true, false)
+}
+
+func TestRootRebootPlan14ReviewBatchPhase1(t *testing.T) { plan14ReviewRebootBatch(t, "1") }
+func TestRootRebootPlan14ReviewBatchPhase2(t *testing.T) { plan14ReviewRebootBatch(t, "2") }
+func plan14ReviewRebootBatch(t *testing.T, phase string) {
+ if phase == "1" {
+ cmd := exec.CommandContext(t.Context(), os.Args[0], "-test.run=^(TestPlan14LocalBackingStateRootRunner|TestPlan14NoCensusReleaseRootRunner|TestPlan14InitialMigrationExclusionRootRunner|TestPlan14RetiredInstallationStartupRootRunner|TestPlan14RetiredMigrationPayloadRootRunner|TestPlan14PendingInventoryStartupRootRunner|TestPlan14RootMigrationBootstrapRootRunner|TestPlan14RootMigrationHistoricRootRunner|TestPlan14RootMigrationFreshStopRootRunner|TestPlan14RootRetryHardenedController|TestPlan14RootFailureProductionOpener)$", "-test.v", "-test.timeout=5m")
+ out, err := cmd.CombinedOutput()
+ t.Log(string(out))
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+ plan14RebootGroups(t, phase, []string{"(InventoryIdentity|Plan14Migration|PersistedFamilies|ClaimUnitMigration|GenerationPinIdentity|GenerationDevices|ClaimUnitDevice|FloorPrevAuthorization|ServingParkChangedMount)"})
+}
+
+func TestRootRebootPlan14AcceptanceSeedBatchPhase1(t *testing.T) {
+ cmd := exec.CommandContext(t.Context(), os.Args[0], "-test.run=^(TestPlan14NoCensusReleaseRootRunner|TestPlan14InitialMigrationExclusionRootRunner)$", "-test.v", "-test.timeout=2m")
+ out, err := cmd.CombinedOutput()
+ t.Log(string(out))
+ if err != nil {
+ t.Fatal(err)
+ }
+ plan14RebootGroups(t, "1", []string{"ServingParkAcceptance", "FilesSeedTwoHost"})
+}
+func TestRootRebootPlan14AcceptanceSeedBatchPhase2(t *testing.T) {
+ plan14RebootGroups(t, "2", []string{"ServingParkAcceptance", "FilesSeedTwoHost"})
+}
+func plan14RebootGroups(t *testing.T, phase string, groups []string) {
+ for _, group := range groups {
+ cmd := exec.CommandContext(t.Context(), os.Args[0], "-test.run=^TestRootReboot"+group+"Phase"+phase+"$", "-test.v", "-test.timeout=8m")
+ out, err := cmd.CombinedOutput()
+ t.Log(string(out))
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+}
+
+func TestPlan14LocalBackingStateRootRunner(t *testing.T) {
+ if err := os.Mkdir("/var/lib/private", 0700); err == nil {
+ t.Cleanup(func() { os.Remove("/var/lib/private") })
+ } else if !os.IsExist(err) {
+ t.Fatal(err)
+ }
+ for _, dir := range []string{"/var/lib/carlos-app", "/var/lib/private/carlos-app"} {
+ t.Run(dir, func(t *testing.T) {
+ if err := os.Mkdir(dir, 0755); err != nil {
+ t.Fatal("existing backing state", err)
+ }
+ t.Cleanup(func() { os.RemoveAll(dir) })
+ if err := os.Mkdir(filepath.Join(dir, "hidden.example"), 0700); err != nil {
+ t.Fatal(err)
+ }
+ reg := restartTestRegistry(t)
+ for _, attack := range []string{"omitted", "cleared"} {
+ if attack == "cleared" {
+ if err := reg.Upsert(registry.Route{Host: "hidden.example", Account: "bab", App: "app", Socket: "/run/hidden.sock"}); err != nil {
+ t.Fatal(err)
+ }
+ }
+ p := &activationPreflightOwner{}
+ scratch := t.TempDir()
+ r := ClaimRuntimeResources{Mode: "instance-control", Registry: reg, DataDir: scratch, OptDir: scratch, UnitsDir: scratch}
+ store := release.NewDirStore(t.TempDir())
+ if _, err := OpenProductionClaimAssembly(t.Context(), store, r, p); !errors.Is(err, hostwrite.ErrUnavailable) || p.calls != 0 {
+ t.Fatal("local backing omitted", attack, err, p.calls)
+ }
+ if err := migrateClaimJournalsWith(t.Context(), store, r, p, true); !errors.Is(err, hostwrite.ErrUnavailable) || p.calls != 0 {
+ t.Fatal("migration backing omitted", attack, err, p.calls)
+ }
+ }
+ })
+ }
+}
+
+func TestRootRebootPlan14FinalSuitePhase1(t *testing.T) {
+ cmd := exec.CommandContext(t.Context(), os.Args[0], "-test.run="+"^(TestClaimRestoreOwnWriteMaterializes|TestClaimRestoreWriteOnlyRetagsPair|TestFilesSeedCLIStopFixture|TestFilesSeedExecutorLifetime|TestFilesSeedLiveFixtureShape|TestPlan14ActivationNamedUnitPreflight|TestPlan14BootLocalRecoveryRootRunner|TestPlan14BoundNetworkEdgeRootRunner|TestPlan14BoundRouteProgramRootRunner|TestPlan14BoundStartupRetirementRootRunner|TestPlan14CombinedBoundRetirementRootRunner|TestPlan14ConsoleLibraryRootRunner|TestPlan14ExecutionHostileCensusRootRunner|TestPlan14FloorRiseWithoutPlatformUpdateRootRunner|TestPlan14GenerationBackendInstalledRootRunner|TestPlan14GenerationContainedRunningRootRunner|TestPlan14GenerationDescriptorTransferRootRunner|TestPlan14GenerationExactRetirementRootRunner|TestPlan14GenerationInstalledUnitRootRunner|TestPlan14GenerationInventoryCheckedRootReader|TestPlan14GenerationKernelCensusRootRunner|TestPlan14GenerationKernelNamespaceMountRootRunner|TestPlan14GenerationKernelRetirementRootRunner|TestPlan14GenerationParkedRootRunner|TestPlan14GenerationPayloadPhysicalRootRunner|TestPlan14GenerationPrivateJournalRootRunner|TestPlan14GenerationRelocationUIDRootRunner|TestPlan14GenerationRequiredArtifactsRootRunner|TestPlan14GenerationRunningTenantPinRootRunner|TestPlan14GuardExecEdgeRootRunner|TestPlan14GuardRecoveryLegacyPolicyRootRunner|TestPlan14InitialMigrationExclusionRootRunner|TestPlan14InstalledClaimLaunchRootRunner|TestPlan14LaunchVerifierRootRunner|TestPlan14LocalBackingStateRootRunner|TestPlan14MaterializedBoundRouteRootRunner|TestPlan14MigratedBoundRouteRootRunner|TestPlan14NamedClaimLaunchRootRunner|TestPlan14NamedStartupDefersReplacementRootRunner|TestPlan14NoCensusReleaseRootRunner|TestPlan14OrdinaryUnitAuthorizationRootRunner|TestPlan14PendingAcquisitionStartupRootRunner|TestPlan14PendingInventoryStartupRootRunner|TestPlan14RealConsoleRootRunner|TestPlan14RealWoodstarRootRunner|TestPlan14RebootStopDescriptorIdentity|TestPlan14RepairExecutorEndToEndRootRunner|TestPlan14RetiredInstallationStartupRootRunner|TestPlan14RetiredMigrationPayloadRootRunner|TestPlan14RootCapabilitiesBothPaths|TestPlan14RootFailureProductionOpener|TestPlan14RootFloorAdoptRefreshRootRunner|TestPlan14RootFloorBootSelectors|TestPlan14RootFloorCrashOrderingRootRunner|TestPlan14RootFloorHealthyAdoptRootRunner|TestPlan14RootFloorRecoveryRollbackRootRunner|TestPlan14RootFloorSignedTargetRootRunner|TestPlan14RootFloorSyscallCrashOrdering|TestPlan14RootFloorUnknownCrashRecoveryRootRunner|TestPlan14RootInventoryPersistentProducer|TestPlan14RootInventoryRejectsOldBootMaterialization|TestPlan14RootJournalNamespaceRootRunner|TestPlan14RootMigrationBootstrapRootRunner|TestPlan14RootMigrationFreshStopRootRunner|TestPlan14RootMigrationHistoricRootRunner|TestPlan14RootMigrationLedgerExclusionRootRunner|TestPlan14RootMigrationMaskRootRunner|TestPlan14RootMigrationRootRunner|TestPlan14RootMigrationTimerAdoptionRootRunner|TestPlan14RootMigrationUnarmedRootRunner|TestPlan14RootRetryHardenedController|TestPlan14RootRetryProjectionDelivery|TestPlan14RootRuntimeInstalledCompletion|TestPlan14RootRuntimeInstalledFailure|TestPlan14RootSeedsFloorRootRunner|TestPlan14RootSeedsFloorSyscallCrashOrdering|TestPlan14SwapFailureHostileRootRunner|TestPlan14SwapFailureNoCensusRootRunner|TestPlan14TimerIntentionRootRunner|TestPlan14TimerRetirementRootRunner|TestPlan14UnitBindingRootRunner|TestRootFilesSeedCombinedRecoveryAllowsUpdate|TestRootFilesSeedDelegatedController|TestRootFilesSeedDelegatedOneShot|TestRootFilesSeedInstanceControlConstruction|TestRootFilesSeedIsolatedTenant|TestRootFilesSeedIsolationNegativeControl|TestRootFilesSeedKilledControllerRerun|TestRootFilesSeedLiveTransport|TestRootFilesSeedRealOriginPreparation|TestRootFilesSeedRealOriginTenant|TestRootFilesSeedSocketCompleteExecutor|TestRootFilesSeedSocketDisconnectExecutor|TestRootFilesSeedSocketShutdownExecutor|TestRootFilesSeedTwoHostIsolation|TestRootFilesSeedWholeExecutorKilledRerun|TestRootServingHandoverDirectoryProof|TestRootServingNamespaceStartup|TestRootServingNamespaceUnrecordedStartup|TestRootServingParkAuthenticatedHandover|TestRootServingParkAuthenticatedPeer|TestRootServingParkColdHandoverBoundary|TestRootServingParkCollection|TestRootServingParkDisplacedRoot|TestRootServingParkInstalledUnit|TestRootServingParkNestedBindRestore|TestRootServingParkQuarantine|TestRootServingParkRetainedDoesNotBlockRetirement|TestRootServingParkSnapshotClone|TestRootServingParkStoppedWriterHandover|TestRootServingParkUnrecordedReplacement)$", "-test.v", "-test.timeout=25m")
+ out, err := cmd.CombinedOutput()
+ t.Log(string(out))
+ if err != nil {
+ t.Fatal(err)
+ }
+ plan14RebootGroups(t, "1", []string{"FilesSeedTwoHostRenumber"})
+}
+
+func TestRootRebootPlan14FinalSuitePhase2(t *testing.T) {
+ plan14RebootGroups(t, "2", []string{"FilesSeedTwoHostRenumber"})
+}
diff --git a/internal/host/plan14_r9_root_linux_test.go b/internal/host/plan14_r9_root_linux_test.go
index 33fa26bc3..ab152f31e 100644
--- a/internal/host/plan14_r9_root_linux_test.go
+++ b/internal/host/plan14_r9_root_linux_test.go
@@ -75,25 +75,40 @@ func plan14InitialMigrationExclusion(t *testing.T, store release.Store, r ClaimR
old := rootMigrationQuiesce
defer func() { rootMigrationQuiesce = old }()
rootMigrationQuiesce = func(context.Context) (func(), error) {
- for _, path := range []string{"adopt", "update"} {
- ctx, cancel := context.WithTimeout(t.Context(), 40*time.Millisecond)
- ran := false
- var err error
- if path == "adopt" {
- err = withClaimRootRelease(ctx, false, func() (rootstate.Store, error) { return rootstate.Open(rootReleasePrivate, true, false) }, func() error { ran = true; return nil }, func() (rootparticipation.Ledger, bool, error) { return rootparticipation.Ledger{}, false, nil })
- } else {
- err = withExistingRootRelease(ctx, func() error { ran = true; return nil })
- }
- cancel()
- if err == nil || ran {
- t.Fatal("initial migration did not exclude", path, err)
- }
+ if err := verifyOfflineRootMigration(t.Context()); err != nil {
+ t.Fatal(err)
}
return nil, os.ErrPermission
}
if err := migrateRootClaimAuthority(t.Context(), store, r, owners, actor, journals, launches); !errors.Is(err, os.ErrPermission) {
t.Fatal("exclusion witness", err)
}
+ before, err := os.ReadDir(rootReleasePrivate)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, flag := range []string{"", "1"} {
+ t.Setenv("CARLOS_HOST_CLAIM", flag)
+ plan14NoCensusCommands(t)
+ if err := withClaimRootRelease(t.Context(), false, func() (rootstate.Store, error) {
+ t.Fatal("release opened after aborted migration")
+ return nil, os.ErrPermission
+ }, func() error { return nil }); err != nil {
+ t.Fatal(err)
+ }
+ for _, recover := range []bool{false, true} {
+ f := newApplyFixture(t, "canary", "v1.0.0")
+ f.a.participation = nil
+ f.a.rootMoves = rootMovesFloorIdentity
+ if err := f.a.runWithRelease(t.Context(), recover); err != nil {
+ t.Fatal("legacy apply after aborted migration", recover, err)
+ }
+ }
+ }
+ after, err := os.ReadDir(rootReleasePrivate)
+ if err != nil || len(after) != len(before) {
+ t.Fatal("new release state after abort", err, before, after)
+ }
}
func TestPlan14RepairExecutorEndToEndRootRunner(t *testing.T) {
@@ -202,6 +217,10 @@ func TestPlan14RepairExecutorEndToEndRootRunner(t *testing.T) {
}
func TestPlan14PendingInventoryStartupRootRunner(t *testing.T) {
+ if err := os.Mkdir("/var/lib/carlos-release", 0700); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() { os.RemoveAll("/var/lib/carlos-release") })
for _, mode := range []string{"instance-control", "system"} {
t.Run(mode, func(t *testing.T) {
t.Setenv("PLAN14_R9_PENDING", mode)
@@ -242,7 +261,11 @@ func filesSeedPendingConstruction(t *testing.T, store release.Store, resources C
t.Fatal(err)
}
t.Cleanup(func() { unix.Unmount("/etc/systemd/system", 0) })
- f := newRecoveryFixture(t)
+ host := os.Getenv("PLAN14_R10_INSTALL_HOST")
+ if host == "" {
+ host = recoveryHost
+ }
+ f := newRecoveryFixture(t, host)
obs := f.read()
obs.Claim.Op.Exec = actor.Box
obs.Claim.Op.Participants = []string{actor.Box}
@@ -297,23 +320,70 @@ func filesSeedPendingConstruction(t *testing.T, store release.Store, resources C
if err != nil {
t.Fatal(err)
}
+ if os.Getenv("PLAN14_R10_RETIRED") != "" {
+ if err := checkClaimJournalMigration(t.Context(), journals, filepath.Join("/data", ".hostclaims")); err != nil {
+ t.Fatal("journal startup before installation", err)
+ }
+ }
state, err := openClaimRootAuthority()
if err != nil {
t.Fatal(err)
}
defer state.Close()
- boundary := &r9PendingBoundary{Store: state}
+ boundary := &r9PendingBoundary{Store: state, failed: os.Getenv("PLAN14_R10_RETIRED") != ""}
authority := hostguard.NewRootAuthority(boundary)
roots := map[string]string{"data": "/data", "opt": resources.OptDir, "units": "/etc/systemd/system"}
guard := hostguard.Guard{Root: authority, DataDir: "/data", Claims: hostguard.StoreReader{Store: store}}
census := &hostbox.EmptyInstallationCensus{Inventory: journals.Inventory, Registry: reg, Launches: journal, Box: actor.Box, Enrollment: actor.Enrollment, DirectoryRevision: "1", Roots: roots, SharedPaths: claimGlobalSharedPaths(resources)}
- root := ClaimRootHTTP{Journals: journals, Launches: journal, Roots: roots, InventoryActor: func(context.Context) (hostbox.Actor, error) { return actor, nil }, Reconcile: hostbox.ReconcileAdapter{Registry: reg, Inventory: journals.Inventory, Census: census, Box: actor.Box, Enrollment: actor.Enrollment}, Stop: ClaimStopHTTP{Coordinator: ClaimStopCoordinator{Guard: guard, Evidence: store}}}
+ root := ClaimRootHTTP{history: productionCreationHistory, Journals: journals, Launches: journal, Roots: roots, InventoryActor: func(context.Context) (hostbox.Actor, error) { return actor, nil }, Reconcile: hostbox.ReconcileAdapter{Registry: reg, Inventory: journals.Inventory, Census: census, Box: actor.Box, Enrollment: actor.Enrollment}, Stop: ClaimStopHTTP{Coordinator: ClaimStopCoordinator{Guard: guard, Evidence: store}}}
_, client := hostbox.RemoteJournals(func(ctx context.Context, q hostbox.JournalRequest) (hostbox.JournalResponse, error) {
out, err := root.commitRootInventory(ctx, q)
t.Logf("pending commit: %v", err)
return out, err
})
_, err = client.WriteExpected(t.Context(), hostbox.StopInventory{Complete: true, Host: obs.Host, Incarnation: h.Incarnation, InstallationStamp: h.Stamp, Box: actor.Box, Enrollment: actor.Enrollment, DirectoryRevision: "1"}, hostbox.InventoryExpectation{RecordKey: h.Key, RecordETag: etag, Target: hostguard.FenceTarget{Host: obs.Host, Incarnation: h.Incarnation, Stamp: h.Stamp, Session: h.Session, Box: actor.Box, Enrollment: actor.Enrollment, ClaimETag: obs.ETag, Op: obs.Claim.Op.ID, Phase: obs.Claim.Op.Phase}})
+ if os.Getenv("PLAN14_R10_RETIRED") != "" {
+ if err != nil {
+ t.Fatal("complete installation", err)
+ }
+ if obs.Host == "sibling.example" {
+ if err := guard.With(t.Context(), obs.Host, hostguard.Captured{Incarnation: h.Incarnation, Stamp: h.Stamp}, func(*hostclaim.Claim) error { return nil }, func() error { return nil }); err != nil {
+ t.Fatal("sibling admission", err)
+ }
+ return
+ }
+ want := hostguard.Captured{Incarnation: h.Incarnation, Stamp: h.Stamp}
+ in, err := authority.Installation(t.Context(), obs.Host, want)
+ if err != nil {
+ t.Fatal(err)
+ }
+ interrupted := os.Getenv("PLAN14_R10_RETIRED") == "interrupted"
+ err = guard.RetireInstallationWith(t.Context(), obs.Host, want, func(*hostclaim.Claim) error { return nil }, func() error {
+ if interrupted {
+ return os.ErrPermission
+ }
+ return nil
+ })
+ if interrupted != errors.Is(err, os.ErrPermission) {
+ t.Fatal("retirement boundary", err)
+ }
+ state.Close()
+ reopened, err := openClaimRootAuthority()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer reopened.Close()
+ authority = hostguard.NewRootAuthority(reopened)
+ retained, err := authority.Installation(t.Context(), obs.Host, want)
+ if err != nil || retained != in {
+ t.Fatal("retired identity lost", retained, in, err)
+ }
+ guard.Root = authority
+ if err := guard.With(t.Context(), obs.Host, want, func(*hostclaim.Claim) error { return nil }, func() error { t.Fatal("retired admitted"); return nil }); err == nil {
+ t.Fatal("retired admitted")
+ }
+ return
+ }
if err == nil || !boundary.failed {
t.Fatal("pending boundary not reached", err)
}
diff --git a/internal/host/rootmigration_bootstrap_root_linux_test.go b/internal/host/rootmigration_bootstrap_root_linux_test.go
index 733d5d84d..a13c292a0 100644
--- a/internal/host/rootmigration_bootstrap_root_linux_test.go
+++ b/internal/host/rootmigration_bootstrap_root_linux_test.go
@@ -8,6 +8,7 @@ import (
"crypto/ed25519"
"encoding/base64"
"encoding/json"
+ "errors"
"os"
"os/exec"
"path/filepath"
@@ -39,6 +40,13 @@ func testRootMigrationBootstrap(t *testing.T, historic, fresh bool) {
if !historic {
generationRunnerRegistryUser(t)
}
+ for _, name := range rootMigrationOfflineUnits {
+ path := filepath.Join("/run/systemd/system", name)
+ if err := os.Symlink("/dev/null", path); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() { os.Remove(path); exec.Command("systemctl", "daemon-reload").Run() })
+ }
unit := "/usr/lib/systemd/system/carlos-edge.service"
for _, p := range []string{unit, "/etc/systemd/system/carlos-edge.service", "/run/systemd/system/carlos-edge.service"} {
if _, err := os.Lstat(p); !os.IsNotExist(err) {
@@ -60,6 +68,21 @@ func testRootMigrationBootstrap(t *testing.T, historic, fresh bool) {
t.Fatal(err, string(out))
}
+ if err := verifyOfflineRootMigration(t.Context()); err != nil {
+ t.Fatal("offline root units", err)
+ }
+ properties := map[string]string{}
+ for _, name := range rootMigrationOfflineUnits {
+ out, err := claimSystemctl(t.Context(), "show", name, "--property=LoadState,ActiveState", "--no-pager")
+ if err != nil {
+ t.Fatal(err)
+ }
+ properties[name] = string(out)
+ if out, err := exec.Command("systemctl", "start", name).CombinedOutput(); err == nil || !bytes.Contains(out, []byte("masked")) {
+ t.Fatal(name, string(out), err)
+ }
+ }
+ savedProperties, _ := json.Marshal(properties)
oldOwner := claimQuiesceOwner
claimQuiesceOwner = func() (int, int, error) { return 65534, 65534, nil }
defer func() { claimQuiesceOwner = oldOwner }()
@@ -77,11 +100,11 @@ func testRootMigrationBootstrap(t *testing.T, historic, fresh bool) {
if fresh {
name = "TestPlan14RootMigrationFreshStopRootRunner"
}
- if t.Name() == "TestRootRebootPlan14MigrationInstalledPhase1" || t.Name() == "TestPlan14InitialMigrationExclusionRootRunner" {
+ if t.Name() == "TestRootRebootPlan14MigrationInstalledPhase1" || t.Name() == "TestPlan14InitialMigrationExclusionRootRunner" || t.Name() == "TestPlan14RetiredMigrationPayloadRootRunner" {
name = t.Name()
}
cmd := exec.CommandContext(t.Context(), "unshare", "--mount", "--pid", "--fork", "--mount-proc", os.Args[0], "-test.run=^"+name+"$", "-test.v")
- cmd.Env = append(os.Environ(), "PLAN14_P3_BOOTSTRAP_CHILD=1")
+ cmd.Env = append(os.Environ(), "PLAN14_P3_BOOTSTRAP_CHILD=1", "PLAN14_OFFLINE_UNITS="+string(savedProperties))
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatal(err, string(out))
} else {
@@ -97,6 +120,13 @@ func testRootMigrationBootstrap(t *testing.T, historic, fresh bool) {
t.Fatal(err)
}
}
+ claimSystemctl = func(ctx context.Context, args ...string) ([]byte, error) {
+ var properties map[string]string
+ if json.Unmarshal([]byte(os.Getenv("PLAN14_OFFLINE_UNITS")), &properties) != nil || len(args) < 2 || args[0] != "show" || properties[args[1]] == "" {
+ return nil, os.ErrPermission
+ }
+ return []byte(properties[args[1]]), nil
+ }
rootMigrationQuiesce = func(context.Context) (func(), error) { return func() {}, nil }
for _, path := range []string{"/var/lib/carlos-claims", "/var/lib/carlos-release"} {
if err := os.Mkdir(path, 0700); err != nil {
@@ -341,6 +371,32 @@ func testRootMigrationBootstrap(t *testing.T, historic, fresh bool) {
if err := g.With(t.Context(), target.Host, captured, allow, launch); err != nil || effects != 1 {
t.Fatal("successor writer", err, effects)
}
+ if t.Name() == "TestPlan14RetiredMigrationPayloadRootRunner" {
+ if err := g.RetireInstallationWith(t.Context(), target.Host, captured, allow, func() error { return os.ErrPermission }); !errors.Is(err, os.ErrPermission) {
+ t.Fatal("retirement interrupt", err)
+ }
+ fences.Close()
+ if err := verifyRootMigrationStartup(t.Context(), id, service.Inventory); err != nil {
+ t.Fatal("retired migration startup", err)
+ }
+ read := func(ctx context.Context) (generationInventory, error) {
+ return generationInventoryAt(ctx, rootparticipation.Namespace, "/var/lib/carlos-claims/authority/fences", "/var/lib/carlos-claims/journals.db")
+ }
+ snapshot, err := read(t.Context())
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(snapshot.Installations) != 1 || snapshot.Installations[0].Installation.Stamp != captured.Stamp || len(snapshot.Payloads) == 0 || len(snapshot.Payloads[0].Inventory.Entries) == 0 {
+ t.Fatal("retired payload lost", snapshot)
+ }
+ verifier := generationReconciler{read: read, kernel: productionGenerationKernelCensus}
+ if _, err := verifier.reconcile(t.Context(), nil); err != nil {
+ t.Fatal("retired generation verification", err)
+ }
+ if _, err := os.Stat(filepath.Join(r.DataDir, "historic-data")); err != nil {
+ t.Fatal("payload removed", err)
+ }
+ }
if t.Name() == "TestRootRebootPlan14MigrationInstalledPhase1" || t.Name() == "TestPlan14InitialMigrationExclusionRootRunner" {
if err := journal.Close(); err != nil {
t.Fatal(err)
diff --git a/internal/host/rootmigration_production_linux.go b/internal/host/rootmigration_production_linux.go
index 39152adcb..3dc7e87d4 100644
--- a/internal/host/rootmigration_production_linux.go
+++ b/internal/host/rootmigration_production_linux.go
@@ -32,7 +32,7 @@ func migrateRootClaimAuthority(ctx context.Context, store release.Store, r Claim
if err := claimActivationVerified(ctx, store, r); err != nil {
return err
}
- if err := quiesceLegacyRootMutators(ctx); err != nil {
+ if err := verifyOfflineRootMigration(ctx); err != nil {
return err
}
state, err := rootstate.Open(rootMigrationDir, true, false)
@@ -279,3 +279,19 @@ func quiesceLegacyRootMutators(ctx context.Context) error {
}
return nil
}
+
+var rootMigrationOfflineUnits = []string{"carlos-adopt.service", "carlos-stage.service", "carlos-config-sync.service", "carlos-restart.service", "carlos-system-update.service", "carlos-system-recover.service"}
+
+func verifyOfflineRootMigration(ctx context.Context) error {
+ for _, unit := range rootMigrationOfflineUnits {
+ raw, err := claimSystemctl(ctx, "show", unit, "--property=LoadState,ActiveState", "--no-pager")
+ if err != nil {
+ return err
+ }
+ props := string(raw)
+ if !strings.Contains(props, "LoadState=masked\n") || !(strings.Contains(props, "ActiveState=inactive\n") || strings.Contains(props, "ActiveState=failed\n")) {
+ return errRootMigrationBusy
+ }
+ }
+ return quiesceLegacyRootMutators(ctx)
+}
diff --git a/internal/host/rootmigration_startup.go b/internal/host/rootmigration_startup.go
index 63f4514be..4f5413261 100644
--- a/internal/host/rootmigration_startup.go
+++ b/internal/host/rootmigration_startup.go
@@ -79,7 +79,7 @@ func verifyRootMigrationStartup(ctx context.Context, id rootFloorIdentity, inven
if err != nil || !required {
return err
}
- c, err := l.Census()
+ c, err := l.RecoveryCensus()
if err != nil {
return err
}
diff --git a/internal/host/rootmovesfloor_source.go b/internal/host/rootmovesfloor_source.go
index 2fcff9fd1..2f12845f0 100644
--- a/internal/host/rootmovesfloor_source.go
+++ b/internal/host/rootmovesfloor_source.go
@@ -22,7 +22,7 @@ func rootMovesFloorIdentity() (rootFloorIdentity, bool, error) {
if err != nil || !required {
return rootFloorIdentity{}, required, err
}
- c, err := l.Census()
+ c, err := l.RecoveryCensus()
id := rootFloorIdentity{Deployment: os.Getenv("CARLOS_DEPLOYMENT_BUCKET"), Box: c.Box, Enrollment: c.Enrollment}
if err == nil && id.Deployment == "" {
err = errRootFloorHistory
diff --git a/internal/host/rootreleaselock.go b/internal/host/rootreleaselock.go
index 39ff11cc3..32a072037 100644
--- a/internal/host/rootreleaselock.go
+++ b/internal/host/rootreleaselock.go
@@ -2,7 +2,6 @@ package host
import (
"context"
- "errors"
"github.com/carlosframework/platform/internal/rootparticipation"
"os"
@@ -37,7 +36,7 @@ func withClaimReleaseCensus(ctx context.Context, census func() (rootparticipatio
return err
}
if !required {
- return withExistingRootRelease(ctx, effect)
+ return effect()
}
return withRootReleaseStore(ctx, open, effect)
}
@@ -55,20 +54,3 @@ type adoptRootSource struct {
OpenRelease func() (rootstate.Store, error)
Census func() (rootparticipation.Ledger, bool, error)
}
-
-func withExistingRootRelease(ctx context.Context, effect func() error) error {
- if _, err := os.Lstat(rootReleasePrivate); errors.Is(err, os.ErrNotExist) {
- return effect()
- } else if err != nil {
- return err
- }
- s, err := rootstate.Open(rootReleasePrivate, false, false)
- if errors.Is(err, os.ErrNotExist) {
- return effect()
- }
- if err != nil {
- return err
- }
- defer s.Close()
- return rootstate.WithRelease(ctx, s, effect)
-}
diff --git a/internal/host/systemapply.go b/internal/host/systemapply.go
index 2865e0a3c..9a959dadf 100644
--- a/internal/host/systemapply.go
+++ b/internal/host/systemapply.go
@@ -135,7 +135,7 @@ func (a *systemApplier) runWithRelease(ctx context.Context, recover bool) error
}
return a.participationDisk().WithParticipation(ctx, func(ctx context.Context, _ rootparticipation.Ledger) error {
return a.Run(ctx, recover)
- }, func() error { return withExistingRootRelease(ctx, func() error { return a.Run(ctx, recover) }) })
+ }, func() error { return a.Run(ctx, recover) })
}
// openSystemStore is the production openStore: the deploy store and the
diff --git a/internal/host/systemparticipation.go b/internal/host/systemparticipation.go
index cbf244a95..268a66731 100644
--- a/internal/host/systemparticipation.go
+++ b/internal/host/systemparticipation.go
@@ -201,7 +201,14 @@ func (a *systemApplier) checkUnarmedParticipationRecovery(ctx context.Context) e
}
recover, reserve := false, false
err = a.participationDisk().WithParticipation(ctx, func(ctx context.Context, l rootparticipation.Ledger) error {
- _, err := l.UpdateID()
+ c, err := l.RecoveryCensus()
+ if err != nil {
+ return err
+ }
+ if c.Pending == "acquisition" {
+ recover = true
+ }
+ _, err = l.UpdateID()
if errors.Is(err, os.ErrNotExist) {
if j == nil {
return nil
diff --git a/internal/hostguard/generationinventory.go b/internal/hostguard/generationinventory.go
index 5298344b0..e0c6c527b 100644
--- a/internal/hostguard/generationinventory.go
+++ b/internal/hostguard/generationinventory.go
@@ -51,7 +51,7 @@ func (a *RootAuthority) generationInstallation(ctx context.Context, host string)
if err != nil {
return FenceView{}, err
}
- view, err := a.viewLocked(host, Captured{Incarnation: r.Incarnation, Stamp: r.ActiveStamp})
+ view, err := a.viewLocked(host, Captured{Incarnation: r.Incarnation, Stamp: r.installation.Stamp})
if err != nil {
return FenceView{}, err
}
diff --git a/internal/hostguard/retired_inventory_test.go b/internal/hostguard/retired_inventory_test.go
new file mode 100644
index 000000000..6af88d7f9
--- /dev/null
+++ b/internal/hostguard/retired_inventory_test.go
@@ -0,0 +1,60 @@
+//go:build unix
+
+package hostguard
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "os"
+ "testing"
+)
+
+func TestPlan14RetiredInstallationEnumeration(t *testing.T) {
+ for _, oldFormat := range []bool{false, true} {
+ t.Run(map[bool]string{false: "retained", true: "old-cleared-stamp"}[oldFormat], func(t *testing.T) {
+ f, target, captured := fenceFixture(t)
+ store := &rootTestStore{dir: t.TempDir()}
+ f.g.Root = NewRootAuthority(store)
+ f.obs.Claim.Holder.Account, f.obs.Claim.Holder.App = "bab", "app"
+ in := RootInstallation{Host: target.Host, Incarnation: captured.Incarnation, Stamp: captured.Stamp, Account: "bab", App: "app", Record: "record", Box: target.Box, Enrollment: target.Enrollment, Migration: 1, InventoryRevision: 7, Artifacts: InstallationArtifacts{Schema: 1, StateDirectory: true, Timers: "kept"}}
+ if err := f.g.EstablishRoot(t.Context(), in, allow); err != nil {
+ t.Fatal(err)
+ }
+ if err := f.g.RetireInstallationWith(t.Context(), target.Host, captured, allow, func() error { return os.ErrPermission }); !errors.Is(err, os.ErrPermission) {
+ t.Fatal(err)
+ }
+ if oldFormat {
+ raw, err := store.Read(target.Host + ".json")
+ if err != nil {
+ t.Fatal(err)
+ }
+ var r rootRecord
+ if err := json.Unmarshal(raw, &r); err != nil {
+ t.Fatal(err)
+ }
+ r.Installation.Stamp = 0
+ raw, _ = json.Marshal(r)
+ if err := store.Write(target.Host+".json", raw); err != nil {
+ t.Fatal(err)
+ }
+ }
+ f.g.Root = NewRootAuthority(&rootTestStore{dir: store.dir})
+ for _, enumerate := range []func(context.Context) ([]FenceView, error){f.g.Root.MigrationInstallations, f.g.Root.GenerationInstallations} {
+ views, err := enumerate(t.Context())
+ if err != nil || len(views) != 1 || views[0].Installation != in {
+ t.Fatal("retired inventory lost", views, err)
+ }
+ if err := views[0].admit(target.Host, captured, target.Session); err == nil {
+ t.Fatal("retired view admitted")
+ }
+ }
+ if err := f.g.With(t.Context(), target.Host, captured, allow, func() error { t.Fatal("retired execution"); return nil }); err == nil {
+ t.Fatal("retired execution admitted")
+ }
+ if err := f.g.RetireInstallationWith(t.Context(), target.Host, captured, allow, func() error { return nil }); err != nil {
+ t.Fatal("cleanup retry", err)
+ }
+ })
+ }
+}
diff --git a/internal/hostguard/root.go b/internal/hostguard/root.go
index 116049f71..ac080d6a4 100644
--- a/internal/hostguard/root.go
+++ b/internal/hostguard/root.go
@@ -65,10 +65,26 @@ func (a *RootAuthority) read(host string) (record, error) {
var saved rootRecord
dec := json.NewDecoder(bytes.NewReader(b))
dec.DisallowUnknownFields()
- if dec.Decode(&saved) != nil || dec.Decode(new(any)) != io.EOF || saved.Schema != 1 || saved.Installation.Host != host || saved.Installation.Incarnation != saved.State.Incarnation || saved.Installation.Stamp != saved.State.ActiveStamp || saved.Installation.Migration == 0 {
+ if dec.Decode(&saved) != nil || dec.Decode(new(any)) != io.EOF || saved.Schema != 1 || saved.Installation.Host != host || saved.Installation.Incarnation != saved.State.Incarnation || saved.Installation.Migration == 0 {
return record{}, ErrMalformedRecord
}
r := saved.State
+ if r.ActiveStamp != 0 {
+ if saved.Installation.Stamp != r.ActiveStamp {
+ return record{}, ErrMalformedRecord
+ }
+ } else {
+ // Older retired records cleared both stamps. Recover only the last
+ // exact root-retained tuple, never an agent supplied installation.
+ if len(saved.Retired) == 0 {
+ return record{}, ErrMalformedRecord
+ }
+ last := saved.Retired[len(saved.Retired)-1]
+ if last.Incarnation != r.Incarnation || last.Stamp == 0 || (saved.Installation.Stamp != 0 && saved.Installation.Stamp != last.Stamp) {
+ return record{}, ErrMalformedRecord
+ }
+ saved.Installation.Stamp = last.Stamp
+ }
r.migrationDenies = saved.MigrationDenies
for _, deny := range r.migrationDenies {
if deny.Incarnation == "" || deny.Stamp == 0 || deny.Session == 0 {
@@ -95,7 +111,10 @@ func (a *RootAuthority) write(r record) error {
if a == nil || a.store == nil || !r.root || r.validate(r.Host) != nil {
return ErrMalformedRecord
}
- r.installation.Host, r.installation.Incarnation, r.installation.Stamp = r.Host, r.Incarnation, r.ActiveStamp
+ r.installation.Host, r.installation.Incarnation = r.Host, r.Incarnation
+ if r.ActiveStamp != 0 {
+ r.installation.Stamp = r.ActiveStamp
+ }
b, err := json.Marshal(rootRecord{MigrationDenies: r.migrationDenies, Quarantined: r.quarantined, RebindCommit: r.rebindCommit, Schema: 1, Installation: r.installation, Retired: r.rootRetired, Barrier: r.barrier, PendingInventory: r.pendingInventory, PendingInstallation: r.pendingInstallation, State: r})
if err != nil {
return err
diff --git a/internal/hostguard/rootmigration_inventory.go b/internal/hostguard/rootmigration_inventory.go
index 6095077be..9048dbc40 100644
--- a/internal/hostguard/rootmigration_inventory.go
+++ b/internal/hostguard/rootmigration_inventory.go
@@ -30,7 +30,7 @@ func (a *RootAuthority) MigrationInstallations(ctx context.Context) ([]FenceView
if err != nil {
return nil, err
}
- view, err := a.viewLocked(host, Captured{Incarnation: r.Incarnation, Stamp: r.ActiveStamp})
+ view, err := a.viewLocked(host, Captured{Incarnation: r.Incarnation, Stamp: r.installation.Stamp})
if err != nil {
return nil, err
}
diff --git a/internal/provision/testdata/plan14-root-reads-e.tsv b/internal/provision/testdata/plan14-root-reads-e.tsv
index 77cc68e5a..4008e394d 100644
--- a/internal/provision/testdata/plan14-root-reads-e.tsv
+++ b/internal/provision/testdata/plan14-root-reads-e.tsv
@@ -1,5 +1,5 @@
# Track E integration after merge3. Earlier baselines, overlays and legacy fingerprints are immutable.
-internal/host/agent.go runAdoptUnderRootRelease authority B3,E1,E2 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. 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. E19: both destructive readers use the root reservation snapshot under existing release exclusion. Exact artifact and containing-tree identities are retained; missing completeness refuses destruction. Production installation/launch/kernel completeness and verified retirement remain unfinished. Fixtures: TestPlan14GenerationDeletionCannotUnpin, TestPlan14GenerationSnapshotCompleteness, TestPlan14GenerationSnapshotPermitsUnreservedRestage, TestPlan14GenerationSnapshotPinsContainingTree, TestPlan14GenerationSnapshotEmptyIsUnknown, TestPlan14GenerationConsumerMode. PROPOSED E50: Root adoption installs the census-gated capability refusal before release convergence; the existing no-census path retains its effects. whole:Route@11104,whole:Route@18654,whole:Route@18690,whole:Route@8406 8d4d89ecf22bf7144495d930b8580c00d4ab59a1cfc83620e4f59d27704b7bb0
+internal/host/agent.go runAdoptUnderRootRelease authority B3,E1,E2 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. 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. E19: both destructive readers use the root reservation snapshot under existing release exclusion. Exact artifact and containing-tree identities are retained; missing completeness refuses destruction. Production installation/launch/kernel completeness and verified retirement remain unfinished. Fixtures: TestPlan14GenerationDeletionCannotUnpin, TestPlan14GenerationSnapshotCompleteness, TestPlan14GenerationSnapshotPermitsUnreservedRestage, TestPlan14GenerationSnapshotPinsContainingTree, TestPlan14GenerationSnapshotEmptyIsUnknown, TestPlan14GenerationConsumerMode. PROPOSED E50: Root adoption installs the census-gated capability refusal before release convergence; the existing no-census path retains its effects. whole:Route@11104,whole:Route@18293,whole:Route@18329,whole:Route@8406 a25835004cca39d1c624fc47e59b7bcac47eb175b55c1db583fac920eaa33068
internal/host/agent.go runAdoptWithEnsureSystemUnits authority B3,C,D,E1,E2,E3,E4,F,J1 PROPOSED E45 / R-E45-3: root checks census presence before opening release exclusion; absent census uses main adoption with no release directory or lock. Retained census remains protected with either flag value. PROPOSED E45: root enforces census-gated release ordering, bounded acquisition and scoped recovery; registry timer primitives preserve supplied identity but producer integration remains pending. PROPOSED E50: Root carries its census probe from the outer release gate into restart and capability callbacks; an injected absent census and the production probe have the same path. file:Open@176 13c055021f5e0453138bf773673521b86851eb16d567f6ccf968f5b7bc4f9199
internal/host/namedrecovery.go namedUnitRecovery.repair authority E1,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. file:Open@2642,registry:Channel@1814,whole:Route@1060,whole:Route@1822 566bc61eafb118d9f6fba29228c372b18c8729549ed9a0bccf32c61b360eb9c3
internal/host/namedrecovery_active.go namedUnitRecovery.recover authority B3,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:Open@5504,registry:Channel@2558,registry:Unit@216,registry:Unit@233,registry:Version@3618,registry:Version@3724,whole:Route@1478,whole:Route@202,whole:Route@2560,whole:Route@3544 dabec20a8ea93c7d72b6d6c3a30a1ae7c99ffcee4de4c0416fba699734739b92
@@ -120,7 +120,7 @@ internal/release/roothistory_retirement.go RootHistory.recoveryRetirementDigest
internal/host/namedrecovery.go namedUnitRecovery.checkHistory authority 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. registry:Account@155,registry:App@177,registry:Host@132,whole:Route@134,whole:Route@157,whole:Route@179 1ef9a1168e0c3f2982823cd9519c65c089a7cc8599a9baed2512af2f01b31207
internal/host/namedrecovery.go namedUnitRecovery.originalRefusal authority 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:Read@413 69b56334f7b80c972c45c59363fceda6058ce281982b989c622cd4ef22ff096e
internal/host/namedrecovery.go type namedUnitRecovery authority 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. a8be56e21473ab4cf3073ff0b997efc9a1e333c4a5ce72d59c2b515101999a35
-internal/host/proofroot.go proofRoot.mintAndLog authority 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:Stat@2011,registry:App@1014,registry:App@1152,registry:App@855,registry:Host@1069,registry:Host@772,registry:Host@893,registry:Unit@725,registry:Unit@742,whole:Route@685,whole:Route@718,whole:Route@830,whole:Route@927,whole:Route@962 f21b61edec1327ecc45f4920839fe43dd8e4ce152e1621acf053936f0ee16203
+internal/host/proofroot.go proofRoot.mintAndLog authority 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:Stat@2049,registry:App@1052,registry:App@1190,registry:App@893,registry:Host@1107,registry:Host@810,registry:Host@931,registry:Unit@763,registry:Unit@780,whole:Route@1000,whole:Route@723,whole:Route@756,whole:Route@868,whole:Route@965 faf5c143825d0f2d263c27d0722fba407b387f0a814b0e8a7212a82e813e240a
internal/host/proofroot.go type proofRoot authority 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. e3a4fd0a9546176f2bcc278c3aa3152344eb352686558da8a5b06152501c0de6
internal/release/roothistory_recovery_policy.go RootHistory.RetireD1 authority 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. 1dfff4e199ac8bede5a31d83a2519325f6610ddd35da9d5a1de61fe600b3f079
internal/release/roothistory_recovery_policy.go RootHistory.recoveryDenials authority 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:Read@164,file:Read@847 e1ea25a10a50fbaf0d41be9a154ee946e4b6cd21aa41549dc9afebb154415845
@@ -159,7 +159,7 @@ internal/host/rootrestart_requests_source.go rootRestartRequestSource.target aut
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/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. 3082c34490fbf6f95ebace69d97161993635c9cc1fee346641fdc61cb1f2e43f
-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. PROPOSED E45: root enforces census-gated release ordering, bounded acquisition and scoped recovery; registry timer primitives preserve supplied identity but producer integration remains pending. file:Open@289,file:Open@368 a26026dd565e4d1d046591fa1534ded1b64ccb8eafbef65a4d4ec899c0c471d3
+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. PROPOSED E45: root enforces census-gated release ordering, bounded acquisition and scoped recovery; registry timer primitives preserve supplied identity but producer integration remains pending. file:Open@297 495a357c0a2a1354d5c5510744f9bf0778034bd6d7b0d68fa1881791e997634f
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 E2 E37: retain independently initialized state-directory and timer requirements in the root installation transaction; preserve them through crash/reopen and rebind. Unknown history cannot certify completeness. Recommit under root-host exclusion before generation use. Canonical record and root retained obligations constrain agent candidate lists; timer enumeration is deny-only. Pre-flight 33, 80, 83. Fixtures: TestPlan14GenerationRequiredArtifacts, TestPlan14GenerationRegistryRows, TestPlan14RootTimerInventory, TestPlan14InventoryRequirementsRegistration, TestPlan14InventoryRequirementsUnknownAndRatchet, TestPlan14InventoryArtifactsCrash, TestPlan14InventoryRequirementsHistoryIdentity. Changed RequiredArtifacts and ExactRetirement root-runner witnesses await acceptance; remaining E2 launch/parked/containment integration is incomplete. 5ca3a1163ea109b6e0ed1a9477023340cf39e6a810761133f1117d311e903b84
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
@@ -224,7 +224,7 @@ internal/release/rootgenerationpins.go type RootGenerationPins authority E2 E23:
internal/host/rootinstallation.go rootInstallationHostApp authority E2,J1 E20: read the atomic root installation under the existing host exclusion without recursive lock acquisition; retain fresh claim, canonical record, signed artifact and final reservation checks. Fixtures: TestPlan14GenerationLaunchDoesNotReenterRootLock, TestPlan14GenerationLaunchRefusals, TestPlan14RootDispatchRejectsRegistryMutation. file:Read@488,registry:Account@1062,registry:App@1091 0bbe17aff8c82841ae47fb01e060e1e1a80865e635d4a5c95dc5a638d2754d1c
internal/hostguard/rootview.go RootAuthority.Installation authority E2,J1 E20: read the atomic root installation under the existing host exclusion without recursive lock acquisition; retain fresh claim, canonical record, signed artifact and final reservation checks. Fixtures: TestPlan14GenerationLaunchDoesNotReenterRootLock, TestPlan14GenerationLaunchRefusals, TestPlan14RootDispatchRejectsRegistryMutation. d819256332950ec26c1b541f8be21a6e8cea839708fcbd0f7b8367d118574f92
internal/release/rootgenerations.go RootGenerations.Retire authority E2 E20: exact reservation closure precedes retirement verification; retained tombstones forbid replay and require sync recommit. Retired records release only their own pins; completeness remains a separate mandatory verifier, still unavailable in production. Fixtures: TestPlan14GenerationRetirementReleasesExactPin, TestPlan14GenerationRetirementRefusals, TestPlan14GenerationClosedReplayRefused, TestPlan14GenerationRetirementProcessCrash, TestPlan14GenerationRetirementKeepsIndependentReference, TestPlan14GenerationClaimsOffRestageMatchesMain. file:Read@297 21bb00d11d6fd8474b4d0a556bd1af7e69dbf6bd75755daeb0f14481166f2a90
-@coverage internal/hostguard c9458bdbbd122ae58dbe6581e6823c9379c397661dbc7a35513ccd656aca344d
+@coverage internal/hostguard d115a22e2a8410adef697af3e73f420b663337dc827fcca82e181e15fdeb8834
internal/host/rootgeneration_kernel_linux.go generationGroupGone authority E2 E21: bounded kernel reader retains executable, sibling, descriptor, mapping and mount references; checks controller boot/start and cgroup emptiness. Unknown nonroot tasks, foreign namespaces, missing kernel state or renamed trees refuse. Fixtures: TestPlan14GenerationKernelReferences, TestPlan14GenerationKernelMappingIdentity, TestPlan14GenerationKernelControllerIdentity, TestPlan14GenerationKernelRunningExecutable, TestPlan14GenerationKernelCgroup, TestPlan14GenerationKernelRetainedFiles, TestPlan14GenerationKernelSiblingAlias. Real proc executable tests are not full privileged namespace acceptance. PROPOSED E47: root-owned immutable unit/shape binding, reserved launch placement and exact invocation stop; retained generation binding carries the canonical unit. Default unit paths remain unchanged. Production named-route wiring and unit/env/timer retirement are not complete. PROPOSED E51: Root uses the existing reboot cgroup collector after a boot change; a reused populated group still refuses. file:Lstat@194 1ed5a4aadf5334f37027e04b258d7fb4a6413356d2d21257ae5ef9c626655376
internal/host/rootgeneration_kernel_linux.go generationIDRef authority E2 E21: bounded kernel reader retains executable, sibling, descriptor, mapping and mount references; checks controller boot/start and cgroup emptiness. Unknown nonroot tasks, foreign namespaces, missing kernel state or renamed trees refuse. Fixtures: TestPlan14GenerationKernelReferences, TestPlan14GenerationKernelMappingIdentity, TestPlan14GenerationKernelControllerIdentity, TestPlan14GenerationKernelRunningExecutable, TestPlan14GenerationKernelCgroup, TestPlan14GenerationKernelRetainedFiles, TestPlan14GenerationKernelSiblingAlias. Real proc executable tests are not full privileged namespace acceptance. 949b4dddb141d7eafd4edc70b41f8dd337069f699ab30d86c46bc6fce986d40b
internal/host/rootgeneration_kernel_linux.go generationKernel.absent authority E2 E21: bounded kernel reader retains executable, sibling, descriptor, mapping and mount references; checks controller boot/start and cgroup emptiness. Unknown nonroot tasks, foreign namespaces, missing kernel state or renamed trees refuse. Fixtures: TestPlan14GenerationKernelReferences, TestPlan14GenerationKernelMappingIdentity, TestPlan14GenerationKernelControllerIdentity, TestPlan14GenerationKernelRunningExecutable, TestPlan14GenerationKernelCgroup, TestPlan14GenerationKernelRetainedFiles, TestPlan14GenerationKernelSiblingAlias. Real proc executable tests are not full privileged namespace acceptance. file:Readlink@312 0dd1a357c428ea9ebab2fc808d8fb602bbd42f73af595fd3dd16319ed7336ff3
@@ -326,7 +326,7 @@ internal/host/rootinventory_requirements.go ClaimRootHTTP.verifyInventoryTimers
internal/host/rootinventory_requirements.go generationRetainedTimers authority E2 E37: retain independently initialized state-directory and timer requirements in the root installation transaction; preserve them through crash/reopen and rebind. Unknown history cannot certify completeness. Recommit under root-host exclusion before generation use. Canonical record and root retained obligations constrain agent candidate lists; timer enumeration is deny-only. Pre-flight 33, 80, 83. Fixtures: TestPlan14GenerationRequiredArtifacts, TestPlan14GenerationRegistryRows, TestPlan14RootTimerInventory, TestPlan14InventoryRequirementsRegistration, TestPlan14InventoryRequirementsUnknownAndRatchet, TestPlan14InventoryArtifactsCrash, TestPlan14InventoryRequirementsHistoryIdentity. Changed RequiredArtifacts and ExactRetirement root-runner witnesses await acceptance; remaining E2 launch/parked/containment integration is incomplete. 73db5d2174f16d56d9cf2dc2d743621590c6e873da3a86712e0a6fd15790af23
internal/host/rootinventory_requirements.go inventoryTimerNames authority E2 E37: retain independently initialized state-directory and timer requirements in the root installation transaction; preserve them through crash/reopen and rebind. Unknown history cannot certify completeness. Recommit under root-host exclusion before generation use. Canonical record and root retained obligations constrain agent candidate lists; timer enumeration is deny-only. Pre-flight 33, 80, 83. Fixtures: TestPlan14GenerationRequiredArtifacts, TestPlan14GenerationRegistryRows, TestPlan14RootTimerInventory, TestPlan14InventoryRequirementsRegistration, TestPlan14InventoryRequirementsUnknownAndRatchet, TestPlan14InventoryArtifactsCrash, TestPlan14InventoryRequirementsHistoryIdentity. Changed RequiredArtifacts and ExactRetirement root-runner witnesses await acceptance; remaining E2 launch/parked/containment integration is incomplete. 3fc943e0b77b892f855425f6f0f95abdf33c634f9d7edabb55011965adbfc390
internal/host/rootinventory_requirements.go rootInventoryArtifacts authority E2 E37: retain independently initialized state-directory and timer requirements in the root installation transaction; preserve them through crash/reopen and rebind. Unknown history cannot certify completeness. Recommit under root-host exclusion before generation use. Canonical record and root retained obligations constrain agent candidate lists; timer enumeration is deny-only. Pre-flight 33, 80, 83. Fixtures: TestPlan14GenerationRequiredArtifacts, TestPlan14GenerationRegistryRows, TestPlan14RootTimerInventory, TestPlan14InventoryRequirementsRegistration, TestPlan14InventoryRequirementsUnknownAndRatchet, TestPlan14InventoryArtifactsCrash, TestPlan14InventoryRequirementsHistoryIdentity. Changed RequiredArtifacts and ExactRetirement root-runner witnesses await acceptance; remaining E2 launch/parked/containment integration is incomplete. registry:DBPath@573 77d90e674c8c0949b9c31b927fff2518f87ebd33c0efd7d6bc6d965feb3446f5
-internal/hostguard/generationinventory.go RootAuthority.generationInstallation authority E2 E37: retain independently initialized state-directory and timer requirements in the root installation transaction; preserve them through crash/reopen and rebind. Unknown history cannot certify completeness. Recommit under root-host exclusion before generation use. Canonical record and root retained obligations constrain agent candidate lists; timer enumeration is deny-only. Pre-flight 33, 80, 83. Fixtures: TestPlan14GenerationRequiredArtifacts, TestPlan14GenerationRegistryRows, TestPlan14RootTimerInventory, TestPlan14InventoryRequirementsRegistration, TestPlan14InventoryRequirementsUnknownAndRatchet, TestPlan14InventoryArtifactsCrash, TestPlan14InventoryRequirementsHistoryIdentity. Changed RequiredArtifacts and ExactRetirement root-runner witnesses await acceptance; remaining E2 launch/parked/containment integration is incomplete. 361a97c539df7c1cee6f6672c095806227b7ca6a9081e3a58d7afab14c56d9cc
+internal/hostguard/generationinventory.go RootAuthority.generationInstallation authority E2 E37: retain independently initialized state-directory and timer requirements in the root installation transaction; preserve them through crash/reopen and rebind. Unknown history cannot certify completeness. Recommit under root-host exclusion before generation use. Canonical record and root retained obligations constrain agent candidate lists; timer enumeration is deny-only. Pre-flight 33, 80, 83. Fixtures: TestPlan14GenerationRequiredArtifacts, TestPlan14GenerationRegistryRows, TestPlan14RootTimerInventory, TestPlan14InventoryRequirementsRegistration, TestPlan14InventoryRequirementsUnknownAndRatchet, TestPlan14InventoryArtifactsCrash, TestPlan14InventoryRequirementsHistoryIdentity. Changed RequiredArtifacts and ExactRetirement root-runner witnesses await acceptance; remaining E2 launch/parked/containment integration is incomplete. fc5ef57b638ee0eba8ae49b724ef9397b2a3ae097d1e892cab888880480eacd2
internal/hostguard/root.go Guard.CommitRootInventory authority E2 E37: retain independently initialized state-directory and timer requirements in the root installation transaction; preserve them through crash/reopen and rebind. Unknown history cannot certify completeness. Recommit under root-host exclusion before generation use. Canonical record and root retained obligations constrain agent candidate lists; timer enumeration is deny-only. Pre-flight 33, 80, 83. Fixtures: TestPlan14GenerationRequiredArtifacts, TestPlan14GenerationRegistryRows, TestPlan14RootTimerInventory, TestPlan14InventoryRequirementsRegistration, TestPlan14InventoryRequirementsUnknownAndRatchet, TestPlan14InventoryArtifactsCrash, TestPlan14InventoryRequirementsHistoryIdentity. Changed RequiredArtifacts and ExactRetirement root-runner witnesses await acceptance; remaining E2 launch/parked/containment integration is incomplete. 59fe1795db13dbc9a7a3159749b1c52bc306f45da94a9b261fcc2e8cb2ca09c1
internal/hostguard/root.go Guard.CommitRootInventoryArtifacts authority E2 E37: retain independently initialized state-directory and timer requirements in the root installation transaction; preserve them through crash/reopen and rebind. Unknown history cannot certify completeness. Recommit under root-host exclusion before generation use. Canonical record and root retained obligations constrain agent candidate lists; timer enumeration is deny-only. Pre-flight 33, 80, 83. Fixtures: TestPlan14GenerationRequiredArtifacts, TestPlan14GenerationRegistryRows, TestPlan14RootTimerInventory, TestPlan14InventoryRequirementsRegistration, TestPlan14InventoryRequirementsUnknownAndRatchet, TestPlan14InventoryArtifactsCrash, TestPlan14InventoryRequirementsHistoryIdentity. Changed RequiredArtifacts and ExactRetirement root-runner witnesses await acceptance; remaining E2 launch/parked/containment integration is incomplete. 08e42f8810e2645a875b5177fc9e104ed4b307c7d80dc77d204e2516b1bd66de
internal/hostguard/root.go type InstallationArtifacts authority E2 E37: retain independently initialized state-directory and timer requirements in the root installation transaction; preserve them through crash/reopen and rebind. Unknown history cannot certify completeness. Recommit under root-host exclusion before generation use. Canonical record and root retained obligations constrain agent candidate lists; timer enumeration is deny-only. Pre-flight 33, 80, 83. Fixtures: TestPlan14GenerationRequiredArtifacts, TestPlan14GenerationRegistryRows, TestPlan14RootTimerInventory, TestPlan14InventoryRequirementsRegistration, TestPlan14InventoryRequirementsUnknownAndRatchet, TestPlan14InventoryArtifactsCrash, TestPlan14InventoryRequirementsHistoryIdentity. Changed RequiredArtifacts and ExactRetirement root-runner witnesses await acceptance; remaining E2 launch/parked/containment integration is incomplete. fab0473af135dbb33e0f4cf9709a07b5026feedd43709c1a681c650c3ae82dd5
@@ -361,7 +361,7 @@ internal/release/releaseexclusion.go ReleaseExclusion.With authority E2 PROPOSED
internal/release/releaseexclusion.go type ReleaseExclusion authority E2 PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. PROPOSED E45: root enforces census-gated release ordering, bounded acquisition and scoped recovery; registry timer primitives preserve supplied identity but producer integration remains pending. file:Open@32 a4026f7a3110a647eaaeabecd5f0a255ae91b5e183f431be3b017433ee40eba3
internal/rootparticipation/production.go ActivationReady authority D,G Refuse claims activation without complete census and registered verifier; Part 3 G remains mandatory. PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. file:Open@61 0103a8635bfc3a4ab17998aab202eebcc9ed8dc1a9d7c41ac8fabad435ff5c05
internal/rootparticipation/production.go Open authority D Claims-on or retained root census uses root exclusion; claims-off with no census preserves main participation and recovery behavior. PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. file:Open@45,file:Open@5 e7feda4757ec1a808e42729cc91ba57c37a34f48640f72dcdb9de706d8a55a24
-internal/host/claimstop_run.go RunInstanceControl authority C,D,G Refuse premature claim activation and continue serving recovery after per-host startup failures. PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. PROPOSED E45: root enforces census-gated release ordering, bounded acquisition and scoped recovery; registry timer primitives preserve supplied identity but producer integration remains pending. 33800eb05fa8ac4d9905cf03efe04363c744cae0713857c87317fb3ef8bc53c6
+internal/host/claimstop_run.go RunInstanceControl authority C,D,G Refuse premature claim activation and continue serving recovery after per-host startup failures. PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. PROPOSED E45: root enforces census-gated release ordering, bounded acquisition and scoped recovery; registry timer primitives preserve supplied identity but producer integration remains pending. d55919f2c48d02f39bb2d0eda76c7854aaf2c7684b8c782483d8fb62e5f5bff4
internal/hostfloor/disk.go Disk.WithParticipation authority D Claims-on or retained root census uses root exclusion; claims-off with no census preserves main participation and recovery behavior. PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. f06b9c85d99dc0fe071f4b2bf0eced7ec8f118c7df5c1871040df3ea7323b3a3
internal/hostfloor/participation.go CheckParticipation authority D Claims-on or retained root census uses root exclusion; claims-off with no census preserves main participation and recovery behavior. PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. d5390731acd74a7ce0ef5e89a1cb1af769d35433c1bf4cf4bd196a2b4da650d2
internal/host/rootlaunchverifier.go rootLaunchVerifier.verify authority E2 PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. PROPOSED E45: root enforces census-gated release ordering, bounded acquisition and scoped recovery; registry timer primitives preserve supplied identity but producer integration remains pending. ff4241ff35d8dd8f7d08c2d5622ce88040791d34839bcecb22ac0d52436bfafa
@@ -434,9 +434,9 @@ internal/instancectl/claimexec.go ClaimUnits.prepareExecGate authority C,G,J2,J4
internal/instancectl/claimunit.go ClaimUnits.cgroupEmpty authority C,G,J2,J4,E2 root-input;kept=C,G;scope=protected-decision-only PROPOSED E47: root-owned immutable unit/shape binding, reserved launch placement and exact invocation stop; retained generation binding carries the canonical unit. Default unit paths remain unchanged. Production named-route wiring and unit/env/timer retirement are not complete. file:ReadFile@196 9e874fe959a3a381442e2900e8184f91ef803dd4f47839de8dbd6d3ab395d03e
internal/host/claimlaunch_authority.go ClaimLaunchRoot.authorize authority D,E2 Both root launch authorizations read root participation and match authenticated peer; TestPlan14ExecutionFinalLaunchUsesRootParticipation. PROPOSED E47: final named-unit launch authorization rechecks the retained root binding and refuses missing, retired, foreign or changed canonical shape. 9e00346c2012343327a230067f2e35f9f960569fbb3b6ee0dc401e7f566cbef6
internal/host/claimlaunch_authority.go ClaimLaunchRoot.launchSpec authority E2 PROPOSED E48: root; retained canonical unit and program ownership, checked root configuration, deny-only candidate matching, reservation-bearing per-host dispatch, and invocation/cgroup-bound executable peer confirmation. No no-census path is added. Full named-route admission, flat sockets and unit/env/timer retirement remain incomplete. 4703055ca1e26021e448839b43b5e281a020e492aac61dadca12f03675a688a4
-internal/host/claimunit_binding.go claimUnitShape.valid authority E2 PROPOSED E48: root; retained canonical unit and program ownership, checked root configuration, deny-only candidate matching, reservation-bearing per-host dispatch, and invocation/cgroup-bound executable peer confirmation. No no-census path is added. Full named-route admission, flat sockets and unit/env/timer retirement remain incomplete. PROPOSED E50: Root validates optional exact predecessor definition digest in canonical configuration; no service route may authorize migration. PROPOSED E51: Root canonical TCP endpoints require a numeric port in 1..65535. b080f7295cacb6db0b4f6cb7873b41c225ae7a83ad5cac0d110f61cf14ceaf0d
+internal/host/claimunit_binding.go claimUnitShape.valid authority E2 PROPOSED E48: root; retained canonical unit and program ownership, checked root configuration, deny-only candidate matching, reservation-bearing per-host dispatch, and invocation/cgroup-bound executable peer confirmation. No no-census path is added. Full named-route admission, flat sockets and unit/env/timer retirement remain incomplete. PROPOSED E50: Root validates optional exact predecessor definition digest in canonical configuration; no service route may authorize migration. PROPOSED E51: Root canonical TCP endpoints require a numeric port in 1..65535. PROPOSED E58: root instance-control uid 0; permits the explicit console capability only for console.service without unit backing and with a pinned program digest; retained shape equality refuses adding/removing the capability on an established binding. ec595295d005d886a8031753c31757c967a4e40af813c4e1dab4b1c7673edfbf
internal/host/claimunit_dispatch.go claimUnitDispatch.StartReserved authority E2 PROPOSED E48: root; retained canonical unit and program ownership, checked root configuration, deny-only candidate matching, reservation-bearing per-host dispatch, and invocation/cgroup-bound executable peer confirmation. No no-census path is added. Full named-route admission, flat sockets and unit/env/timer retirement remain incomplete. 02dfbc7e31f9a13ba210d2bed3181da5e1874ae388c2d1060cbdb57c398a04e5
-internal/host/claimunit_dispatch.go claimUnitDispatch.forStart authority E2 PROPOSED E48: root; retained canonical unit and program ownership, checked root configuration, deny-only candidate matching, reservation-bearing per-host dispatch, and invocation/cgroup-bound executable peer confirmation. No no-census path is added. Full named-route admission, flat sockets and unit/env/timer retirement remain incomplete. PROPOSED E50: Root start requires canonical shape, retained binding, exact owned fragment and effective systemd properties; missing binding selects ordinary placement only when no configured named host exists. 3e0672074eadb441f2272a1d6f42a9e58759c3146d363939bea9805090d2c32c
+internal/host/claimunit_dispatch.go claimUnitDispatch.forStart authority E2 PROPOSED E48: root; retained canonical unit and program ownership, checked root configuration, deny-only candidate matching, reservation-bearing per-host dispatch, and invocation/cgroup-bound executable peer confirmation. No no-census path is added. Full named-route admission, flat sockets and unit/env/timer retirement remain incomplete. PROPOSED E50: Root start requires canonical shape, retained binding, exact owned fragment and effective systemd properties; missing binding selects ordinary placement only when no configured named host exists. 3d358cb14cd248cea66750f64a4ea49be91eddb563e6ca389c12c7664f6c6706
internal/host/claimunit_dispatch.go claimUnitDispatch.selected authority E2 PROPOSED E48: root; retained canonical unit and program ownership, checked root configuration, deny-only candidate matching, reservation-bearing per-host dispatch, and invocation/cgroup-bound executable peer confirmation. No no-census path is added. Full named-route admission, flat sockets and unit/env/timer retirement remain incomplete. PROPOSED E50: Root cleanup placement uses retained binding only; malformed current configuration cannot redirect cleanup or prevent an ordinary host stop. PROPOSED E51: Root retained binding supplies both unit placement and the TCP address requiring the pre-exec kernel filter. 1b4a8e6ddb43675f4a1d492089312f6dd8553f993adeafe601080a8954d67425
internal/host/claimunit_production.go var openClaimUnitBindings authority E2 PROPOSED E48: root; retained canonical unit and program ownership, checked root configuration, deny-only candidate matching, reservation-bearing per-host dispatch, and invocation/cgroup-bound executable peer confirmation. No no-census path is added. Full named-route admission, flat sockets and unit/env/timer retirement remain incomplete. file:Open@70 f332e5eeef4116b6a9242f7f5c6c47bef78639a042ef0db3993a54dfae71963c
internal/host/claimunit_program.go readClaimUnitProgram authority E2 PROPOSED E48: root; retained canonical unit and program ownership, checked root configuration, deny-only candidate matching, reservation-bearing per-host dispatch, and invocation/cgroup-bound executable peer confirmation. No no-census path is added. Full named-route admission, flat sockets and unit/env/timer retirement remain incomplete. file:ReadAll@353 28ee039bab9f2461f10797082d8cfafe751f094271f377d04565e410127808ec
@@ -457,7 +457,7 @@ internal/host/claimretire_transport.go ClaimRootHTTP.retire authority E2 PROPOSE
internal/host/claimretire_transport.go type ClaimRootHTTP authority E2 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. PROPOSED E49: root; named placement/program readback from retained root binding, durable unit intent and inode-conditional retirement, and readiness socket ownership. Service route fields are candidates only; final root launch rechecks independently. No no-census path added. Full always-running adoption/handover remains pending. PROPOSED E50: Root: R-E50-7 restores the pre-E49 basis verbatim; current declaration behavior is retained. dee282541c5597b1af69dbd986817936dcdbb80e53471db215df9f8b1318b7da
internal/host/claimroleassembly.go ClaimRoleAssembly.PreflightLaunch authority E2 PROPOSED E49: agent (service UID); named placement/program readback from retained root binding, durable unit intent and inode-conditional retirement, and readiness socket ownership. Service route fields are candidates only; final root launch rechecks independently. No no-census path added. Full always-running adoption/handover remains pending. PROPOSED E50: Agent (service UID): retain authority classification under R-E50-7 because this declaration gates work; independent final root validation does not make the agent gate a passive hint. data-literal@200 5bf02fb5b6911924dbd18f063fec53552a994bcc9ae790a67db5786fa877de1e
internal/host/claimroleassembly_linux.go ClaimRoleAssembly.Launch authority E2 PROPOSED E49: agent (service UID); named placement/program readback from retained root binding, durable unit intent and inode-conditional retirement, and readiness socket ownership. Service route fields are candidates only; final root launch rechecks independently. No no-census path added. Full always-running adoption/handover remains pending. PROPOSED E50: Agent (service UID): retain authority classification under R-E50-7 because this declaration gates work; independent final root validation does not make the agent gate a passive hint. PROPOSED E53: Executing identity root (state capture also runs as carlos); boot-local device and mount numbers are checked through retained filesystem, handle, inode, owner and topology evidence; no old-boot recapture grants. Tests: TestPlan14InventoryIdentityBootBoundary, TestRootRebootInventoryIdentityPhase1/2. file:Command@5889,file:EvalSymlinks@5438,file:Fstat@3521,file:Lstat@2269,file:OpenFile@3111,file:OpenFile@3298,file:Read@711 ff3c65c83908a6803f53b0f61eedf9840eba50c109429bbb2c3620e9154bf689
-internal/host/claimunit_definition.go claimBoundUnitDefinition authority E2 PROPOSED E49: root; named placement/program readback from retained root binding, durable unit intent and inode-conditional retirement, and readiness socket ownership. Service route fields are candidates only; final root launch rechecks independently. No no-census path added. Full always-running adoption/handover remains pending. PROPOSED E51: Root renders the independent bound-unit profile; TCP permits only the exact configured address and bind port, with reservation-time connect/sendmsg filtering. PROPOSED E51: Root renders the independent bound-unit profile; TCP permits only the exact configured address and bind port, with reservation-time connect/sendmsg filtering. PROPOSED E51: Root renders the independent bound-unit profile; TCP permits only the exact configured address and bind port, with reservation-time connect/sendmsg filtering. 81006df22045ec86dc586e9abf7986b3c2b5eb1634911fcc6a849c6472e74263
+internal/host/claimunit_definition.go claimBoundUnitDefinition authority E2 PROPOSED E49: root; named placement/program readback from retained root binding, durable unit intent and inode-conditional retirement, and readiness socket ownership. Service route fields are candidates only; final root launch rechecks independently. No no-census path added. Full always-running adoption/handover remains pending. PROPOSED E51: Root renders the independent bound-unit profile; TCP permits only the exact configured address and bind port, with reservation-time connect/sendmsg filtering. PROPOSED E51: Root renders the independent bound-unit profile; TCP permits only the exact configured address and bind port, with reservation-time connect/sendmsg filtering. PROPOSED E51: Root renders the independent bound-unit profile; TCP permits only the exact configured address and bind port, with reservation-time connect/sendmsg filtering. PROPOSED E58: root instance-control uid 0; explicit root Console binding exposes only the enrollment file read-only in an otherwise empty /etc/carlos and permits only IPv4 IMDS in addition to the existing exact listener; root control state and sibling connect filters remain inaccessible. 8a3ce6f039cbcc055a78fbc0845b95b2999a5760dcec1bf81d1b43e0c487c37d
internal/host/claimunit_files_linux.go claimUnitFiles.read authority E2 PROPOSED E49: root; named placement/program readback from retained root binding, durable unit intent and inode-conditional retirement, and readiness socket ownership. Service route fields are candidates only; final root launch rechecks independently. No no-census path added. Full always-running adoption/handover remains pending. PROPOSED E51: Root reads the owned unit through checked public ancestry and captures a filesystem handle, filesystem identity, boot, mount topology and ownership. file:ReadAll@354 b7f65702d1502250c459874857f9e64ba144b851c0e5a0eb923c91eb11ffab2f
internal/host/claimunit_files_linux.go claimUnitFiles.write authority E2 PROPOSED E49: root; named placement/program readback from retained root binding, durable unit intent and inode-conditional retirement, and readiness socket ownership. Service route fields are candidates only; final root launch rechecks independently. No no-census path added. Full always-running adoption/handover remains pending. PROPOSED E51: Root conditionally replaces only a persistently matching definition; current-boot device numbers remain exact within a boot. file:CreateTemp@381,file:Open@182 e36085aeb00a20ae11f056111dcef5272621cc88d90e9e75a927660a0834e9b1
internal/host/claimunit_files_linux.go projectClaimBoundSocket hint E2 PROPOSED E49: root; named placement/program readback from retained root binding, durable unit intent and inode-conditional retirement, and readiness socket ownership. Service route fields are candidates only; final root launch rechecks independently. No no-census path added. Full always-running adoption/handover remains pending. file:Readlinkat@433 4f3c8daab1f68102e7be9511d1ec29d89c584c99aed79641f2b740031e503ab4
@@ -472,22 +472,22 @@ internal/hostbox/rolelaunch.go RoleLaunchRequest.Validate hint E2 PROPOSED E49:
@coverage internal/activator 0a01bc096c64f1886f810f4856d60f9a2642074a8b2ac0c56cbed77d122e76c8
@coverage internal/hostbox f877b123ea1c5c361a3046f5962ea5ef8e1a3b8a534a29aa2c15aac507b37081
internal/host/claimlaunch_authority.go ClaimLaunchRoot.waitReady authority E2 PROPOSED E49: root; delegated retained binding, exact unit identity and receipt-authorized retirement or per-connection kernel readiness. No service candidate grants authority. PROPOSED E50: Root checks the exact reserved host binding and endpoint before accepting the already verified invocation; no stale startup shape list decides whether readiness applies. 1f4fec01731cdaed47c201f92164d9e0843392959ffae870f45b1434d1a982ea
-internal/host/claimunit_materialize.go claimUnitMaterializer.install authority E2 PROPOSED E49: root; delegated retained binding, exact unit identity and receipt-authorized retirement or per-connection kernel readiness. No service candidate grants authority. PROPOSED E50: Root journals explicit digest-authorized predecessor capture before stopping it and replacing only the recorded definition inode; replay never infers consent from route fields. PROPOSED E51: Root accepts an existing definition only when its persistent identity and exact bytes match the root materialization record. 2ec82ffc3e4f78bfa4fd5ca0111d402fa8b58a6d1ad89fcc088f8c7d12bfc146
-internal/host/claimunit_materialize.go claimUnitMaterializer.verify authority E2 PROPOSED E49: root; delegated retained binding, exact unit identity and receipt-authorized retirement or per-connection kernel readiness. No service candidate grants authority. PROPOSED E51: Root verifies persistent identity and bytes of a bound definition, allowing device renumbering only across different boots. 1e0b517da0c037f2a56611b80422ecaa05bb9c026c19b1093b3b1f4b2ec82246
+internal/host/claimunit_materialize.go claimUnitMaterializer.install authority E2 PROPOSED E49: root; delegated retained binding, exact unit identity and receipt-authorized retirement or per-connection kernel readiness. No service candidate grants authority. PROPOSED E50: Root journals explicit digest-authorized predecessor capture before stopping it and replacing only the recorded definition inode; replay never infers consent from route fields. PROPOSED E51: Root accepts an existing definition only when its persistent identity and exact bytes match the root materialization record. ed99fda682170367aee0a3d6270153b7051ae03849aa93dc24ecca60eb7afec4
+internal/host/claimunit_materialize.go claimUnitMaterializer.verify authority E2 PROPOSED E49: root; delegated retained binding, exact unit identity and receipt-authorized retirement or per-connection kernel readiness. No service candidate grants authority. PROPOSED E51: Root verifies persistent identity and bytes of a bound definition, allowing device renumbering only across different boots. PROPOSED E58: root instance-control uid 0; each effective-definition check on an explicit console binding verifies the root-owned enrollment matches the retained installation box and enrollment before any start/exec handshake. 7ae19a4aa1a832319da315f50afbe6fe8be46bb646ea3090244df890091d08a7
internal/host/claimunit_materialize.go claimUnitMaterializer.retire authority E2 PROPOSED E49: root; delegated retained binding, exact unit identity and receipt-authorized retirement or per-connection kernel readiness. No service candidate grants authority. PROPOSED E51: Root retires only the persistently identified definition or exact retained tombstone; substituted mounts/filesystems refuse. f9a5fdd174918a4823e175c2e10811c9e236f2e1a85552372eee46942d2efd2b
internal/host/claimunit_retire.go retireClaimBoundUnit authority E2 PROPOSED E49: root; delegated retained binding, exact unit identity and receipt-authorized retirement or per-connection kernel readiness. No service candidate grants authority. PROPOSED E50: Root retirement uses the retained binding even when canonical config is unavailable; absence of a binding leaves ordinary typed retirement unchanged. 43beae021aa4272d21b5beb8ffb59746b1194885576c131934b9f29946029f95
internal/host/claimunit_retire.go claimBoundUnitCleanup.WithCleanup authority E2 PROPOSED E49: root; delegated retained binding, exact unit identity and receipt-authorized retirement or per-connection kernel readiness. No service candidate grants authority. b1bd5b012973d2abdd29ffcbc65ab6980eef0cbb75caefde7ea62e0738c81b7a
internal/host/claimunit_ready_linux.go claimBoundUnitReady authority E2 PROPOSED E49: root; delegated retained binding, exact unit identity and receipt-authorized retirement or per-connection kernel readiness. No service candidate grants authority. 62e76b8a9ab27be350f3da6d75224600c6cb02d3259e2bd9efbd52e1df28eb95
# PROPOSED E49
-internal/host/claimunit_binding.go claimUnitBindings.establish authority E2 PROPOSED E49: root; named placement/program readback from retained root binding, durable unit intent and inode-conditional retirement, and readiness socket ownership. Service route fields are candidates only; final root launch rechecks independently. No no-census path added. Full always-running adoption/handover remains pending. PROPOSED E50: Root startup may supply a pre-resolved claim owner, rechecked against the local installation under host exclusion. A corrupt unrelated record does not block this host; canonical unit uniqueness and owned materialization remain required. f98a38666f269684126ca51e1947e81f5276b7c297a654bbca097958b6f3b5a9
+internal/host/claimunit_binding.go claimUnitBindings.establish authority E2 PROPOSED E49: root; named placement/program readback from retained root binding, durable unit intent and inode-conditional retirement, and readiness socket ownership. Service route fields are candidates only; final root launch rechecks independently. No no-census path added. Full always-running adoption/handover remains pending. PROPOSED E50: Root startup may supply a pre-resolved claim owner, rechecked against the local installation under host exclusion. A corrupt unrelated record does not block this host; canonical unit uniqueness and owned materialization remain required. ef56f2db27a3d4a95803407a4b389b402592ed5b450bb6723aeffac42a041cab
internal/host/claimunit_materialize.go claimUnitMaterializer.load authority E2 PROPOSED E49: root; named placement/program readback from retained root binding, durable unit intent and inode-conditional retirement, and readiness socket ownership. Service route fields are candidates only; final root launch rechecks independently. No no-census path added. Full always-running adoption/handover remains pending. PROPOSED E50: Root durable materialization decode validates the migration intention, predecessor unit/invocation/cgroup identity and definition digest. file:Read@130 552a22f6d99ad0efd966645e988ad4f839ec2009060cdbd64befe23b7d908b7c
internal/host/claimunit_program.go ClaimLaunchRoot.boundLaunchSpec authority E2 PROPOSED E49: root; named placement/program readback from retained root binding, durable unit intent and inode-conditional retirement, and readiness socket ownership. Service route fields are candidates only; final root launch rechecks independently. No no-census path added. Full always-running adoption/handover remains pending. 7738f116f2fffb3d2ded6c31c2a7680ab509bba27d52fac85e3ea64f416318f4
internal/host/claimunit_handover.go claimUnitMaterializer.handover authority E2 PROPOSED E49: root; retained retired predecessor binding and exact tombstone inode authorize only same-shape successor installation. The caller independently resolves the current root installation. PROPOSED E51: Root compares persistent predecessor/tombstone identity before successor replacement; owner and shape checks remain exact. dfd500b03a93044c4a8c9f67a8eeee0408e4ff163184197a8eb8fe40ee8ff351
# PROPOSED E49
-internal/host/claimunit_production.go bindProductionClaimUnits authority E2 PROPOSED E49: root; named placement/program readback from retained root binding, durable unit intent and inode-conditional retirement, and readiness socket ownership. Service route fields are candidates only; final root launch rechecks independently. No no-census path added. Full always-running adoption/handover remains pending. PROPOSED E50: Root installs retained-placement stop/retirement callbacks, resumes saved retirement before binding, and isolates each configuration/program/materialization failure. Initial binding reads occur outside host exclusion then local owner is checked inside. Readiness looks up the reserved host, not a startup list. PROPOSED E51: Root supplies the kernel boot reader to predecessor capture and stop; startup retirement replay remains ahead of per-unit binding. PROPOSED E52: Root wires peer-verified flat-socket capture and exact retained socket parking into canonical unit migration; no-census path unchanged. PROPOSED E54: root skips named-unit binding/materialization until reserved always-running startup exists; retirement recovery still precedes that gate, and backing=unit retains its existing path. Root witness rejects replacement of live TCP and socket predecessors. file:Read@1150 f8cc464409153f09a65a429fabb17dc52d58df5ae170a2eab41e58f81ea04204
-@coverage internal/host 2b4a4ad57ecf851e00b65bb565a4bc07b23b6cfe3415caee0b3cc0880d884e55
+internal/host/claimunit_production.go bindProductionClaimUnits authority E2 PROPOSED E49: root; named placement/program readback from retained root binding, durable unit intent and inode-conditional retirement, and readiness socket ownership. Service route fields are candidates only; final root launch rechecks independently. No no-census path added. Full always-running adoption/handover remains pending. PROPOSED E50: Root installs retained-placement stop/retirement callbacks, resumes saved retirement before binding, and isolates each configuration/program/materialization failure. Initial binding reads occur outside host exclusion then local owner is checked inside. Readiness looks up the reserved host, not a startup list. PROPOSED E51: Root supplies the kernel boot reader to predecessor capture and stop; startup retirement replay remains ahead of per-unit binding. PROPOSED E52: Root wires peer-verified flat-socket capture and exact retained socket parking into canonical unit migration; no-census path unchanged. PROPOSED E54: root skips named-unit binding/materialization until reserved always-running startup exists; retirement recovery still precedes that gate, and backing=unit retains its existing path. Root witness rejects replacement of live TCP and socket predecessors. file:Read@1275 e4b9166fe33bcbafe91efb09d99d139d1f697df35dd7cff6765c9e483972d4c5
+@coverage internal/host a677b583bf8038d28c61ee49fad2ed734f068a1127f3cbfeb3c7e3f623e9758f
# PROPOSED E50: root unit migration and R-E50 safety gates
internal/caps/converge.go Converge authority E2 PROPOSED E50: Root capability producer calls CheckHost before any filesystem or systemd effect. 30fc6af73046bfd8a30fd8097573a5e774b3fe987ec1ee49d3b08e20bdc23761
@@ -495,9 +495,9 @@ internal/caps/converge.go Restart authority E2 PROPOSED E50: Root capability res
internal/caps/converge.go Restore authority E2 PROPOSED E50: Root capability rollback calls CheckHost before restoring a drop-in or restarting. a01809a6dd4215271e7ceda81618aa01f8d58d13f84b670b9e366f34a410f99c
internal/caps/converge.go type Ops authority E2 schema-only PROPOSED E50: Root capability callback includes an independent pre-effect host gate used by census-bound operations; unrelated callers keep nil semantics. file:Query@347 9336d819ed8ab77a2716fa56538bffed55b3bba6f2a07c624965be493db6c15f
internal/host/caps_pass.go capabilityPass authority E4,J1,J4 root-route PROPOSED E50: Root capability pass consumes its caller-installed CheckHost callback. Production adoption installs the census-bound refusal on both convergence paths; fixture/legacy nil callbacks keep prior semantics. whole:Route@2320,whole:Route@596,whole:Route@657 00565b36d74d8700ca17d9b190e49db161b98117e7d98249012901a6f1aec7de
-internal/host/claimunit_effective.go claimEffectiveUnit authority E2 PROPOSED E50: Root checks loaded fragment, all drop-ins, ExecStart, UID/GID, network and sandbox properties. Only exact root-generated reservation bytes in the private runtime directory are permitted. PROPOSED E51: Root verifies loaded socket-bind restrictions and absence of edge stop/restart coupling alongside its fragment/drop-in and sandbox checks. file:ReadAll@2841 ea61db9fbf557b92600ad659c07b571c9cd5a21b4d20cd124515fcdd75c08f94
+internal/host/claimunit_effective.go claimEffectiveUnit authority E2 PROPOSED E50: Root checks loaded fragment, all drop-ins, ExecStart, UID/GID, network and sandbox properties. Only exact root-generated reservation bytes in the private runtime directory are permitted. PROPOSED E51: Root verifies loaded socket-bind restrictions and absence of edge stop/restart coupling alongside its fragment/drop-in and sandbox checks. file:ReadAll@3614,file:ReadAll@643,file:ReadFile@457 7f02ae5a7047e09fffb5ad8dd8fd5e4336236f5f5128b77d41bef043171b66eb
internal/host/claimunit_effective.go claimIPSet authority E2 PROPOSED E50: Root normalizes systemd IP prefixes for exact network-policy comparison; malformed prefixes refuse. afa75b50fae4f7d0a785fc2fcfc03f27b68b4accbc8bcf6b1404984a7df282fc
-internal/host/claimunit_effective.go claimReservedDropin authority E2 PROPOSED E50: Root constructs exact expected reservation drop-in bytes from the retained host identity and fixed private paths; a caller cannot choose bind destinations. data-literal@253 759da720bdcb3a0e3f0b6b80325653d6704a56b3187871c5e763b1a2040f670e
+internal/host/claimunit_effective.go claimReservedDropin authority E2 PROPOSED E50: Root constructs exact expected reservation drop-in bytes from the retained host identity and fixed private paths; a caller cannot choose bind destinations. data-literal@382 bf9e5d43b589d965ee9d9af968610bab7342e274eba33dcea3a1f76c3bda50d3
internal/host/claimunit_migrate.go claimPredecessorPlacement authority E2 PROPOSED E50: Root accepts only the exact system.slice service path or systemd default instantiated-service slice for the canonical unit. 7c2248d3c2a76d2c46d813ab960dbc80095db5348a21bd1fc709633810a4be4a
internal/host/claimunit_migrate.go claimUnitMaterializer.finishMigration authority E2 PROPOSED E50: Root resumes durable migration; exact definition identity gates replacement and the complete predecessor cgroup must be empty before materialization completes. PROPOSED E51: Root compares persistent operator-definition identity before stopping the predecessor and replacing its bytes. PROPOSED E52: Root independently stops the retained predecessor before parking its socket and replacing its definition; retry repeats the socket identity gate before recording completion. 925389d58a0c061f6999cacc5a28b9e80ab32b2713768fbdfcb5674ac5fdfa38
internal/host/claimunit_migrate.go claimUnitMaterializer.migration authority E2 PROPOSED E50: Root accepts an existing operator definition only with canonical digest consent and independently captured systemd invocation plus cgroup inode. PROPOSED E52: Root captures the operator socket peer and boot-local inode identity before saving migration intent; missing capture/park callbacks refuse. a4769ec9098d9d8a549e803dd025704d7b2fea1b1b8fa748eb620e32380ef0f7
@@ -527,7 +527,7 @@ internal/host/claimunit_effective.go claimEffectiveRefusal.Error diagnostic E2 P
internal/host/claimunit_effective.go claimEffectiveRefusal.Unwrap diagnostic E2 PROPOSED E50: Root diagnostic exposes the underlying refusal to errors.Is; it grants no operation authority. 8678c5f6cd504036763f2fb1617f64cbe1c696cfac6bf71d40e0c3a459170a43
# PROPOSED E50: root unit migration and R-E50 safety gates
-internal/host/claimunit_dropin_linux.go openClaimUnitDropin authority E2 PROPOSED E50: Root opens the validated unit name beneath checked /run/systemd/system using no-follow descriptors; the unit directory and regular single-link file must be root-owned and not group/world-writable. Instantiated @ names are accepted without relaxing trust. file:Fstat@594,file:Fstat@884,file:Open@190,file:Openat@396 bd769502743c400fa31ee7b1cf125d52fc03f7dd6f5bd99e3195c2e60be95cb9
+internal/host/claimunit_dropin_linux.go openClaimUnitDropin authority E2 PROPOSED E50: Root opens the validated unit name beneath checked /run/systemd/system using no-follow descriptors; the unit directory and regular single-link file must be root-owned and not group/world-writable. Instantiated @ names are accepted without relaxing trust. file:Fstat@694,file:Fstat@984,file:Open@290,file:Openat@496 f9a9915d67e7f8e4a540a484a7c978bf2d22eb803c04564349f9db5df119de6b
internal/host/claimunit_dropin_other.go openClaimUnitDropin authority E2 PROPOSED E50: Root opens the validated unit name beneath checked /run/systemd/system using no-follow descriptors; the unit directory and regular single-link file must be root-owned and not group/world-writable. Instantiated @ names are accepted without relaxing trust. bb3a29d1ad8840139f7236971ecb2bae93e6323007f2fdd625114bb3ff105397
internal/host/claimunit_migrate_linux.go claimUnitBoot authority E2 PROPOSED E51: Root reads the kernel boot UUID to scope predecessor cgroup identities. file:ReadFile@52 2b30b38b42041e83aff15674c164ede8702a800110f3c944a761244b3e47ef33
internal/host/rootgeneration_kernel_linux.go generationCurrentFiles authority E2 PROPOSED E51: Root reopens the canonical executable through trusted ancestry and its containing tree; persistent proof rebinds only the transient scanner copy to current devices. Historical pins without proof refuse across boots. file:Open@219 1c1a5ebdd68091af35b8e26cf07d20e623b54e7109a64cffd5a55017fd721fa4
@@ -592,3 +592,5 @@ internal/consolecmd/main.go serve authority E2 accepted-legacy;rev3=census-rulin
internal/consolecmd/member.go runMember authority E2 accepted-legacy;rev3=J4;basis=root-input; PROPOSED E57 R-E56-1: console service or console administrator identity; unchanged command entrypoint now shared by production binary and self-contained rootrunner data-literal@3649 b742c773fa52c99a85b18f699ab01bf5e80f8d68fc647e29dd996af8bda00303
@coverage cmd/console 7ff6a1c77ce512d21215ca6c81aa529aa3a20cfd285be84211c65db6aeffd733
@coverage internal/consolecmd d10e51824ca6f7ca79c9288bfcfc3eadcd8db8391827b412735b1a4e9d3c1ff6
+internal/host/claimunit_console.go verifyClaimConsoleEnrollment authority E2 PROPOSED E58: root instance-control uid 0; opens only /etc/carlos/host-actor.json through checked root-owned non-writable ancestry before materialization verification authorizes console execution. 394ef75fd2470ac51123a4315ccc5765784a5bf6f9da31a4c601063ad226226e
+internal/host/claimunit_console.go checkClaimConsoleEnrollment authority E2 PROPOSED E58: root instance-control uid 0; bounded decoding rejects foreign box/enrollment, missing instance identity and trailing data; the console independently compares the enrolled instance with live hardware/IMDS inside containment. e116c478a817ecf7bb62856946eba3c91fb581f1b4fa0ada75500c3a32eacd6b
diff --git a/internal/provision/testdata/plan14-root-reads-p3.tsv b/internal/provision/testdata/plan14-root-reads-p3.tsv
index ea799d0c0..c2df3830a 100644
--- a/internal/provision/testdata/plan14-root-reads-p3.tsv
+++ b/internal/provision/testdata/plan14-root-reads-p3.tsv
@@ -24,11 +24,11 @@ internal/host/rootmigration_production_linux.go migrationExecutionEmpty authorit
internal/host/rootmigration_production_linux.go publishMigrationReceipt authority G,J4 rev3=PROPOSED-p3;identity=carlos system journal-migrate, offline operator CLI, uid 0;basis=reconstructs immutable key/digest from original signed receipt bytes without changing evidence time db91dbe64b57fe10aa1635fb46938e2ccb585afb6aa138800507acac6a83c377
internal/host/rootmigration_production_linux.go randomMigrationID authority G,J4 rev3=PROPOSED-p3;identity=carlos system journal-migrate, offline operator CLI, uid 0;basis=cryptographic nonce identifies newly collected stop evidence, never authorizes a stop file:Read@64 c2779c80b5628eca7d484ffba0b289c1068317b1f474c06e1ee5b39c3f769196
internal/host/rootmigration_production_linux.go rootMigrationLegacySuspects authority G,J4 rev3=PROPOSED-p3;identity=carlos system journal-migrate, offline operator CLI, uid 0;basis=agent-writable hostguard filenames only identify unexplained hosts to refuse, never authorize inventory file:Readdirnames@277 6bfa73d903baa8a028abb007a7ec1da0066dbcda966e92e29b50a2b56fda10ab
-internal/host/rootmigration_production_other.go migrateRootClaimAuthority authority G,J4 rev3=PROPOSED-p3;run=13;identity=offline migration uid 0;basis=takes mandatory release exclusion even before census exists, rechecks canonical cohort eligibility, and refuses active legacy root mutators before state/census transition 327e0adfe387eac7bc74b87258efc9831f5a35ee9c9c9e71640d4e6b54853aae
+internal/host/rootmigration_production_other.go migrateRootClaimAuthority authority G,J4 rev3=PROPOSED-p3;run=15;identity=non-Linux offline root migration;basis=unsupported-platform stub refuses only; performs no locking, opening or publication 327e0adfe387eac7bc74b87258efc9831f5a35ee9c9c9e71640d4e6b54853aae
internal/host/rootmigration_startup.go var openRootMigrationParticipation authority G,J4 rev3=PROPOSED-p3;identity=root claim controller construction uid 0;basis=fixed production participation opener remains rootparticipation.OpenForUpdate; package tests substitute explicit no-census fixtures without accessing the machine store 53a17d908b433d98cdb817ab72543bd53788962625ccc9cfa0e48f829bbc1aa3
internal/host/rootmigration_startup.go verifyRootMigrationFloors authority G,F,J4 rev3=PROPOSED-p3;run=13;identity=root startup/migration uid 0;basis=requires certified current and guard repair executors and readable local bounds; a bound above installed executors emits the existing alert rather than blocking repair construction 7e558cfe3164a5a2d25a88dad074b85e1a23325ae98e777dac6c6c3543bc1228
internal/host/rootmigration_startup.go verifyRootMigrationRetained authority G,F,J4 rev3=PROPOSED-p3;identity=root claim controller construction uid 0;basis=requires each migrated installation to retain its old-session deny, exact stopped predecessor receipt and complete root inventory f2dc230494f4659e6d4bf71961d617854454bd8f713585a1cd4489414c288cb1
-internal/host/rootmigration_startup.go verifyRootMigrationStartup authority G,F,J4 rev3=PROPOSED-p3;identity=root claim controller construction uid 0;basis=checks root-owned migration records and typed journal revisions before issuing startup grants; absent census leaves main path intact file:Open@337 b5149f8db369dbf474612ec6f6afe55c518f337f619479bdfeb648f518d4c5af
+internal/host/rootmigration_startup.go verifyRootMigrationStartup authority G,F,J4 rev3=PROPOSED-p3;run=15;identity=root instance-control startup and recovery uid 0;basis=validates retained recovery census and migration identity, including pending acquisition, then complete installation inventory and repair floors; execution admission remains behind verified broker settlement and complete census file:Open@345 6508eb50ecb7cc517041c7f5eb431a597c282274945c0dab2542e6416b3edc9e
internal/host/rootmigration_startup.go verifyRootMigrationState authority G,F,J4 rev3=PROPOSED-p3;run=13;identity=root startup/recovery uid 0;basis=validates retained migration header then complete installations after pending recovery has settled 531c7d47066a7dfad787aed67f08ba6078091c7b9426d8f685872c4948c2d2ba
internal/host/rootmigration_stopped_linux.go verifyMigrationStopped authority G,J4 rev3=PROPOSED-p3;identity=carlos system journal-migrate offline operator uid 0;basis=root checks real proc/cgroup mounts, closed writer journal and absence of cgroups and held inventory descriptors; all observations can only refuse recovery file:ReadFile@588 06b82196857608ace8bfaa8ce7108caad961868b530a0d9cb152326a1aaf6819
internal/host/rootmovesfloor.go openRootMovesFloors authority F,J4 rev3=PROPOSED-p3;identity=root adopt refresh, root system update/recovery and root guard floor readers, uid 0;basis=selects fixed moves namespace then delegates checked root store opening 4af0bda66779af3474e64fda54015763761d058feead1b8a2ebf0c2478a05a3d
@@ -38,7 +38,7 @@ internal/host/rootmovesfloor.go rootMovesFloors.persist authority F,J4 rev3=PROP
internal/host/rootmovesfloor.go rootMovesFloors.projection authority F,J4 rev3=PROPOSED-p3;identity=carlos system apply/recover and root adopt, systemd root units or offline CLI, uid 0;basis=matches public acceptance to private generation, identity and retained componentwise maximum file:Read@109 6b0ed0053db247385ccbabfd95a738fd48a8d27b14882b5e73b4e5a82eda26b6
internal/host/rootmovesfloor.go rootMovesFloors.reconstruct authority F,J4 rev3=PROPOSED-p3;identity=offline root reconstruction capability, uid 0; no production caller installed;basis=requires private root-issued box/generation/nonce token and refuses weakening; no production caller installed 1a6b2923d55208b8a53cfd17d3cf84f01087485f7b5cadc9012cb1b123120038
internal/host/rootmovesfloor.go rootMovesFloors.record authority F,J4 rev3=PROPOSED-p3;identity=carlos system apply/recover and root adopt, systemd root units or offline CLI, uid 0;basis=bounded strict private JSON read requires matching deployment, box and enrollment file:Read@108 2e2bc3e9e915e4a0a70c40933de3928284bbdab25ee223e4cf05500a12913b83
-internal/host/rootmovesfloor.go rootMovesFloors.refresh authority F,J4 rev3=PROPOSED-p3;identity=carlos system apply/recover and root adopt, systemd root units or offline CLI, uid 0;basis=normal refresh ratchets accepted history; unknown history persists only observed denies and never advertises accepted cb70a1eefbb4f8f243090270f23141c9c9a773ee278af002c79a6c3ef6b7c3b7
+internal/host/rootmovesfloor.go rootMovesFloors.refresh authority F,J4 rev3=PROPOSED-p3;run=15;identity=normal root adopt/update refresh uid 0;basis=normal refresh ratchets accepted history; unknown history persists only observed denies and never advertises accepted cb70a1eefbb4f8f243090270f23141c9c9a773ee278af002c79a6c3ef6b7c3b7
internal/host/rootmovesfloor.go rootMovesFloors.selector authority F,J4 rev3=PROPOSED-p3;identity=root apply/recover/adopt units uid 0 and agent edges publisher carlos-edge.service uid carlos;basis=strict bounded canonical protocol/release line supplies independent crash-surviving deny file:Read@73 5c46438590323610c67dc5bbfaa191fc5ba23e3b280b6063dbd48e48e7ce3927
internal/host/rootmovesfloor_authorization.go rootMovesFloors.authorizeReconstruction authority F,J4 rev3=PROPOSED-p3;identity=offline root reconstruction capability, uid 0; no production caller installed;basis=issues private nonce capability for this root identity and next generation under stable lock; no production caller file:Read@426 a5235f6aab5714cee3e3e41604eed5b1eff7aa4355f9444f44c4811f7dfbbc08
internal/host/rootmovesfloor_authorization.go rootMovesFloors.authorizedReconstruction authority F,J4 rev3=PROPOSED-p3;identity=offline root reconstruction capability, uid 0; no production caller installed;basis=requires exact private persisted nonce, box identity and next generation; caller strings cannot authorize file:Read@249 c449d7087a6cbcdf4388b7945e84045a0b895349b1bc14347aca7040c822391c
@@ -47,13 +47,13 @@ internal/host/rootmovesfloor_boot.go rootFloorBootCandidate authority F,J4 rev3=
internal/host/rootmovesfloor_boot.go rootFloorJournalPresent authority F,J4 rev3=PROPOSED-p3;identity=carlos system apply/recover and root adopt, systemd root units or offline CLI, uid 0;basis=checked authority-file presence selects recovery without opening a deployment store b189d04dc579f4825f5668efd1f93f178acca738bb2ed886a14f3b5321d7755b
internal/host/rootmovesfloor_boot.go rootFloorPrevVerdict authority F,J4 rev3=PROPOSED-p3;run=13;identity=root boot/recovery selector uid 0;basis=reauthorizes previous bytes against signed root identities and accepted verdict; boot-local device and inode numbers cannot reject unchanged signed repair bytes file:Read@436 ad5acd71daa00ffbe34fe9b3cb7224b4d6ef1d9b21d1f3e6b2d1d213c88e2e4e
internal/host/rootmovesfloor_boot.go withRootFloorGuardCandidate authority F,J4 rev3=PROPOSED-p3;identity=carlos system guard-exec apply/recover, systemd root units uid 0;basis=release then previous-binary exclusion; participation check precedes moves and seeds locks; certified current remains executor fallback file:Open@218 74d00903cf028a74237153d25879f768a3bd8546cb107d85b4d11feb7f77e04c
-internal/host/rootmovesfloor_identity.go rememberRootFloorSystemIdentity authority F,J4 rev3=PROPOSED-p3;identity=carlos system apply/recover and root adopt, systemd root units or offline CLI, uid 0;basis=re-fetches and verifies signed pointer equality before retaining its executable identity file:Open@543 b4e56a0637c28e5fb65d3a33b7dfd1fea8bed55acbf5231bf26e0011168141fe
+internal/host/rootmovesfloor_identity.go rememberRootFloorSystemIdentity authority F,J4 rev3=PROPOSED-p3;run=15;identity=normal root apply pointer verification uid 0;basis=called from apply pointer verification; re-fetches signed pointer equality before retaining executable identity; never recovery or adopt file:Open@543 b4e56a0637c28e5fb65d3a33b7dfd1fea8bed55acbf5231bf26e0011168141fe
internal/host/rootmovesfloor_refresh.go readRootFloorBinary authority F,J4 rev3=PROPOSED-p3;identity=root adopt refresh, root system update/recovery and root guard floor readers, uid 0;basis=verifies checked executable and signed system manifest; reads signed moves and seeds protocol numbers rather than version sidecar file:Open@684,file:Read@941,file:Stat@276 2a23dd9c46c6fa56476ecfa4061712f05c6f1a1a098295e14f395def2d24324a
internal/host/rootmovesfloor_refresh.go rootFloorAdvertisementFor authority F,J4 rev3=PROPOSED-p3;identity=carlos agent edgesFileLoop, carlos-edge.service uid carlos;basis=agent reports accepted only for matching generation with signed compatible current and previous verdict file:Read@203 4e464b6add24a06b1c3d7a5859be7a1055750bc25f27c8ca641241be3baa2ef8
internal/host/rootmovesfloor_refresh.go rootFloorBinary.supports authority F,J4 rev3=PROPOSED-p3;identity=root apply/recover/adopt units uid 0 and agent edges publisher carlos-edge.service uid carlos;basis=signed digest/release are required; protocol minimum applies only when a retained floor exists, preserving unknown-history permission 7a6eaf5432f2668aa47f24d9c667355a985f83eadb8c4619a704168288b16fbf
internal/host/rootmovesfloor_refresh.go rootFloorDowngradeIdle authority D,E2,F,J4 rev3=PROPOSED-p3;identity=root adopt, guard boot selector and system apply/recover uid 0;basis=uses E network-free retained generation obligations and kernel census under participation before floor; borrowed transaction matches the original root store; no deployment provider read file:Open@171,file:Open@612 d49e47d72f752d492a4c56ed73d4af53ffaacd5e95cbc147998e173fcadf2793
internal/host/rootmovesfloor_refresh.go rootFloorJudge.judge authority F,J4 rev3=PROPOSED-p3;identity=carlos adopt normal refresh and carlos system apply notePrev, uid 0;basis=observes participation before floor lock; writes identity-bound current/prev verdict even under unknown history without blocking repair executor 95062652c0403183b18c074da17d7e4c504f7fd6deab7148668a2dea03711e9c
-internal/host/rootmovesfloor_refresh.go rootFloorJudge.refresh authority F,J4 rev3=PROPOSED-p3;identity=carlos system apply/recover and root adopt, systemd root units or offline CLI, uid 0;basis=refreshes retained floor before signed current/previous judgement, even when previous is absent 362798a60b4e3d6fc798db52bfc204979f724efe33a858f3d7ca2c1ca12f6eaa
+internal/host/rootmovesfloor_refresh.go rootFloorJudge.refresh authority F,J4 rev3=PROPOSED-p3;run=15;identity=normal root adopt/update refresh uid 0;basis=refreshes retained floor before signed current/previous judgement, even when previous is absent 362798a60b4e3d6fc798db52bfc204979f724efe33a858f3d7ca2c1ca12f6eaa
internal/host/rootmovesfloor_refresh.go rootRefreshAndJudgeMovesPrev authority F,J4 rev3=PROPOSED-p3;identity=carlos adopt root pass, root unit uid 0;basis=adopt opens deployment store then refreshes floor before any no-prev return 2d4d1ce1d6143555f3b94b19359c014ffb20272f41f756f7c02bbf6ff29a70a9
internal/host/rootmovesfloor_refresh.go withRootMovesFields authority F,J4 rev3=PROPOSED-p3;identity=carlos agent edgesFileLoop, carlos-edge.service uid carlos;basis=agent publishes only checked root public verdict and explicitly quarantines unreadable participant state file:Open@405 29fb652e22df888f18c691b91db6b83b9781d89d005a2fae431d8102e77b2f88
internal/host/rootmovesfloor_source.go readEffectiveMovesFloor authority F,J4 rev3=PROPOSED-p3;identity=root adopt refresh, root system update/recovery and root guard floor readers, uid 0;basis=census chooses local root protocol bound; no census chooses existing legacy reader a6420f9074a459fa5107cbd60ca18828f0dec132eb53add849152db2a9b0f62a
@@ -61,9 +61,9 @@ internal/host/rootmovesfloor_source.go readRootFloorProjection authority F,J4 re
internal/host/rootmovesfloor_source.go readRootLegacyProtocolJSON authority F,J4 rev3=PROPOSED-p3;identity=carlos adopt and system apply refresh, uid 0; no recovery or boot-selector call;basis=bounded descriptor read of optional legacy moves or seeds mirror; shape validation and caller denial-only merge prevent acceptance file:ReadAll@441,file:Stat@278 835f913509edbe65d72986bb23e7af580aaf2d4cc222e87064e29d0d16e61103
internal/host/rootmovesfloor_source.go readRootProtocolBound authority F,J4 rev3=PROPOSED-p3;identity=root adopt refresh, root system update/recovery and root guard floor readers, uid 0;basis=reads local root accepted and retained deny state; public-only deny survives missing private history; missing unknown history is not acceptance file:Open@187 2e35ae8bdee1433877d928f22b4a3315dfd2027378a71124ca361284323c22b2
internal/host/rootmovesfloor_source.go readRootProtocolProjection authority F,J4 rev3=PROPOSED-p3;identity=carlos agent in carlos-edge.service uid carlos;basis=checked public root projection must agree with selector and directory sync before advertising acceptance file:Open@86,file:Read@232 36f45f87d3cabebaaf759e2cd2b3c4397a5247247e329a0fd71de291ad1a2bf0
-internal/host/rootmovesfloor_source.go refreshRootMovesFloor authority F,J4 rev3=PROPOSED-p3;identity=carlos system apply/recover and root adopt, systemd root units or offline CLI, uid 0;basis=normal adopt/update combines remote object with independent deny-only legacy evidence under root floor lock a41df2c69e7d869b5232d545440b8135e8c7124d490965e641b8d1fba83cb490
+internal/host/rootmovesfloor_source.go refreshRootMovesFloor authority F,J4 rev3=PROPOSED-p3;run=15;identity=normal root adopt/update refresh uid 0;basis=normal adopt/update combines remote object with independent deny-only legacy evidence under root floor lock a41df2c69e7d869b5232d545440b8135e8c7124d490965e641b8d1fba83cb490
internal/host/rootmovesfloor_source.go rootFloorLegacy authority F,J4 rev3=PROPOSED-p3;identity=carlos adopt and system apply refresh, uid 0; no recovery or boot-selector call;basis=selects existing moves mirror paths for denial-only generic legacy reader e5dacb9fa26be5f5f961e99909624f38643f93c43432ff3dce1cff552f7e486a
-internal/host/rootmovesfloor_source.go rootMovesFloorIdentity authority F,J4 rev3=PROPOSED-p3;identity=carlos system apply/recover and root adopt, systemd root units or offline CLI, uid 0;basis=root-only census reader supplies deployment/box/enrollment; edge mode cannot call it 457cdc76e2cff8fdfe4d146c7baf086dd58d54f151ce9416c32efd0a03778d98
+internal/host/rootmovesfloor_source.go rootMovesFloorIdentity authority F,J4 rev3=PROPOSED-p3;run=15;identity=root update/recovery/guard uid 0;basis=identifies root deployment and enrollment from retained RecoveryCensus even during pending acquisition; identifies floor authority without granting execution admission 440cbd8eb3dd525bf55eb0bdd131825565ef5c15003326399e83f84747033f7c
internal/host/rootmovesfloor_source.go rootProtocolLegacy authority F,J4 rev3=PROPOSED-p3;identity=carlos adopt and system apply refresh, uid 0; no recovery or boot-selector call;basis=bounded agent-writable JSON and line mirrors contribute only increasing denies; neither can establish accepted state file:ReadAll@729,file:Stat@537 0b62a121d683312c34a5c7af486d60d496482ef25502e8e2ef0ef18b5e7c6fd6
internal/host/rootmovesfloor_source.go withEffectiveMovesFloorLock authority F,J4 rev3=PROPOSED-p3;identity=carlos system apply/recover final activation, uid 0;basis=locks moves then seeds private root stores across final activation; no census uses unchanged legacy lock 752817cc4fc1110aa055324bedb2b9e2b7da7f87445b48c8290ebb53ea3d85d7
internal/host/rootseedsfloor.go judgeRootSeedsPrev authority F,J4 rev3=PROPOSED-p3;identity=carlos adopt and system apply refresh, uid 0; no recovery or boot-selector call;basis=evaluates signed current and prev seeds support under shared durable floor judge and independent downgrade completeness 79ac6f71f9588f9f7757663ca7a9bc2d014253ff93153949ec30fd17d3cbc9d4
@@ -77,9 +77,9 @@ internal/host/rootseedsfloor.go rootSeedsFloorDir authority F,J4 rev3=PROPOSED-p
internal/host/rootseedsfloor.go systemApplier.rootProtocolAllowsBinary authority F,J4 rev3=PROPOSED-p3;identity=carlos system apply/recover, root systemd units or offline root CLI, uid 0;basis=final installation restore or forward-completion checks signed image against both local root bounds while activation locks are held 9e0321c02fe9ea45685804b9435f340f75117d9e444c98b794676ea66103955e
internal/host/rootseedsfloor.go systemApplier.seedsActivationRefusal authority F,J4 rev3=PROPOSED-p3;identity=carlos system apply/recover, root systemd units or offline root CLI, uid 0;basis=refuses activation below selected local seeds release bound; missing unknown history grants no acceptance projection abc430ca5063fde4bcfdd0a795c1f593324c0dce6f6d9330abbb519917e07aa7
internal/host/rootseedsfloor.go withRootSeedsFields authority F,J4 rev3=PROPOSED-p3;identity=carlos agent in carlos-edge.service uid carlos;basis=advertises only checked synced root seeds projection matching deployment and box; no valid projection stays quarantined file:Open@413,file:Read@531 f53f45621ab1e7bba07315980de981b321826b5985350489ee686d952480bc13
-internal/host/seedsfloor.go systemApplier.armSeedsFloor authority F,J4 rev3=PROPOSED-p3;identity=carlos system apply/recover, root systemd units or offline root CLI, uid 0;basis=normal apply refreshes root seeds history on census participants; nonparticipants retain legacy sync; never called by recovery or boot ace92bbe72e79e998daeebfe5fc2380ce6174cc0241b60a26e0093bea614bce1
+internal/host/seedsfloor.go systemApplier.armSeedsFloor authority F,J4 rev3=PROPOSED-p3;run=15;identity=normal root apply uid 0;basis=normal apply refreshes root seeds history on census participants; nonparticipants retain legacy sync; never called by recovery or boot ace92bbe72e79e998daeebfe5fc2380ce6174cc0241b60a26e0093bea614bce1
internal/host/seedsfloor.go withSeedsFields authority F,J4 rev3=PROPOSED-p3;identity=carlos agent in carlos-edge.service uid carlos;basis=root participation selects checked root projection; legacy agent mirror remains unchanged for nonparticipants file:Lstat@84 57febc09559f80119ba36ca13188febf0ceb27bcdc29d829403798aba1cb6f1d
-internal/host/systemapply.go RunSystemApply authority D,F,J4 rev3=PROPOSED-p3;identity=carlos system apply/recover root systemd units uid 0;basis=registers local launch verifier and delegates to the census-gated outer release wrapper while retaining local root floor checks and main no-census path 9111d10530cef84980095152bb1a8be5c3299c6fcdc2c0a50b3055957541f0d8
+internal/host/systemapply.go RunSystemApply authority D,F,J4 rev3=PROPOSED-p3;run=15;identity=root apply/recovery uid 0;basis=registers local verifier and invokes census-gated release wrapper; absent census executes main apply directly even with leftover release state 9111d10530cef84980095152bb1a8be5c3299c6fcdc2c0a50b3055957541f0d8
internal/host/systemapply.go systemApplier.activationGuard authority D,F,J4 rev3=PROPOSED-p3;identity=carlos system apply/recover root units and offline root CLI uid 0;basis=release exclusion then scoped participation transaction then effective moves/seeds floor and registry; signed candidate checks remain inside guard; nested journal close reuses live exclusion whole:Route@1315 9d8cb52184b6fd2c4a071962515b0162a99af758954c18b72384e8e9c29fd7c8
internal/host/systemapply.go systemApplier.activationRule authority F,J4 rev3=PROPOSED-p3;identity=carlos system apply/recover, root systemd units or offline root CLI, uid 0;basis=preliminary activation refuses against both local protocol bounds before existing hold checks whole:Route@1036,whole:Route@827,whole:Route@834 d7a045107010e06f1b93f2920b42d0b464c6a0a71ed5cad0d15c2473fd81c191
internal/host/systemapply.go systemApplier.armFloor authority F,J4 rev3=PROPOSED-p3;identity=carlos system apply/recover, root systemd units or offline root CLI, uid 0;basis=normal update refreshes root moves and seeds histories in census mode; unknown history remains unacknowledged; no recovery or boot refresh d7e16eaaa3994896bf9acd763777cc4d8998056dfa2d492c5320395102009a16
@@ -97,12 +97,12 @@ internal/host/units/carlos-system-update.service external authority F,J4 rev3=PR
internal/hostbox/migration_census_linux.go CheckMigrationRoots authority G,J4 rev3=PROPOSED-p3;identity=root claim retirement or offline migration uid 0; shared helpers also run for legacy agent requests without root authority;basis=checks retained materialized roots against persistent filesystem evidence before census migration accepts coverage;rev3=PROPOSED-p3;run=11;identity=root claim, migration or adoption controller uid 0;basis=merges E FileIdentity callers and P3 authority effects; retains the recorded family checks and both tracks behavioral fixtures file:Lstat@932,file:WalkDir@1202 a9491adef92ee5948b6d8840b047f2ead713c043c65c499c124d5bda62ce51fc
internal/hostguard/guard.go record.admits authority G,J4 rev3=PROPOSED-p3;identity=root claim controller construction uid 0;basis=root admission retains migration denial until the exact predecessor session has a completed retained receipt 5864d1fa79a66ca6f69057416079b46524ed4bfdf1ce1de88124b5a24a428418
internal/hostguard/root.go Guard.CommitRootInventoryArtifacts authority G,J4 rev3=PROPOSED-p3;identity=root claim controller construction uid 0;basis=root inventory commit admits a newer migration writer only after its exact predecessor completed; old sessions stay denied 08e42f8810e2645a875b5177fc9e104ed4b307c7d80dc77d204e2516b1bd66de
-internal/hostguard/root.go RootAuthority.read authority C,G,J4 rev3=PROPOSED-p3;identity=carlos instance-control, root control service uid 0; denial views also consumed by carlos agent uid carlos;basis=strict checked root record decoding includes retained migration session denials file:Read@175 7ff862f573a05a17951546a56ff791d42962d693331a69e8338bd8eb4f283d6b
-internal/hostguard/root.go RootAuthority.write authority C,G,J4 rev3=PROPOSED-p3;identity=carlos instance-control, root control service uid 0; denial views also consumed by carlos agent uid carlos;basis=retains migration denials atomically with the existing root installation, fence and retired-state record 678156528bb5d3e92f8c07835ff7a7d05afb5d498ff604966d2b6d9293867c0c
+internal/hostguard/root.go RootAuthority.read authority C,G,J4 rev3=PROPOSED-p3;run=15;identity=root control/startup/generation uid 0;basis=validates active stamp equality; inactive identity is the exact last root-retained retired tuple, including repair of older records with cleared installation stamp file:Read@175 5c2c9ab014458e086f6df9255e1d967a96fd78cd1e599b891d8369c8506421f8
+internal/hostguard/root.go RootAuthority.write authority C,G,J4 rev3=PROPOSED-p3;run=15;identity=root control uid 0;basis=persists inactive state while retaining historical installation stamp, registration and artifacts; only a nonzero active stamp replaces the retained stamp 5f6db65d9de7b98b119f5f124189e0322336835c9c80fb6c253bb828cfc9e9d2
internal/hostguard/rootmigration.go RootAuthority.RetainMigrationDeny authority C,G,J4 rev3=PROPOSED-p3;identity=carlos system journal-migrate, offline operator CLI, uid 0;basis=root retains old incarnation/stamp/session denial under stable per-host lock; grants no launch authority fb821d19b6ca6e1454d025013d27ef8a856a684262421d3ee76804723e875c37
internal/hostguard/rootmigration.go migrationAdmissionDenied authority G,J4 rev3=PROPOSED-p3;identity=root claim controller construction uid 0;basis=old migrated sessions remain denied; a newer same-installation session requires completed receipt for every retained migration predecessor c7312f5e0e67b2b69bbea1b8f432dd48ba06f0b4a26ebf349b6724769651631e
internal/hostguard/rootmigration.go migrationDenied authority C,G,J4 rev3=PROPOSED-p3;identity=carlos instance-control, root control service uid 0; denial views also consumed by carlos agent uid carlos;basis=old sessions at/below retained captured stamp refuse root and agent admission 6ccf5c00e33d880aa50ea15d2bce22a3cce14455d5cc7f05197e42f789da8805
-internal/hostguard/rootmigration_inventory.go RootAuthority.MigrationInstallations authority C,G,J4 rev3=PROPOSED-p3;run=13;identity=root migration/startup uid 0;basis=enumerates retained installations including zero-revision inventory; pending installation transactions are refused and must first settle through restricted startup recovery ad855b461cb12e5499ecec74b210f6f41a758a3ed36c0eac0ef5931bc7fad6c2
+internal/hostguard/rootmigration_inventory.go RootAuthority.MigrationInstallations authority C,G,J4 rev3=PROPOSED-p3;run=15;identity=root migration/startup uid 0;basis=enumerates active and retired installations from preserved root installation identity; incomplete inventory remains visible, pending transactions must settle before complete enumeration 8c4dee4b5af31ce13c581b20c5cde79f1917720b80e558dda6766cf8548f2a02
internal/hostguard/rootview.go FenceView.admit authority G,J4 rev3=PROPOSED-p3;identity=root claim controller construction uid 0;basis=read-only root view applies same predecessor-completion requirement as locked admission 5bcc6b69bb3e98a1bda6ace2b0b2a147f8c39b61601c609c30e3fb806231efae
internal/hostguard/rootview.go Guard.fenceLocked authority C,G,J4 rev3=PROPOSED-p3;identity=carlos instance-control, root control service uid 0; denial views also consumed by carlos agent uid carlos;basis=projects root-only migration denials into agent admission snapshot without trusting local hostguard records 290295ed69e06548a41e101317098ae2d6aac10c41b260e120af55860686a96d
internal/hostguard/rootview.go RootAuthority.viewLocked authority C,G,J4 rev3=PROPOSED-p3;identity=carlos instance-control, root control service uid 0; denial views also consumed by carlos agent uid carlos;basis=projects root migration denials together with canonical installation and existing fences 5db6824b38f6ad3e4072008ffa3ff9fab98db6a5d40ecba59ca30900e331a46f
@@ -141,7 +141,7 @@ internal/rootparticipation/transaction.go Ledger.lockTransaction authority D,F,J
internal/rootparticipation/transaction.go TransactionLedger authority D,F,J4 rev3=PROPOSED-p3;identity=root system apply/recover uid 0;basis=returns only an active root-minted held ledger; stale and cancelled scopes refuse 2046fbd7b7ec9455794dbaf614ea4cb50b9147929004434a172a67656fb7ff63
internal/rootparticipation/transaction.go type heldTransaction authority D,F,J4 rev3=PROPOSED-p3;identity=root system apply/recover uid 0;basis=binds transaction reuse to a root-opened ledger and live callback lifetime 4769022372ba86be2255c15a53c5d7acd88bc9e50652391e84284a436ae615c7
internal/rootparticipation/transaction.go type transactionKey authority D,F,J4 rev3=PROPOSED-p3;identity=root system apply/recover uid 0;basis=unexported key prevents callers from forging the held participation scope c309e812e20a85927d3cc93c8048d811c7187137797ad76646cc12f892d82a63
-internal/host/systemapply.go systemApplier.runWithRelease authority D,F,J4 rev3=PROPOSED-p3;run=13;identity=root update/recovery uid 0;basis=census updates retain participation release exclusion; legacy updates honor explicit migration exclusion before acquiring apply lock ebdeb0eeb9e4300e3309961994efccab531fc9d2b10fb078f4c34e875d905106
+internal/host/systemapply.go systemApplier.runWithRelease authority D,F,J4 rev3=PROPOSED-p3;run=15;identity=root update/recovery uid 0;basis=retained floor identity permits restricted acquisition recovery; participating execution holds release exclusion; no-census effect is main Run directly 462d91eb211467885431108bc352b1913ea430fca5602434fc9fd7ef39713efa
internal/hostfloor/disk.go Disk.withParticipation authority D,F,J4 rev3=PROPOSED-p3;identity=root system apply/recover or root participation caller uid 0;basis=uses a live root-minted scoped ledger only for its original store; otherwise opens the ordinary census; absent census keeps legacy behavior 3bf4a54d1ab3621eee974a797ffa63a988369b9f245ceddde1cb99cc6386ec82
internal/rootparticipation/ledger.go Ledger.Idle authority D,F,J4 rev3=PROPOSED-p3;identity=root system apply/recover or root participation caller uid 0;basis=checks settled execution and local launch completeness under participation; nested activation uses only its already-held store e50d8f839d39f771ad4b6018f315cd7002a6e470424df0a46482906c31ef575e
internal/rootparticipation/ledger.go Ledger.RecoverAcquisition authority D,F,J4 rev3=PROPOSED-p3;identity=root system apply/recover or root participation caller uid 0;basis=retains acquisition-recovery checks and census commit under the same participation transaction; no new authority from a missing record d1f44274ba0a53aae7e39ce016da787748123665bda0cdf7a40339a4fc52e9fc
@@ -189,7 +189,7 @@ internal/rootparticipation/ledger.go Ledger.RecoverAcquisition authority D,F,J4
@removed internal/rootparticipation/production.go ActivationReady D,G census-ruling-J4
internal/historyreadback/readback.go RequestStatus diagnostic E1 rev3=PROPOSED-p3;identity=diagnostic request library, no production caller;basis=P3-R8 #2; signed readback request only; no reconstruction or retirement d89052b0cdd12c8c8dcbc353e9ae995005caeb88d8e4c70974cb9e6f4bfd46d7
internal/hostbox/migration_census_other.go CheckMigrationRoots hint G,J4 rev3=PROPOSED-p3;identity=non-Linux offline migration caller;basis=P3-R8 #17; unsupported platform refusal only 594ecda260fcf80baf5a2709c9cc5a04e91c655c8bd2f7ac307eef44216eb9c8
-internal/host/rootmigration_production_linux.go migrateRootClaimAuthority authority G,J4,E3 rev3=PROPOSED-p3;run=13;identity=offline migration uid 0;basis=takes mandatory release exclusion even before census exists, rechecks canonical cohort eligibility, and refuses active legacy root mutators before state/census transition file:Open@1768,file:Open@374,file:Open@614,file:Open@7062,file:Open@738,file:Read@2720,file:ReadFile@1570,whole:Route@3033 752f5c566102d49a27c01c8d43ec2638a64b09b7b1868662ef652fca5f6d5f6a
+internal/host/rootmigration_production_linux.go migrateRootClaimAuthority authority G,J4,E3 rev3=PROPOSED-p3;run=15;identity=offline root migration uid 0;basis=requires masked inactive root mutator units and no running legacy root command under mandatory release exclusion; ordinary nonparticipants acquire no migration lock file:Open@1769,file:Open@374,file:Open@615,file:Open@7063,file:Open@739,file:Read@2721,file:ReadFile@1571,whole:Route@3034 9be47c70f4fb2a1d3c280814525085b261e9514e79df6f8918a0e67f9e6724d8
internal/host/rootmigration_timers.go rootMigrationTimers.adopt authority G,J4 rev3=PROPOSED-p3;identity=offline migration root uid 0;basis=offline migration binds the exact observed pre-claim registrations to root retained stop evidence and denies; durable intention precedes conditional registry adoption and root inventory completion registry:ClaimOwner@1997,registry:ClaimOwner@2135,registry:ClaimOwnership@1860,registry:ClaimOwnership@1914,registry:ClaimOwnership@4009,registry:ClaimOwnership@4202,registry:ClaimOwnership@4314,registry:Host@1836,registry:Host@4107,registry:Name@1677,registry:Name@4122,registry:Registration@1153,registry:Registration@1785,registry:Registration@1810,registry:Registration@1962,registry:Registration@2117,registry:Registration@4046,whole:ClaimOwnership@1864,whole:ClaimOwnership@1918,whole:ClaimOwnership@1936,whole:ClaimOwnership@4015,whole:ClaimOwnership@4211,whole:ClaimOwnership@4320,whole:Route@2456,whole:Timer@1067,whole:Timer@1073,whole:Timer@1101,whole:Timer@1663,whole:Timer@2304,whole:Timer@3896,whole:Timer@3911,whole:Timer@3980,whole:Timer@3998,whole:Timer@4179,whole:Timer@4190,whole:Timer@4304,whole:Timer@4383,whole:Timer@4394 35914f3267326de830fb7c149bf330638cb49794a3e8c6f41445078a92669a04
internal/host/rootmigration_timers.go rootMigrationTimers.load authority G,J4 rev3=PROPOSED-p3;identity=offline migration root uid 0;basis=reads only the root-private saved adoption intention, requiring the same host, incarnation, stamp, session and enrolled box; preserves exact registration IDs on retry file:Read@145,registry:Registration@844,whole:Timer@668,whole:Timer@846 f58c918185b3c382e5c1545f441cb589157b0c6cf60bb54322ca7edf40f1c1c9
internal/host/rootmigration_timers.go rootMigrationTimers.key authority G,J4 rev3=PROPOSED-p3;identity=offline migration root uid 0;basis=scopes the durable timer adoption intention to the root-observed installation and denied session 76a349236e49d55a5b9866b86ea60aa56a7a5b179b7b77b82d87535f0a305f0c
@@ -201,11 +201,11 @@ internal/hostguard/rootmigration.go RootAuthority.CompleteMigrationInventory aut
@coverage internal/activator 0a01bc096c64f1886f810f4856d60f9a2642074a8b2ac0c56cbed77d122e76c8
@coverage internal/console 6f4998c39e6d66cd73ea98500000cbecac251dd21dafd58f51a85c2291e0c533
@coverage internal/edgesfile 7b48368150a427e4620f1c9ded49942b688c9e6c9cd983b7da43d3f2c0810b72
-@coverage internal/host 344ee47b16ca6b4b243eed29bae501eda413fbf4e1098dfec1d3adafb9fce145
+@coverage internal/host a677b583bf8038d28c61ee49fad2ed734f068a1127f3cbfeb3c7e3f623e9758f
@coverage internal/host/units bf6ae516012701e0ca41908167e4efd60dd3c2c24e9af1555dec077e8be44461
@coverage internal/hostbox f877b123ea1c5c361a3046f5962ea5ef8e1a3b8a534a29aa2c15aac507b37081
@coverage internal/hostfloor 696e5ea4fa37b924b68e8e262235b99298ee19342d875213ba8209e5287dd3c6
-@coverage internal/hostguard c9458bdbbd122ae58dbe6581e6823c9379c397661dbc7a35513ccd656aca344d
+@coverage internal/hostguard d115a22e2a8410adef697af3e73f420b663337dc827fcca82e181e15fdeb8834
@coverage internal/release 4bf58e7d369ab2ceca1a6605ff71eabf453af7c1ae4b30565c72d6833bcea369
@coverage internal/rootparticipation ffa18f535e706793976bb235426fa5852e65baba211be5077666c1dc738e7f0b
@coverage internal/rootstate 2cedbf9f0230a1e6e2ed41afe421a33796a58cdf50a37f0f709dabea24c9088b
@@ -215,7 +215,7 @@ internal/host/rootgenerations_launch.go productionRootGenerations authority E2,E
internal/host/rootgenerations_launch.go rootGenerationSource.resolve authority E2 E20: read the atomic root installation under the existing host exclusion without recursive lock acquisition; retain fresh claim, canonical record, signed artifact and final reservation checks. Fixtures: TestPlan14GenerationLaunchDoesNotReenterRootLock, TestPlan14GenerationLaunchRefusals, TestPlan14RootDispatchRejectsRegistryMutation. PROPOSED E47: root-owned immutable unit/shape binding, reserved launch placement and exact invocation stop; retained generation binding carries the canonical unit. Default unit paths remain unchanged. Production named-route wiring and unit/env/timer retirement are not complete.;rev3=PROPOSED-p3;run=8;identity=root claim launch uid 0;basis=captures persistent executable and tree proofs only after canonical installation and signed artifact verification under exclusion;rev3=PROPOSED-p3;run=11;identity=root claim, migration or adoption controller uid 0;basis=merges E FileIdentity callers and P3 authority effects; retains the recorded family checks and both tracks behavioral fixtures file:Open@2501 3cd7c8132e9c451c983392f4d1cc676ddc14b761c90dec3e631d54ae800f9951
internal/host/rootgenerations_launch.go type rootGenerationSource authority E2,E3 rev3=PROPOSED-p3;identity=root claim/adoption controller uid 0;basis=binds production runtime authority and persistent file capture to the root launch source;rev3=PROPOSED-p3;run=11;identity=root claim, migration or adoption controller uid 0;basis=merges E FileIdentity callers and P3 authority effects; retains the recorded family checks and both tracks behavioral fixtures f291da85d463e50322a32fed1208629c6bf991112c4e8f614d9bf30bdb0338f5
internal/host/rootupdates.go initializeMigrationFailures authority G,E3 rev3=PROPOSED-p3;run=8;identity=offline journal-migrate uid 0;basis=requires complete root census before creating migration-bound root failure book; missing legacy history supplies no known refusal file:Open@254 5f7f652988f3def733e1b525260a52fac3568242960468e5457975ced1cd41d8
-internal/host/rootupdates.go productionRootUpdates authority E3 rev3=PROPOSED-p3;run=10;identity=root claim controller and adopt uid 0;basis=keeps canonical root inputs and retry; leaves sticky failure enforcement disabled; legacy bytes only identify an optional exact retry mirror and cannot block opening or inject fresh denials PROPOSED E56; identity=root claim controller; reserved generation completion requires its supplied exact invocation readiness verifier and does not probe a record-derived legacy endpoint; ordinary update readiness and failure attribution unchanged. Witness TestPlan14ReservedRuntimeReadiness. 01ab6985fb991b2d967f1fff7c9b0eb90e84bfa192c0fd61a966f74acd486237
+internal/host/rootupdates.go productionRootUpdates authority E3 rev3=PROPOSED-p3;run=10;identity=root claim controller and adopt uid 0;basis=keeps canonical root inputs and retry; leaves sticky failure enforcement disabled; legacy bytes only identify an optional exact retry mirror and cannot block opening or inject fresh denials 01ab6985fb991b2d967f1fff7c9b0eb90e84bfa192c0fd61a966f74acd486237
internal/host/rootupdates.go rootFailureLegacy authority E3 rev3=PROPOSED-p3;run=10;identity=root adoption and offline migration uid 0;basis=bounded nonblocking regular-file read supplies optional exact retry mirror identity or one-time migration deny evidence; refuses symlinks and special files file:OpenFile@65,file:ReadAll@265 efae62d1b996f14baa592e39e0cc22e0f5c8cde5cd94f429498d41039f5a70e4
internal/host/rootupdates.go rootUpdateSource.resolve authority E3,J1,J3 rev3=PROPOSED-p3;run=10;identity=root claim controller and adopt uid 0;basis=binds canonical settings and retries; gates sticky root failure veto and projection delivery errors off while failure lifecycle acceptance is incomplete file:Read@1368,registry:Account@1543,registry:Account@2428,registry:App@1581,registry:App@2473,registry:BuildState@1210,registry:GoodDemand@1176,registry:GoodDigest@1161,registry:GoodVersion@1145,registry:Host@1515,registry:Host@2393,registry:ServingVersion@1191,registry:Unit@2124,whole:Route@1521,whole:Route@1549,whole:Route@1587,whole:Route@1756,whole:Route@1763,whole:Route@2183,whole:Route@2406,whole:Route@2441,whole:Route@2486,whole:Route@3254,whole:Route@3262,whole:Route@4890,whole:Route@4897 e2247ec55dae1342cccc71cb84f8dd8dc901968ba8453bbc2af572c78c8505d3
internal/host/updatesroot.go updatesPassOption authority E3 rev3=PROPOSED-p3;run=8;identity=root adopt uid 0; unchanged agent opener uid carlos;basis=installs census-only root input provider while preserving the legacy failure-book opener for nonparticipating passes 4f66774059952bda531435d68487fa032a95b8d1c8ca33ec8b71b6639fa117e3
@@ -264,7 +264,7 @@ internal/host/rootupdates_runtime.go beginRootGenerationUpdate authority E2,E3 r
internal/host/rootupdates_runtime.go publishRootRetry authority E3 rev3=PROPOSED-p3;identity=root claim/adoption controller uid 0;basis=writes root-owned public retry status only from the independently verified root book file:Open@84 e3c680d5a7d3c423dc183319bb3d0518bbea6cdc752c7bd6b540d57f884b414e
internal/host/rootupdates_runtime.go rootRetryStatus hint E3 rev3=PROPOSED-p3;identity=carlos agent status probe uid carlos;basis=agent reads root-owned public retry status; an exact legacy-book acknowledgement may clear only the corresponding local mirror, never root authority file:Lstat@89,file:Open@183,file:Read@721 7db6b16062bd095634a03c58cfdcd3dde2d49c990a34fe37d260d0908a16aa5f
internal/host/rootupdates_runtime.go rootUpdateReady authority E3 rev3=PROPOSED-p3;identity=root claim/adoption controller uid 0;basis=probes the canonical deployment endpoint before root records runtime served/request success whole:Route@95 fe93218ee43c1346b148e4c9f6f55731d8865acde4f72ec85f60c5732a6a7d76
-internal/host/rootupdates_runtime.go rootUpdateSource.beginRuntime authority E3 rev3=PROPOSED-p3;run=10;identity=root claim controller uid 0;basis=retains durable attribution readiness and exact process settlement; diagnostic retry delivery cannot hold a successful launch while sticky enforcement is disabled PROPOSED E56; identity=root claim controller; reserved generation completion requires its supplied exact invocation readiness verifier and does not probe a record-derived legacy endpoint; ordinary update readiness and failure attribution unchanged. Witness TestPlan14ReservedRuntimeReadiness. file:Exec@1528,registry:Account@1276,registry:Account@1487,registry:Account@1930,registry:Account@533,registry:App@1293,registry:App@1504,registry:App@1942,registry:App@550,registry:Host@1117,registry:Host@1307,registry:Host@1376,registry:Host@1453,registry:Host@1642,registry:Host@1959,registry:Host@2382,registry:Host@500,whole:Route@180,whole:Route@939 1f4d21b77f96b6c0b35a85a45fd1b707903c2787cc77596756e167b3234f5ea1
+internal/host/rootupdates_runtime.go rootUpdateSource.beginRuntime authority E3 rev3=PROPOSED-p3;run=10;identity=root claim controller uid 0;basis=retains durable attribution readiness and exact process settlement; diagnostic retry delivery cannot hold a successful launch while sticky enforcement is disabled file:Exec@1528,registry:Account@1276,registry:Account@1487,registry:Account@1930,registry:Account@533,registry:App@1293,registry:App@1504,registry:App@1942,registry:App@550,registry:Host@1117,registry:Host@1307,registry:Host@1376,registry:Host@1453,registry:Host@1642,registry:Host@1959,registry:Host@2382,registry:Host@500,whole:Route@180,whole:Route@939 1f4d21b77f96b6c0b35a85a45fd1b707903c2787cc77596756e167b3234f5ea1
internal/host/rootupdates_runtime.go rootUpdateSource.publishRetry authority E3 rev3=PROPOSED-p3;identity=root claim/adoption controller uid 0;basis=projects the root book identity and epoch and includes a mirror acknowledgement only after the root denial is durably cleared registry:Account@284,registry:Account@378,registry:App@305,registry:App@399,registry:Host@333,registry:Host@427,registry:Host@490,whole:Route@290,whole:Route@311,whole:Route@339,whole:Route@384,whole:Route@405,whole:Route@433,whole:Route@496 d18d5c78c905754df748c0bf887a7170f6378a6da76df196af7508f28372be28
internal/host/rootupdates_runtime.go rootUpdateSource.retryStatus authority E3 rev3=PROPOSED-p3;identity=root claim/adoption controller uid 0;basis=derives retry identity and restrictions from root-held failures and epoch-bound served observations registry:Account@154,registry:Account@380,registry:Account@518,registry:Account@586,registry:Account@644,registry:Account@795,registry:Account@888,registry:App@171,registry:App@403,registry:App@530,registry:App@598,registry:App@656,registry:App@807,registry:App@900,registry:Host@351,registry:Host@544,registry:Host@612,registry:Host@670,registry:Host@821,registry:Host@914,whole:Route@112 a3eafae0a95559cfb0c7677215d89dbf353e44150453f252fed0e56b575e1f11
internal/hostbox/coldhandover.go ColdHandover.Run authority C,J2,J4 rev3=PROPOSED-p3;identity=root claim retirement or offline migration uid 0; shared helpers also run for legacy agent requests without root authority;basis=carries persistent identity into root-guarded cold retirement without treating old device numbers as filesystem identity;rev3=PROPOSED-p3;run=11;identity=root claim, migration or adoption controller uid 0;basis=merges E FileIdentity callers and P3 authority effects; retains the recorded family checks and both tracks behavioral fixtures file:Exec@1027,file:Lstat@10796,file:OpenRoot@10718,file:Read@1379,registry:Addr@3654,registry:Addrs@3672,registry:Backing@3691,registry:Canary@3748,registry:ClaimIncarnation@3532,registry:ClaimIncarnation@4034,registry:ClaimOwner@11546,registry:ClaimOwner@4116,registry:ClaimOwner@4136,registry:ClaimOwner@6011,registry:ClaimOwner@7103,registry:ClaimOwner@8357,registry:ClaimOwnership@11138,registry:ClaimOwnership@3502,registry:ClaimOwnership@4480,registry:ClaimOwnership@4698,registry:ClaimOwnership@4731,registry:ClaimRowID@11436,registry:ClaimRowID@11517,registry:ClaimRowID@4154,registry:ClaimRowID@4762,registry:ClaimRowID@7062,registry:ClaimRowID@8382,registry:ClaimStamp@3577,registry:ClaimStamp@4081,registry:Host@6939,registry:Host@8319,registry:Kind@3610,registry:Name@4816,registry:Name@6954,registry:Name@8338,registry:Platform@3732,registry:Registration@4180,registry:Registration@7019,registry:Registration@8407,registry:Replica@3762,registry:Route@11138,registry:Route@4480,registry:Route@4731,registry:Route@5262,registry:Route@6011,registry:Timers@4679,registry:Timers@4698,registry:Timers@4762,registry:Timers@4816,registry:Timers@6891,registry:Timers@8194,registry:Tunnel@3712,registry:Unit@3636,whole:ClaimOwnership@11152,whole:ClaimOwnership@3506,whole:ClaimOwnership@4492,whole:ClaimOwnership@4714,whole:ClaimOwnership@4743,whole:ColdRows@11140,whole:ColdRows@12252,whole:ColdRows@12262,whole:ColdRows@4977,whole:ColdRows@4985,whole:ColdRows@4993,whole:ColdRows@5000,whole:ColdRows@5264,whole:ColdRows@6013,whole:ColdRows@6893,whole:ColdRows@8196,whole:Route@11146,whole:Route@3487,whole:Route@3847,whole:Route@4279,whole:Route@4287,whole:Route@4338,whole:Route@4346,whole:Route@4486,whole:Route@4737,whole:Route@5271,whole:Route@6020,whole:Timer@4018,whole:Timer@4292,whole:Timer@4300,whole:Timer@4351,whole:Timer@4389,whole:Timer@4685,whole:Timer@4704,whole:Timer@4768,whole:Timer@4822,whole:Timer@6900,whole:Timer@8203 63533f0956c007cc4ef676c2f9b3264c0235fda43adcf7f39f8387bb2e83773b
@@ -305,18 +305,18 @@ internal/rootstate/state.go FileIdentity.Same authority E2 PROPOSED E51: Persist
internal/host/claimwakeassembly.go type ClaimLaunchInputs authority C,J4 rev3=PROPOSED-p3;run=12;basis=agent launch selection; preserves E named-unit inputs and seed own-write CAS callback together f64420b0bc9f2479961d965b297b86274ef69b31894b0dbe056e19850eb5c3b8
internal/host/movesfiles.go movesFiles.Establish authority C,J4 rev3=PROPOSED-p3;run=12;basis=agent files preparation; retains seed admitted-writer and ordinary origin proof with one C8 commit; H3 is inventory bookkeeping, C owns the enabled effect 172fe8888da88c249b7990bd9fb4bd2821bd402fb96ab52af13c58500f31d50f
internal/hostbox/servinghandover.go ServingHandover.Run authority C,J4 rev3=PROPOSED-p3;run=12;identity=root handover controller uid 0;basis=preserves seed fresh boot-aware directory verification and persists its checked full filesystem identity in successor inventory only; immutable predecessor retained registry:Account@2154,registry:Addr@2373,registry:Addrs@2391,registry:App@2198,registry:Backing@2410,registry:Canary@2467,registry:ClaimOwner@5643,registry:ClaimOwnership@2841,registry:ClaimOwnership@3057,registry:ClaimOwnership@3090,registry:ClaimRowID@3120,registry:ClaimRowID@5714,registry:ClaimRowID@5888,registry:DBPath@2286,registry:Kind@2128,registry:Name@3174,registry:Name@5853,registry:Platform@2451,registry:Replica@2481,registry:Route@2841,registry:Route@3090,registry:Route@5643,registry:Route@5714,registry:Socket@2234,registry:Timers@3039,registry:Timers@3057,registry:Timers@3120,registry:Timers@3174,registry:Timers@5796,registry:Tunnel@2431,registry:Unit@2355,whole:ClaimOwnership@2853,whole:ClaimOwnership@3073,whole:ClaimOwnership@3102,whole:ColdRows@3514,whole:ColdRows@3522,whole:ColdRows@3530,whole:ColdRows@3537,whole:ColdRows@5645,whole:ColdRows@5716,whole:ColdRows@5798,whole:ColdRows@6379,whole:ColdRows@6389,whole:Route@2114,whole:Route@2643,whole:Route@2651,whole:Route@2701,whole:Route@2709,whole:Route@2847,whole:Route@3096,whole:Route@5651,whole:Route@5722,whole:Timer@2656,whole:Timer@2664,whole:Timer@2714,whole:Timer@2752,whole:Timer@3045,whole:Timer@3063,whole:Timer@3126,whole:Timer@3180,whole:Timer@5804 c1367b936e3e0caa9e2bbd0da117a9b89ce3b7b7d4f518069e7d265a48b89a04
-internal/host/claimactivation_preflight.go claimActivationVerified authority G rev3=PROPOSED-p3;run=13;identity=agent uid carlos; root migration/adopt/control uid 0;basis=reads strict canonical deployment records in this placement scope; root additionally refuses retained unit configuration, bindings or materializations while 4a is incomplete cec48f637f31d76872d4d27c5e4391d8423c44de00a1bbd4aac63dcb70baa063
+internal/host/claimactivation_preflight.go claimActivationVerified authority G rev3=PROPOSED-p3;run=15;identity=agent uid carlos; root migration/adopt/control uid 0;basis=canonical records, installed local units and retained root bindings independently refuse incomplete named-unit activation before claim state 8420c4b5b416bfb653788d69fbdf0f2c3acaad739c85be8bb2c1462f2ad56474
internal/host/claimactivation_preflight.go var openClaimActivationState authority G rev3=PROPOSED-p3;run=13;identity=root migration/adopt/control uid 0;basis=opens retained root unit namespaces read-only without creating claim state file:Open@31 9b4c4aeddcc3756f3c7fbb267096147f5670511fc4fda809d88d98540ad685f6
internal/host/claimstop_run.go RunInstanceControl authority C,D,G rev3=PROPOSED-p3;run=13;identity=instance-control uid 0;basis=requires retained recovery census before construction and complete launch verification after pending recovery before serving d55919f2c48d02f39bb2d0eda76c7854aaf2c7684b8c782483d8fb62e5f5bff4
internal/host/claimstop_run.go claimRecoveryReady authority C,G rev3=PROPOSED-p3;run=13;identity=instance-control uid 0;basis=requires valid retained recovery census but defers complete launch inventory until restricted recovery has settled 627ab800a8f5772b81c3a893cf546438d7f76ae30ed29949ae6af52b5c2a9a7b
internal/host/claimunit_binding.go claimUnitBindings.establish authority E2 rev3=PROPOSED-p3;run=13;identity=root unit controller uid 0;basis=production admission refuses binding while named-unit 4a is incomplete, including backing=unit ef56f2db27a3d4a95803407a4b389b402592ed5b450bb6723aeffac42a041cab
internal/host/claimunit_dispatch.go claimUnitDispatch.forStart authority E2 rev3=PROPOSED-p3;run=13;identity=root launch controller uid 0;basis=verifies effective ordinary templates as well as retained named bindings before start and at the reservation permit recheck 3d358cb14cd248cea66750f64a4ea49be91eddb563e6ca389c12c7664f6c6706
internal/host/claimunit_dropin_linux.go openClaimUnitDropin authority E2 rev3=PROPOSED-p3;run=13;identity=root launch controller uid 0;basis=opens only the exact reservation drop-in under checked root ancestry for valid named or ordinary run units file:Fstat@694,file:Fstat@984,file:Open@290,file:Openat@496 f9a9915d67e7f8e4a540a484a7c978bf2d22eb803c04564349f9db5df119de6b
-internal/host/claimunit_dropin_other.go openClaimUnitDropin authority E2 rev3=PROPOSED-p3;run=13;identity=root launch controller uid 0;basis=opens only the exact reservation drop-in under checked root ancestry for valid named or ordinary run units bb3a29d1ad8840139f7236971ecb2bae93e6323007f2fdd625114bb3ff105397
+internal/host/claimunit_dropin_other.go openClaimUnitDropin authority E2 rev3=PROPOSED-p3;run=15;identity=non-Linux root launch controller;basis=unsupported-platform stub refuses only; performs no locking, opening or publication bb3a29d1ad8840139f7236971ecb2bae93e6323007f2fdd625114bb3ff105397
internal/host/claimunit_effective.go claimEffectiveUnit authority E2 rev3=PROPOSED-p3;run=13;identity=root launch controller uid 0;basis=ordinary launches require exact embedded template bytes, only the reservation drop-in and checked effective systemd identity, capabilities and containment; bound launches retain definition checks file:ReadAll@3614,file:ReadAll@643,file:ReadFile@457 7f02ae5a7047e09fffb5ad8dd8fd5e4336236f5f5128b77d41bef043171b66eb
internal/host/claimunit_effective.go claimReservedDropin authority E2 rev3=PROPOSED-p3;run=13;identity=root launch controller uid 0;basis=derives exact host reservation mounts and trampoline arguments; ordinary units retain systemd instance expansion data-literal@382 bf9e5d43b589d965ee9d9af968610bab7342e274eba33dcea3a1f76c3bda50d3
internal/host/claimunit_files_linux.go projectClaimBoundSocket authority E2 rev3=PROPOSED-p3;run=13;identity=root unit materializer uid 0;basis=selects and publishes the root-bound socket symlink and synchronizes its parent; this is publication authority file:Readlinkat@433 4f3c8daab1f68102e7be9511d1ec29d89c584c99aed79641f2b740031e503ab4
-internal/host/claimunit_files_other.go projectClaimBoundSocket authority E2 rev3=PROPOSED-p3;run=13;identity=root unit materializer uid 0;basis=selects and publishes the root-bound socket symlink and synchronizes its parent; this is publication authority 3f72016917b749fed665db738fbb54d90b40f59eec09aa3751b6abbb2f14319a
+internal/host/claimunit_files_other.go projectClaimBoundSocket authority E2 rev3=PROPOSED-p3;run=15;identity=non-Linux root unit materializer;basis=unsupported-platform stub refuses only; performs no locking, opening or publication 3f72016917b749fed665db738fbb54d90b40f59eec09aa3751b6abbb2f14319a
internal/host/claimunit_materialize.go claimUnitMaterializer.install authority E2 rev3=PROPOSED-p3;run=13;identity=root unit controller uid 0;basis=production admission refuses materialization while named-unit 4a is incomplete, including backing=unit ed99fda682170367aee0a3d6270153b7051ae03849aa93dc24ecca60eb7afec4
internal/host/claimunit_production.go bindProductionClaimUnits authority E2 rev3=PROPOSED-p3;run=13;identity=root control startup uid 0;basis=installs effective verification for ordinary and bound starts; incomplete 4a refuses production binding and materialization for named and backing units file:Read@1275 e4b9166fe33bcbafe91efb09d99d139d1f697df35dd7cff6765c9e483972d4c5
internal/host/proofroot.go configureRootProof authority E1,B3 rev3=PROPOSED-p3;run=13;identity=root adopt uid 0;basis=checks participation before opening history or installing root proof policy; errors deny minting without falling back 322c7da5d034cd1cc870e748c499864796fa8bc11e4bbed6f96f7312d004dbd1
@@ -326,5 +326,10 @@ internal/host/rootinventory_startup.go recoverRootInventoryStartup authority C,G
internal/host/rootmigration_production_linux.go quiesceLegacyRootMutators authority G rev3=PROPOSED-p3;run=13;identity=offline migration uid 0;basis=under mandatory release exclusion scans root process command lines and refuses already-running legacy adopt/apply/guard-arm before census creation file:ReadDir@77,file:ReadFile@537,file:Stat@381 5b8d7964c595634d80699448aa5f052fc2e8c215e3c9bd8549378d37ce25d2ca
internal/host/rootmigration_startup.go readRootMigrationState authority G rev3=PROPOSED-p3;run=13;identity=root startup/recovery uid 0;basis=validates retained census box/enrollment/generation and complete migration record before restricted recovery; grants no launch file:Read@425 9a8ddf2b605833b7fa4c0908ade98a6fc2efbe87c26e92ede289bbcc1d05beec
internal/host/rootmovesfloor_boot.go sameRootFloorImage authority F rev3=PROPOSED-p3;run=13;identity=root boot/recovery uid 0;basis=compares freshly verified signed digest, release and protocols, not saved boot-local file numbers 532078238827331f9282d85d1e17e146ed4842a215db057238aa5f6cb40ba137
-internal/host/rootreleaselock.go withClaimReleaseCensus authority E2 rev3=PROPOSED-p3;run=13;identity=root adopt/guard uid 0;basis=census paths require release exclusion; legacy paths use an already-created migration exclusion without creating state 514b5e479c5340b02ec78106cdc19b90a03ec4a3ac229eb82c654f09bc6a92e5
-internal/host/rootreleaselock.go withExistingRootRelease authority G rev3=PROPOSED-p3;run=13;identity=root legacy adopt/update uid 0;basis=unchanged legacy effect when migration exclusion does not exist; otherwise serializes with explicit migration before any mutation file:Lstat@93,file:Open@225 be32aad37d2b0a17979c74ed097d00728da6fedefd30862b8ec5537745bf55ae
+internal/host/rootreleaselock.go withClaimReleaseCensus authority E2 rev3=PROPOSED-p3;run=15;identity=root adopt/guard uid 0;basis=no census invokes main effect directly without opening release state, creating a file or waiting on any migration lock; participating effects retain release exclusion 35bcc5e18e89f9755cf1f749bbaa4cbf493380b75500183687476abd71c946b6
+internal/host/claimactivation_preflight.go claimActivationLocalUnits authority G rev3=PROPOSED-p3;run=15;identity=agent uid carlos; root migration/adopt/control uid 0;basis=refusal-only inventory of backing unit instances, local named services and drop-ins, legacy app environment configuration and both systemd backing state directories; registry omission cannot confer eligibility file:ReadDir@215,file:ReadFile@1206,file:ReadFile@1289,file:WalkDir@603 bfe1faa17fcbe6d2ea25252698e925e26616b6cdaef55759de0c291b1b30749b
+internal/host/claimassembly.go ClaimAssembly.Build authority C,G rev3=PROPOSED-p3;run=15;identity=agent uid carlos; root control/adopt/recovery uid 0;basis=supplies root registration client, guard, enrollment and typed inventory; local root obligations and exact intention bind inventory revision; service SQL contributes refusals only file:Prepare@3854,file:Prepare@3871,file:Prepare@4583,file:Read@4513,whole:Timer@2044 b77068fb6ba255c31031e3328b6a7ae976369cd5098c51602b0c235effbed253
+internal/host/rootgeneration_inventory.go readProductionGenerationInventory authority E2 rev3=PROPOSED-p3;run=15;identity=root generation/update/recovery uid 0;basis=local=true reads retained census, fences, obligations and journals without deployment store; local=false opens deployment store for canonical records; SQL remains refusal-only 0a9180acd9e78efc5905fe37df25d877c7343fcec037786f699b03182acaedc3
+internal/host/rootmigration_production_linux.go verifyOfflineRootMigration authority G rev3=PROPOSED-p3;run=15;identity=offline root migration uid 0;basis=requires offline masked inactive adopt, stage, config-sync, restart, update and recovery services and absence of concurrent legacy root mutators before creating census state 300a81411020b33318dd8e15418acc95654c3980eb03c8581276d40a951a1a15
+internal/host/systemparticipation.go systemApplier.checkUnarmedParticipationRecovery authority G,F rev3=PROPOSED-p3;run=15;identity=root system update/recovery uid 0;basis=pending acquisition triggers production controller settlement even without an update journal; normal ledger admission stays refused until verified recovery clears the marker 4360d1c0c50c554afc06b3a9ad216acc646728f97750e00ca1fe038b1bccce51
+internal/hostguard/generationinventory.go RootAuthority.generationInstallation authority E2 rev3=PROPOSED-p3;run=15;identity=root generation verification uid 0;basis=enumerates using preserved installation stamp including exact retired tuples; resyncs retained authority without admitting inactive installations or dropping payload obligations fc5ef57b638ee0eba8ae49b724ef9397b2a3ae097d1e892cab888880480eacd2