carlos / platform Public

WIP files seed checkpoint 30 (not for merge)

FS-R7 EBS/NVMe volume identity before destructive cross-device rebinding (snapshot-clone negative control); FS-R8 per-boot device comparison with later-retirement linking; FS-R9 no-census shutdown as main; FS-R10 census; fixture space.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev fb125958ef7fea0800ce400f51ae6aaadabeefdf parent 54bafd8
24 files changed, +723 −61
  • internal/host/agent.go +14 −18
  • internal/host/agent_test.go +5 −0
  • internal/host/claimfilesseed_isolated_rootrunner_linux_test.go +3 −0
  • internal/host/claimfilesseed_restore_linux_test.go +2 −0
  • internal/host/claimfilesseed_shutdown.go +35 −0
  • internal/host/claimfilesseed_shutdown_test.go +25 −0
  • internal/host/claimservingidentity.go +6 −0
  • internal/host/claimservingpark_linux.go +24 −1
  • internal/host/claimservingpark_linux_test.go +1 −0
  • internal/host/claimservingpark_records.go +7 −5
  • internal/host/claimservingpark_rootrunner_linux_test.go +26 −6
  • internal/host/claimservingreboot_binding.go +1 −0
  • internal/host/claimservingreboot_commit_linux.go +10 −0
  • internal/host/claimservingreboot_linux.go +10 −1
  • internal/host/claimservingreboot_prepare_linux_test.go +1 −0
  • internal/host/claimservingrenumber_linux_test.go +112 −0
  • internal/host/claimservingtopology_linux.go +7 −2
  • internal/host/claimservingvolume_linux.go +66 −0
  • internal/host/claimservingvolume_linux_test.go +120 −0
  • internal/host/claimservingvolume_rootrunner_linux_test.go +180 −0
  • internal/host/startup_largebox_linux_test.go +35 −3
  • internal/provision/plan14_legacy_identity_test.go +6 −3
  • internal/provision/plan14_root_reads_test.go +2 −2
  • internal/provision/testdata/plan14-root-reads-seed.tsv +25 −20

