carlos / platform Public

WIP files seed checkpoint 23 (not for merge)

0R retirement/reboot bindings, bounded park and verified expiry/pressure reaper; 0S and Task 3 private C8 staging; Task 2 classification; 0A guards; 1R settlement.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev 271ae939442b971c6079cc86e1d80edf415459d0 parent 1739c9b
39 files changed, +2454 −224
  • internal/activator/servingretry.go +35 −0
  • internal/activator/servingretry_test.go +70 −0
  • internal/host/claimconstruction.go +18 −1
  • internal/host/claimfilesseed_assembly_linux_test.go +54 −0
  • internal/host/claimfilesseed_guard.go +77 −0
  • internal/host/claimfilesseed_guard_linux_test.go +112 −0
  • internal/host/claimfilesseed_restore_linux_test.go +11 −2
  • internal/host/claimfilesseed_select.go +237 −0
  • internal/host/claimfilesseed_select_linux_test.go +204 −0
  • internal/host/claimfilesseed_stage.go +189 −0
  • internal/host/claimfilesseed_stage_linux_test.go +188 −0
  • internal/host/claimservingboot_linux.go +14 −0
  • internal/host/claimservingcollection.go +5 −0
  • internal/host/claimservinglegacy.go +5 −0
  • internal/host/claimservingmaintenance.go +151 −0
  • internal/host/claimservingmaintenance_linux.go +62 −0
  • internal/host/claimservingmaintenance_linux_test.go +132 −0
  • internal/host/claimservingnamespace_linux.go +21 −3
  • internal/host/claimservingpark_linux.go +14 −2
  • internal/host/claimservingpark_other.go +2 −0
  • internal/host/claimservingpark_records.go +19 −4
  • internal/host/claimservingpark_recovery_linux.go +23 −0
  • internal/host/claimservingpark_rootrunner_linux_test.go +85 −3
  • internal/host/claimservingpark_transport.go +8 −8
  • internal/host/claimservingreboot_binding.go +97 −0
  • internal/host/claimservingreboot_commit_linux.go +76 −0
  • internal/host/claimservingreboot_commit_linux_test.go +164 −0
  • internal/host/claimservingreboot_linux.go +2 −2
  • internal/host/claimservingreboot_prepare_linux.go +39 −8
  • internal/host/claimservingreboot_prepare_linux_test.go +17 −6
  • internal/host/claimservingtopology_linux.go +1 −1
  • internal/host/claimstop_run.go +1 −0
  • internal/host/claimwakeassembly.go +17 −1
  • internal/host/movesfiles.go +22 −3
  • internal/host/relocationflush.go +13 −26
  • internal/hostfiles/seed.go +28 −0
  • internal/hostfiles/seed_test.go +43 −0
  • internal/provision/plan9_b8_census_test.go +5 −0
  • internal/provision/testdata/plan14-root-reads-seed.tsv +193 −154

CI passed — run details

