carlos / platform Public

WIP claim Plan 14 track E checkpoint 25 (not for merge): active-pin retirement and kernel descriptor scanning fixes; four root-runner acceptance tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev e6196f864ec9c9fa6980edccf99471c4aaa14b82 parent 980d851
9 files changed, +424 −102
  • internal/host/plan14_generation_census_root_linux_test.go +40 −26
  • internal/host/plan14_generation_inventory_root_test.go +27 −26
  • internal/host/plan14_generation_kernel_linux_test.go +19 −0
  • internal/host/plan14_generation_retirement_binding_test.go +140 −0
  • internal/host/plan14_generation_retirement_root_linux_test.go +112 −0
  • internal/host/rootgeneration_kernel_linux.go +28 −10
  • internal/host/rootgeneration_retirement.go +47 −24
  • internal/provision/testdata/plan14-root-reads-e.tsv +8 −5
  • internal/release/plan14_generation_completeness_root_test.go +3 −11

CI passed — run details

CI log
=== gate ===
cd launchctx && go vet ./... && go test ./... -count=1
ok  	amadan.net/carlos/platform.git/launchctx	0.012s
?   	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	349.488s
?   	github.com/carlosframework/platform/cmd/changelog	[no test files]
ok  	github.com/carlosframework/platform/cmd/console	3.306s
ok  	github.com/carlosframework/platform/cmd/moveprobe	92.318s
ok  	github.com/carlosframework/platform/cmd/zddprobe	0.707s
ok  	github.com/carlosframework/platform/internal/activator	234.308s
ok  	github.com/carlosframework/platform/internal/agentsock	0.015s
ok  	github.com/carlosframework/platform/internal/alerting	0.004s
ok  	github.com/carlosframework/platform/internal/analytics	0.023s
ok  	github.com/carlosframework/platform/internal/appconfig	2.380s
ok  	github.com/carlosframework/platform/internal/apperr	0.074s
ok  	github.com/carlosframework/platform/internal/applog	0.139s
ok  	github.com/carlosframework/platform/internal/awsinv	0.004s
ok  	github.com/carlosframework/platform/internal/boxchoice	0.004s
ok  	github.com/carlosframework/platform/internal/boxid	0.650s
?   	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.008s
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.697s
ok  	github.com/carlosframework/platform/internal/changelog	0.303s
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	366.063s
?   	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	5.777s
ok  	github.com/carlosframework/platform/internal/dbdoor/dbdoortest	0.009s
ok  	github.com/carlosframework/platform/internal/dbverify	0.004s
ok  	github.com/carlosframework/platform/internal/dnsrec	0.004s
ok  	github.com/carlosframework/platform/internal/docsite	0.005s
ok  	github.com/carlosframework/platform/internal/domains	2.194s
ok  	github.com/carlosframework/platform/internal/doorkey	0.006s
ok  	github.com/carlosframework/platform/internal/economics	0.007s
ok  	github.com/carlosframework/platform/internal/edge	26.353s
?   	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.126s
ok  	github.com/carlosframework/platform/internal/gate	1.656s
ok  	github.com/carlosframework/platform/internal/genselect	0.015s
ok  	github.com/carlosframework/platform/internal/geo	0.017s
ok  	github.com/carlosframework/platform/internal/geoip	0.030s
ok  	github.com/carlosframework/platform/internal/historyreadback	0.155s
ok  	github.com/carlosframework/platform/internal/host	135.176s
ok  	github.com/carlosframework/platform/internal/hostactors	0.286s
ok  	github.com/carlosframework/platform/internal/hostbox	2.299s
ok  	github.com/carlosframework/platform/internal/hostclaim	0.516s
ok  	github.com/carlosframework/platform/internal/hostevidence	0.017s
ok  	github.com/carlosframework/platform/internal/hostexec	5.084s
ok  	github.com/carlosframework/platform/internal/hostfence	0.035s
ok  	github.com/carlosframework/platform/internal/hostfiles	10.618s
ok  	github.com/carlosframework/platform/internal/hostfloor	0.017s
ok  	github.com/carlosframework/platform/internal/hostguard	0.975s
ok  	github.com/carlosframework/platform/internal/hostops	1.508s
?   	github.com/carlosframework/platform/internal/hostops/fencerelease	[no test files]
ok  	github.com/carlosframework/platform/internal/hostops/internal/fencerelease	0.015s
ok  	github.com/carlosframework/platform/internal/hostquarantine	0.017s
ok  	github.com/carlosframework/platform/internal/hostrelocate	0.478s
ok  	github.com/carlosframework/platform/internal/hostresolve	0.145s
ok  	github.com/carlosframework/platform/internal/hostwrite	0.015s
ok  	github.com/carlosframework/platform/internal/infraview	0.015s
ok  	github.com/carlosframework/platform/internal/instancectl	1.828s
ok  	github.com/carlosframework/platform/internal/instanceexec	2.758s
ok  	github.com/carlosframework/platform/internal/instancefs	0.015s
ok  	github.com/carlosframework/platform/internal/instancekey	0.021s
ok  	github.com/carlosframework/platform/internal/instanceupdates	0.023s
ok  	github.com/carlosframework/platform/internal/layout	1.482s
ok  	github.com/carlosframework/platform/internal/lease	9.466s
ok  	github.com/carlosframework/platform/internal/lease/leasetest	0.005s
ok  	github.com/carlosframework/platform/internal/ledger	0.012s
ok  	github.com/carlosframework/platform/internal/mail	0.159s
ok  	github.com/carlosframework/platform/internal/maintenance	0.091s
ok  	github.com/carlosframework/platform/internal/maintenanceexec	5.562s
ok  	github.com/carlosframework/platform/internal/maintenanceguard	0.093s
ok  	github.com/carlosframework/platform/internal/metrics	0.005s
ok  	github.com/carlosframework/platform/internal/moves	21.131s
ok  	github.com/carlosframework/platform/internal/moves/archive	0.787s
ok  	github.com/carlosframework/platform/internal/objstore	0.174s
ok  	github.com/carlosframework/platform/internal/ops	0.161s
ok  	github.com/carlosframework/platform/internal/park	0.210s
ok  	github.com/carlosframework/platform/internal/privatefile	0.006s
ok  	github.com/carlosframework/platform/internal/provision	13.888s
ok  	github.com/carlosframework/platform/internal/publicrelease	0.007s
?   	github.com/carlosframework/platform/internal/regionname	[no test files]
ok  	github.com/carlosframework/platform/internal/registry	4.121s
ok  	github.com/carlosframework/platform/internal/relay	7.237s
ok  	github.com/carlosframework/platform/internal/release	22.294s
ok  	github.com/carlosframework/platform/internal/rollout	3.806s
ok  	github.com/carlosframework/platform/internal/rolloutwords	0.004s
ok  	github.com/carlosframework/platform/internal/rootaws	0.007s
ok  	github.com/carlosframework/platform/internal/rootparticipation	0.008s
ok  	github.com/carlosframework/platform/internal/rootstate	0.025s
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.345s
ok  	github.com/carlosframework/platform/internal/sim/hostlife	10.209s
ok  	github.com/carlosframework/platform/internal/sim/litestreamfacts	3.725s
ok  	github.com/carlosframework/platform/internal/status	0.042s
ok  	github.com/carlosframework/platform/internal/steering	0.008s
ok  	github.com/carlosframework/platform/internal/stopevidence	0.273s
ok  	github.com/carlosframework/platform/internal/svcrole	0.002s
ok  	github.com/carlosframework/platform/internal/telemetry	0.005s
ok  	github.com/carlosframework/platform/internal/termfence	0.005s
ok  	github.com/carlosframework/platform/internal/tunnelclient	0.004s
ok  	github.com/carlosframework/platform/internal/unixdial	0.003s
ok  	github.com/carlosframework/platform/internal/updatewords	0.006s
ok  	github.com/carlosframework/platform/internal/usage	0.004s
ok  	github.com/carlosframework/platform/internal/version	0.004s
ok  	github.com/carlosframework/platform/internal/vet	1.573s
=== cross ===
--- shard 0 of 5 ---
cross-compiling darwin/arm64
--- shard 1 of 5 ---
cross-compiling darwin/amd64
--- shard 2 of 5 ---
cross-compiling linux/arm64
--- shard 3 of 5 ---
cross-compiling windows/amd64
--- shard 4 of 5 ---
cross-compiling windows/arm64
=== minio-contract ===
minio contract: arm64, minio RELEASE.2025-09-07T16-13-09Z, mc RELEASE.2025-08-13T08-35-41Z
  minio: cached, digest verified
  mc: cached, digest verified
  minio healthy on 127.0.0.1:20999
