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>
24 files changed,
+723
−61
internal/host/agent.go+14 −18internal/host/agent_test.go+5 −0internal/host/claimfilesseed_isolated_rootrunner_linux_test.go+3 −0internal/host/claimfilesseed_restore_linux_test.go+2 −0internal/host/claimfilesseed_shutdown.go+35 −0internal/host/claimfilesseed_shutdown_test.go+25 −0internal/host/claimservingidentity.go+6 −0internal/host/claimservingpark_linux.go+24 −1internal/host/claimservingpark_linux_test.go+1 −0internal/host/claimservingpark_records.go+7 −5internal/host/claimservingpark_rootrunner_linux_test.go+26 −6internal/host/claimservingreboot_binding.go+1 −0internal/host/claimservingreboot_commit_linux.go+10 −0internal/host/claimservingreboot_linux.go+10 −1internal/host/claimservingreboot_prepare_linux_test.go+1 −0internal/host/claimservingrenumber_linux_test.go+112 −0internal/host/claimservingtopology_linux.go+7 −2internal/host/claimservingvolume_linux.go+66 −0internal/host/claimservingvolume_linux_test.go+120 −0internal/host/claimservingvolume_rootrunner_linux_test.go+180 −0internal/host/startup_largebox_linux_test.go+35 −3internal/provision/plan14_legacy_identity_test.go+6 −3internal/provision/plan14_root_reads_test.go+2 −2internal/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 |