WIP files seed checkpoint 28 (not for merge)
Delegated one-shot publishes and reruns; all 30 plain root tests pass together; Task 7 strings split ship/deferred with the activation runbook step.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
13 files changed,
+260
−26
internal/host/claimfilesseed.go+1 −0internal/host/claimfilesseed_cli_rootrunner_linux_test.go+22 −12internal/host/claimfilesseed_integration_linux_test.go+30 −0internal/host/claimfilesseed_isolated_rootrunner_linux_test.go+3 −0internal/host/claimfilesseed_lifetime_rootrunner_linux_test.go+5 −0internal/host/claimfilesseed_reboot_rootrunner_linux_test.go+85 −2internal/host/claimfilesseed_stage.go+15 −0internal/host/claimservingartifact_linux.go+9 −0internal/host/claimservingartifact_other.go+4 −0internal/host/claimservingpark_recovery.go+2 −2internal/host/claimservingretire.go+18 −3internal/host/claimservingretire_linux_test.go+57 −0internal/provision/testdata/plan14-root-reads-seed.tsv+9 −7
CI passed — run details
CI log
=== gate ===
cd launchctx && go vet ./... && go test ./... -count=1
ok amadan.net/carlos/platform.git/launchctx 0.011s
? amadan.net/carlos/platform.git/launchctx/controlname [no test files]
? amadan.net/carlos/platform.git/launchctx/wireid [no test files]
go vet ./...
gofmt -l .: clean
go test ./... -count=1 -timeout 30m
ok github.com/carlosframework/platform 280.703s
? github.com/carlosframework/platform/cmd/changelog [no test files]
ok github.com/carlosframework/platform/cmd/console 2.666s
ok github.com/carlosframework/platform/cmd/moveprobe 92.259s
ok github.com/carlosframework/platform/cmd/zddprobe 0.713s
ok github.com/carlosframework/platform/internal/activator 248.738s
ok github.com/carlosframework/platform/internal/agentsock 0.016s
ok github.com/carlosframework/platform/internal/alerting 0.003s
ok github.com/carlosframework/platform/internal/analytics 0.019s
ok github.com/carlosframework/platform/internal/appconfig 2.446s
ok github.com/carlosframework/platform/internal/apperr 0.080s
ok github.com/carlosframework/platform/internal/applog 0.120s
ok github.com/carlosframework/platform/internal/awsinv 0.003s
ok github.com/carlosframework/platform/internal/boxchoice 0.004s
ok github.com/carlosframework/platform/internal/boxid 0.586s
? 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.015s
ok github.com/carlosframework/platform/internal/certwarm 0.002s
ok github.com/carlosframework/platform/internal/cgroup 0.703s
ok github.com/carlosframework/platform/internal/changelog 0.303s
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 385.772s
? github.com/carlosframework/platform/internal/controlname [no test files]
? github.com/carlosframework/platform/internal/controlsock [no test files]
ok github.com/carlosframework/platform/internal/dbdoor 6.093s
ok github.com/carlosframework/platform/internal/dbdoor/dbdoortest 0.009s
ok github.com/carlosframework/platform/internal/dbverify 0.004s
ok github.com/carlosframework/platform/internal/dnsrec 0.002s
ok github.com/carlosframework/platform/internal/docsite 0.005s
ok github.com/carlosframework/platform/internal/domains 2.436s
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.599s
? 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.129s
ok github.com/carlosframework/platform/internal/gate 1.655s
ok github.com/carlosframework/platform/internal/genselect 0.014s
ok github.com/carlosframework/platform/internal/geo 0.017s
ok github.com/carlosframework/platform/internal/geoip 0.031s
ok github.com/carlosframework/platform/internal/host 160.365s
ok github.com/carlosframework/platform/internal/hostactors 0.356s
ok github.com/carlosframework/platform/internal/hostbox 2.335s
ok github.com/carlosframework/platform/internal/hostclaim 0.473s
ok github.com/carlosframework/platform/internal/hostevidence 0.012s
ok github.com/carlosframework/platform/internal/hostexec 4.956s
ok github.com/carlosframework/platform/internal/hostfence 0.039s
ok github.com/carlosframework/platform/internal/hostfiles 10.564s
ok github.com/carlosframework/platform/internal/hostfloor 0.012s
ok github.com/carlosframework/platform/internal/hostguard 1.015s
ok github.com/carlosframework/platform/internal/hostops 1.570s
? github.com/carlosframework/platform/internal/hostops/fencerelease [no test files]
ok github.com/carlosframework/platform/internal/hostops/internal/fencerelease 0.012s
ok github.com/carlosframework/platform/internal/hostquarantine 0.012s
ok github.com/carlosframework/platform/internal/hostrelocate 0.414s
ok github.com/carlosframework/platform/internal/hostresolve 0.140s
ok github.com/carlosframework/platform/internal/hostwrite 0.012s
ok github.com/carlosframework/platform/internal/infraview 0.013s
ok github.com/carlosframework/platform/internal/instancectl 4.195s
ok github.com/carlosframework/platform/internal/instanceexec 2.707s
ok github.com/carlosframework/platform/internal/instancefs 0.016s
ok github.com/carlosframework/platform/internal/instancekey 0.014s
ok github.com/carlosframework/platform/internal/instanceupdates 0.018s
ok github.com/carlosframework/platform/internal/layout 1.498s
ok github.com/carlosframework/platform/internal/lease 9.800s
ok github.com/carlosframework/platform/internal/lease/leasetest 0.017s
ok github.com/carlosframework/platform/internal/ledger 0.010s
ok github.com/carlosframework/platform/internal/mail 0.153s
ok github.com/carlosframework/platform/internal/maintenance 0.077s
ok github.com/carlosframework/platform/internal/maintenanceexec 5.520s
ok github.com/carlosframework/platform/internal/maintenanceguard 0.061s
ok github.com/carlosframework/platform/internal/metrics 0.007s
ok github.com/carlosframework/platform/internal/moves 20.960s
ok github.com/carlosframework/platform/internal/moves/archive 0.762s
ok github.com/carlosframework/platform/internal/objstore 0.171s
ok github.com/carlosframework/platform/internal/ops 0.178s
ok github.com/carlosframework/platform/internal/park 0.197s
ok github.com/carlosframework/platform/internal/privatefile 0.003s
ok github.com/carlosframework/platform/internal/provision 16.914s
ok github.com/carlosframework/platform/internal/publicrelease 0.010s
? github.com/carlosframework/platform/internal/regionname [no test files]
ok github.com/carlosframework/platform/internal/registry 4.988s
ok github.com/carlosframework/platform/internal/relay 7.242s
ok github.com/carlosframework/platform/internal/release 19.620s
ok github.com/carlosframework/platform/internal/rollout 3.792s
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.006s
ok github.com/carlosframework/platform/internal/rootstate 0.025s
ok github.com/carlosframework/platform/internal/runs 0.580s
ok github.com/carlosframework/platform/internal/s3compat 0.005s
ok github.com/carlosframework/platform/internal/safefile 0.003s
ok github.com/carlosframework/platform/internal/schedule 2.464s
ok github.com/carlosframework/platform/internal/seeds 0.011s
ok github.com/carlosframework/platform/internal/sim/hostlife 9.534s
ok github.com/carlosframework/platform/internal/sim/litestreamfacts 3.669s
ok github.com/carlosframework/platform/internal/status 0.038s
ok github.com/carlosframework/platform/internal/steering 0.005s
ok github.com/carlosframework/platform/internal/stopevidence 0.195s
ok github.com/carlosframework/platform/internal/svcrole 0.002s
ok github.com/carlosframework/platform/internal/telemetry 0.004s
ok github.com/carlosframework/platform/internal/termfence 0.004s
ok github.com/carlosframework/platform/internal/tunnelclient 0.004s
ok github.com/carlosframework/platform/internal/unixdial 0.002s
ok github.com/carlosframework/platform/internal/updatewords 0.004s
ok github.com/carlosframework/platform/internal/usage 0.004s
ok github.com/carlosframework/platform/internal/version 0.002s
ok github.com/carlosframework/platform/internal/vet 1.557s
=== cross ===
--- shard 0 of 5 ---
cross-compiling darwin/arm64
--- shard 1 of 5 ---
cross-compiling darwin/amd64
--- shard 2 of 5 ---
cross-compiling linux/arm64
linux/arm64 carlos binary: 46989474 bytes (ceiling 52000000)
--- shard 3 of 5 ---
cross-compiling windows/amd64
--- shard 4 of 5 ---
cross-compiling windows/arm64
=== minio-contract ===
minio contract: arm64, minio RELEASE.2025-09-07T16-13-09Z, mc RELEASE.2025-08-13T08-35-41Z
minio: cached, digest verified
mc: cached, digest verified
minio healthy on 127.0.0.1:20999
Added `local` successfully.
Bucket created successfully `local/contract-bucket`.
Bucket created successfully `local/denied-bucket`.
Created policy `contract-only` successfully.
Added user `scopeduser` successfully.
Attached Policies: [contract-only]
To User: scopeduser
buckets and scoped user ready
=== RUN TestDeliveredContractMinio
=== RUN TestDeliveredContractMinio/put
=== RUN TestDeliveredContractMinio/get_round-trips_the_body
=== RUN TestDeliveredContractMinio/list_shows_the_key
=== RUN TestDeliveredContractMinio/head_and_list_agree_on_the_version
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_its_own_object
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed
=== RUN TestDeliveredContractMinio/delete_removes_it
=== RUN TestDeliveredContractMinio/cross-bucket_denial
--- PASS: TestDeliveredContractMinio (0.02s)
--- PASS: TestDeliveredContractMinio/put (0.00s)
--- PASS: TestDeliveredContractMinio/get_round-trips_the_body (0.00s)
--- PASS: TestDeliveredContractMinio/list_shows_the_key (0.00s)
--- PASS: TestDeliveredContractMinio/head_and_list_agree_on_the_version (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route (0.01s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed (0.00s)
--- PASS: TestDeliveredContractMinio/delete_removes_it (0.00s)
--- PASS: TestDeliveredContractMinio/cross-bucket_denial (0.00s)
PASS
ok github.com/carlosframework/platform/internal/objstore 0.022s
=== RUN TestT4StorageProbeMinio
2026/10/15 18:42:55 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18DEC827FC8DDE18, 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.00s)
PASS
ok github.com/carlosframework/platform/internal/host 0.019s
minio contract: PASS
diff --git a/internal/host/claimfilesseed.go b/internal/host/claimfilesseed.go| index 880e912ba..feba7abe6 100644 |
| --- a/internal/host/claimfilesseed.go |
| +++ b/internal/host/claimfilesseed.go |
| @@ -251,6 +251,7 @@ func (e *filesSeedExecutor) host(ctx context.Context, o hostclaim.Observed, out |
| return err |
| } |
| admitted, settled, err := prepareFilesSeed(ctx, *row, runtime, o, token) |
| + err = errors.Join(err, op.reportSkipped(out, o.Host)) |
| if !settled { |
| e.pending[o.Host] = filesSeedPending{exclusion: token, wake: wake} |
| } |
diff --git a/internal/host/claimfilesseed_cli_rootrunner_linux_test.go b/internal/host/claimfilesseed_cli_rootrunner_linux_test.go| index 97ca3440a..e079bfd05 100644 |
| --- a/internal/host/claimfilesseed_cli_rootrunner_linux_test.go |
| +++ b/internal/host/claimfilesseed_cli_rootrunner_linux_test.go |
| @@ -86,8 +86,30 @@ func rootFilesSeedDelegatedCLI(t *testing.T, binary string) { |
| f.f.kernel.noProcess = true |
| f.p.root.Stop.Coordinator.Kernel = f.f.kernel |
| f.p.root.Stop.Coordinator.RetainNoProcess = f.p.retainLegacyNoProcess |
| + reg, err := registry.Open("/data/registry.db") |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + t.Cleanup(func() { reg.Close() }) |
| + row, err := executor.registry.Get(recoveryHost) |
| + if err != nil || row == nil { |
| + t.Fatal(err) |
| + } |
| + owned, err := f.p.root.Reconcile.Registry.Get(recoveryHost) |
| + if err != nil || owned == nil || !owned.ClaimOwnership.Valid() { |
| + t.Fatal("missing root-owned fixture route", err) |
| + } |
| + row.ClaimOwnership = owned.ClaimOwnership |
| + if err := reg.Upsert(*row); err != nil { |
| + t.Fatal(err) |
| + } |
| + // Production root and agent observe the same ownership transfer. |
| + f.p.root.Reconcile.Registry = reg |
| h := servingTransportRoot(t, f) |
| + containmentRoot := claimContainmentAgentRoot |
| capability := newLaunchFixture(t, hostclaim.Op{Kind: hostclaim.KindServing, Exec: f.actor.Box}).root |
| + // The capability fixture assumes system.slice; this installed edge uses carlos.slice. |
| + claimContainmentAgentRoot = containmentRoot |
| capability.Authenticate = h.Stop.Authenticate |
| capability.SeedController = verifyFilesSeedDelegation |
| listener, err := net.Listen("unix", controlsock.Path) |
| @@ -107,18 +129,6 @@ func rootFilesSeedDelegatedCLI(t *testing.T, binary string) { |
| t.Error(err) |
| } |
| }) |
| - reg, err := registry.Open("/data/registry.db") |
| - if err != nil { |
| - t.Fatal(err) |
| - } |
| - row, err := executor.registry.Get(recoveryHost) |
| - if err != nil || row == nil { |
| - t.Fatal(err) |
| - } |
| - if err := reg.Upsert(*row); err != nil { |
| - t.Fatal(err) |
| - } |
| - reg.Close() |
| if err := os.MkdirAll(filepath.Dir(cfg.Executable), 0755); err != nil { |
| t.Fatal(err) |
| } |
diff --git a/internal/host/claimfilesseed_integration_linux_test.go b/internal/host/claimfilesseed_integration_linux_test.go| index d696aac5c..6b6057683 100644 |
| --- a/internal/host/claimfilesseed_integration_linux_test.go |
| +++ b/internal/host/claimfilesseed_integration_linux_test.go |
| @@ -7,6 +7,7 @@ import ( |
| "context" |
| "os" |
| "path/filepath" |
| + "strings" |
| "testing" |
| "time" |
| @@ -212,6 +213,35 @@ func TestFilesSeedRealOriginPreparation(t *testing.T) { |
| runFilesSeedOrdinaryPreparation(t, "real-seed", "same-controller") |
| } |
| +func TestFilesSeedReportsSkippedPaths(t *testing.T) { |
| + witness := filesSeedLiveWitness{seed: func(t *testing.T, p *servingPreparation, executor *filesSeedExecutor) { |
| + host := p.f.f.hostName() |
| + dir := filepath.Join(filepath.Dir(p.f.db), host) |
| + large, err := os.Create(filepath.Join(dir, "large.bin")) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + err = large.Truncate(hostfiles.MaxFileBytes + 1) |
| + large.Close() |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := os.Symlink("payload", filepath.Join(dir, "link\nname")); err != nil { |
| + t.Fatal(err) |
| + } |
| + var output bytes.Buffer |
| + if err := executor.run(t.Context(), host, &output); err != nil { |
| + t.Fatalf("seed: %v\n%s", err, output.String()) |
| + } |
| + for _, want := range []string{`skipped file "large.bin": exceeds 64 MiB`, `skipped file "link\nname": not a regular file.`, `: seeded.`} { |
| + if !strings.Contains(output.String(), want) { |
| + t.Fatalf("missing %q in output:\n%s", want, output.String()) |
| + } |
| + } |
| + }} |
| + runFilesSeedPreparationWithLive(t, "real-seed", "same-controller", nil, []filesSeedLiveWitness{witness}) |
| +} |
| + |
| func TestFilesSeedPreparationChangedController(t *testing.T) { |
| witness := filesSeedLiveWitness{configure: func(*ClaimWakeAssembly) {}, serve: func(*testing.T, *servingPreparation, hostclaim.Observed, activator.ClaimStart) {}} |
| witness.prepare = func(p *servingPreparation) { |
diff --git a/internal/host/claimfilesseed_isolated_rootrunner_linux_test.go b/internal/host/claimfilesseed_isolated_rootrunner_linux_test.go| index 78608e3cf..eed1a8202 100644 |
| --- a/internal/host/claimfilesseed_isolated_rootrunner_linux_test.go |
| +++ b/internal/host/claimfilesseed_isolated_rootrunner_linux_test.go |
| @@ -95,6 +95,9 @@ func rootFilesSeedIsolatedWitness(t *testing.T, binary string, twoHosts ...bool) |
| } |
| t.Cleanup(func() { os.RemoveAll(dir) }) |
| } |
| + if t.Name() == "TestRootRebootFilesSeedTwoHostPhase1" || t.Name() == "TestRootRebootFilesSeedTwoHostPhase2" { |
| + seedRebootDataMount(t) |
| + } |
| account, err := user.Lookup("carlos") |
| if err != nil { |
| t.Fatal(err) |
diff --git a/internal/host/claimfilesseed_lifetime_rootrunner_linux_test.go b/internal/host/claimfilesseed_lifetime_rootrunner_linux_test.go| index 469c8dbdb..5dcd24565 100644 |
| --- a/internal/host/claimfilesseed_lifetime_rootrunner_linux_test.go |
| +++ b/internal/host/claimfilesseed_lifetime_rootrunner_linux_test.go |
| @@ -173,6 +173,11 @@ func rootFilesSeedLifetime(t *testing.T, p *servingPreparation, e *filesSeedExec |
| } |
| func TestFilesSeedExecutorLifetime(t *testing.T) { |
| + // The runner's /tmp is a small tmpfs. Keep the real start-floor check, |
| + // and place this direct fixture on its disk, as the installed /data tests do. |
| + if os.Geteuid() == 0 { |
| + t.Setenv("TMPDIR", "/var/tmp") |
| + } |
| for _, mode := range []string{"Complete", "Disconnect", "Shutdown"} { |
| t.Run(mode, func(t *testing.T) { |
| runFilesSeedPreparationWithLive(t, "real-seed", "same-controller", nil, []filesSeedLiveWitness{{seed: rootFilesSeedLifetime}}) |
diff --git a/internal/host/claimfilesseed_reboot_rootrunner_linux_test.go b/internal/host/claimfilesseed_reboot_rootrunner_linux_test.go| index e3a9c890b..a5664ce30 100644 |
| --- a/internal/host/claimfilesseed_reboot_rootrunner_linux_test.go |
| +++ b/internal/host/claimfilesseed_reboot_rootrunner_linux_test.go |
| @@ -10,7 +10,9 @@ import ( |
| "net" |
| "net/http/httptest" |
| "os" |
| + "os/exec" |
| "path/filepath" |
| + "reflect" |
| "sort" |
| "strconv" |
| "strings" |
| @@ -25,6 +27,7 @@ import ( |
| "github.com/carlosframework/platform/internal/registry" |
| "github.com/carlosframework/platform/internal/release" |
| "github.com/carlosframework/platform/internal/rootstate" |
| + "golang.org/x/sys/unix" |
| ) |
| const seedRebootPath = "/var/lib/carlos-rootrunner-files-seed-reboot" |
| @@ -173,6 +176,7 @@ func TestRootRebootFilesSeedTwoHostPhase2(t *testing.T) { |
| t.Cleanup(func() { state.Close() }) |
| fs := newServingParkFS("/data", state) |
| ledger := &servingParkLedger{state: state, fs: fs, now: time.Now} |
| + rebootRoot := &servingParkRoot{ledger: ledger} |
| for _, h := range saved.Hosts { |
| for _, r := range h.Records { |
| if err := ledger.fs.CheckParked(&r); err == nil { |
| @@ -181,10 +185,15 @@ func TestRootRebootFilesSeedTwoHostPhase2(t *testing.T) { |
| if !r.Complete { |
| t.Fatal("unfinished reboot fixture", h.Host) |
| } |
| - if err := ledger.withNamespace(t.Context(), func() error { return ledger.prepareRebootLocked(t.Context(), r, nil) }); err != nil { |
| + if err := rebootRoot.prepareServingReboot(t.Context(), r.Authorization.ID); err != nil { |
| + topology, topErr := fs.inspectRebootTopology(t.Context()) |
| + observation, inspectErr := fs.inspectRebootRecord(t.Context(), r.Authorization.ID) |
| + rawRecord, readErr := ledger.readHistorical(r.Authorization.ID) |
| + slot, slotErr := ledger.slot(r.Authorization) |
| + t.Logf("reboot diagnosis topology=%+v err=%v inspection=%+v err=%v savedMatches=%v readErr=%v slot=%+v slotErr=%v saved=%+v", topology, topErr, observation, inspectErr, reflect.DeepEqual(rawRecord, r), readErr, slot, slotErr, r) |
| t.Fatal("prepare completed park", h.Host, err) |
| } |
| - if err := ledger.withNamespace(t.Context(), func() error { return ledger.commitRebootLocked(t.Context(), r, nil) }); err != nil { |
| + if err := rebootRoot.commitServingReboot(t.Context(), r.Authorization.ID); err != nil { |
| t.Fatal("recover park", h.Host, err) |
| } |
| } |
| @@ -292,3 +301,77 @@ func reopenSeedLiveHost(t *testing.T, saved seedRebootHost, witness filesSeedLiv |
| p := newServingPreparation(t, f, "boot", fmt.Sprintf("reboot-%d", os.Getpid())) |
| return &seedLiveHost{p: p, endpoint: server.URL, files: opened.(*movesFiles), witness: witness} |
| } |
| + |
| +// A grown root volume can receive different partition device numbers at its |
| +// first reboot. The production contract refuses device renumbering. Give this |
| +// reboot witness its own stable filesystem instead of relaxing that contract. |
| +func seedRebootDataMount(t *testing.T) { |
| + t.Helper() |
| + var root unix.Stat_t |
| + if err := unix.Stat("/", &root); err != nil { |
| + t.Fatal(err) |
| + } |
| + t.Logf("runner root device before data mount: %d", root.Dev) |
| + image := filepath.Join(seedRebootPath, "data.ext4") |
| + deviceFile := filepath.Join(seedRebootPath, "data-device") |
| + command := func(args ...string) string { |
| + t.Helper() |
| + out, err := exec.CommandContext(t.Context(), args[0], args[1:]...).CombinedOutput() |
| + if err != nil { |
| + t.Fatalf("reboot data command %q: %v %s", args, err, out) |
| + } |
| + return strings.TrimSpace(string(out)) |
| + } |
| + var device string |
| + if t.Name() == "TestRootRebootFilesSeedTwoHostPhase1" { |
| + file, err := os.OpenFile(image, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0600) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + err = file.Truncate(2 << 30) |
| + closeErr := file.Close() |
| + if err != nil || closeErr != nil { |
| + t.Fatal(err, closeErr) |
| + } |
| + command("mkfs.ext4", "-q", "-F", image) |
| + device = command("losetup", "--find", "--show", image) |
| + if err := os.WriteFile(deviceFile, []byte(device), 0600); err != nil { |
| + t.Fatal(err) |
| + } |
| + } else { |
| + body, err := os.ReadFile(deviceFile) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + device = string(body) |
| + number := strings.TrimPrefix(device, "/dev/loop") |
| + if number == device || number == "" { |
| + t.Fatal("invalid saved loop device", device) |
| + } |
| + if _, err := strconv.ParseUint(number, 10, 32); err != nil { |
| + t.Fatal(err) |
| + } |
| + // losetup refuses a device already in use; never detach someone else's loop. |
| + command("losetup", device, image) |
| + } |
| + t.Cleanup(func() { |
| + cleanup, cancel := context.WithTimeout(context.Background(), 10*time.Second) |
| + defer cancel() |
| + if out, err := exec.CommandContext(cleanup, "losetup", "--detach", device).CombinedOutput(); err != nil { |
| + t.Errorf("detach fixture loop: %v %s", err, out) |
| + } |
| + }) |
| + if err := unix.Mount(device, "/data", "ext4", unix.MS_NOSUID|unix.MS_NODEV, ""); err != nil { |
| + t.Fatal(err) |
| + } |
| + t.Cleanup(func() { |
| + if err := unix.Unmount("/data", 0); err != nil { |
| + t.Error(err) |
| + } |
| + }) |
| + var data unix.Stat_t |
| + if err := unix.Stat("/data", &data); err != nil { |
| + t.Fatal(err) |
| + } |
| + t.Logf("persistent data device: %s (%d)", device, data.Dev) |
| +} |
diff --git a/internal/host/claimfilesseed_stage.go b/internal/host/claimfilesseed_stage.go| index 94e7e4625..5e3bebbf6 100644 |
| --- a/internal/host/claimfilesseed_stage.go |
| +++ b/internal/host/claimfilesseed_stage.go |
| @@ -4,6 +4,7 @@ import ( |
| "bytes" |
| "context" |
| "errors" |
| + "fmt" |
| "io" |
| "os" |
| "strings" |
| @@ -29,6 +30,20 @@ type filesSeedOperation struct { |
| stats hostfiles.SyncStats |
| } |
| +func (s *filesSeedOperation) reportSkipped(out io.Writer, host string) error { |
| + for _, path := range s.stats.TooLarge { |
| + if _, err := fmt.Fprintf(out, "Host %q: skipped file %q: exceeds 64 MiB and will not be restored on a later cold wake.\n", host, path); err != nil { |
| + return err |
| + } |
| + } |
| + for _, path := range s.stats.NotRegular { |
| + if _, err := fmt.Fprintf(out, "Host %q: skipped file %q: not a regular file.\n", host, path); err != nil { |
| + return err |
| + } |
| + } |
| + return nil |
| +} |
| + |
| // 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) { |
diff --git a/internal/host/claimservingartifact_linux.go b/internal/host/claimservingartifact_linux.go| index 54b22c879..2332cdb7d 100644 |
| --- a/internal/host/claimservingartifact_linux.go |
| +++ b/internal/host/claimservingartifact_linux.go |
| @@ -39,3 +39,12 @@ func checkServingArtifactAbsence(db string, expected servingArtifactDirectory) e |
| } |
| return nil |
| } |
| + |
| +func servingAgentDirectory(db string) (servingArtifactDirectory, error) { |
| + dir, err := instancefs.OpenDirBeneath(filepath.Dir(db), ".") |
| + if err != nil { |
| + return servingArtifactDirectory{}, err |
| + } |
| + defer dir.Close() |
| + return servingParkDirID(dir) |
| +} |
diff --git a/internal/host/claimservingartifact_other.go b/internal/host/claimservingartifact_other.go| index a69c868f3..6bb477a39 100644 |
| --- a/internal/host/claimservingartifact_other.go |
| +++ b/internal/host/claimservingartifact_other.go |
| @@ -7,3 +7,7 @@ import "github.com/carlosframework/platform/internal/hostbox" |
| func checkServingArtifactAbsence(string, servingArtifactDirectory) error { |
| return hostbox.ErrUnsupported |
| } |
| + |
| +func servingAgentDirectory(string) (servingArtifactDirectory, error) { |
| + return servingArtifactDirectory{}, hostbox.ErrUnsupported |
| +} |
diff --git a/internal/host/claimservingpark_recovery.go b/internal/host/claimservingpark_recovery.go| index b0e594a4f..238944d89 100644 |
| --- a/internal/host/claimservingpark_recovery.go |
| +++ b/internal/host/claimservingpark_recovery.go |
| @@ -14,7 +14,7 @@ import ( |
| const servingParkRecoveryLimit = 128 |
| -var errServingParkRetagPending = errors.New("the earlier park is incomplete; the next wake or root service restart completes it before a new park") |
| +var errServingParkRetagPending = errors.New("the earlier park is incomplete; retain its evidence and inspect the root service journal before retrying") |
| type servingParkRecoveryResult struct { |
| Host, ID string |
| @@ -144,7 +144,7 @@ func (p *servingParkRuntime) recoverPending(ctx context.Context) error { |
| for _, result := range page.Results { |
| if result.Err != nil { |
| // Draft retirement.reap_failed and reason.park_pending from the index. |
| - reason := "a local park operation failed; inspect the park listing and root service journal" |
| + reason := "a local park operation failed; inspect the root service journal" |
| if errors.Is(result.Err, errServingParkRetagPending) { |
| reason = errServingParkRetagPending.Error() |
| } else if errors.Is(result.Err, hostbox.ErrChanged) || errors.Is(result.Err, ErrClaimStopReceipt) { |
diff --git a/internal/host/claimservingretire.go b/internal/host/claimservingretire.go| index ce954ab71..11d0d0f29 100644 |
| --- a/internal/host/claimservingretire.go |
| +++ b/internal/host/claimservingretire.go |
| @@ -185,6 +185,10 @@ func (r *claimServingArtifactRetirement) recover(ctx context.Context, admitted h |
| return err |
| } |
| } |
| + local, err := servingAgentDirectory(in.DBPath) |
| + if err != nil { |
| + return err |
| + } |
| authorization, err := r.Root.Park(ctx, current, prior) |
| if err != nil { |
| return err |
| @@ -192,7 +196,7 @@ func (r *claimServingArtifactRetirement) recover(ctx context.Context, admitted h |
| if generation != "" && authorization.Predecessor.Generation != generation { |
| return hostbox.ErrChanged |
| } |
| - return r.finish(ctx, admitted, in, authorization, nil) |
| + return r.finish(ctx, admitted, in, authorization, local, nil) |
| } |
| func (r *claimServingArtifactRetirement) BeforeLegacyInstall(ctx context.Context, admitted hostclaim.Observed, in ClaimLaunchInputs, work hostwrite.Work) error { |
| @@ -211,6 +215,10 @@ func (r *claimServingArtifactRetirement) BeforeLegacyInstall(ctx context.Context |
| if (prior.Session == 0) != (prior.Generation == "") || prior.Session >= current.Session || (prior.Empty && prior.Session != 0) { |
| return hostbox.ErrChanged |
| } |
| + local, err := servingAgentDirectory(in.DBPath) |
| + if err != nil { |
| + return err |
| + } |
| authorization, err := r.Root.ParkLegacy(ctx, current, prior) |
| if err != nil { |
| return err |
| @@ -218,7 +226,7 @@ func (r *claimServingArtifactRetirement) BeforeLegacyInstall(ctx context.Context |
| if !authorization.Legacy { |
| return hostbox.ErrChanged |
| } |
| - return r.finish(ctx, admitted, in, authorization, work) |
| + return r.finish(ctx, admitted, in, authorization, local, work) |
| } |
| func (r *claimServingArtifactRetirement) validateAuthorization(ctx context.Context, fresh hostclaim.Observed, current servingArtifactIdentity, a servingParkAuthorization) error { |
| @@ -263,7 +271,7 @@ func (r *claimServingArtifactRetirement) validateAuthorization(ctx context.Conte |
| return nil |
| } |
| -func (r *claimServingArtifactRetirement) finish(ctx context.Context, admitted hostclaim.Observed, in ClaimLaunchInputs, a servingParkAuthorization, work hostwrite.Work) error { |
| +func (r *claimServingArtifactRetirement) finish(ctx context.Context, admitted hostclaim.Observed, in ClaimLaunchInputs, a servingParkAuthorization, local servingArtifactDirectory, work hostwrite.Work) error { |
| fresh, current, _, _, err := r.snapshot(ctx, admitted, in) |
| if err != nil { |
| return err |
| @@ -271,6 +279,13 @@ func (r *claimServingArtifactRetirement) finish(ctx context.Context, admitted ho |
| if err = r.validateAuthorization(ctx, fresh, current, a); err != nil { |
| return err |
| } |
| + // Mount IDs belong to a namespace. Root keeps its original mount identity; |
| + // the hardened agent pins its own mount before the RPC and checks it again |
| + // during enrollment. Both views must still name the same device and inode. |
| + if a.Directory.Device != local.Device || a.Directory.Inode != local.Inode || local.Mount == 0 { |
| + return hostbox.ErrChanged |
| + } |
| + a.Directory = local |
| enroll := func() error { return r.enrollPairLocked(ctx, admitted, in, a) } |
| if work != nil { |
| err = work.With(ctx, in.Identity, enroll) |
diff --git a/internal/host/claimservingretire_linux_test.go b/internal/host/claimservingretire_linux_test.go| index 995368673..743262d4f 100644 |
| --- a/internal/host/claimservingretire_linux_test.go |
| +++ b/internal/host/claimservingretire_linux_test.go |
| @@ -765,3 +765,60 @@ func TestServingRetirementFailedReplayInvalidatesPermission(t *testing.T) { |
| t.Fatal("old completion survived failed replay") |
| } |
| } |
| + |
| +// Root and the hardened edge see the same inode through different mounts. |
| +type servingNamespaceParker struct { |
| + servingDatabaseParker |
| + change func(*servingParkAuthorization) |
| +} |
| + |
| +func (p servingNamespaceParker) Park(ctx context.Context, current, prior servingArtifactIdentity) (servingParkAuthorization, error) { |
| + a, err := p.servingDatabaseParker.Park(ctx, current, prior) |
| + p.change(&a) |
| + return a, err |
| +} |
| + |
| +func (p servingNamespaceParker) ParkLegacy(ctx context.Context, current servingArtifactIdentity, prior servingLegacyPredecessor) (servingParkAuthorization, error) { |
| + a, err := p.servingDatabaseParker.ParkLegacy(ctx, current, prior) |
| + p.change(&a) |
| + return a, err |
| +} |
| +func TestServingEnrollmentMountNamespaces(t *testing.T) { |
| + for _, kind := range []string{"namespace", "device", "inode", "replacement"} { |
| + t.Run(kind, func(t *testing.T) { |
| + f, r, p, in := servingRetirementFixture(t) |
| + admitted := servingAdmit(t, f, &in, "G") |
| + r.Root = servingNamespaceParker{p, func(a *servingParkAuthorization) { |
| + a.Directory.Mount++ |
| + switch kind { |
| + case "device": |
| + a.Directory.Device++ |
| + case "inode": |
| + a.Directory.Inode++ |
| + case "replacement": |
| + dir := filepath.Dir(in.DBPath) |
| + if err := os.Rename(dir, dir+".old"); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := os.Mkdir(dir, 0700); err != nil { |
| + t.Fatal(err) |
| + } |
| + } |
| + }} |
| + err := r.BeforeLegacyInstall(t.Context(), admitted, in, servingRetirementWork{p, r, admitted}) |
| + if kind == "namespace" { |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := r.EnrollLocked(t.Context(), admitted, in); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := r.Recover(t.Context(), admitted, in); err != nil { |
| + t.Fatal("namespace recovery", err) |
| + } |
| + } else if err == nil { |
| + t.Fatal("changed source accepted") |
| + } |
| + }) |
| + } |
| +} |
diff --git a/internal/provision/testdata/plan14-root-reads-seed.tsv b/internal/provision/testdata/plan14-root-reads-seed.tsv| index 090a298df..a0c1f85a7 100644 |
| --- a/internal/provision/testdata/plan14-root-reads-seed.tsv |
| +++ b/internal/provision/testdata/plan14-root-reads-seed.tsv |
| @@ -31,7 +31,7 @@ internal/host/agent.go runAgent authority B,C,D,F,J4 rev3=PROPOSED-seed; agent s |
| internal/host/claimassembly.go ClaimAssembly.Build authority C,G,J4 rev3=PROPOSED-seed; captured claim source, writer and runtime resources; construct fresh private serving owner without copying mutex-bearing live state file:Prepare@3640,file:Prepare@3657,file:Prepare@4369,file:Read@4299 42fc9bdc95130d62b0c5001821a924499dbe8cbf0d7e72d78e2f367978d0c8a6 |
| internal/host/claimassembly.go NewProductionRootClaimStop authority C,J4 rev3=PROPOSED-seed; verified root assembly, actor and managed roots; binds private serving-park runtime and retained session-zero stop evidence; agent serving construction remains refused 7debdfe43ca6f17e2229c8aa8b566ee22112d86a595a66fb0cb0213253854aa8 |
| internal/host/claimconstruction.go OpenProductionClaimAssembly authority C,G rev3=PROPOSED-seed; production serving retirement, enrollment and readiness bindings; early metadata and capacity guards before retry reservation file:Read@4150,file:Read@9908,file:ReadFile@3694,registry:Host@9935,whole:Route@10023,whole:Route@10772 60f3c8f0a32435e0341912069f33238793659b7059a2a5661308eaae8b08c6c7 |
| -internal/host/claimfilesseed.go filesSeedExecutor.host authority C,J4 rev3=PROPOSED-seed; bounded request lifetime, shared cleanup deadline and exclusion; no resource closure before handler join file:Exec@476,file:Exec@661,file:Open@1102,file:Read@2129,file:Read@3787,registry:Backing@974,registry:Replica@815,registry:Seed@956,registry:Tunnel@1032,registry:Unit@995,whole:Route@1383,whole:Route@2453,whole:Route@2735,whole:Route@3480,whole:Route@4570,whole:Route@4665,whole:Route@767 d49f4bbd7c5abe81d09b1c2b396b4546f3ea97f37c0e8f5e3952fbdb187fedb4 |
| +internal/host/claimfilesseed.go filesSeedExecutor.host authority C,J4 rev3=PROPOSED-seed; existing selection and exclusion; report collected skipped paths after preparation, propagating output errors; no new authority or filesystem reads file:Exec@476,file:Exec@661,file:Open@1102,file:Read@2129,file:Read@3787,registry:Backing@974,registry:Replica@815,registry:Seed@956,registry:Tunnel@1032,registry:Unit@995,whole:Route@1383,whole:Route@2453,whole:Route@2735,whole:Route@3480,whole:Route@4570,whole:Route@4665,whole:Route@767 021e70ac6e298d1315425386258da392c408737ca39a6a924c440f05ab21fd41 |
| internal/host/claimfilesseed.go filesSeedExecutor.run authority C,J4 rev3=PROPOSED-seed; local serving claims, registry, authoritative files evidence and ordinary inputs under one host exclusion; serial candidates, exact rerun settlement, one admitted attempt and private C8 without launch 017c0b7c89f6bfa337e294624b0edaeafc2d360369b6a3ddbf417045b71fc71f |
| internal/host/claimfilesseed.go filesSeedRetryReport diagnostic C,J4 rev3=PROPOSED-seed; incarnation-keyed missing-files diagnostics and indexed retry results; throttles output only and grants no retry, stop or launch c9f069dbde2eb1bfb7d98f8fcf1d949111d729d3dffa0dacc2a9591edb263193 |
| internal/host/claimfilesseed.go type filesSeedExecutor authority C,J4 rev3=PROPOSED-seed; local serving claims, registry, authoritative files evidence and ordinary inputs under one host exclusion; serial candidates, exact rerun settlement, one admitted attempt and private C8 without launch 47b12d2f11ee4b5490b5ec5f6ffcf50100481fe063d5a53d960f3adce081e11b |
| @@ -204,10 +204,10 @@ internal/host/claimservingpark_records.go validateServingParkRecord authority C, |
| 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; root lifecycle and retained recovery; independent crashed-controller recovery, per-journal errors retained, no agent evidence elevated ac8d81393bd0eea077e0f8b29a402f3c13a0d2627b228be5271a4c6c7dab493b |
| +internal/host/claimservingpark_recovery.go servingParkRuntime.recoverPending authority C,J4 rev3=PROPOSED-seed; root lifecycle and retained recovery; independent crashed-controller recovery, per-journal errors retained, no agent evidence elevated; draft diagnostic wording correction only; reads unchanged 67ccc5c3a30f7a3894cd5842a4ca3a9a9a0114f2f02faa0583fa8b220444919c |
| 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.go var errServingParkRetagPending authority C,J4 rev3=PROPOSED-seed; fixed pending-retag refusal; startup cannot write the independent claim writer tags; draft diagnostic wording correction only; reads unchanged 4ab3ce0ee055f4c01e8f6e12a5c1eed34bc3b4265adbc62b3bf9488fbb62e831 |
| 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; census-ruling-FS-R6; root startup and minute worker recover immutable incomplete parks and reboot bindings; per-record refusal retained 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 |
| @@ -257,14 +257,14 @@ internal/host/claimservingreboot_prepare_linux.go servingParkRoot.prepareServing |
| 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.BeforeLegacyInstall authority C,J4 rev3=PROPOSED-seed; admitted tuple and observed predecessor selection; root legacy authorization outside Work followed by guarded enrollment; agent mount anchored before root call and same device/inode bound across namespaces; root records unchanged 1295beaea78b831a9dc90a52608cc1abaa0fcc6224a0f499b2e615745595f901 |
| 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.finish authority C,J4 rev3=PROPOSED-seed; root authorization; guarded paired-tag update before root completion; in-memory final-install permission only after completion; agent mount anchored before root call and same device/inode bound across namespaces; root records unchanged 98b740f4463d8b287640cbd31204a94e6aeac01a4ff006afeac546f07ac11190 |
| 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.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; agent mount anchored before root call and same device/inode bound across namespaces; root records unchanged 7a501bc99cec3aca9591746cf254b2e16049f1ee154dde9d415e1649df468d85 |
| 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 |
| @@ -325,12 +325,14 @@ system.go runSystem authority B,C,D,F,J4 rev3=PROPOSED-seed; command arguments, |
| system.go systemVerb authority B,C,D,F,J4 rev3=PROPOSED-seed; command arguments, claim flags, service identity and root delegation capability before constructing agent-mode resources; one-shot skips Configure, Start, duties and listeners; close resources only after private runtime joins ee25513ed72f9b8338d95682c4c76b4ced26ec1f060528fc5804da9afbfbfd83 |
| @coverage . bf3d70b6a2c5d769a3c580c9d5511231499b788d9c08a542895de8381e5a39c9 |
| @coverage internal/activator 41f0ad01ac54bae63f089ef8ec5bbfa9155bb76720d72b2d88563481de40fe5c |
| -@coverage internal/host 276b5d95e109772c8af5bc7408bf4d888656ad032ef5d552e169203f55be023e |
| +@coverage internal/host 8872d86cde27a46eec09390c6bf7a35dfd977e4afca9825f2a32b068f420f038 |
| @coverage internal/hostbox db890260533bdace6ad9a7c23dc1538cb919962894f005fc7c5a59a18e969429 |
| @coverage internal/hostfiles a38dbcd9789ba4acd237e88c834f2b2bfca9847b0d43c32b0116dc8996d7a142 |
| @coverage internal/hostguard a721142f4f3ac5fa17b6b1e2ccfc66ca809cfdcbf2733fdc00fbd0efaa90aeb6 |
| internal/host/rootexecution_recovery.go recoverRootExecutionBeforeUpdate authority D rev3=PROPOSED-seed; extracted update recovery ordering; root instance-control identity; broker durable controller state then best-effort local park and retirement journals; retained park failures cannot gate update eb97b162b0d712dd2fe8971984353ebc10efe1142077a3c2531781dd969cbbf5 |
| internal/lease/leasetest/s3server.go NewS3Server authority C,J4 rev3=PROPOSED-seed; test-process S3 fixture only, including privileged runner; HTTP query selects simulated versions, GET exposes stored ETag and conditional PUT refuses stale owners under the fixture mutex; not called by production root paths file:Query@479 ac2435bd4d5114736ba9b7e964f62d5fac3dd97eee5f11cbadd4facc342a9583 |
| +internal/host/claimservingartifact_linux.go servingAgentDirectory authority C,J4 rev3=PROPOSED-seed; agent descriptor identity before root RPC; no-follow directory open and existing statx device/inode/mount helper; used only for agent namespace checks 5edaa27cde0a470efb1a820ed15eca551df9c43fe6d7aa3c20f753bc3e3ae718 |
| +internal/host/claimservingartifact_other.go servingAgentDirectory authority C,J4 rev3=PROPOSED-seed; non-Linux refusal; no directory read or permission ef4826226980538555ab8152b6f5a8167b599eec64a2e48e53a7d8bd5e8332e7 |
| @coverage internal/lease/leasetest a589d673ded02391c4dfbbe6a9b86890f44522d3985bfa219c19d6ed40a0fd07 |
| internal/instancectl/claimexec.go claimExecGate.authorize authority C,G,J2,J4 rev3=PROPOSED-seed; root instance-control; systemd MainPID refreshed only within the same active invocation and cgroup at accepted trampoline connection; exact kernel peer match and repeated Same checks before permit 2c7461c0ce1f033f163bb660214b9e906945e75a8067bb1d6b1faa3b6916649d |
| internal/instancectl/claimunit.go ClaimUnits.start authority C,G,J2,J4 rev3=PROPOSED-seed; root instance-control; retain the refreshed authenticated trampoline MainPID for post-start kernel launch verification and exact-invocation cleanup 7f01cf7ac8fd9db0f74f7b08c412ee7d229ff2e097ae1d69706d8b3c87f1e30d |