Added `local` successfully.
Bucket created successfully `local/contract-bucket`.
Bucket created successfully `local/denied-bucket`.
Created policy `contract-only` successfully.
Added user `scopeduser` successfully.
Attached Policies: [contract-only]
To User: scopeduser
  buckets and scoped user ready
=== RUN   TestDeliveredContractMinio
=== RUN   TestDeliveredContractMinio/put
=== RUN   TestDeliveredContractMinio/get_round-trips_the_body
=== RUN   TestDeliveredContractMinio/list_shows_the_key
=== RUN   TestDeliveredContractMinio/head_and_list_agree_on_the_version
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_its_own_object
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed
=== RUN   TestDeliveredContractMinio/delete_removes_it
=== RUN   TestDeliveredContractMinio/cross-bucket_denial
--- PASS: TestDeliveredContractMinio (0.02s)
    --- PASS: TestDeliveredContractMinio/put (0.00s)
    --- PASS: TestDeliveredContractMinio/get_round-trips_the_body (0.00s)
    --- PASS: TestDeliveredContractMinio/list_shows_the_key (0.00s)
    --- PASS: TestDeliveredContractMinio/head_and_list_agree_on_the_version (0.00s)
    --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload (0.00s)
    --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route (0.01s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed (0.00s)
    --- PASS: TestDeliveredContractMinio/delete_removes_it (0.00s)
    --- PASS: TestDeliveredContractMinio/cross-bucket_denial (0.00s)
PASS
ok  	github.com/carlosframework/platform/internal/objstore	0.023s
=== RUN   TestT4StorageProbeMinio
2026/10/14 13:44:37 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18DE694C2A5F1253, HostID: 7c420053e0fbcd7f41653fdd92ffa2802c903d05303b1aa15adc2e8bb65d183f, api error SignatureDoesNotMatch: The request signature we calculated does not match the signature you provided. Check your key and signing method.
--- PASS: TestT4StorageProbeMinio (0.01s)
PASS
ok  	github.com/carlosframework/platform/internal/host	0.021s
minio contract: PASS
diff --git a/internal/host/plan14_generation_census_root_linux_test.go b/internal/host/plan14_generation_census_root_linux_test.go
index 31a2932e1..043bde8cb 100644
--- a/internal/host/plan14_generation_census_root_linux_test.go
+++ b/internal/host/plan14_generation_census_root_linux_test.go
@@ -1,4 +1,4 @@
-//go:build linux
+//go:build linux && rootrunner
package host
@@ -8,7 +8,7 @@ import (
"path/filepath"
"testing"
- "github.com/carlosframework/platform/internal/rootstate"
+ "golang.org/x/sys/unix"
)
func TestPlan14GenerationKernelCensusRootRunner(t *testing.T) {
@@ -44,32 +44,46 @@ func TestPlan14GenerationKernelCensusRootRunner(t *testing.T) {
if _, _, err := productionGenerationKernelCensus(t.Context()); err != nil {
t.Fatal("closed file still retained", err)
}
- return
- }
- parent := os.Getenv("PLAN14_GENERATION_INVENTORY_ROOT_RUNNER")
- if parent == "" {
- t.Skip("requires a designated disposable Linux root runner with unshare and cgroup support")
- }
- if os.Geteuid() != 0 {
- t.Fatal("designated runner must execute as root")
- }
- state, err := rootstate.Open(parent, false, true)
- if err != nil {
- t.Fatal(err)
- }
- state.Close()
- dir, err := os.MkdirTemp(parent, "generation-kernel-")
- if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() {
- if err := os.RemoveAll(dir); err != nil {
- t.Error(err)
+ if err := os.WriteFile(path, []byte("root census reference"), 0644); err != nil {
+ t.Fatal(err)
+ }
+ alias := filepath.Join(dir, "alias")
+ if err := os.Mkdir(alias, 0755); err != nil {
+ t.Fatal(err)
+ }
+ if err := unix.Mount(filepath.Dir(path), alias, "", unix.MS_BIND, ""); err != nil {
+ t.Fatal(err)
}
- })
- if err := os.Chmod(dir, 0755); err != nil {
- t.Fatal(err)
+ mounted := true
+ t.Cleanup(func() {
+ if mounted {
+ if err := unix.Unmount(alias, 0); err != nil {
+ t.Error(err)
+ }
+ }
+ })
+ if _, _, err := productionGenerationKernelCensus(t.Context()); err == nil {
+ t.Fatal("bind alias certified")
+ }
+ if err := unix.Unmount(alias, 0); err != nil {
+ t.Fatal(err)
+ }
+ mounted = false
+ ns, err := os.Open("/proc/self/ns/mnt")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, _, err := productionGenerationKernelCensus(t.Context()); err == nil {
+ ns.Close()
+ t.Fatal("retained namespace descriptor certified")
+ }
+ ns.Close()
+ if _, _, err := productionGenerationKernelCensus(t.Context()); err != nil {
+ t.Fatal("released references still retained", err)
+ }
+ return
}
+ dir := generationRootRunnerDir(t)
// A private PID/mount namespace gives the scan a controlled all-root census.
// No generated tenant unit or running-tenant containment is accepted here.
cmd := exec.CommandContext(t.Context(), "unshare", "--mount", "--pid", "--fork", "--mount-proc", os.Args[0], "-test.run=^TestPlan14GenerationKernelCensusRootRunner$", "-test.count=1")
diff --git a/internal/host/plan14_generation_inventory_root_test.go b/internal/host/plan14_generation_inventory_root_test.go
index 88bca6946..97012a507 100644
--- a/internal/host/plan14_generation_inventory_root_test.go
+++ b/internal/host/plan14_generation_inventory_root_test.go
@@ -1,4 +1,4 @@
-//go:build linux
+//go:build linux && rootrunner
package host
@@ -14,31 +14,7 @@ import (
)
func TestPlan14GenerationInventoryCheckedRootReader(t *testing.T) {
- parent := os.Getenv("PLAN14_GENERATION_INVENTORY_ROOT_RUNNER")
- if parent == "" {
- t.Skip("requires a designated disposable root runner and its publicly traversable scratch parent")
- }
- if os.Geteuid() != 0 {
- t.Fatal("designated runner must execute as root")
- }
- check, err := rootstate.Open(parent, false, true)
- if err != nil {
- t.Fatal(err)
- }
- check.Close()
- // Match the production journal ancestry checked by OpenTrustedFile.
- dir, err := os.MkdirTemp(parent, "generation-inventory-")
- if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() {
- if err := os.RemoveAll(dir); err != nil {
- t.Error(err)
- }
- })
- if err := os.Chmod(dir, 0755); err != nil {
- t.Fatal(err)
- }
+ dir := generationRootRunnerDir(t)
censusPath, fencesPath, journalPath := filepath.Join(dir, "census"), filepath.Join(dir, "fences"), filepath.Join(dir, "journals.db")
census, err := rootstate.Open(censusPath, true, false)
if err != nil {
@@ -120,3 +96,28 @@ func TestPlan14GenerationInventoryCheckedRootReader(t *testing.T) {
})
}
}
+
+func generationRootRunnerDir(t *testing.T) string {
+ t.Helper()
+ if os.Geteuid() != 0 {
+ t.Fatal("designated runner must execute as root")
+ }
+ dir, err := os.MkdirTemp("/var/lib", "plan14-generation-")
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := os.RemoveAll(dir); err != nil {
+ t.Error(err)
+ }
+ })
+ if err := os.Chmod(dir, 0755); err != nil {
+ t.Fatal(err)
+ }
+ check, err := rootstate.Open(dir, false, true)
+ if err != nil {
+ t.Fatal(err)
+ }
+ check.Close()
+ return dir
+}
diff --git a/internal/host/plan14_generation_kernel_linux_test.go b/internal/host/plan14_generation_kernel_linux_test.go
index e390bfd8d..60ad8f0f3 100644
--- a/internal/host/plan14_generation_kernel_linux_test.go
+++ b/internal/host/plan14_generation_kernel_linux_test.go
@@ -68,6 +68,25 @@ func generationRelink(t *testing.T, target, path string) {
generationLink(t, target, path)
}
+func TestPlan14GenerationKernelOwnDescriptors(t *testing.T) {
+ f := generationProc(t)
+ if err := os.Remove(f.task + "/fd"); err != nil {
+ t.Fatal(err)
+ }
+ generationLink(t, "/proc/self/fd", f.task+"/fd")
+ if _, err := (generationKernel{proc: f.proc}).absent(t.Context(), f.reservation); err != nil {
+ t.Fatal("scanner's own descriptor disappeared during inspection", err)
+ }
+ file, err := os.Open(f.reservation.Binding.Path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer file.Close()
+ if _, err := (generationKernel{proc: f.proc}).absent(t.Context(), f.reservation); err == nil {
+ t.Fatal("real open staged file ignored")
+ }
+}
+
func TestPlan14GenerationKernelReferences(t *testing.T) {
for _, attack := range []string{"none", "exe", "fd", "deleted-fd", "cwd", "root", "mapped", "mount", "foreign-mount-ns", "foreign-pid-ns", "namespace-fd", "uncontained", "malformed-stat", "malformed-maps", "missing-status", "missing-fds", "missing-mounts", "missing-boot", "cancel", "thread"} {
t.Run(attack, func(t *testing.T) {
diff --git a/internal/host/plan14_generation_retirement_binding_test.go b/internal/host/plan14_generation_retirement_binding_test.go
index 8a6ea95b8..41036596c 100644
--- a/internal/host/plan14_generation_retirement_binding_test.go
+++ b/internal/host/plan14_generation_retirement_binding_test.go
@@ -3,8 +3,10 @@ package host
import (
"context"
"os"
+ "strings"
"testing"
+ "github.com/carlosframework/platform/internal/hostbox"
"github.com/carlosframework/platform/internal/release"
"github.com/carlosframework/platform/internal/rootstate"
)
@@ -105,6 +107,61 @@ func TestPlan14GenerationRetirementAfterLostStarted(t *testing.T) {
}
}
+func TestPlan14GenerationRetirementKeepsActivePin(t *testing.T) {
+ for _, mode := range []string{"running", "running-wrong-controller", "running-wrong-inputs", "running-wrong-process", "settled-open-writers", "closed-unsettled", "closed", "wrong-controller", "wrong-inputs", "kernel"} {
+ t.Run(mode, func(t *testing.T) {
+ f, disk, _ := generationLaunchFixture(t)
+ if _, err := f.root.Serve(t.Context(), f.peer, f.req); err != nil {
+ t.Fatal(err)
+ }
+ r := readGenerationLaunch(t, disk)
+ if !strings.HasPrefix(mode, "running") && mode != "closed-unsettled" {
+ if err := f.root.stop(t.Context(), f.peer, *f.req.Reservation); err != nil {
+ t.Fatal(err)
+ }
+ }
+ if !strings.HasPrefix(mode, "running") && mode != "settled-open-writers" {
+ if err := f.journal.CloseWriters(t.Context(), f.target); err != nil {
+ t.Fatal(err)
+ }
+ }
+ if mode == "wrong-controller" || mode == "running-wrong-controller" {
+ r.Binding.ControllerPID++
+ }
+ if mode == "wrong-inputs" || mode == "running-wrong-inputs" {
+ r.Binding.Inputs = "other"
+ }
+ if mode == "running-wrong-process" {
+ r.Process.PID++
+ }
+ calls := 0
+ v := generationRetirement{guard: f.root.Guard, journal: f.journal, kernel: func(context.Context, release.GenerationReservation) (string, error) {
+ calls++
+ if mode == "kernel" {
+ return "", os.ErrPermission
+ }
+ return r.Binding.Digest, nil
+ }}
+ err := v.retire(t.Context(), &release.RootGenerations{Store: disk}, r)
+ got := readGenerationLaunch(t, disk)
+ switch mode {
+ case "running", "settled-open-writers", "closed-unsettled":
+ if err != nil || calls != 0 || got != r {
+ t.Fatalf("active pin changed or blocked the pass: %v calls=%d got=%+v", err, calls, got)
+ }
+ case "closed":
+ if err != nil || calls != 1 || got.Retirement == "" {
+ t.Fatalf("closed pin not retired: %v calls=%d got=%+v", err, calls, got)
+ }
+ default:
+ if err == nil || got.Retirement != "" {
+ t.Fatalf("uncertain pin released: %v %+v", err, got)
+ }
+ }
+ })
+ }
+}
+
func TestPlan14GenerationRetirementCensusGate(t *testing.T) {
for _, flag := range []string{"", "1"} {
for _, mode := range []string{"legacy", "census", "unreadable"} {
@@ -155,3 +212,86 @@ func TestPlan14GenerationRetirementCensusGate(t *testing.T) {
}
}
}
+
+func TestPlan14GenerationRetirementContinuesPastActivePin(t *testing.T) {
+ f, disk, _ := generationLaunchFixture(t)
+ if _, err := f.root.Serve(t.Context(), f.peer, f.req); err != nil {
+ t.Fatal(err)
+ }
+ running := readGenerationLaunch(t, disk)
+ target := f.target
+ target.ControllerID = "closed-controller"
+ prior, err := f.journal.Read(t.Context(), f.target)
+ if err != nil {
+ t.Fatal(err)
+ }
+ prior.Target, prior.Reservations = target, nil
+ prior.Files, prior.ProducerIDs, prior.NoState = nil, nil, true
+ if err := f.journal.Prelaunch(t.Context(), prior); err != nil {
+ t.Fatal(err)
+ }
+ b := running.Binding
+ b.Target = generationOwner(target)
+ b.Attempt, b.Invocation = "second:main", "second-invocation"
+ g := &release.RootGenerations{Store: disk}
+ second, err := g.Reserve(b)
+ if err != nil {
+ t.Fatal(err)
+ }
+ unit := *f.req.Reservation
+ unit.Target, unit.ID, unit.InvocationID = target, b.Attempt, b.Invocation
+ if err := f.journal.IntendUnit(t.Context(), unit); err != nil {
+ t.Fatal(err)
+ }
+ if err := f.journal.SettleUnit(t.Context(), target, unit.ID); err != nil {
+ t.Fatal(err)
+ }
+ if err := f.journal.CloseWriters(t.Context(), target); err != nil {
+ t.Fatal(err)
+ }
+ calls := 0
+ v := generationRetirement{guard: f.root.Guard, journal: f.journal, kernel: func(_ context.Context, r release.GenerationReservation) (string, error) {
+ calls++
+ if r.Binding != second.Binding {
+ t.Fatal("active reservation reached kernel retirement")
+ }
+ return r.Binding.Digest, nil
+ }}
+ if err := v.retireAll(t.Context(), g, []release.GenerationReservation{running, second}); err != nil {
+ t.Fatal(err)
+ }
+ got, err := g.Read(running.Binding)
+ if err != nil || got != running {
+ t.Fatalf("active pin changed: %+v %v", got, err)
+ }
+ got, err = g.Read(second.Binding)
+ if err != nil || calls != 1 || got.Retirement == "" {
+ t.Fatalf("later closed pin not retired: %+v %v calls=%d", got, err, calls)
+ }
+ launch, err := f.journal.Read(t.Context(), f.target)
+ if err != nil || launch.WritersClosed || launch.Reservations[0].State != hostbox.UnitStarted {
+ t.Fatalf("active launch changed: %+v %v", launch, err)
+ }
+}
+
+func TestPlan14GenerationRetirementKeepsPrelaunchPin(t *testing.T) {
+ f, disk, _ := generationLaunchFixture(t)
+ permit, err := f.root.generations.reserve(t.Context(), f.root, f.peer, f.req)
+ if err != nil {
+ t.Fatal(err)
+ }
+ r := permit.reservation
+ v := generationRetirement{guard: f.root.Guard, journal: f.journal, kernel: func(context.Context, release.GenerationReservation) (string, error) {
+ t.Fatal("prelaunch reservation reached kernel retirement")
+ return "", os.ErrPermission
+ }}
+ if err := v.retire(t.Context(), &release.RootGenerations{Store: disk}, r); err != nil {
+ t.Fatal(err)
+ }
+ if got := readGenerationLaunch(t, disk); got != r {
+ t.Fatalf("prelaunch pin changed: %+v", got)
+ }
+ if err := v.closed(t.Context(), r); err == nil {
+ t.Fatal("prelaunch mistaken for closed authority")
+ }
+}
diff --git a/internal/host/plan14_generation_retirement_root_linux_test.go b/internal/host/plan14_generation_retirement_root_linux_test.go
new file mode 100644
index 000000000..f578133f4
--- /dev/null
+++ b/internal/host/plan14_generation_retirement_root_linux_test.go
@@ -0,0 +1,112 @@
+//go:build linux && rootrunner
+
+package host
+
+import (
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strconv"
+ "testing"
+
+ "github.com/carlosframework/platform/internal/release"
+ "github.com/carlosframework/platform/internal/rootstate"
+)
+
+func TestPlan14GenerationKernelRetirementRootRunner(t *testing.T) {
+ dir := os.Getenv("PLAN14_E25_RETIRE_KERNEL_CHILD")
+ if dir == "" {
+ dir = generationRootRunnerDir(t)
+ cmd := exec.CommandContext(t.Context(), "unshare", "--mount", "--pid", "--fork", "--mount-proc", os.Args[0], "-test.run=^TestPlan14GenerationKernelRetirementRootRunner$", "-test.count=1")
+ cmd.Dir = dir
+ cmd.Env = append(os.Environ(), "PLAN14_E25_RETIRE_KERNEL_CHILD="+dir)
+ if out, err := cmd.CombinedOutput(); err != nil {
+ t.Fatalf("isolated root retirement: %v %s", err, out)
+ }
+ return
+ }
+ t.Setenv("CARLOS_OPT_DIR", dir)
+ path := release.VersionPath(dir, "bab", "v1", "shop")
+ if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.WriteFile(path, []byte("retained generation"), 0644); err != nil {
+ t.Fatal(err)
+ }
+ identity := func(path string) rootstate.FileIdentity {
+ t.Helper()
+ file, err := os.Open(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer file.Close()
+ id, err := rootstate.Identity(file)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return id
+ }
+ k := generationKernel{proc: "/proc"}
+ boot, err := k.boot()
+ if err != nil {
+ t.Fatal(err)
+ }
+ controller := exec.CommandContext(t.Context(), "/bin/sleep", "60")
+ controller.Dir = dir
+ if err := controller.Start(); err != nil {
+ t.Fatal(err)
+ }
+ joined := false
+ t.Cleanup(func() {
+ if !joined {
+ _ = controller.Process.Kill()
+ _ = controller.Wait()
+ }
+ })
+ raw, err := generationRead(filepath.Join("/proc", strconv.Itoa(controller.Process.Pid), "stat"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ start, err := generationStart(raw)
+ if err != nil {
+ t.Fatal(err)
+ }
+ r := release.GenerationReservation{Binding: release.GenerationBinding{
+ Binding: release.HistoryBinding{Host: filepath.Base(dir) + ".example", Account: "bab", App: "shop"},
+ Target: release.GenerationOwner{BootID: boot}, Version: "v1", Path: path, Tree: filepath.Dir(path),
+ Executable: identity(path), TreeIdentity: identity(filepath.Dir(path)),
+ ControllerPID: controller.Process.Pid, ControllerStart: start,
+ }}
+ if _, err := productionGenerationAbsence(t.Context(), r); err == nil {
+ t.Fatal("live controller released")
+ }
+ if err := controller.Process.Kill(); err != nil {
+ t.Fatal(err)
+ }
+ _ = controller.Wait()
+ joined = true
+ if digest, err := productionGenerationAbsence(t.Context(), r); err != nil || len(digest) != 64 {
+ t.Fatalf("joined controller: %s %v", digest, err)
+ }
+ file, err := os.Open(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := productionGenerationAbsence(t.Context(), r); err == nil {
+ file.Close()
+ t.Fatal("controller join erased an open reference")
+ }
+ file.Close()
+ if err := os.Rename(filepath.Dir(path), filepath.Dir(path)+".old"); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.Mkdir(filepath.Dir(path), 0755); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.WriteFile(path, []byte("replacement"), 0644); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := productionGenerationAbsence(t.Context(), r); err == nil {
+ t.Fatal("replaced staged tree discharged the original identity")
+ }
+}
diff --git a/internal/host/rootgeneration_kernel_linux.go b/internal/host/rootgeneration_kernel_linux.go
index dc99ca0a6..524c4b140 100644
--- a/internal/host/rootgeneration_kernel_linux.go
+++ b/internal/host/rootgeneration_kernel_linux.go
@@ -305,6 +305,33 @@ func generationRootTask(raw []byte) (bool, error) {
}
return kernel, nil
}
+
+func generationTaskFDs(ctx context.Context, path string, b release.GenerationBinding, refs map[rootstate.FileIdentity]bool) error {
+ f, err := os.Open(path)
+ if err != nil {
+ return err
+ }
+ // Our own descriptor appears when scanning this process. Keep it open
+ // until inspection finishes, so it cannot manufacture a missing entry.
+ defer f.Close()
+ names, err := f.Readdirnames(8193)
+ if err != nil && err != io.EOF {
+ return err
+ }
+ if len(names) > 8192 {
+ return os.ErrPermission
+ }
+ for _, name := range names {
+ if err := ctx.Err(); err != nil {
+ return err
+ }
+ if err := generationLinkRef(filepath.Join(path, name), b, refs); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
func (k generationKernel) task(ctx context.Context, path string, b release.GenerationBinding, ns map[string]string, refs map[rootstate.FileIdentity]bool) (string, error) {
if err := ctx.Err(); err != nil {
return "", err
@@ -340,18 +367,9 @@ func (k generationKernel) task(ctx context.Context, path string, b release.Gener
return "", err
}
}
- fds, err := generationNames(path + "/fd")
- if err != nil {
+ if err := generationTaskFDs(ctx, path+"/fd", b, refs); err != nil {
return "", err
}
- for _, fd := range fds {
- if err := ctx.Err(); err != nil {
- return "", err
- }
- if err := generationLinkRef(path+"/fd/"+fd, b, refs); err != nil {
- return "", err
- }
- }
maps, err := generationRead(path + "/maps")
if err != nil {
return "", err
diff --git a/internal/host/rootgeneration_retirement.go b/internal/host/rootgeneration_retirement.go
index fe09845f9..2868c794e 100644
--- a/internal/host/rootgeneration_retirement.go
+++ b/internal/host/rootgeneration_retirement.go
@@ -17,56 +17,74 @@ type generationRetirement struct {
kernel func(context.Context, release.GenerationReservation) (string, error)
}
-// closed verifies root's existing irreversible closure. It never stops a unit,
-// closes another controller's admission, or interprets a missing journal as join.
-func (v generationRetirement) closed(ctx context.Context, r release.GenerationReservation) error {
+func (v generationRetirement) closure(ctx context.Context, r release.GenerationReservation) (bool, error) {
if v.guard.Root == nil || v.journal == nil || v.journal.Remote() || v.kernel == nil {
- return os.ErrPermission
+ return false, os.ErrPermission
}
b := r.Binding
t := b.Target
in, err := v.guard.Root.Installation(ctx, t.Host, hostguard.Captured{Incarnation: t.Incarnation, Stamp: t.Stamp})
if err != nil {
- return err
+ return false, err
}
// Installation admits an older capture only when root retained it as
// retired. Rebind preserves this identity's account, enrollment and migration.
// Its old launch journal and kernel obligations still have to settle below.
if in.Incarnation != t.Incarnation || in.Stamp < t.Stamp {
- return os.ErrPermission
+ return false, os.ErrPermission
}
in.Stamp = t.Stamp
if in.Host != b.Binding.Host || in.Account != b.Binding.Account || in.App != b.Binding.App || in.Box != t.Box || in.Enrollment != t.Enrollment || in.InventoryRevision == 0 || in.Record != b.Record || rootHistoryInstallation(in) != b.Installation {
- return os.ErrPermission
+ return false, os.ErrPermission
}
- launches, err := v.journal.HostLaunches(ctx, t.Host)
+ launches, err := v.journal.GenerationLaunches(ctx)
if err != nil {
- return err
+ return false, err
}
- matches := 0
+ matches, controllers := 0, 0
+ closed := false
for _, launch := range launches {
target := launch.Target
if target.Host != t.Host || target.Incarnation != t.Incarnation || target.Stamp != t.Stamp || target.Session != t.Session || target.Box != t.Box || target.Enrollment != t.Enrollment || target.BootID != t.BootID || target.ControllerID != t.ControllerID {
continue
}
- if !launch.WritersClosed || launch.ControllerPID != b.ControllerPID || launch.ProcessStart != b.ControllerStart {
- return os.ErrPermission
+ controllers++
+ if launch.ControllerPID != b.ControllerPID || launch.ProcessStart != b.ControllerStart {
+ return false, os.ErrPermission
}
for _, unit := range launch.Reservations {
if unit.ID != b.Attempt {
continue
}
- if !hostbox.IsUnitReservation(unit) || unit.State != hostbox.UnitSettled || !hostbox.SameLaunchOwner(unit.Target, target) || unit.Inputs != b.Inputs || unit.InvocationID != b.Invocation || unit.Executable.Device != b.Executable.Device || unit.Executable.Inode != b.Executable.Inode || unit.Group.RelativePath != instancectl.ClaimUnitControlGroup(t.Host) {
- return os.ErrPermission
+ if !hostbox.IsUnitReservation(unit) || (unit.State != hostbox.UnitIntended && unit.State != hostbox.UnitStarted && unit.State != hostbox.UnitSettled) || !hostbox.SameLaunchOwner(unit.Target, target) || unit.Inputs != b.Inputs || unit.InvocationID != b.Invocation || unit.Executable.Device != b.Executable.Device || unit.Executable.Inode != b.Executable.Inode || unit.Group.RelativePath != instancectl.ClaimUnitControlGroup(t.Host) {
+ return false, os.ErrPermission
}
p := r.Process
if p != (release.GenerationProcess{}) && (unit.PID != p.PID || unit.ProcessStart != p.Start || unit.Group.RelativePath != p.Group || unit.Group.InvocationID != p.Invocation || unit.Group.Device != p.GroupIdentity.Device || unit.Group.Inode != p.GroupIdentity.Inode) {
- return os.ErrPermission
+ return false, os.ErrPermission
}
matches++
+ closed = launch.WritersClosed && unit.State == hostbox.UnitSettled
}
}
- if matches != 1 {
+ // Reserve precedes IntendUnit. Keep that exact prelaunch obligation pinned.
+ if controllers == 1 && matches == 0 && !r.Closed && r.Process == (release.GenerationProcess{}) {
+ return false, nil
+ }
+ if controllers != 1 || matches != 1 || (r.Closed && !closed) {
+ return false, os.ErrPermission
+ }
+ return closed, nil
+}
+
+// closed verifies root's existing irreversible closure. It never stops a unit,
+// closes another controller's admission, or interprets a missing journal as join.
+func (v generationRetirement) closed(ctx context.Context, r release.GenerationReservation) error {
+ closed, err := v.closure(ctx, r)
+ if err != nil {
+ return err
+ }
+ if !closed {
return os.ErrPermission
}
return nil
@@ -88,7 +106,13 @@ func (v generationRetirement) verify(ctx context.Context, r release.GenerationRe
// The caller holds release exclusion; host exclusion remains held through both
// ledger commits and the kernel check, in the same order used by launch.
func (v generationRetirement) retire(ctx context.Context, g *release.RootGenerations, r release.GenerationReservation) error {
- return v.guard.WithLocal(ctx, r.Binding.Binding.Host, func() error { return g.Retire(ctx, r, v.closed, v.verify) })
+ return v.guard.WithLocal(ctx, r.Binding.Binding.Host, func() error {
+ closed, err := v.closure(ctx, r)
+ if err != nil || !closed {
+ return err
+ }
+ return g.Retire(ctx, r, v.closed, v.verify)
+ })
}
func productionRetireGenerations(ctx context.Context, g *release.RootGenerations) error {
@@ -105,22 +129,21 @@ func productionRetireGenerations(ctx context.Context, g *release.RootGenerations
return err
}
defer state.Close()
- file, err := rootstate.OpenTrustedFile("/var/lib/carlos-claims/journals.db")
- if err != nil {
- return err
- }
- file.Close()
- journal, err := hostbox.OpenLaunchJournal("/var/lib/carlos-claims/journals.db")
+ journal, err := openGenerationLaunchInventory("/var/lib/carlos-claims/journals.db")
if err != nil {
return err
}
defer journal.Close()
verifier := generationRetirement{guard: hostguard.Guard{DataDir: dataDir(), Root: hostguard.NewRootAuthority(state)}, journal: journal, kernel: productionGenerationAbsence}
+ return verifier.retireAll(ctx, g, records)
+}
+
+func (v generationRetirement) retireAll(ctx context.Context, g *release.RootGenerations, records []release.GenerationReservation) error {
for _, r := range records {
if r.Retirement != "" {
continue
}
- if err := verifier.retire(ctx, g, r); err != nil {
+ if err := v.retire(ctx, g, r); err != nil {
return err
}
}
diff --git a/internal/provision/testdata/plan14-root-reads-e.tsv b/internal/provision/testdata/plan14-root-reads-e.tsv
index acb6645b3..5e004d0c8 100644
--- a/internal/provision/testdata/plan14-root-reads-e.tsv
+++ b/internal/provision/testdata/plan14-root-reads-e.tsv
@@ -54,7 +54,7 @@ internal/release/roothistory_replace.go replacementReconstruction authority E1 S
internal/release/signedhead.go StageSignedHead authority E1 Canonical route is supplied by the root unit verifier. Verifies signed channel, digest ratchet and manifest/artifact before staging; unavailable pin census never authorizes restaging differing bytes. Fixtures: TestPlan14Incident20261006WithLedger and TestPlan14SignedNamedRecoveryAuthority. registry:Account@202,registry:Account@302,registry:Account@403,registry:App@214,registry:App@415,registry:Channel@222,registry:Host@434 ca02f04e55556ba0233dfa0848b0c533a9d9aa06cad6abd33cfd2968b3bd838a
internal/rootstate/link_linux.go ReadTrustedLink authority A,E1 Checks the full root-owned parent chain before descriptor-based live-link reading. Fixture: TestPlan14TrustedLiveLink; privileged acceptance remains required. file:Open@156 90f8e8de4e2c71f07b5d749cbe4bb1c1dfa426576a82138823449f56fc9ec5ab
internal/rootstate/link_linux.go directory.readLink authority A,E1 Pins the symlink inode with O_PATH and checks owner/type/link count before readlinkat. Fixture: TestPlan14TrustedLiveLink. file:Fstat@356,file:Openat@183,file:Readlinkat@554 a8559fe63dce48830916004ecfe7fbbe5d419568c7daf7a3073cf1b5bb12c3d7
-@coverage internal/host 7a0f83f08620422a955ded1e353b5dd4e77aeaf0f07580c7bff85d7393923de2
+@coverage internal/host a164d7f7e44edd30db1657361141593e267d4982d1d0cbca63677722429c5e0d
@coverage internal/release 2bb546a86c1ab49b02e424eb0b366b46776857a1249baa2d6fb61ead4209d6bd
@coverage internal/rootstate 6338f74a6b2059cf77cf478bd6111b37b441eb9d65a309464c378359d4bdac05
internal/release/roothistory_status.go RootHistory.MigrationStatus authority E1 Exact completion binds the accepted authorization digest from durable private history to enrolled-root signed readback. Pending originals, changed evidence and canonical sources cannot supply retirement completion. Fixtures: TestPlan14HistoryReconstructionCompletion, TestPlan14HistoryRetirementCompletion, TestPlan14ReadReconstructionCompletion, TestPlan14ReadRetirementCompletion, TestPlan14RecoveryRetirementProductionCallback, TestPlan14RootHistoryReconstructionCrash, TestPlan14HistoryRetirementProcessCrash. file:Read@1062,file:Read@3891 f0ab7ad2d2d70f8c22ebfd575f5a1c6931f7eba30ccdba80ad43d0efffc88154
@@ -233,7 +233,7 @@ internal/host/rootgeneration_kernel_linux.go generationIDRef authority E2 E21: b
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 18269bf4b64e05138dff05e7894d4925bc24bcc012fa64a336723d981a9c52dd
internal/host/rootgeneration_kernel_linux.go generationKernel.boot 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. c3cc84ed2af21ee0e57e91160561a3e4996e75990df2617a14865b084b176ced
internal/host/rootgeneration_kernel_linux.go generationKernel.controllerGone 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. fa19ddebc7bf12297313e1431e4d8e534949032ff495b37be0038252bad355d3
-internal/host/rootgeneration_kernel_linux.go generationKernel.task 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@607 d5d6f2f6b04c52914095e0060bee2fe715632493909e944eeecd90aac0fa5a36
+internal/host/rootgeneration_kernel_linux.go generationKernel.task authority E2 E25: keep the descriptor-listing handle open through inspection, including when scanning the current process. Check every listed descriptor against paths and artifact identities. Fixtures: TestPlan14GenerationKernelOwnDescriptors and TestPlan14GenerationKernelReferences. Root-runner acceptance pending: TestPlan14GenerationKernelCensusRootRunner and TestPlan14GenerationKernelRetirementRootRunner. file:Readlink@607 f965b29343f3e5e5e464eaf1f4929f9ac4e5ff3c76b0e87750dad64839caece8
internal/host/rootgeneration_kernel_linux.go generationLinkRef 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@128,file:Stat@423 1b9492ac2f9066f0ab6de429fafd707a7c826db76f3ac7b244d924385130ef5c
internal/host/rootgeneration_kernel_linux.go generationMajor 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. 48d037aef3349cd11492bb1cf7e9608f05bfde15e004f67f3bcad62a0518ab82
internal/host/rootgeneration_kernel_linux.go generationMaps 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. 7549bed808c1431fb382c48cddde8d1730713aba73b59467ad0677901d72becc
@@ -251,10 +251,10 @@ internal/host/rootgeneration_kernel_linux.go productionGenerationAbsence authori
internal/host/rootgeneration_kernel_linux.go type generationKernel 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. 5a8673c8cf4c7b2e660688c4cd0a1043271159cebb11835da2b00bdfef798f69
internal/host/rootgeneration_kernel_linux.go type generationMount 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. b4cd115a225b26a7906357404f8d04692a8d1b062662f80672280972049a8acb
internal/host/rootgeneration_kernel_other.go productionGenerationAbsence authority E2 E21: non-Linux production generation retirement refuses. Darwin, Windows and Linux ARM64 compile gates; no non-Linux retirement authority. de47d997019075326e1d5513d7576390d085566fd69edc35efd00f067fb1db24
-internal/host/rootgeneration_retirement.go generationRetirement.closed authority E2 E23: the root installation reader admits an old capture only from its retained retired set; restore that same-incarnation stamp solely to check the original installation identity. Require exact closed launch and independent kernel absence afterward. Fixtures: TestPlan14GenerationRetirementRetainedRebind, TestPlan14GenerationRetirementRootBinding. Running-tenant kernel acceptance remains pending. f871fd29171851a2300b68c947124d5d014ed97cdef45eef496645411df76107
-internal/host/rootgeneration_retirement.go generationRetirement.retire authority E2 E21: release then host exclusion; root installation and exact settled launch with irreversibly closed writers bind retirement before and after independent kernel verification. Production invokes it from the census-only pin source. Fixtures: TestPlan14GenerationRetirementRootBinding, TestPlan14GenerationRetirementAfterLostStarted, TestPlan14GenerationRetirementBindingProcessCrash, TestPlan14GenerationRetirementCensusGate. Privileged production opener acceptance remains pending. e4caafad8e42625aa21927f447f5f50a82f0890778635b74c3498c1d86efe1e2
+internal/host/rootgeneration_retirement.go generationRetirement.closed authority E2 E25: validate installation, controller, exact unit and process before retaining an active or prelaunch pin. Only closed writers plus a settled unit reach durable retirement and independent kernel absence. Use the checked read-only journal and continue past active pins. Fixtures: TestPlan14GenerationRetirementKeepsActivePin, TestPlan14GenerationRetirementKeepsPrelaunchPin, TestPlan14GenerationRetirementContinuesPastActivePin, TestPlan14GenerationRetirementBindingProcessCrash, TestPlan14GenerationRetirementCensusGate. Running-tenant completeness remains unfinished. e69c69a74695791f8432e24a33397e49c22c47288e1bba66e56f11e605bf9881
+internal/host/rootgeneration_retirement.go generationRetirement.retire authority E2 E25: validate installation, controller, exact unit and process before retaining an active or prelaunch pin. Only closed writers plus a settled unit reach durable retirement and independent kernel absence. Use the checked read-only journal and continue past active pins. Fixtures: TestPlan14GenerationRetirementKeepsActivePin, TestPlan14GenerationRetirementKeepsPrelaunchPin, TestPlan14GenerationRetirementContinuesPastActivePin, TestPlan14GenerationRetirementBindingProcessCrash, TestPlan14GenerationRetirementCensusGate. Running-tenant completeness remains unfinished. d11a2871718fdd7bcbcca568ab0b0717496e92176d3bfd296007914bf6d08c99
internal/host/rootgeneration_retirement.go generationRetirement.verify authority E2 E21: release then host exclusion; root installation and exact settled launch with irreversibly closed writers bind retirement before and after independent kernel verification. Production invokes it from the census-only pin source. Fixtures: TestPlan14GenerationRetirementRootBinding, TestPlan14GenerationRetirementAfterLostStarted, TestPlan14GenerationRetirementBindingProcessCrash, TestPlan14GenerationRetirementCensusGate. Privileged production opener acceptance remains pending. 7bf7540ad8bac41c5ea23d7bbf2f09c870b9bed78cf912fb9645c1a621289f70
-internal/host/rootgeneration_retirement.go productionRetireGenerations authority E2 E21: release then host exclusion; root installation and exact settled launch with irreversibly closed writers bind retirement before and after independent kernel verification. Production invokes it from the census-only pin source. Fixtures: TestPlan14GenerationRetirementRootBinding, TestPlan14GenerationRetirementAfterLostStarted, TestPlan14GenerationRetirementBindingProcessCrash, TestPlan14GenerationRetirementCensusGate. Privileged production opener acceptance remains pending. file:Open@292 fbe2431f88b9f2bd904f046855b63f619d20d5bdde6bcb0dc6d962388046a3c7
+internal/host/rootgeneration_retirement.go productionRetireGenerations authority E2 E25: validate installation, controller, exact unit and process before retaining an active or prelaunch pin. Only closed writers plus a settled unit reach durable retirement and independent kernel absence. Use the checked read-only journal and continue past active pins. Fixtures: TestPlan14GenerationRetirementKeepsActivePin, TestPlan14GenerationRetirementKeepsPrelaunchPin, TestPlan14GenerationRetirementContinuesPastActivePin, TestPlan14GenerationRetirementBindingProcessCrash, TestPlan14GenerationRetirementCensusGate. Running-tenant completeness remains unfinished. file:Open@292 0c2baaf1832ab91e36c93a70468428f3e2584c6bc69579ad55f11728477e4385
internal/host/rootgeneration_retirement.go type generationRetirement authority E2 E21: release then host exclusion; root installation and exact settled launch with irreversibly closed writers bind retirement before and after independent kernel verification. Production invokes it from the census-only pin source. Fixtures: TestPlan14GenerationRetirementRootBinding, TestPlan14GenerationRetirementAfterLostStarted, TestPlan14GenerationRetirementBindingProcessCrash, TestPlan14GenerationRetirementCensusGate. Privileged production opener acceptance remains pending. 942978edbce75390dd3fcbfee5ec8b1d86102053f99da7915b7afdd89a503b8e
internal/release/rootgenerationpins.go RootGenerations.Reservations authority E2 E23: versioned generation completeness binds exact reservations to independently supplied installation, launch and kernel evidence. Reservation mutations invalidate before commit; every destructive read reconciles again and retries sync. Production reconciliation remains unavailable. Fixtures: TestPlan14GenerationCompleteness, TestPlan14GenerationCompletenessInvalidation, TestPlan14GenerationCompletenessDurability, TestPlan14GenerationCompletenessCertificateSyncFailure, TestPlan14GenerationCompletenessPinConsumer, TestPlan14GenerationCompletenessProcessCrash; TestPlan14GenerationCompletenessCheckedRootStore is runner-pending. file:Read@541,file:Read@890 87f58596d5807a0e9eaed6dbd0cf5ba4e21a0906fba5959a2d974a68e9365d8c
internal/host/movesheldrelease_production.go WithProductionMovesHeldRelease authority B2,C,E1,E2 Root held-release constructor requires root guard and private release store, authenticates peer and reads root health history. E22: root generation pins guard existing-tree artifact and manifest writes in adoption and moves-held staging/publication. Held snapshot is read under release exclusion before the leaf held-stage lock; failures remain restrictive for writes. Claims-off source returns no root pins. Fresh wholly absent version trees retain signed staging. Fixtures: TestPlan14GenerationSiblingWrites, TestPlan14GenerationVerifiedSiblingWrites, TestPlan14GenerationHeldStage (production source assembly, both flag values without census, lock order, pin/error/fresh cases). Production completeness remains unavailable; no new completeness grant. file:Open@1365,file:Read@1612 9981c473733dd5214ab5baf79fa4402e8112ec3ca9095d1ff742fcde10119d5f
@@ -307,3 +307,6 @@ internal/host/rootgeneration_inventory.go productionGenerationInventory authorit
internal/host/rootgeneration_inventory.go type generationInventory authority E2 E24: independent root installation and launch enumeration, exact reservation reconciliation and fresh quiescent whole-version-tree kernel census; missing sources, open unreserved launches, foreign namespaces and uncontained tasks refuse. Production pin source certifies this evidence before returning pins. Fixtures: TestPlan14GenerationInventoryCompleteness, TestPlan14GenerationInventoryPinConsumer, TestPlan14GenerationInventoryLifecycle, TestPlan14GenerationInventoryProcessCrash, TestPlan14GenerationKernelCensusUnknownReference, TestPlan14GenerationLaunchInventory, TestPlan14GenerationReaderDoesNotInitialize, TestPlan14GenerationInstallationInventory. Checked production source/kernel tests are runner-pending. 398337e54b33efb37b21b181520f1bf9bcce48c34a0d66430f2e58505ae7bf00
internal/host/rootgeneration_inventory.go type generationReconciler authority E2 E24: independent root installation and launch enumeration, exact reservation reconciliation and fresh quiescent whole-version-tree kernel census; missing sources, open unreserved launches, foreign namespaces and uncontained tasks refuse. Production pin source certifies this evidence before returning pins. Fixtures: TestPlan14GenerationInventoryCompleteness, TestPlan14GenerationInventoryPinConsumer, TestPlan14GenerationInventoryLifecycle, TestPlan14GenerationInventoryProcessCrash, TestPlan14GenerationKernelCensusUnknownReference, TestPlan14GenerationLaunchInventory, TestPlan14GenerationReaderDoesNotInitialize, TestPlan14GenerationInstallationInventory. Checked production source/kernel tests are runner-pending. a979cdff1cd6311cf57d4a93e4501fb8dc2f65772d0013e622175b2a1f610d91
internal/hostguard/generationinventory.go RootAuthority.GenerationInstallations authority E2 E24: independent root installation and launch enumeration, exact reservation reconciliation and fresh quiescent whole-version-tree kernel census; missing sources, open unreserved launches, foreign namespaces and uncontained tasks refuse. Production pin source certifies this evidence before returning pins. Fixtures: TestPlan14GenerationInventoryCompleteness, TestPlan14GenerationInventoryPinConsumer, TestPlan14GenerationInventoryLifecycle, TestPlan14GenerationInventoryProcessCrash, TestPlan14GenerationKernelCensusUnknownReference, TestPlan14GenerationLaunchInventory, TestPlan14GenerationReaderDoesNotInitialize, TestPlan14GenerationInstallationInventory. Checked production source/kernel tests are runner-pending. 593768e6ba937cf2cfa2635c8f796f767d6c2354737a72073cc351f514faed20
+internal/host/rootgeneration_retirement.go generationRetirement.closure authority E2 E25: validate installation, controller, exact unit and process before retaining an active or prelaunch pin. Only closed writers plus a settled unit reach durable retirement and independent kernel absence. Use the checked read-only journal and continue past active pins. Fixtures: TestPlan14GenerationRetirementKeepsActivePin, TestPlan14GenerationRetirementKeepsPrelaunchPin, TestPlan14GenerationRetirementContinuesPastActivePin, TestPlan14GenerationRetirementBindingProcessCrash, TestPlan14GenerationRetirementCensusGate. Running-tenant completeness remains unfinished. f698ed949535287a55d5a8fc103f7dc5bf0f2c5020e27ff96552f42852024d03
+internal/host/rootgeneration_retirement.go generationRetirement.retireAll authority E2 E25: validate installation, controller, exact unit and process before retaining an active or prelaunch pin. Only closed writers plus a settled unit reach durable retirement and independent kernel absence. Use the checked read-only journal and continue past active pins. Fixtures: TestPlan14GenerationRetirementKeepsActivePin, TestPlan14GenerationRetirementKeepsPrelaunchPin, TestPlan14GenerationRetirementContinuesPastActivePin, TestPlan14GenerationRetirementBindingProcessCrash, TestPlan14GenerationRetirementCensusGate. Running-tenant completeness remains unfinished. c2ef0459f6836cceddd5dab65127f10323ffc797fe738d2c5f6040fadb1a6465
+internal/host/rootgeneration_kernel_linux.go generationTaskFDs authority E2 E25: keep the descriptor-listing handle open through inspection, including when scanning the current process. Check every listed descriptor against paths and artifact identities. Fixtures: TestPlan14GenerationKernelOwnDescriptors and TestPlan14GenerationKernelReferences. Root-runner acceptance pending: TestPlan14GenerationKernelCensusRootRunner and TestPlan14GenerationKernelRetirementRootRunner. file:Open@143,file:Readdirnames@367 06417eff13beac0b675b55f2664183f938afee33a733d51bb571c9a0ad3d50dc
diff --git a/internal/release/plan14_generation_completeness_root_test.go b/internal/release/plan14_generation_completeness_root_test.go
index 65b0e230c..2a7816f85 100644
--- a/internal/release/plan14_generation_completeness_root_test.go
+++ b/internal/release/plan14_generation_completeness_root_test.go
@@ -1,3 +1,5 @@
+//go:build linux && rootrunner
+
package release
import (
@@ -10,20 +12,10 @@ import (
)
func TestPlan14GenerationCompletenessCheckedRootStore(t *testing.T) {
- parent := os.Getenv("PLAN14_GENERATION_ROOT_RUNNER")
- if parent == "" {
- t.Skip("requires a designated disposable root runner and its private scratch parent")
- }
if os.Geteuid() != 0 {
t.Fatal("designated runner must execute as root")
}
- // The runner supplies a root-owned private parent, outside production state.
- check, err := rootstate.Open(parent, false, false)
- if err != nil {
- t.Fatal(err)
- }
- check.Close()
- dir, err := os.MkdirTemp(parent, "generation-certificate-")
+ dir, err := os.MkdirTemp("/var/lib", "plan14-generation-certificate-")
if err != nil {
t.Fatal(err)
}