CI passed — 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	282.923s
?   	github.com/carlosframework/platform/cmd/changelog	[no test files]
ok  	github.com/carlosframework/platform/cmd/console	2.834s
ok  	github.com/carlosframework/platform/cmd/moveprobe	92.164s
ok  	github.com/carlosframework/platform/cmd/zddprobe	0.705s
ok  	github.com/carlosframework/platform/internal/activator	249.902s
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.018s
ok  	github.com/carlosframework/platform/internal/appconfig	2.443s
ok  	github.com/carlosframework/platform/internal/apperr	0.071s
ok  	github.com/carlosframework/platform/internal/applog	0.126s
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.486s
?   	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.002s
ok  	github.com/carlosframework/platform/internal/cgroup	0.691s
ok  	github.com/carlosframework/platform/internal/changelog	0.301s
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	385.461s
?   	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.754s
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.002s
ok  	github.com/carlosframework/platform/internal/docsite	0.006s
ok  	github.com/carlosframework/platform/internal/domains	2.326s
ok  	github.com/carlosframework/platform/internal/doorkey	0.007s
ok  	github.com/carlosframework/platform/internal/economics	0.004s
ok  	github.com/carlosframework/platform/internal/edge	26.645s
?   	github.com/carlosframework/platform/internal/edgeproof	[no test files]
ok  	github.com/carlosframework/platform/internal/edgesfile	0.007s
ok  	github.com/carlosframework/platform/internal/fleetapi	0.130s
ok  	github.com/carlosframework/platform/internal/gate	1.655s
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.034s
ok  	github.com/carlosframework/platform/internal/host	157.900s
ok  	github.com/carlosframework/platform/internal/hostactors	0.431s
ok  	github.com/carlosframework/platform/internal/hostbox	2.356s
ok  	github.com/carlosframework/platform/internal/hostclaim	0.530s
ok  	github.com/carlosframework/platform/internal/hostevidence	0.008s
ok  	github.com/carlosframework/platform/internal/hostexec	4.904s
ok  	github.com/carlosframework/platform/internal/hostfence	0.031s
ok  	github.com/carlosframework/platform/internal/hostfiles	10.566s
ok  	github.com/carlosframework/platform/internal/hostfloor	0.015s
ok  	github.com/carlosframework/platform/internal/hostguard	1.007s
ok  	github.com/carlosframework/platform/internal/hostops	1.547s
?   	github.com/carlosframework/platform/internal/hostops/fencerelease	[no test files]
ok  	github.com/carlosframework/platform/internal/hostops/internal/fencerelease	0.005s
ok  	github.com/carlosframework/platform/internal/hostquarantine	0.007s
ok  	github.com/carlosframework/platform/internal/hostrelocate	0.449s
ok  	github.com/carlosframework/platform/internal/hostresolve	0.135s
ok  	github.com/carlosframework/platform/internal/hostwrite	0.003s
ok  	github.com/carlosframework/platform/internal/infraview	0.003s
ok  	github.com/carlosframework/platform/internal/instancectl	4.170s
ok  	github.com/carlosframework/platform/internal/instanceexec	2.725s
ok  	github.com/carlosframework/platform/internal/instancefs	0.005s
ok  	github.com/carlosframework/platform/internal/instancekey	0.005s
ok  	github.com/carlosframework/platform/internal/instanceupdates	0.013s
ok  	github.com/carlosframework/platform/internal/layout	1.495s
ok  	github.com/carlosframework/platform/internal/lease	9.798s
ok  	github.com/carlosframework/platform/internal/lease/leasetest	0.011s
ok  	github.com/carlosframework/platform/internal/ledger	0.014s
ok  	github.com/carlosframework/platform/internal/mail	0.153s
ok  	github.com/carlosframework/platform/internal/maintenance	0.080s
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.008s
ok  	github.com/carlosframework/platform/internal/moves	20.948s
ok  	github.com/carlosframework/platform/internal/moves/archive	0.760s
ok  	github.com/carlosframework/platform/internal/objstore	0.172s
ok  	github.com/carlosframework/platform/internal/ops	0.169s
ok  	github.com/carlosframework/platform/internal/park	0.190s
ok  	github.com/carlosframework/platform/internal/privatefile	0.003s
ok  	github.com/carlosframework/platform/internal/provision	15.681s
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	4.892s
ok  	github.com/carlosframework/platform/internal/relay	7.241s
ok  	github.com/carlosframework/platform/internal/release	19.845s
ok  	github.com/carlosframework/platform/internal/rollout	3.705s
ok  	github.com/carlosframework/platform/internal/rolloutwords	0.003s
ok  	github.com/carlosframework/platform/internal/rootaws	0.007s
ok  	github.com/carlosframework/platform/internal/rootparticipation	0.007s
ok  	github.com/carlosframework/platform/internal/rootstate	0.025s
ok  	github.com/carlosframework/platform/internal/runs	0.580s
ok  	github.com/carlosframework/platform/internal/s3compat	0.005s
ok  	github.com/carlosframework/platform/internal/safefile	0.004s
ok  	github.com/carlosframework/platform/internal/schedule	2.474s
ok  	github.com/carlosframework/platform/internal/seeds	0.012s
ok  	github.com/carlosframework/platform/internal/sim/hostlife	9.429s
ok  	github.com/carlosframework/platform/internal/sim/litestreamfacts	4.397s
ok  	github.com/carlosframework/platform/internal/status	0.041s
ok  	github.com/carlosframework/platform/internal/steering	0.005s
ok  	github.com/carlosframework/platform/internal/stopevidence	0.202s
ok  	github.com/carlosframework/platform/internal/svcrole	0.002s
ok  	github.com/carlosframework/platform/internal/telemetry	0.004s
ok  	github.com/carlosframework/platform/internal/termfence	0.004s
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.004s
ok  	github.com/carlosframework/platform/internal/usage	0.004s
ok  	github.com/carlosframework/platform/internal/version	0.001s
ok  	github.com/carlosframework/platform/internal/vet	1.531s
=== 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
linux/arm64 carlos binary: 46989474 bytes (ceiling 52000000)
--- 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.022s
=== RUN   TestT4StorageProbeMinio
2026/10/15 22:33:12 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18DED4B8F2543B80, 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.020s
minio contract: PASS
diff --git a/internal/host/agent.go b/internal/host/agent.go
index 7caebda1d..d2461402f 100644
--- a/internal/host/agent.go
+++ b/internal/host/agent.go
@@ -666,6 +666,7 @@ func runAgent(args []string, buildClaims ClaimFactory) {
close(started)
}
+ seedEnabled := claims.Writes.Enabled && os.Getenv("CARLOS_HOST_CLAIM_ORIGINS") == "1" && svc != nil
safeClaimClose := true
var shutdownCtx context.Context
var shutdownCancel context.CancelFunc
@@ -678,7 +679,7 @@ func runAgent(args []string, buildClaims ClaimFactory) {
return
}
base := shutdownCtx
- if base == nil {
+ if !seedEnabled || base == nil {
base = context.Background()
}
ctx, cancel := context.WithTimeout(base, 5*time.Second)
@@ -829,14 +830,17 @@ func runAgent(args []string, buildClaims ClaimFactory) {
}
// Seed handlers borrow this agent's resources and finish before they close.
- seedCtx, seedCancel := signal.NotifyContext(claimCtx, syscall.SIGTERM, os.Interrupt)
- defer seedCancel()
- seedCtx, seedFinish := activator.WithCleanupBudget(seedCtx, 30*time.Second)
- defer seedFinish()
- stopSeed := context.AfterFunc(startupCtx, seedCancel)
- defer stopSeed()
+ var seedCtx context.Context
+ var seedCancel context.CancelFunc
var seedServer *filesSeedServer
- if claims.Writes.Enabled && os.Getenv("CARLOS_HOST_CLAIM_ORIGINS") == "1" && svc != nil {
+ if seedEnabled {
+ seedCtx, seedCancel = signal.NotifyContext(claimCtx, syscall.SIGTERM, os.Interrupt)
+ defer seedCancel()
+ var seedFinish func()
+ seedCtx, seedFinish = activator.WithCleanupBudget(seedCtx, 30*time.Second)
+ defer seedFinish()
+ stopSeed := context.AfterFunc(startupCtx, seedCancel)
+ defer stopSeed()
seedServer, err = startAgentFilesSeed(seedCtx, svc.Startup, svc)
if err != nil {
log.Printf("Files seed failed: %s.", err)
@@ -914,21 +918,13 @@ func runAgent(args []string, buildClaims ClaimFactory) {
go usageLoop(m.Drain, am)
}, edgeOpts...)
- shutdownCtx, shutdownCancel = activator.CleanupContext(context.Background(), 60*time.Second)
- cancelStartup()
- seedCancel()
- seedJoin, seedJoinCancel := activator.CleanupContext(seedCtx, 30*time.Second)
- seedErr := seedServer.close(seedJoin)
- seedJoinCancel()
+ var seedErr error
+ shutdownCtx, shutdownCancel, seedErr = joinAgentShutdown(seedCtx, seedCancel, seedServer, cancelStartup, startupDone, dutiesDone)
if seedErr != nil {
safeClaimClose = false
log.Printf("Files seed failed: %s.", seedErr)
os.Exit(1)
}
- joinCtx, joinCancel := context.WithTimeout(shutdownCtx, 5*time.Second)
- defer joinCancel()
- joinAgentStartup(joinCtx, startupDone)
- joinAgentStartup(joinCtx, dutiesDone)
// The edge has drained; take the children down too (they're this process's
// children — orphaning them would strand their leases until TTL expiry).
diff --git a/internal/host/agent_test.go b/internal/host/agent_test.go
index bd97d0570..2ddeb221d 100644
--- a/internal/host/agent_test.go
+++ b/internal/host/agent_test.go
@@ -528,6 +528,11 @@ func restartTestRegistry(t *testing.T) *registry.Registry {
if err != nil {
t.Fatalf("open registry: %v", err)
}
+ t.Cleanup(func() {
+ if err := reg.Close(); err != nil {
+ t.Error(err)
+ }
+ })
return reg
}
diff --git a/internal/host/claimfilesseed_isolated_rootrunner_linux_test.go b/internal/host/claimfilesseed_isolated_rootrunner_linux_test.go
index 78608e3cf..4049c0c1d 100644
--- a/internal/host/claimfilesseed_isolated_rootrunner_linux_test.go
+++ b/internal/host/claimfilesseed_isolated_rootrunner_linux_test.go
@@ -319,6 +319,9 @@ func rootFilesSeedIsolatedWitness(t *testing.T, binary string, twoHosts ...bool)
}
cmd, _, settle, _, err := launch.Start(t.Context(), "main", start.CurrentAttempt(), start.Build, func() error { return nil })
if err != nil {
+ if body, readErr := os.ReadFile(filepath.Join(filepath.Dir(p.f.db), o.Host, "payload.isolation-violation")); readErr == nil {
+ t.Fatalf("isolation mutation detected: %s", body)
+ }
out, _ := exec.Command("journalctl", "-u", instancectl.UnitName(instancectl.RoleRun, o.Host), "--no-pager", "-n", "30").CombinedOutput()
t.Fatalf("isolated launch: %v\n%s", err, out)
}
diff --git a/internal/host/claimfilesseed_restore_linux_test.go b/internal/host/claimfilesseed_restore_linux_test.go
index 6c6949c8c..f1d8405e8 100644
--- a/internal/host/claimfilesseed_restore_linux_test.go
+++ b/internal/host/claimfilesseed_restore_linux_test.go
@@ -105,6 +105,8 @@ func newServingPreparation(t *testing.T, f *servingRootFixture, boot, controller
}
result, err := coordinator.FenceAndStop(ctx, req)
if err != nil {
+ inventory, inventoryErr := coordinator.Inventory.ReadStopInventory(ctx, req.Host)
+ t.Logf("stop diagnosis request=%+v inventory=%+v inventoryErr=%v stopErr=%v", req, inventory, inventoryErr, err)
return result, err
}
return validateTransportStop(req, result)
diff --git a/internal/host/claimfilesseed_shutdown.go b/internal/host/claimfilesseed_shutdown.go
new file mode 100644
index 000000000..5e40090e6
--- /dev/null
+++ b/internal/host/claimfilesseed_shutdown.go
@@ -0,0 +1,35 @@
+package host
+
+import (
+ "context"
+ "time"
+
+ "github.com/carlosframework/platform/internal/activator"
+)
+
+func joinAgentShutdown(seedCtx context.Context, seedCancel context.CancelFunc, seedServer *filesSeedServer, cancelStartup context.CancelFunc, startupDone, dutiesDone <-chan struct{}) (context.Context, context.CancelFunc, error) {
+ base := context.Background()
+ cancel := func() {}
+ if seedCtx != nil {
+ base, cancel = activator.CleanupContext(base, 60*time.Second)
+ }
+ cancelStartup()
+ if seedCtx != nil {
+ seedCancel()
+ join, stop := activator.CleanupContext(seedCtx, 30*time.Second)
+ err := seedServer.close(join)
+ stop()
+ if err != nil {
+ return base, cancel, err
+ }
+ }
+ join, stop := context.WithTimeout(base, 5*time.Second)
+ defer stop()
+ joinAgentStartup(join, startupDone)
+ joinAgentStartup(join, dutiesDone)
+ if seedCtx == nil {
+ // Main starts this clock after joining startup duties.
+ base, cancel = context.WithTimeout(context.Background(), 60*time.Second)
+ }
+ return base, cancel, nil
+}
diff --git a/internal/host/claimfilesseed_shutdown_test.go b/internal/host/claimfilesseed_shutdown_test.go
new file mode 100644
index 000000000..ef9d4fce9
--- /dev/null
+++ b/internal/host/claimfilesseed_shutdown_test.go
@@ -0,0 +1,25 @@
+package host
+
+import (
+ "testing"
+ "time"
+)
+
+func TestAgentNoCensusShutdownBudget(t *testing.T) {
+ startup, duties := make(chan struct{}), make(chan struct{})
+ cancelled := make(chan struct{})
+ go func() { <-cancelled; time.Sleep(200 * time.Millisecond); close(startup); close(duties) }()
+ began := time.Now()
+ ctx, cancel, err := joinAgentShutdown(nil, nil, nil, func() { close(cancelled) }, startup, duties)
+ defer cancel()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if time.Since(began) < 200*time.Millisecond {
+ t.Fatal("did not join delayed duties")
+ }
+ deadline, ok := ctx.Deadline()
+ if !ok || time.Until(deadline) < 59950*time.Millisecond || ctx.Err() != nil {
+ t.Fatal("no-census shutdown lost its fresh 60 seconds", deadline, ctx.Err())
+ }
+}
diff --git a/internal/host/claimservingidentity.go b/internal/host/claimservingidentity.go
index f14755439..442e585ab 100644
--- a/internal/host/claimservingidentity.go
+++ b/internal/host/claimservingidentity.go
@@ -93,5 +93,11 @@ func sameServingParkSource(saved, fresh servingParkSource) bool {
fresh.Files[i].Persistent = servingParkPersistentFile{}
}
}
+ if saved.ParentPersistent == (servingParkPersistentDirectory{}) {
+ fresh.ParentPersistent = servingParkPersistentDirectory{}
+ }
+ if saved.Volume == "" {
+ fresh.Volume = ""
+ }
return saved == fresh
}
diff --git a/internal/host/claimservingpark_linux.go b/internal/host/claimservingpark_linux.go
index 98e2de183..e60ea3012 100644
--- a/internal/host/claimservingpark_linux.go
+++ b/internal/host/claimservingpark_linux.go
@@ -29,6 +29,7 @@ type servingParkFS struct {
owner uint32
mount func(string) (bool, error)
boot func() (string, error)
+ volume func(uint64) string
handle func(*os.File) (servingParkPersistentDirectory, error)
rename func(int, string, int, string, uint) error
sync func(int) error
@@ -38,7 +39,7 @@ type servingParkFS struct {
// Production construction always requires root and a nosymfollow mount. Tests
// use the private fields to exercise filesystems without mount privileges.
func newServingParkFS(data string, state rootstate.Store) *servingParkFS {
- return &servingParkFS{data: data, state: state, owner: 0, mount: instancefs.MountNoSymfollow, boot: servingParkBootID, handle: servingParkPersistentDir, rename: unix.Renameat2, sync: unix.Fsync, unlink: unix.Unlinkat}
+ return &servingParkFS{data: data, state: state, owner: 0, mount: instancefs.MountNoSymfollow, boot: servingParkBootID, handle: servingParkPersistentDir, volume: servingParkVolume, rename: unix.Renameat2, sync: unix.Fsync, unlink: unix.Unlinkat}
}
func servingParkDirID(f *os.File) (servingArtifactDirectory, error) {
@@ -248,7 +249,23 @@ func (f *servingParkFS) Capture(a servingParkAuthorization) (servingParkSource,
return out, err
}
defer source.Close()
+ if f.handle == nil || f.volume == nil {
+ return out, hostbox.ErrUnsupported
+ }
out.Directory, out.Parent = a.Directory, parent
+ p, pid, err := f.parent()
+ if err != nil {
+ return out, err
+ }
+ defer p.Close()
+ if pid != parent {
+ return out, hostbox.ErrChanged
+ }
+ out.ParentPersistent, err = f.handle(p)
+ if err != nil {
+ return out, err
+ }
+ out.Volume = f.volume(parent.Device)
out.BootID, err = f.currentBoot()
if err != nil {
return out, err
@@ -491,6 +508,9 @@ func (f *servingParkFS) Move(r *servingParkRecord, save func() error) error {
}
func (f *servingParkFS) Delete(r *servingParkRecord, save func() error) error {
+ if err := f.checkReapVolume(r); err != nil {
+ return err
+ }
if err := f.checkRecordBoot(r); err != nil {
return err
}
@@ -655,6 +675,9 @@ func (f *servingParkFS) CheckParked(r *servingParkRecord) error {
// removeEvent removes only a recorded empty event. The durable intent makes
// absence after a lost unlink reply replayable; it never licenses a replacement.
func (f *servingParkFS) removeEvent(r *servingParkRecord, save func() error) error {
+ if err := f.checkReapVolume(r); err != nil {
+ return err
+ }
if !r.Deleting || !r.Prepared || !r.Parked {
return hostbox.ErrChanged
}
diff --git a/internal/host/claimservingpark_linux_test.go b/internal/host/claimservingpark_linux_test.go
index 976cb82e9..f16f0a929 100644
--- a/internal/host/claimservingpark_linux_test.go
+++ b/internal/host/claimservingpark_linux_test.go
@@ -44,6 +44,7 @@ func newServingParkDiskFixture(t *testing.T) *servingParkDiskFixture {
}
disk := &rollbackDisk{dir: t.TempDir()}
fs := newServingParkFS(rf.guard.DataDir, disk)
+ fs.volume = func(uint64) string { return "vol0123456789abcdef0" }
fs.owner = uint32(os.Geteuid())
fs.mount = func(string) (bool, error) { return true, nil }
f := &servingParkDiskFixture{t: t, fs: fs, disk: disk, data: rf.guard.DataDir, now: time.Date(2026, 10, 14, 12, 0, 0, 0, time.UTC), base: base, target: receipt.Target}
diff --git a/internal/host/claimservingpark_records.go b/internal/host/claimservingpark_records.go
index b1a7901c7..929b52ea1 100644
--- a/internal/host/claimservingpark_records.go
+++ b/internal/host/claimservingpark_records.go
@@ -38,11 +38,13 @@ type servingParkFile struct {
}
type servingParkSource struct {
- Persistent servingParkPersistentDirectory `json:",omitzero"`
- BootID string `json:",omitempty"`
- Directory servingArtifactDirectory
- Parent servingArtifactDirectory
- Files [4]servingParkFile
+ ParentPersistent servingParkPersistentDirectory `json:",omitzero"`
+ Volume string `json:",omitempty"`
+ Persistent servingParkPersistentDirectory `json:",omitzero"`
+ BootID string `json:",omitempty"`
+ Directory servingArtifactDirectory
+ Parent servingArtifactDirectory
+ Files [4]servingParkFile
}
type servingParkRecord struct {
diff --git a/internal/host/claimservingpark_rootrunner_linux_test.go b/internal/host/claimservingpark_rootrunner_linux_test.go
index 52ce8dc5e..f60cfb247 100644
--- a/internal/host/claimservingpark_rootrunner_linux_test.go
+++ b/internal/host/claimservingpark_rootrunner_linux_test.go
@@ -29,8 +29,8 @@ func servingRootRunner(t *testing.T) {
if err != nil || os.Geteuid() != 0 || strings.Join(strings.Fields(string(mapping)), " ") != "0 0 4294967295" {
t.Fatalf("root runner needs real root in the initial user namespace: %v", err)
}
- if t.Name() == "TestRootServingParkUnitPayload" {
- // The hardened unit uses the parent-provided data root; do not write outside it.
+ if t.Name() == "TestRootServingParkUnitPayload" || os.Getenv("PLAN14_ROOT_FIXTURE") == t.Name() {
+ // Unit and chroot children use the parent-owned fixture filesystem.
return
}
// The runner's /tmp is a small RAM filesystem. Serving now enforces the
@@ -44,7 +44,12 @@ func servingRootRunner(t *testing.T) {
t.Error(err)
}
})
- t.Setenv("TMPDIR", data)
+ if !strings.HasPrefix(t.Name(), "TestRootReboot") {
+ scratch := servingRunnerScratchFilesystem(t, data, 2<<30)
+ t.Setenv("TMPDIR", scratch)
+ } else {
+ t.Setenv("TMPDIR", data)
+ }
}
// This includes loopback S3 and Unix-socket fixtures; it makes no remote calls.
@@ -664,14 +669,29 @@ func TestRootRebootServingParkChangedMountPhase2(t *testing.T) {
t.Fatal(err)
}
ledger.now = func() time.Time { return current.ReapAfter }
- if err := ledger.reapLocked(t.Context(), a.ID); err != nil {
- t.Fatal("verified reap after reboot", err)
+ reapErr := ledger.reapLocked(t.Context(), a.ID)
+ if strings.Contains(t.Name(), "GrownDisk") {
+ if !errors.Is(reapErr, hostbox.ErrChanged) || current.Source.Volume != "" {
+ t.Fatal("serial-less loop gained deletion authority", reapErr)
+ }
+ for _, item := range current.Source.Files {
+ if item.Present {
+ if _, err := os.Stat(filepath.Join(data, "instances", ".serving-parked", a.ID, item.Name)); err != nil {
+ t.Fatal("serial-less park lost bytes", err)
+ }
+ }
+ }
+ t.Log("grown filesystem recovered and completed; no independent loop volume serial, so reaping retained every artifact")
+ } else if reapErr != nil {
+ t.Fatal("verified reap after reboot", reapErr)
}
historical, err := ledger.readHistorical(a.ID)
if err != nil || historical.Authorization != saved.Record.Authorization || historical.Source.BootID != saved.Record.Source.BootID || historical.ReapAfter != saved.Record.ReapAfter {
t.Fatal("changed history", err)
}
- t.Log("committed current mount identities and reaped the exact parked artifacts after an actual reboot")
+ if !strings.Contains(t.Name(), "GrownDisk") {
+ t.Log("committed current mount identities and reaped the exact parked artifacts after an actual reboot")
+ }
}
diff --git a/internal/host/claimservingreboot_binding.go b/internal/host/claimservingreboot_binding.go
index 23705c011..48d739e54 100644
--- a/internal/host/claimservingreboot_binding.go
+++ b/internal/host/claimservingreboot_binding.go
@@ -11,6 +11,7 @@ import (
// A binding changes only boot-local device and mount numbers. Durable records keep their
// original identities; progress writes translate back to those identities.
type servingParkBootBinding struct {
+ Volume string `json:",omitempty"`
Version int
ID, BeforeBoot, BootID string
BeforeParent, Parent servingArtifactDirectory
diff --git a/internal/host/claimservingreboot_commit_linux.go b/internal/host/claimservingreboot_commit_linux.go
index 5b22b16c9..5389540f6 100644
--- a/internal/host/claimservingreboot_commit_linux.go
+++ b/internal/host/claimservingreboot_commit_linux.go
@@ -4,6 +4,7 @@ package host
import (
"context"
+ "log"
"reflect"
"github.com/carlosframework/platform/internal/hostbox"
@@ -48,6 +49,15 @@ func (l *servingParkLedger) commitRebootLocked(ctx context.Context, saved servin
if err := ctx.Err(); err != nil {
return err
}
+ prior, err := servingParkReadBinding(l.state, record.ID)
+ if err != nil {
+ return err
+ }
+ if saved.Source.Volume != "" && saved.Source.Volume == t.Volume && (prior == nil || prior.Volume != "") {
+ record.Volume = t.Volume
+ } else {
+ log.Printf("Host %q: serving database retirement %q retained: %s.", saved.Authorization.Successor.Host, saved.Authorization.ID, hostbox.ErrChanged)
+ }
return l.write("current-"+record.ID+".json", record)
}
diff --git a/internal/host/claimservingreboot_linux.go b/internal/host/claimservingreboot_linux.go
index 5b4ef8d6e..3493c9f80 100644
--- a/internal/host/claimservingreboot_linux.go
+++ b/internal/host/claimservingreboot_linux.go
@@ -54,7 +54,16 @@ func (f *servingParkFS) observeRebootRecord(ctx context.Context, id string) (ser
return out, err
}
n := topology.Previous
- if !validServingParkBootID(r.Source.BootID) || r.Source.BootID == topology.BootID || !r.Source.Persistent.valid() || r.Source.Parent.Device != n.Parent.Device || r.Source.Parent.Inode != n.Parent.Inode || r.Source.Persistent.Filesystem != n.Topology.Parent.Filesystem || r.Prepared && (r.Namespace.Device != n.Root.Device || r.Namespace.Inode != n.Root.Inode || r.Namespace.Mount != r.Source.Parent.Mount) {
+ // A later retirement binds its parent to namespace history with root-private
+ // persistent evidence. Device/mount numbers constrain only the same boot.
+ if r.Source.BootID == n.BootID {
+ if r.Source.Parent != n.Parent {
+ return out, hostbox.ErrChanged
+ }
+ } else if !r.Source.ParentPersistent.valid() || r.Source.ParentPersistent != n.Topology.Parent {
+ return out, hostbox.ErrChanged
+ }
+ if !validServingParkBootID(r.Source.BootID) || r.Source.BootID == topology.BootID || !r.Source.Persistent.valid() || r.Source.Parent.Inode != n.Parent.Inode || r.Source.Persistent.Filesystem != n.Topology.Parent.Filesystem || r.Prepared && (r.Namespace.Inode != n.Root.Inode || r.Namespace.Mount != r.Source.Parent.Mount) {
return out, hostbox.ErrChanged
}
parent, pid, err := f.parent()
diff --git a/internal/host/claimservingreboot_prepare_linux_test.go b/internal/host/claimservingreboot_prepare_linux_test.go
index 3ad6dbf9e..bb9bf1c5a 100644
--- a/internal/host/claimservingreboot_prepare_linux_test.go
+++ b/internal/host/claimservingreboot_prepare_linux_test.go
@@ -21,6 +21,7 @@ func servingRebootPreparationFixture(t *testing.T, phase string) (*servingRootFi
t.Helper()
f := newServingRootFixture(t, true)
fs := f.p.ledger.fs.(*servingParkFS)
+ fs.volume = func(uint64) string { return "vol0123456789abcdef0" }
if phase == "rename-reply" {
fs.rename = func(a int, b string, c int, d string, flags uint) error {
if err := unix.Renameat2(a, b, c, d, flags); err != nil {
diff --git a/internal/host/claimservingrenumber_linux_test.go b/internal/host/claimservingrenumber_linux_test.go
index de5e1aefe..f8a109970 100644
--- a/internal/host/claimservingrenumber_linux_test.go
+++ b/internal/host/claimservingrenumber_linux_test.go
@@ -4,6 +4,7 @@ package host
import (
"errors"
+ "fmt"
"os"
"reflect"
"testing"
@@ -199,3 +200,114 @@ func TestServingParkRebootDeviceRenumberLostWrite(t *testing.T) {
}
}
}
+
+func TestServingParkRetirementAfterDeviceRenumber(t *testing.T) {
+ for _, renumberAgain := range []bool{false, true} {
+ for _, replacement := range []bool{false, true} {
+ t.Run(fmt.Sprint(renumberAgain, "/replacement-", replacement), func(t *testing.T) {
+ f, old := servingRebootRecordFixture(t, "complete")
+ servingRenumberHistory(t, f, &old) // A/D1 -> B/D2.
+ root := &servingParkRoot{ledger: f.ledger}
+ if err := root.commitServingReboot(t.Context(), old.Authorization.ID); err != nil {
+ t.Fatal(err)
+ }
+ f.put("", "boot B served database")
+ f.put(".gen", "G2")
+ a, stops := f.evidence(2, false, "started")
+ park, err := f.park(a, stops)
+ if err != nil {
+ t.Fatal("B retirement and replacement", err)
+ }
+ f.complete(a)
+ park, err = f.ledger.readHistorical(a.ID)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if renumberAgain {
+ // C's real device is D3; B's immutable record used D2.
+ b := servingParkBootBinding{Version: 1, ID: "retirement-" + a.ID, BeforeBoot: park.Source.BootID, BootID: park.Source.BootID, BeforeParent: park.Source.Parent, Parent: park.Source.Parent}
+ b.Parent.Device += 456
+ if err := b.record(&park, false); err != nil {
+ t.Fatal(err)
+ }
+ if err := f.ledger.save(park); err != nil {
+ t.Fatal(err)
+ }
+ }
+ f.fs.boot = func() (string, error) { return "33333333-3333-4333-8333-333333333333", nil }
+ if err := root.commitServingReboot(t.Context(), a.ID); err != nil {
+ t.Fatal("C recovery", err)
+ }
+ if replacement {
+ f.put("", "boot C served database")
+ f.put(".gen", "G3")
+ next, stops := f.evidence(3, false, "started")
+ if _, err := f.park(next, stops); err != nil {
+ t.Fatal("C replacement", err)
+ }
+ f.complete(next)
+ nextPark, err := f.ledger.read(next.ID)
+ if err != nil {
+ t.Fatal(err)
+ }
+ f.now = nextPark.ReapAfter
+ if err := f.ledger.reapLocked(t.Context(), next.ID); err != nil {
+ t.Fatal("C replacement reaping", err)
+ }
+ }
+ f.now = park.ReapAfter
+ if err := f.ledger.reapLocked(t.Context(), a.ID); err != nil {
+ t.Fatal("C reap", err)
+ }
+ if _, err := os.Stat(f.path(a, "")); !os.IsNotExist(err) {
+ t.Fatal("park survived", err)
+ }
+ })
+ }
+ }
+
+}
+
+func TestServingParkLaterRetirementRequiresParentProof(t *testing.T) {
+ for _, fault := range []string{"missing", "handle", "filesystem"} {
+ t.Run(fault, func(t *testing.T) {
+ f, r := servingRebootRecordFixture(t, "complete")
+ servingRenumberHistory(t, f, &r)
+ root := &servingParkRoot{ledger: f.ledger}
+ if err := root.commitServingReboot(t.Context(), r.Authorization.ID); err != nil {
+ t.Fatal(err)
+ }
+ f.put("", "later served database")
+ f.put(".gen", "G2")
+ a, stops := f.evidence(2, false, "started")
+ if _, err := f.park(a, stops); err != nil {
+ t.Fatal(err)
+ }
+ f.complete(a)
+ later, err := f.ledger.readHistorical(a.ID)
+ if err != nil {
+ t.Fatal(err)
+ }
+ switch fault {
+ case "missing":
+ later.Source.ParentPersistent = servingParkPersistentDirectory{}
+ case "handle":
+ later.Source.ParentPersistent.Handle = "aa"
+ case "filesystem":
+ later.Source.ParentPersistent.Filesystem[0]++
+ }
+ if err := f.ledger.write("retirement-"+a.ID+".json", later); err != nil {
+ t.Fatal(err)
+ }
+ f.fs.boot = func() (string, error) { return "33333333-3333-4333-8333-333333333333", nil }
+ before := servingBootSnapshot(t, f.disk.dir)
+ if err := root.commitServingReboot(t.Context(), a.ID); err == nil {
+ t.Fatal("unbound historical parent accepted")
+ }
+ if !reflect.DeepEqual(before, servingBootSnapshot(t, f.disk.dir)) {
+ t.Fatal("refusal rewrote authority")
+ }
+ f.retained(a, "later served database", later.ReapAfter)
+ })
+ }
+}
diff --git a/internal/host/claimservingtopology_linux.go b/internal/host/claimservingtopology_linux.go
index d223689d6..320159d56 100644
--- a/internal/host/claimservingtopology_linux.go
+++ b/internal/host/claimservingtopology_linux.go
@@ -92,6 +92,7 @@ func (f *servingParkFS) captureTopology(parent, root *os.File, n servingParkName
// A later recovery transaction must preserve retirement history and validate
// every affected source/event before it can publish current mount authority.
type servingParkRebootObservation struct {
+ Volume string `json:",omitempty"`
Previous servingParkNamespace
Parent servingParkParent
BootID string
@@ -148,6 +149,10 @@ func (f *servingParkFS) inspectRebootTopology(ctx context.Context) (servingParkR
return zero, hostbox.ErrChanged
}
current := servingParkNamespace{Version: 1, Parent: pid, Root: rid, BootID: boot}
+ volume := ""
+ if f.volume != nil {
+ volume = f.volume(pid.Device)
+ }
topology, err := f.captureTopology(parent, root, current)
if err != nil {
return zero, err
@@ -174,11 +179,11 @@ func (f *servingParkFS) inspectRebootTopology(ctx context.Context) (servingParkR
if err != nil {
return zero, err
}
- if *againTopology != *topology {
+ if *againTopology != *topology || f.volume != nil && f.volume(nextParent.Device) != volume {
return zero, hostbox.ErrChanged
}
if err := ctx.Err(); err != nil {
return zero, err
}
- return servingParkRebootObservation{Previous: n, Parent: p, BootID: boot, CurrentParent: pid, CurrentRoot: rid}, nil
+ return servingParkRebootObservation{Volume: volume, Previous: n, Parent: p, BootID: boot, CurrentParent: pid, CurrentRoot: rid}, nil
}
diff --git a/internal/host/claimservingvolume_linux.go b/internal/host/claimservingvolume_linux.go
new file mode 100644
index 000000000..6739f0a79
--- /dev/null
+++ b/internal/host/claimservingvolume_linux.go
@@ -0,0 +1,66 @@
+//go:build linux
+
+package host
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/carlosframework/platform/internal/hostbox"
+ "golang.org/x/sys/unix"
+)
+
+// Resolve the filesystem's kernel device, then its containing disk if it is a
+// partition. Only an EBS NVMe controller serial supplies independent identity;
+// filesystem UUIDs and loop backing files cannot distinguish snapshot clones.
+func servingParkVolume(device uint64) string {
+ return servingParkVolumeAt("/sys", device)
+}
+
+func servingParkVolumeAt(sys string, device uint64) string {
+ path, err := filepath.EvalSymlinks(filepath.Join(sys, "dev", "block", fmt.Sprintf("%d:%d", unix.Major(device), unix.Minor(device))))
+ if err != nil {
+ return ""
+ }
+ if _, err := os.Stat(filepath.Join(path, "partition")); err == nil {
+ path = filepath.Dir(path)
+ } else if !os.IsNotExist(err) {
+ return ""
+ }
+ raw, err := os.ReadFile(filepath.Join(path, "device", "serial"))
+ if err != nil {
+ return ""
+ }
+ serial := strings.TrimSpace(string(raw))
+ if !strings.HasPrefix(serial, "vol") {
+ return ""
+ }
+ id := strings.TrimPrefix(strings.TrimPrefix(serial, "vol"), "-")
+ if len(id) != 8 && len(id) != 17 {
+ return ""
+ }
+ for _, c := range id {
+ if !(c >= '0' && c <= '9' || c >= 'a' && c <= 'f') {
+ return ""
+ }
+ }
+ return "vol" + id
+}
+
+// A reboot binding grants recovery but never supplies missing volume evidence.
+// Check again at deletion so a subsequent reboot or remount cannot borrow it.
+func (f *servingParkFS) checkReapVolume(r *servingParkRecord) error {
+ b, err := servingParkReadBinding(f.state, "retirement-"+r.Authorization.ID)
+ if err != nil {
+ return err
+ }
+ if b == nil {
+ return nil
+ }
+ if f.volume == nil || b.Volume == "" || b.Volume != r.Source.Volume || f.volume(r.Source.Parent.Device) != r.Source.Volume {
+ return hostbox.ErrChanged
+ }
+ return f.checkBoot(b.BootID)
+}
diff --git a/internal/host/claimservingvolume_linux_test.go b/internal/host/claimservingvolume_linux_test.go
new file mode 100644
index 000000000..15cb39725
--- /dev/null
+++ b/internal/host/claimservingvolume_linux_test.go
@@ -0,0 +1,120 @@
+//go:build linux
+
+package host
+
+import (
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/carlosframework/platform/internal/instancectl"
+ "golang.org/x/sys/unix"
+)
+
+func TestServingParkRebootVolumeIdentity(t *testing.T) {
+ for _, serial := range []string{"vol0123456789abcdef0", "vol11111111111111111", ""} {
+ t.Run("serial-"+serial, func(t *testing.T) {
+ f, r := servingRebootRecordFixture(t, "complete")
+ servingRenumberHistory(t, f, &r)
+ f.fs.volume = func(uint64) string { return serial }
+ root := &servingParkRoot{ledger: f.ledger}
+ if err := root.commitServingReboot(t.Context(), r.Authorization.ID); err != nil {
+ t.Fatal("retention recovery", err)
+ }
+ current, err := f.ledger.read(r.Authorization.ID)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if current.Source.BootID != servingOtherBoot {
+ t.Fatal("serving authority not rebound")
+ }
+ b, err := servingParkReadBinding(f.disk, "retirement-"+r.Authorization.ID)
+ if err != nil || b == nil {
+ t.Fatal(err)
+ }
+ if (b.Volume != "") != (serial == "vol0123456789abcdef0") {
+ t.Fatal("wrong destructive binding", b)
+ }
+ if serial != "vol0123456789abcdef0" {
+ if err := f.ledger.maintainParks(t.Context(), func() error { return instancectl.ErrDiskFull }); err == nil {
+ t.Fatal("pressure deleted retained volume")
+ }
+ f.retained(r.Authorization, "last database", r.ReapAfter)
+ }
+ f.now = r.ReapAfter
+ err = f.ledger.reapLocked(t.Context(), r.Authorization.ID)
+ if serial == "vol0123456789abcdef0" {
+ if err != nil {
+ t.Fatal("same volume reap", err)
+ }
+ if _, err := os.Stat(f.path(r.Authorization, "")); !os.IsNotExist(err) {
+ t.Fatal("park survived", err)
+ }
+ } else {
+ if err == nil {
+ t.Fatal("destructive clone/unreadable rebind")
+ }
+ f.retained(r.Authorization, "last database", r.ReapAfter)
+ // A later readable serial does not silently re-enroll a retained park.
+ f.fs.volume = func(uint64) string { return "vol0123456789abcdef0" }
+ f.fs.boot = func() (string, error) { return "33333333-3333-4333-8333-333333333333", nil }
+ if err := root.commitServingReboot(t.Context(), r.Authorization.ID); err != nil {
+ t.Fatal(err)
+ }
+ b, err := servingParkReadBinding(f.disk, "retirement-"+r.Authorization.ID)
+ if err != nil || b == nil || b.Volume != "" {
+ t.Fatal("automatic re-enrollment", b, err)
+ }
+ if err := f.ledger.reapLocked(t.Context(), r.Authorization.ID); err == nil {
+ t.Fatal("retained park gained deletion authority")
+ }
+ }
+ })
+ }
+}
+
+func TestServingParkKernelVolumeResolution(t *testing.T) {
+ sys := t.TempDir()
+ disk := filepath.Join(sys, "devices", "nvme0n1")
+ part := filepath.Join(disk, "nvme0n1p1")
+ if err := os.MkdirAll(filepath.Join(disk, "device"), 0700); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.Mkdir(part, 0700); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.WriteFile(filepath.Join(part, "partition"), []byte("1\n"), 0600); err != nil {
+ t.Fatal(err)
+ }
+ links := filepath.Join(sys, "dev", "block")
+ if err := os.MkdirAll(links, 0700); err != nil {
+ t.Fatal(err)
+ }
+ for name, target := range map[string]string{"259:0": disk, "259:1": part} {
+ if err := os.Symlink(target, filepath.Join(links, name)); err != nil {
+ t.Fatal(err)
+ }
+ }
+ serial := filepath.Join(disk, "device", "serial")
+ for _, raw := range []string{"vol-0123456789abcdef0\n", " vol0123456789abcdef0 ", "bad", "vol../escape", "vol", "vol0123456789abcdefg"} {
+ if err := os.WriteFile(serial, []byte(raw), 0600); err != nil {
+ t.Fatal(err)
+ }
+ for _, minor := range []uint32{0, 1} {
+ got := servingParkVolumeAt(sys, unix.Mkdev(259, minor))
+ want := ""
+ if raw == "vol-0123456789abcdef0\n" || raw == " vol0123456789abcdef0 " {
+ want = "vol0123456789abcdef0"
+ }
+ if got != want {
+ t.Fatal(raw, minor, got, want)
+ }
+ }
+ }
+ if err := os.Remove(serial); err != nil {
+ t.Fatal(err)
+ }
+ if servingParkVolumeAt(sys, unix.Mkdev(259, 1)) != "" || servingParkVolumeAt(sys, unix.Mkdev(7, 0)) != "" {
+ t.Fatal("unreadable disk/loop supplied identity")
+ }
+}
diff --git a/internal/host/claimservingvolume_rootrunner_linux_test.go b/internal/host/claimservingvolume_rootrunner_linux_test.go
new file mode 100644
index 000000000..402a1899f
--- /dev/null
+++ b/internal/host/claimservingvolume_rootrunner_linux_test.go
@@ -0,0 +1,180 @@
+//go:build linux && rootrunner
+
+package host
+
+import (
+ "bytes"
+ "context"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "reflect"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/carlosframework/platform/internal/instancefs"
+ "golang.org/x/sys/unix"
+)
+
+func servingRunnerScratchFilesystem(t *testing.T, dir string, size int64) string {
+ t.Helper()
+ image := filepath.Join(dir, "scratch.ext4")
+ f, err := os.OpenFile(image, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0600)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = f.Truncate(size)
+ closeErr := f.Close()
+ if err != nil || closeErr != nil {
+ t.Fatal(err, closeErr)
+ }
+ if out, err := exec.CommandContext(t.Context(), "mkfs.ext4", "-q", "-F", image).CombinedOutput(); err != nil {
+ t.Fatal(err, string(out))
+ }
+ out, err := exec.CommandContext(t.Context(), "losetup", "--find", "--show", image).CombinedOutput()
+ if err != nil {
+ t.Fatal(err, string(out))
+ }
+ device := strings.TrimSpace(string(out))
+ t.Cleanup(func() {
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ defer cancel()
+ if out, err := exec.CommandContext(ctx, "losetup", "--detach", device).CombinedOutput(); err != nil {
+ t.Error(err, string(out))
+ }
+ })
+ mount := filepath.Join(dir, "scratch")
+ if err := os.Mkdir(mount, 0700); err != nil {
+ t.Fatal(err)
+ }
+ if err := unix.Mount(device, mount, "ext4", unix.MS_NOSUID|unix.MS_NODEV, ""); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := unix.Unmount(mount, 0); err != nil {
+ t.Error(err)
+ }
+ })
+ t.Logf("private fixture filesystem: %d MiB on %s", size>>20, device)
+ return mount
+}
+
+func TestRootServingParkSnapshotClone(t *testing.T) {
+ servingRootRunner(t)
+ // This runner really is EBS/NVMe; exercise the production sysfs resolver.
+ rootDir, err := os.Open("/")
+ if err != nil {
+ t.Fatal(err)
+ }
+ id, err := servingParkDirID(rootDir)
+ rootDir.Close()
+ if err != nil || servingParkVolume(id.Device) == "" {
+ t.Fatal("runner has no readable EBS volume identity", err)
+ }
+ t.Log("kernel backing EBS volume", servingParkVolume(id.Device))
+ base := t.TempDir()
+ mount := servingRunnerScratchFilesystem(t, base, 64<<20)
+ t.Setenv("TMPDIR", mount)
+ t.Run("clone", func(t *testing.T) {
+ f, r := servingRebootRecordFixture(t, "complete")
+ if err := f.ledger.journal.Close(); err != nil {
+ t.Fatal(err)
+ }
+ servingRunnerMount(t, f.data)
+ f.fs.mount = instancefs.MountNoSymfollow
+ before := r.Source.Files
+ // Tear down the nested bind for the block-level snapshot, then recreate it.
+ instances := filepath.Join(f.data, "instances")
+ if err := unix.Unmount(instances, 0); err != nil {
+ t.Fatal(err)
+ }
+ if err := unix.Unmount(mount, 0); err != nil {
+ t.Fatal(err)
+ }
+ clone := filepath.Join(base, "clone.ext4")
+ if out, err := exec.CommandContext(t.Context(), "cp", "--sparse=always", filepath.Join(base, "scratch.ext4"), clone).CombinedOutput(); err != nil {
+ t.Fatal(err, string(out))
+ }
+ out, err := exec.CommandContext(t.Context(), "losetup", "--find", "--show", clone).CombinedOutput()
+ if err != nil {
+ t.Fatal(err, string(out))
+ }
+ device := strings.TrimSpace(string(out))
+ // The original helper owns unmount; detach this device only after that.
+ defer func() {
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ defer cancel()
+ if out, err := exec.CommandContext(ctx, "losetup", "--detach", device).CombinedOutput(); err != nil {
+ t.Error(err, string(out))
+ }
+ }()
+ if err := unix.Mount(device, mount, "ext4", unix.MS_NOSUID|unix.MS_NODEV, ""); err != nil {
+ t.Fatal(err)
+ }
+ if err := unix.Mount(instances, instances, "", unix.MS_BIND, ""); err != nil {
+ t.Fatal(err)
+ }
+ if err := unix.Mount("", instances, "", unix.MS_BIND|unix.MS_REMOUNT|unix.MS_NOSYMFOLLOW|unix.MS_NOSUID|unix.MS_NODEV|unix.MS_NOEXEC, ""); err != nil {
+ t.Fatal(err)
+ }
+ // Loop devices have no EBS serial. Model only this hardware boundary;
+ // filesystem identity and cloning below are real kernel observations.
+ f.fs.volume = func(uint64) string { return "vol11111111111111111" }
+ path := f.path(r.Authorization, "")
+ original, err := os.ReadFile(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ changed := bytes.Repeat([]byte{'X'}, len(original))
+ if err := os.WriteFile(path, changed, 0600); err != nil {
+ t.Fatal(err)
+ }
+ observation, err := f.fs.inspectRebootRecord(t.Context(), r.Authorization.ID)
+ if err != nil || !reflect.DeepEqual(observation.Record.Source.Files, before) {
+ t.Fatal("clone did not preserve filesystem proof", err)
+ }
+ if observation.Topology.CurrentParent.Device == r.Source.Parent.Device {
+ t.Fatal("clone did not change device")
+ }
+ root := &servingParkRoot{ledger: f.ledger}
+ if err := root.commitServingReboot(t.Context(), r.Authorization.ID); err != nil {
+ t.Fatal("retention recovery", err)
+ }
+ f.now = r.ReapAfter
+ if err := f.ledger.reapLocked(t.Context(), r.Authorization.ID); err == nil {
+ t.Fatal("snapshot clone reaped")
+ }
+ for _, item := range r.Source.Files {
+ if item.Present {
+ if _, err := os.Stat(f.path(r.Authorization, strings.TrimPrefix(item.Name, r.Authorization.Successor.Host+".db"))); err != nil {
+ t.Fatal("clone artifact removed", err)
+ }
+ }
+ }
+ got, err := os.ReadFile(path)
+ if err != nil || !bytes.Equal(got, changed) {
+ t.Fatal("clone bytes changed", err)
+ }
+ t.Log("snapshot clone preserves handles/inodes, changes content and volume serial; recovery succeeds and deletion refuses")
+ // Explicit test-only root re-enrollment is the positive control: with the
+ // original serial now asserted, the very same cloned artifacts can be reaped.
+ // This makes the serial refusal decisive, rather than an unrelated mismatch.
+ binding, err := servingParkReadBinding(f.disk, "retirement-"+r.Authorization.ID)
+ if err != nil || binding == nil || binding.Volume != "" {
+ t.Fatal("clone acquired destructive binding", binding, err)
+ }
+ binding.Volume = r.Source.Volume
+ if err := f.ledger.write("current-retirement-"+r.Authorization.ID+".json", binding); err != nil {
+ t.Fatal(err)
+ }
+ f.fs.volume = func(uint64) string { return r.Source.Volume }
+ if err := f.ledger.reapLocked(t.Context(), r.Authorization.ID); err != nil {
+ t.Fatal("same-volume control has another refusal", err)
+ }
+ if _, err := os.Stat(path); !os.IsNotExist(err) {
+ t.Fatal("same-volume control did not delete", err)
+ }
+ t.Log("same snapshot with explicit fixture root re-enrollment reaped successfully; volume guard was decisive")
+ })
+}
diff --git a/internal/host/startup_largebox_linux_test.go b/internal/host/startup_largebox_linux_test.go
index 58410ee4a..6ecd9c0af 100644
--- a/internal/host/startup_largebox_linux_test.go
+++ b/internal/host/startup_largebox_linux_test.go
@@ -397,12 +397,12 @@ func TestRunAgentStartupWiring(t *testing.T) {
key = types.ExprString(fd.Recv.List[0].Type) + "." + key
}
switch key {
- case "runAgent", "startActivator", "recoverLayoutAtStart", "activatorStart.Run":
+ case "runAgent", "startActivator", "recoverLayoutAtStart", "activatorStart.Run", "joinAgentShutdown":
decls[key] = fd
}
}
}
- for _, key := range []string{"runAgent", "startActivator", "recoverLayoutAtStart", "activatorStart.Run"} {
+ for _, key := range []string{"runAgent", "startActivator", "recoverLayoutAtStart", "activatorStart.Run", "joinAgentShutdown"} {
if decls[key] == nil {
t.Fatalf("%s not found in package host", key)
}
@@ -451,7 +451,7 @@ func TestRunAgentStartupWiring(t *testing.T) {
return token.NoPos
}
prev, prevName := edgeCall.Pos(), "edge.RunWithActivatorReady"
- for _, step := range []struct{ name, arg string }{{"cancelStartup", ""}, {"joinAgentStartup", "startupDone"}, {"Shutdown", ""}} {
+ for _, step := range []struct{ name, arg string }{{"joinAgentShutdown", "dutiesDone"}, {"Shutdown", ""}} {
p := after(step.name, step.arg)
if p == token.NoPos || p < prev {
t.Errorf("runAgent: %s is not called synchronously after %s", step.name, prevName)
@@ -460,6 +460,38 @@ func TestRunAgentStartupWiring(t *testing.T) {
prev, prevName = p, step.name
}
+ // Follow the synchronous helper without weakening cancel/join ordering or
+ // allowing the caller to pass different startup channels or cancellation.
+ for _, call := range sync {
+ if calleeName(call) != "joinAgentShutdown" {
+ continue
+ }
+ want := []string{"seedCtx", "seedCancel", "seedServer", "cancelStartup", "startupDone", "dutiesDone"}
+ if len(call.Args) != len(want) {
+ t.Fatal("shutdown helper argument count", len(call.Args))
+ }
+ for i, arg := range call.Args {
+ if str(arg) != want[i] {
+ t.Errorf("shutdown argument %d: %s, want %s", i, str(arg), want[i])
+ }
+ }
+ }
+ helper := syncCalls(decls["joinAgentShutdown"].Body)
+ previous := token.NoPos
+ for _, step := range []struct{ name, arg string }{{"cancelStartup", ""}, {"joinAgentStartup", "startupDone"}, {"joinAgentStartup", "dutiesDone"}} {
+ found := token.NoPos
+ for _, call := range helper {
+ if calleeName(call) == step.name && (step.arg == "" || len(call.Args) > 0 && str(call.Args[len(call.Args)-1]) == step.arg) {
+ found = call.Pos()
+ break
+ }
+ }
+ if found == token.NoPos || found <= previous {
+ t.Fatalf("shutdown helper lost synchronous order at %s(%s)", step.name, step.arg)
+ }
+ previous = found
+ }
+
// The activatorStart fields the child copies, and the steps inside
// them. Run calls each field on its background goroutine, so inside a
// field each step must be synchronous.
diff --git a/internal/provision/plan14_legacy_identity_test.go b/internal/provision/plan14_legacy_identity_test.go
index 1cfe45139..ce7fc9d9f 100644
--- a/internal/provision/plan14_legacy_identity_test.go
+++ b/internal/provision/plan14_legacy_identity_test.go
@@ -616,10 +616,10 @@ func TestPlan14SeedOverlayPreservesLegacySeal(t *testing.T) {
if err := loadRootReadInventory(root+"/internal/provision/testdata/plan14-root-reads-seed.tsv", &inv, true); err != nil {
t.Fatal(err)
}
- if len(inv.Legacy) != before+3 {
- t.Fatal("FS-R6 permits exactly three additional legacy fingerprints")
+ if len(inv.Legacy) != before+4 {
+ t.Fatal("FS-R6 and FS-R10 permit exactly four additional legacy fingerprints")
}
- if errs := checkRootReadLegacy(inv, "b5b04f416089d97dbff3b9c6b6cb9d9c83f874449577946f651dc8c7a66a9f67"); len(errs) != 0 {
+ if errs := checkRootReadLegacy(inv, "e8d252989435bc3ee6e9c0d097f4d111100d63d19aadc1e2f8ea69e132b3301b"); len(errs) != 0 {
t.Fatal(errs)
}
// FS-R6 keeps these existing legacy classes; the seed cannot promote them.
@@ -628,6 +628,9 @@ func TestPlan14SeedOverlayPreservesLegacySeal(t *testing.T) {
t.Fatalf("unruled legacy disposition %s: %+v", key, e)
}
}
+ if e := inv.Entries["internal/lease/leasetest/s3server.go:NewS3Server"]; e.Class != "legacy" || !strings.Contains(e.Basis, "census-ruling-FS-R10") {
+ t.Fatal("test S3 fixture lost its legacy ruling", e)
+ }
if e := inv.Entries["internal/host/claimwakeassembly.go:ClaimWakeAssembly.Build"]; e.Task != "C,G,J1,J4" {
t.Fatal("wake assembly lost its owners", e.Task)
}
diff --git a/internal/provision/plan14_root_reads_test.go b/internal/provision/plan14_root_reads_test.go
index 4706c311b..588e106bb 100644
--- a/internal/provision/plan14_root_reads_test.go
+++ b/internal/provision/plan14_root_reads_test.go
@@ -747,8 +747,8 @@ func TestPlan14RootReadCensus(t *testing.T) {
if err = loadRootReadInventory(filepath.Join(root, "internal/provision/testdata/plan14-root-reads-seed.tsv"), &inv, true); err != nil {
t.Fatal(err)
}
- // FS-R6 explicitly preserves these three legacy readers; all other proposed rows remain unapproved.
- seal = "b5b04f416089d97dbff3b9c6b6cb9d9c83f874449577946f651dc8c7a66a9f67"
+ // FS-R6 preserves three legacy readers; FS-R10 re-digests the test-only S3 server. Other proposals remain unapproved.
+ seal = "e8d252989435bc3ee6e9c0d097f4d111100d63d19aadc1e2f8ea69e132b3301b"
errs := append(compareRootReads(got, inv), checkRootReadLegacy(inv, seal)...)
errs = append(errs, checkRootReadDeclarations(got, inv)...)
if len(errs) > 0 {
diff --git a/internal/provision/testdata/plan14-root-reads-seed.tsv b/internal/provision/testdata/plan14-root-reads-seed.tsv
index e41d97382..fb7ab6ef8 100644
--- a/internal/provision/testdata/plan14-root-reads-seed.tsv
+++ b/internal/provision/testdata/plan14-root-reads-seed.tsv
@@ -27,7 +27,7 @@ internal/activator/seed_exclusion.go type SeedExclusion authority B,C,D,F,J4 rev
internal/activator/servingretry.go ServingRetries.SettleAbandoned authority C,J4 rev3=PROPOSED-seed; current serving retry identity, stop settlement and bounded reservation; capacity checked before side effects and immediately before reservation; explicit rerun settlement spends no new attempt file:Exec@279,file:Read@387,file:Read@732 8885d486d4daea05411f8c5ab0afa5b340542bb624bbfe82518d0b1f5063d171
internal/activator/servingretry.go ServingRetries.Wrap legacy accepted-legacy accepted-legacy;rev3=PROPOSED-seed;basis=census-ruling-FS-R6; BeforeReserve refuses only; cleanup deadline bounds lifetime; route identity remains legacy file:Prepare@128,file:Prepare@4325,file:Prepare@4455,file:Read@2888,file:Read@3531,file:Read@5054,file:Read@5991,file:Read@6656,whole:Route@1212,whole:Route@1550,whole:Route@1648,whole:Route@1948,whole:Route@2360,whole:Route@4473,whole:Route@5421,whole:Route@6522,whole:Route@876,whole:Route@954 e0fe1c8acbe4b1631ff9c3ce63dceba8297fbeab55fc6e4ace72be1d2d9d5bfa
internal/activator/swaphold.go Service.wakeFailureEvent diagnostic C,J4 rev3=PROPOSED-seed; incarnation-keyed missing-files diagnostics and indexed retry results; throttles output only and grants no retry, stop or launch debf64b1e73210eb4a6c88dd82f76e8be2e3e0a4f43e272341efe227703f9ca0
-internal/host/agent.go runAgent authority B,C,D,F,J4 rev3=PROPOSED-seed; agent startup and claim-gated seed endpoint; shared shutdown deadline and handler join before shared closure file:Exec@13674,file:Open@731,registry:App@10065,whole:Route@10057,whole:Route@22981 95e74886a35fbe7d522c47e94b531df15e8a1bacc5417f5540bbff04d9039b4e
+internal/host/agent.go runAgent authority B,C,D,F,J4 rev3=PROPOSED-seed; FS-R7/8/9; agent; signal subscription, cleanup budget and seed join share one activation gate; inactive lifecycle retains main startup joins, fresh 60-second service shutdown and fresh claims-close budget file:Exec@13674,file:Open@731,registry:App@10065,whole:Route@10057,whole:Route@22981 36e63cdf0cc75b4fae905d13f78cd13c14963753282f5767980d2a022b910505
internal/host/claimassembly.go ClaimAssembly.Build authority C,G,J4 rev3=PROPOSED-seed; captured claim source, writer and runtime resources; construct fresh private serving owner without copying mutex-bearing live state file:Prepare@3640,file:Prepare@3657,file:Prepare@4369,file:Read@4299 42fc9bdc95130d62b0c5001821a924499dbe8cbf0d7e72d78e2f367978d0c8a6
internal/host/claimassembly.go NewProductionRootClaimStop authority C,J4 rev3=PROPOSED-seed; verified root assembly, actor and managed roots; binds private serving-park runtime and retained session-zero stop evidence; agent serving construction remains refused 7debdfe43ca6f17e2229c8aa8b566ee22112d86a595a66fb0cb0213253854aa8
internal/host/claimconstruction.go OpenProductionClaimAssembly authority C,G rev3=PROPOSED-seed; production serving retirement, enrollment and readiness bindings; early metadata and capacity guards before retry reservation file:Read@4150,file:Read@9908,file:ReadFile@3694,registry:Host@9935,whole:Route@10023,whole:Route@10772 60f3c8f0a32435e0341912069f33238793659b7059a2a5661308eaae8b08c6c7
@@ -52,7 +52,7 @@ internal/host/claimfilesseed_delegation_other.go filesSeedManager authority C,J4
internal/host/claimfilesseed_delegation_other.go verifyFilesSeedDelegation authority C,J4 rev3=PROPOSED-seed; installed edge MainPID, stable controller identity and kernel cgroup identity, owner and delegated controls; root rejects non-controller and unprepared delegation; non-Linux refuses b3c849c2a61ed819d5392a03ad88b030653c772f036567109e5f634c336a8ebb
internal/host/claimfilesseed_guard.go movesFiles.checkServingFilesEvidence authority C,J4 rev3=PROPOSED-seed; safe nested layout and bounded manifest metadata; evidence absence refuses before retries; only genuine empty authority can omit instance directory whole:Route@265 4c75f947c4061022996dca4af4a54a10ad974e5a7b1fbe69c3f628d5e5decf84
internal/host/claimfilesseed_guard.go servingFilesPrerequisites authority C,J4 rev3=PROPOSED-seed; bounded current-version file evidence and exact claim reread before retry wrappers; typed missing-evidence refusal carries incarnation for rate-limited diagnostics file:Read@761,whole:Route@548 52abed55f7182a0f3166b9dcea1097c49e68f17bd83a13d924bee9d7a1ec5ea7
-internal/host/claimfilesseed_killed_linux.go LinuxStopKernel.recoverUnlaunchedAbsent authority C,D,J4 rev3=PROPOSED-seed; root recovery and lifetime; per-host failures retained and reported, controller recovery first, pressure cleanup independently budgeted file:Lstat@1566,file:Read@1222,file:Read@590,file:Read@740 fbd4fa26890d04e8a5f34caebd10b1ea171374ccf677a20c63ad6d09057042b7
+internal/host/claimfilesseed_killed_linux.go LinuxStopKernel.recoverUnlaunchedAbsent authority C,D,J4 rev3=PROPOSED-seed; census-ruling-FS-R10; root launch/stop journals, no role reservations, dead controller in the same boot, absent recorded cgroup and descriptor scans before and after settlement file:Lstat@1566,file:Read@1222,file:Read@590,file:Read@740 fbd4fa26890d04e8a5f34caebd10b1ea171374ccf677a20c63ad6d09057042b7
internal/host/claimfilesseed_retry.go filesSeedOperation.prerequisites authority C,J4 rev3=PROPOSED-seed; exact owned host exclusion, serving claim and nested layout; bypass only missing evidence for one eligible private seed; rerun settlement uses ordinary root stop and spends no new attempt registry:Host@256,whole:Route@596,whole:Route@798 44a9d61481d9499b1e1305595a43d94cd364da6abe1f91c337f4deb2c48d2fbc
internal/host/claimfilesseed_retry.go settleFilesSeedRerun authority C,J4 rev3=PROPOSED-seed; exact owned host exclusion, serving claim and nested layout; bypass only missing evidence for one eligible private seed; rerun settlement uses ordinary root stop and spends no new attempt registry:Host@241,registry:Host@323,registry:Host@721,whole:Route@485 24337d0b6a864850a4f4203757ed40d634a770abff9dc4c96cd9c746ba8ced2e
internal/host/claimfilesseed_runtime.go newFilesSeedRuntime authority C,J4 rev3=PROPOSED-seed; fresh private serving owner and captured ordinary preparation/stop closures; reread same-writer claim after root certification before C8 origin proof; ambiguous cleanup retains exclusion and retry debt whole:Route@1955 3d91da897f65e34f09d636bac9a19b25d641df17d10b37c69a724cf122b9d27d
@@ -84,10 +84,10 @@ internal/host/claimfilesseed_transport_linux.go type filesSeedReply authority C,
internal/host/claimfilesseed_transport_linux.go type filesSeedRequest authority C,J4 rev3=PROPOSED-seed; Unix peer credentials, pidfd liveness, repeated process/controller/cgroup identity, trusted socket; root lifecycle and bounded recovery 9be6d7a745e27a89cb6faabb28899578bef1602ba8b8dcf7f39efd97274eb42c
internal/host/claimfilesseed_transport_linux.go type filesSeedServer authority C,J4 rev3=PROPOSED-seed; Unix peer credentials, pidfd liveness, repeated process/controller/cgroup identity, trusted socket; root lifecycle and bounded recovery 8a899cbc4d18bf65e2c53ee68d36fd05412c99bfc9d621b5e8ee7dd530124bbd
internal/host/claimfilesseed_transport_linux.go type filesSeedStream authority C,J4 rev3=PROPOSED-seed; Unix peer credentials, pidfd liveness, repeated process/controller/cgroup identity, trusted socket; root lifecycle and bounded recovery d51d90e4e9cd1ae397db0941798f719897c26c4e6ed84b289543eba363fe0a96
-internal/host/claimfilesseed_transport_other.go filesSeedClient authority C,J4 rev3=PROPOSED-seed; Unix peer credentials, pidfd liveness, repeated process/controller/cgroup identity, trusted socket; root lifecycle and bounded recovery 6e57a07086521dd818f025dfdbc964695ea10c32a5ad6e9d98498ab0032c0090
-internal/host/claimfilesseed_transport_other.go filesSeedServer.close authority C,J4 rev3=PROPOSED-seed; Unix peer credentials, pidfd liveness, repeated process/controller/cgroup identity, trusted socket; root lifecycle and bounded recovery 9340754f2e5e90a99a8b15d5cec4c8d27a070252e55e8664c323ecc658ad02d9
-internal/host/claimfilesseed_transport_other.go startAgentFilesSeed authority C,J4 rev3=PROPOSED-seed; Unix peer credentials, pidfd liveness, repeated process/controller/cgroup identity, trusted socket; root lifecycle and bounded recovery 71ef4ddedbcc4d35bb79ce23b53f21ad6801d7cff9814b6ccdefbd5816164594
-internal/host/claimfilesseed_transport_other.go type filesSeedServer authority C,J4 rev3=PROPOSED-seed; Unix peer credentials, pidfd liveness, repeated process/controller/cgroup identity, trusted socket; root lifecycle and bounded recovery 258bd08914bac2d2014a35d4c0a1c9e41f5299cb6ef5fd1532f2d266924c07de
+internal/host/claimfilesseed_transport_other.go filesSeedClient authority C,J4 rev3=PROPOSED-seed; census-ruling-FS-R10; non-Linux refusal-only client; returns unsupported delegation, no Unix peer or pidfd authentication 6e57a07086521dd818f025dfdbc964695ea10c32a5ad6e9d98498ab0032c0090
+internal/host/claimfilesseed_transport_other.go filesSeedServer.close diagnostic C,J4 rev3=PROPOSED-seed; census-ruling-FS-R10; non-Linux no-op close; no resources, reads, authentication or lifecycle authority 9340754f2e5e90a99a8b15d5cec4c8d27a070252e55e8664c323ecc658ad02d9
+internal/host/claimfilesseed_transport_other.go startAgentFilesSeed authority C,J4 rev3=PROPOSED-seed; census-ruling-FS-R10; non-Linux refusal-only server constructor; returns unsupported delegation, no socket, signal subscription or Unix authentication 71ef4ddedbcc4d35bb79ce23b53f21ad6801d7cff9814b6ccdefbd5816164594
+internal/host/claimfilesseed_transport_other.go type filesSeedServer diagnostic C,J4 rev3=PROPOSED-seed; census-ruling-FS-R10; non-Linux empty server stub; no state, reads or authentication 258bd08914bac2d2014a35d4c0a1c9e41f5299cb6ef5fd1532f2d266924c07de
internal/host/claimhandover_transport.go ClaimRootHTTP.serveColdHandover authority C,J4 rev3=PROPOSED-seed; authenticated existing handover transport; serving-specific transfer and repeated peer identity check under the host lock; operation handover remains separate 9ea7b9ea4a4962ad92a5f6ce19a24fcffca73e40aae9318a2d8c79ef25351e6a
internal/host/claimhandover_transport.go coldAdmissionHook authority C,J4 rev3=PROPOSED-seed; serving predecessor receipt or session-zero identity selector; root supplies the retained no-process result 70974148b3f47e4cb2057e4000c472cc4b372707fb1ff64b5e62582dc92b70c2
internal/host/claimjournal_transport.go ClaimRootHTTP.serveJournal authority C,J4 rev3=PROPOSED-seed; typed journal envelope; routes v4 serving-park selectors before generic journal replay; existing operations retain their dispatch file:Read@3311,whole:Route@4465 51276984eeb10a9480b9ff1a92ed8f16e9a141134cb3913ec75ff18c03570868
@@ -116,7 +116,7 @@ internal/host/claimservinghandover.go ClaimRootHTTP.servingHandover authority C,
internal/host/claimservinghandover.go ClaimRootHTTP.servingLegacyHandoverTarget authority C,J4 rev3=PROPOSED-seed; root-private no-process record selected by host/box/incarnation; only its retained exact stop tuple supplies legacy transfer authority file:Read@524 3fbec96fa13bd112c83253221e808a80943118b1dc06b6b5bcbbeb41ac5771d9
internal/host/claimservinghandover.go ClaimRootHTTP.validateServingHandover authority C,J4 rev3=PROPOSED-seed; fresh actor/census, claim, canonical deployment record, root installation and predecessor admission; exact retained session-zero no-process evidence or stopped serving predecessor file:Read@2660,file:Read@3770,registry:ClaimIncarnation@1500,registry:ClaimOwner@1642,registry:ClaimRowID@1664,registry:ClaimStamp@1528,whole:Route@1767 10a3ea792139e8328eec6292e9612d0f3d84c2a8500f8959ede3b92230a00788
internal/host/claimservinghandover.go servingPriorRevision authority C,J4 rev3=PROPOSED-seed; canonical recorded directory revision bounded by the current authenticated enrollment revision; never rewrites retained stop evidence 8007e188e2ad600cf36d8eed0fb5adb591f08f52be52285c85db348e0780cb84
-internal/host/claimservingidentity.go sameServingParkSource authority C,J4 rev3=PROPOSED-seed; immutable source versus fresh capture; old records compare original identity fields without acquiring new historical evidence 251d4c3de760b413dbbd3a0bdad03ff57560146e20794b9acb723cc142d046bf
+internal/host/claimservingidentity.go sameServingParkSource authority C,J4 rev3=PROPOSED-seed; FS-R7/8/9; root; exact immutable source replay, with absent historical parent/volume evidence kept absent rather than upgraded e351fe9daee39009a165d749e8d618e1f3ed9d45497b3e5d1defe2820b3cd5ad
internal/host/claimservingidentity.go servingParkPersistentDirectory.valid authority C,J4 rev3=PROPOSED-seed; persistent handle format, bounded 1-128 byte handle, filesystem identity, directory type and non-writable group/other mode a40d3a371641b4b51616f86b60c9fd6506cd72c5667df940cfd981ccd3925191
internal/host/claimservingidentity.go servingParkPersistentFile.valid authority C,J4 rev3=PROPOSED-seed; persistent regular-file type and opaque handle validation; no pathname or handle opening 6e071ea46888326a8a5040f486d42b07194c46f2b186fb163d53f60d45f147ba
internal/host/claimservingidentity.go type servingParkPersistentDirectory authority C,J4 rev3=PROPOSED-seed; root-recorded filesystem identity, opaque persistent directory handle and owner/mode, independent of numeric mount identity dced817ce21faf3ea261c82c6bf2b8036a74c14a266cd96b48618ce5bc746445
@@ -152,24 +152,24 @@ internal/host/claimservingnamespace_linux.go servingParkFS.recoverRecordedNamesp
internal/host/claimservingnamespace_linux.go servingParkRoot.prepareServingNamespace authority C,J4 rev3=PROPOSED-seed; private namespace records, topology and boot bindings; capacity refusal before new namespace capture b9c323e15dd3e3e91f6094d39003e2c1b274668412a225db2a01229ed53d1ce0
internal/host/claimservingnamespace_linux.go type servingParkParent authority C,J4 rev3=PROPOSED-seed; root-installation parent identity and additive boot scope; startup never infers an earlier boot from a matching mount number c58afd9dda038b4adb9ebf67fe0a42f1f3dd48f21d11b8f5660bad278e4dfb87
internal/host/claimservingpark.go servingGenerationParkDisposition authority C,J4 rev3=PROPOSED-seed; exact serving stop bytes and root-local launch journal; only settled root-observed tenant start permits slot retention; absent database preserves old slot; ambiguous intended launch refuses a11aaa2f4bb12cd7f4c52a0a38fb3b9cfe98714cea5ca3cc75f0e95053aca63d
-internal/host/claimservingpark_linux.go newServingParkFS authority C,J4 rev3=PROPOSED-seed; root-only filesystem dependencies, fixed nosymfollow validation and private state store; bound through private root service construction; production boot identity reader is fixed at construction; persistent file-handle reader fixed at production construction 30278f0fb52daaea0247eecaeeeb23aef50051f752817aca3681333c4e538293
+internal/host/claimservingpark_linux.go newServingParkFS authority C,J4 rev3=PROPOSED-seed; FS-R7/8/9; root; constructs no-follow filesystem driver with kernel EBS volume serial resolver bfa3e50b1cc14c38f6cbfecda1b0f0e764debd52890cb4472a09610ef54ce4e9
internal/host/claimservingpark_linux.go sameServingParkFile authority C,J4 rev3=PROPOSED-seed; recorded basename, presence, mount, device, inode, regular type, single link and allocated-byte identity; new recorded files also require the original filesystem handle, owner and mode; old saved identities retain their existing checks 58b2d78cd6a6d35e1da3e43d9f9761a6986da63ff6fb171e73c3874746674dae
internal/host/claimservingpark_linux.go servingParkDirID authority C,J4 rev3=PROPOSED-seed; descriptor statx directory type, device, inode and mount identity file:Statx@102 3480dad6d65fedcc17e678df38cf13352ad3c4a8a9d0a09a395973d92b98094e
-internal/host/claimservingpark_linux.go servingParkFS.Capture authority C,J4 rev3=PROPOSED-seed; canonical nested source directory, four fixed basenames, source mount and bounded generation-marker bytes through the captured descriptor; source boot and persistent directory handle captured before mutation and rechecked after all artifact reads file:Fstat@1199,file:Openat@973,file:ReadAll@1417 1a0278ee90f2d1b7f3a604ba2b6f1e17a8c4e0b2b0a35c6b4f3f0667b78a8c91
+internal/host/claimservingpark_linux.go servingParkFS.Capture authority C,J4 rev3=PROPOSED-seed; FS-R7/8/9; root; reads pinned source/parent descriptors, boot, filesystem handles and kernel EBS volume serial; retains parent proof and volume with immutable source evidence file:Fstat@1535,file:Openat@1309,file:ReadAll@1753 aa599384e7850908e756cceda3ca205e60353ee2ca0ecb5db22a4ad73551d043
internal/host/claimservingpark_linux.go servingParkFS.CheckParked authority C,J4 rev3=PROPOSED-seed; root filesystem descriptors, statx and persistent handles; each prepare/move/delete/parked check refuses an unbound historical boot file:Readdirnames@1328 5e6e74785bfe6928a36408f5c50cb82124bb31bbd1f86ae07c2d2c9004e4334f
-internal/host/claimservingpark_linux.go servingParkFS.Delete authority C,J4 rev3=PROPOSED-seed; root filesystem descriptors, statx and persistent handles; each prepare/move/delete/parked check refuses an unbound historical boot file:Readdirnames@464 f2c6d7acd3c5015f9accaa7c8809c3cc99e9de1f39b814879b1c3a2398834f23
+internal/host/claimservingpark_linux.go servingParkFS.Delete authority C,J4 rev3=PROPOSED-seed; FS-R7/8/9; root; independent recorded/current EBS volume identity and destructive binding before any unlink; retains full original per-artifact identity checks file:Readdirnames@526 90f1d7ab959b7f1776b32cf78ba1a1d453b35a48ac5aa4c30d35cb71a1b2e2ec
internal/host/claimservingpark_linux.go servingParkFS.Move authority C,J4 rev3=PROPOSED-seed; root filesystem descriptors, statx and persistent handles; each prepare/move/delete/parked check refuses an unbound historical boot c8c741b5508f3fb143f587e324e64a614949c5606fc3b92a8ee3634f0fac197a
internal/host/claimservingpark_linux.go servingParkFS.Prepare authority C,J4 rev3=PROPOSED-seed; root filesystem descriptors, statx and persistent handles; each prepare/move/delete/parked check refuses an unbound historical boot file:Prepare@24,file:Readdirnames@1029 f6b45af1842cd6fbff097bf347964048edc2a202bd61162f1952b8e3892bf822
internal/host/claimservingpark_linux.go servingParkFS.event authority C,J4 rev3=PROPOSED-seed; current or proven displaced namespace and exact private event mount/device/inode; old records may fall back only to the previous root recorded before quarantine 625ed442442a9b07c57392f72dea64e9e91eba65d2e2ef0e9899275756336788
internal/host/claimservingpark_linux.go servingParkFS.namespace authority C,J4 rev3=PROPOSED-seed; root filesystem descriptors, statx and persistent handles; each prepare/move/delete/parked check refuses an unbound historical boot 95dcd2ac8fe6022436caa48ca7e38c9e5d202ee684490e81be629129a3202982
internal/host/claimservingpark_linux.go servingParkFS.parent authority C,J4 rev3=PROPOSED-seed; deployment-controlled instances path, descriptor mount identity and effective nosymfollow topology; path replacement refuses 1d241a7d446bbbade6e460efd911863f8a5246b8ac1d47b2548034e3feb2f216
internal/host/claimservingpark_linux.go servingParkFS.privateDir authority C,J4 rev3=PROPOSED-seed; no-follow directory descriptor, exact private owner and mode plus device/inode/mount identity file:Fstat@261 8a4a33c6871fa88cd53f9922f99e173102966827bfea3998b2b3bd749a2ad11b
-internal/host/claimservingpark_linux.go servingParkFS.removeEvent authority C,J4 rev3=PROPOSED-seed; recorded current or displaced namespace and empty event; durable removal intent, descriptor-relative rmdir and fsync; ambiguous old-root absence refuses file:Readdirnames@790 66aa36321bc2bf3245daec35032783bb164cb21fe9b47620fbec71fd4b56cb89
+internal/host/claimservingpark_linux.go servingParkFS.removeEvent authority C,J4 rev3=PROPOSED-seed; FS-R7/8/9; root; volume-bound reboot authority before empty-event rmdir, plus existing durable remove intent and identity checks file:Readdirnames@852 e36228d64fb5b3d6dd9f7cf4a5a37abbcddbe6b93c79efc98893f9dd056cbb57
internal/host/claimservingpark_linux.go servingParkFS.removedEvent authority C,J4 rev3=PROPOSED-seed; absence within the exact recorded current or displaced root; root replacement and ambiguous pre-upgrade absence refuse d4100944fa7e41b83578839a91df3e546fa069f2c2558b9e8216b53fd329306d
internal/host/claimservingpark_linux.go servingParkFS.source authority C,J4 rev3=PROPOSED-seed; canonical host-derived nested state directory checked against independently supplied root directory identity; no registry path b7a30539c735e811c366ebb7c853a962500e82305ba1caa64f73402ae6edbce5
internal/host/claimservingpark_linux.go servingParkOpenDir authority C,J4 rev3=PROPOSED-seed; single basename opened relative to a pinned parent with no-follow directory semantics file:Openat@210 665ff3adc3e1cd1c6c4b92439160a0ef8013ea8e35375bcdfa90747888751ff9
internal/host/claimservingpark_linux.go servingParkStat authority C,J4 rev3=PROPOSED-seed; no-follow artifact statx identity, single links, explicit absence and checked allocated-byte count; regular files additionally obtain a descriptor-pinned persistent filesystem handle file:Statx@143 52bf86dad4d27ade2cb85ddff8faa7bf5a12bf34706641855dc4b43c06bfa9d0
-internal/host/claimservingpark_linux.go type servingParkFS authority C,J4 rev3=PROPOSED-seed; private filesystem dependencies including production kernel boot reader; requests cannot supply boot identity; descriptor-only persistent-handle reader, no open-by-handle operation 0a35b6ac9affb2c9be7577efe9081a54e7c6a74368a9d3502e193a6b29fdd2b2
+internal/host/claimservingpark_linux.go type servingParkFS authority C,J4 rev3=PROPOSED-seed; FS-R7/8/9; root; filesystem driver including injected kernel volume resolver; production constructor fixes the resolver e2fd168c6ba0414a92aeba23438e5404f4350ca4610c15e12cd6c2f8334a0af5
internal/host/claimservingpark_linux.go type servingParkNamespace authority C,J4 rev3=PROPOSED-seed; durable parent and private-root identities with additive kernel boot scope; an old record is decodable but lacks current mutation authority; optional persistent parent/root proof captured at creation, never inferred for older records e5b695d1869a0565d199267dae6f16ace6090c57a4b3c4bdfe2871fbe81d46df
internal/host/claimservingpark_other.go newServingParkAuthority authority C,J4 rev3=PROPOSED-seed; local root journal, guard, authentication, actor and empty-bootstrap dependencies; Linux fixes root ownership and nosymfollow validation, non-Linux refuses 5abda60daa2346c10d93bd92b4f10b470d792d2b3021b1a077734fd10c541efc
internal/host/claimservingpark_other.go newServingParkFS authority C,J4 rev3=PROPOSED-seed; non-Linux refusal or construction of refusal-only filesystem; no filesystem authority; production boot identity reader is fixed at construction; persistent file-handle reader fixed at production construction 69baa4c747e687578e73b8369f2df9987fe6fb5836b78796cc41c776ba12c802
@@ -199,7 +199,7 @@ internal/host/claimservingpark_records.go type servingLegacyConsumption authorit
internal/host/claimservingpark_records.go type servingParkFile authority C,J4 rev3=PROPOSED-seed; recorded basename, presence, device/inode/mount, type, link count, allocation and progress, with additive persistent file handle; optional persistent filesystem handle for the original file a5524de42a9f8514811326c297b304f9ab4f99b4650f6239895e9207725f30dc
internal/host/claimservingpark_records.go type servingParkFilesystem authority C,J4 rev3=PROPOSED-seed; private filesystem effect interface over root-recorded retirement identities; no wire or public request fields file:Prepare@180 b93f0e2af16b66aee1ff71294e215eeba9e661fe696c917c982d9ebc2dc682c7
internal/host/claimservingpark_records.go type servingParkRecord authority C,J4 rev3=PROPOSED-seed; private versioned retirement shape; additive collection deadline requires full completion and event removal; original authorization and retention deadline remain immutable; optional persistent event-directory handle recorded before rename 2bfdfe6cffe89e4a070c274463b65496eb2e17457f062fe6bbf6157ec1d14a62
-internal/host/claimservingpark_records.go type servingParkSource authority C,J4 rev3=PROPOSED-seed; immutable source directory and four artifacts; additive capture boot and persistent source identity; absent proof never gains inferred history; optional persistent source-directory handle and capture boot 5acbdb6ed1b1d3f2344ede87fc63652e8deea59e3105499e11ac6d465c1ac848
+internal/host/claimservingpark_records.go type servingParkSource authority C,J4 rev3=PROPOSED-seed; FS-R7/8/9; root-private immutable retirement evidence; source and parent persistent identities, original boot-local device/mount and independent EBS volume serial c4a89406e06467f09c2d631ee4855f1caae1979ce839864d406ac01b28e0bfdc
internal/host/claimservingpark_records.go validateServingParkRecord authority C,J4 rev3=PROPOSED-seed; private immutable authorization, source/event identities and durable progress; collection deadline requires complete closed removal and known namespace, no earlier than parked time plus transport receipt retention; validates all-or-none persistent source and event proof before accepting progress 23633a2a4949c1de529f192bf19aab927b0218bc0ed8cdbc67a55463c22e15bd
internal/host/claimservingpark_recovery.go const servingParkRecoveryLimit authority C,J4 rev3=PROPOSED-seed; fixed root-side processing limit of 128 retirement records per recovery page 6b949581334f55a7bae1b4579e56ac74810fc3042fdf568fbfa8e6b3c5a2ef11
internal/host/claimservingpark_recovery.go servingParkLedger.recoverCompleted authority C,J4 rev3=PROPOSED-seed; private completed retirement, exact filesystem deletion and slot references; resume removal, clear completed pending reference and collect only obsolete tombstones after durable replay deadline a2a277c862ec65e8abaf8b93b7eba77a79e538a76d061fb080f37f6c0c6d48f4
@@ -241,12 +241,12 @@ internal/host/claimservingreboot_binding.go servingParkBootBinding.directory aut
internal/host/claimservingreboot_binding.go servingParkBootBinding.record authority C,J4 rev3=PROPOSED-seed; maps all directories, continuation authorizations and present artifacts through private reboot binding; reverse save preserves historical device, mount and boot e72d4c59265421e3cef5f31692029bee46f63638f8c72401422731e550e15763
internal/host/claimservingreboot_binding.go servingParkLedger.bindRecord authority C,J4 rev3=PROPOSED-seed; private current-boot bindings; validate original boot/parent before translating boot-local device and mount identities; historical retirement/legacy bytes preserved e98b72dbd294f4a0b57bad42a2d6b3a429d56518db56dbdf36405529130aa513
internal/host/claimservingreboot_binding.go servingParkReadBinding authority C,J4 rev3=PROPOSED-seed; private committed reboot binding; nonzero boot-local device/mount on both sides, unchanged parent inode and distinct boots; historical bytes unchanged file:Read@111 8a748537b12e8d72b42c07cda845509bb334a3552ae2735205f52e7d6fbafd9a
-internal/host/claimservingreboot_binding.go type servingParkBootBinding authority C,J4 rev3=PROPOSED-seed; private current-boot bindings; validate original boot/parent before translating boot-local device and mount identities; historical retirement/legacy bytes preserved 4ecb75e5992c97d9d0445210689aa1f659f4c1fb8c8dd81ba4b84bd639f7fee7
+internal/host/claimservingreboot_binding.go type servingParkBootBinding authority C,J4 rev3=PROPOSED-seed; FS-R7/8/9; root-private reboot binding; recovery translates boot-local numbers; nonempty Volume separately grants destructive use and missing Volume retains the park 5341f8309a38519dc1fe39f96731da1846a1bcca0302cf428ee559242dcc48a4
internal/host/claimservingreboot_commit_linux.go servingParkFS.readNamespace authority C,J4 rev3=PROPOSED-seed; fresh persistent handles, topology, slot and preparation; namespace then record binding; every unbound old-boot mutation refuses file:Read@114 d92accaa928f703132adef9aa1e81cae95a7bfa959c7a6fe5086189154a7c7f1
-internal/host/claimservingreboot_commit_linux.go servingParkLedger.commitRebootLocked authority C,J4 rev3=PROPOSED-seed; fresh persistent handles, topology, slot and preparation; namespace then record binding; every unbound old-boot mutation refuses file:Read@246 d711de74b486ec757cbdac7aacb28e4414d47ba740082f8930857774b51d048a
+internal/host/claimservingreboot_commit_linux.go servingParkLedger.commitRebootLocked authority C,J4 rev3=PROPOSED-seed; FS-R7/8/9; root; compares repeated filesystem and kernel volume observations before durable binding; matching recorded volume permits destruction; unreadable/changed/prior-retained volume remains recovery-only and is logged file:Read@246 db0446f4520039d15c40eb8a439f05a19b4217ed9990c39c19e20dde1f3869bf
internal/host/claimservingreboot_commit_linux.go servingParkRoot.commitServingReboot authority C,J4 rev3=PROPOSED-seed; fresh persistent handles, topology, slot and preparation; namespace then record binding; every unbound old-boot mutation refuses 6d511474a2112ea80f5457b0faa939da7144fb7bf72526e021e696ad90a0917b
internal/host/claimservingreboot_linux.go servingParkFS.inspectRebootRecord authority C,J4 rev3=PROPOSED-seed; repeated read-only per-retirement inspection under namespace lock; unchanged historical record and topology; result is not mutation or agent authority baacf7e1df1d8fd4a09a5f6204597ab51b8765feca9c614ddc74d863a3cf9055
-internal/host/claimservingreboot_linux.go servingParkFS.observeRebootRecord authority C,J4 rev3=PROPOSED-seed; repeated topology and immutable retirement read; source/event/artifact proof uses the independently observed current device and mount; no historical rewrite file:Readdirnames@2503 01bbe5c19696cae8a43e5a80c960c12b69b5fbc1defdfa597d432a6eb4df6682
+internal/host/claimservingreboot_linux.go servingParkFS.observeRebootRecord authority C,J4 rev3=PROPOSED-seed; FS-R7/8/9; root; device/mount comparison only within the recording boot; later retirements bind to historical namespace through recorded persistent parent evidence, plus source/event/artifact handles and root-private records file:Readdirnames@2815 0f4ce0c69a4a783c8ca15d08a96672007a724ff681d5a1aeac92495ef77165c0
internal/host/claimservingreboot_linux.go servingParkFS.rebootDirectory authority C,J4 rev3=PROPOSED-seed; descriptor has historical inode, filesystem handle, owner and mode and current parent device/mount; no effects cc4eed6e9b2ff80f01f29c1933cc7238e01ac515ba85005c0c07a69234053df7
internal/host/claimservingreboot_linux.go servingParkRebootFileMatches authority C,J4 rev3=PROPOSED-seed; historical artifact handle and inode plus current parent device/mount; preserves single-link, size, ownership and progress checks 73a12fe037864c8111b4fd91e4cba824d307f49a6cb0fcb2723141bb34dcdcbd
internal/host/claimservingreboot_linux.go type servingParkRebootRecord authority C,J4 rev3=PROPOSED-seed; read-only historical retirement and current source/event/namespace observations; has no effect or transport consumer 2752876f2e40f92a720c17c05e479dbfc7c4910392a1e6dc5ba4dc82b566763c
@@ -274,9 +274,9 @@ internal/host/claimservingretire.go servingPredecessor authority C,J4 rev3=PROPO
internal/host/claimservingretire.go servingRetirementID authority C,J4 rev3=PROPOSED-seed; immutable predecessor and original successor tuples; deterministic journal identity independent of claim operation IDs c984974d17e621d7fe9c276a03ae789b86789abbccbb82a09d06f55c3bf13819
internal/host/claimservingretire.go servingWriterIdentity authority C,J4 rev3=PROPOSED-seed; authoritative serving holder and unique cold admission; exact local executor, stamp, session, generation, incarnation, location and region file:Exec@403 f7d8c868ce9856eab448aa72bdf20ee02cb70179ce0f435d45888f72e0c3b7bf
internal/host/claimservingtopology_linux.go servingParkFS.captureTopology authority C,J4 rev3=PROPOSED-seed; current boot and pinned parent/root descriptors; persistent filesystem identities, handles, owner and mode; rereads descriptor identities and boot before returning creation evidence 169ff4183b9d65770c49aeaa341ad061ee38105e8cdea4ccfb5ee3f57079ec20
-internal/host/claimservingtopology_linux.go servingParkFS.inspectRebootTopology authority C,J4 rev3=PROPOSED-seed; same persistent filesystem and parent/root handles and inodes across boots; current device/mount must agree; reopen names and recheck boot; read-only file:Read@501,file:Read@956 1e1584cd4a0456f050209e46029d6a58e973d7af39f47a47ada75f0927a24a96
+internal/host/claimservingtopology_linux.go servingParkFS.inspectRebootTopology authority C,J4 rev3=PROPOSED-seed; FS-R7/8/9; root; repeated persistent parent/root proof and current kernel volume serial observations; device/mount numbers used only for current-boot relationships; observation grants no deletion file:Read@501,file:Read@956 0433dec56dfc406a3450626e9186eabdaed017c6d0d82095dc4edb81dc75ee7f
internal/host/claimservingtopology_linux.go servingParkPersistentDir authority C,J4 rev3=PROPOSED-seed; descriptor-only Fstat, Fstatfs and NameToHandleAt with AT_EMPTY_PATH; bounded canonical handle and matching statx mount, mode and owner; no caller path, handle opening or permission widening file:Fstat@279,file:Fstatfs@375 2c60437509897dc3d7e861bf554a92cd5c338301b3bfbdee6b5d731ea9d3f7b8
-internal/host/claimservingtopology_linux.go type servingParkRebootObservation authority C,J4 rev3=PROPOSED-seed; read-only old namespace and current boot/topology observation; no effect consumer or transport result accepts it e000b7d132c36a4979c5be443fc87996beafe2a8db320bf978ef1f026a09baa2
+internal/host/claimservingtopology_linux.go type servingParkRebootObservation authority C,J4 rev3=PROPOSED-seed; FS-R7/8/9; root; repeated filesystem topology observation and independent current kernel volume serial; not deletion authority 9f632f146ea05070168a4ebe5349dc4286903c216fe3db142cddd6500d0e4fcc
internal/host/claimservingtopology_linux.go type servingParkTopology authority C,J4 rev3=PROPOSED-seed; persistent parent/root evidence attached only when root creates and records a new namespace 16a7fce2850bf204ddd67aa10876979be08a5d500a21ea3072e2fc5c1c369586
internal/host/claimstop.go ClaimStopCoordinator.witnessNoProcess authority C,G,J4 rev3=PROPOSED-seed; existing peer, claim, root inventory and positive kernel no-process checks; local adopted holder requires new retained-evidence binding; original observation is retained before receipt publication file:Exec@1845,file:Read@2541,file:Read@668 4a0df51297d2f6ed2e9c284ee11c26b73747bf84e1da5214d00f190eece5eb38
internal/host/claimstop.go type ClaimStopCoordinator authority C,G,J4 rev3=PROPOSED-seed; root-only coordinator dependency for retaining its own session-zero observation; nil preserves existing refusal for adopted legacy holders b7114aa7340cdba82fef42d62b784eae16da7acc48407ae6edba6453a4889f9a
@@ -325,15 +325,20 @@ system.go runSystem authority B,C,D,F,J4 rev3=PROPOSED-seed; command arguments,
system.go systemVerb authority B,C,D,F,J4 rev3=PROPOSED-seed; command arguments, claim flags, service identity and root delegation capability before constructing agent-mode resources; one-shot skips Configure, Start, duties and listeners; close resources only after private runtime joins ee25513ed72f9b8338d95682c4c76b4ced26ec1f060528fc5804da9afbfbfd83
@coverage . bf3d70b6a2c5d769a3c580c9d5511231499b788d9c08a542895de8381e5a39c9
@coverage internal/activator 41f0ad01ac54bae63f089ef8ec5bbfa9155bb76720d72b2d88563481de40fe5c
-@coverage internal/host dd140d90fe80775bf62b1b5a9992c1f399731781c9ec38afc237d285f9388425
+@coverage internal/host d5fedd5c2c15db96502000e3573af7a0f4d055e1c37f91aafea730349d557c7f
@coverage internal/hostbox db890260533bdace6ad9a7c23dc1538cb919962894f005fc7c5a59a18e969429
@coverage internal/hostfiles a38dbcd9789ba4acd237e88c834f2b2bfca9847b0d43c32b0116dc8996d7a142
@coverage internal/hostguard a721142f4f3ac5fa17b6b1e2ccfc66ca809cfdcbf2733fdc00fbd0efaa90aeb6
internal/host/rootexecution_recovery.go recoverRootExecutionBeforeUpdate authority D rev3=PROPOSED-seed; extracted update recovery ordering; root instance-control identity; broker durable controller state then best-effort local park and retirement journals; retained park failures cannot gate update eb97b162b0d712dd2fe8971984353ebc10efe1142077a3c2531781dd969cbbf5
-internal/lease/leasetest/s3server.go NewS3Server authority C,J4 rev3=PROPOSED-seed; test-process S3 fixture only, including privileged runner; HTTP query selects simulated versions, GET exposes stored ETag and conditional PUT refuses stale owners under the fixture mutex; not called by production root paths file:Query@479 ac2435bd4d5114736ba9b7e964f62d5fac3dd97eee5f11cbadd4facc342a9583
+@legacy 2b7d202a09e3f37dc54b6957c614fdf5ce9710dfe3516b1573ed624cbbdeccb1 internal/lease/leasetest/s3server.go NewS3Server legacy accepted-legacy accepted-legacy;rev3=PROPOSED-seed;basis=census-ruling-FS-R10; test-only S3 fixture, simulated ETags and conditional writes grant no production root authority file:Query@479 ac2435bd4d5114736ba9b7e964f62d5fac3dd97eee5f11cbadd4facc342a9583
+internal/lease/leasetest/s3server.go NewS3Server legacy accepted-legacy accepted-legacy;rev3=PROPOSED-seed;basis=census-ruling-FS-R10; test-only S3 fixture, simulated ETags and conditional writes grant no production root authority file:Query@479 ac2435bd4d5114736ba9b7e964f62d5fac3dd97eee5f11cbadd4facc342a9583
internal/host/claimservingartifact_linux.go servingAgentDirectory authority C,J4 rev3=PROPOSED-seed; agent descriptor identity before root RPC; no-follow directory open and existing statx device/inode/mount helper; used only for agent namespace checks 5edaa27cde0a470efb1a820ed15eca551df9c43fe6d7aa3c20f753bc3e3ae718
internal/host/claimservingartifact_other.go servingAgentDirectory authority C,J4 rev3=PROPOSED-seed; non-Linux refusal; no directory read or permission ef4826226980538555ab8152b6f5a8167b599eec64a2e48e53a7d8bd5e8332e7
@coverage internal/lease/leasetest a589d673ded02391c4dfbbe6a9b86890f44522d3985bfa219c19d6ed40a0fd07
internal/instancectl/claimexec.go claimExecGate.authorize authority C,G,J2,J4 rev3=PROPOSED-seed; root instance-control; systemd MainPID refreshed only within the same active invocation and cgroup at accepted trampoline connection; exact kernel peer match and repeated Same checks before permit 2c7461c0ce1f033f163bb660214b9e906945e75a8067bb1d6b1faa3b6916649d
internal/instancectl/claimunit.go ClaimUnits.start authority C,G,J2,J4 rev3=PROPOSED-seed; root instance-control; retain the refreshed authenticated trampoline MainPID for post-start kernel launch verification and exact-invocation cleanup 7f01cf7ac8fd9db0f74f7b08c412ee7d229ff2e097ae1d69706d8b3c87f1e30d
@coverage internal/instancectl a9b87eaf6abd540b9e80a4f2fccbce59877e4cf9049b47fbf8829b348d6ef14d
+internal/host/claimfilesseed_shutdown.go joinAgentShutdown authority C,J4 rev3=PROPOSED-seed; FS-R7/9; agent; seed-active joined shutdown shares bounded cleanup; inactive seed has no seed join and starts a fresh 60-second service context after both startup joins 7f85bc3ba05eb5b7ce3f5848295bc1f476d7660c49279fbf177a3403982efe43
+internal/host/claimservingvolume_linux.go servingParkFS.checkReapVolume authority C,J4 rev3=PROPOSED-seed; FS-R7/9; root; reads root-private per-retirement boot binding, requires its nonempty serial to match immutable source and current kernel volume, then checks the binding boot 80198a64328bcaf332275e3527f8bf2ca2011931b0b51fbdd97a0629a412052e
+internal/host/claimservingvolume_linux.go servingParkVolume authority C,J4 rev3=PROPOSED-seed; FS-R7/9; root; filesystem device number resolved through kernel sysfs only, never filesystem UUID or tenant payload d23bbf792ad2ae2b48a8bb72b8127ab36fe0d3eb577775eac877ff22e75c8f0b
+internal/host/claimservingvolume_linux.go servingParkVolumeAt authority C,J4 rev3=PROPOSED-seed; FS-R7/9; root; resolves sysfs /dev/block major:minor and containing disk for partitions, reads device/serial, accepts only normalized EBS volume IDs; missing/non-EBS serial grants no destructive rebind file:EvalSymlinks@75,file:ReadFile@386,file:Stat@241 d338711821e05b649ddb9af4674cb300e8781d71c72601f18e84351da51aa3a9