Plan 14 integrated: extended lifecycle E2E passes every case (not for merge)
Real activation gate, active sibling, reinstall refusal, ordinary path and all three crash points pass on the root runner. Retirement accepts an independently contained successor reusing the predecessor's cgroup pathname.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
11 files changed,
+238
−15
internal/host/plan14_generation_containment_linux_test.go+52 −0internal/host/plan14_lifecycle_diagnostic_root_linux_test.go+6 −0internal/host/plan14_lifecycle_reinstall_root_linux_test.go+21 −0internal/host/plan14_lifecycle_retire_root_linux_test.go+10 −1internal/host/plan14_lifecycle_root_linux_test.go+45 −2internal/host/plan14_lifecycle_trace_root_linux_test.go+53 −4internal/host/rootgeneration_containment_linux.go+33 −0internal/host/rootgeneration_kernel_linux.go+5 −0internal/host/rootgeneration_retired_linux.go+5 −2internal/host/rootgeneration_retirement.go+1 −1internal/provision/testdata/plan14-root-reads-p3.tsv+7 −5
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 294.150s
? github.com/carlosframework/platform/cmd/changelog [no test files]
ok github.com/carlosframework/platform/cmd/console 2.534s
ok github.com/carlosframework/platform/cmd/moveprobe 92.209s
ok github.com/carlosframework/platform/cmd/zddprobe 0.703s
ok github.com/carlosframework/platform/internal/activator 250.675s
ok github.com/carlosframework/platform/internal/agentsock 0.028s
ok github.com/carlosframework/platform/internal/alerting 0.007s
ok github.com/carlosframework/platform/internal/analytics 0.033s
ok github.com/carlosframework/platform/internal/appconfig 2.404s
ok github.com/carlosframework/platform/internal/apperr 0.073s
ok github.com/carlosframework/platform/internal/applog 0.147s
ok github.com/carlosframework/platform/internal/awsinv 0.007s
ok github.com/carlosframework/platform/internal/boxchoice 0.004s
ok github.com/carlosframework/platform/internal/boxid 0.424s
? 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.011s
ok github.com/carlosframework/platform/internal/caps 0.012s
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.309s
ok github.com/carlosframework/platform/internal/clamd 0.055s
? github.com/carlosframework/platform/internal/cliflag [no test files]
ok github.com/carlosframework/platform/internal/console 400.244s
? 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.195s
ok github.com/carlosframework/platform/internal/dbdoor/dbdoortest 0.011s
ok github.com/carlosframework/platform/internal/dbverify 0.009s
ok github.com/carlosframework/platform/internal/dnsrec 0.006s
ok github.com/carlosframework/platform/internal/docsite 0.008s
ok github.com/carlosframework/platform/internal/domains 2.333s
ok github.com/carlosframework/platform/internal/doorkey 0.009s
ok github.com/carlosframework/platform/internal/economics 0.004s
ok github.com/carlosframework/platform/internal/edge 44.730s
? github.com/carlosframework/platform/internal/edgeproof [no test files]
ok github.com/carlosframework/platform/internal/edgesfile 0.011s
ok github.com/carlosframework/platform/internal/fleetapi 0.136s
ok github.com/carlosframework/platform/internal/gate 1.665s
ok github.com/carlosframework/platform/internal/genselect 0.014s
ok github.com/carlosframework/platform/internal/geo 0.009s
ok github.com/carlosframework/platform/internal/geoip 0.030s
ok github.com/carlosframework/platform/internal/historyreadback 0.153s
ok github.com/carlosframework/platform/internal/host 180.479s
ok github.com/carlosframework/platform/internal/hostactors 0.643s
ok github.com/carlosframework/platform/internal/hostbox 2.675s
ok github.com/carlosframework/platform/internal/hostclaim 0.567s
ok github.com/carlosframework/platform/internal/hostevidence 0.005s
ok github.com/carlosframework/platform/internal/hostexec 4.672s
ok github.com/carlosframework/platform/internal/hostfence 0.055s
ok github.com/carlosframework/platform/internal/hostfiles 10.477s
ok github.com/carlosframework/platform/internal/hostfloor 0.012s
ok github.com/carlosframework/platform/internal/hostguard 1.028s
ok github.com/carlosframework/platform/internal/hostops 1.558s
? github.com/carlosframework/platform/internal/hostops/fencerelease [no test files]
ok github.com/carlosframework/platform/internal/hostops/internal/fencerelease 0.002s
ok github.com/carlosframework/platform/internal/hostquarantine 0.003s
ok github.com/carlosframework/platform/internal/hostrelocate 0.461s
ok github.com/carlosframework/platform/internal/hostresolve 0.140s
ok github.com/carlosframework/platform/internal/hostwrite 0.002s
ok github.com/carlosframework/platform/internal/infraview 0.005s
ok github.com/carlosframework/platform/internal/instancectl 5.222s
ok github.com/carlosframework/platform/internal/instanceexec 2.720s
ok github.com/carlosframework/platform/internal/instancefs 0.005s
ok github.com/carlosframework/platform/internal/instancekey 0.002s
ok github.com/carlosframework/platform/internal/instanceupdates 0.009s
2026/10/17 06:38:44 activator: h.example.com hibernated
--- FAIL: TestRawInstancesDirectoryIsNamedOnlyHere (0.34s)
rawdir_test.go:58: ../../internal/host/rootinventory_sibling.go:23 names the raw .instances directory; only internal/layout (and bootstrap's tmpfiles lines) may
FAIL
FAIL github.com/carlosframework/platform/internal/layout 1.516s
ok github.com/carlosframework/platform/internal/lbhealth 0.003s
ok github.com/carlosframework/platform/internal/lease 9.798s
ok github.com/carlosframework/platform/internal/lease/leasetest 0.008s
ok github.com/carlosframework/platform/internal/ledger 0.012s
ok github.com/carlosframework/platform/internal/mail 0.179s
ok github.com/carlosframework/platform/internal/maintenance 0.079s
ok github.com/carlosframework/platform/internal/maintenanceexec 5.513s
ok github.com/carlosframework/platform/internal/maintenanceguard 0.076s
ok github.com/carlosframework/platform/internal/metrics 0.005s
ok github.com/carlosframework/platform/internal/moves 20.997s
ok github.com/carlosframework/platform/internal/moves/archive 0.782s
ok github.com/carlosframework/platform/internal/objstore 0.199s
ok github.com/carlosframework/platform/internal/ops 0.167s
ok github.com/carlosframework/platform/internal/park 0.213s
ok github.com/carlosframework/platform/internal/privatefile 0.003s
ok github.com/carlosframework/platform/internal/provision 28.609s
ok github.com/carlosframework/platform/internal/publicrelease 0.006s
? github.com/carlosframework/platform/internal/regionname [no test files]
ok github.com/carlosframework/platform/internal/registry 5.458s
ok github.com/carlosframework/platform/internal/relay 7.232s
ok github.com/carlosframework/platform/internal/release 21.421s
ok github.com/carlosframework/platform/internal/rollout 3.608s
ok github.com/carlosframework/platform/internal/rolloutwords 0.004s
ok github.com/carlosframework/platform/internal/rootaws 0.006s
ok github.com/carlosframework/platform/internal/rootparticipation 0.060s
ok github.com/carlosframework/platform/internal/rootstate 0.027s
ok github.com/carlosframework/platform/internal/runs 0.574s
ok github.com/carlosframework/platform/internal/s3compat 0.006s
ok github.com/carlosframework/platform/internal/safefile 0.004s
ok github.com/carlosframework/platform/internal/schedule 2.470s
ok github.com/carlosframework/platform/internal/seeds 0.011s
ok github.com/carlosframework/platform/internal/sim/hostlife 8.817s
ok github.com/carlosframework/platform/internal/sim/litestreamfacts 4.006s
ok github.com/carlosframework/platform/internal/status 0.039s
ok github.com/carlosframework/platform/internal/steering 0.005s
ok github.com/carlosframework/platform/internal/stopevidence 0.388s
ok github.com/carlosframework/platform/internal/svcrole 0.005s
ok github.com/carlosframework/platform/internal/telemetry 0.004s
ok github.com/carlosframework/platform/internal/termfence 0.004s
ok github.com/carlosframework/platform/internal/tunnelclient 0.004s
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.553s
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/plan14_generation_containment_linux_test.go b/internal/host/plan14_generation_containment_linux_test.go| index b506834df..c3c30307b 100644 |
| --- a/internal/host/plan14_generation_containment_linux_test.go |
| +++ b/internal/host/plan14_generation_containment_linux_test.go |
| @@ -59,6 +59,11 @@ func TestPlan14GenerationContainedTask(t *testing.T) { |
| } |
| generationLink(t, r.Binding.Path, root+"/opt/carlos/app") |
| generationWrite(t, f.task+"/mountinfo", fmt.Sprintf("1 0 %d:%d / / ro - ext4 disk ro\n2 1 %d:%d %s /opt/carlos/app ro - ext4 disk ro\n", generationMajor(gid.Device), generationMinor(gid.Device), generationMajor(r.Binding.Executable.Device), generationMinor(r.Binding.Executable.Device), r.Binding.Path)) |
| + old := r |
| + old.Closed = true |
| + old.Binding.Attempt += "-old" |
| + old.Process.Invocation = "old-invocation" |
| + old.Process.GroupIdentity.Inode++ |
| status := []byte(containedStatus) |
| switch attack { |
| case "old record": |
| @@ -113,6 +118,53 @@ func TestPlan14GenerationContainedTask(t *testing.T) { |
| } else if ok && err == nil { |
| t.Fatal("unsafe containment accepted") |
| } |
| + for _, pid := range []string{"100", "200"} { |
| + p := filepath.Join(f.proc, pid) |
| + generationWrite(t, p+"/status", string(status)) |
| + generationLink(t, root, p+"/root") |
| + mounts, err := os.ReadFile(f.task + "/mountinfo") |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + generationWrite(t, p+"/mountinfo", string(mounts)) |
| + } |
| + for _, name := range []string{"status", "root", "mountinfo", "ns", "cgroup"} { |
| + generationLink(t, f.task+"/"+name, f.proc+"/41/"+name) |
| + } |
| + generationWrite(t, filepath.Join(cgroups, group, "cgroup.procs"), "100\n200\n41\n") |
| + err = k.replacedGroup(t.Context(), old) |
| + if attack == "none" || attack == "closed history" { |
| + if err != nil { |
| + t.Fatal("independently contained replacement refused", err) |
| + } |
| + for _, mutation := range []string{"missing reservation", "live old", "same group", "same invocation", "other host", "unrecorded child"} { |
| + t.Run(mutation, func(t *testing.T) { |
| + candidate, prior, check := r, old, k |
| + switch mutation { |
| + case "live old": |
| + prior.Closed = false |
| + case "same group": |
| + prior.Process.GroupIdentity = candidate.Process.GroupIdentity |
| + case "same invocation": |
| + prior.Process.Invocation = candidate.Process.Invocation |
| + case "other host": |
| + candidate.Binding.Binding.Host = "foreign.bab.example" |
| + case "unrecorded child": |
| + generationWrite(t, filepath.Join(cgroups, group, "cgroup.procs"), "100\n200\n999\n") |
| + t.Cleanup(func() { generationWrite(t, filepath.Join(cgroups, group, "cgroup.procs"), "100\n200\n41\n") }) |
| + } |
| + check.contained = []release.GenerationReservation{candidate} |
| + if mutation == "missing reservation" { |
| + check.contained = nil |
| + } |
| + if err := check.replacedGroup(t.Context(), prior); err == nil { |
| + t.Fatal("unverified replacement accepted") |
| + } |
| + }) |
| + } |
| + } else if err == nil { |
| + t.Fatal("unsafe replacement accepted") |
| + } |
| }) |
| } |
| } |
diff --git a/internal/host/plan14_lifecycle_diagnostic_root_linux_test.go b/internal/host/plan14_lifecycle_diagnostic_root_linux_test.go| index 763c2a967..7f6e80003 100644 |
| --- a/internal/host/plan14_lifecycle_diagnostic_root_linux_test.go |
| +++ b/internal/host/plan14_lifecycle_diagnostic_root_linux_test.go |
| @@ -9,6 +9,7 @@ import ( |
| "github.com/carlosframework/platform/internal/release" |
| "github.com/carlosframework/platform/internal/rootparticipation" |
| "github.com/carlosframework/platform/internal/rootstate" |
| + "os/exec" |
| "testing" |
| ) |
| @@ -67,6 +68,9 @@ func lifecycleInventoryDiagnostic(t *testing.T, ctx context.Context, inv generat |
| func lifecycleRetirementDiagnostic(t *testing.T, ctx context.Context, g *release.RootGenerations) { |
| records, err := g.Reservations(ctx) |
| t.Log("retirement records", err) |
| + ctx = context.WithValue(ctx, generationCensusReservationsKey{}, records) |
| + out, lockErr := exec.Command("lslocks", "--json", "--output", "PID,COMMAND,TYPE,PATH").CombinedOutput() |
| + t.Logf("retirement locks: %s %v", out, lockErr) |
| s, err := rootstate.Open("/var/lib/carlos-claims/authority/fences", false, false) |
| if err != nil { |
| t.Log(err) |
| @@ -81,12 +85,14 @@ func lifecycleRetirementDiagnostic(t *testing.T, ctx context.Context, g *release |
| defer j.Close() |
| v := generationRetirement{guard: hostguard.Guard{Root: hostguard.NewRootAuthority(s)}, journal: j, kernel: productionGenerationAbsence} |
| for _, r := range records { |
| + t.Log("retirement ledger", r.Binding.Target.Host, r.Binding.Target.Stamp, r.Closed, r.Retirement) |
| closed, e := v.closure(ctx, r) |
| t.Log("retirement closure", r.Binding.Target.Host, r.Binding.Target.Stamp, closed, e) |
| view, e := v.guard.Root.View(ctx, r.Binding.Target.Host, hostguard.Captured{Incarnation: r.Binding.Target.Incarnation, Stamp: r.Binding.Target.Stamp}) |
| if e == nil && closed { |
| _, e = productionRetiredGenerationAbsence(ctx, r, view) |
| t.Log("retirement absence", r.Binding.Target.Host, r.Binding.Target.Stamp, e) |
| + t.Log("retirement replacement", (generationKernel{proc: "/proc", cgroups: "/sys/fs/cgroup", contained: records}).replacedGroup(ctx, r)) |
| } |
| } |
| } |
diff --git a/internal/host/plan14_lifecycle_reinstall_root_linux_test.go b/internal/host/plan14_lifecycle_reinstall_root_linux_test.go| index 1790e4075..cb9296f4e 100644 |
| --- a/internal/host/plan14_lifecycle_reinstall_root_linux_test.go |
| +++ b/internal/host/plan14_lifecycle_reinstall_root_linux_test.go |
| @@ -7,7 +7,10 @@ import ( |
| "crypto/ed25519" |
| "crypto/sha256" |
| "errors" |
| + "io" |
| "io/fs" |
| + "net" |
| + "net/http" |
| "os" |
| "path/filepath" |
| "reflect" |
| @@ -16,6 +19,7 @@ import ( |
| "time" |
| "github.com/carlosframework/platform/internal/controlsock" |
| + "github.com/carlosframework/platform/internal/domains" |
| "github.com/carlosframework/platform/internal/hostbox" |
| "github.com/carlosframework/platform/internal/hostclaim" |
| execlease "github.com/carlosframework/platform/internal/hostexec" |
| @@ -43,6 +47,18 @@ func lifecycleActiveSibling(t *testing.T) { |
| t.Fatal("sibling retired") |
| } |
| } |
| + transport := &http.Transport{DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { |
| + return (&net.Dialer{}).DialContext(ctx, "unix", "/run/carlos/instances/"+lifecycleSibling+"/app.sock") |
| + }} |
| + defer transport.CloseIdleConnections() |
| + response, err := (&http.Client{Transport: transport, Timeout: time.Second}).Get("http://sibling/") |
| + lifecycleCheck(t, "active sibling response", err) |
| + defer response.Body.Close() |
| + raw, err := io.ReadAll(response.Body) |
| + lifecycleCheck(t, "active sibling body", err) |
| + if response.StatusCode != http.StatusOK || string(raw) != "lifecycle tenant" { |
| + t.Fatal("sibling stopped serving", response.StatusCode, string(raw)) |
| + } |
| return |
| } |
| t.Fatal("missing production sibling installation") |
| @@ -75,6 +91,11 @@ func lifecycleRetainedBytes(t *testing.T) map[string][32]byte { |
| func lifecycleReinstall(t *testing.T, store release.Store, key ed25519.PrivateKey, actor hostbox.Actor, pointer release.SystemPointer) { |
| t.Helper() |
| lifecycleActiveSibling(t) |
| + removed, err := domains.DetachClaim(t.Context(), store, lifecycleHost, "bab", "lifecycle") |
| + lifecycleCheck(t, "release deleted instance domain", err) |
| + if !removed { |
| + t.Fatal("deleted instance domain was not released") |
| + } |
| before := lifecycleRetainedBytes(t) |
| lifecycleCreate(t, store, key, actor, pointer, provision.Record{Schema: 1, Host: lifecycleHost, Account: "bab", App: "reinstall", Channel: "stable", CreatedAt: time.Now().UTC()}) |
| lifecycleCheck(t, "request reinstall", os.WriteFile("/run/carlos/lifecycle-reinstall", []byte("ready"), 0644)) |
diff --git a/internal/host/plan14_lifecycle_retire_root_linux_test.go b/internal/host/plan14_lifecycle_retire_root_linux_test.go| index 746d19ccf..05356d2f9 100644 |
| --- a/internal/host/plan14_lifecycle_retire_root_linux_test.go |
| +++ b/internal/host/plan14_lifecycle_retire_root_linux_test.go |
| @@ -135,6 +135,7 @@ func lifecycleVerifyRetiredPins(t *testing.T, store release.Store, key ed25519.P |
| t.Log("pins retirement", err) |
| if err != nil { |
| lifecycleRetirementDiagnostic(t, ctx, g) |
| + lifecycleCrashDiagnostic(t) |
| } |
| return err |
| } |
| @@ -174,13 +175,21 @@ func lifecycleVerifyRetiredPins(t *testing.T, store release.Store, key ed25519.P |
| if err != nil { |
| return err |
| } |
| - pinned, err := pins.Refers(release.VersionPath(release.OptDir(), "bab", "v1", "lifecycle")) |
| + pinned, err := pins.Refers(filepath.Dir(release.VersionPath(release.OptDir(), "bab", "v1", "lifecycle"))) |
| if err != nil { |
| return err |
| } |
| if !pinned { |
| t.Fatal("active sibling failed to pin the shared generation tree") |
| } |
| + sibling, err := pins.Refers(release.VersionPath(release.OptDir(), "bab", "v1", "sibling")) |
| + if err != nil || !sibling { |
| + t.Fatal("active sibling executable is not pinned", err) |
| + } |
| + retired, err := pins.Refers(release.VersionPath(release.OptDir(), "bab", "v1", "lifecycle")) |
| + if err != nil || retired { |
| + t.Fatal("retired executable still has a direct pin", err) |
| + } |
| return nil |
| })) |
| lifecycleActiveSibling(t) |
diff --git a/internal/host/plan14_lifecycle_root_linux_test.go b/internal/host/plan14_lifecycle_root_linux_test.go| index e74474ac7..db4bc8cb4 100644 |
| --- a/internal/host/plan14_lifecycle_root_linux_test.go |
| +++ b/internal/host/plan14_lifecycle_root_linux_test.go |
| @@ -22,6 +22,7 @@ import ( |
| "path/filepath" |
| "runtime" |
| "strings" |
| + "sync" |
| "testing" |
| "time" |
| @@ -427,6 +428,7 @@ func lifecycleStartAgent(t *testing.T, endpoint string, completeCreate func() er |
| break |
| } |
| if time.Now().After(until) { |
| + lifecycleCrashDiagnostic(t) |
| t.Fatal("edge did not finish cleanup after controller crash") |
| } |
| time.Sleep(100 * time.Millisecond) |
| @@ -487,6 +489,7 @@ func lifecycleStartAgent(t *testing.T, endpoint string, completeCreate func() er |
| } |
| func lifecycleAgent(t *testing.T) { |
| + lifecycleStackTrace(t) |
| deadlineReady := time.Now().Add(3 * time.Minute) |
| for { |
| if _, err := os.Stat("/run/carlos-control/lifecycle-ready"); err == nil { |
| @@ -518,7 +521,9 @@ func lifecycleAgent(t *testing.T) { |
| defer func() { |
| deadline := time.Now().Add(30 * time.Second) |
| for { |
| + t.Log("agent assembly close enter") |
| err := assembly.Close(context.Background()) |
| + t.Log("agent assembly close returned", err) |
| if err == nil { |
| return |
| } |
| @@ -721,8 +726,45 @@ func lifecycleLaunch(t *testing.T, ctx context.Context, runtime *activator.Claim |
| lifecycleCheck(t, "materialize", start.Materialize(ctx)) |
| launch, err := start.PrepareLaunch(ctx) |
| lifecycleCheck(t, "launch preparation", err) |
| - cmd, _, settle, _, err := launch.Start(ctx, "main", start.CurrentAttempt(), start.Build, func() error { return ctx.Err() }) |
| + cmd, _, settle, producer, err := launch.Start(ctx, "main", start.CurrentAttempt(), start.Build, func() error { return ctx.Err() }) |
| + if err != nil && settle != nil { |
| + cleanup, cancel := context.WithTimeout(context.WithoutCancel(ctx), time.Minute) |
| + cleanupErr := lifecycleRetry(cleanup, settle) |
| + cancel() |
| + t.Log("failed launch settlement", cleanupErr) |
| + } |
| lifecycleCheck(t, "root launch", err) |
| + var stopMu sync.Mutex |
| + stopped := make(chan struct{}) |
| + stop := func(call context.Context) error { |
| + stopMu.Lock() |
| + defer stopMu.Unlock() |
| + select { |
| + case <-stopped: |
| + return nil |
| + default: |
| + } |
| + if err := settle(call); err != nil { |
| + return err |
| + } |
| + if cmd.Process != nil { |
| + _ = cmd.Process.Release() |
| + } |
| + close(stopped) |
| + return nil |
| + } |
| + if producer.Context != nil { |
| + go func() { |
| + select { |
| + case <-stopped: |
| + return |
| + case <-producer.Context.Done(): |
| + } |
| + cleanup, cancel := context.WithTimeout(context.WithoutCancel(ctx), 30*time.Second) |
| + defer cancel() |
| + _ = stop(cleanup) |
| + }() |
| + } |
| lifecycleCheck(t, "tenant ready", start.Ready(ctx, cmd)) |
| prefix, err := hostbox.GenerationPrefix(*admitted.Claim.Holder) |
| lifecycleCheck(t, "generation prefix", err) |
| @@ -746,7 +788,7 @@ func lifecycleLaunch(t *testing.T, ctx context.Context, runtime *activator.Claim |
| t.Fatalf("tenant response %d %q", response.StatusCode, raw) |
| } |
| return func() { |
| - lifecycleCheck(t, "stop tenant", lifecycleRetry(ctx, settle)) |
| + lifecycleCheck(t, "stop tenant", lifecycleRetry(ctx, stop)) |
| lifecycleCheck(t, "stop replication", rep.Close(ctx)) |
| current, err = runtime.Read(ctx, route.Host) |
| lifecycleCheck(t, "read stopped session", err) |
| @@ -911,6 +953,7 @@ func lifecycleController(t *testing.T, raw release.Store, key ed25519.PrivateKey |
| } |
| func TestPlan14LifecycleControlProcess(t *testing.T) { |
| + lifecycleStackTrace(t) |
| if os.Getenv("PLAN14_LIFECYCLE_CONTROL") != "1" { |
| t.Fatal("controller helper") |
| } |
diff --git a/internal/host/plan14_lifecycle_trace_root_linux_test.go b/internal/host/plan14_lifecycle_trace_root_linux_test.go| index 1d21ba551..679b1750a 100644 |
| --- a/internal/host/plan14_lifecycle_trace_root_linux_test.go |
| +++ b/internal/host/plan14_lifecycle_trace_root_linux_test.go |
| @@ -9,14 +9,19 @@ import ( |
| "io" |
| "net/http" |
| "net/http/httptest" |
| + "os" |
| "os/exec" |
| + "os/signal" |
| + "runtime" |
| "strings" |
| "sync" |
| + "syscall" |
| "testing" |
| "time" |
| "github.com/carlosframework/platform/internal/hostbox" |
| "github.com/carlosframework/platform/internal/hostclaim" |
| + execlease "github.com/carlosframework/platform/internal/hostexec" |
| "github.com/carlosframework/platform/internal/instancectl" |
| "github.com/carlosframework/platform/internal/park" |
| ) |
| @@ -38,22 +43,37 @@ func (w *lifecycleResponse) WriteHeader(status int) { |
| w.ResponseWriter.WriteHeader(status) |
| } |
| func (w *lifecycleResponse) Write(body []byte) (int, error) { |
| - if w.status >= 400 { |
| + if w.body.Len() < 64<<10 { |
| w.body.Write(body) |
| } |
| return w.ResponseWriter.Write(body) |
| } |
| func (h lifecycleControlTrace) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
| var q hostbox.RoleLaunchRequest |
| - if r.URL.Path == "/claim/launch" { |
| + var execution execlease.ExecutionRequest |
| + if r.URL.Path == "/claim/launch" || r.URL.Path == "/claim/execution" { |
| raw, _ := io.ReadAll(r.Body) |
| r.Body = io.NopCloser(bytes.NewReader(raw)) |
| - json.Unmarshal(raw, &q) |
| + if r.URL.Path == "/claim/launch" { |
| + json.Unmarshal(raw, &q) |
| + } else { |
| + json.Unmarshal(raw, &execution) |
| + } |
| } |
| + started := time.Now() |
| response := &lifecycleResponse{ResponseWriter: w} |
| h.ClaimRootWithLaunch.ServeHTTP(response, r) |
| - if response.status >= 400 { |
| + if response.status >= 400 || bytes.Contains(response.body.Bytes(), []byte(`"Failed":true`)) { |
| + if execution.Host != "" { |
| + raw, err := h.Root.executionRuntime.state.Read(execution.Host + ".json") |
| + diagnostic, cancel := context.WithTimeout(context.WithoutCancel(r.Context()), time.Second) |
| + a, authorityErr := h.Root.executionAuthorityWithMigration(diagnostic, h.Root.executionRuntime.state, execution, execlease.ControllerProcess{}) |
| + cancel() |
| + h.t.Logf("execution current authority=%+v err=%v", a, authorityErr) |
| + h.t.Logf("execution failure request=%+v retained=%s read=%v", execution, raw, err) |
| + } |
| h.t.Logf("root RPC %s status=%d reply=%s", r.URL.Path, response.status, response.body.String()) |
| + h.t.Log("root RPC elapsed", time.Since(started)) |
| } |
| if q.Op == hostbox.RoleLaunchStop && q.Reservation != nil { |
| in, err := h.Launch.Journal.Read(r.Context(), q.Reservation.Target) |
| @@ -175,3 +195,32 @@ func TestPlan14LifecycleTraceStreams(t *testing.T) { |
| t.Fatal("drain frame buffered") |
| } |
| } |
| + |
| +func lifecycleStackTrace(t *testing.T) { |
| + signals := make(chan os.Signal, 1) |
| + signal.Notify(signals, syscall.SIGUSR1) |
| + done := make(chan struct{}) |
| + t.Cleanup(func() { signal.Stop(signals); close(done) }) |
| + go func() { |
| + for { |
| + select { |
| + case <-done: |
| + return |
| + case <-signals: |
| + buf := make([]byte, 2<<20) |
| + n := runtime.Stack(buf, true) |
| + t.Logf("lifecycle goroutines:\n%s", buf[:n]) |
| + } |
| + } |
| + }() |
| +} |
| + |
| +func lifecycleCrashDiagnostic(t *testing.T) { |
| + for _, unit := range []string{"carlos-edge.service", "carlos-instance-control.service"} { |
| + out, err := exec.Command("systemctl", "kill", "--kill-who=main", "--signal=SIGUSR1", unit).CombinedOutput() |
| + t.Logf("stack signal %s: %s %v", unit, out, err) |
| + } |
| + time.Sleep(time.Second) |
| + out, err := exec.Command("journalctl", "-u", "carlos-edge.service", "--no-pager", "-o", "cat").CombinedOutput() |
| + t.Logf("full edge log: %s %v", out, err) |
| +} |
diff --git a/internal/host/rootgeneration_containment_linux.go b/internal/host/rootgeneration_containment_linux.go| index 5a901e96d..ec432e960 100644 |
| --- a/internal/host/rootgeneration_containment_linux.go |
| +++ b/internal/host/rootgeneration_containment_linux.go |
| @@ -197,3 +197,36 @@ func (k generationKernel) containedBinding(ctx context.Context, path string, r r |
| } |
| return nil |
| } |
| + |
| +func (k generationKernel) replacedGroup(ctx context.Context, old release.GenerationReservation) error { |
| + if !old.Closed || !isolatedGeneration(old) || old.Process == (release.GenerationProcess{}) { |
| + return os.ErrPermission |
| + } |
| + boot, err := k.boot() |
| + if err != nil { |
| + return err |
| + } |
| + for _, next := range k.contained { |
| + if next.Closed || next.Retirement != "" || !isolatedGeneration(next) || next.Process.Containment != 1 || |
| + next.Binding.Binding != old.Binding.Binding || next.Binding.Attempt == old.Binding.Attempt || |
| + next.Process.Invocation == "" || next.Process.Invocation == old.Process.Invocation || |
| + next.Process.Group != old.Process.Group || next.Binding.Target.BootID != boot { |
| + continue |
| + } |
| + if old.Binding.Target.BootID == boot && next.Process.GroupIdentity == old.Process.GroupIdentity { |
| + continue |
| + } |
| + path := filepath.Join(k.proc, strconv.Itoa(next.Process.PID)) |
| + status, err := generationRead(path + "/status") |
| + if err != nil { |
| + return err |
| + } |
| + k.contained = []release.GenerationReservation{next} |
| + ok, err := k.containedTask(ctx, path, status) |
| + if err != nil || !ok { |
| + return os.ErrPermission |
| + } |
| + return k.emptyGroups(ctx, filepath.Join(k.cgroups, next.Process.Group)) |
| + } |
| + return os.ErrPermission |
| +} |
diff --git a/internal/host/rootgeneration_kernel_linux.go b/internal/host/rootgeneration_kernel_linux.go| index 1c8806aa4..ea3e73966 100644 |
| --- a/internal/host/rootgeneration_kernel_linux.go |
| +++ b/internal/host/rootgeneration_kernel_linux.go |
| @@ -577,6 +577,11 @@ func generationGroupGone(ctx context.Context, root string, r release.GenerationR |
| if r.Binding.Target.BootID == "" { |
| return os.ErrPermission |
| } |
| + records, _ := ctx.Value(generationCensusReservationsKey{}).([]release.GenerationReservation) |
| + k := generationKernel{proc: "/proc", cgroups: root, contained: records} |
| + if k.replacedGroup(ctx, r) == nil { |
| + return nil |
| + } |
| if r.Binding.Target.BootID != boot { |
| _, err := scanRebootCgroups(ctx, root, []hostbox.StopCgroupIdentity{{RelativePath: group, Device: r.Process.GroupIdentity.Device, Inode: r.Process.GroupIdentity.Inode}}, r.Binding.Target.BootID, boot) |
| return err |
diff --git a/internal/host/rootgeneration_retired_linux.go b/internal/host/rootgeneration_retired_linux.go| index 0f7dc49b3..ebf91d641 100644 |
| --- a/internal/host/rootgeneration_retired_linux.go |
| +++ b/internal/host/rootgeneration_retired_linux.go |
| @@ -133,6 +133,10 @@ func retiredGenerationSnapshot(ctx context.Context, inv generationInventory, rec |
| } |
| func emptyGenerationGroups(ctx context.Context, root string, active ...release.GenerationReservation) error { |
| + return (generationKernel{proc: "/proc", cgroups: "/sys/fs/cgroup", contained: active}).emptyGroups(ctx, root) |
| +} |
| + |
| +func (k generationKernel) emptyGroups(ctx context.Context, root string) error { |
| count := 0 |
| return filepath.WalkDir(root, func(path string, entry fs.DirEntry, err error) error { |
| if path == root && errors.Is(err, os.ErrNotExist) { |
| @@ -159,12 +163,11 @@ func emptyGenerationGroups(ctx context.Context, root string, active ...release.G |
| if _, err := strconv.ParseUint(pid, 10, 32); err != nil { |
| return os.ErrPermission |
| } |
| - task := filepath.Join("/proc", pid) |
| + task := filepath.Join(k.proc, pid) |
| status, err := generationRead(task + "/status") |
| if err != nil { |
| return err |
| } |
| - k := generationKernel{proc: "/proc", cgroups: "/sys/fs/cgroup", contained: active} |
| ok, err := k.containedTask(ctx, task, status) |
| if err != nil { |
| return err |
diff --git a/internal/host/rootgeneration_retirement.go b/internal/host/rootgeneration_retirement.go| index c1091924d..aacaebbc5 100644 |
| --- a/internal/host/rootgeneration_retirement.go |
| +++ b/internal/host/rootgeneration_retirement.go |
| @@ -149,7 +149,7 @@ func productionRetireGenerations(ctx context.Context, g *release.RootGenerations |
| } |
| defer journal.Close() |
| verifier := generationRetirement{guard: hostguard.Guard{DataDir: dataDir(), Root: hostguard.NewRootAuthority(state)}, journal: journal, kernel: productionGenerationAbsence, retiredKernel: productionRetiredGenerationAbsence} |
| - return verifier.retireAll(ctx, g, records) |
| + return verifier.retireAll(context.WithValue(ctx, generationCensusReservationsKey{}, records), g, records) |
| } |
| func (v generationRetirement) retireAll(ctx context.Context, g *release.RootGenerations, records []release.GenerationReservation) error { |
diff --git a/internal/provision/testdata/plan14-root-reads-p3.tsv b/internal/provision/testdata/plan14-root-reads-p3.tsv| index 8b99c5914..c4f16a6af 100644 |
| --- a/internal/provision/testdata/plan14-root-reads-p3.tsv |
| +++ b/internal/provision/testdata/plan14-root-reads-p3.tsv |
| @@ -248,7 +248,7 @@ internal/host/prooffile.go proofFileSource authority B3 rev3=PROPOSED-p3;identit |
| internal/host/prooffile.go sameProofFiles authority B3 rev3=PROPOSED-p3;identity=root adoption and rollback proof binder uid 0;basis=compares retained and current proof files using persistent evidence, refusing an implicit upgrade of old unproven files e531e1eac189f957d9cc0151319093e0b2ab0fcd80412abc0d2f3fefcfc3421a |
| internal/host/proofnamed.go proofBindingSource.record authority B3 rev3=PROPOSED-p3;identity=root adoption and rollback proof binder uid 0;basis=binds named-unit provenance to boot-stable file evidence and preserves the original no-census hash shape 97f1501b9543e36c6a231170425c5c79c7c22489500aadf119e064abc1abde93 |
| internal/host/proofnamed.go proofBindingSource.trustedBytes authority B3,J1 rev3=PROPOSED-p3;identity=root adoption and rollback proof binder uid 0;basis=captures persistent evidence from the checked root-owned unit file descriptor alongside its bytes file:ReadAll@422 aa6e98efc7a57b01ed38cd051642d8d2f0b6abf2d527d5f70533e7365746096e |
| -internal/host/rootgeneration_kernel_linux.go generationGroupGone authority E2 rev3=PROPOSED-p3;identity=root claim/adoption controller uid 0;basis=refuses a present cgroup placement from another boot; an absent old placement carries no process identity to compare;rev3=PROPOSED-p3;run=11;identity=root claim, migration or adoption controller uid 0;basis=requires a known boot before interpreting a present cgroup; same-boot exact scan and E reboot population scan use separate authority rules;rev3=PROPOSED-p3;run=11;identity=root claim, migration or adoption controller uid 0;basis=requires a known boot before interpreting a present cgroup; same-boot exact scan and E reboot population scan use separate authority rules file:Lstat@194 1ed5a4aadf5334f37027e04b258d7fb4a6413356d2d21257ae5ef9c626655376 |
| +internal/host/rootgeneration_kernel_linux.go generationGroupGone authority E2 rev3=PROPOSED-p3;run=26;identity=root retirement and pin readers uid 0 under release exclusion;basis=checks absence of the exact recorded containment, or a different current containment independently bound to a live root reservation; neither pathname reuse nor population alone proves replacement; original process death remains mandatory file:Lstat@194 ff5cfdde7ebfda21069cd204cb52dab21cd1885d84658ea7e8bef5de445f68c1 |
| internal/host/rootgeneration_parked.go generationRetirements authority E2 rev3=PROPOSED-p3;run=16;identity=root claim/adoption controller uid 0;basis=verifies retained completed serving-handover journals against both root payloads, retired predecessor, exact stop receipt, ownership and persistent path identity; follows transfers through terminal retirement without removing launch reservations; TestPlan14ServingHandoverPins and TestPlan14ServingHandoverPinsRootRunner e7fb5fc50073e49b5e8f1c0d9a09e9e3cc8da8f96902229d2af4d2c7df8fb20e |
| internal/host/rootgeneration_payload_linux.go generationPayloadFiles authority E2 rev3=PROPOSED-p3;run=24;identity=root claim/adopt/update controller uid 0;basis=verifies exact retained persistent file identity; on a cross-boot ancestry refusal permits only the configured carlos owner at the managed root itself, retaining root-owned protected ancestors and refusing writable roots 58e62ff06eba73b12b0e892d862a338dafb62170a47a0abc7e92989f5f11b0d7 |
| internal/host/rootgenerations_launch.go rootGenerationSource.reserve authority E2,E3 rev3=PROPOSED-p3;identity=root claim/adoption controller uid 0;basis=writes root runtime intent after the generation reservation and before launch under release and host exclusion b09d7390279bcb302d83c282a517680f43c44013e07fc490f37bc798fd1703ba |
| @@ -365,7 +365,7 @@ internal/hostguard/recoveryfence.go Guard.PendingStopFences authority C,J4 rev3= |
| internal/hostguard/recoveryfence.go Guard.pendingFences authority C,J2,J4,J1 rev3=PROPOSED-p3;run=19;identity=root;basis=reads root-owned records under host lock and keeps exact barrier classification separate from ordinary stop recovery; legacy behavior retained 20be9535fd54587a54e3027f5388581ae5a64e5fcfced5a6031ffc9a4fc76a3b |
| internal/host/claimroleassembly_linux.go verifiedClaimExecutable authority C,G,J4 rev3=PROPOSED-p3;run=19;identity=agent uid carlos;basis=pins root-owned executable bytes and keeps the verified descriptor while supplying its absolute path to the root launcher file:Fstat@1122,file:Fstat@579,file:OpenFile@376,file:Read@949 7a7e9af520e4157e60ae93d12c3268a629a9b0bd1e83b263cdd27103cf2e227c |
| internal/host/claimroleassembly_linux.go claimExecutableName authority C,G,J4 rev3=PROPOSED-p3;run=19;identity=agent uid carlos;basis=duplicates the existing descriptor with CLOEXEC and its validated absolute name; never reopens a potentially replaced path e887880731a16c9e7934aef33a08169b34e7236f6d3b85a195602d35f8b69107 |
| -@coverage internal/host b81380b5d103ae9aef7caeb8ee7aae0604be9d19c418aaf0324ee80fff5497d2 |
| +@coverage internal/host 2b0767bf084d05d2baf485436fbe58e3d418c3b51c21679b5a6d5ea86d27850e |
| @coverage internal/hostbox 0b6ddb07bc7ef62f23e402757155825e8d098eb47ec9288c09b8754bb1632e56 |
| @coverage internal/hostguard ca664497969680d692172abda403b6a5d6664f8d7c8bc16a6440f02dd8426be6 |
| internal/host/claimstop.go ClaimStopCoordinator.FenceAndStop authority C,J4 rev3=PROPOSED-p3;run=20;identity=root instance-control uid 0;basis=authenticated session stop retains root fence before independent unjournaled absence check; proves no journaled installation, containment or open files before bypassing nonexistent writer closure; normal drain remains for recorded launches; no-census unchanged file:Read@1610 58147f3abbf278037fc660392aeec0b6e76b667e3fe8435845ebaedc62d2ead1 |
| @@ -430,7 +430,7 @@ internal/hostclaim/delete.go FreeAfterPark authority C rev3=PROPOSED-p3;run=22;i |
| internal/hostclaim/retainedloss.go retainNativeAdmissionLosses authority C rev3=PROPOSED-p3;run=22;identity=authenticated console or lifecycle worker through existing claim CAS;basis=copies exact native admission evidence and original holder identity into claim-retained state; does not certify losses or relax signed-declaration archival 278b599c5e822d63a2c50bb2e0ba563371564cbdfb9c05531d7efe223aa26d3f |
| internal/host/rootexecution_settle.go retiredExecutionLaunch authority C,E2,J4 rev3=PROPOSED-p3;run=22;identity=root execution broker uid 0;basis=exact retired capture and root launch closure only; caller first verifies canonical root receipt and exact inventory; no new stop effect or admission 6ccc8586537ab228f95543307e20caa6023101ffe54e72155e57b36f9d4c3d34 |
| internal/host/rootgenerationpins.go productionRootGenerationPins authority E2 rev3=PROPOSED-p3;run=22;identity=root uid 0 under release exclusion;basis=retired ordinary isolated cohort only: canonical root stop receipts, irreversible retired captures, closed journals, exact original process identities gone and all managed instance cgroups empty; live pending named or legacy state retains prior verifier; generic process identity accepts valid Linux zero boot ticks file:Open@195 2cf7afda7ffe06c85030ff80c1d1d44b3f67b1589267ca0c0dfde5c81d4827fc |
| -internal/host/rootgeneration_retirement.go productionRetireGenerations authority E2 rev3=PROPOSED-p3;run=22;identity=root uid 0 under release exclusion;basis=retired ordinary isolated cohort only: canonical root stop receipts, irreversible retired captures, closed journals, exact original process identities gone and all managed instance cgroups empty; live pending named or legacy state retains prior verifier; generic process identity accepts valid Linux zero boot ticks file:Open@292 f1eca8626049f13e079b6fe34f5f38117c7abf97a11105250bc1c4760bbd8f0d |
| +internal/host/rootgeneration_retirement.go productionRetireGenerations authority E2 rev3=PROPOSED-p3;run=26;identity=root retirement and pin readers uid 0 under release exclusion;basis=carries the root-owned reservation snapshot under release exclusion into absence checks; closure and retirement receipts remain independently checked file:Open@292 bbbb4a42802b06da0b12d4bb921cb2d3d4b8c53a15af2054d0bdc971f8a3ea83 |
| internal/host/rootgeneration_kernel_linux.go generationStart authority E2 rev3=PROPOSED-p3;run=22;identity=root uid 0 under release exclusion;basis=retired ordinary isolated cohort only: canonical root stop receipts, irreversible retired captures, closed journals, exact original process identities gone and all managed instance cgroups empty; live pending named or legacy state retains prior verifier; generic process identity accepts valid Linux zero boot ticks a10512a1faad1675d091227b19feade6f65a0f834c19be472014fd6f77f7f389 |
| internal/host/rootgeneration_kernel_linux.go productionGenerationAbsence authority E2 rev3=PROPOSED-p3;run=24;identity=root claim/adopt/update controller uid 0;basis=checks recorded launch targets and controller absence then inspects all tasks with independently verified service classification 03a98919c6c32638e9ec5a2658a68c58d895d931b3186e4968533ad28e57eadc |
| internal/host/rootgeneration_kernel_linux.go generationRecordedAbsence authority E2 rev3=PROPOSED-p3;run=24;identity=root claim/adopt/update controller uid 0;basis=general absence retains recorded controller death plus exact main/tenant process and containment absence; only retired isolated proofs use the scoped controller reference check c817b4c8ecc0045bde88afe7c07f040e309db684f3a0455908ca61d672f2b9f5 |
| @@ -438,7 +438,7 @@ internal/host/rootgeneration_retired_linux.go isolatedGeneration authority E2 re |
| internal/host/rootgeneration_retired_linux.go retiredGenerationReceipt authority E2 rev3=PROPOSED-p3;run=22;identity=root uid 0 under release exclusion;basis=retired ordinary isolated cohort only: canonical root stop receipts, irreversible retired captures, closed journals, exact original process identities gone and all managed instance cgroups empty; live pending named or legacy state retains prior verifier; generic process identity accepts valid Linux zero boot ticks 258b9f88fe29b944d2dfafebcbb383ac9742b4920909eda57c4588d254939377 |
| internal/host/rootgeneration_retired_linux.go productionRetiredGenerationAbsence authority E2 rev3=PROPOSED-p3;run=24;identity=root claim/adopt/update controller uid 0;basis=requires isolated reservation and exact root retirement receipt before checking recorded target absence and scoped shared-controller references; generic or named paths retain the stricter verifier 8f95c6232d728233a3a2ea091894308e2c238cfc83ed33812c08b562da814a9b |
| internal/host/rootgeneration_retired_linux.go retiredGenerationSnapshot authority E2 rev3=PROPOSED-p3;run=24;identity=root claim/adopt/update controller uid 0;basis=proves exact retired installations through receipts and closed matched journals; active isolated siblings remain separately retained and pinned rather than making retirement a whole-box proof a00bec36603a167bbbe9d3d724da30a7db0025f7769801868c8514466644ef21 |
| -internal/host/rootgeneration_retired_linux.go emptyGenerationGroups authority E2 rev3=PROPOSED-p3;run=24;identity=root claim/adopt/update controller uid 0;basis=walks managed groups and refuses every process not independently matched to active root containment; recorded retired groups still require emptiness file:ReadFile@522,file:WalkDir@130 7cf453e76cda0e9235de0b5acfa45f875dad52ef51b177dde148c8d56e9ea247 |
| +internal/host/rootgeneration_retired_linux.go emptyGenerationGroups authority E2 rev3=PROPOSED-p3;run=26;identity=root retirement and pin readers uid 0 under release exclusion;basis=production wrapper supplies procfs, cgroupfs and the root reservation snapshot to the bounded managed-group inspection 24b4a91713227557ce9d66fff32aaf5ae80c3f532ea8335e1bc132661f4d9bb5 |
| internal/host/rootgeneration_retired_linux.go productionRetiredGenerationKernelCensus authority E2 rev3=PROPOSED-p3;run=24;identity=root claim/adopt/update controller uid 0;basis=checks retired installations own recorded groups and controller references; all populated managed groups must match a live root reservation, so active isolated siblings pass but unknown processes refuse 543f196321218063c074314fb9d05e95bc205db553890b127e11f7a8f40815aa |
| internal/host/rootgeneration_retired_other.go productionRetiredGenerationAbsence authority E2 rev3=PROPOSED-p3;run=24;identity=root claim/adopt/update controller uid 0;basis=requires isolated reservation and exact root retirement receipt before checking recorded target absence and scoped shared-controller references; generic or named paths retain the stricter verifier a281b05dae1d2b65b0fbcffe6b7cb65002da5dabfdc4f2596bd05511594b3546 |
| internal/host/rootgeneration_retired_other.go productionRetiredGenerationKernelCensus authority E2 rev3=PROPOSED-p3;run=24;identity=root claim/adopt/update controller uid 0;basis=checks retired installations own recorded groups and controller references; all populated managed groups must match a live root reservation, so active isolated siblings pass but unknown processes refuse f61b35d6064ff8a5a4fa757e229167d1b8e71b0109f8d116e0f288c533552a5c |
| @@ -453,7 +453,7 @@ internal/host/rootgeneration_census_other.go productionGenerationKernelCensus au |
| internal/host/rootgeneration_containment_linux.go generationKernel.containedTask authority E2 rev3=PROPOSED-p3;run=24;identity=root claim/adopt/update controller uid 0;basis=matches only live unclosed containment reservations to current kernel group and process identity; completed historical reservations cannot shadow an active sibling wake 25b3655284fb97a02d0f2e090ec105a18f178669979c147645b5a0f1966c893a |
| internal/host/rootgeneration_kernel_linux.go generationKernel.task authority E2 rev3=PROPOSED-p3;run=24;identity=root claim/adopt/update controller uid 0;basis=retains the original root-only scan with matching namespaces and strict reference checks when service lookup is unavailable; only independently verified unit, cgroup and executable identity permits nonroot or private-namespace services; every accepted service still has all references and process start rechecked file:Readlink@1197 d75cd84dda21cba70763794bfd3a996f212fc75f6d01e4ce0ffae93959b61bb7 |
| internal/host/rootgeneration_kernel_linux.go generationMounts authority E2 rev3=PROPOSED-p3;run=24;identity=root claim/adopt/update controller uid 0;basis=rejects target-tree bind mounts and nsfs references; independently verified system services may retain ancestor mounts used by shipped private mount namespaces; unknown tasks retain the strict namespace policy 8bfb9496d7c24fd4bca0f7b84cd0b9cf01a1c116a9112f66738be5dde93b1f75 |
| -internal/host/rootgeneration_kernel_linux.go generationRecordedTargets authority E2 rev3=PROPOSED-p3;run=24;identity=root claim/adopt/update controller uid 0;basis=revalidates persistent executable/tree identity, exact cgroup absence and recorded main/tenant process death; shared controller is checked separately by the caller 220fa257f513ea71a6e0b19e4fd5b5ec04c4454d272e09493d9fa1977db70f6d |
| +internal/host/rootgeneration_kernel_linux.go generationRecordedTargets authority E2 rev3=PROPOSED-p3;run=26;identity=root retirement and pin readers uid 0 under release exclusion;basis=revalidates persistent executable/tree identity and exact recorded main/tenant process death; a reused group requires independently verified successor containment; shared controller is checked separately by the caller 220fa257f513ea71a6e0b19e4fd5b5ec04c4454d272e09493d9fa1977db70f6d |
| internal/host/rootgeneration_kernel_linux.go generationTaskFDs authority E2 rev3=PROPOSED-p3;run=24;identity=root claim/adopt/update controller uid 0;basis=holds the inspected descriptor directory open and checks every live descriptor against the retained generation identities; reports the refusing descriptor without weakening namespace checks file:Open@143,file:Readdirnames@367 b8be888cc74e90cc0b0f3a6c4efd320b3edfd6f16a3c08e0ae9ad34549ea00de |
| internal/host/rootgeneration_kernel_linux.go type generationKernel authority E2 rev3=PROPOSED-p3;run=24;identity=root claim/adopt/update controller uid 0;basis=root kernel generation verifier carries service classification and exact retained containment; all observations are independently checked before certification 3463afcbd055b35fa94988868d8cb721272c2bc066220a0bd59e3529e811131b |
| internal/host/rootgeneration_kernel_other.go productionGenerationAbsence authority E2 rev3=PROPOSED-p3;run=24;identity=root claim/adopt/update controller uid 0;basis=checks recorded launch targets and controller absence then inspects all tasks with independently verified service classification de47d997019075326e1d5513d7576390d085566fd69edc35efd00f067fb1db24 |
| @@ -485,3 +485,5 @@ internal/host/rootgeneration_kernel_linux.go generationFileProof authority E2,G |
| internal/host/rootgeneration_services_linux.go generationServiceSupervisor authority E2,G rev3=PROPOSED-p3;run=25;identity=root generation kernel reader uid 0;basis=systemd main PID and trusted unit/executable identify only the edge main process threads in its exact supervise subgroup; unknown child processes and other services remain refused; does not exempt descriptor or generation-reference checks 62fcbfa690841ecc68ed935cae404a75b0d4f3b533af871af64a9ff19586d85b |
| internal/release/rootgenerationproof.go GenerationFileProof authority E2 rev3=PROPOSED-p3;run=25;identity=root generation activation, retirement and release readers uid 0;basis=both generation consumers require the same persistent filesystem/handle/inode/type/owner/mode and filesystem-relative source path across private and host mount layouts; same-boot device stays exact; root-owned ancestry remains independently checked by each opener 1f61004a6273ecd4b92d5cdb31b06d4e1cf1ff57f668131ea9b2d0c57b5b4f02 |
| internal/release/rootgenerationproof.go generationProofUnescape authority E2 rev3=PROPOSED-p3;run=25;identity=root generation activation, retirement and release readers uid 0;basis=both generation consumers require the same persistent filesystem/handle/inode/type/owner/mode and filesystem-relative source path across private and host mount layouts; same-boot device stays exact; root-owned ancestry remains independently checked by each opener 744d84125ad095053c32cdbfe83632b284d222ada237fa3dca5b5c9f5f307656 |
| +internal/host/rootgeneration_containment_linux.go generationKernel.replacedGroup authority E2 rev3=PROPOSED-p3;run=26;identity=root retirement and pin readers uid 0 under release exclusion;basis=only a closed isolated predecessor may accept a different ordinary containment at its canonical pathname; requires another root reservation for the same host/account/app, a distinct attempt and invocation, current boot, exact live kernel containment and every occupant independently checked; old process identities are still checked absent by the caller bc1e292d60f204bf470d4805d24b9b11fefe22c792b1dba2a55cfc2675670e03 |
| +internal/host/rootgeneration_retired_linux.go generationKernel.emptyGroups authority E2 rev3=PROPOSED-p3;run=26;identity=root retirement and pin readers uid 0 under release exclusion;basis=bounded cgroup tree inspection rejects symlinks and every task lacking a matching live root reservation with verified process, executable, namespace and containment identity file:ReadFile@492,file:WalkDir@100 3918f75eb9d2a483079dcd05e3c4a8b2be99d6f6a6c1692c45b9255965885723 |