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>
29 files changed,
+1310
−125
internal/host/claimactivation_preflight.go+65 −0internal/host/claimactivation_preflight_test.go+41 −0internal/host/claimconstruction_rootrunner_linux_test.go+55 −2internal/host/claimunit_binding.go+4 −0internal/host/claimunit_console.go+28 −0internal/host/claimunit_console_test.go+40 −0internal/host/claimunit_definition.go+4 −0internal/host/claimunit_definition_test.go+59 −0internal/host/claimunit_effective_test.go+46 −37internal/host/claimunit_materialize.go+5 −0internal/host/plan14_enabled_console_root_linux_test.go+185 −0internal/host/plan14_fixture_nat_root_linux_test.go+136 −0internal/host/plan14_generation_registry_root_linux_test.go+6 −0internal/host/plan14_installed_launch_root_linux_test.go+45 −0internal/host/plan14_r10_root_linux_test.go+288 −0internal/host/plan14_r9_root_linux_test.go+86 −16internal/host/rootmigration_bootstrap_root_linux_test.go+58 −2internal/host/rootmigration_production_linux.go+17 −1internal/host/rootmigration_startup.go+1 −1internal/host/rootmovesfloor_source.go+1 −1internal/host/rootreleaselock.go+1 −19internal/host/systemapply.go+1 −1internal/host/systemparticipation.go+8 −1internal/hostguard/generationinventory.go+1 −1internal/hostguard/retired_inventory_test.go+60 −0internal/hostguard/root.go+21 −2internal/hostguard/rootmigration_inventory.go+1 −1internal/provision/testdata/plan14-root-reads-e.tsv+19 −17internal/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 |