CI log
=== gate ===
cd launchctx && go vet ./... && go test ./... -count=1
ok  	amadan.net/carlos/platform.git/launchctx	0.012s
?   	amadan.net/carlos/platform.git/launchctx/controlname	[no test files]
?   	amadan.net/carlos/platform.git/launchctx/wireid	[no test files]
go vet ./...
gofmt -l .: clean
go test ./... -count=1 -timeout 30m
ok  	github.com/carlosframework/platform	304.167s
?   	github.com/carlosframework/platform/cmd/changelog	[no test files]
ok  	github.com/carlosframework/platform/cmd/console	2.721s
ok  	github.com/carlosframework/platform/cmd/moveprobe	92.232s
ok  	github.com/carlosframework/platform/cmd/zddprobe	0.703s
ok  	github.com/carlosframework/platform/internal/activator	249.441s
ok  	github.com/carlosframework/platform/internal/agentsock	0.021s
ok  	github.com/carlosframework/platform/internal/alerting	0.003s
ok  	github.com/carlosframework/platform/internal/analytics	0.022s
ok  	github.com/carlosframework/platform/internal/appconfig	2.499s
ok  	github.com/carlosframework/platform/internal/apperr	0.081s
ok  	github.com/carlosframework/platform/internal/applog	0.140s
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.640s
?   	github.com/carlosframework/platform/internal/boxid/boxidtest	[no test files]
?   	github.com/carlosframework/platform/internal/boxid/internal/signtest	[no test files]
ok  	github.com/carlosframework/platform/internal/boxvolumes	0.008s
ok  	github.com/carlosframework/platform/internal/caps	0.012s
ok  	github.com/carlosframework/platform/internal/certwarm	0.003s
ok  	github.com/carlosframework/platform/internal/cgroup	0.703s
ok  	github.com/carlosframework/platform/internal/changelog	0.311s
ok  	github.com/carlosframework/platform/internal/clamd	0.054s
?   	github.com/carlosframework/platform/internal/cliflag	[no test files]
ok  	github.com/carlosframework/platform/internal/console	393.210s
?   	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.972s
ok  	github.com/carlosframework/platform/internal/dbdoor/dbdoortest	0.010s
ok  	github.com/carlosframework/platform/internal/dbverify	0.004s
ok  	github.com/carlosframework/platform/internal/dnsrec	0.003s
ok  	github.com/carlosframework/platform/internal/docsite	0.005s
ok  	github.com/carlosframework/platform/internal/domains	2.460s
ok  	github.com/carlosframework/platform/internal/doorkey	0.006s
ok  	github.com/carlosframework/platform/internal/economics	0.004s
ok  	github.com/carlosframework/platform/internal/edge	26.779s
?   	github.com/carlosframework/platform/internal/edgeproof	[no test files]
ok  	github.com/carlosframework/platform/internal/edgesfile	0.008s
ok  	github.com/carlosframework/platform/internal/fleetapi	0.135s
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.010s
ok  	github.com/carlosframework/platform/internal/geoip	0.029s
ok  	github.com/carlosframework/platform/internal/host	160.903s
ok  	github.com/carlosframework/platform/internal/hostactors	0.530s
ok  	github.com/carlosframework/platform/internal/hostbox	2.511s
ok  	github.com/carlosframework/platform/internal/hostclaim	0.517s
ok  	github.com/carlosframework/platform/internal/hostevidence	0.014s
ok  	github.com/carlosframework/platform/internal/hostexec	5.103s
ok  	github.com/carlosframework/platform/internal/hostfence	0.037s
ok  	github.com/carlosframework/platform/internal/hostfiles	10.593s
ok  	github.com/carlosframework/platform/internal/hostfloor	0.014s
ok  	github.com/carlosframework/platform/internal/hostguard	1.017s
ok  	github.com/carlosframework/platform/internal/hostops	1.609s
?   	github.com/carlosframework/platform/internal/hostops/fencerelease	[no test files]
ok  	github.com/carlosframework/platform/internal/hostops/internal/fencerelease	0.016s
ok  	github.com/carlosframework/platform/internal/hostquarantine	0.016s
ok  	github.com/carlosframework/platform/internal/hostrelocate	0.453s
ok  	github.com/carlosframework/platform/internal/hostresolve	0.147s
ok  	github.com/carlosframework/platform/internal/hostwrite	0.014s
ok  	github.com/carlosframework/platform/internal/infraview	0.014s
ok  	github.com/carlosframework/platform/internal/instancectl	4.186s
ok  	github.com/carlosframework/platform/internal/instanceexec	2.748s
ok  	github.com/carlosframework/platform/internal/instancefs	0.021s
ok  	github.com/carlosframework/platform/internal/instancekey	0.016s
ok  	github.com/carlosframework/platform/internal/instanceupdates	0.023s
ok  	github.com/carlosframework/platform/internal/layout	1.546s
ok  	github.com/carlosframework/platform/internal/lease	9.810s
ok  	github.com/carlosframework/platform/internal/lease/leasetest	0.018s
ok  	github.com/carlosframework/platform/internal/ledger	0.014s
ok  	github.com/carlosframework/platform/internal/mail	0.158s
ok  	github.com/carlosframework/platform/internal/maintenance	0.079s
ok  	github.com/carlosframework/platform/internal/maintenanceexec	5.563s
ok  	github.com/carlosframework/platform/internal/maintenanceguard	0.082s
ok  	github.com/carlosframework/platform/internal/metrics	0.006s
ok  	github.com/carlosframework/platform/internal/moves	21.142s
ok  	github.com/carlosframework/platform/internal/moves/archive	0.783s
ok  	github.com/carlosframework/platform/internal/objstore	0.189s
ok  	github.com/carlosframework/platform/internal/ops	0.181s
ok  	github.com/carlosframework/platform/internal/park	0.209s
ok  	github.com/carlosframework/platform/internal/privatefile	0.003s
ok  	github.com/carlosframework/platform/internal/provision	17.881s
ok  	github.com/carlosframework/platform/internal/publicrelease	0.011s
?   	github.com/carlosframework/platform/internal/regionname	[no test files]
ok  	github.com/carlosframework/platform/internal/registry	4.898s
ok  	github.com/carlosframework/platform/internal/relay	7.234s
ok  	github.com/carlosframework/platform/internal/release	20.259s
ok  	github.com/carlosframework/platform/internal/rollout	3.876s
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.583s
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.570s
ok  	github.com/carlosframework/platform/internal/seeds	0.013s
ok  	github.com/carlosframework/platform/internal/sim/hostlife	10.153s
ok  	github.com/carlosframework/platform/internal/sim/litestreamfacts	3.989s
ok  	github.com/carlosframework/platform/internal/status	0.041s
ok  	github.com/carlosframework/platform/internal/steering	0.006s
ok  	github.com/carlosframework/platform/internal/stopevidence	0.243s
ok  	github.com/carlosframework/platform/internal/svcrole	0.004s
ok  	github.com/carlosframework/platform/internal/telemetry	0.008s
ok  	github.com/carlosframework/platform/internal/termfence	0.007s
ok  	github.com/carlosframework/platform/internal/tunnelclient	0.005s
ok  	github.com/carlosframework/platform/internal/unixdial	0.003s
ok  	github.com/carlosframework/platform/internal/updatewords	0.005s
ok  	github.com/carlosframework/platform/internal/usage	0.005s
ok  	github.com/carlosframework/platform/internal/version	0.002s
ok  	github.com/carlosframework/platform/internal/vet	1.582s
=== 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: 46858402 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.023s
=== RUN   TestT4StorageProbeMinio
2026/10/15 13:56:19 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18DEB884210AA12F, 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.023s
minio contract: PASS
diff --git a/internal/activator/servingretry.go b/internal/activator/servingretry.go
index c96af20ad..7e4aa3dd1 100644
--- a/internal/activator/servingretry.go
+++ b/internal/activator/servingretry.go
@@ -14,6 +14,7 @@ import (
)
type ServingRetries struct {
+ BeforeReserve func(context.Context, registry.Route, hostclaim.Observed) error
Claims release.Store
Box, Enrollment, BootID, ControllerID string
Now func() time.Time
@@ -82,6 +83,11 @@ func (d ServingRetries) Wrap(base *ClaimRuntime) (*ClaimRuntime, error) {
}
}
}
+ if o.Claim != nil && o.Claim.Op.Kind == hostclaim.KindServing && !p.Probe && d.BeforeReserve != nil {
+ if err := d.BeforeReserve(ctx, rt, o); err != nil {
+ return err
+ }
+ }
return base.Prerequisites(ctx, rt, o, p)
}
out.Admit = func(ctx context.Context, rt registry.Route, o hostclaim.Observed, p StartPolicy) (result hostclaim.Observed, resultErr error) {
@@ -96,6 +102,11 @@ func (d ServingRetries) Wrap(base *ClaimRuntime) (*ClaimRuntime, error) {
resultErr = errors.Join(resultErr, base.FailedAdmission(recovery, cleanupObservation, resultErr))
}
}()
+ if d.BeforeReserve != nil {
+ if err := d.BeforeReserve(ctx, rt, o); err != nil {
+ return hostclaim.Observed{}, err
+ }
+ }
inputs, err := d.Inputs(ctx, rt)
if err != nil || inputs == "" {
return hostclaim.Observed{}, errors.Join(err, hostwrite.ErrUnavailable)
@@ -105,6 +116,11 @@ func (d ServingRetries) Wrap(base *ClaimRuntime) (*ClaimRuntime, error) {
episode = r.EpisodeID
}
attempt := d.attempt(inputs, 0)
+ if d.BeforeReserve != nil {
+ if err := d.BeforeReserve(ctx, rt, o); err != nil {
+ return hostclaim.Observed{}, err
+ }
+ }
reserved, err := hostclaim.Update(ctx, d.Claims, o, hostclaim.ReserveWakeRetryAttempt(hostclaim.WakeRetryReservation{HolderIncarnation: o.Claim.Holder.Incarnation, EpisodeID: episode, Attempt: attempt, Now: d.Now()}))
if err != nil {
return hostclaim.Observed{}, err
@@ -236,3 +252,22 @@ func (d ServingRetries) Wrap(base *ClaimRuntime) (*ClaimRuntime, error) {
}
return &out, nil
}
+
+// SettleAbandoned is the seed rerun's settlement boundary. Its caller holds the
+// host exclusion. It spends no new attempt and remains usable below disk floor.
+func (d ServingRetries) SettleAbandoned(ctx context.Context, expected hostclaim.Observed) (hostclaim.Observed, error) {
+ if !d.valid() || ctx == nil || expected.Absent || expected.Claim == nil || expected.Claim.Holder == nil || expected.Claim.Op.Kind != hostclaim.KindServing || expected.Claim.Op.Exec != d.Box {
+ return hostclaim.Observed{}, hostwrite.ErrUnavailable
+ }
+ fresh, err := hostclaim.Read(ctx, d.Claims, expected.Host)
+ if err != nil {
+ return hostclaim.Observed{}, err
+ }
+ if fresh.Absent || fresh.ETag != expected.ETag || !hostclaim.SameWriterIdentity(fresh, expected) {
+ return hostclaim.Observed{}, hostbox.ErrChanged
+ }
+ if err := d.settle(ctx, fresh); err != nil {
+ return hostclaim.Observed{}, err
+ }
+ return hostclaim.Read(ctx, d.Claims, expected.Host)
+}
diff --git a/internal/activator/servingretry_test.go b/internal/activator/servingretry_test.go
index 99dc6f674..ce4e09e8b 100644
--- a/internal/activator/servingretry_test.go
+++ b/internal/activator/servingretry_test.go
@@ -11,6 +11,7 @@ import (
"github.com/carlosframework/platform/internal/hostbox"
"github.com/carlosframework/platform/internal/hostclaim"
+ "github.com/carlosframework/platform/internal/instancectl"
"github.com/carlosframework/platform/internal/registry"
"github.com/carlosframework/platform/internal/release"
)
@@ -167,3 +168,72 @@ func TestServingRetriesHealthBindsExactLaunch(t *testing.T) {
t.Fatal("exact health did not clear")
}
}
+
+func TestServingDiskRefusalSpendsNoRetry(t *testing.T) {
+ st, o, base, d, _, stops := servingRetryFixture(t)
+ effects := 0
+ base.Prerequisites = func(context.Context, registry.Route, hostclaim.Observed, StartPolicy) error { effects++; return nil }
+ d.BeforeReserve = func(context.Context, registry.Route, hostclaim.Observed) error { return instancectl.ErrDiskFull }
+ runtime, err := d.Wrap(base)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for i := 0; i < 12; i++ {
+ if err := runtime.Prerequisites(t.Context(), registry.Route{Host: o.Host}, o, StartPolicy{}); !errors.Is(err, instancectl.ErrDiskFull) {
+ t.Fatal(err)
+ }
+ if _, err := runtime.Admit(t.Context(), registry.Route{Host: o.Host}, o, StartPolicy{}); !errors.Is(err, instancectl.ErrDiskFull) {
+ t.Fatal(err)
+ }
+ }
+ after, err := hostclaim.Read(t.Context(), st, o.Host)
+ if err != nil || after.ETag != o.ETag || effects != 0 || *stops != 0 {
+ t.Fatal("low disk spent a retry or stopped a writer", err, effects, *stops)
+ }
+}
+
+func TestServingSeedRerunSettlesWithoutReservation(t *testing.T) {
+ for _, fault := range []string{"", "stop-fails", "stale"} {
+ t.Run(fault, func(t *testing.T) {
+ st, o, base, d, _, stops := servingRetryFixture(t)
+ runtime, err := d.Wrap(base)
+ if err != nil {
+ t.Fatal(err)
+ }
+ admitted, err := runtime.Admit(t.Context(), registry.Route{Host: o.Host}, o, StartPolicy{})
+ if err != nil {
+ t.Fatal(err)
+ }
+ expected := admitted.Claim.Holder.WakeRetry.AttemptsReserved
+ if fault == "stop-fails" {
+ d.StopAttempt = func(context.Context, hostclaim.Observed, hostclaim.WakeRetryAttempt) error { return hostbox.ErrChanged }
+ }
+ if fault == "stale" {
+ admitted.ETag = "stale"
+ }
+ d.BeforeReserve = func(context.Context, registry.Route, hostclaim.Observed) error {
+ t.Fatal("settlement required new preparation capacity")
+ return nil
+ }
+ settled, err := d.SettleAbandoned(t.Context(), admitted)
+ if fault != "" {
+ if err == nil {
+ t.Fatal("settled", fault)
+ }
+ return
+ }
+ r := settled.Claim.Holder.WakeRetry
+ if err != nil || r.State != "open" || r.Attempt != nil || r.AttemptsReserved != expected || *stops != 1 {
+ t.Fatal("rerun settlement", r, err, *stops)
+ }
+ again, err := d.SettleAbandoned(t.Context(), settled)
+ if err != nil || again.ETag != settled.ETag || *stops != 1 {
+ t.Fatal("settled replay changed state", err)
+ }
+ fresh, err := hostclaim.Read(t.Context(), st, o.Host)
+ if err != nil || fresh.ETag != settled.ETag {
+ t.Fatal(err)
+ }
+ })
+ }
+}
diff --git a/internal/host/claimconstruction.go b/internal/host/claimconstruction.go
index 3989ada12..eee73a4fe 100644
--- a/internal/host/claimconstruction.go
+++ b/internal/host/claimconstruction.go
@@ -295,7 +295,7 @@ func OpenProductionClaimAssembly(ctx context.Context, store release.Store, r Cla
inputs, admissionEvidence, publicationEvidence = dispatch, dispatch.AdmissionEvidence, dispatch.PublicationEvidence
publisher = dispatch
}
- wake := &ClaimWakeAssembly{Manager: r.Manager, Inputs: inputs, LeaseRegion: actor.Cloud.Region, Publication: publisher}
+ wake := &ClaimWakeAssembly{Ready: claimSocketReady, Manager: r.Manager, Inputs: inputs, LeaseRegion: actor.Cloud.Region, Publication: publisher}
wake.RequireServingEnrollment = servingEnabled
wake.Producers = func(ctx context.Context, o hostclaim.Observed) (*ClaimProducers, error) {
if o.Claim == nil || o.Claim.Holder == nil || controller == "" {
@@ -336,6 +336,13 @@ func OpenProductionClaimAssembly(ctx context.Context, store release.Store, r Cla
wake.Operations = groupD.Operations
wake.ProbeReady = groupD.ProbeReady
}
+ if servingEnabled {
+ retirement := &claimServingArtifactRetirement{Claims: store, Guard: guard, DataDir: r.DataDir, Box: actor.Box, Region: actor.Region, Root: hostbox.ServingParkClient{Call: rootJournalClient(controlsock.Path)}}
+ if err := bindServingArtifactRetirement(wake, retirement); err != nil {
+ return nil, err
+ }
+ }
+
wake.Retain = wake.retainCaptured
wake.ObserveLoss = func(o hostclaim.Observed, err error) { log.Printf("host claim lease loss for %s: %v", o.Host, err) }
wake.Retries = activator.ServingRetries{Claims: store, Box: actor.Box, Enrollment: actor.Enrollment, BootID: strings.TrimSpace(string(boot)), ControllerID: controller, Now: time.Now, NewID: newID}
@@ -360,6 +367,16 @@ func OpenProductionClaimAssembly(ctx context.Context, store release.Store, r Cla
return hex.EncodeToString(digest[:]), nil
}
if servingEnabled {
+ wake.ServingPrerequisites = servingFilesPrerequisites(store, owners.Serving.Files)
+ wake.Retries.BeforeReserve = func(ctx context.Context, rt registry.Route, o hostclaim.Observed) error {
+ if err := ctx.Err(); err != nil {
+ return err
+ }
+ if err := wake.ServingPrerequisites(ctx, rt, o); err != nil {
+ return err
+ }
+ return claimServingRoom(r.DataDir)
+ }
wake.Retries.Inputs = serving.launchDigest
}
// ClaimAssembly.Build binds Retries.StopAttempt to the attempt's persisted
diff --git a/internal/host/claimfilesseed_assembly_linux_test.go b/internal/host/claimfilesseed_assembly_linux_test.go
index 6d3adc7f5..dae7fd994 100644
--- a/internal/host/claimfilesseed_assembly_linux_test.go
+++ b/internal/host/claimfilesseed_assembly_linux_test.go
@@ -5,8 +5,13 @@ package host
import (
"context"
"errors"
+ "fmt"
+ "net"
+ "net/http"
"os"
+ "os/exec"
"path/filepath"
+ "sync/atomic"
"testing"
"time"
@@ -105,6 +110,55 @@ func TestClaimServingAssemblyWithoutMoves(t *testing.T) {
if data.LatestID(actor.Bucket, key) == "" {
t.Fatal("production C8 did not commit")
}
+ if assembly.Wake.BeforeLegacyInstall == nil || assembly.Wake.RecoverArtifacts == nil || assembly.Wake.BeforeInstallLocked == nil || assembly.Wake.Retries.BeforeReserve == nil {
+ t.Fatal("serving retirement or capacity not wired")
+ }
+ rootKey := fmt.Sprintf("%s/g-%s/0000/0000000000000001-0000000000000001.ltx", admitted.Claim.Holder.Location, in.Admission.NewGeneration)
+ syncs := 0
+ publication := ClaimPublication{Sync: func(context.Context) error { syncs++; data.Put(actor.Bucket, rootKey, plan9D5RootBody(t)); return nil }, Check: func(context.Context) error { return nil }}
+ receipt, err := assembly.Wake.Publication.PublishFenced(ctx, admitted, in, publication)
+ if err != nil || receipt.MarkerVersion == "" || receipt.Root.Key != rootKey || syncs != 1 {
+ t.Fatal("moves-off publication", receipt, err, syncs)
+ }
+ writer.PublicationEvidence = assembly.PublicationEvidence
+ writer.NewID = func() string { return "serving-published" }
+ published, err := writer.Published(ctx, admitted)
+ if err != nil || published.Claim.Holder.Generation != in.Admission.NewGeneration {
+ t.Fatal("moves-off publication evidence", err)
+ }
+ sockdir, err := os.MkdirTemp(os.Getenv("TMPDIR"), "seed-ready-")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(sockdir)
+ rt.Socket = filepath.Join(sockdir, "ready.sock")
+ listener, err := net.Listen("unix", rt.Socket)
+ if err != nil {
+ t.Fatal(err)
+ }
+ var requests atomic.Int32
+ server := &http.Server{Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { requests.Add(1); w.WriteHeader(http.StatusOK) })}
+ done := make(chan struct{})
+ go func() { defer close(done); _ = server.Serve(listener) }()
+ defer func() { server.Close(); <-done }()
+ process, err := os.FindProcess(os.Getpid())
+ if err != nil {
+ t.Fatal(err)
+ }
+ readyCtx, cancel := context.WithTimeout(ctx, time.Second)
+ err = assembly.Wake.Ready(readyCtx, &exec.Cmd{Process: process}, rt, in)
+ cancel()
+ if err != nil || requests.Load() != 1 {
+ t.Fatal("moves-off readiness", err, requests.Load())
+ }
+ wrong, _ := os.FindProcess(os.Getpid() + 1000000)
+ readyCtx, cancel = context.WithTimeout(ctx, 40*time.Millisecond)
+ err = assembly.Wake.Ready(readyCtx, &exec.Cmd{Process: wrong}, rt, in)
+ cancel()
+ if err == nil || requests.Load() != 1 {
+ t.Fatal("readiness accepted another process", err, requests.Load())
+ }
+
for _, kind := range []string{hostclaim.KindMoving, hostclaim.KindLanding, hostclaim.KindReverting, hostclaim.KindRestoring, hostclaim.KindRelocating} {
if err := plan9D5Call(dispatch, plan9D5Kind(kind)); err == nil {
t.Fatalf("moves-off accepted %s", kind)
diff --git a/internal/host/claimfilesseed_guard.go b/internal/host/claimfilesseed_guard.go
new file mode 100644
index 000000000..aede400c6
--- /dev/null
+++ b/internal/host/claimfilesseed_guard.go
@@ -0,0 +1,77 @@
+package host
+
+import (
+ "context"
+ "reflect"
+
+ "github.com/carlosframework/platform/internal/hostbox"
+ "github.com/carlosframework/platform/internal/hostclaim"
+ "github.com/carlosframework/platform/internal/hostfiles"
+ "github.com/carlosframework/platform/internal/registry"
+ "github.com/carlosframework/platform/internal/release"
+)
+
+// This boundary only reads current-version metadata. Normal C8 still proves
+// and validates the selected manifest after admission; presence is not proof.
+func (f *movesFiles) checkServingFilesEvidence(ctx context.Context, o hostclaim.Observed, rt registry.Route) error {
+ if f == nil || f.Data == nil || o.Claim == nil || o.Claim.Holder == nil {
+ return hostbox.ErrUnsupported
+ }
+ if _, err := checkFilesSeedLayout(f, rt, claimEmptyBootstrap(o.Claim.Holder)); err != nil {
+ return err
+ }
+ h := o.Claim.Holder
+ if claimEmptyBootstrap(h) {
+ return nil
+ }
+ bounded, cancel := context.WithTimeout(ctx, hostbox.RequestLimit)
+ defer cancel()
+ chain := movesFilesChain(h, f.HomeRegion, f.Region)
+ if h.Generation == "" {
+ chain = []string{""}
+ }
+ for _, gen := range chain {
+ version, err := f.manifestVersion(bounded, hostfiles.ManifestKey(movesFilesPrefix(h.Location, gen)))
+ if err != nil {
+ return err
+ }
+ if version != "" {
+ return nil
+ }
+ }
+ if f.Evidence != nil {
+ pin, found, err := f.Evidence(bounded, o)
+ if err != nil {
+ return err
+ }
+ if found && pin.NeverPublished() {
+ return nil
+ }
+ }
+ return hostfiles.ErrNoEvidence
+}
+
+func servingFilesPrerequisites(claims release.Store, files ClaimFilesManifest) func(context.Context, registry.Route, hostclaim.Observed) error {
+ return func(ctx context.Context, rt registry.Route, o hostclaim.Observed) error {
+ if o.Claim == nil || o.Claim.Op.Kind != hostclaim.KindServing {
+ return nil
+ }
+ f, ok := files.(interface {
+ checkServingFilesEvidence(context.Context, hostclaim.Observed, registry.Route) error
+ })
+ if !ok || claims == nil {
+ return hostbox.ErrUnsupported
+ }
+ if err := f.checkServingFilesEvidence(ctx, o, rt); err != nil {
+ return err
+ }
+ fresh, err := hostclaim.Read(ctx, claims, o.Host)
+ if err != nil {
+ return err
+ }
+ if fresh.Absent || fresh.ETag != o.ETag || !reflect.DeepEqual(fresh.Claim, o.Claim) {
+ return hostbox.ErrChanged
+ }
+ return ctx.Err()
+ }
+}
diff --git a/internal/host/claimfilesseed_guard_linux_test.go b/internal/host/claimfilesseed_guard_linux_test.go
new file mode 100644
index 000000000..d034d9db7
--- /dev/null
+++ b/internal/host/claimfilesseed_guard_linux_test.go
@@ -0,0 +1,112 @@
+//go:build linux
+
+package host
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "reflect"
+ "testing"
+ "time"
+
+ "github.com/carlosframework/platform/internal/hostclaim"
+ "github.com/carlosframework/platform/internal/hostfiles"
+ "github.com/carlosframework/platform/internal/lease"
+ "github.com/carlosframework/platform/internal/lease/leasetest"
+ "github.com/carlosframework/platform/internal/provision"
+ "github.com/carlosframework/platform/internal/registry"
+)
+
+type filesEvidenceMetadata struct {
+ lease.VersionedStore
+ lists, reads int
+}
+
+func (s *filesEvidenceMetadata) ListVersions(ctx context.Context, b, k string) ([]lease.ObjectVersion, error) {
+ s.lists++
+ return s.VersionedStore.ListVersions(ctx, b, k)
+}
+func (s *filesEvidenceMetadata) GetVersion(context.Context, string, string, string) ([]byte, error) {
+ s.reads++
+ return nil, hostfiles.ErrNoEvidence
+}
+func (s *filesEvidenceMetadata) OpenVersion(context.Context, string, string, string) (io.ReadCloser, error) {
+ s.reads++
+ return nil, hostfiles.ErrNoEvidence
+}
+
+func TestUnseededServingRequestsSpendNoRetry(t *testing.T) {
+ for _, state := range []string{"none", "attempting", "open", "failed"} {
+ t.Run(state, func(t *testing.T) {
+ f := newServingRootFixture(t, true)
+ count := 0
+ if state != "none" {
+ count = 1
+ }
+ if state == "failed" {
+ count = 5
+ }
+ for i := 0; i < count; i++ {
+ o := f.f.read()
+ attempt := hostclaim.WakeRetryAttempt{ID: fmt.Sprint("attempt-", i), Box: f.actor.Box, Enrollment: f.actor.Enrollment, BootID: "old-boot", ControllerID: "old-controller", InvocationID: fmt.Sprint("invocation-", i), LaunchInputs: "inputs"}
+ reserved, err := hostclaim.Update(t.Context(), f.f.claims, o, hostclaim.ReserveWakeRetryAttempt(hostclaim.WakeRetryReservation{HolderIncarnation: o.Claim.Holder.Incarnation, EpisodeID: "episode", Attempt: attempt, Now: f.f.now}))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if state != "attempting" {
+ if _, err := hostclaim.Update(t.Context(), f.f.claims, reserved, hostclaim.FailWakeRetryAttempt(hostclaim.WakeRetryResult{HolderIncarnation: o.Claim.Holder.Incarnation, EpisodeID: "episode", Attempt: attempt, Now: f.f.now})); err != nil {
+ t.Fatal(err)
+ }
+ f.f.now = f.f.now.Add(time.Minute)
+ }
+ }
+ bucket := leasetest.NewVersionedBucket()
+ bucket.AddBucket("data")
+ data := &filesEvidenceMetadata{VersionedStore: bucket}
+ provider := &movesFiles{Data: data, Bucket: "data", Box: f.actor.Box, Region: f.actor.Region, DataDir: f.f.guard.DataDir, EnvFile: func(host, db string) (claimEnvFile, error) {
+ return claimEnvFile{Dir: filepath.Dir(db), Name: host + ".env", Nested: true}, nil
+ }}
+ p := newServingPreparation(t, f, "boot", "controller", func(w *ClaimWakeAssembly) { w.ServingPrerequisites = servingFilesPrerequisites(f.f.claims, provider) })
+ before := f.f.read()
+ disk := servingBootSnapshot(t, f.disk.dir)
+ roots := servingBootSnapshot(t, f.rootDisk.dir)
+ for i := 0; i < 12; i++ {
+ f.f.now = f.f.now.Add(time.Minute)
+ err := p.runtime.Prerequisites(t.Context(), registry.Route{Host: before.Host, DBPath: provision.NestedDBPath(f.f.guard.DataDir, before.Host)}, before, recoveryPolicy)
+ if !errors.Is(err, hostfiles.ErrNoEvidence) {
+ t.Fatal(err)
+ }
+ }
+ after := f.f.read()
+ if !reflect.DeepEqual(before, after) || !reflect.DeepEqual(disk, servingBootSnapshot(t, f.disk.dir)) || !reflect.DeepEqual(roots, servingBootSnapshot(t, f.rootDisk.dir)) || len(p.events) != 0 {
+ t.Fatal("missing evidence spent retries or changed root state", p.events)
+ }
+ if data.reads != 0 || data.lists < 12 {
+ t.Fatal("guard did not use metadata only", data.lists, data.reads)
+ }
+ })
+ }
+}
+
+func TestServingEmptyBootstrapNeedsNoExistingInstanceDirectory(t *testing.T) {
+ f := newFilesSeedFixture(t)
+ if err := os.RemoveAll(filepath.Dir(f.route.DBPath)); err != nil {
+ t.Fatal(err)
+ }
+ o := f.observed
+ o.Claim.Holder.Generation = ""
+ o.Claim.Holder.Session = 0
+ o.Claim.Holder.Admissions = nil
+ o.Claim.Holder.WriterBox = ""
+ // Match the production empty-authority predicate, not absence of a manifest.
+ if !claimEmptyBootstrap(o.Claim.Holder) {
+ t.Fatal("fixture not bootstrap")
+ }
+ if err := f.files.checkServingFilesEvidence(t.Context(), o, f.route); err != nil {
+ t.Fatal(err)
+ }
+}
diff --git a/internal/host/claimfilesseed_restore_linux_test.go b/internal/host/claimfilesseed_restore_linux_test.go
index 617175f38..40e49260e 100644
--- a/internal/host/claimfilesseed_restore_linux_test.go
+++ b/internal/host/claimfilesseed_restore_linux_test.go
@@ -47,7 +47,7 @@ type servingPreparation struct {
afterEnroll func()
}
-func newServingPreparation(t *testing.T, f *servingRootFixture, boot, controller string) *servingPreparation {
+func newServingPreparation(t *testing.T, f *servingRootFixture, boot, controller string, configure ...func(*ClaimWakeAssembly)) *servingPreparation {
t.Helper()
p := &servingPreparation{f: f, local: claimStopLocal{Actor: f.actor, BootID: boot, ControllerID: controller, PID: 77, ProcessStart: controller + "-start"}}
f.f.peer = StopPeer{UID: 1001, PID: p.local.PID, ProcessStart: p.local.ProcessStart, BootID: boot, ControllerID: controller}
@@ -127,6 +127,9 @@ func newServingPreparation(t *testing.T, f *servingRootFixture, boot, controller
source := hostbox.WorkSource{Guard: f.f.guard, Box: f.actor.Box, Region: f.actor.Region, Now: func() time.Time { return f.f.now }, Policy: func(context.Context, *hostclaim.Claim, hostbox.Selection, hostwrite.Identity) error { return nil }, Local: func(context.Context, *hostclaim.Claim, hostbox.Selection) error { return nil }}
writer := hostbox.Writer{Data: f.f.data, Bucket: f.f.bucket, Claims: f.f.claims, Box: f.actor.Box, Region: f.actor.Region, AdmissionEvidence: p.owners.admissionEvidence}
var err error
+ for _, fn := range configure {
+ fn(w)
+ }
p.runtime, err = w.Build(t.Context(), source, writer)
if err != nil {
t.Fatal(err)
@@ -229,7 +232,7 @@ func runFilesSeedOrdinaryPreparation(t *testing.T, failure, restart string, moun
if err := os.Mkdir("/data", 0700); err != nil {
t.Fatal(err)
}
- if err := unix.Mount("tmpfs", "/data", "tmpfs", unix.MS_NOSUID|unix.MS_NODEV, "size=1m"); err != nil {
+ if err := unix.Mount("tmpfs", "/data", "tmpfs", unix.MS_NOSUID|unix.MS_NODEV, "size=2g"); err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
@@ -438,6 +441,9 @@ func runFilesSeedOrdinaryPreparation(t *testing.T, failure, restart string, moun
p.afterEnroll = cancel
case "restore-enospc":
p.afterEnroll = func() {
+ if err := unix.Mount("tmpfs", "/data", "tmpfs", unix.MS_REMOUNT|unix.MS_NOSUID|unix.MS_NODEV, "size=1m"); err != nil {
+ t.Fatal(err)
+ }
err := os.WriteFile(filepath.Join(filepath.Dir(f.db), ".disk-full-witness"), make([]byte, 1<<20), 0600)
if !errors.Is(err, syscall.ENOSPC) {
t.Fatalf("small private tmpfs did not fill: %v", err)
@@ -453,6 +459,9 @@ func runFilesSeedOrdinaryPreparation(t *testing.T, failure, restart string, moun
if err := os.Remove(filepath.Join(filepath.Dir(f.db), ".disk-full-witness")); err != nil {
t.Fatal(err)
}
+ if err := unix.Mount("tmpfs", "/data", "tmpfs", unix.MS_REMOUNT|unix.MS_NOSUID|unix.MS_NODEV, "size=2g"); err != nil {
+ t.Fatal(err)
+ }
}
if outputCancel != nil && !outputCancel.observed.Load() {
t.Fatalf("cancellation never observed real restore output: %v", restoreErr)
diff --git a/internal/host/claimfilesseed_select.go b/internal/host/claimfilesseed_select.go
new file mode 100644
index 000000000..5c4d2734c
--- /dev/null
+++ b/internal/host/claimfilesseed_select.go
@@ -0,0 +1,237 @@
+package host
+
+import (
+ "context"
+ "os"
+ "path/filepath"
+ "reflect"
+ "strings"
+
+ "github.com/carlosframework/platform/internal/hostbox"
+ "github.com/carlosframework/platform/internal/hostclaim"
+ "github.com/carlosframework/platform/internal/hostfiles"
+ "github.com/carlosframework/platform/internal/instancefs"
+ "github.com/carlosframework/platform/internal/provision"
+ "github.com/carlosframework/platform/internal/regionname"
+ "github.com/carlosframework/platform/internal/registry"
+ "github.com/carlosframework/platform/internal/release"
+)
+
+type filesSeedSelection struct {
+ State string
+ Observation hostclaim.Observed
+ Chain []string
+ Env claimEnvFile
+}
+
+func inspectFilesSeed(ctx context.Context, claims release.Store, f *movesFiles, o hostclaim.Observed, actor hostbox.Actor, rt *registry.Route) (out filesSeedSelection, resultErr error) {
+ if ctx != nil {
+ var cancel context.CancelFunc
+ ctx, cancel = context.WithTimeout(ctx, hostbox.RequestLimit)
+ defer cancel()
+ }
+ out.Observation = o
+ out.State = "blocked"
+ if ctx == nil || claims == nil || f == nil || f.Data == nil || f.EnvFile == nil || !actor.Eligible || actor.Quarantined || actor.Enrollment == "" || actor.Revision == 0 || actor.Box != f.Box || actor.Bucket != f.Bucket || !regionname.SameRegion(f.HomeRegion, actor.Region, f.Region) {
+ return out, hostbox.ErrUnsupported
+ }
+ check := func() error {
+ fresh, err := hostclaim.Read(ctx, claims, o.Host)
+ if err != nil {
+ return err
+ }
+ if fresh.ETag != o.ETag || fresh.Absent != o.Absent || !reflect.DeepEqual(fresh.Claim, o.Claim) {
+ return hostbox.ErrChanged
+ }
+ return ctx.Err()
+ }
+ if err := check(); err != nil {
+ return out, err
+ }
+ defer func() {
+ if resultErr == nil {
+ resultErr = check()
+ }
+ }()
+ if o.Absent || o.Claim == nil || o.Claim.Holder == nil || o.Claim.Op.Kind != hostclaim.KindServing || o.Claim.Op.Exec != actor.Box || o.Claim.Op.ID != "" || o.Claim.Op.Phase != "" {
+ out.State = "outOfScope"
+ return out, nil
+ }
+ if rt == nil || rt.Host != o.Host {
+ out.State = "noRow"
+ return out, hostbox.ErrChanged
+ }
+ if rt.Replica {
+ out.State = "replica"
+ return out, nil
+ }
+ if rt.Seed != "" || rt.Backing != "" || rt.Unit != "" || rt.IsSidecar() || rt.Tunnel != "" {
+ out.State = "unsupported"
+ return out, nil
+ }
+ if rt.Account != o.Claim.Holder.Account || rt.App != o.Claim.Holder.App {
+ return out, hostbox.ErrChanged
+ }
+ env, err := checkFilesSeedLayout(f, *rt, claimEmptyBootstrap(o.Claim.Holder))
+ if err != nil {
+ out.State = "layoutBlocked"
+ return out, err
+ }
+ out.Env = env
+ if _, pending := hostclaim.PendingMoveFenceLoss(o.Claim); pending {
+ return out, hostclaim.ErrFence
+ }
+ for key := range o.Claim.Op.Journal {
+ if strings.HasPrefix(key, "fence.") {
+ return out, hostclaim.ErrFence
+ }
+ }
+ h := o.Claim.Holder
+ if claimEmptyBootstrap(h) {
+ versions, err := f.Data.ListVersions(ctx, f.Bucket, strings.TrimSuffix(h.Location, "/")+"/")
+ if err != nil {
+ return out, err
+ }
+ if len(versions) != 0 {
+ return out, hostbox.ErrChanged
+ }
+ out.State = "emptyBootstrap"
+ return out, nil
+ }
+ chain, err := filesSeedChain(h, f.HomeRegion, f.Region, f.Box, o.Host)
+ if err != nil {
+ return out, err
+ }
+ out.Chain = chain
+ established, err := f.inspectSeedManifests(ctx, o, chain)
+ if err != nil {
+ return out, err
+ }
+ if established {
+ out.State = "established"
+ return out, nil
+ }
+ root, err := instancefs.OpenDirBeneath(env.Dir, o.Host)
+ if err != nil {
+ out.State = "sourceMissing"
+ return out, err
+ }
+ if err := root.Close(); err != nil {
+ return out, err
+ }
+ out.State = "eligible"
+ return out, nil
+}
+
+func checkFilesSeedLayout(f *movesFiles, rt registry.Route, emptyBootstrap bool) (claimEnvFile, error) {
+ var zero claimEnvFile
+ if f == nil || !filepath.IsAbs(f.DataDir) || rt.DBPath != provision.NestedDBPath(f.DataDir, rt.Host) {
+ return zero, hostbox.ErrChanged
+ }
+ env, err := f.EnvFile(rt.Host, rt.DBPath)
+ if err != nil {
+ return zero, err
+ }
+ if !env.Nested || env.Dir != provision.InstanceDir(f.DataDir, rt.Host) || env.Name != rt.Host+".env" {
+ return zero, hostbox.ErrChanged
+ }
+ parent, err := instancefs.OpenDirBeneath(f.DataDir, filepath.Join("instances", provision.InstanceID(rt.Host)))
+ if err != nil {
+ if emptyBootstrap && os.IsNotExist(err) {
+ return env, nil
+ }
+ return zero, err
+ }
+ if err := parent.Close(); err != nil {
+ return zero, err
+ }
+ return env, nil
+}
+
+// Retained admissions constrain the complete chain; they never prove an origin.
+// A gap or a foreign cold writer refuses even when no manifest is present.
+func filesSeedChain(h *hostclaim.Holder, home, region, box, host string) ([]string, error) {
+ if h == nil || h.WriterBox != box || h.Location != "instances/"+host || h.Incarnation == "" {
+ return nil, hostbox.ErrChanged
+ }
+ current := h.Generation
+ if current == "" {
+ if h.Session != 0 || len(h.Admissions) != 0 || h.Stamp == 0 {
+ return nil, hostbox.ErrChanged
+ }
+ return []string{""}, nil
+ }
+ seen := map[string]bool{}
+ var chain []string
+ session := h.Session
+ for current != "" {
+ if !movesFilesGeneration(current) || seen[current] {
+ return nil, hostbox.ErrChanged
+ }
+ seen[current] = true
+ chain = append(chain, current)
+ var entry *hostclaim.AdmissionRecord
+ for i := range h.Admissions {
+ a := &h.Admissions[i]
+ if a.Generation == current {
+ if entry != nil {
+ return nil, hostbox.ErrChanged
+ }
+ entry = a
+ }
+ }
+ if entry == nil || entry.Session != session || entry.Box != box || entry.Mode != "cold" || !regionname.SameRegion(home, entry.Region, region) || !regionname.SameRegion(home, entry.PredecessorRegion, region) {
+ return nil, hostbox.ErrChanged
+ }
+ current = entry.Predecessor
+ if current != "" {
+ found := false
+ for _, a := range h.Admissions {
+ if a.Generation == current && a.Session < session {
+ session = a.Session
+ found = true
+ break
+ }
+ }
+ if !found {
+ return nil, hostbox.ErrChanged
+ }
+ }
+ }
+ return append(chain, ""), nil
+}
+
+// Inspect the whole chain, including older entries after an established one.
+// A bad manifest is never absence and never grants permission to seed past it.
+func (f *movesFiles) inspectSeedManifests(ctx context.Context, o hostclaim.Observed, chain []string) (bool, error) {
+ found := false
+ for _, gen := range chain {
+ prefix := movesFilesPrefix(o.Claim.Holder.Location, gen)
+ key := hostfiles.ManifestKey(prefix)
+ version, err := f.manifestVersion(ctx, key)
+ if err != nil {
+ return false, err
+ }
+ if version == "" {
+ continue
+ }
+ found = true
+ m, present, err := hostfiles.Load(ctx, versionStore{data: f.Data, bucket: f.Bucket, key: key, version: version}, prefix)
+ if err != nil {
+ return false, err
+ }
+ if !present {
+ return false, hostfiles.ErrNoEvidence
+ }
+ if err := hostfiles.RestorableWithinCap(m); err != nil {
+ return false, err
+ }
+ if gen == "" {
+ return false, hostfiles.ErrOrigin
+ }
+ if _, err := f.movesFilesAllowed(ctx, o, hostclaim.Admission{}, o.Claim.Holder.Location, gen, m); err != nil {
+ return false, err
+ }
+ }
+ return found, nil
+}
diff --git a/internal/host/claimfilesseed_select_linux_test.go b/internal/host/claimfilesseed_select_linux_test.go
new file mode 100644
index 000000000..5cece3d53
--- /dev/null
+++ b/internal/host/claimfilesseed_select_linux_test.go
@@ -0,0 +1,204 @@
+//go:build linux
+
+package host
+
+import (
+ "encoding/json"
+ "errors"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/carlosframework/platform/internal/hostbox"
+ "github.com/carlosframework/platform/internal/hostclaim"
+ "github.com/carlosframework/platform/internal/hostfiles"
+ "github.com/carlosframework/platform/internal/lease/leasetest"
+ "github.com/carlosframework/platform/internal/provision"
+ "github.com/carlosframework/platform/internal/registry"
+ "github.com/carlosframework/platform/internal/release"
+)
+
+type filesSeedFixture struct {
+ t *testing.T
+ claims release.Store
+ data *leasetest.VersionedBucket
+ files *movesFiles
+ route registry.Route
+ observed hostclaim.Observed
+ actor hostbox.Actor
+}
+
+func newFilesSeedFixture(t *testing.T) *filesSeedFixture {
+ t.Helper()
+ t.Setenv("CARLOS_HOST_CLAIM_ORIGINS", "1")
+ t.Setenv("CARLOS_HOST_CLAIM", "1")
+ claims := release.NewDirStore(t.TempDir())
+ o, err := hostclaim.Read(t.Context(), claims, filesHost)
+ if err != nil {
+ t.Fatal(err)
+ }
+ nh := hostclaim.NewHolder{Family: "instance", Key: "apps/bab/jam/instances/" + filesHost + ".json", Account: "bab", App: "jam", Host: filesHost, Region: "ie", CreatedAt: filesCreated}
+ o, err = hostclaim.Update(t.Context(), claims, o, hostclaim.Adopt(nh, "box-1", nil, nil, "adopt", func() string { return "adopt" }))
+ if err != nil {
+ t.Fatal(err)
+ }
+ data := leasetest.NewVersionedBucket()
+ data.AddBucket("data")
+ dir := t.TempDir()
+ rt := registry.Route{Host: filesHost, Kind: "instance", Account: "bab", App: "jam", Hibernate: true, DBPath: provision.NestedDBPath(dir, filesHost), Socket: provision.NestedSocket(filesHost)}
+ env := filepath.Dir(rt.DBPath)
+ if err := os.MkdirAll(filepath.Join(env, filesHost), 0700); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.WriteFile(filepath.Join(env, filesHost+".env"), []byte("KEY=value\n"), 0600); err != nil {
+ t.Fatal(err)
+ }
+ f := &movesFiles{Files: casBucketStore{b: data, bucket: "data"}, Data: data, Bucket: "data", Box: "box-1", Region: "ie", HomeRegion: "ie", DataDir: dir, EnvFile: claimEnvFileFor(dir, testClaimRoutes{rows: map[string]registry.Route{filesHost: rt}}), Proof: ClaimOriginProof{Claims: claims, HomeRegion: "ie", Region: "ie"}}
+ return &filesSeedFixture{t: t, claims: claims, data: data, files: f, route: rt, observed: o, actor: hostbox.Actor{Box: "box-1", Region: "ie", HomeRegion: "ie", Bucket: "data", Enrollment: "enrolled", Revision: 1, Eligible: true}}
+}
+func (f *filesSeedFixture) admit(gen string) {
+ f.t.Helper()
+ o := f.observed
+ a := hostclaim.Admission{ObservedSession: o.Claim.Holder.Session, ObservedGeneration: o.Claim.Holder.Generation, Box: f.actor.Box, Region: f.actor.Region, PredecessorRegion: f.actor.Region, Mode: "cold", NewGeneration: gen}
+ var err error
+ f.observed, err = hostclaim.Update(f.t.Context(), f.claims, o, hostclaim.AdmitWriter(a))
+ if err != nil {
+ f.t.Fatal(err)
+ }
+}
+func (f *filesSeedFixture) inspect() (filesSeedSelection, error) {
+ return inspectFilesSeed(f.t.Context(), f.claims, f.files, f.observed, f.actor, &f.route)
+}
+
+func TestFilesSeedClassificationRealOrigins(t *testing.T) {
+ f := newFilesSeedFixture(t)
+ for _, gen := range []string{"", "first", "retry"} {
+ if gen != "" {
+ f.admit(gen)
+ }
+ before := f.observed.ETag
+ got, err := f.inspect()
+ if err != nil || got.State != "eligible" {
+ t.Fatal(gen, got, err)
+ }
+ fresh, err := hostclaim.Read(t.Context(), f.claims, filesHost)
+ if err != nil || fresh.ETag != before {
+ t.Fatal("classifier wrote claim", err)
+ }
+ }
+ if f.observed.Claim.Origins == nil {
+ t.Fatal("admissions did not record real origins")
+ }
+}
+
+func TestFilesSeedClassificationRefusals(t *testing.T) {
+ for _, fault := range []string{"missing-files", "flat", "symlink", "replica", "sidecar", "backing", "seed", "unit", "tunnel", "no-row", "stale", "foreign", "chain-gap", "foreign-predecessor", "cycle"} {
+ t.Run(fault, func(t *testing.T) {
+ f := newFilesSeedFixture(t)
+ f.admit("first")
+ f.admit("second")
+ var row *registry.Route = &f.route
+ switch fault {
+ case "missing-files":
+ if err := os.Remove(filepath.Join(filepath.Dir(f.route.DBPath), filesHost)); err != nil {
+ t.Fatal(err)
+ }
+ case "flat":
+ f.route.DBPath = filepath.Join(f.files.DataDir, filesHost+".db")
+ case "symlink":
+ path := filepath.Join(filepath.Dir(f.route.DBPath), filesHost)
+ if err := os.Remove(path); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.Symlink(t.TempDir(), path); err != nil {
+ t.Fatal(err)
+ }
+ case "replica":
+ f.route.Replica = true
+ case "sidecar":
+ f.route.Kind = "sidecar"
+ case "backing":
+ f.route.Backing = "foreign"
+ case "seed":
+ f.route.Seed = "seed"
+ case "unit":
+ f.route.Unit = "unit"
+ case "tunnel":
+ f.route.Tunnel = "tunnel"
+ case "no-row":
+ row = nil
+ case "stale":
+ old := f.observed
+ f.admit("third")
+ f.observed = old
+ default:
+ o, err := hostclaim.Update(t.Context(), f.claims, f.observed, func(c *hostclaim.Claim) error {
+ switch fault {
+ case "foreign":
+ c.Op.Exec = "other"
+ case "chain-gap":
+ c.Holder.Admissions = c.Holder.Admissions[1:]
+ case "foreign-predecessor":
+ c.Holder.Admissions[0].Box = "other"
+ case "cycle":
+ c.Holder.Admissions[0].Predecessor = "second"
+ }
+ return nil
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ f.observed = o
+ }
+ got, err := inspectFilesSeed(t.Context(), f.claims, f.files, f.observed, f.actor, row)
+ if err == nil && got.State == "eligible" {
+ t.Fatal("eligible", fault)
+ }
+ })
+ }
+}
+
+func TestFilesSeedExistingManifestNeverSeededPast(t *testing.T) {
+ f := newFilesSeedFixture(t)
+ f.admit("first")
+ f.admit("second")
+ prefix := movesFilesPrefix(f.observed.Claim.Holder.Location, "first")
+ p, err := hostfiles.NewFromCopy(f.files.Files, prefix, emptyManifest(), nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ p.SetOrigins([]string{prefix})
+ if _, err = p.Commit(t.Context()); err != nil {
+ t.Fatal(err)
+ }
+ got, err := f.inspect()
+ if err != nil || got.State != "established" {
+ t.Fatal(got, err)
+ }
+ // An unreadable older manifest still refuses after a newer valid one.
+ f.data.Put("data", movesFilesPrefix(f.observed.Claim.Holder.Location, "")+"manifest.json", []byte("broken"))
+ if _, err = f.inspect(); err == nil {
+ t.Fatal("seeded past malformed older manifest")
+ }
+}
+
+func TestFilesSeedManifestCapIncludesTombstonesAndSkips(t *testing.T) {
+ for _, entry := range []hostfiles.Entry{{Deleted: true, Seq: 1}, {Skipped: "too-large", Seq: 1}} {
+ f := newFilesSeedFixture(t)
+ f.admit("first")
+ prefix := movesFilesPrefix(f.observed.Claim.Holder.Location, "first")
+ m := emptyManifest()
+ m.Seq = 1
+ // Valid path components, beyond the cumulative path cap.
+ path := "a"
+ for len(path) < 5000 {
+ path += "/a"
+ }
+ m.Entries = map[string]hostfiles.Entry{path: entry}
+ body, _ := json.Marshal(m)
+ f.data.Put("data", prefix+"manifest.json", body)
+ if _, err := f.inspect(); !errors.Is(err, hostfiles.ErrTooDeep) {
+ t.Fatal("cap", err)
+ }
+ }
+}
diff --git a/internal/host/claimfilesseed_stage.go b/internal/host/claimfilesseed_stage.go
new file mode 100644
index 000000000..94e7e4625
--- /dev/null
+++ b/internal/host/claimfilesseed_stage.go
@@ -0,0 +1,189 @@
+package host
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "io"
+ "os"
+ "strings"
+
+ "github.com/carlosframework/platform/internal/hostbox"
+ "github.com/carlosframework/platform/internal/hostclaim"
+ "github.com/carlosframework/platform/internal/hostfiles"
+ "github.com/carlosframework/platform/internal/hostwrite"
+ "github.com/carlosframework/platform/internal/instancefs"
+ "github.com/carlosframework/platform/internal/lease"
+ "github.com/carlosframework/platform/internal/registry"
+ "github.com/carlosframework/platform/internal/release"
+)
+
+type filesSeedOperation struct {
+ ctx context.Context
+ claims release.Store
+ registry *registry.Registry
+ actor hostbox.Actor
+ files *movesFiles
+ selected filesSeedSelection
+ bound servingArtifactIdentity
+ stats hostfiles.SyncStats
+}
+
+// The private provider is freshly opened. No live provider or mutex-bearing
+// owner is copied or modified. The command binds admission before materializing.
+func newFilesSeedProvider(ctx context.Context, source *movesFilesSource, claims release.Store, data lease.VersionedStore, actor hostbox.Actor, reg *registry.Registry, selected filesSeedSelection) (*movesFiles, *filesSeedOperation, error) {
+ if ctx == nil || reg == nil || selected.State != "eligible" {
+ return nil, nil, hostbox.ErrUnsupported
+ }
+ opened, err := source.Open(ctx, claims, data, actor)
+ if err != nil {
+ return nil, nil, err
+ }
+ f, ok := opened.(*movesFiles)
+ if !ok {
+ return nil, nil, hostbox.ErrUnsupported
+ }
+ op := &filesSeedOperation{ctx: ctx, claims: claims, registry: reg, actor: actor, files: f, selected: selected}
+ f.Seed = op
+ return f, op, nil
+}
+
+func (s *filesSeedOperation) admitted(ctx context.Context, o hostclaim.Observed) error {
+ if s == nil || s.ctx == nil || s.ctx.Err() != nil || s.bound != (servingArtifactIdentity{}) || s.selected.State != "eligible" {
+ return hostbox.ErrChanged
+ }
+ current, err := servingWriterIdentity(o, s.actor.Box, s.actor.Region)
+ if err != nil {
+ return err
+ }
+ old := s.selected.Observation.Claim.Holder
+ if current.Host != s.selected.Observation.Host || current.Incarnation != old.Incarnation || current.Location != old.Location || current.Session <= old.Session || current.Generation == old.Generation {
+ return hostbox.ErrChanged
+ }
+ row, err := s.registry.Get(o.Host)
+ if err != nil {
+ return err
+ }
+ selected, err := inspectFilesSeed(ctx, s.claims, s.files, o, s.actor, row)
+ if err != nil {
+ return err
+ }
+ if selected.State != "eligible" {
+ return hostfiles.ErrManifestExists
+ }
+ s.bound = current
+ return nil
+}
+
+func (s *filesSeedOperation) check(ctx context.Context, o hostclaim.Observed, in ClaimLaunchInputs) error {
+ if s == nil || s.ctx == nil || s.ctx.Err() != nil || in.Empty || in.Prepared || in.Admission.NewGeneration == "" {
+ return hostbox.ErrChanged
+ }
+ current, err := servingWriterIdentity(o, s.actor.Box, s.actor.Region)
+ if err != nil || current != s.bound || current.Generation != in.Admission.NewGeneration {
+ return hostbox.ErrChanged
+ }
+ row, err := s.registry.Get(o.Host)
+ if err != nil {
+ return err
+ }
+ selected, err := inspectFilesSeed(ctx, s.claims, s.files, o, s.actor, row)
+ if err != nil {
+ return err
+ }
+ if selected.State != "eligible" {
+ return hostfiles.ErrManifestExists
+ }
+ _, err = s.files.proveOrigins(ctx, o, in.Admission, current.Location, []string{current.Generation})
+ return err
+}
+
+func (s *filesSeedOperation) stage(ctx context.Context, o hostclaim.Observed, in ClaimLaunchInputs, work hostwrite.Work, p *hostfiles.Publisher, env claimEnvFile) error {
+ if err := s.check(ctx, o, in); err != nil {
+ return err
+ }
+ var root *os.File
+ var wake hostfiles.WakeState
+ err := work.With(ctx, in.Identity, func() error {
+ var err error
+ root, err = instancefs.OpenDirBeneath(env.Dir, o.Host)
+ if err != nil {
+ return err
+ }
+ wake, err = hostfiles.ExportWake(s.registry, o.Host)
+ return err
+ })
+ if root != nil {
+ defer root.Close()
+ }
+ if err != nil {
+ return err
+ }
+ before, err := root.Stat()
+ if err != nil {
+ return err
+ }
+ s.stats, err = hostfiles.Seed(ctx, p, root, wake, nil)
+ if err != nil {
+ return err
+ }
+ return work.With(ctx, in.Identity, func() error {
+ fresh, err := instancefs.OpenDirBeneath(env.Dir, o.Host)
+ if err != nil {
+ return err
+ }
+ defer fresh.Close()
+ after, err := fresh.Stat()
+ if err != nil {
+ return err
+ }
+ if !os.SameFile(before, after) {
+ return hostbox.ErrChanged
+ }
+ return nil
+ })
+}
+
+type filesSeedCheckingStore struct {
+ release.Store
+ data lease.VersionedStore
+ bucket, prefix string
+}
+
+// A successful create needs no readback. On conflict, pin the current object
+// version and compare bounded bytes before the publisher may reference it.
+func (s filesSeedCheckingStore) Put(ctx context.Context, key string, body []byte, etag string) (string, error) {
+ result, err := s.Store.Put(ctx, key, body, etag)
+ if !errors.Is(err, release.ErrCASConflict) || etag != "" || !strings.HasPrefix(key, s.prefix+"objects/") {
+ return result, err
+ }
+ versions, e := s.data.ListVersions(ctx, s.bucket, key)
+ if e != nil {
+ return "", e
+ }
+ version := ""
+ for _, v := range versions {
+ if v.Key == key && v.IsLatest {
+ if version != "" || v.DeleteMarker || v.VersionID == "" || v.VersionID == "null" {
+ return "", hostfiles.ErrSuperseded
+ }
+ version = v.VersionID
+ }
+ }
+ if version == "" {
+ return "", hostfiles.ErrSuperseded
+ }
+ reader, e := s.data.OpenVersion(ctx, s.bucket, key, version)
+ if e != nil {
+ return "", e
+ }
+ actual, e := io.ReadAll(io.LimitReader(reader, int64(len(body))+1))
+ e = errors.Join(e, reader.Close())
+ if e != nil {
+ return "", e
+ }
+ if !bytes.Equal(actual, body) {
+ return "", hostfiles.ErrSuperseded
+ }
+ return result, err
+}
diff --git a/internal/host/claimfilesseed_stage_linux_test.go b/internal/host/claimfilesseed_stage_linux_test.go
new file mode 100644
index 000000000..f7b02b4d9
--- /dev/null
+++ b/internal/host/claimfilesseed_stage_linux_test.go
@@ -0,0 +1,188 @@
+//go:build linux
+
+package host
+
+import (
+ "context"
+ "errors"
+ "io"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+
+ "github.com/carlosframework/platform/internal/hostclaim"
+ "github.com/carlosframework/platform/internal/hostfiles"
+ "github.com/carlosframework/platform/internal/hostwrite"
+ "github.com/carlosframework/platform/internal/lease"
+ "github.com/carlosframework/platform/internal/lease/leasetest"
+ "github.com/carlosframework/platform/internal/registry"
+ "github.com/carlosframework/platform/internal/release"
+)
+
+func filesSeedPrivateFixture(t *testing.T) (*filesSeedFixture, *movesFiles, *filesSeedOperation, ClaimLaunchInputs) {
+ f := newFilesSeedFixture(t)
+ selected, err := f.inspect()
+ if err != nil {
+ t.Fatal(err)
+ }
+ reg, err := registry.Open(filepath.Join(t.TempDir(), "registry.db"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() { reg.Close() })
+ if err := reg.Upsert(f.route); err != nil {
+ t.Fatal(err)
+ }
+ if err := reg.SetNextDue(filesHost, 777); err != nil {
+ t.Fatal(err)
+ }
+ f.actor.Cloud.Region = "eu-west-1"
+ source := &movesFilesSource{DataDir: f.files.DataDir, Routes: testClaimRoutes{rows: map[string]registry.Route{filesHost: f.route}}, newFiles: func(context.Context, string, string) (release.Store, error) { return f.files.Files, nil }}
+ private, op, err := newFilesSeedProvider(t.Context(), source, f.claims, f.data, f.actor, reg, selected)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if strings.HasSuffix(t.Name(), "/origins-off") {
+ t.Setenv("CARLOS_HOST_CLAIM_ORIGINS", "")
+ }
+ f.admit("seed")
+ if err := op.admitted(t.Context(), f.observed); err != nil {
+ t.Fatal(err)
+ }
+ in := ClaimLaunchInputs{Identity: hostwrite.Identity{Config: "config", Release: "release"}, DBPath: f.route.DBPath, Admission: hostclaim.Admission{Box: f.actor.Box, Region: f.actor.Region, Mode: "cold", NewGeneration: "seed"}}
+ return f, private, op, in
+}
+
+func TestFilesSeedMaterializeUsesRealAdmittedPrefix(t *testing.T) {
+ f, private, op, in := filesSeedPrivateFixture(t)
+ path := filepath.Join(filepath.Dir(f.route.DBPath), filesHost, "tenant.txt")
+ if err := os.WriteFile(path, []byte("tenant bytes"), 0600); err != nil {
+ t.Fatal(err)
+ }
+ owners := plan9D5Fixture(t).owners
+ owners.Sources.Files = private
+ work := &filesFixture{t: t}
+ if err := owners.Materialize(t.Context(), f.observed, in, work); err != nil {
+ t.Fatal(err)
+ }
+ prefix := movesFilesPrefix(f.observed.Claim.Holder.Location, "seed")
+ m, found, err := hostfiles.Load(t.Context(), f.files.Files, prefix)
+ if err != nil || !found || m.Entries["tenant.txt"].Object == "" || len(m.Origins) != 1 || m.Origins[0] != prefix || op.stats.Published != 1 {
+ t.Fatal(m, err, op.stats)
+ }
+ ws, found, err := hostfiles.LoadWake(t.Context(), f.files.Files, prefix, m, m.Origins)
+ if err != nil || !found || ws.NextDue != 777 {
+ t.Fatal(ws, err)
+ }
+ env, err := hostfiles.ReadPlatform(t.Context(), f.files.Files, prefix, m, hostfiles.EnvItem, m.Origins)
+ if err != nil || string(env) != "KEY=value\n" {
+ t.Fatal(string(env), err)
+ }
+ version := f.data.LatestID("data", prefix+"manifest.json")
+ if err := private.Establish(t.Context(), f.observed, in, work); !errors.Is(err, hostfiles.ErrManifestExists) {
+ t.Fatal("armed replay", err)
+ }
+ if f.data.LatestID("data", prefix+"manifest.json") != version || f.files.Seed != nil {
+ t.Fatal("replay or ordinary provider changed")
+ }
+ // An ordinary successor carries the committed manifest without a seed hook.
+ f.admit("next")
+ in.Admission.NewGeneration = "next"
+ if err := f.files.Establish(t.Context(), f.observed, in, work); err != nil {
+ t.Fatal("ordinary successor", err)
+ }
+ if f.files.Seed != nil {
+ t.Fatal("seed escaped into ordinary provider")
+ }
+}
+
+type filesSeedReadCounter struct {
+ lease.VersionedStore
+ reads int
+}
+
+func (s *filesSeedReadCounter) OpenVersion(ctx context.Context, bucket, key, version string) (io.ReadCloser, error) {
+ if strings.Contains(key, "/objects/") {
+ s.reads++
+ }
+ return s.VersionedStore.OpenVersion(ctx, bucket, key, version)
+}
+
+type filesSeedConflictStore struct {
+ release.Store
+ data *leasetest.VersionedBucket
+ poison bool
+ injected bool
+}
+
+func (s *filesSeedConflictStore) Put(ctx context.Context, key string, body []byte, etag string) (string, error) {
+ if !s.injected && strings.Contains(key, "/objects/") {
+ s.injected = true
+ actual := body
+ if s.poison {
+ actual = []byte("poisoned bytes")
+ }
+ s.data.Put("data", key, actual)
+ }
+ return s.Store.Put(ctx, key, body, etag)
+}
+func TestFilesSeedCreateConflictChecksBytesBeforeCommit(t *testing.T) {
+ for _, kind := range []string{"created", "same", "poisoned"} {
+ t.Run(kind, func(t *testing.T) {
+ f, private, _, in := filesSeedPrivateFixture(t)
+ counter := &filesSeedReadCounter{VersionedStore: f.data}
+ private.Data = counter
+ if kind != "created" {
+ private.Files = &filesSeedConflictStore{Store: private.Files, data: f.data, poison: kind == "poisoned"}
+ }
+ err := private.Establish(t.Context(), f.observed, in, &filesFixture{t: t})
+ prefix := movesFilesPrefix(f.observed.Claim.Holder.Location, "seed")
+ if kind == "poisoned" {
+ if !errors.Is(err, hostfiles.ErrSuperseded) || f.data.LatestID("data", prefix+"manifest.json") != "" {
+ t.Fatal("poison committed", err)
+ }
+ } else if err != nil {
+ t.Fatal(err)
+ }
+ want := 1
+ if kind == "created" {
+ want = 0
+ }
+ if counter.reads != want {
+ t.Fatal("object read count", counter.reads, want)
+ }
+ })
+ }
+}
+
+func TestFilesSeedRefusesMissingOriginAndWrongTuple(t *testing.T) {
+ for _, fault := range []string{"generation", "empty", "prepared", "source-gone", "origins-off"} {
+ t.Run(fault, func(t *testing.T) {
+ f, private, _, in := filesSeedPrivateFixture(t)
+ switch fault {
+ case "generation":
+ in.Admission.NewGeneration = "foreign"
+ case "empty":
+ in.Empty = true
+ case "prepared":
+ in.Prepared = true
+ case "source-gone":
+ if err := os.Remove(filepath.Join(filepath.Dir(f.route.DBPath), filesHost)); err != nil {
+ t.Fatal(err)
+ }
+ case "origins-off":
+ if f.observed.Claim.Origins != nil {
+ t.Fatal("fixture recorded origins")
+ }
+
+ }
+ if err := private.Establish(t.Context(), f.observed, in, &filesFixture{t: t}); err == nil {
+ t.Fatal("accepted", fault)
+ }
+ if f.data.LatestID("data", movesFilesPrefix(f.observed.Claim.Holder.Location, in.Admission.NewGeneration)+"manifest.json") != "" {
+ t.Fatal("refusal committed")
+ }
+ })
+ }
+}
diff --git a/internal/host/claimservingboot_linux.go b/internal/host/claimservingboot_linux.go
index 158d47629..de8e72c5f 100644
--- a/internal/host/claimservingboot_linux.go
+++ b/internal/host/claimservingboot_linux.go
@@ -48,3 +48,17 @@ func (f *servingParkFS) checkBoot(expected string) error {
}
return nil
}
+
+func (f *servingParkFS) checkRecordBoot(r *servingParkRecord) error {
+ if r.Source.BootID != "" {
+ return f.checkBoot(r.Source.BootID)
+ }
+ b, err := servingParkReadBinding(f.state, "namespace")
+ if err != nil {
+ return err
+ }
+ if b != nil {
+ return hostbox.ErrChanged
+ }
+ return nil
+}
diff --git a/internal/host/claimservingcollection.go b/internal/host/claimservingcollection.go
index a2fe3bf7c..9585a66f8 100644
--- a/internal/host/claimservingcollection.go
+++ b/internal/host/claimservingcollection.go
@@ -43,6 +43,11 @@ func (l *servingParkLedger) collectClosedLocked(ctx context.Context, r *servingP
// but before the first retirement intent was saved.
if !consumed.Collected {
consumed.Collected = true
+ historical := servingParkRecord{Authorization: consumed.Authorization, Source: consumed.Source}
+ if err := l.bindRecord(&historical, true); err != nil {
+ return err
+ }
+ consumed.Authorization, consumed.Source = historical.Authorization, historical.Source
if err := l.write(servingLegacyKey("legacy", r.Authorization.Successor), consumed); err != nil {
return err
}
diff --git a/internal/host/claimservinglegacy.go b/internal/host/claimservinglegacy.go
index af42f56c0..0c26886b4 100644
--- a/internal/host/claimservinglegacy.go
+++ b/internal/host/claimservinglegacy.go
@@ -81,6 +81,11 @@ func (l *servingParkLedger) legacyConsumption(a servingArtifactIdentity) (*servi
if servingParkDecode(b, &c) != nil || !c.Authorization.Legacy || c.Authorization.Successor.Host != a.Host || c.Authorization.Successor.Box != a.Box || c.Authorization.Successor.Incarnation != a.Incarnation {
return nil, hostbox.ErrChanged
}
+ r := servingParkRecord{Authorization: c.Authorization, Source: c.Source}
+ if err := l.bindRecord(&r, false); err != nil {
+ return nil, err
+ }
+ c.Authorization, c.Source = r.Authorization, r.Source
return &c, nil
}
diff --git a/internal/host/claimservingmaintenance.go b/internal/host/claimservingmaintenance.go
new file mode 100644
index 000000000..aefaae140
--- /dev/null
+++ b/internal/host/claimservingmaintenance.go
@@ -0,0 +1,151 @@
+package host
+
+import (
+ "context"
+ "errors"
+ "log"
+ "sort"
+ "time"
+
+ "github.com/carlosframework/platform/internal/hostbox"
+ "github.com/carlosframework/platform/internal/instancectl"
+)
+
+// Select at most one page of the oldest complete parks. Incomplete records and
+// unknown contents never contribute reclaimable space. The namespace lock
+// serializes selection, slot ownership and identity-checked deletion.
+func (l *servingParkLedger) maintainParks(ctx context.Context, room func() error) error {
+ if room == nil {
+ return hostbox.ErrUnsupported
+ }
+ return l.withNamespace(ctx, func() error {
+ if err := ctx.Err(); err != nil {
+ return err
+ }
+ pressure := room()
+ if pressure != nil && !errors.Is(pressure, instancectl.ErrDiskFull) {
+ return pressure
+ }
+ var candidates []servingParkRecord
+ var failures []error
+ after := ""
+ for {
+ ids, more, err := l.recoveryIDs(ctx, after, servingParkRecoveryLimit)
+ if err != nil {
+ return err
+ }
+ for _, id := range ids {
+ if err := ctx.Err(); err != nil {
+ return err
+ }
+ r, err := l.read(id)
+ if err != nil {
+ if len(failures) < servingParkRecoveryLimit {
+ failures = append(failures, err)
+ }
+ continue
+ }
+ if r.Complete && !r.Closed && r.Disposition == servingParkRetain && (pressure != nil || !l.now().Before(r.ReapAfter)) {
+ candidates = append(candidates, r)
+ sort.Slice(candidates, func(i, j int) bool {
+ if candidates[i].ParkedAt.Equal(candidates[j].ParkedAt) {
+ return candidates[i].Authorization.ID < candidates[j].Authorization.ID
+ }
+ return candidates[i].ParkedAt.Before(candidates[j].ParkedAt)
+ })
+ if len(candidates) > servingParkRecoveryLimit {
+ candidates = candidates[:servingParkRecoveryLimit]
+ }
+ }
+ }
+ if !more {
+ break
+ }
+ after = ids[len(ids)-1]
+ }
+ for _, r := range candidates {
+ if err := ctx.Err(); err != nil {
+ return err
+ }
+ cause := "expiry"
+ if l.now().Before(r.ReapAfter) {
+ err := room()
+ if err == nil {
+ continue
+ }
+ if !errors.Is(err, instancectl.ErrDiskFull) {
+ return err
+ }
+ cause = "pressure"
+ }
+ err := func() error {
+ unlock, err := l.state.Lock(ctx, "slot-"+servingParkKey(r.Authorization.Successor.Box, r.Authorization.Successor.Host)+".lock")
+ if err != nil {
+ return err
+ }
+ defer unlock()
+ unlock, err = l.state.Lock(ctx, "retirement-"+r.Authorization.ID+".lock")
+ if err != nil {
+ return err
+ }
+ defer unlock()
+ if r.Deleting {
+ cause = r.Cause
+ }
+ if err := l.deleteLocked(ctx, &r, cause); err != nil {
+ return err
+ }
+ slot, err := l.slot(r.Authorization)
+ if err != nil {
+ return err
+ }
+ return l.collectClosedLocked(ctx, &r, slot)
+ }()
+ if err != nil {
+ failures = append(failures, err)
+ }
+ }
+ return errors.Join(failures...)
+ })
+}
+
+func (p *servingParkRuntime) Start(ctx context.Context) {
+ if p == nil {
+ return
+ }
+ p.mu.Lock()
+ if p.started || p.closing {
+ p.mu.Unlock()
+ return
+ }
+ p.started = true
+ p.mu.Unlock()
+ call, done, err := p.begin(ctx)
+ if err != nil {
+ return
+ }
+ go func() {
+ defer done()
+ ticker := time.NewTicker(time.Minute)
+ defer ticker.Stop()
+ after := ""
+ for {
+ bounded, cancel := context.WithTimeout(call, 10*time.Second)
+ if a, ok := p.authority.(interface {
+ maintainServingParks(context.Context, string) (string, error)
+ }); ok {
+ var err error
+ after, err = a.maintainServingParks(bounded, after)
+ if err != nil && call.Err() == nil {
+ log.Print(err)
+ }
+ }
+ cancel()
+ select {
+ case <-call.Done():
+ return
+ case <-ticker.C:
+ }
+ }
+ }()
+}
diff --git a/internal/host/claimservingmaintenance_linux.go b/internal/host/claimservingmaintenance_linux.go
new file mode 100644
index 000000000..852c6a2a3
--- /dev/null
+++ b/internal/host/claimservingmaintenance_linux.go
@@ -0,0 +1,62 @@
+//go:build linux
+
+package host
+
+import (
+ "context"
+ "errors"
+ "github.com/carlosframework/platform/internal/instancefs"
+ "os"
+
+ "github.com/carlosframework/platform/internal/hostbox"
+ "github.com/carlosframework/platform/internal/instancectl"
+ "golang.org/x/sys/unix"
+)
+
+func (f *servingParkFS) room() error {
+ parent, _, err := f.parent()
+ if err != nil {
+ return err
+ }
+ defer parent.Close()
+ return servingRoomFile(parent)
+}
+
+func servingRoomFile(parent *os.File) error {
+ var s unix.Statfs_t
+ if err := unix.Fstatfs(int(parent.Fd()), &s); err != nil {
+ return err
+ }
+ if s.Bsize <= 0 || s.Blocks > ^uint64(0)/uint64(s.Bsize) || s.Bavail > ^uint64(0)/uint64(s.Bsize) {
+ return hostbox.ErrChanged
+ }
+ if s.Bavail*uint64(s.Bsize) < instancectl.DiskFloor(s.Blocks*uint64(s.Bsize)) {
+ return instancectl.ErrDiskFull
+ }
+ return nil
+}
+
+func (p *servingParkRoot) maintainServingParks(ctx context.Context, after string) (string, error) {
+ page, err := p.recoverServingParks(ctx, after, servingParkRecoveryLimit)
+ for _, result := range page.Results {
+ err = errors.Join(err, result.Err)
+ }
+ next := ""
+ if page.More {
+ next = page.Next
+ }
+ fs, ok := p.ledger.fs.(*servingParkFS)
+ if !ok {
+ return next, hostbox.ErrUnsupported
+ }
+ return next, errors.Join(err, p.ledger.maintainParks(ctx, fs.room))
+}
+
+func claimServingRoom(data string) error {
+ parent, err := instancefs.OpenDirBeneath(data, "instances")
+ if err != nil {
+ return err
+ }
+ defer parent.Close()
+ return servingRoomFile(parent)
+}
diff --git a/internal/host/claimservingmaintenance_linux_test.go b/internal/host/claimservingmaintenance_linux_test.go
new file mode 100644
index 000000000..d43600254
--- /dev/null
+++ b/internal/host/claimservingmaintenance_linux_test.go
@@ -0,0 +1,132 @@
+//go:build linux
+
+package host
+
+import (
+ "context"
+ "errors"
+ "os"
+ "path/filepath"
+ "testing"
+ "time"
+
+ "github.com/carlosframework/platform/internal/instancectl"
+)
+
+func TestServingParkMaintenanceExpiryAndPressure(t *testing.T) {
+ for _, mode := range []string{"healthy", "expiry", "pressure", "unknown", "incomplete", "replacement"} {
+ t.Run(mode, func(t *testing.T) {
+ f := newServingParkDiskFixture(t)
+ a, receipts := f.evidence(1, true, "")
+ f.put("", "retained database")
+ if _, err := f.park(a, receipts); err != nil {
+ t.Fatal(err)
+ }
+ if mode != "incomplete" {
+ f.complete(a)
+ }
+ if mode == "expiry" {
+ f.now = f.now.Add(7 * 24 * time.Hour)
+ }
+ if mode == "replacement" {
+ path := f.path(a, "")
+ if err := os.Rename(path, path+"-original"); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.WriteFile(path, []byte("foreign"), 0600); err != nil {
+ t.Fatal(err)
+ }
+ }
+ calls := 0
+ room := func() error {
+ calls++
+ if mode == "unknown" {
+ return os.ErrPermission
+ }
+ if mode == "pressure" || mode == "incomplete" || mode == "replacement" {
+ return instancectl.ErrDiskFull
+ }
+ return nil
+ }
+ err := f.ledger.maintainParks(t.Context(), room)
+ if mode == "unknown" || mode == "replacement" {
+ if err == nil {
+ t.Fatal("expected refusal")
+ }
+ } else if err != nil {
+ t.Fatal(err)
+ }
+ r, err := f.ledger.read(a.ID)
+ if err != nil {
+ t.Fatal(err)
+ }
+ want := mode == "expiry" || mode == "pressure"
+ if r.Closed != want {
+ t.Fatalf("closed=%v want=%v", r.Closed, want)
+ }
+ if mode == "pressure" && (r.Cause != "pressure" || calls < 2) {
+ t.Fatal("pressure not remeasured", r.Cause, calls)
+ }
+ if !want {
+ if _, err := os.Stat(f.path(a, "")); err != nil {
+ t.Fatal("retained bytes lost", err)
+ }
+ }
+ })
+ }
+}
+
+func TestServingParkMaintenanceCancellation(t *testing.T) {
+ f := newServingParkDiskFixture(t)
+ ctx, cancel := context.WithCancel(t.Context())
+ cancel()
+ if err := f.ledger.maintainParks(ctx, func() error { t.Fatal("cancelled pass measured disk"); return nil }); !errors.Is(err, context.Canceled) {
+ t.Fatal(err)
+ }
+}
+
+type servingMaintenanceWait struct {
+ servingParkAuthority
+ entered chan struct{}
+}
+
+func (s servingMaintenanceWait) maintainServingParks(ctx context.Context, _ string) (string, error) {
+ close(s.entered)
+ <-ctx.Done()
+ return "", ctx.Err()
+}
+func TestServingParkMaintenanceShutdownJoins(t *testing.T) {
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+ s := &servingCloseStore{}
+ entered := make(chan struct{})
+ p := &servingParkRuntime{authority: servingMaintenanceWait{entered: entered}, state: s, ctx: ctx, cancel: cancel, idle: make(chan struct{})}
+ p.Start(t.Context())
+ select {
+ case <-entered:
+ case <-time.After(time.Second):
+ t.Fatal("maintenance did not start")
+ }
+ closeCtx, done := context.WithTimeout(t.Context(), time.Second)
+ defer done()
+ if err := p.Close(closeCtx); err != nil || !s.closed.Load() {
+ t.Fatal("maintenance did not join", err)
+ }
+}
+
+func TestServingCapacityFromAgentIdentity(t *testing.T) {
+ data := t.TempDir()
+ if err := os.Mkdir(filepath.Join(data, "instances"), 0700); err != nil {
+ t.Fatal(err)
+ }
+ if err := claimServingRoom(data); err != nil {
+ t.Fatal(err)
+ }
+ other := t.TempDir()
+ if err := os.Symlink(other, filepath.Join(data, "alias")); err != nil {
+ t.Fatal(err)
+ }
+ if err := claimServingRoom(filepath.Join(data, "alias")); err == nil {
+ t.Fatal("symlink data root accepted")
+ }
+}
diff --git a/internal/host/claimservingnamespace_linux.go b/internal/host/claimservingnamespace_linux.go
index cdaa6a090..2d83ca17e 100644
--- a/internal/host/claimservingnamespace_linux.go
+++ b/internal/host/claimservingnamespace_linux.go
@@ -22,6 +22,22 @@ type servingParkParent struct {
}
func (f *servingParkFS) namespaceParent() (servingParkParent, error) {
+ p, err := f.historicalNamespaceParent()
+ if err != nil {
+ return p, err
+ }
+ b, err := servingParkReadBinding(f.state, "namespace")
+ if err != nil || b == nil {
+ return p, err
+ }
+ if p.BootID != b.BeforeBoot || p.Parent != b.BeforeParent {
+ return p, hostbox.ErrChanged
+ }
+ p.BootID, p.Parent = b.BootID, b.Parent
+ return p, nil
+}
+
+func (f *servingParkFS) historicalNamespaceParent() (servingParkParent, error) {
var p servingParkParent
b, err := f.state.Read("namespace-parent.json")
if err != nil {
@@ -133,10 +149,9 @@ func (f *servingParkFS) recoverRecordedNamespace(ctx context.Context) error {
} else if !errors.Is(err, os.ErrNotExist) {
return err
}
- body, err := f.state.Read("namespace.json")
+ n, err := f.readNamespace()
if err == nil {
- var n servingParkNamespace
- if servingParkDecode(body, &n) != nil || n.Version != 1 || n.Parent != p.Parent || n.Root.Inode == 0 || n.Root.Device != id.Device || n.Root.Mount != id.Mount || f.checkBoot(n.BootID) != nil {
+ if n.Version != 1 || n.Parent != p.Parent || n.Root.Inode == 0 || n.Root.Device != id.Device || n.Root.Mount != id.Mount || f.checkBoot(n.BootID) != nil {
return hostbox.ErrChanged
}
root, rid, err := f.privateDir(parent, ".serving-parked")
@@ -168,6 +183,9 @@ func (p *servingParkRoot) prepareServingNamespace(ctx context.Context, a serving
return hostbox.ErrUnsupported
}
if _, err := p.ledger.read(a.ID); errors.Is(err, os.ErrNotExist) {
+ if err := fs.room(); err != nil {
+ return err
+ }
if _, err := p.ledger.capture(ctx, a, receipts, legacy); err != nil {
return err
}
diff --git a/internal/host/claimservingpark_linux.go b/internal/host/claimservingpark_linux.go
index bc4d23e87..98e2de183 100644
--- a/internal/host/claimservingpark_linux.go
+++ b/internal/host/claimservingpark_linux.go
@@ -131,7 +131,7 @@ func (f *servingParkFS) namespace() (*os.File, servingParkNamespace, error) {
return nil, n, err
}
defer parent.Close()
- b, err := f.state.Read("namespace.json")
+ n, err = f.readNamespace()
if errors.Is(err, os.ErrNotExist) {
p, e := f.namespaceParent()
if e == nil && (p.Parent != id || f.checkBoot(p.BootID) != nil) {
@@ -174,7 +174,7 @@ func (f *servingParkFS) namespace() (*os.File, servingParkNamespace, error) {
if err != nil {
return nil, n, err
}
- if servingParkDecode(b, &n) != nil || n.Version != 1 || n.Parent != id || f.checkBoot(n.BootID) != nil {
+ if n.Version != 1 || n.Parent != id || f.checkBoot(n.BootID) != nil {
return nil, n, hostbox.ErrChanged
}
if err = f.checkNamespaceQuarantine(n); err != nil {
@@ -349,6 +349,9 @@ func (f *servingParkFS) event(r *servingParkRecord) (*os.File, *os.File, error)
}
func (f *servingParkFS) Prepare(r *servingParkRecord, save func() error) error {
+ if err := f.checkRecordBoot(r); err != nil {
+ return err
+ }
if r.Prepared {
root, event, err := f.event(r)
if err != nil {
@@ -410,6 +413,9 @@ func (f *servingParkFS) Prepare(r *servingParkRecord, save func() error) error {
}
func (f *servingParkFS) Move(r *servingParkRecord, save func() error) error {
+ if err := f.checkRecordBoot(r); err != nil {
+ return err
+ }
root, event, err := f.event(r)
if err != nil {
return err
@@ -485,6 +491,9 @@ func (f *servingParkFS) Move(r *servingParkRecord, save func() error) error {
}
func (f *servingParkFS) Delete(r *servingParkRecord, save func() error) error {
+ if err := f.checkRecordBoot(r); err != nil {
+ return err
+ }
if r.EventRemoveIntent {
return f.removeEvent(r, save)
}
@@ -575,6 +584,9 @@ func (f *servingParkFS) Delete(r *servingParkRecord, save func() error) error {
}
func (f *servingParkFS) CheckParked(r *servingParkRecord) error {
+ if err := f.checkRecordBoot(r); err != nil {
+ return err
+ }
if r.EventRemoved {
if err := f.removedEvent(r); err != nil {
return err
diff --git a/internal/host/claimservingpark_other.go b/internal/host/claimservingpark_other.go
index b11e9006d..764651654 100644
--- a/internal/host/claimservingpark_other.go
+++ b/internal/host/claimservingpark_other.go
@@ -22,3 +22,5 @@ func (*servingParkFS) CheckParked(*servingParkRecord) error { return hostbox.Err
func newServingParkAuthority(ClaimRootHTTP, rootstate.Store, servingEmptyAuthority) (servingParkAuthority, error) {
return nil, hostbox.ErrUnsupported
}
+
+func claimServingRoom(string) error { return hostbox.ErrUnsupported }
diff --git a/internal/host/claimservingpark_records.go b/internal/host/claimservingpark_records.go
index 73357dd02..e4c06c985 100644
--- a/internal/host/claimservingpark_records.go
+++ b/internal/host/claimservingpark_records.go
@@ -21,10 +21,11 @@ import (
// Its callbacks are private implementation seams; requests cannot supply a
// source, disposition, timestamp or completion.
type servingParkLedger struct {
- state rootstate.Store
- fs servingParkFilesystem
- journal *hostbox.LaunchJournal
- now func() time.Time
+ historical bool
+ state rootstate.Store
+ fs servingParkFilesystem
+ journal *hostbox.LaunchJournal
+ now func() time.Time
}
type servingParkFile struct {
@@ -119,6 +120,17 @@ func (l *servingParkLedger) write(name string, value any) error {
}
func (l *servingParkLedger) read(id string) (servingParkRecord, error) {
+ r, err := l.readHistorical(id)
+ if err == nil {
+ err = l.bindRecord(&r, false)
+ }
+ if err == nil {
+ err = validateServingParkRecord(r)
+ }
+ return r, err
+}
+
+func (l *servingParkLedger) readHistorical(id string) (servingParkRecord, error) {
var r servingParkRecord
if len(id) != 64 {
return r, hostbox.ErrChanged
@@ -278,6 +290,9 @@ func validateServingParkRecord(r servingParkRecord) error {
}
func (l *servingParkLedger) save(r servingParkRecord) error {
+ if err := l.bindRecord(&r, true); err != nil {
+ return err
+ }
if err := validateServingParkRecord(r); err != nil {
return err
}
diff --git a/internal/host/claimservingpark_recovery_linux.go b/internal/host/claimservingpark_recovery_linux.go
index eff7eb2f0..ead2b1d13 100644
--- a/internal/host/claimservingpark_recovery_linux.go
+++ b/internal/host/claimservingpark_recovery_linux.go
@@ -46,6 +46,20 @@ func (p *servingParkRoot) recoverServingParks(ctx context.Context, after string,
if !ok {
return hostbox.ErrUnsupported
}
+ if err := fs.recoverRecordedNamespace(ctx); err == nil {
+ return nil
+ }
+ observation, err := fs.inspectRebootTopology(ctx)
+ if err != nil {
+ return err
+ }
+ binding := servingParkBootBinding{Version: 1, ID: "namespace", BeforeBoot: observation.Previous.BootID, BootID: observation.BootID, BeforeParent: observation.Previous.Parent, Parent: observation.CurrentParent}
+ if err := ctx.Err(); err != nil {
+ return err
+ }
+ if err := p.ledger.write("current-namespace.json", binding); err != nil {
+ return err
+ }
return fs.recoverRecordedNamespace(ctx)
})
if err != nil {
@@ -66,7 +80,16 @@ func (p *servingParkRoot) recoverServingParks(ctx context.Context, after string,
result := servingParkRecoveryResult{Host: "unknown", ID: id, Err: readErr}
if readErr == nil {
result.Host = r.Authorization.Successor.Host
+ fs, ok := p.ledger.fs.(*servingParkFS)
+ if ok && r.Source.BootID != "" && fs.checkBoot(r.Source.BootID) != nil {
+ result.Err = p.commitServingReboot(ctx, id)
+ if result.Err == nil {
+ r, result.Err = p.ledger.read(id)
+ }
+ }
switch {
+ case result.Err != nil:
+
case !r.Complete:
result.Err = p.recoverServingPark(ctx, r)
default:
diff --git a/internal/host/claimservingpark_rootrunner_linux_test.go b/internal/host/claimservingpark_rootrunner_linux_test.go
index edef0f3e5..24e11cfd5 100644
--- a/internal/host/claimservingpark_rootrunner_linux_test.go
+++ b/internal/host/claimservingpark_rootrunner_linux_test.go
@@ -29,6 +29,22 @@ 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.
+ return
+ }
+ // The runner's /tmp is a small RAM filesystem. Serving now enforces the
+ // real data-disk floor, so positive fixtures need the instance's disk.
+ data, err := os.MkdirTemp("/var/lib", "carlos-serving-test-")
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := os.RemoveAll(data); err != nil {
+ t.Error(err)
+ }
+ })
+ t.Setenv("TMPDIR", data)
}
// This includes loopback S3 and Unix-socket fixtures; it makes no remote calls.
@@ -364,8 +380,8 @@ type servingRebootWitness struct {
Record servingParkRecord
}
-// The runner reboots between these phases. This currently proves refusal and
-// byte preservation on changed topology; automatic reboot recovery is not built.
+// The runner reboots between these phases. Raw history stays unchanged while
+// fresh persistent evidence permits current-boot completion and reaping.
func TestRootRebootServingParkChangedMountPhase1(t *testing.T) {
servingRootRunner(t)
if err := os.Mkdir(servingRebootRoot, 0700); err != nil {
@@ -434,6 +450,8 @@ func TestRootRebootServingParkChangedMountPhase2(t *testing.T) {
if err != nil {
t.Fatal(err)
}
+ originalNamespace := append([]byte(nil), before...)
+
// Prove independent recognition after a real reboot before exercising the
// old-authority refusals below. No namespace or retirement is rewritten.
parentBefore, err := state.Read("namespace-parent.json")
@@ -556,6 +574,7 @@ func TestRootRebootServingParkChangedMountPhase2(t *testing.T) {
if err := (&servingParkLedger{state: state}).write("namespace.json", namespace); err != nil {
t.Fatal(err)
}
+ r.Source.BootID = strings.TrimSpace(string(boot))
controlErr := fs.CheckParked(&r)
if err := state.Write("namespace.json", before); err != nil {
t.Fatal(err)
@@ -563,6 +582,46 @@ func TestRootRebootServingParkChangedMountPhase2(t *testing.T) {
if controlErr != nil {
t.Fatal("numeric-reuse control has another identity mismatch", controlErr)
}
+ if err := state.Write("namespace.json", originalNamespace); err != nil {
+ t.Fatal(err)
+ }
+ ledger.fs, ledger.now = fs, time.Now
+ observe, err := fs.inspectRebootRecord(t.Context(), saved.Record.Authorization.ID)
+ if err != nil {
+ t.Fatal(err)
+ }
+ a := saved.Record.activeAuthorization()
+ active := &servingParkRebootAuthority{ClaimETag: "runner-claim", Actor: hostbox.Actor{Box: a.Successor.Box, Region: a.Successor.Region, Enrollment: "runner-enrollment", Revision: 1, Eligible: true}, Directory: observe.Source, Tags: a.TagsFrom}
+ if err := ledger.withNamespace(t.Context(), func() error { return ledger.commitRebootLocked(t.Context(), saved.Record, active) }); err != nil {
+ t.Fatal("commit after actual reboot", err)
+ }
+ current, err := ledger.read(a.ID)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if current.Source.BootID != strings.TrimSpace(string(boot)) || current.Source.Parent.Mount != actual.Mount {
+ t.Fatal("wrong committed topology")
+ }
+ if err := fs.CheckParked(&current); err != nil {
+ t.Fatal("current park check", err)
+ }
+ if err := ledger.completeLocked(t.Context(), a.ID, a.Successor); err != nil {
+ t.Fatal("complete", err)
+ }
+ current, err = ledger.read(a.ID)
+ if err != nil {
+ 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)
+ }
+ 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")
+
}
// Installation, census and stopped-writer permission remain fixture evidence.
@@ -582,7 +641,7 @@ func servingRunnerPrepareReboot(t *testing.T, fs *servingParkFS, r servingParkRe
if err := prepare(); err != nil {
t.Fatal("durable reboot preparation", err)
}
- name := "reboot-" + a.ID + ".json"
+ name := servingRebootPreparationName(a.ID, observe.Topology.BootID)
before, err := fs.state.Read(name)
if err != nil {
t.Fatal(err)
@@ -598,3 +657,26 @@ func servingRunnerPrepareReboot(t *testing.T, fs *servingParkFS, r servingParkRe
t.Fatal("preparation granted old-boot namespace authority")
}
}
+
+// Run the existing privileged acceptance batch and the actual reboot in one
+// runner allocation. Each batch test still runs by its original top-level name.
+func TestRootRebootServingParkAcceptancePhase1(t *testing.T) {
+ servingRootRunner(t)
+ binary, err := os.Executable()
+ if err != nil {
+ t.Fatal(err)
+ }
+ pattern := "^(TestRootServingParkAuthenticatedPeer|TestRootServingParkAuthenticatedHandover|TestRootServingParkStoppedWriterHandover|TestRootServingParkColdHandoverBoundary|TestRootServingParkNestedBindRestore|TestRootServingParkInstalledUnit|TestRootServingParkQuarantine|TestRootServingNamespaceStartup|TestRootServingParkUnrecordedReplacement|TestRootServingNamespaceUnrecordedStartup|TestRootServingParkDisplacedRoot|TestRootServingParkCollection)$"
+ cmd := exec.CommandContext(t.Context(), binary, "-test.run="+pattern, "-test.v", "-test.count=1", "-test.timeout=8m")
+ cmd.Env = os.Environ()
+ out, err := cmd.CombinedOutput()
+ t.Log(string(out))
+ if err != nil {
+ t.Error(err)
+ }
+ TestRootRebootServingParkChangedMountPhase1(t)
+}
+
+func TestRootRebootServingParkAcceptancePhase2(t *testing.T) {
+ TestRootRebootServingParkChangedMountPhase2(t)
+}
diff --git a/internal/host/claimservingpark_transport.go b/internal/host/claimservingpark_transport.go
index 1c8d2d806..9bf5d1145 100644
--- a/internal/host/claimservingpark_transport.go
+++ b/internal/host/claimservingpark_transport.go
@@ -32,14 +32,14 @@ type servingEmptyAuthority interface {
// Closing cancels and joins every request and stop-evidence retention before
// releasing private state. A timed-out join leaves the store open for retry.
type servingParkRuntime struct {
- authority servingParkAuthority
- state rootstate.Store
- ctx context.Context
- cancel context.CancelFunc
- mu sync.Mutex
- active int
- closing, closed bool
- idle chan struct{}
+ authority servingParkAuthority
+ state rootstate.Store
+ ctx context.Context
+ cancel context.CancelFunc
+ mu sync.Mutex
+ active int
+ closing, closed, started bool
+ idle chan struct{}
}
func withServingParkRoot(h ClaimRootHTTP, empty servingEmptyAuthority) (ClaimRootHTTP, error) {
diff --git a/internal/host/claimservingreboot_binding.go b/internal/host/claimservingreboot_binding.go
new file mode 100644
index 000000000..977d985d3
--- /dev/null
+++ b/internal/host/claimservingreboot_binding.go
@@ -0,0 +1,97 @@
+package host
+
+import (
+ "errors"
+ "os"
+
+ "github.com/carlosframework/platform/internal/hostbox"
+ "github.com/carlosframework/platform/internal/rootstate"
+)
+
+// A binding changes only boot-local mount numbers. Durable records keep their
+// original identities; progress writes translate back to those identities.
+type servingParkBootBinding struct {
+ Version int
+ ID, BeforeBoot, BootID string
+ BeforeParent, Parent servingArtifactDirectory
+}
+
+func servingParkReadBinding(state rootstate.Store, id string) (*servingParkBootBinding, error) {
+ body, err := state.Read("current-" + id + ".json")
+ if errors.Is(err, os.ErrNotExist) {
+ return nil, nil
+ }
+ if err != nil {
+ return nil, err
+ }
+ var b servingParkBootBinding
+ if servingParkDecode(body, &b) != nil || b.Version != 1 || b.ID != id || !validServingParkBootID(b.BeforeBoot) || !validServingParkBootID(b.BootID) || b.BeforeBoot == b.BootID || b.BeforeParent.Device == 0 || b.BeforeParent.Inode == 0 || b.BeforeParent.Mount == 0 || b.Parent.Device != b.BeforeParent.Device || b.Parent.Inode != b.BeforeParent.Inode || b.Parent.Mount == 0 {
+ return nil, hostbox.ErrChanged
+ }
+ return &b, nil
+}
+
+func (b servingParkBootBinding) directory(d *servingArtifactDirectory, reverse bool) error {
+ if *d == (servingArtifactDirectory{}) {
+ return nil
+ }
+ from, to := b.BeforeParent, b.Parent
+ if reverse {
+ from, to = to, from
+ }
+ if d.Device != from.Device || d.Mount != from.Mount {
+ return hostbox.ErrChanged
+ }
+ d.Mount = to.Mount
+ return nil
+}
+
+func (b servingParkBootBinding) record(r *servingParkRecord, reverse bool) error {
+ from, to := b.BeforeBoot, b.BootID
+ if reverse {
+ from, to = to, from
+ }
+ parent := b.BeforeParent
+ if reverse {
+ parent = b.Parent
+ }
+ if b.ID != "retirement-"+r.Authorization.ID || r.Source.BootID != from || r.Source.Parent != parent {
+ return hostbox.ErrChanged
+ }
+ r.Source.BootID = to
+ for _, d := range []*servingArtifactDirectory{&r.Source.Directory, &r.Source.Parent, &r.Namespace, &r.Event, &r.Authorization.Directory} {
+ if err := b.directory(d, reverse); err != nil {
+ return err
+ }
+ }
+ // Copy before changing slices shared with a caller's immutable snapshot.
+ r.Continuations = append([]servingParkContinuation(nil), r.Continuations...)
+ for i := range r.Continuations {
+ if err := b.directory(&r.Continuations[i].Authorization.Directory, reverse); err != nil {
+ return err
+ }
+ }
+ for i := range r.Source.Files {
+ f := &r.Source.Files[i]
+ if !f.Present {
+ continue
+ }
+ d := servingArtifactDirectory{Device: f.Device, Inode: f.Inode, Mount: f.Mount}
+ if err := b.directory(&d, reverse); err != nil {
+ return err
+ }
+ f.Mount = d.Mount
+ }
+ return nil
+}
+
+func (l *servingParkLedger) bindRecord(r *servingParkRecord, reverse bool) error {
+ if l.historical {
+ return nil
+ }
+ b, err := servingParkReadBinding(l.state, "retirement-"+r.Authorization.ID)
+ if err != nil || b == nil {
+ return err
+ }
+ return b.record(r, reverse)
+}
diff --git a/internal/host/claimservingreboot_commit_linux.go b/internal/host/claimservingreboot_commit_linux.go
new file mode 100644
index 000000000..5b22b16c9
--- /dev/null
+++ b/internal/host/claimservingreboot_commit_linux.go
@@ -0,0 +1,76 @@
+//go:build linux
+
+package host
+
+import (
+ "context"
+ "reflect"
+
+ "github.com/carlosframework/platform/internal/hostbox"
+)
+
+func (p *servingParkRoot) commitServingReboot(ctx context.Context, id string) error {
+ return p.prepareOrCommitServingReboot(ctx, id, true)
+}
+
+// The namespace binding is durable first. An unbound record still carries its
+// old boot and every filesystem effect refuses it, including reused mount IDs.
+// A lost reply at either write is replayed against fresh independent evidence.
+func (l *servingParkLedger) commitRebootLocked(ctx context.Context, saved servingParkRecord, active *servingParkRebootAuthority) error {
+ fresh, err := l.observeRebootPreparation(ctx, saved, active)
+ if err != nil {
+ return err
+ }
+ body, err := l.state.Read(servingRebootPreparationName(saved.Authorization.ID, fresh.Observation.Topology.BootID))
+ if err != nil {
+ return err
+ }
+ var prepared servingParkRebootPreparation
+ if servingParkDecode(body, &prepared) != nil || !reflect.DeepEqual(prepared, fresh) {
+ return hostbox.ErrChanged
+ }
+ t := fresh.Observation.Topology
+ namespace := servingParkBootBinding{Version: 1, ID: "namespace", BeforeBoot: t.Previous.BootID, BootID: t.BootID, BeforeParent: t.Previous.Parent, Parent: t.CurrentParent}
+ if err := ctx.Err(); err != nil {
+ return err
+ }
+ if err := l.write("current-namespace.json", namespace); err != nil {
+ return err
+ }
+ again, err := l.observeRebootPreparation(ctx, saved, active)
+ if err != nil {
+ return err
+ }
+ if !reflect.DeepEqual(fresh, again) {
+ return hostbox.ErrChanged
+ }
+ record := servingParkBootBinding{Version: 1, ID: "retirement-" + saved.Authorization.ID, BeforeBoot: saved.Source.BootID, BootID: t.BootID, BeforeParent: saved.Source.Parent, Parent: t.CurrentParent}
+ if err := ctx.Err(); err != nil {
+ return err
+ }
+ return l.write("current-"+record.ID+".json", record)
+}
+
+func (f *servingParkFS) readNamespace() (servingParkNamespace, error) {
+ var n servingParkNamespace
+ body, err := f.state.Read("namespace.json")
+ if err != nil {
+ return n, err
+ }
+ if servingParkDecode(body, &n) != nil {
+ return n, hostbox.ErrChanged
+ }
+ b, err := servingParkReadBinding(f.state, "namespace")
+ if err != nil || b == nil {
+ return n, err
+ }
+ if n.BootID != b.BeforeBoot || n.Parent != b.BeforeParent {
+ return n, hostbox.ErrChanged
+ }
+ n.BootID = b.BootID
+ if err = b.directory(&n.Parent, false); err != nil {
+ return n, err
+ }
+ err = b.directory(&n.Root, false)
+ return n, err
+}
diff --git a/internal/host/claimservingreboot_commit_linux_test.go b/internal/host/claimservingreboot_commit_linux_test.go
new file mode 100644
index 000000000..0fbdbccd7
--- /dev/null
+++ b/internal/host/claimservingreboot_commit_linux_test.go
@@ -0,0 +1,164 @@
+//go:build linux
+
+package host
+
+import (
+ "context"
+ "errors"
+ "os"
+ "reflect"
+ "testing"
+
+ "golang.org/x/sys/unix"
+)
+
+func TestServingParkRebootCommitResumes(t *testing.T) {
+ for _, phase := range []string{"rename-reply", "parked", "retagged", "complete", "source-gone"} {
+ t.Run(phase, func(t *testing.T) {
+ f, original := servingRebootPreparationFixture(t, phase)
+ id := original.Authorization.ID
+ before, err := f.disk.Read("retirement-" + id + ".json")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := f.p.commitServingReboot(t.Context(), id); err != nil {
+ t.Fatal(err)
+ }
+ after, _ := f.disk.Read("retirement-" + id + ".json")
+ if string(before) != string(after) {
+ t.Fatal("commit rewrote historical retirement")
+ }
+ fs := f.p.ledger.fs.(*servingParkFS)
+ if err := fs.recoverRecordedNamespace(t.Context()); err != nil {
+ t.Fatal(err)
+ }
+ r, err := f.p.ledger.read(id)
+ if err != nil || r.Source.BootID != servingOtherBoot {
+ t.Fatal("current authority", err)
+ }
+ if r.ParkedAt != original.ParkedAt || r.ReapAfter != original.ReapAfter {
+ t.Fatal("retention changed")
+ }
+ if r.Complete {
+ f.f.now = r.ReapAfter
+ if err := f.p.ledger.reapLocked(t.Context(), id); err != nil {
+ t.Fatal("reap", err)
+ }
+ } else {
+ f.tags(f.current.Stamp)
+ if err := f.p.recoverServingPark(t.Context(), r); err != nil {
+ t.Fatal("resume", err)
+ }
+ }
+ raw, err := f.p.ledger.readHistorical(id)
+ if err != nil || raw.Source.BootID != original.Source.BootID || raw.Authorization != original.Authorization {
+ t.Fatal("history changed", err)
+ }
+ })
+ }
+}
+
+func TestServingParkRebootCommitLostWrite(t *testing.T) {
+ for _, before := range []bool{true, false} {
+ for at := 1; at <= 3; at++ {
+ f, r := servingRebootPreparationFixture(t, "complete")
+ fault := &servingParkFaultStore{Store: f.disk, failAt: at, before: before}
+ f.p.ledger.state = fault
+ fs := f.p.ledger.fs.(*servingParkFS)
+ fs.state = fault
+ err := f.p.commitServingReboot(t.Context(), r.Authorization.ID)
+ if !errors.Is(err, os.ErrPermission) {
+ t.Fatalf("%v/%d: %v", before, at, err)
+ }
+ f.p.ledger.state = f.disk
+ fs.state = f.disk
+ if err := f.p.commitServingReboot(t.Context(), r.Authorization.ID); err != nil {
+ t.Fatal("retry", err)
+ }
+ current, err := f.p.ledger.read(r.Authorization.ID)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := fs.CheckParked(&current); err != nil {
+ t.Fatal(err)
+ }
+ }
+ }
+}
+
+func TestServingParkRebootCommitRefusesChangedEvidence(t *testing.T) {
+ for _, fault := range []string{"file", "slot", "claim", "census"} {
+ t.Run(fault, func(t *testing.T) {
+ f, r := servingRebootPreparationFixture(t, "parked")
+ if err := f.p.prepareServingReboot(t.Context(), r.Authorization.ID); err != nil {
+ t.Fatal(err)
+ }
+ switch fault {
+ case "file":
+ fs := f.p.ledger.fs.(*servingParkFS)
+ fs.handle = func(*os.File) (servingParkPersistentDirectory, error) {
+ return servingParkPersistentDirectory{}, unix.EIO
+ }
+ case "slot":
+ s, _ := f.p.ledger.slot(r.Authorization)
+ s.ID = ""
+ if err := f.p.ledger.write("slot-"+servingParkKey(s.Box, s.Host)+".json", s); err != nil {
+ t.Fatal(err)
+ }
+ case "claim":
+ f.tags(f.current.Stamp)
+ case "census":
+ f.p.root.RequireNewWork = func(_ context.Context) error { return unix.EPERM }
+ }
+ before := servingBootSnapshot(t, f.disk.dir)
+ if err := f.p.commitServingReboot(t.Context(), r.Authorization.ID); err == nil {
+ t.Fatal("accepted changed evidence")
+ }
+ if !reflect.DeepEqual(before, servingBootSnapshot(t, f.disk.dir)) {
+ t.Fatal("refusal wrote authority")
+ }
+ })
+ }
+}
+
+func TestServingParkRebootCommitSecondBoot(t *testing.T) {
+ f, r := servingRebootPreparationFixture(t, "complete")
+ id := r.Authorization.ID
+ if err := f.p.commitServingReboot(t.Context(), id); err != nil {
+ t.Fatal(err)
+ }
+ fs := f.p.ledger.fs.(*servingParkFS)
+ nextBoot := "11234567-89ab-4cde-8fab-0123456789ab"
+ fs.boot = func() (string, error) { return nextBoot, nil }
+ if err := f.p.commitServingReboot(t.Context(), id); err != nil {
+ t.Fatal("second boot", err)
+ }
+ current, err := f.p.ledger.read(id)
+ if err != nil || current.Source.BootID != nextBoot {
+ t.Fatal(err)
+ }
+ if err := fs.CheckParked(&current); err != nil {
+ t.Fatal(err)
+ }
+ historical, err := f.p.ledger.readHistorical(id)
+ if err != nil || !reflect.DeepEqual(historical, r) {
+ t.Fatal("history changed", err)
+ }
+}
+
+func TestServingParkRebootStartupCommits(t *testing.T) {
+ f, r := servingRebootPreparationFixture(t, "retagged")
+ page, err := f.p.recoverServingParks(t.Context(), "", servingParkRecoveryLimit)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, result := range page.Results {
+ if result.Err != nil {
+ t.Fatal(result)
+ }
+ }
+ current, err := f.p.ledger.read(r.Authorization.ID)
+ if err != nil || !current.Complete || current.Source.BootID != servingOtherBoot {
+ t.Fatal(current, err)
+ }
+}
diff --git a/internal/host/claimservingreboot_linux.go b/internal/host/claimservingreboot_linux.go
index a1b05a5f7..dcb3e6565 100644
--- a/internal/host/claimservingreboot_linux.go
+++ b/internal/host/claimservingreboot_linux.go
@@ -49,12 +49,12 @@ func (f *servingParkFS) observeRebootRecord(ctx context.Context, id string) (ser
if err != nil {
return out, err
}
- r, err := (&servingParkLedger{state: f.state}).read(id)
+ r, err := (&servingParkLedger{state: f.state}).readHistorical(id)
if err != nil {
return out, err
}
n := topology.Previous
- if r.Source.BootID != n.BootID || !r.Source.Persistent.valid() || r.Source.Parent != n.Parent || r.Source.Persistent.Filesystem != n.Topology.Parent.Filesystem || r.Prepared && r.Namespace != n.Root {
+ 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) {
return out, hostbox.ErrChanged
}
parent, pid, err := f.parent()
diff --git a/internal/host/claimservingreboot_prepare_linux.go b/internal/host/claimservingreboot_prepare_linux.go
index 8ae44800b..14c1015e2 100644
--- a/internal/host/claimservingreboot_prepare_linux.go
+++ b/internal/host/claimservingreboot_prepare_linux.go
@@ -34,25 +34,39 @@ type servingParkRebootAuthority struct {
// current installation, claim, census, stopped history and unlaunched successor.
// A completed park remains inspectable after the host's installation is gone.
func (p *servingParkRoot) prepareServingReboot(ctx context.Context, id string) error {
+ return p.prepareOrCommitServingReboot(ctx, id, false)
+}
+
+func (p *servingParkRoot) prepareOrCommitServingReboot(ctx context.Context, id string, commit bool) error {
if ctx == nil || p == nil || p.ledger == nil || p.ledger.state == nil {
return hostbox.ErrUnsupported
}
if err := ctx.Err(); err != nil {
return err
}
- saved, err := p.ledger.read(id)
+ copyLedger := *p.ledger
+ copyLedger.historical = true
+ saved, err := p.ledger.readHistorical(id)
if err != nil {
return err
}
prepare := func(active *servingParkRebootAuthority) error {
- return p.ledger.prepareRebootLocked(ctx, saved, active)
+ return copyLedger.prepareRebootLocked(ctx, saved, active)
}
if saved.Complete {
- return p.ledger.withNamespace(ctx, func() error { return prepare(nil) })
+ return p.ledger.withNamespace(ctx, func() error {
+ if err := prepare(nil); err != nil {
+ return err
+ }
+ if commit {
+ return copyLedger.commitRebootLocked(ctx, saved, nil)
+ }
+ return nil
+ })
}
current := saved.activeAuthorization().Successor
return p.withRootInstallation(ctx, current, ctx.Err, func(o hostclaim.Observed, view hostguard.FenceView, actor hostbox.Actor, dir servingArtifactDirectory, tags uint64) error {
- r, err := p.ledger.read(id)
+ r, err := p.ledger.readHistorical(id)
if err != nil || !reflect.DeepEqual(r, saved) || r.Complete {
return hostbox.ErrChanged
}
@@ -60,7 +74,7 @@ func (p *servingParkRoot) prepareServingReboot(ctx context.Context, id string) e
if tags != active.TagsFrom && tags != current.Stamp {
return hostbox.ErrChanged
}
- if err := p.validateServingRecoveryAuthority(ctx, o, view, actor, r); err != nil {
+ if err := p.validateServingRebootAuthority(ctx, o, view, actor, r); err != nil {
return err
}
if err := prepare(&servingParkRebootAuthority{ClaimETag: o.ETag, Actor: actor, Directory: dir, Tags: tags}); err != nil {
@@ -83,10 +97,16 @@ func (p *servingParkRoot) prepareServingReboot(ctx context.Context, id string) e
if err := p.root.RequireNewWork(ctx); err != nil {
return err
}
- if err := p.validateServingRecoveryAuthority(ctx, fresh, view, actor, r); err != nil {
+ if err := p.validateServingRebootAuthority(ctx, fresh, view, actor, r); err != nil {
+ return err
+ }
+ if err := ctx.Err(); err != nil {
return err
}
- return ctx.Err()
+ if commit {
+ return copyLedger.commitRebootLocked(ctx, r, &servingParkRebootAuthority{ClaimETag: o.ETag, Actor: actor, Directory: dir, Tags: tags})
+ }
+ return nil
})
}
@@ -108,7 +128,7 @@ func (l *servingParkLedger) prepareRebootLocked(ctx context.Context, saved servi
if err != nil {
return err
}
- name := "reboot-" + a.ID + ".json"
+ name := servingRebootPreparationName(a.ID, first.Observation.Topology.BootID)
body, err := l.state.Read(name)
if err == nil {
var prior servingParkRebootPreparation
@@ -190,3 +210,14 @@ func (l *servingParkLedger) observeRebootPreparation(ctx context.Context, saved
}
return servingParkRebootPreparation{Version: 1, Observation: observation, Slot: slot, Legacy: legacy, Active: active}, nil
}
+
+func (p *servingParkRoot) validateServingRebootAuthority(ctx context.Context, o hostclaim.Observed, view hostguard.FenceView, actor hostbox.Actor, r servingParkRecord) error {
+ current, ledger := *p, *p.ledger
+ ledger.historical = true
+ current.ledger = &ledger
+ return current.validateServingRecoveryAuthority(ctx, o, view, actor, r)
+}
+
+func servingRebootPreparationName(id, boot string) string {
+ return "reboot-" + id + "-" + boot + ".json"
+}
diff --git a/internal/host/claimservingreboot_prepare_linux_test.go b/internal/host/claimservingreboot_prepare_linux_test.go
index 6fe10edd4..3ad6dbf9e 100644
--- a/internal/host/claimservingreboot_prepare_linux_test.go
+++ b/internal/host/claimservingreboot_prepare_linux_test.go
@@ -68,7 +68,7 @@ func TestServingParkRebootPreparationPreservesHistory(t *testing.T) {
before := servingBootSnapshot(t, f.disk.dir)
claim := f.f.read()
root := servingBootSnapshot(t, f.rootDisk.dir)
- name := "reboot-" + r.Authorization.ID + ".json"
+ name := servingRebootPreparationName(r.Authorization.ID, servingOtherBoot)
f.trace = nil
if err := f.p.prepareServingReboot(t.Context(), r.Authorization.ID); err != nil {
t.Fatal(err)
@@ -222,7 +222,7 @@ func TestServingParkRebootPreparationLegacyConsumption(t *testing.T) {
}
after := servingBootSnapshot(t, f.disk.dir)
if fault == "" {
- delete(after, "reboot-"+r.Authorization.ID+".json")
+ delete(after, servingRebootPreparationName(r.Authorization.ID, servingOtherBoot))
}
if !reflect.DeepEqual(before, after) {
t.Fatal("changed permanent history")
@@ -246,7 +246,7 @@ func TestServingParkRebootPreparationLostWrite(t *testing.T) {
t.Fatal("retry", err)
}
after := servingBootSnapshot(t, f.disk.dir)
- delete(after, "reboot-"+r.Authorization.ID+".json")
+ delete(after, servingRebootPreparationName(r.Authorization.ID, servingOtherBoot))
if !reflect.DeepEqual(original, after) {
t.Fatal("write retry changed history")
}
@@ -259,7 +259,7 @@ func TestServingParkRebootPreparationNeverRefreshesStaleEvidence(t *testing.T) {
t.Run(fault, func(t *testing.T) {
f, r := servingRebootPreparationFixture(t, "parked")
id := r.Authorization.ID
- name := "reboot-" + id + ".json"
+ name := servingRebootPreparationName(id, servingOtherBoot)
if err := f.p.prepareServingReboot(t.Context(), id); err != nil {
t.Fatal(err)
}
@@ -281,7 +281,18 @@ func TestServingParkRebootPreparationNeverRefreshesStaleEvidence(t *testing.T) {
case "claim":
f.tags(f.current.Stamp)
case "boot":
- f.p.ledger.fs.(*servingParkFS).boot = func() (string, error) { return "11234567-89ab-4cde-8fab-0123456789ab", nil }
+ body, e := f.disk.Read(name)
+ if e != nil {
+ t.Fatal(e)
+ }
+ var prep servingParkRebootPreparation
+ if servingParkDecode(body, &prep) != nil {
+ t.Fatal("decode")
+ }
+ prep.Observation.Topology.BootID = "11234567-89ab-4cde-8fab-0123456789ab"
+ if e = f.p.ledger.write(name, prep); e != nil {
+ t.Fatal(e)
+ }
case "body":
if err := f.disk.Write(name, []byte("{}")); err != nil {
t.Fatal(err)
@@ -392,7 +403,7 @@ func TestServingParkRebootPreparationLegacyContinuation(t *testing.T) {
if err := f.p.prepareServingReboot(t.Context(), a.ID); err != nil {
t.Fatal(err)
}
- name := "reboot-" + a.ID + ".json"
+ name := servingRebootPreparationName(a.ID, servingOtherBoot)
body, err := f.disk.Read(name)
var prep servingParkRebootPreparation
if err != nil || servingParkDecode(body, &prep) != nil || prep.Active == nil || prep.Legacy == nil || prep.Observation.Record.Authorization.OriginalSuccessor != original || prep.Observation.Record.activeAuthorization().Successor != f.current {
diff --git a/internal/host/claimservingtopology_linux.go b/internal/host/claimservingtopology_linux.go
index ee7d649b5..a463ea951 100644
--- a/internal/host/claimservingtopology_linux.go
+++ b/internal/host/claimservingtopology_linux.go
@@ -113,7 +113,7 @@ func (f *servingParkFS) inspectRebootTopology(ctx context.Context) (servingParkR
if err != nil {
return zero, err
}
- p, err := f.namespaceParent()
+ p, err := f.historicalNamespaceParent()
if err != nil {
return zero, err
}
diff --git a/internal/host/claimstop_run.go b/internal/host/claimstop_run.go
index 51ea563d9..a576159c0 100644
--- a/internal/host/claimstop_run.go
+++ b/internal/host/claimstop_run.go
@@ -83,6 +83,7 @@ func RunInstanceControl(args []string) {
}
finishStartup()
execution.Start(ctx)
+ handler.(ClaimRootHTTP).servingParks.Start(ctx)
fenceDone := make(chan struct{})
go func() { defer close(fenceDone); runHostFencePass(ctx) }()
defer func() { cancel(); <-fenceDone }()
diff --git a/internal/host/claimwakeassembly.go b/internal/host/claimwakeassembly.go
index 5f250030f..b9311e491 100644
--- a/internal/host/claimwakeassembly.go
+++ b/internal/host/claimwakeassembly.go
@@ -95,6 +95,7 @@ type claimSyncer interface {
// ClaimWakeAssembly owns cold restore, lease transfer and failure cleanup.
// Root supplies Stop and Launch; moves supplies only its input/publication API.
type ClaimWakeAssembly struct {
+ ServingPrerequisites func(context.Context, registry.Route, hostclaim.Observed) error
recoveryIntent func(context.Context, hostclaim.Observed, hostclaim.Admission) (hostclaim.Admission, error)
recoveryPrior func(context.Context, hostclaim.Observed) (hostclaim.Observed, bool, error)
recoveryPrelaunched func(context.Context, hostclaim.Observed) error
@@ -578,7 +579,22 @@ func (w *ClaimWakeAssembly) Build(ctx context.Context, source hostbox.WorkSource
}
return w.release(ctx, o.Host, a)
}
- return w.Retries.Wrap(runtime)
+ wrapped, err := w.Retries.Wrap(runtime)
+ if err != nil {
+ return nil, err
+ }
+ if w.ServingPrerequisites != nil {
+ previous := wrapped.Prerequisites
+ wrapped.Prerequisites = func(ctx context.Context, rt registry.Route, o hostclaim.Observed, p activator.StartPolicy) error {
+ if !p.Probe {
+ if err := w.ServingPrerequisites(ctx, rt, o); err != nil {
+ return err
+ }
+ }
+ return previous(ctx, rt, o, p)
+ }
+ }
+ return wrapped, nil
}
// claimHeldLease is the part of lease.Held one admitted attempt uses.
diff --git a/internal/host/movesfiles.go b/internal/host/movesfiles.go
index 683d50f21..58ff91b25 100644
--- a/internal/host/movesfiles.go
+++ b/internal/host/movesfiles.go
@@ -55,6 +55,7 @@ import (
// Absence of a manifest, of a pin, or of a reader is never evidence: with
// Evidence nil, a chain without a manifest refuses every wake.
type movesFiles struct {
+ Seed *filesSeedOperation
// Files is this box's data bucket as a release.Store: current reads
// with the object's etag, create-only and compare-and-swap writes
// (the S3 store's Put with If-None-Match and If-Match). Every write
@@ -1001,8 +1002,16 @@ func (f *movesFiles) Establish(ctx context.Context, o hostclaim.Observed, in Cla
if err != nil {
return err
}
+
prefix := movesFilesPrefix(h.Location, gen)
- files := selectionStore{Store: f.Files, fence: writerFence(work, in.Identity)}
+ var storage release.Store = f.Files
+ if f.Seed != nil {
+ if err := f.Seed.check(ctx, o, in); err != nil {
+ return err
+ }
+ storage = filesSeedCheckingStore{Store: f.Files, data: f.Data, bucket: f.Bucket, prefix: prefix}
+ }
+ files := selectionStore{Store: storage, fence: writerFence(work, in.Identity)}
var p *hostfiles.Publisher
switch {
case prepared:
@@ -1043,14 +1052,14 @@ func (f *movesFiles) Establish(ctx context.Context, o hostclaim.Observed, in Cla
}
if !found {
never := false
- if f.Evidence != nil {
+ if f.Evidence != nil && f.Seed == nil {
pin, ok, err := f.Evidence(ctx, o)
if err != nil {
return fmt.Errorf("host: the claim pin's files evidence: %w", err)
}
never = ok && pin.NeverPublished()
}
- if !never {
+ if !never && f.Seed == nil {
return fmt.Errorf("%w: no files manifest in the admitted predecessor chain of %s (%s)", hostfiles.ErrNoEvidence, o.Host, h.Generation)
}
own, err := f.proveOrigins(ctx, o, in.Admission, h.Location, []string{gen})
@@ -1065,12 +1074,22 @@ func (f *movesFiles) Establish(ctx context.Context, o hostclaim.Observed, in Cla
}
p.SetOrigins(origins)
}
+ if f.Seed != nil {
+ if err := f.Seed.stage(ctx, o, in, work, p, envFile); err != nil {
+ return err
+ }
+ }
if err := p.StagePlatform(ctx, hostfiles.EnvItem, env); err != nil {
return err
}
if err := p.StagePlatformDelete(instancekey.AdoptionItem); err != nil {
return err
}
+ if f.Seed != nil {
+ if err := f.Seed.check(ctx, o, in); err != nil {
+ return err
+ }
+ }
if _, err := p.Commit(ctx); err != nil {
return err
}
diff --git a/internal/host/relocationflush.go b/internal/host/relocationflush.go
index 4ee812d1a..34226d2ee 100644
--- a/internal/host/relocationflush.go
+++ b/internal/host/relocationflush.go
@@ -67,9 +67,20 @@ func (f ReleaseFlushFiles) Flush(ctx context.Context, obs hostclaim.Observed) (i
if err != nil {
return 0, err
}
- if err := hostfiles.StageWake(ctx, p, ws); err != nil {
+ root, err := instancefs.OpenDirBeneath(filepath.Dir(filesDir), filepath.Base(filesDir))
+ if err != nil {
+ return 0, fmt.Errorf("%w: release flush of %s: %w", ErrClaimStopUnavailable, obs.Host, err)
+ }
+ defer root.Close()
+ tenantRootSeam("opened")
+ st, err := hostfiles.Seed(ctx, p, root, ws, f.Skip)
+ if err != nil {
+ if len(st.Unreadable) > 0 || len(st.Retry) > 0 || len(st.Torn) > 0 {
+ return 0, fmt.Errorf("hostfiles: release flush of %s: unreadable %q replaced %q torn %q", obs.Host, st.Unreadable, st.Retry, st.Torn)
+ }
return 0, err
}
+
// The env file and the adoption record live in the instance directory,
// which the tenant can write: both are read through main's one safe
// read for that ground (readInstanceFile), never os.ReadFile.
@@ -119,31 +130,7 @@ func (f ReleaseFlushFiles) Flush(ctx context.Context, obs hostclaim.Observed) (i
return 0, err
}
}
- // SyncDir opens its root by path and follows a symlink there (the
- // root is the platform's own on main); beneath it nothing is
- // followed. This root, <instance dir>/<host>, is the tenant's to
- // rename, so a link planted at it would publish whatever directory it
- // names (the box's own /data among them) as the tenant's files. So the
- // flush opens it once, with no symlink at any component and never
- // waiting on a FIFO (instancefs.OpenDirBeneath), and publishes from
- // that descriptor (SyncDirAt): a link renamed in after the open is
- // never followed.
- root, err := instancefs.OpenDirBeneath(filepath.Dir(filesDir), filepath.Base(filesDir))
- if err != nil {
- return 0, fmt.Errorf("%w: release flush of %s: %w", ErrClaimStopUnavailable, obs.Host, err)
- }
- defer root.Close()
- tenantRootSeam("opened")
- st, err := hostfiles.SyncDirAt(ctx, p, root, f.Skip)
- if err != nil {
- return 0, err
- }
- if len(st.Unreadable) > 0 || len(st.Retry) > 0 || len(st.Torn) > 0 {
- // Writers are stopped (step 1), so an unstable file is a fault,
- // not a race: the release does not vouch for an image it could
- // not read whole.
- return 0, fmt.Errorf("hostfiles: release flush of %s: unreadable %q replaced %q torn %q", obs.Host, st.Unreadable, st.Retry, st.Torn)
- }
+
return p.Commit(ctx)
}
diff --git a/internal/hostfiles/seed.go b/internal/hostfiles/seed.go
new file mode 100644
index 000000000..d45c1d38b
--- /dev/null
+++ b/internal/hostfiles/seed.go
@@ -0,0 +1,28 @@
+package hostfiles
+
+import (
+ "context"
+ "os"
+)
+
+// Seed stages the stopped host's wake state and descriptor-opened files tree.
+// The caller owns the descriptor, origin proof and single manifest commit.
+func Seed(ctx context.Context, p *Publisher, root *os.File, wake WakeState, skip func(string, bool) bool) (SyncStats, error) {
+ if ctx == nil || p == nil || root == nil {
+ return SyncStats{}, os.ErrInvalid
+ }
+ if err := ctx.Err(); err != nil {
+ return SyncStats{}, err
+ }
+ if err := StageWake(ctx, p, wake); err != nil {
+ return SyncStats{}, err
+ }
+ stats, err := SyncDirAt(ctx, p, root, skip)
+ if err != nil {
+ return stats, err
+ }
+ if len(stats.Unreadable) != 0 || len(stats.Retry) != 0 || len(stats.Torn) != 0 {
+ return stats, os.ErrInvalid
+ }
+ return stats, nil
+}
diff --git a/internal/hostfiles/seed_test.go b/internal/hostfiles/seed_test.go
new file mode 100644
index 000000000..7d625c8d9
--- /dev/null
+++ b/internal/hostfiles/seed_test.go
@@ -0,0 +1,43 @@
+package hostfiles
+
+import (
+ "errors"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/carlosframework/platform/internal/release"
+)
+
+func TestSeedStagesWithoutCommit(t *testing.T) {
+ store := release.NewDirStore(t.TempDir())
+ root, err := os.Open(t.TempDir())
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer root.Close()
+ if err := os.WriteFile(filepath.Join(root.Name(), "file"), []byte("tenant"), 0600); err != nil {
+ t.Fatal(err)
+ }
+ p, err := Open(t.Context(), store, "files/g-seed/")
+ if err != nil {
+ t.Fatal(err)
+ }
+ stats, err := Seed(t.Context(), p, root, WakeState{V: 1, Host: "example.com", NextDue: 123, Timers: []WakeTimer{}}, nil)
+ if err != nil || stats.Published != 1 {
+ t.Fatal(stats, err)
+ }
+ if _, _, err := store.Get(t.Context(), ManifestKey("files/g-seed/")); !errors.Is(err, os.ErrNotExist) {
+ t.Fatal("staging committed", err)
+ }
+ if _, err := root.Stat(); err != nil {
+ t.Fatal("staging closed caller descriptor", err)
+ }
+ if _, err := p.Commit(t.Context()); err != nil {
+ t.Fatal(err)
+ }
+ m, found, err := Load(t.Context(), store, "files/g-seed/")
+ if err != nil || !found || m.Entries["file"].Object == "" || m.Platform[WakeItem].Object == "" {
+ t.Fatal(m, err)
+ }
+}
diff --git a/internal/provision/plan9_b8_census_test.go b/internal/provision/plan9_b8_census_test.go
index ff1747952..64c50a42a 100644
--- a/internal/provision/plan9_b8_census_test.go
+++ b/internal/provision/plan9_b8_census_test.go
@@ -472,6 +472,11 @@ func Reviewed(ctx context.Context, s Store, k string) error {
// BEGIN REVIEWED INVENTORY
var plan9B8CensusInventory = []censusEntry{
+ // Seed serving paths: three local/data keys and one files-object transport.
+ {"internal/host/claimfilesseed_select.go", "checkFilesSeedLayout", "key-site", "lit:instances", "cb0de1645ceeb6726ca342bec8487a807b0da6f200a277c41be87e3bc62de4cd"},
+ {"internal/host/claimfilesseed_select.go", "filesSeedChain", "key-site", "lit:instances", "be995b5de271102602ff47d5c532ed226502dd364264fa688673ee7e7ad16493"},
+ {"internal/host/claimfilesseed_stage.go", "filesSeedCheckingStore.Put", "nonrecord", "def:Put,sel:Put", "1b9671f8b7b441b98087fe762ad54013ead435faa8eb62a930cc0ff207e897b0"},
+ {"internal/host/claimservingmaintenance_linux.go", "claimServingRoom", "key-site", "lit:instances", "ce0cb25c53adfd3dba7344a98049f44fc3f0b3e633b22ad37299dea81503e403"},
// rev3=PROPOSED-seed: local instances mount/state references; no deployment record writer. Root authority remains in the seed overlay.
{"internal/host/claimservingpark_linux.go", "servingParkFS.parent", "key-site", "lit:instances", "1d241a7d446bbbade6e460efd911863f8a5246b8ac1d47b2548034e3feb2f216"},
{"internal/host/claimservingparksource_linux.go", "servingParkStateDirectory", "key-site", "lit:instances", "ec4a121330c5c8f27343f21af0bbe458cced885d4754e96c0d87b12d9b4a8f35"},
diff --git a/internal/provision/testdata/plan14-root-reads-seed.tsv b/internal/provision/testdata/plan14-root-reads-seed.tsv
index 8a68b5791..01768347c 100644
--- a/internal/provision/testdata/plan14-root-reads-seed.tsv
+++ b/internal/provision/testdata/plan14-root-reads-seed.tsv
@@ -1,113 +1,232 @@
-# Files seed checkpoint 22 overlay; pending Paul’s census ruling.
-# Database retirement activation remains refused; serving installation transfer uses the existing root transport.
-# Load after main’s full overlay chain through hostfiles-cap-236. No legacy fingerprints are added or removed.
-# Every classification below is rev3=PROPOSED-seed, not an approved disposition.
-internal/host/claimconstruction.go OpenProductionClaimAssembly authority C,G rev3=PROPOSED-seed; box process identity and provider bindings; serving dispatch independent of moves; serving enrollment required before a runtime can be built file:Read@4150,file:Read@9550,file:ReadFile@3694,registry:Host@9577,whole:Route@9665 088f34c7fe1b4f9bb7a3187d40d60bff0fd811ebf1e1888ce3d4781dc4de80d7
-internal/host/claimproduction.go ProductionClaimOwnerProvider.OpenClaimOwners authority C,G rev3=PROPOSED-seed; verified roster, cloud identity, floors and configured provider dependencies through existing helpers; no moves-provider Open or Close with moves disabled f1e9aa15fb6142f839bb76dafa574cad7122d6053cf2099d852ad97a528f1127
+# Files seed run 23 overlay; pending Paul’s census ruling.
+# Serving retirement is bound; private seed command and integrated acceptance remain unfinished.
+# Loaded LAST after main through hostfiles-cap-236; no legacy fingerprints changed.
+# Every classification is rev3=PROPOSED-seed.
+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 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:Prepare@128,file:Prepare@4369,file:Prepare@4499,file:Read@2932,file:Read@3575,file:Read@5098,file:Read@6063,file:Read@6728,whole:Route@1212,whole:Route@1572,whole:Route@1670,whole:Route@1970,whole:Route@2382,whole:Route@4517,whole:Route@5465,whole:Route@6594,whole:Route@876,whole:Route@954 dc27bad808ca7cab72e3e0b6ea229186f954f982db44e5d6da3d0fcad1ac6942
+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
+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; safe nested layout and bounded manifest metadata; evidence absence refuses before retries; only genuine empty authority can omit instance directory file:Read@600,whole:Route@548 43fc71ee9a589203b1889c3432421638d5f61dc5741610c17376d4129ff55619
+internal/host/claimfilesseed_select.go checkFilesSeedLayout authority C,J4 rev3=PROPOSED-seed; fresh claim and route, safe nested layout, complete local retained-admission chain and exact manifest versions; all entries checked for restore cap and real origin; missing source is never empty registry:DBPath@174,registry:DBPath@301,registry:Host@221,registry:Host@292,registry:Host@414,registry:Host@438,registry:Host@593 cb0de1645ceeb6726ca342bec8487a807b0da6f200a277c41be87e3bc62de4cd
+internal/host/claimfilesseed_select.go filesSeedChain authority C,J4 rev3=PROPOSED-seed; fresh claim and route, safe nested layout, complete local retained-admission chain and exact manifest versions; all entries checked for restore cap and real origin; missing source is never empty be995b5de271102602ff47d5c532ed226502dd364264fa688673ee7e7ad16493
+internal/host/claimfilesseed_select.go inspectFilesSeed authority C,J4 rev3=PROPOSED-seed; fresh claim and route, safe nested layout, complete local retained-admission chain and exact manifest versions; all entries checked for restore cap and real origin; missing source is never empty file:Exec@1186,file:Read@730,registry:Account@1618,registry:App@1658,registry:Backing@1492,registry:Host@1331,registry:Replica@1413,registry:Seed@1475,registry:Tunnel@1547,registry:Unit@1512,whole:Route@1318,whole:Route@1763 37a068572b9069edf0b2676a960897e40d27e193a516276efcfa252044caaa70
+internal/host/claimfilesseed_select.go movesFiles.inspectSeedManifests authority C,J4 rev3=PROPOSED-seed; fresh claim and route, safe nested layout, complete local retained-admission chain and exact manifest versions; all entries checked for restore cap and real origin; missing source is never empty b69e3c79814750c29a6989feb1283143c4af40f80e2c2da0e876237c2381c472
+internal/host/claimfilesseed_select.go type filesSeedSelection authority C,J4 rev3=PROPOSED-seed; fresh claim and route, safe nested layout, complete local retained-admission chain and exact manifest versions; all entries checked for restore cap and real origin; missing source is never empty c4229397b9ff57b5326381b5052b8cba752671a4d80a686c191b3517490dba69
+internal/host/claimfilesseed_stage.go filesSeedCheckingStore.Put authority C,J4 rev3=PROPOSED-seed; fresh private provider, real admitted tuple and origins; guarded safe source descriptor and wake snapshot; create conflicts compare bounded bytes at exact current version before commit file:ReadAll@794 1b9671f8b7b441b98087fe762ad54013ead435faa8eb62a930cc0ff207e897b0
+internal/host/claimfilesseed_stage.go filesSeedOperation.admitted authority C,J4 rev3=PROPOSED-seed; fresh private provider, real admitted tuple and origins; guarded safe source descriptor and wake snapshot; create conflicts compare bounded bytes at exact current version before commit whole:Route@770 a4f97a70ab72756c46ec3dbda713b3a6f1a105774ad00c2f6e7a87754ba98e3d
+internal/host/claimfilesseed_stage.go filesSeedOperation.check authority C,J4 rev3=PROPOSED-seed; fresh private provider, real admitted tuple and origins; guarded safe source descriptor and wake snapshot; create conflicts compare bounded bytes at exact current version before commit whole:Route@588 67e209a92b7028247d0df3a8275787ecd492335cf3ac790b48da65d94b5fa3ad
+internal/host/claimfilesseed_stage.go filesSeedOperation.stage authority C,J4 rev3=PROPOSED-seed; fresh private provider, real admitted tuple and origins; guarded safe source descriptor and wake snapshot; create conflicts compare bounded bytes at exact current version before commit file:Stat@604,file:Stat@922 9ac5f1093bcb2acf55f14c147b69bff02e84126896f9a59081a73d986bbf3d54
+internal/host/claimfilesseed_stage.go newFilesSeedProvider authority C,J4 rev3=PROPOSED-seed; fresh private provider, real admitted tuple and origins; guarded safe source descriptor and wake snapshot; create conflicts compare bounded bytes at exact current version before commit file:Open@364 391a4dd6ae1ee7b12c49d4cf2cbb4007716634af75591565dc4c3f754d4cbf35
+internal/host/claimfilesseed_stage.go type filesSeedCheckingStore authority C,J4 rev3=PROPOSED-seed; fresh private provider, real admitted tuple and origins; guarded safe source descriptor and wake snapshot; create conflicts compare bounded bytes at exact current version before commit 41f415ce4f78804c1191a2dfc450e355f44b979f9cdeb210a482c35b42591f4f
+internal/host/claimfilesseed_stage.go type filesSeedOperation authority C,J4 rev3=PROPOSED-seed; fresh private provider, real admitted tuple and origins; guarded safe source descriptor and wake snapshot; create conflicts compare bounded bytes at exact current version before commit a34058915d17d03fd83847b5ebdaf38104ee763a3629b09e236ad30e50781046
+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
internal/host/claimowners.go checkClaimServingSources authority C rev3=PROPOSED-seed; in-process serving launch, marker and files provider availability before serving selection ed3ad706b8142cd514c04e7e6eeb37a61f881a0e160ffbcb78898d0f1d419351
internal/host/claimowners.go claimServingOwners.serving authority C,G,J4 rev3=PROPOSED-seed; claim executor and serving shape plus complete serving sources; missing launch, marker or files source refuses file:Exec@450 6d15b7a206526a4df95b4eb2cc5ac5973a7360947ef3eca7a1df8f3e04719719
-internal/host/claimwakeassembly.go ClaimWakeAssembly.Build authority C,G,J1,J4 rev3=PROPOSED-seed; claim/route selection and launch inputs; serving callbacks bind exact database and admitted generation; unbound retirement hooks refuse construction file:Command@9968,file:Prepare@7029,file:Read@1348,file:Read@1442,registry:Backing@2672,registry:Host@12582,registry:Host@3354,registry:Host@3957,registry:Host@4002,registry:Host@4709,registry:Host@5430,registry:Host@6477,registry:Host@7185,registry:Host@7858,registry:Host@9166,registry:Seed@2655,registry:Tunnel@2727,registry:Unit@2692,whole:Route@10106,whole:Route@2899,whole:Route@5830 60104a1c8f22af5a2a9f6aad71d6c0360b193eba93efc3baab6cfddc8145cf12
-internal/hostbox/restore.go ColdRestore.Install authority C,J2,J4 rev3=PROPOSED-seed; selected database versions, local generation marker and restored SQLite data; legacy callback outside Work and enrollment callback before any local write file:CreateTemp@2200,file:Open@3189 7054cf1c887f4b6b4e20b62fd94e17949770c675cbe7e8efe2accf7f20b1c7ba
-internal/hostbox/restore.go ColdRestore.InstallEmpty authority C rev3=PROPOSED-seed; existing empty-bootstrap proof and local marker; legacy callback outside Work and enrollment callback before purge or marker creation f8c0ea4eaf3fa692abe52dd3b0799c9239bcb0d761c69644cc4be78d51d74c04
-internal/hostguard/writerstop.go Guard.CompletedServingStops authority C rev3=PROPOSED-seed; root fence journal through the existing Guard backend; exact host, incarnation, stamp, session and box, with empty operation, phase and obligation; no mixed-session or pending-only evidence 79260151b0fe4f86c59cbe65d727cdceb56028bdfdb208fc9f02cebba1473a98
+internal/host/claimproduction.go ProductionClaimOwnerProvider.OpenClaimOwners authority C,G rev3=PROPOSED-seed; verified roster, cloud identity, floors and configured provider dependencies through existing helpers; no moves-provider Open or Close with moves disabled f1e9aa15fb6142f839bb76dafa574cad7122d6053cf2099d852ad97a528f1127
+internal/host/claimretire_transport.go ClaimRootHTTP.RecoverPending authority C,J4 rev3=PROPOSED-seed; startup serving handover recovery before ordinary cold and root recovery; old destructive serving intents refuse 627b40daecf11e1a6a6c9f92aeb3ebcf226131da6dfa584ed85559c8b9b5ff30
internal/host/claimservingartifact_linux.go checkServingArtifactAbsence authority C,J4 rev3=PROPOSED-seed; source directory opened without symlinks; descriptor statx mount/device/inode and no-follow lookup of database, marker, WAL and SHM file:Fstatat@680,file:Statx@234 ab45d3f0651d25e8a6d1c48def6cc222c35dc152aca477ebd1555a67c9b89881
internal/host/claimservingartifact_other.go checkServingArtifactAbsence authority C,J4 rev3=PROPOSED-seed; non-Linux refusal; no source inspection or restore permission 27a7173119b5af75be262e9fcc56fddadb84a539f69fe5882f8c6613cfbc88c9
+internal/host/claimservingboot_linux.go servingParkBootID authority C,J4 rev3=PROPOSED-seed; bounded kernel boot-ID read from /proc; no caller-supplied path or identity file:Open@54,file:ReadAll@162 af8e5641ef60fcd307dec013df6eba3e50a29d2cb811c5b2cddf10c5c9404e30
+internal/host/claimservingboot_linux.go servingParkFS.checkBoot authority C,J4 rev3=PROPOSED-seed; private namespace boot identity compared with the current kernel boot; missing old identity or a reboot refuses without refreshing mount numbers 3d380a2fa685f3be883f9c595a01ee437d51597a1b05ab09fb9c50dfead2cac1
+internal/host/claimservingboot_linux.go servingParkFS.currentBoot authority C,J4 rev3=PROPOSED-seed; production boot reader and canonical nonzero UUID validation; missing or unreadable boot evidence refuses f6d671a41210250791f421efcf168fa98e3c2b4bd8b6aa314e9664261fd2b034
+internal/host/claimservingcollection.go servingParkLedger.collectClosedLocked authority C,J4 rev3=PROPOSED-seed; identity-checked closed record and slot; legacy collection marker writes through inverse historical projection 5176a798a8f6258f3284c67334ec9812788f2c24a5143060350cd9efd89345cb
+internal/host/claimservingdisplaced_linux.go const servingParkRootSearchLimit authority C,J4 rev3=PROPOSED-seed; fixed bound of 128 immediate parent entries; overflow refuses before opening candidate contents or deleting artifacts f7767fc1ea8f855aa6ed5e145e5b576c061c374876b57291f7182ebbdd899595
+internal/host/claimservingdisplaced_linux.go servingParkFS.displacedRoot authority C,J4 rev3=PROPOSED-seed; completed private quarantine and prior namespace identity; no-follow bounded parent listing/statx and exact root-owned directory open; same device/inode/mount and safe parent recheck; no unknown child traversal file:Fstat@649,file:Read@144,file:Readdirnames@939,file:Statx@1259 762661ee74aaed30ca1147160a995786ad3a55200f6ffd25106125799ff0c74d
+internal/host/claimservingdisplaced_linux.go servingParkFS.eventRoot authority C,J4 rev3=PROPOSED-seed; current namespace and recorded retirement root; selects only current or independently recorded prior namespace; ambiguous old absent-event records refuse file:Read@562 7c47b53f88059eea930d483465570fc7205b3cec27a118472071ca4d6df7d5e7
+internal/host/claimservingdisplaced_linux.go servingParkFS.recordEventRoot authority C,J4 rev3=PROPOSED-seed; descriptor identity of an already verified event root; persist old-record namespace before first deletion; existing namespace must match exactly b4febb444cd09cd5ed45fc616d172966038d71026024c89dfce9e8c530435756
+internal/host/claimservinghandover.go ClaimRootHTTP.servingHandover authority C,J4 rev3=PROPOSED-seed; root-only serving transfer constructor and existing private journal, inventory and registry dependencies 213a1885dd9aa5d0079cd52c4f88c6893ea5b43bfbfd83a67ec2c4697a83265a
+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 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
+internal/host/claimservingidentity.go type servingParkPersistentFile authority C,J4 rev3=PROPOSED-seed; root-recorded regular-file filesystem handle, owner and mode, stored with immutable source evidence 003b704d58342ed25fd8c9cd557a69094115e0f22f70f35bb544e57da35c5ada
+internal/host/claimservingidentity.go validServingParkBootID authority C,J4 rev3=PROPOSED-seed; canonical nonzero kernel boot UUID validation shared by live boot reads and historical topology inspection 76350dcd46b8c66e2e70945267537fb580e15b8da9c80fb158392d317973d3b2
+internal/host/claimservingidentity.go validServingParkHandle authority C,J4 rev3=PROPOSED-seed; bounded canonical opaque handle, nonzero filesystem and handle type; pure validation shared by directory and regular-file records 48c0cbdeb5a2173629a1d7414819eea3894403b1f39241f5f49a9717e579466f
+internal/host/claimservingidentity.go validateServingParkPersistentRecord authority C,J4 rev3=PROPOSED-seed; source boot and directory identity, every present or absent file handle and prepared event proof; rejects partial evidence, preserves old record decoding bc7480d85f681f30580779f7d8fa3155d93509b804bd34212b56cc01e4eec69e
+internal/host/claimservingidentity_linux.go servingParkPersistentArtifact authority C,J4 rev3=PROPOSED-seed; no-follow O_PATH file descriptor, Fstatfs, Statx and NameToHandleAt with AT_EMPTY_PATH; name/descriptor device, inode, mount, type, links, allocation, ownership and mode checks; no tenant payload read file:Fstatfs@608,file:Openat@163,file:Statx@1062,file:Statx@347 b3fd1ed9332350bb19d0d34143b3c25ff44a39705207c0034f472690432575db
+internal/host/claimservingidentity_linux.go servingParkStatxMatches authority C,J4 rev3=PROPOSED-seed; recorded file identity versus descriptor or name statx metadata, including single regular-file link and allocated bytes 42ce5eb11aceab2c7f5d1999e91750d57ae668b0467a15b2c87953ca4aa17d63
+internal/host/claimservinglegacy.go sameServingLegacyConsumption authority C,J4 rev3=PROPOSED-seed; immutable legacy consumption and retained retirement source identities; compares original evidence while ignoring per-artifact progress and the separate collection flag; source boot and persistent directory identity must remain equal to the permanent legacy-consumption record dff50631bc0eac3586c220ea2a131cf233a35b20e3b3f5e8fe35904fcc43c722
+internal/host/claimservinglegacy.go sameServingLegacyEvidence authority C,J4 rev3=PROPOSED-seed; exact immutable legacy selector and retained no-process observation comparison 806588dfe151a558273222fcd77c6f780a72e293f312531ab7ba9fcde7a9e46f
+internal/host/claimservinglegacy.go servingLegacyKey authority C,J4 rev3=PROPOSED-seed; box, host and incarnation derive private stop and consumption keys; never a caller path 93c10e40202ab23986b54a366edd7abfab9f8d942736305b5732e8741e96f3ab
+internal/host/claimservinglegacy.go servingParkLedger.legacyConsumption authority C,J4 rev3=PROPOSED-seed; permanent legacy consumption and separate current-boot projection; preserves original authorization file:Read@121 87a072fd30c3bdb118e12d561a75ae145dfadeb8c424c8cd425cd3f76f604128
+internal/host/claimservinglegacy.go servingParkLedger.legacyStop authority C,J4 rev3=PROPOSED-seed; strict private retained no-process record and exact host, box, region and incarnation; no bucket or caller evidence file:Read@131 3cc2122431a095406bcd11a1a6ace825a870f5f8d0b8053d2b27486084838c4f
+internal/host/claimservinglegacy.go servingParkLedger.legacyUnlaunched authority C,J4 rev3=PROPOSED-seed; local root launch history for current successor; any main-role intention refuses legacy source capture and completion 1963414899b4ce2bb059950bc6cfdad4e8c5ea0a575d2e19f87c91a4cf82c8ad
+internal/host/claimservinglegacy.go validateServingLegacyEvidence authority C,J4 rev3=PROPOSED-seed; original successor, legacy selector and original session-zero holder; first-creator status is not a request assertion 485ab1232cc0817ad52a638bb220fe532c5a16086379e689b1c27dea6948f6b5
+internal/host/claimservinglegacy.go validateServingLegacyStop authority C,J4 rev3=PROPOSED-seed; original session-zero serving observation and exact root no-process bytes; claim ETag, receipt digest, kernel evidence and empty fence identity file:Exec@413 88bf4d578819b8d3f62b80e2068a4c574a1aaa8118a8461bbeee99569e035ed0
+internal/host/claimservinglegacyroot_linux.go sameServingLegacySource authority C,J4 rev3=PROPOSED-seed; immutable directory and artifact identities from consumed source and retirement; mutable rename/delete progress is excluded; source boot and persistent source-directory identity also constrain the immutable legacy source comparison af04f97f71570ea92099c78c613d252d1174864d54a5161318fcd9d5ecd958fa
+internal/host/claimservinglegacyroot_linux.go servingParkRoot.parkLegacyServingDatabase authority C,J4 rev3=PROPOSED-seed; fresh serving claim, root installation, consumed legacy identity and exact stop/empty evidence; collected consumption refuses recapture, while consumption saved before the first intent still resumes file:Read@3525 c192eda0fc53bc5211b02123ed7745e8e2b1b2fc53ad5fb3b0d11fe95fd9672c
+internal/host/claimservinglegacyroot_linux.go servingParkRoot.retainLegacyNoProcess authority C,J4 rev3=PROPOSED-seed; root coordinator result before publication; namespace and host guards, census, roster, fresh original claim and immutable retained stop; repeated preadmission witnesses tolerate only Gen, ETag and retry metadata changes file:Read@1210 5460530ae4367720515089e9fda3105aa1032b8282f4923bbf7f8af9600f4f63
+internal/host/claimservinglegacyroot_linux.go servingParkRoot.validateLegacyRootEvidence authority C,J4 rev3=PROPOSED-seed; retained no-process or stopped-admission evidence; exact predecessor fields with canonical recorded directory revision bounded by the current actor aa0dfe36c042ab65fd826e4b83eb81bfb9d03e6a54daafa828d5164492329d0d
+internal/host/claimservingmaintenance.go servingParkLedger.maintainParks authority C,J4 rev3=PROPOSED-seed; root park records, slot ownership and deadlines; bounded candidate set, oldest complete parks, verified deletion under pressure or expiry; cancelled on shutdown ed7abaaf843199235a186f3de9698cf54143584732a5d2f36876f53e55438cb7
+internal/host/claimservingmaintenance.go servingParkRuntime.Start authority C,J4 rev3=PROPOSED-seed; root park records, slot ownership and deadlines; bounded candidate set, oldest complete parks, verified deletion under pressure or expiry; cancelled on shutdown fd8fb4702b9dcdf016343689e40e050006ec90d1af128cde283c388b79da458d
+internal/host/claimservingmaintenance_linux.go claimServingRoom authority C,J4 rev3=PROPOSED-seed; descriptor-opened data filesystem capacity, conservative DiskFloor; root recovery and bounded cleanup; carlos capacity path has no root-only state dependency ce0cb25c53adfd3dba7344a98049f44fc3f0b3e633b22ad37299dea81503e403
+internal/host/claimservingmaintenance_linux.go servingParkFS.room authority C,J4 rev3=PROPOSED-seed; descriptor-opened data filesystem capacity, conservative DiskFloor; root recovery and bounded cleanup; carlos capacity path has no root-only state dependency 5725a03b76664476dda6c2cb380339c26eff9967a4c04cf4e57fb9c68727872b
+internal/host/claimservingmaintenance_linux.go servingParkRoot.maintainServingParks authority C,J4 rev3=PROPOSED-seed; descriptor-opened data filesystem capacity, conservative DiskFloor; root recovery and bounded cleanup; carlos capacity path has no root-only state dependency 0c611463f1aa7c6a75348e460621305d54da6b619bf34394d430ba525d9608b7
+internal/host/claimservingmaintenance_linux.go servingRoomFile authority C,J4 rev3=PROPOSED-seed; descriptor-opened data filesystem capacity, conservative DiskFloor; root recovery and bounded cleanup; carlos capacity path has no root-only state dependency file:Fstatfs@78 9a76893f007a92f8a418660cc41fd9551d38b47523911ae1838bf110adb69d9a
+internal/host/claimservingnamespace_linux.go servingParkFS.historicalNamespaceParent authority C,J4 rev3=PROPOSED-seed; private namespace records, topology and boot bindings; capacity refusal before new namespace capture file:Read@117 bdb9f4c2b649160fcb79eb4772f37242be2ebace16fd4c32009a943ce5b483b9
+internal/host/claimservingnamespace_linux.go servingParkFS.namespaceParent authority A,C,J4 rev3=PROPOSED-seed; private namespace records, topology and boot bindings; capacity refusal before new namespace capture bae834ae17f4c3d45bceb9e7c3fd08785454a51825d72b0cf26a0f2c996c1f91
+internal/host/claimservingnamespace_linux.go servingParkFS.recordNamespaceParent authority A,C,J4 rev3=PROPOSED-seed; root-validated source directory under namespace and host locks; current nosymfollow parent and safe permissions; existing parent, namespace and quarantine evidence constrain first capture and replay; capture current kernel boot with the authenticated parent; never infer missing historical boot evidence file:Fstat@536,file:Read@1132 a8cbc51a05a00ca6cfba4bc920673334ab21c651edf56f30e46066a3df585c7b
+internal/host/claimservingnamespace_linux.go servingParkFS.recoverRecordedNamespace authority A,C,J4 rev3=PROPOSED-seed; private namespace records, topology and boot bindings; capacity refusal before new namespace capture file:Fstat@425,file:Read@651 5a056b2e1304ef7b6241a84978a8b9a85ef8589ca32cf57e7743b9526e787d0f
+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/claimservingretire.go bindServingArtifactRetirement authority C,J4 rev3=PROPOSED-seed; in-process root-client dependency and existing operation callbacks; serving dispatch does not replace landing/recovery dispatch 19ade85561b59f2f0b75f4f50b33333ab2598cba7d20e596893530c6a2f8102a
-internal/host/claimservingretire.go claimServingArtifactRetirement.BeforeLegacyInstall authority C,J4 rev3=PROPOSED-seed; admitted tuple and observed predecessor selection; root legacy authorization outside Work followed by guarded enrollment e8c23b09776dd4367e2a0fb5b85102747c3fe14cd45581f24e502e61b7fbcfd0
-internal/host/claimservingretire.go claimServingArtifactRetirement.EnrollLocked authority C,J4 rev3=PROPOSED-seed; completed root authorization cached for exact writer, fresh current tags and source absence; missing or stale permission refuses local mutation cf33600f5c3faaa0cde5658e53b1657ceadb7340244a68c9791441834a372729
-internal/host/claimservingretire.go claimServingArtifactRetirement.Recover authority C,J4 rev3=PROPOSED-seed; admitted serving observation and launch inputs; resumes retirement without treating absent marker as enrollment authority 674a5a3c598cbdc29494a3cfc0859dd632c41be154a966ca7684f4440cbccdcb
-internal/host/claimservingretire.go claimServingArtifactRetirement.Retire authority C,J4 rev3=PROPOSED-seed; captured predecessor generation; delegates only to serving recovery and root parking contract 6cf2e52f7e2aa511622268dd0a8dd37f1713e5beb4eba920e70057c2c90c32a3
-internal/host/claimservingretire.go claimServingArtifactRetirement.enrollPairLocked authority C,J4 rev3=PROPOSED-seed; fresh claim and root-bound source directory; descriptor-relative absence before and after paired tag CAS; exact readback resolves uncertain CAS 351faf39946ade5f9166b79c0a177001c8b4287ee698ce37c2c1e8337b534c30
-internal/host/claimservingretire.go claimServingArtifactRetirement.finish authority C,J4 rev3=PROPOSED-seed; root authorization; guarded paired-tag update before root completion; in-memory final-install permission only after completion 4ecf52c799a177f0df88d0429eb5bffc8d485045be065eb0107d0821772b0a29
-internal/host/claimservingretire.go claimServingArtifactRetirement.forgetCompletion authority C,J4 rev3=PROPOSED-seed; in-memory completed authorization for the selected host; failed replay must invalidate prior final-install permission 771a11b74e74ca98cbb51a1da72ecffea91801c2b622448a559eb1bdc6f7b3b5
-internal/host/claimservingretire.go claimServingArtifactRetirement.recover authority C,J4 rev3=PROPOSED-seed; fresh paired tags and predecessor admission; stop evidence before path-free root request; existing root record required for current-tag replay 00755911d37655eea3449e3fd69c884dd955a4f7d67487efa7cc6b9b9c7514f9
-internal/host/claimservingretire.go claimServingArtifactRetirement.snapshot authority C,J4 rev3=PROPOSED-seed; fresh claim and nested database path; paired inventory stamps, normalized paths and ancestor conflicts before root requests file:Read@908 202138f761733b9853154f6b9382e68d74e4e933a600949cdd284a9cf6df765a
-internal/host/claimservingretire.go claimServingArtifactRetirement.stopped authority C,J4 rev3=PROPOSED-seed; completed serving fence receipts from the existing Guard backend; exact predecessor and byte-identical stop evidence ea358c5594a469b11d6255df9da2f0567f5d911fb94e1a3702f7cdc4f60c7751
-internal/host/claimservingretire.go claimServingArtifactRetirement.validateAuthorization authority C,J4 rev3=PROPOSED-seed; root-returned retirement identity, original and continuing successor tuples, tag source stamp and source-directory identity 8324a81ca00f5d6cf79a38560b7a99f4de65d203545074b24bc2144fc26ac52d
-internal/host/claimservingretire.go servingInventoryPair authority C,J4 rev3=PROPOSED-seed; normalized inventory paths and stamps; paired ownership and ancestor-conflict validation shared by preflight and CAS 700cd20c58f0bfb130c54810a5baf1f1df0f89f8e4cce16716d7961cde4d554a
-internal/host/claimservingretire.go servingInventoryTransition authority C,J4 rev3=PROPOSED-seed; claim writer identity and complete local inventory; changes only database/marker pair and preserves other paths and boxes 2f4024d4d05b348e4d2b9e2fa4e25bbda69575fa8ac495c4f986444527425d48
-internal/host/claimservingretire.go servingPredecessor authority C,J4 rev3=PROPOSED-seed; retained local cold admission at the strictly older installation stamp; exact generation, box and region e5d5534da88e3bbbc051c8f3c19c9cfd14918d84bd6a0206faf87a2a612c3a57
-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/hostbox/servingpark.go LaunchJournal.RootSessionLaunches authority C,J4 rev3=PROPOSED-seed; root-local persisted launch records for exact host/incarnation/stamp/session/box/enrollment; refuses agent journal transport cbbb9378bc53ac499972bed5fb654e05856a9afd7256d55323a717140ffcba18
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 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.CheckParked authority C,J4 rev3=PROPOSED-seed; all recorded event identities, unexpected children and source absence before root completion file:Readdirnames@1266 ac675e110e1493ab1ec69359696d462228f1e1265923c2c0d471111851a2c645
-internal/host/claimservingpark_linux.go servingParkFS.Delete authority C,J4 rev3=PROPOSED-seed; recorded event and complete artifact set before journaled unlink; persist proven namespace identity before old-record deletion; remove only the recorded empty event file:Readdirnames@402 7fa2b1a5f32a7c67eceb48e6eee5adaef44fbb0c065e05a21fcd60391f40ca34
-internal/host/claimservingpark_linux.go servingParkFS.Move authority C,J4 rev3=PROPOSED-seed; recorded source/destination inodes and explicit absences; no-replace same-mount rename, post-rename identity and both parent fsyncs before progress 6e43ae58b6a02d167135fabdde7ade06502511c5237b313e6ca6c2a8e9a2c8d0
-internal/host/claimservingpark_linux.go servingParkFS.Prepare authority C,J4 rev3=PROPOSED-seed; recorded private namespace and empty event; persist namespace and event identities together; old prepared events gain a namespace only after exact identity proof; new intents record the private event filesystem handle before the first rename; old records gain no persistent evidence file:Prepare@24,file:Readdirnames@967 653a70287033f46e8b898d5b3ef7fdec2af31f399e8bebc248e2d3b11e23517c
+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.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; private rootstate namespace and quarantine completion records, effective parent mount and root-owned park directory; pending quarantine blocks namespace creation and use; unknown roots refused without adoption; the saved namespace boot must equal the current kernel boot before opening or changing park contents; new namespace publication includes persistent parent/root handles before any event creation file:Read@282 768954e03ac18ebb0e28b04ebb0ad0e1e09a546fee93476367e4be1679b08f1a
+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.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 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
internal/host/claimservingpark_other.go servingParkFS.Capture authority C,J4 rev3=PROPOSED-seed; non-Linux refusal or construction of refusal-only filesystem; no filesystem authority; source boot and persistent directory handle captured before mutation and rechecked after all artifact reads a5560b338356792386a5a1b2e27cb08ca0d5aa024cd03b567a78dfd986ce7871
internal/host/claimservingpark_other.go servingParkFS.CheckParked authority C,J4 rev3=PROPOSED-seed; non-Linux refusal or construction of refusal-only filesystem; no filesystem authority 3a8cb4d8052bbdea8cf7a300944de72c68012e6efa252c293fc1dc06a3a2150e
internal/host/claimservingpark_other.go servingParkFS.Delete authority C,J4 rev3=PROPOSED-seed; recorded event and artifact identities; validates complete child set before journaled unlink, then removes only the recorded empty event with durable intent and progress 8c169e8428b913d2f05b502ce971be49d5febaad95d8ae811aae21c81f3434ce
internal/host/claimservingpark_other.go servingParkFS.Move authority C,J4 rev3=PROPOSED-seed; non-Linux refusal or construction of refusal-only filesystem; no filesystem authority 9d215b168da646166104db5624727038f793d2ae0c40eb71f23883f12b6b4b13
internal/host/claimservingpark_other.go servingParkFS.Prepare authority C,J4 rev3=PROPOSED-seed; non-Linux refusal or construction of refusal-only filesystem; no filesystem authority; new intents record the private event filesystem handle before the first rename; old records gain no persistent evidence file:Prepare@22 645770372357707f635351a377e943e78f1c160222385e41f42b80d57adedc29
+internal/host/claimservingpark_records.go sameServingRootStops authority C,J4 rev3=PROPOSED-seed; byte-identical root stop sets including targets, evidence timestamps and original receipt bytes c9e0803635546eecc3a7d07760cb69fc915182f96b0d1581ed655de3382edd88
internal/host/claimservingpark_records.go servingParkDecode authority C,J4 rev3=PROPOSED-seed; strict private-record JSON with unknown-field and trailing-value refusal 189c7944ea9f5d269d39ed6a869b6bd739e3033195f06cc1f2c9f23a23180184
internal/host/claimservingpark_records.go servingParkKey authority C,J4 rev3=PROPOSED-seed; box/host identity used for stable per-host namespace and slot keys 67518302550380dbf9b741a73758e5c94307eff70ff8a09cc56763a8aa794d86
+internal/host/claimservingpark_records.go servingParkLedger.capture authority C,J4 rev3=PROPOSED-seed; no-follow source snapshot, root launch/stop disposition, legacy marker and empty-bootstrap refusal, and complete retirement-record validation; read-only prerequisite before parent or retirement writes e3f3c2c919d96ae0a02fe0ac1a4e8694851dce7b73b3a9968f93f4445f3f35b3
internal/host/claimservingpark_records.go servingParkLedger.completeLocked authority C,J4 rev3=PROPOSED-seed; root retirement and pending-slot records plus fresh filesystem absence/identity checks; completion requires outer root claim-tag authority through typed journal completion 00f9c10b8130c134a41dea74848aa1651ae3331c8440a81e1b3f59f54f6a7218
+internal/host/claimservingpark_records.go servingParkLedger.continueLocked authority C,J4 rev3=PROPOSED-seed; namespace/host-protected slot and record locks; pending original retirement, absent source and retained parked identities before durable bounded continuation 87f297a924f7f153624304233218d8b5548a016fc7dbf0f7cb8dedb789324009
internal/host/claimservingpark_records.go servingParkLedger.deleteLocked authority C,J4 rev3=PROPOSED-seed; complete retained record or proved discard disposition and persisted cause/progress before filesystem deletion 1545950c564d51d9c29b0d76daca1ddec5a159481698b11accd4dd7411157b37
internal/host/claimservingpark_records.go servingParkLedger.parkLocked authority C,J4 rev3=PROPOSED-seed; generation or test-fixture legacy entry delegates to immutable evidence-aware ledger; no independent source authority 1f16bea822e6c63df15c8259c8972a4084cdf364769f10396f396424e0fe848c
-internal/host/claimservingpark_records.go servingParkLedger.read authority C,J4 rev3=PROPOSED-seed; private retirement record by validated deterministic ID and strict record invariants file:Read@249 868c8c3f5682dc36738d6960d57f2f1fcc6512374c8f8e0790187bf69cf3de64
+internal/host/claimservingpark_records.go servingParkLedger.parkWithEvidenceLocked authority C,J4 rev3=PROPOSED-seed; root-authenticated source, prior slot and immutable intent; a missing same-incarnation retirement must be newer than the last successor; preserve park ownership and replay refusal after collection; old-format replay preserves original consumption and source evidence without inferred handles file:Prepare@3772,file:Read@1774 c0cdd64d93a729abd8cf2a48606579516824c35181f977631de4a1a395a0b8c7
+internal/host/claimservingpark_records.go servingParkLedger.read authority C,J4 rev3=PROPOSED-seed; root-private retirement bytes and separate mount projection; forward reads and inverse progress writes validate both representations f029f0fb8d31bdccaf86a5e336ffc1643eb2487fc7adb87852a9d21ad287e732
+internal/host/claimservingpark_records.go servingParkLedger.readHistorical authority C,J4 rev3=PROPOSED-seed; root-private retirement bytes and separate mount projection; forward reads and inverse progress writes validate both representations file:Read@259 7af371e0f68db2dbf035c4dd75c9f8312ea3733d1bbfbdd4139088b2e1805bb3
internal/host/claimservingpark_records.go servingParkLedger.reapLocked authority C,J4 rev3=PROPOSED-seed; root completion, retention disposition and fixed seven-day deadline; rollback or pending work refuses expiry aec4f50b45d1d955fac9814fe9e0d403f5f3f7f939c71f8f6b8ac15e9173b846
-internal/host/claimservingpark_records.go servingParkLedger.save authority C,J4 rev3=PROPOSED-seed; validated private retirement record before durable rootstate write b6f940b1e21cf1beb67638effea958d2b1119695494a562e22bbbe0469d8af49
+internal/host/claimservingpark_records.go servingParkLedger.replaceLocked authority C,J4 rev3=PROPOSED-seed; completed incumbent record under its record lock; resumes its recorded deletion cause before slot replacement 3e67316e959c9a901c32a2a61fda4e6ea5e92fe51bdb7fdb5f5c53d9d5960aad
+internal/host/claimservingpark_records.go servingParkLedger.save authority C,J4 rev3=PROPOSED-seed; root-private retirement bytes and separate mount projection; forward reads and inverse progress writes validate both representations 187f2c360d38e10a9f22b20f4c02a021c66b9308cad404e536ef5d0f0a860674
internal/host/claimservingpark_records.go servingParkLedger.slot authority C,J4 rev3=PROPOSED-seed; private box/host slot and pending retirement identities across incarnation changes; last-retirement identity supports discarded/artifact-free completion replay file:Read@179 239b806b2e2bfdd568f5d6b749d299f0bc7b118d675e872429432591a89b7b68
internal/host/claimservingpark_records.go servingParkLedger.withNamespace authority C,J4 rev3=PROPOSED-seed; private rootstate namespace lock and cancellation before invoking root authority; composed with root host authority through the typed journal service b7cfae44fdb7de9ef8130f5581710ae3a1a526bb059b4f616650172490f5c8ac
internal/host/claimservingpark_records.go servingParkLedger.write authority C,J4 rev3=PROPOSED-seed; serialized private record sent only to the supplied rootstate store 0635d2c27871e67079119a0f2e3034e817b765d0fe3a47e7d9bdc66fb2779571
+internal/host/claimservingpark_records.go servingParkRecord.activeAuthorization authority C,J4 rev3=PROPOSED-seed; last persisted continuation authorization or immutable original authorization; no source or deadline changes fe168fe52317e45ce70c3e97a9bc2c52d2409328ab4844a8d2a34fc4babba36d
+internal/host/claimservingpark_records.go type servingLegacyConsumption authority C,J4 rev3=PROPOSED-seed; permanent legacy source and authorization evidence; additive Collected flag distinguishes garbage collection from interrupted first-intent creation 1f7d2cd2baaad00efda56e7c5637e8b386d80ad7e5f2b311487ebcf109c4861b
+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 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/claimservingparksource_linux.go servingParkStateDirectory authority C,J4 rev3=PROPOSED-seed; root installation/inventory snapshots and authoritative serving observation; exact account/app/record, revision/enrollment, canonical nested path, ownership and descriptor identity; root acquires these inputs under the host lock through the typed journal service ec4a121330c5c8f27343f21af0bbe458cced885d4754e96c0d87b12d9b4a8f35
-internal/hostguard/servingretirement.go Guard.WithServingRetirement authority C,J4 rev3=PROPOSED-seed; fresh serving claim and root installation/fence snapshot under root and shared host locks; refuses pending, quarantined, foreign, retired or already stopped successors; permits an older completed predecessor 3d105c50be7e613777055f561321efadf77fe823d8dc3263a60f2c1211b71108
-internal/host/claimservingparkroot_linux.go servingParkRoot.withAuthority authority C,J4 rev3=PROPOSED-seed; live edge peer reauthentication under namespace and host locks before census, inventory and final claim checks; root request entry refuses nil connections d3582f0760b4459721b3e6040cda738b105c1da19cc5d0746d04fea26e313b5c
-internal/host/claimservingparkroot_linux.go servingRootStops authority C,J4 rev3=PROPOSED-seed; retained exact root serving receipts and known retired installation; same enrollment, session and region, with canonical recorded revision no newer than the active actor d371f01614c0bb64d3f412123fc9a90b8bee43d02b6c48e67fc89d9a35187005
-internal/host/claimservingparkroot_linux.go servingParkRoot.parkServingDatabase authority C,J4 rev3=PROPOSED-seed; fresh paired tags, root slot/record and retained admissions; post-retag replay selects the predecessor from the saved active tag-source stamp and revalidates its exact identity; recorded legacy work dispatches to separately locked fresh legacy authority; independently validated source records namespace parent before bounded quarantine recovery 8552695acfec22f46f6f2b17af955ae19290bdca3a9a7ee7c93dae0d28d6dbe0
-internal/host/claimservingparkroot_linux.go servingParkRoot.completeServingDatabasePark authority C,J4 rev3=PROPOSED-seed; fresh paired enrollment, same current writer, root source and original/continuation stop history; legacy additionally authenticates persistent no-process or admitted predecessor evidence 8ca9bdd3c1209c6d5257bc07a7d12d0bab900e9c2911d0698077eb2116e5123c
-internal/host/claimservingparkroot_linux.go validateServingRootHistory authority C,J4 rev3=PROPOSED-seed; retained local successor admissions and byte-exact root stops for every persisted continuation; rejects relabelled or missing history c85a1be96de5fa61acd48c4adf9393d8e837880f5133a2f965f1104037e02329
-internal/host/claimservingpark_records.go servingParkRecord.activeAuthorization authority C,J4 rev3=PROPOSED-seed; last persisted continuation authorization or immutable original authorization; no source or deadline changes fe168fe52317e45ce70c3e97a9bc2c52d2409328ab4844a8d2a34fc4babba36d
-internal/host/claimservingpark_records.go servingParkLedger.continueLocked authority C,J4 rev3=PROPOSED-seed; namespace/host-protected slot and record locks; pending original retirement, absent source and retained parked identities before durable bounded continuation 87f297a924f7f153624304233218d8b5548a016fc7dbf0f7cb8dedb789324009
-internal/host/claimservingpark_records.go servingParkLedger.replaceLocked authority C,J4 rev3=PROPOSED-seed; completed incumbent record under its record lock; resumes its recorded deletion cause before slot replacement 3e67316e959c9a901c32a2a61fda4e6ea5e92fe51bdb7fdb5f5c53d9d5960aad
-@coverage internal/host 5c64330609cccabed38ed525a7bf4b3259c53181cddf49b7b3652c6b374965c0
-@coverage internal/hostbox 3865bfeb63bcb671b919d1cfcf97b70405a0378ff6be4444648d12a678e67411
-@coverage internal/hostguard a721142f4f3ac5fa17b6b1e2ccfc66ca809cfdcbf2733fdc00fbd0efaa90aeb6
-internal/host/claimservinglegacy.go servingLegacyKey authority C,J4 rev3=PROPOSED-seed; box, host and incarnation derive private stop and consumption keys; never a caller path 93c10e40202ab23986b54a366edd7abfab9f8d942736305b5732e8741e96f3ab
-internal/host/claimservinglegacy.go validateServingLegacyStop authority C,J4 rev3=PROPOSED-seed; original session-zero serving observation and exact root no-process bytes; claim ETag, receipt digest, kernel evidence and empty fence identity file:Exec@413 88bf4d578819b8d3f62b80e2068a4c574a1aaa8118a8461bbeee99569e035ed0
-internal/host/claimservinglegacy.go servingParkLedger.legacyStop authority C,J4 rev3=PROPOSED-seed; strict private retained no-process record and exact host, box, region and incarnation; no bucket or caller evidence file:Read@131 3cc2122431a095406bcd11a1a6ace825a870f5f8d0b8053d2b27486084838c4f
-internal/host/claimservinglegacy.go servingParkLedger.legacyConsumption authority C,J4 rev3=PROPOSED-seed; private persistent consumption record for the box, host and incarnation; absence differs from malformed or unavailable state file:Read@121 f8acefe9ac0f00640b68e7ad51d02b6544fb06c4130863d9159f8e2800281ad8
-internal/host/claimservinglegacy.go sameServingLegacyEvidence authority C,J4 rev3=PROPOSED-seed; exact immutable legacy selector and retained no-process observation comparison 806588dfe151a558273222fcd77c6f780a72e293f312531ab7ba9fcde7a9e46f
-internal/host/claimservinglegacy.go validateServingLegacyEvidence authority C,J4 rev3=PROPOSED-seed; original successor, legacy selector and original session-zero holder; first-creator status is not a request assertion 485ab1232cc0817ad52a638bb220fe532c5a16086379e689b1c27dea6948f6b5
-internal/host/claimservinglegacy.go servingParkLedger.legacyUnlaunched authority C,J4 rev3=PROPOSED-seed; local root launch history for current successor; any main-role intention refuses legacy source capture and completion 1963414899b4ce2bb059950bc6cfdad4e8c5ea0a575d2e19f87c91a4cf82c8ad
-internal/host/claimservinglegacyroot_linux.go servingParkRoot.retainLegacyNoProcess authority C,J4 rev3=PROPOSED-seed; root coordinator result before publication; namespace and host guards, census, roster, fresh original claim and immutable retained stop; repeated preadmission witnesses tolerate only Gen, ETag and retry metadata changes file:Read@1210 5460530ae4367720515089e9fda3105aa1032b8282f4923bbf7f8af9600f4f63
-internal/host/claimservinglegacyroot_linux.go servingParkRoot.validateLegacyRootEvidence authority C,J4 rev3=PROPOSED-seed; retained no-process or stopped-admission evidence; exact predecessor fields with canonical recorded directory revision bounded by the current actor aa0dfe36c042ab65fd826e4b83eb81bfb9d03e6a54daafa828d5164492329d0d
-internal/host/claimservinglegacyroot_linux.go servingParkRoot.parkLegacyServingDatabase authority C,J4 rev3=PROPOSED-seed; fresh serving claim, root installation, consumed legacy identity and exact stop/empty evidence; collected consumption refuses recapture, while consumption saved before the first intent still resumes file:Read@3525 c192eda0fc53bc5211b02123ed7745e8e2b1b2fc53ad5fb3b0d11fe95fd9672c
-internal/host/claimservinglegacyroot_linux.go sameServingLegacySource authority C,J4 rev3=PROPOSED-seed; immutable directory and artifact identities from consumed source and retirement; mutable rename/delete progress is excluded; source boot and persistent source-directory identity also constrain the immutable legacy source comparison af04f97f71570ea92099c78c613d252d1174864d54a5161318fcd9d5ecd958fa
-internal/host/claimservingpark_records.go servingParkLedger.parkWithEvidenceLocked authority C,J4 rev3=PROPOSED-seed; root-authenticated source, prior slot and immutable intent; a missing same-incarnation retirement must be newer than the last successor; preserve park ownership and replay refusal after collection; old-format replay preserves original consumption and source evidence without inferred handles file:Prepare@3772,file:Read@1774 c0cdd64d93a729abd8cf2a48606579516824c35181f977631de4a1a395a0b8c7
-internal/host/claimservingpark_records.go sameServingRootStops authority C,J4 rev3=PROPOSED-seed; byte-identical root stop sets including targets, evidence timestamps and original receipt bytes c9e0803635546eecc3a7d07760cb69fc915182f96b0d1581ed655de3382edd88
-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
-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/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
-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_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
+internal/host/claimservingpark_recovery.go servingParkLedger.recoveryIDs authority C,J4 rev3=PROPOSED-seed; private rootstate names; sorted bounded retirement page and continuation cursor; malformed record IDs retained for reporting 5b450cd0fb6ae6694bca2f9f75fe972c0d65681981a4caddde7e5c7269108782
+internal/host/claimservingpark_recovery.go servingParkRuntime.recoverPending authority C,J4 rev3=PROPOSED-seed; service lifetime cancellation and join; bounded startup results rendered only with indexed host/record/fixed-reason text 0aa3ef7edd0ac2c12b4221907ccd5927e7e3e03a4c2c0b77b669bc10cc49c241
+internal/host/claimservingpark_recovery.go type servingParkRecoveryPage authority C,J4 rev3=PROPOSED-seed; private bounded recovery results, cursor and incomplete indicator; never a transport selector c2fb4445ed50b5237b72e8a5fff9b1b2f6c9dd1397473eeb2c78a6b8c2804b27
+internal/host/claimservingpark_recovery.go type servingParkRecoveryResult authority C,J4 rev3=PROPOSED-seed; root-derived host and record ID with local recovery outcome 4dd13c07e97470a6c5aa4fa98d326de8947422018f0d30d5c5af32d753cfcc39
+internal/host/claimservingpark_recovery.go var errServingParkRetagPending authority C,J4 rev3=PROPOSED-seed; fixed pending-retag refusal; startup cannot write the independent claim writer tags 8fa87ebef4defae4fc1733528a76e0cc260711c2f5a664308e5368d69286d45b
+internal/host/claimservingpark_recovery_linux.go servingParkRoot.recoverServingPark authority C,J4 rev3=PROPOSED-seed; retained intent, exact current successor, independently rechecked installation, enrollment, receipt and local launch history; resumes original park and completes only existing paired tags; no retry settlement c02a2727bb45c39df280800ade0aaae6dfba18b268e48bea5fa2f7fcb7a2af23
+internal/host/claimservingpark_recovery_linux.go servingParkRoot.recoverServingParks authority C,J4 rev3=PROPOSED-seed; startup validates raw persistent namespace and commits boot binding; incomplete records still require current installation and stop/launch authority 87c40006c0aeb45e507a81c715f658f83100c07002d8fc5c5d53a1d87e70c196
+internal/host/claimservingpark_recovery_linux.go servingParkRoot.validateServingRecoveryAuthority authority C,J4 rev3=PROPOSED-seed; fresh root history, original and continuation stop receipts, permanent legacy consumption, launch disposition and unlaunched active successor; extracted unchanged checks shared with same-boot startup 60ff31e49f8bafdf5a9c57b87ec8901e9ad3bafa6cc9e15d541a5659a83cf846
internal/host/claimservingpark_transport.go ClaimRootHTTP.serveServingPark authority C,J4 rev3=PROPOSED-seed; authenticated edge connection and census actor; only identity selectors reach root authority; completed replay rechecks census and enrollment; pressure unavailable 6d5783a57cc3a7ee3c8303b9b205e5f5ccf0fd606f1e73e3bf4e69092f5085f4
internal/host/claimservingpark_transport.go newServingParkRuntime authority C,J4 rev3=PROPOSED-seed; private state and platform-specific root authority; constructs bounded service lifetime without starting workers c432ba5861dbb9b10bf102216cdeb5eb9b2cea21b547779a981a91a071d1dfc8
internal/host/claimservingpark_transport.go servingParkRuntime.Close authority C,J4 rev3=PROPOSED-seed; active-call count and cancellation state; cancels and joins requests before closing private state, preserving it on timeout 03776c15bc8d12062d84794817a681210d23e131a5c30706bc48682c382da0be
internal/host/claimservingpark_transport.go servingParkRuntime.begin authority C,J4 rev3=PROPOSED-seed; in-process shutdown state; registers each request and cancels it with the service lifetime b5ec7229c514152b70acdb34840b66eefcd12d8d250de140aac1703a973a70e6
internal/host/claimservingpark_transport.go servingParkRuntime.retainNoProcess authority C,J4 rev3=PROPOSED-seed; root coordinator observation and kernel no-process result; lifetime-bound retention before receipt publication 5ef08963d95589e3653e48d85ea12081a761be1901263303134eeb6696ed760f
+internal/host/claimservingpark_transport.go var openClaimServingParkAuthority authority C,J4 rev3=PROPOSED-seed; descriptor-relative private rootstate at /var/lib/carlos-claims/authority/serving-parks with creation enabled and public reads disabled file:Open@78 7a1db088a7240e6a65ba0bed96fc5f973a69f70c2273c4475c486a6e0359fb23
internal/host/claimservingpark_transport.go withServingParkRoot authority C,J4 rev3=PROPOSED-seed; private rootstate constructor and empty-bootstrap authority; binds retention callback and closes state on construction failure 1ef9ae1b2325c6fe809fef8d1f82b04828cb00e9cc1dd76858c0731f01543265
internal/host/claimservingparkroot_linux.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 728ae9a258bfcd70dd29cb4c26cea705faee6bcd49558b5336a99acd1311dc83
+internal/host/claimservingparkroot_linux.go servingParkRoot.completeServingDatabasePark authority C,J4 rev3=PROPOSED-seed; fresh paired enrollment, same current writer, root source and original/continuation stop history; legacy additionally authenticates persistent no-process or admitted predecessor evidence 8ca9bdd3c1209c6d5257bc07a7d12d0bab900e9c2911d0698077eb2116e5123c
+internal/host/claimservingparkroot_linux.go servingParkRoot.parkServingDatabase authority C,J4 rev3=PROPOSED-seed; fresh paired tags, root slot/record and retained admissions; post-retag replay selects the predecessor from the saved active tag-source stamp and revalidates its exact identity; recorded legacy work dispatches to separately locked fresh legacy authority; independently validated source records namespace parent before bounded quarantine recovery 8552695acfec22f46f6f2b17af955ae19290bdca3a9a7ee7c93dae0d28d6dbe0
+internal/host/claimservingparkroot_linux.go servingParkRoot.withAuthority authority C,J4 rev3=PROPOSED-seed; live edge peer reauthentication under namespace and host locks before census, inventory and final claim checks; root request entry refuses nil connections d3582f0760b4459721b3e6040cda738b105c1da19cc5d0746d04fea26e313b5c
+internal/host/claimservingparkroot_linux.go servingParkRoot.withRootInstallation authority C,J4 rev3=PROPOSED-seed; fresh claim, root installation and directory inventory, eligible enrollment, paired tags, census gate and post-input claim reread under namespace then host guard; startup grants no new identity file:Read@2113 8060d3a2628f86257c3600c986242e0d4957ee918214a89655af828dd3ddf93b
+internal/host/claimservingparkroot_linux.go servingRootStops authority C,J4 rev3=PROPOSED-seed; retained exact root serving receipts and known retired installation; same enrollment, session and region, with canonical recorded revision no newer than the active actor d371f01614c0bb64d3f412123fc9a90b8bee43d02b6c48e67fc89d9a35187005
+internal/host/claimservingparkroot_linux.go validateServingRootHistory authority C,J4 rev3=PROPOSED-seed; retained local successor admissions and byte-exact root stops for every persisted continuation; rejects relabelled or missing history c85a1be96de5fa61acd48c4adf9393d8e837880f5133a2f965f1104037e02329
+internal/host/claimservingparksource_linux.go servingParkStateDirectory authority C,J4 rev3=PROPOSED-seed; root installation/inventory snapshots and authoritative serving observation; exact account/app/record, revision/enrollment, canonical nested path, ownership and descriptor identity; root acquires these inputs under the host lock through the typed journal service ec4a121330c5c8f27343f21af0bbe458cced885d4754e96c0d87b12d9b4a8f35
+internal/host/claimservingquarantine_linux.go servingParkFS.checkNamespaceQuarantine authority C,J4 rev3=PROPOSED-seed; strict private quarantine completion record and exact published namespace identity; ordinary parking cannot use a replacement before durable quarantine completion; completed quarantine and namespace must share the current kernel boot file:Read@95 236143830da52e9b0d8778691d2cd1ae0e55c970fe6ed1ccc4750dbc8c0e3937
+internal/host/claimservingquarantine_linux.go servingParkFS.checkQuarantine authority C,J4 rev3=PROPOSED-seed; no-follow descriptor identity of the root-chosen quarantine basename; does not inspect children or authorize deletion bbf7d5600fc6fcf0c60d2b7c9e83d8ac57f9edaca3367c9c6e3fadffab303d8d
+internal/host/claimservingquarantine_linux.go servingParkFS.checkRetainedDirectory authority C,J4 rev3=PROPOSED-seed; no-follow descriptor identity of a retained directory; exact mount, device and inode; never reads children 8d3d2b44f7b8f08e7a19c1741a9979d7181cc76156be8ffbf43190a43c67de9d
+internal/host/claimservingquarantine_linux.go servingParkFS.createQuarantineReplacement authority C,J4 rev3=PROPOSED-seed; unrecorded root directory identity captured only for retention; one durable bounded retention intent, identity-checked move, new mkdir and parent fsync before fresh identity publication; repeated loss refuses cfbc39cc48eb37af01c972c2c2309100cfc1cff38f95addc0b7e2ded40ac46da
+internal/host/claimservingquarantine_linux.go servingParkFS.moveQuarantine authority C,J4 rev3=PROPOSED-seed; recorded obstruction device, inode and mount before and after same-mount NOREPLACE rename; lost-reply replay requires exact destination identity 12801fc3c906d28c41919fed8eb425dd01a480c42c7cd51653f686dc1f76a794
+internal/host/claimservingquarantine_linux.go servingParkFS.moveRetainedDirectory authority C,J4 rev3=PROPOSED-seed; pinned source and retained destination identities around no-replace same-mount rename and parent sync; lost reply accepts only the recorded destination 8e341682ab62bfe67680fbc3a478fb78d257444a3b9bde23daa98b7a13d1ada5
+internal/host/claimservingquarantine_linux.go servingParkFS.recoverNamespace authority C,J4 rev3=PROPOSED-seed; independently recorded parent, effective mount and strict private quarantine records; retains one obstruction plus one unrecorded replacement; completed replay is read-only; no content deletion; parent, prior namespace and quarantine boot identities must match the current boot; no mount-number refresh; replacement namespace captures its own persistent proof; previous proof remains immutable file:Fstat@454,file:Read@1075,file:Read@1391,file:Read@1986,file:Read@2327,file:Readdirnames@3984 aab5a7073809ff4d48a1749c952114a62d6ae45d44a8b1147c4df948917ee9ab
+internal/host/claimservingquarantine_linux.go servingParkQuarantine.name authority C,J4 rev3=PROPOSED-seed; root-generated 128-bit quarantine identity converted to a fixed basename; no caller pathname 08554ada2338c42eb0c53d7b39709583f6af12e51f5a005f42dc3bd44906b4ee
+internal/host/claimservingquarantine_linux.go servingParkQuarantine.unrecordedName authority C,J4 rev3=PROPOSED-seed; fixed retention basename from the existing private quarantine nonce; no caller path or new nonce on retry e037771d8e939a426c26104c10b92a2b205376d14574a92015f6f5a4bff90375
+internal/host/claimservingquarantine_linux.go servingParkQuarantine.validate authority C,J4 rev3=PROPOSED-seed; private quarantine shape and phase checks; original, prior, unrecorded and fresh directories must have distinct same-mount identities; retention only; prior namespace and quarantine must carry the same boot scope 285f4c277e15af5001ff7d69723d1ff84a6137c8d08bedf5a7c8580d2e246398
+internal/host/claimservingquarantine_linux.go type servingParkQuarantine authority C,J4 rev3=PROPOSED-seed; private versioned quarantine intent; additive unrecorded-replacement identity and rename progress bounded to one spare; no artifact deletion authority; additive BootID scopes parent, obstruction, prior root and replacement identities 602321aa523aa4d4b9bade287b70abf2c86f4daeb6bb245bc7f5412b688a1680
+internal/host/claimservingreboot_binding.go servingParkBootBinding.directory authority C,J4 rev3=PROPOSED-seed; private current-boot bindings; validate original boot/parent before translating only mount identities; historical retirement/legacy bytes preserved 791c49920c31e1db6826f6d285731669226f12dd0f277ce04457630b6df014d7
+internal/host/claimservingreboot_binding.go servingParkBootBinding.record authority C,J4 rev3=PROPOSED-seed; private current-boot bindings; validate original boot/parent before translating only mount identities; historical retirement/legacy bytes preserved 0ad6e9a4a807af142d647ed5edfbd772a32dcf9da5f3eaffeafaab019a677b57
+internal/host/claimservingreboot_binding.go servingParkLedger.bindRecord authority C,J4 rev3=PROPOSED-seed; private current-boot bindings; validate original boot/parent before translating only mount identities; historical retirement/legacy bytes preserved e98b72dbd294f4a0b57bad42a2d6b3a429d56518db56dbdf36405529130aa513
+internal/host/claimservingreboot_binding.go servingParkReadBinding authority C,J4 rev3=PROPOSED-seed; private current-boot bindings; validate original boot/parent before translating only mount identities; historical retirement/legacy bytes preserved file:Read@111 1b4ea3111bdadb39201c67fe3ba5bc5505ef5f7da2a3f9beb485dbe8bc72708e
+internal/host/claimservingreboot_binding.go type servingParkBootBinding authority C,J4 rev3=PROPOSED-seed; private current-boot bindings; validate original boot/parent before translating only mount identities; historical retirement/legacy bytes preserved 4ecb75e5992c97d9d0445210689aa1f659f4c1fb8c8dd81ba4b84bd639f7fee7
+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 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; immutable historical records and live persistent source/park identities; refuses inconsistent boot, mount, filesystem or ownership file:Readdirnames@2515 48a675389be8a8184e428c50971ddb8b7e8c33f8ceccef3703858fc307415053
+internal/host/claimservingreboot_linux.go servingParkFS.rebootDirectory authority C,J4 rev3=PROPOSED-seed; pinned descriptor device/inode, current mount and historical filesystem handle, owner and mode; no effects 2224be87510731990ca620abc0624a80675eca21228e561fa3e540d2ac8273c0
+internal/host/claimservingreboot_linux.go servingParkRebootFileMatches authority C,J4 rev3=PROPOSED-seed; historical file handle and current mount compared to source/event observations; lost rename and unlink replies require original progress; no writes 180ec916768fd2d012e689199963586dabbaa77553cea9d806882d1eacb15a75
+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
+internal/host/claimservingreboot_prepare_linux.go servingParkLedger.observeRebootPreparation authority C,J4 rev3=PROPOSED-seed; persistent source/event/file inspection, original retirement, validated slot references and exact permanent legacy consumption; current installation binds incomplete records; malformed or stale evidence refuses 252a5c4463f979e5442585b66d06453e279f45ac17fe4f0ff993df7dee678da6
+internal/host/claimservingreboot_prepare_linux.go servingParkLedger.prepareRebootLocked authority C,J4 rev3=PROPOSED-seed; historical retirement, fresh root installation, census, exact claim, stop and launch history; per-target-boot immutable preparation, repeated validation file:Read@625 a355b666f2aad99389f1c665a57a9b1603db5511538c68b64433bbde8beda0fb
+internal/host/claimservingreboot_prepare_linux.go servingParkRoot.prepareOrCommitServingReboot authority C,J4 rev3=PROPOSED-seed; historical retirement, fresh root installation, census, exact claim, stop and launch history; per-target-boot immutable preparation, repeated validation file:Read@1505 f32543a935745e52031a532ef3c4495a9299655b2401c9e18ba8c50e5ba60a4c
+internal/host/claimservingreboot_prepare_linux.go servingParkRoot.prepareServingReboot authority C,J4 rev3=PROPOSED-seed; historical retirement, fresh root installation, census, exact claim, stop and launch history; per-target-boot immutable preparation, repeated validation 61b3cf83d4e9f7b8ca2a12b7e5a7a0a512be24247375a36897af5e6830cec9f8
+internal/host/claimservingreboot_prepare_linux.go type servingParkRebootAuthority authority C,J4 rev3=PROPOSED-seed; current claim ETag, independently selected actor, directory and paired tags for an incomplete retirement; preparation only, never active mount authority 17bec014a0d01c9882d41727f6e9e97ad850104225c3c7f1f4c2ce09bc39bb34
+internal/host/claimservingreboot_prepare_linux.go type servingParkRebootPreparation authority C,J4 rev3=PROPOSED-seed; private pending reboot evidence containing unchanged retirement, topology, slot and legacy history plus separately observed current authority; no filesystem or transport consumer a52a92a9b9d6507a2cbdffb1d8a76af3a541a023d421690ce588cf13a802c0d9
+internal/host/claimservingretire.go bindServingArtifactRetirement authority C,J4 rev3=PROPOSED-seed; in-process root-client dependency and existing operation callbacks; serving dispatch does not replace landing/recovery dispatch 19ade85561b59f2f0b75f4f50b33333ab2598cba7d20e596893530c6a2f8102a
+internal/host/claimservingretire.go claimServingArtifactRetirement.BeforeLegacyInstall authority C,J4 rev3=PROPOSED-seed; admitted tuple and observed predecessor selection; root legacy authorization outside Work followed by guarded enrollment e8c23b09776dd4367e2a0fb5b85102747c3fe14cd45581f24e502e61b7fbcfd0
+internal/host/claimservingretire.go claimServingArtifactRetirement.EnrollLocked authority C,J4 rev3=PROPOSED-seed; completed root authorization cached for exact writer, fresh current tags and source absence; missing or stale permission refuses local mutation cf33600f5c3faaa0cde5658e53b1657ceadb7340244a68c9791441834a372729
+internal/host/claimservingretire.go claimServingArtifactRetirement.Recover authority C,J4 rev3=PROPOSED-seed; admitted serving observation and launch inputs; resumes retirement without treating absent marker as enrollment authority 674a5a3c598cbdc29494a3cfc0859dd632c41be154a966ca7684f4440cbccdcb
+internal/host/claimservingretire.go claimServingArtifactRetirement.Retire authority C,J4 rev3=PROPOSED-seed; captured predecessor generation; delegates only to serving recovery and root parking contract 6cf2e52f7e2aa511622268dd0a8dd37f1713e5beb4eba920e70057c2c90c32a3
+internal/host/claimservingretire.go claimServingArtifactRetirement.enrollPairLocked authority C,J4 rev3=PROPOSED-seed; fresh claim and root-bound source directory; descriptor-relative absence before and after paired tag CAS; exact readback resolves uncertain CAS 351faf39946ade5f9166b79c0a177001c8b4287ee698ce37c2c1e8337b534c30
+internal/host/claimservingretire.go claimServingArtifactRetirement.finish authority C,J4 rev3=PROPOSED-seed; root authorization; guarded paired-tag update before root completion; in-memory final-install permission only after completion 4ecf52c799a177f0df88d0429eb5bffc8d485045be065eb0107d0821772b0a29
+internal/host/claimservingretire.go claimServingArtifactRetirement.forgetCompletion authority C,J4 rev3=PROPOSED-seed; in-memory completed authorization for the selected host; failed replay must invalidate prior final-install permission 771a11b74e74ca98cbb51a1da72ecffea91801c2b622448a559eb1bdc6f7b3b5
+internal/host/claimservingretire.go claimServingArtifactRetirement.recover authority C,J4 rev3=PROPOSED-seed; fresh paired tags and predecessor admission; stop evidence before path-free root request; existing root record required for current-tag replay 00755911d37655eea3449e3fd69c884dd955a4f7d67487efa7cc6b9b9c7514f9
+internal/host/claimservingretire.go claimServingArtifactRetirement.snapshot authority C,J4 rev3=PROPOSED-seed; fresh claim and nested database path; paired inventory stamps, normalized paths and ancestor conflicts before root requests file:Read@908 202138f761733b9853154f6b9382e68d74e4e933a600949cdd284a9cf6df765a
+internal/host/claimservingretire.go claimServingArtifactRetirement.stopped authority C,J4 rev3=PROPOSED-seed; completed serving fence receipts from the existing Guard backend; exact predecessor and byte-identical stop evidence ea358c5594a469b11d6255df9da2f0567f5d911fb94e1a3702f7cdc4f60c7751
+internal/host/claimservingretire.go claimServingArtifactRetirement.validateAuthorization authority C,J4 rev3=PROPOSED-seed; root-returned retirement identity, original and continuing successor tuples, tag source stamp and source-directory identity 8324a81ca00f5d6cf79a38560b7a99f4de65d203545074b24bc2144fc26ac52d
+internal/host/claimservingretire.go servingInventoryPair authority C,J4 rev3=PROPOSED-seed; normalized inventory paths and stamps; paired ownership and ancestor-conflict validation shared by preflight and CAS 700cd20c58f0bfb130c54810a5baf1f1df0f89f8e4cce16716d7961cde4d554a
+internal/host/claimservingretire.go servingInventoryTransition authority C,J4 rev3=PROPOSED-seed; claim writer identity and complete local inventory; changes only database/marker pair and preserves other paths and boxes 2f4024d4d05b348e4d2b9e2fa4e25bbda69575fa8ac495c4f986444527425d48
+internal/host/claimservingretire.go servingPredecessor authority C,J4 rev3=PROPOSED-seed; retained local cold admission at the strictly older installation stamp; exact generation, box and region e5d5534da88e3bbbc051c8f3c19c9cfd14918d84bd6a0206faf87a2a612c3a57
+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; historical namespace parent and persistent filesystem handles; mount numbers alone never establish reboot identity file:Read@501,file:Read@956 d17c8bec59bd873dcf72479b066dd715dc1f329011af3f36e9c43d7291553549
+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 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
+internal/host/claimstop_run.go RunInstanceControl authority C,D,G rev3=PROPOSED-seed; instance-control lifecycle; start cancellable root park maintenance and join it before closing private state 6adf5d9e0bbc87b7e010fb445bf624290fe63b620fb8b42941724e8ad7407bd0
+internal/host/claimwakeassembly.go ClaimWakeAssembly.Build authority C,G,J1,J4 rev3=PROPOSED-seed; serving metadata/layout prerequisite before retry settlement and lease acquisition; existing launch authority unchanged file:Command@9968,file:Prepare@7029,file:Read@1348,file:Read@1442,registry:Backing@2672,registry:Host@12582,registry:Host@3354,registry:Host@3957,registry:Host@4002,registry:Host@4709,registry:Host@5430,registry:Host@6477,registry:Host@7185,registry:Host@7858,registry:Host@9166,registry:Seed@2655,registry:Tunnel@2727,registry:Unit@2692,whole:Route@10106,whole:Route@14302,whole:Route@14374,whole:Route@2899,whole:Route@5830 1ecfe167224d33fb7dcf12f2db34e86e542b324b912436da98b2b0e46166d684
+internal/host/claimwakestop.go ClaimAssembly.collectCapturedStop authority C,G,J4 rev3=PROPOSED-seed; fresh claim and verified local stop identity; session-zero legacy holder may name only the local writer box, with no admissions; root independently proves and retains no-process evidence file:Exec@2994,file:Read@605 d4cd80663dfae5f9737a74e319d3423f4ef853e1843666b02b4cd2341e869d72
+internal/host/movesfiles.go movesFiles.Establish authority C,H3 rev3=PROPOSED-seed; ordinary origin proof and single C8 commit; private seed checks exact admitted writer and stages shared snapshot; no armed live provider 172fe8888da88c249b7990bd9fb4bd2821bd402fb96ab52af13c58500f31d50f
+internal/host/relocationflush.go ReleaseFlushFiles.Flush authority C,J4 rev3=PROPOSED-seed; existing release-flush authority and safe source descriptor; shared wake/files staging preserves single manifest commit and unstable-file refusal file:Open@772 7cc12267c16d9d1ae1116c9d10528f97d3983c77fb6486e0aac73142d8999c4b
internal/host/rootexecution_runtime.go claimExecutionRuntime.Close authority C,J4 rev3=PROPOSED-seed; existing execution monitor lifetime plus serving-park lifetime; both share caller deadline before authority stores close 08352d84ef3a8cd55924338abc3d33229f2216a608c0daef707aec8c472e2a8e
+internal/hostbox/coldhandover.go ColdHandover.Run authority C,J4 rev3=PROPOSED-seed; production operation-only gate refuses destructive serving handover, including old pending journals; existing move effects unchanged file:Exec@1027,file:Lstat@9838,file:OpenRoot@9760,file:Read@1379,registry:Addr@3654,registry:Addrs@3672,registry:Backing@3691,registry:Canary@3748,registry:ClaimIncarnation@3532,registry:ClaimIncarnation@4034,registry:ClaimOwner@10588,registry:ClaimOwner@4116,registry:ClaimOwner@4136,registry:ClaimOwner@6011,registry:ClaimOwner@7439,registry:ClaimOwnership@10180,registry:ClaimOwnership@3502,registry:ClaimOwnership@4480,registry:ClaimOwnership@4698,registry:ClaimOwnership@4731,registry:ClaimRowID@10478,registry:ClaimRowID@10559,registry:ClaimRowID@4154,registry:ClaimRowID@4762,registry:ClaimRowID@7464,registry:ClaimStamp@3577,registry:ClaimStamp@4081,registry:Host@7401,registry:Kind@3610,registry:Name@4816,registry:Name@7420,registry:Platform@3732,registry:Registration@4180,registry:Registration@7489,registry:Replica@3762,registry:Route@10180,registry:Route@4480,registry:Route@4731,registry:Route@5262,registry:Route@6011,registry:Timers@4679,registry:Timers@4698,registry:Timers@4762,registry:Timers@4816,registry:Timers@7276,registry:Tunnel@3712,registry:Unit@3636,whole:ClaimOwnership@10194,whole:ClaimOwnership@3506,whole:ClaimOwnership@4492,whole:ClaimOwnership@4714,whole:ClaimOwnership@4743,whole:ColdRows@10182,whole:ColdRows@11294,whole:ColdRows@11304,whole:ColdRows@4977,whole:ColdRows@4985,whole:ColdRows@4993,whole:ColdRows@5000,whole:ColdRows@5264,whole:ColdRows@6013,whole:ColdRows@7278,whole:Route@10188,whole:Route@3487,whole:Route@3847,whole:Route@4279,whole:Route@4287,whole:Route@4338,whole:Route@4346,whole:Route@4486,whole:Route@4737,whole:Route@5271,whole:Route@6020,whole:Timer@4018,whole:Timer@4292,whole:Timer@4300,whole:Timer@4351,whole:Timer@4389,whole:Timer@4685,whole:Timer@4704,whole:Timer@4768,whole:Timer@4822,whole:Timer@7285 050ae1e6818687e19e7a0cd443c09e837e54bdc00f4ea1feb6430cfedd2b5343
+internal/hostbox/coldhandover.go type ColdHandover authority C,J4 rev3=PROPOSED-seed; operation-only production cold-handover policy; no source-preserving serving authority from operation cleanup 874b2b74ea349dd93655d4e11f4d9e97752c3f216593ebb922d819caba7981c4
+internal/hostbox/coldhandover.go type coldJournal authority C,J4 rev3=PROPOSED-seed; additive root installation snapshot for serving transfer; retained predecessor inventory, receipt, row contents and exact successor 2f5e48fec1ec2b6f1774f905fc27f8c8d0b33605f260e1ea483cbf55ace9b53e
internal/hostbox/journalservice.go NewJournalService authority C,J4 rev3=PROPOSED-seed; root-local SQLite launch journal; existing schema and durable receipts plus a cancellable single-request parking gate file:Exec@1218,file:Exec@1551,file:Exec@1692,file:Exec@823,file:Open@1834,file:QueryRow@1037,file:QueryRow@1373 1cdff9ce046394bd609b41f01bb0c86164e6513b44b8d2485f5efbea79b0235c
internal/hostbox/journalservice.go journalPrepare authority C,J4 rev3=PROPOSED-seed; per-call nonce and issue time; selects v4 for parking identities without changing prior encoders file:Read@184,file:Read@333 c8d747d257f0ad48a7bfc645de0a9c50a05fec4c333a75df2a1360d9de9fb9f1
internal/hostbox/journalservice.go journalRequestHash authority C,J4 rev3=PROPOSED-seed; frozen versioned request tuples; v4 includes every parking selector while v1 through v3 remain unchanged aa4856b2927d21a68cd14a360b09a6d02057afadbe71fffbfee682aef3719ac4
internal/hostbox/journalservice.go validateJournalRequest authority C,J4 rev3=PROPOSED-seed; typed versioned envelope; rejects parking fields on older versions and delegates v4 to strict selector validation 2d05c14a0afd5bbe4b5dd9e03e7eb885329caec62fb360e02256183d2894d351
+internal/hostbox/restore.go ColdRestore.Install authority C,J2,J4 rev3=PROPOSED-seed; selected database versions, local generation marker and restored SQLite data; legacy callback outside Work and enrollment callback before any local write file:CreateTemp@2200,file:Open@3189 7054cf1c887f4b6b4e20b62fd94e17949770c675cbe7e8efe2accf7f20b1c7ba
+internal/hostbox/restore.go ColdRestore.InstallEmpty authority C rev3=PROPOSED-seed; existing empty-bootstrap proof and local marker; legacy callback outside Work and enrollment callback before purge or marker creation f8c0ea4eaf3fa692abe52dd3b0799c9239bcb0d761c69644cc4be78d51d74c04
+internal/hostbox/restore.go loadPinned authority C,J2,J4 rev3=PROPOSED-seed; exact selected LTX versions and original trailer/pin proof; validated bytes normalized for Litestream while retaining original database checksum for final comparison file:ReadAll@662 1e2b22c7c501d31cd1f2cb3be0fe419fa7e25afde87c6ecb43ca85edb7dbc3a4
+internal/hostbox/servinghandover.go ServingHandover.Recover authority C,J4 rev3=PROPOSED-seed; trusted serving transfer journals and pending root barriers; replay exact original transfer before prelaunch 7ec3bd1b929ca3b3fd7d20c53437740d54086fbb2ae1fc8c545dbca3e388bab1
+internal/hostbox/servinghandover.go ServingHandover.Run authority C,J4 rev3=PROPOSED-seed; root installation/inventory snapshot, exact stopped predecessor, canonical nested row, no-follow state identity, successor launch absence and private journal; fenced inventory/row transfer with retained old evidence registry:Account@2154,registry:Addr@2373,registry:Addrs@2391,registry:App@2198,registry:Backing@2410,registry:Canary@2467,registry:ClaimOwner@5480,registry:ClaimOwnership@2841,registry:ClaimOwnership@3057,registry:ClaimOwnership@3090,registry:ClaimRowID@3120,registry:ClaimRowID@5551,registry:ClaimRowID@5725,registry:DBPath@2286,registry:Kind@2128,registry:Name@3174,registry:Name@5690,registry:Platform@2451,registry:Replica@2481,registry:Route@2841,registry:Route@3090,registry:Route@5480,registry:Route@5551,registry:Socket@2234,registry:Timers@3039,registry:Timers@3057,registry:Timers@3120,registry:Timers@3174,registry:Timers@5633,registry:Tunnel@2431,registry:Unit@2355,whole:ClaimOwnership@2853,whole:ClaimOwnership@3073,whole:ClaimOwnership@3102,whole:ColdRows@3514,whole:ColdRows@3522,whole:ColdRows@3530,whole:ColdRows@3537,whole:ColdRows@5482,whole:ColdRows@5553,whole:ColdRows@5635,whole:ColdRows@6216,whole:ColdRows@6226,whole:Route@2114,whole:Route@2643,whole:Route@2651,whole:Route@2701,whole:Route@2709,whole:Route@2847,whole:Route@3096,whole:Route@5488,whole:Route@5559,whole:Timer@2656,whole:Timer@2664,whole:Timer@2714,whole:Timer@2752,whole:Timer@3045,whole:Timer@3063,whole:Timer@3126,whole:Timer@3180,whole:Timer@5641 01b9e603a5f2a2476017777a7d02fafc76424d491201d6757170d66997fa481a
+internal/hostbox/servinghandover.go ServingHandover.at authority C,J4 rev3=PROPOSED-seed; in-process interruption seam; nil in production 0178e5fcfb2cc194fb4f33f0f230b52849ddeb87a4b7c4e6cfb640e8b2f00b8a
+internal/hostbox/servinghandover.go ServingHandover.validate authority C,J4 rev3=PROPOSED-seed; fresh serving claim, unchanged root-recorded inventory, exact recorded state-directory inode/type, complete route/timer set and production authority callback file:Exec@258,registry:ClaimIncarnation@2164,registry:ClaimOwner@1379,registry:ClaimOwner@2238,registry:ClaimOwnership@2132,registry:ClaimRowID@2273,registry:ClaimStamp@2207,registry:Name@2118,registry:Registration@2310,registry:Route@1033,registry:Route@1379,registry:Timers@2064,registry:Timers@2507,whole:ClaimOwnership@2138,whole:ColdRows@1035,whole:ColdRows@1381,whole:ColdRows@2066,whole:ColdRows@2509,whole:Route@1042,whole:Route@1388,whole:Timer@2073,whole:Timer@2516 2985e74f1c0aed6a6470e4ee4f03e794e32a97806542a51045a21e76ba7bce03
+internal/hostbox/servinghandover.go type ServingHandover authority C,J4 rev3=PROPOSED-seed; root-only serving transfer dependencies and validation callback; source state remains in place d56e1f341efd969be8757c2a2440170192d30338095c28ee9cba337de7a81bd6
+internal/hostbox/servingpark.go LaunchJournal.RootSessionLaunches authority C,J4 rev3=PROPOSED-seed; root-local persisted launch records for exact host/incarnation/stamp/session/box/enrollment; refuses agent journal transport cbbb9378bc53ac499972bed5fb654e05856a9afd7256d55323a717140ffcba18
internal/hostbox/servingpark_service.go JournalService.ExecuteServingPark authority C,J4 rev3=PROPOSED-seed; exact journal request and authenticated enrollment; durable pending receipt before filesystem effects, completed reply after effects; uncertain outcomes retain request df93855480924b39cf49edbeedd136799a7e327860ee4dec7742796d3d428333
internal/hostbox/servingpark_service.go JournalService.prepareServingPark authority C,J4 rev3=PROPOSED-seed; SQLite peer, digest, reply and retention boundary; exact completed replay, bounded request age and clock-rollback rejection before callback; existing bounded receipt pruning file:QueryRowContext@1050,file:QueryRowContext@331 83f1bae59de484a9d1cae09ce8e359aa173634c091203ea3ce4f6e8639e20d5e
internal/hostbox/servingpark_transport.go ServingParkClient.Complete authority C,J4 rev3=PROPOSED-seed; root retirement ID and successor tuple; no caller completion flag, filesystem path or deadline 355c7f3cc975b38456636038f2adead7c742fc08f423125e563d80ffae2f55aa
@@ -119,94 +238,14 @@ internal/hostbox/servingpark_transport.go ServingParkOperation authority C,J4 re
internal/hostbox/servingpark_transport.go journalServingParkV4 authority C,J4 rev3=PROPOSED-seed; explicit frozen identity and selector tuple for v4 request hashing d5c2405c66384a349002bdd5856d5d1af5c1fdd5c506b9389a8f9cf5f74504e9
internal/hostbox/servingpark_transport.go servingParkResponse authority C,J4 rev3=PROPOSED-seed; typed retirement reply and exact requested successor; missing or mismatched result stays uncertain fc8602dfe28b78ee04809509b1357778777b26c191b089a47d70defbca8ffd88
internal/hostbox/servingpark_transport.go validateServingParkRequest authority C,J4 rev3=PROPOSED-seed; v4 envelope, nonce, digest and identities; rejects mixed operation fields and unsupported pressure before any effect 2c87ae130afc670925c87f52b9688fdc24d940d0a5fcdbf09667df2b662f7f59
-internal/host/claimservingpark_transport.go var openClaimServingParkAuthority authority C,J4 rev3=PROPOSED-seed; descriptor-relative private rootstate at /var/lib/carlos-claims/authority/serving-parks with creation enabled and public reads disabled file:Open@78 7a1db088a7240e6a65ba0bed96fc5f973a69f70c2273c4475c486a6e0359fb23
-internal/host/claimwakestop.go ClaimAssembly.collectCapturedStop authority C,G,J4 rev3=PROPOSED-seed; fresh claim and verified local stop identity; session-zero legacy holder may name only the local writer box, with no admissions; root independently proves and retains no-process evidence file:Exec@2994,file:Read@605 d4cd80663dfae5f9737a74e319d3423f4ef853e1843666b02b4cd2341e869d72
-internal/hostbox/restore.go loadPinned authority C,J2,J4 rev3=PROPOSED-seed; exact selected LTX versions and original trailer/pin proof; validated bytes normalized for Litestream while retaining original database checksum for final comparison file:ReadAll@662 1e2b22c7c501d31cd1f2cb3be0fe419fa7e25afde87c6ecb43ca85edb7dbc3a4
-internal/host/claimretire_transport.go ClaimRootHTTP.RecoverPending authority C,J4 rev3=PROPOSED-seed; startup serving handover recovery before ordinary cold and root recovery; old destructive serving intents refuse 627b40daecf11e1a6a6c9f92aeb3ebcf226131da6dfa584ed85559c8b9b5ff30
-internal/host/claimservingparkroot_linux.go servingParkRoot.withRootInstallation authority C,J4 rev3=PROPOSED-seed; fresh claim, root installation and directory inventory, eligible enrollment, paired tags, census gate and post-input claim reread under namespace then host guard; startup grants no new identity file:Read@2113 8060d3a2628f86257c3600c986242e0d4957ee918214a89655af828dd3ddf93b
-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.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_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_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 var errServingParkRetagPending authority C,J4 rev3=PROPOSED-seed; fixed pending-retag refusal; startup cannot write the independent claim writer tags 8fa87ebef4defae4fc1733528a76e0cc260711c2f5a664308e5368d69286d45b
-internal/host/claimservingpark_recovery.go type servingParkRecoveryPage authority C,J4 rev3=PROPOSED-seed; private bounded recovery results, cursor and incomplete indicator; never a transport selector c2fb4445ed50b5237b72e8a5fff9b1b2f6c9dd1397473eeb2c78a6b8c2804b27
-internal/host/claimservingpark_recovery.go type servingParkRecoveryResult authority C,J4 rev3=PROPOSED-seed; root-derived host and record ID with local recovery outcome 4dd13c07e97470a6c5aa4fa98d326de8947422018f0d30d5c5af32d753cfcc39
-internal/host/claimservingpark_recovery.go servingParkLedger.recoveryIDs authority C,J4 rev3=PROPOSED-seed; private rootstate names; sorted bounded retirement page and continuation cursor; malformed record IDs retained for reporting 5b450cd0fb6ae6694bca2f9f75fe972c0d65681981a4caddde7e5c7269108782
-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
-internal/host/claimservingpark_recovery.go servingParkRuntime.recoverPending authority C,J4 rev3=PROPOSED-seed; service lifetime cancellation and join; bounded startup results rendered only with indexed host/record/fixed-reason text 0aa3ef7edd0ac2c12b4221907ccd5927e7e3e03a4c2c0b77b669bc10cc49c241
-internal/host/claimservinglegacy.go sameServingLegacyConsumption authority C,J4 rev3=PROPOSED-seed; immutable legacy consumption and retained retirement source identities; compares original evidence while ignoring per-artifact progress and the separate collection flag; source boot and persistent directory identity must remain equal to the permanent legacy-consumption record dff50631bc0eac3586c220ea2a131cf233a35b20e3b3f5e8fe35904fcc43c722
-internal/host/claimservingpark_recovery_linux.go servingParkRoot.recoverServingPark authority C,J4 rev3=PROPOSED-seed; retained intent, exact current successor, independently rechecked installation, enrollment, receipt and local launch history; resumes original park and completes only existing paired tags; no retry settlement c02a2727bb45c39df280800ade0aaae6dfba18b268e48bea5fa2f7fcb7a2af23
-internal/host/claimservingpark_recovery_linux.go servingParkRoot.recoverServingParks authority C,J4 rev3=PROPOSED-seed; private record page and per-record validation; continues past corrupt or refused hosts and reports incomplete enumeration; completed deletion separated from live host authority; first page recovers namespace under its lock using separate private parent evidence, without selecting a live host fb838aec596eaf1b26069f16006b5b2512284710c9bcebf8abfc65dbc9eb13cf
-internal/host/claimservinghandover.go ClaimRootHTTP.servingHandover authority C,J4 rev3=PROPOSED-seed; root-only serving transfer constructor and existing private journal, inventory and registry dependencies 213a1885dd9aa5d0079cd52c4f88c6893ea5b43bfbfd83a67ec2c4697a83265a
-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 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/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/hostbox/coldhandover.go type ColdHandover authority C,J4 rev3=PROPOSED-seed; operation-only production cold-handover policy; no source-preserving serving authority from operation cleanup 874b2b74ea349dd93655d4e11f4d9e97752c3f216593ebb922d819caba7981c4
-internal/hostbox/coldhandover.go type coldJournal authority C,J4 rev3=PROPOSED-seed; additive root installation snapshot for serving transfer; retained predecessor inventory, receipt, row contents and exact successor 2f5e48fec1ec2b6f1774f905fc27f8c8d0b33605f260e1ea483cbf55ace9b53e
-internal/hostbox/coldhandover.go ColdHandover.Run authority C,J4 rev3=PROPOSED-seed; production operation-only gate refuses destructive serving handover, including old pending journals; existing move effects unchanged file:Exec@1027,file:Lstat@9838,file:OpenRoot@9760,file:Read@1379,registry:Addr@3654,registry:Addrs@3672,registry:Backing@3691,registry:Canary@3748,registry:ClaimIncarnation@3532,registry:ClaimIncarnation@4034,registry:ClaimOwner@10588,registry:ClaimOwner@4116,registry:ClaimOwner@4136,registry:ClaimOwner@6011,registry:ClaimOwner@7439,registry:ClaimOwnership@10180,registry:ClaimOwnership@3502,registry:ClaimOwnership@4480,registry:ClaimOwnership@4698,registry:ClaimOwnership@4731,registry:ClaimRowID@10478,registry:ClaimRowID@10559,registry:ClaimRowID@4154,registry:ClaimRowID@4762,registry:ClaimRowID@7464,registry:ClaimStamp@3577,registry:ClaimStamp@4081,registry:Host@7401,registry:Kind@3610,registry:Name@4816,registry:Name@7420,registry:Platform@3732,registry:Registration@4180,registry:Registration@7489,registry:Replica@3762,registry:Route@10180,registry:Route@4480,registry:Route@4731,registry:Route@5262,registry:Route@6011,registry:Timers@4679,registry:Timers@4698,registry:Timers@4762,registry:Timers@4816,registry:Timers@7276,registry:Tunnel@3712,registry:Unit@3636,whole:ClaimOwnership@10194,whole:ClaimOwnership@3506,whole:ClaimOwnership@4492,whole:ClaimOwnership@4714,whole:ClaimOwnership@4743,whole:ColdRows@10182,whole:ColdRows@11294,whole:ColdRows@11304,whole:ColdRows@4977,whole:ColdRows@4985,whole:ColdRows@4993,whole:ColdRows@5000,whole:ColdRows@5264,whole:ColdRows@6013,whole:ColdRows@7278,whole:Route@10188,whole:Route@3487,whole:Route@3847,whole:Route@4279,whole:Route@4287,whole:Route@4338,whole:Route@4346,whole:Route@4486,whole:Route@4737,whole:Route@5271,whole:Route@6020,whole:Timer@4018,whole:Timer@4292,whole:Timer@4300,whole:Timer@4351,whole:Timer@4389,whole:Timer@4685,whole:Timer@4704,whole:Timer@4768,whole:Timer@4822,whole:Timer@7285 050ae1e6818687e19e7a0cd443c09e837e54bdc00f4ea1feb6430cfedd2b5343
-internal/hostbox/servinghandover.go type ServingHandover authority C,J4 rev3=PROPOSED-seed; root-only serving transfer dependencies and validation callback; source state remains in place d56e1f341efd969be8757c2a2440170192d30338095c28ee9cba337de7a81bd6
-internal/hostbox/servinghandover.go ServingHandover.Run authority C,J4 rev3=PROPOSED-seed; root installation/inventory snapshot, exact stopped predecessor, canonical nested row, no-follow state identity, successor launch absence and private journal; fenced inventory/row transfer with retained old evidence registry:Account@2154,registry:Addr@2373,registry:Addrs@2391,registry:App@2198,registry:Backing@2410,registry:Canary@2467,registry:ClaimOwner@5480,registry:ClaimOwnership@2841,registry:ClaimOwnership@3057,registry:ClaimOwnership@3090,registry:ClaimRowID@3120,registry:ClaimRowID@5551,registry:ClaimRowID@5725,registry:DBPath@2286,registry:Kind@2128,registry:Name@3174,registry:Name@5690,registry:Platform@2451,registry:Replica@2481,registry:Route@2841,registry:Route@3090,registry:Route@5480,registry:Route@5551,registry:Socket@2234,registry:Timers@3039,registry:Timers@3057,registry:Timers@3120,registry:Timers@3174,registry:Timers@5633,registry:Tunnel@2431,registry:Unit@2355,whole:ClaimOwnership@2853,whole:ClaimOwnership@3073,whole:ClaimOwnership@3102,whole:ColdRows@3514,whole:ColdRows@3522,whole:ColdRows@3530,whole:ColdRows@3537,whole:ColdRows@5482,whole:ColdRows@5553,whole:ColdRows@5635,whole:ColdRows@6216,whole:ColdRows@6226,whole:Route@2114,whole:Route@2643,whole:Route@2651,whole:Route@2701,whole:Route@2709,whole:Route@2847,whole:Route@3096,whole:Route@5488,whole:Route@5559,whole:Timer@2656,whole:Timer@2664,whole:Timer@2714,whole:Timer@2752,whole:Timer@3045,whole:Timer@3063,whole:Timer@3126,whole:Timer@3180,whole:Timer@5641 01b9e603a5f2a2476017777a7d02fafc76424d491201d6757170d66997fa481a
-internal/hostbox/servinghandover.go ServingHandover.validate authority C,J4 rev3=PROPOSED-seed; fresh serving claim, unchanged root-recorded inventory, exact recorded state-directory inode/type, complete route/timer set and production authority callback file:Exec@258,registry:ClaimIncarnation@2164,registry:ClaimOwner@1379,registry:ClaimOwner@2238,registry:ClaimOwnership@2132,registry:ClaimRowID@2273,registry:ClaimStamp@2207,registry:Name@2118,registry:Registration@2310,registry:Route@1033,registry:Route@1379,registry:Timers@2064,registry:Timers@2507,whole:ClaimOwnership@2138,whole:ColdRows@1035,whole:ColdRows@1381,whole:ColdRows@2066,whole:ColdRows@2509,whole:Route@1042,whole:Route@1388,whole:Timer@2073,whole:Timer@2516 2985e74f1c0aed6a6470e4ee4f03e794e32a97806542a51045a21e76ba7bce03
-internal/hostbox/servinghandover.go ServingHandover.Recover authority C,J4 rev3=PROPOSED-seed; trusted serving transfer journals and pending root barriers; replay exact original transfer before prelaunch 7ec3bd1b929ca3b3fd7d20c53437740d54086fbb2ae1fc8c545dbca3e388bab1
-internal/hostbox/servinghandover.go ServingHandover.at authority C,J4 rev3=PROPOSED-seed; in-process interruption seam; nil in production 0178e5fcfb2cc194fb4f33f0f230b52849ddeb87a4b7c4e6cfb640e8b2f00b8a
+internal/hostfiles/seed.go Seed authority C,J4 rev3=PROPOSED-seed; already-open source directory and wake snapshot; shared staging only, unstable reads refuse; caller owns origin proof and manifest commit a45fe18025b9add89b6011ec3227679c89b11fe4ac6356317b71686ba8377d63
internal/hostguard/coldhandover.go Guard.BeginColdHandover authority C,J4 rev3=PROPOSED-seed; unchanged operation handover checks delegated to shared locked implementation f06625d82015893692dce315f8014a7f44d65de69f681a5102f418e9772b875e
internal/hostguard/coldhandover.go Guard.BeginServingHandover authority C,J4 rev3=PROPOSED-seed; serving-only root installation and empty-operation checks; session-zero authorization remains in root callback bd953e2794294c5638161eff30fd743000fbd467d6ea37df734668021d8f6c02
internal/hostguard/coldhandover.go Guard.beginColdHandover authority C,J4 rev3=PROPOSED-seed; fresh claim and root installation under host locks; exact completed stop for admitted predecessors, validated no-process path for session zero, and durable successor barrier 6bdeec90f4322aa0824557cd35950af428b25889e15badeb4b017ccbbf0aae70
-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/claimservingquarantine_linux.go servingParkFS.checkQuarantine authority C,J4 rev3=PROPOSED-seed; no-follow descriptor identity of the root-chosen quarantine basename; does not inspect children or authorize deletion bbf7d5600fc6fcf0c60d2b7c9e83d8ac57f9edaca3367c9c6e3fadffab303d8d
-internal/host/claimservingquarantine_linux.go servingParkFS.moveQuarantine authority C,J4 rev3=PROPOSED-seed; recorded obstruction device, inode and mount before and after same-mount NOREPLACE rename; lost-reply replay requires exact destination identity 12801fc3c906d28c41919fed8eb425dd01a480c42c7cd51653f686dc1f76a794
-internal/host/claimservingquarantine_linux.go servingParkFS.recoverNamespace authority C,J4 rev3=PROPOSED-seed; independently recorded parent, effective mount and strict private quarantine records; retains one obstruction plus one unrecorded replacement; completed replay is read-only; no content deletion; parent, prior namespace and quarantine boot identities must match the current boot; no mount-number refresh; replacement namespace captures its own persistent proof; previous proof remains immutable file:Fstat@454,file:Read@1075,file:Read@1391,file:Read@1986,file:Read@2327,file:Readdirnames@3984 aab5a7073809ff4d48a1749c952114a62d6ae45d44a8b1147c4df948917ee9ab
-internal/host/claimservingquarantine_linux.go servingParkQuarantine.name authority C,J4 rev3=PROPOSED-seed; root-generated 128-bit quarantine identity converted to a fixed basename; no caller pathname 08554ada2338c42eb0c53d7b39709583f6af12e51f5a005f42dc3bd44906b4ee
-internal/host/claimservingquarantine_linux.go servingParkQuarantine.validate authority C,J4 rev3=PROPOSED-seed; private quarantine shape and phase checks; original, prior, unrecorded and fresh directories must have distinct same-mount identities; retention only; prior namespace and quarantine must carry the same boot scope 285f4c277e15af5001ff7d69723d1ff84a6137c8d08bedf5a7c8580d2e246398
-internal/host/claimservingquarantine_linux.go type servingParkQuarantine authority C,J4 rev3=PROPOSED-seed; private versioned quarantine intent; additive unrecorded-replacement identity and rename progress bounded to one spare; no artifact deletion authority; additive BootID scopes parent, obstruction, prior root and replacement identities 602321aa523aa4d4b9bade287b70abf2c86f4daeb6bb245bc7f5412b688a1680
-internal/host/claimservingquarantine_linux.go servingParkFS.checkNamespaceQuarantine authority C,J4 rev3=PROPOSED-seed; strict private quarantine completion record and exact published namespace identity; ordinary parking cannot use a replacement before durable quarantine completion; completed quarantine and namespace must share the current kernel boot file:Read@95 236143830da52e9b0d8778691d2cd1ae0e55c970fe6ed1ccc4750dbc8c0e3937
-internal/host/claimservingnamespace_linux.go servingParkFS.namespaceParent authority A,C,J4 rev3=PROPOSED-seed; strict private parent record with nonzero device, inode and mount; missing authority differs from corrupt state file:Read@107 0b2be4cfd1acf24c2e031cbd6d7bb013832db708031a4b107987b9c652269ebe
-internal/host/claimservingnamespace_linux.go servingParkFS.recordNamespaceParent authority A,C,J4 rev3=PROPOSED-seed; root-validated source directory under namespace and host locks; current nosymfollow parent and safe permissions; existing parent, namespace and quarantine evidence constrain first capture and replay; capture current kernel boot with the authenticated parent; never infer missing historical boot evidence file:Fstat@536,file:Read@1132 a8cbc51a05a00ca6cfba4bc920673334ab21c651edf56f30e46066a3df585c7b
-internal/host/claimservingnamespace_linux.go servingParkFS.recoverRecordedNamespace authority A,C,J4 rev3=PROPOSED-seed; separate private parent identity, current mount and permissions, namespace and quarantine records; no parent capture at startup; bounded quarantine retention without artifact deletion; startup refuses missing or changed parent/namespace boot identity before quarantine work file:Fstat@425,file:Read@651,file:Read@840 08cb520599e196fbd90610b315cdc55e853ad5fe9f6cb187c2bc206517579090
-internal/host/claimservingnamespace_linux.go servingParkRoot.prepareServingNamespace authority C,J4 rev3=PROPOSED-seed; root-authorized retirement identity and existing record; full source/disposition validation precedes first parent write and bounded quarantine recovery; final ledger capture revalidates source 6411305a91f27caf14805823eb57d36b0549beef39541324e1f814c922ec5cfe
-internal/host/claimservingpark_records.go servingParkLedger.capture authority C,J4 rev3=PROPOSED-seed; no-follow source snapshot, root launch/stop disposition, legacy marker and empty-bootstrap refusal, and complete retirement-record validation; read-only prerequisite before parent or retirement writes e3f3c2c919d96ae0a02fe0ac1a4e8694851dce7b73b3a9968f93f4445f3f35b3
-internal/host/claimservingquarantine_linux.go servingParkQuarantine.unrecordedName authority C,J4 rev3=PROPOSED-seed; fixed retention basename from the existing private quarantine nonce; no caller path or new nonce on retry e037771d8e939a426c26104c10b92a2b205376d14574a92015f6f5a4bff90375
-internal/host/claimservingquarantine_linux.go servingParkFS.checkRetainedDirectory authority C,J4 rev3=PROPOSED-seed; no-follow descriptor identity of a retained directory; exact mount, device and inode; never reads children 8d3d2b44f7b8f08e7a19c1741a9979d7181cc76156be8ffbf43190a43c67de9d
-internal/host/claimservingquarantine_linux.go servingParkFS.moveRetainedDirectory authority C,J4 rev3=PROPOSED-seed; pinned source and retained destination identities around no-replace same-mount rename and parent sync; lost reply accepts only the recorded destination 8e341682ab62bfe67680fbc3a478fb78d257444a3b9bde23daa98b7a13d1ada5
-internal/host/claimservingquarantine_linux.go servingParkFS.createQuarantineReplacement authority C,J4 rev3=PROPOSED-seed; unrecorded root directory identity captured only for retention; one durable bounded retention intent, identity-checked move, new mkdir and parent fsync before fresh identity publication; repeated loss refuses cfbc39cc48eb37af01c972c2c2309100cfc1cff38f95addc0b7e2ded40ac46da
-internal/host/claimservingdisplaced_linux.go const servingParkRootSearchLimit authority C,J4 rev3=PROPOSED-seed; fixed bound of 128 immediate parent entries; overflow refuses before opening candidate contents or deleting artifacts f7767fc1ea8f855aa6ed5e145e5b576c061c374876b57291f7182ebbdd899595
-internal/host/claimservingdisplaced_linux.go servingParkFS.eventRoot authority C,J4 rev3=PROPOSED-seed; current namespace and recorded retirement root; selects only current or independently recorded prior namespace; ambiguous old absent-event records refuse file:Read@562 7c47b53f88059eea930d483465570fc7205b3cec27a118472071ca4d6df7d5e7
-internal/host/claimservingdisplaced_linux.go servingParkFS.displacedRoot authority C,J4 rev3=PROPOSED-seed; completed private quarantine and prior namespace identity; no-follow bounded parent listing/statx and exact root-owned directory open; same device/inode/mount and safe parent recheck; no unknown child traversal file:Fstat@649,file:Read@144,file:Readdirnames@939,file:Statx@1259 762661ee74aaed30ca1147160a995786ad3a55200f6ffd25106125799ff0c74d
-internal/host/claimservingdisplaced_linux.go servingParkFS.recordEventRoot authority C,J4 rev3=PROPOSED-seed; descriptor identity of an already verified event root; persist old-record namespace before first deletion; existing namespace must match exactly b4febb444cd09cd5ed45fc616d172966038d71026024c89dfce9e8c530435756
-internal/host/claimservingcollection.go servingParkLedger.collectClosedLocked authority C,J4 rev3=PROPOSED-seed; private completed retirement, durable replay deadline, slot references and legacy-consumption evidence; preserve referenced records, mark permanent legacy consumption, then remove only obsolete private record and lock after 24 hours; no data deletion authority 97201d6a0b793715b5e8c0c0abb36d1c303e3b38f3d0e1a97aadd49063735984
-internal/host/claimservingpark_records.go type servingLegacyConsumption authority C,J4 rev3=PROPOSED-seed; permanent legacy source and authorization evidence; additive Collected flag distinguishes garbage collection from interrupted first-intent creation 1f7d2cd2baaad00efda56e7c5637e8b386d80ad7e5f2b311487ebcf109c4861b
-internal/host/claimservingboot_linux.go servingParkBootID authority C,J4 rev3=PROPOSED-seed; bounded kernel boot-ID read from /proc; no caller-supplied path or identity file:Open@54,file:ReadAll@162 af8e5641ef60fcd307dec013df6eba3e50a29d2cb811c5b2cddf10c5c9404e30
-internal/host/claimservingboot_linux.go servingParkFS.currentBoot authority C,J4 rev3=PROPOSED-seed; production boot reader and canonical nonzero UUID validation; missing or unreadable boot evidence refuses f6d671a41210250791f421efcf168fa98e3c2b4bd8b6aa314e9664261fd2b034
-internal/host/claimservingboot_linux.go servingParkFS.checkBoot authority C,J4 rev3=PROPOSED-seed; private namespace boot identity compared with the current kernel boot; missing old identity or a reboot refuses without refreshing mount numbers 3d380a2fa685f3be883f9c595a01ee437d51597a1b05ab09fb9c50dfead2cac1
-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_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/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/claimservingidentity.go validServingParkBootID authority C,J4 rev3=PROPOSED-seed; canonical nonzero kernel boot UUID validation shared by live boot reads and historical topology inspection 76350dcd46b8c66e2e70945267537fb580e15b8da9c80fb158392d317973d3b2
-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; private namespace and independently recorded parent, absent quarantine, current nosymfollow mount, persistent filesystem/handle identity and repeated path/permission checks; read-only observation grants no parking or deletion authority file:Read@491,file:Read@946 39d3d1b6e6791937f18f4064e0b523c708c457459adc27be7e1d5bb3870afbbe
-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/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 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
-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 servingParkTopology authority C,J4 rev3=PROPOSED-seed; persistent parent/root evidence attached only when root creates and records a new namespace 16a7fce2850bf204ddd67aa10876979be08a5d500a21ea3072e2fc5c1c369586
-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 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 servingParkPersistentFile authority C,J4 rev3=PROPOSED-seed; root-recorded regular-file filesystem handle, owner and mode, stored with immutable source evidence 003b704d58342ed25fd8c9cd557a69094115e0f22f70f35bb544e57da35c5ada
-internal/host/claimservingidentity.go validServingParkHandle authority C,J4 rev3=PROPOSED-seed; bounded canonical opaque handle, nonzero filesystem and handle type; pure validation shared by directory and regular-file records 48c0cbdeb5a2173629a1d7414819eea3894403b1f39241f5f49a9717e579466f
-internal/host/claimservingidentity.go validateServingParkPersistentRecord authority C,J4 rev3=PROPOSED-seed; source boot and directory identity, every present or absent file handle and prepared event proof; rejects partial evidence, preserves old record decoding bc7480d85f681f30580779f7d8fa3155d93509b804bd34212b56cc01e4eec69e
-internal/host/claimservingidentity_linux.go servingParkPersistentArtifact authority C,J4 rev3=PROPOSED-seed; no-follow O_PATH file descriptor, Fstatfs, Statx and NameToHandleAt with AT_EMPTY_PATH; name/descriptor device, inode, mount, type, links, allocation, ownership and mode checks; no tenant payload read file:Fstatfs@608,file:Openat@163,file:Statx@1062,file:Statx@347 b3fd1ed9332350bb19d0d34143b3c25ff44a39705207c0034f472690432575db
-internal/host/claimservingidentity_linux.go servingParkStatxMatches authority C,J4 rev3=PROPOSED-seed; recorded file identity versus descriptor or name statx metadata, including single regular-file link and allocated bytes 42ce5eb11aceab2c7f5d1999e91750d57ae668b0467a15b2c87953ca4aa17d63
-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 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/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; private retirement record and persistent namespace evidence; exact source/event/file handles across boots, bounded event children, per-artifact rename/delete progress; completed parks do not consult live sources file:Readdirnames@2298 4de98aacfd52eff4b1e20395555e352ced86e55ef545d0013febb4b6cc9a3bf1
-internal/host/claimservingreboot_linux.go servingParkFS.rebootDirectory authority C,J4 rev3=PROPOSED-seed; pinned descriptor device/inode, current mount and historical filesystem handle, owner and mode; no effects 2224be87510731990ca620abc0624a80675eca21228e561fa3e540d2ac8273c0
-internal/host/claimservingreboot_linux.go servingParkRebootFileMatches authority C,J4 rev3=PROPOSED-seed; historical file handle and current mount compared to source/event observations; lost rename and unlink replies require original progress; no writes 180ec916768fd2d012e689199963586dabbaa77553cea9d806882d1eacb15a75
-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
-internal/host/claimservingpark_recovery_linux.go servingParkRoot.validateServingRecoveryAuthority authority C,J4 rev3=PROPOSED-seed; fresh root history, original and continuation stop receipts, permanent legacy consumption, launch disposition and unlaunched active successor; extracted unchanged checks shared with same-boot startup 60ff31e49f8bafdf5a9c57b87ec8901e9ad3bafa6cc9e15d541a5659a83cf846
-internal/host/claimservingreboot_prepare_linux.go type servingParkRebootPreparation authority C,J4 rev3=PROPOSED-seed; private pending reboot evidence containing unchanged retirement, topology, slot and legacy history plus separately observed current authority; no filesystem or transport consumer a52a92a9b9d6507a2cbdffb1d8a76af3a541a023d421690ce588cf13a802c0d9
-internal/host/claimservingreboot_prepare_linux.go type servingParkRebootAuthority authority C,J4 rev3=PROPOSED-seed; current claim ETag, independently selected actor, directory and paired tags for an incomplete retirement; preparation only, never active mount authority 17bec014a0d01c9882d41727f6e9e97ad850104225c3c7f1f4c2ce09bc39bb34
-internal/host/claimservingreboot_prepare_linux.go servingParkRoot.prepareServingReboot authority C,J4 rev3=PROPOSED-seed; root-local preparation using fresh installation, census, current claim and stop/launch history under host guard; complete parks need no live installation; rechecks claim, actor and eligibility after durable preparation file:Read@1275 8174661c2c862ff9f312e2bb543113ca4813679e087a2fa0efdf1a34b2f4c8a2
-internal/host/claimservingreboot_prepare_linux.go servingParkLedger.prepareRebootLocked authority C,J4 rev3=PROPOSED-seed; namespace then host then slot/record locking; repeated complete observation, immutable private preparation and exact replay; no namespace, parent, retirement or permanent legacy rewrite file:Read@582 2de764d98b11ed5e5cc9147e29968caa3ced668fa310975a60538a6b15f9fc26
-internal/host/claimservingreboot_prepare_linux.go servingParkLedger.observeRebootPreparation authority C,J4 rev3=PROPOSED-seed; persistent source/event/file inspection, original retirement, validated slot references and exact permanent legacy consumption; current installation binds incomplete records; malformed or stale evidence refuses 252a5c4463f979e5442585b66d06453e279f45ac17fe4f0ff993df7dee678da6
+internal/hostguard/servingretirement.go Guard.WithServingRetirement authority C,J4 rev3=PROPOSED-seed; fresh serving claim and root installation/fence snapshot under root and shared host locks; refuses pending, quarantined, foreign, retired or already stopped successors; permits an older completed predecessor 3d105c50be7e613777055f561321efadf77fe823d8dc3263a60f2c1211b71108
+internal/hostguard/writerstop.go Guard.CompletedServingStops authority C rev3=PROPOSED-seed; root fence journal through the existing Guard backend; exact host, incarnation, stamp, session and box, with empty operation, phase and obligation; no mixed-session or pending-only evidence 79260151b0fe4f86c59cbe65d727cdceb56028bdfdb208fc9f02cebba1473a98
+@coverage internal/activator 5ecc6e99362bbd2c071607da2a247d01018d5380858d53dc9fdb9a88523200f4
+@coverage internal/host 18a469804421746da4e1057cb05e58db6c0606183a7557c0d603ba1a5967b1ab
+@coverage internal/hostbox 3865bfeb63bcb671b919d1cfcf97b70405a0378ff6be4444648d12a678e67411
+@coverage internal/hostfiles a38dbcd9789ba4acd237e88c834f2b2bfca9847b0d43c32b0116dc8996d7a142
+@coverage internal/hostguard a721142f4f3ac5fa17b6b1e2ccfc66ca809cfdcbf2733fdc00fbd0efaa90aeb6