WIP files seed checkpoint 15 (not for merge)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8 files changed,
+622
−32
internal/host/claimservinglegacyroot_linux.go+3 −0internal/host/claimservingnamespace_linux.go+173 −0internal/host/claimservingnamespace_linux_test.go+323 −0internal/host/claimservingpark_records.go+39 −26internal/host/claimservingpark_recovery_linux.go+12 −0internal/host/claimservingparkroot_linux.go+3 −0internal/host/claimservingpeer_rootrunner_linux_test.go+58 −0internal/provision/testdata/plan14-root-reads-seed.tsv+11 −6
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 352.275s
? github.com/carlosframework/platform/cmd/changelog [no test files]
ok github.com/carlosframework/platform/cmd/console 4.046s
ok github.com/carlosframework/platform/cmd/moveprobe 94.178s
ok github.com/carlosframework/platform/cmd/zddprobe 0.724s
ok github.com/carlosframework/platform/internal/activator 236.123s
ok github.com/carlosframework/platform/internal/agentsock 0.015s
ok github.com/carlosframework/platform/internal/alerting 0.137s
ok github.com/carlosframework/platform/internal/analytics 0.028s
ok github.com/carlosframework/platform/internal/appconfig 2.631s
ok github.com/carlosframework/platform/internal/apperr 0.130s
ok github.com/carlosframework/platform/internal/applog 0.153s
ok github.com/carlosframework/platform/internal/awsinv 0.006s
ok github.com/carlosframework/platform/internal/boxchoice 0.005s
ok github.com/carlosframework/platform/internal/boxid 0.897s
? 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.011s
ok github.com/carlosframework/platform/internal/caps 0.012s
ok github.com/carlosframework/platform/internal/certwarm 0.002s
ok github.com/carlosframework/platform/internal/cgroup 0.761s
ok github.com/carlosframework/platform/internal/changelog 0.424s
ok github.com/carlosframework/platform/internal/clamd 0.063s
? github.com/carlosframework/platform/internal/cliflag [no test files]
ok github.com/carlosframework/platform/internal/console 370.770s
? 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.961s
ok github.com/carlosframework/platform/internal/dbdoor/dbdoortest 0.011s
ok github.com/carlosframework/platform/internal/dbverify 0.018s
ok github.com/carlosframework/platform/internal/dnsrec 0.013s
ok github.com/carlosframework/platform/internal/docsite 0.141s
ok github.com/carlosframework/platform/internal/domains 2.421s
ok github.com/carlosframework/platform/internal/doorkey 0.153s
ok github.com/carlosframework/platform/internal/economics 0.007s
ok github.com/carlosframework/platform/internal/edge 27.336s
? github.com/carlosframework/platform/internal/edgeproof [no test files]
ok github.com/carlosframework/platform/internal/edgesfile 0.009s
ok github.com/carlosframework/platform/internal/fleetapi 0.162s
ok github.com/carlosframework/platform/internal/gate 1.891s
ok github.com/carlosframework/platform/internal/genselect 0.152s
ok github.com/carlosframework/platform/internal/geo 0.013s
ok github.com/carlosframework/platform/internal/geoip 0.032s
ok github.com/carlosframework/platform/internal/host 156.011s
ok github.com/carlosframework/platform/internal/hostactors 1.005s
ok github.com/carlosframework/platform/internal/hostbox 3.219s
ok github.com/carlosframework/platform/internal/hostclaim 1.703s
ok github.com/carlosframework/platform/internal/hostevidence 0.015s
ok github.com/carlosframework/platform/internal/hostexec 7.595s
ok github.com/carlosframework/platform/internal/hostfence 0.057s
ok github.com/carlosframework/platform/internal/hostfiles 11.040s
ok github.com/carlosframework/platform/internal/hostfloor 0.025s
ok github.com/carlosframework/platform/internal/hostguard 1.039s
ok github.com/carlosframework/platform/internal/hostops 2.574s
? github.com/carlosframework/platform/internal/hostops/fencerelease [no test files]
ok github.com/carlosframework/platform/internal/hostops/internal/fencerelease 0.003s
ok github.com/carlosframework/platform/internal/hostquarantine 0.004s
ok github.com/carlosframework/platform/internal/hostrelocate 0.818s
ok github.com/carlosframework/platform/internal/hostresolve 0.262s
ok github.com/carlosframework/platform/internal/hostwrite 0.003s
ok github.com/carlosframework/platform/internal/infraview 0.003s
ok github.com/carlosframework/platform/internal/instancectl 1.823s
ok github.com/carlosframework/platform/internal/instanceexec 3.420s
ok github.com/carlosframework/platform/internal/instancefs 0.006s
ok github.com/carlosframework/platform/internal/instancekey 0.004s
ok github.com/carlosframework/platform/internal/instanceupdates 0.037s
ok github.com/carlosframework/platform/internal/layout 2.536s
ok github.com/carlosframework/platform/internal/lease 9.659s
ok github.com/carlosframework/platform/internal/lease/leasetest 0.010s
ok github.com/carlosframework/platform/internal/ledger 0.051s
ok github.com/carlosframework/platform/internal/mail 0.539s
ok github.com/carlosframework/platform/internal/maintenance 0.219s
ok github.com/carlosframework/platform/internal/maintenanceexec 6.293s
ok github.com/carlosframework/platform/internal/maintenanceguard 0.227s
ok github.com/carlosframework/platform/internal/metrics 0.011s
ok github.com/carlosframework/platform/internal/moves 23.313s
ok github.com/carlosframework/platform/internal/moves/archive 1.532s
ok github.com/carlosframework/platform/internal/objstore 0.335s
ok github.com/carlosframework/platform/internal/ops 0.209s
ok github.com/carlosframework/platform/internal/park 0.359s
ok github.com/carlosframework/platform/internal/privatefile 0.004s
ok github.com/carlosframework/platform/internal/provision 16.345s
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.422s
ok github.com/carlosframework/platform/internal/relay 7.299s
ok github.com/carlosframework/platform/internal/release 22.934s
ok github.com/carlosframework/platform/internal/rollout 4.496s
ok github.com/carlosframework/platform/internal/rolloutwords 0.006s
ok github.com/carlosframework/platform/internal/rootaws 0.012s
ok github.com/carlosframework/platform/internal/rootparticipation 0.009s
ok github.com/carlosframework/platform/internal/rootstate 0.026s
ok github.com/carlosframework/platform/internal/s3compat 0.010s
ok github.com/carlosframework/platform/internal/safefile 0.008s
ok github.com/carlosframework/platform/internal/schedule 2.604s
ok github.com/carlosframework/platform/internal/sim/hostlife 22.281s
ok github.com/carlosframework/platform/internal/sim/litestreamfacts 3.963s
ok github.com/carlosframework/platform/internal/status 0.087s
ok github.com/carlosframework/platform/internal/steering 0.008s
ok github.com/carlosframework/platform/internal/stopevidence 0.400s
ok github.com/carlosframework/platform/internal/svcrole 0.002s
ok github.com/carlosframework/platform/internal/telemetry 0.020s
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.004s
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.917s
=== cross ===
--- shard 0 of 5 ---
cross-compiling darwin/arm64
--- shard 1 of 5 ---
cross-compiling darwin/amd64
--- shard 2 of 5 ---
cross-compiling linux/arm64
--- shard 3 of 5 ---
cross-compiling windows/amd64
--- shard 4 of 5 ---
cross-compiling windows/arm64
=== minio-contract ===
minio contract: arm64, minio RELEASE.2025-09-07T16-13-09Z, mc RELEASE.2025-08-13T08-35-41Z
minio: cached, digest verified
mc: cached, digest verified
minio healthy on 127.0.0.1:20999
Added `local` successfully.
Bucket created successfully `local/contract-bucket`.
Bucket created successfully `local/denied-bucket`.
Created policy `contract-only` successfully.
Added user `scopeduser` successfully.
Attached Policies: [contract-only]
To User: scopeduser
buckets and scoped user ready
=== RUN TestDeliveredContractMinio
=== RUN TestDeliveredContractMinio/put
=== RUN TestDeliveredContractMinio/get_round-trips_the_body
=== RUN TestDeliveredContractMinio/list_shows_the_key
=== RUN TestDeliveredContractMinio/head_and_list_agree_on_the_version
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_its_own_object
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed
=== RUN TestDeliveredContractMinio/delete_removes_it
=== RUN TestDeliveredContractMinio/cross-bucket_denial
--- PASS: TestDeliveredContractMinio (0.02s)
--- PASS: TestDeliveredContractMinio/put (0.00s)
--- PASS: TestDeliveredContractMinio/get_round-trips_the_body (0.00s)
--- PASS: TestDeliveredContractMinio/list_shows_the_key (0.00s)
--- PASS: TestDeliveredContractMinio/head_and_list_agree_on_the_version (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route (0.01s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed (0.00s)
--- PASS: TestDeliveredContractMinio/delete_removes_it (0.00s)
--- PASS: TestDeliveredContractMinio/cross-bucket_denial (0.00s)
PASS
ok github.com/carlosframework/platform/internal/objstore 0.021s
=== RUN TestT4StorageProbeMinio
2026/10/14 19:54:55 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18DE7D8142DA8DC3, HostID: 7c420053e0fbcd7f41653fdd92ffa2802c903d05303b1aa15adc2e8bb65d183f, api error SignatureDoesNotMatch: The request signature we calculated does not match the signature you provided. Check your key and signing method.
--- PASS: TestT4StorageProbeMinio (0.01s)
PASS
ok github.com/carlosframework/platform/internal/host 0.020s
minio contract: PASS
diff --git a/internal/host/claimservinglegacyroot_linux.go b/internal/host/claimservinglegacyroot_linux.go| index 459c9c82a..eeb5fe062 100644 |
| --- a/internal/host/claimservinglegacyroot_linux.go |
| +++ b/internal/host/claimservinglegacyroot_linux.go |
| @@ -232,6 +232,9 @@ func (p *servingParkRoot) parkLegacyServingDatabase(ctx context.Context, conn ne |
| if fresh.Absent || fresh.ETag != o.ETag { |
| return hostbox.ErrChanged |
| } |
| + if err = p.prepareServingNamespace(ctx, a, receipts, e); err != nil { |
| + return err |
| + } |
| r, err := p.ledger.parkWithEvidenceLocked(ctx, a, receipts, e) |
| if err != nil { |
| return err |
diff --git a/internal/host/claimservingnamespace_linux.go b/internal/host/claimservingnamespace_linux.go| new file mode 100644 |
| index 000000000..3856b7f69 |
| --- /dev/null |
| +++ b/internal/host/claimservingnamespace_linux.go |
| @@ -0,0 +1,173 @@ |
| +//go:build linux |
| + |
| +package host |
| + |
| +import ( |
| + "context" |
| + "errors" |
| + "os" |
| + |
| + "github.com/carlosframework/platform/internal/hostbox" |
| + "github.com/carlosframework/platform/internal/hostguard" |
| + "golang.org/x/sys/unix" |
| +) |
| + |
| +// The parent is established through a root-validated installation before any |
| +// namespace mutation. Startup can retain an obstruction using this evidence |
| +// even after the originating host has gone; it grants no artifact deletion. |
| +type servingParkParent struct { |
| + Version int |
| + Parent servingArtifactDirectory |
| +} |
| + |
| +func (f *servingParkFS) namespaceParent() (servingParkParent, error) { |
| + var p servingParkParent |
| + b, err := f.state.Read("namespace-parent.json") |
| + if err != nil { |
| + return p, err |
| + } |
| + if servingParkDecode(b, &p) != nil || p.Version != 1 || p.Parent.Device == 0 || p.Parent.Inode == 0 || p.Parent.Mount == 0 { |
| + return p, hostbox.ErrChanged |
| + } |
| + return p, nil |
| +} |
| + |
| +// The caller holds namespace and host locks and has checked the claim, root |
| +// installation, inventory and stop evidence. Directory is never a wire path. |
| +func (f *servingParkFS) recordNamespaceParent(ctx context.Context, current servingArtifactIdentity, directory servingArtifactDirectory) error { |
| + if err := ctx.Err(); err != nil { |
| + return err |
| + } |
| + source, expected, err := f.source(servingParkAuthorization{Successor: current, Directory: directory}) |
| + if err != nil { |
| + return err |
| + } |
| + defer source.Close() |
| + parent, actual, err := f.parent() |
| + if err != nil { |
| + return err |
| + } |
| + defer parent.Close() |
| + var st unix.Stat_t |
| + if err = unix.Fstat(int(parent.Fd()), &st); err != nil { |
| + return err |
| + } |
| + if actual != expected || st.Mode&0022 != 0 { |
| + return hostbox.ErrChanged |
| + } |
| + p, err := f.namespaceParent() |
| + if err == nil { |
| + if p.Parent != expected { |
| + return hostbox.ErrChanged |
| + } |
| + return nil |
| + } |
| + if !errors.Is(err, os.ErrNotExist) { |
| + return err |
| + } |
| + // Existing private records constrain a first parent capture too. A new |
| + // authenticated host cannot bless a replacement of an older mount parent. |
| + for _, name := range []string{"namespace.json", "namespace-quarantine.json"} { |
| + body, err := f.state.Read(name) |
| + if errors.Is(err, os.ErrNotExist) { |
| + continue |
| + } |
| + if err != nil { |
| + return err |
| + } |
| + if name == "namespace.json" { |
| + var n servingParkNamespace |
| + if servingParkDecode(body, &n) != nil || n.Version != 1 || n.Parent != expected || n.Root.Inode == 0 || n.Root.Device != expected.Device || n.Root.Mount != expected.Mount { |
| + return hostbox.ErrChanged |
| + } |
| + } else { |
| + var q servingParkQuarantine |
| + if servingParkDecode(body, &q) != nil || q.validate() != nil || q.Parent != expected { |
| + return hostbox.ErrChanged |
| + } |
| + } |
| + } |
| + if err = ctx.Err(); err != nil { |
| + return err |
| + } |
| + return (&servingParkLedger{state: f.state}).write("namespace-parent.json", servingParkParent{Version: 1, Parent: expected}) |
| +} |
| + |
| +// Called under the namespace lock. Absence of parent evidence never permits |
| +// recovery from a pathname alone. A normal empty installation needs no work. |
| +func (f *servingParkFS) recoverRecordedNamespace(ctx context.Context) error { |
| + if err := ctx.Err(); err != nil { |
| + return err |
| + } |
| + p, err := f.namespaceParent() |
| + if errors.Is(err, os.ErrNotExist) { |
| + return nil |
| + } |
| + if err != nil { |
| + return err |
| + } |
| + parent, id, err := f.parent() |
| + if err != nil { |
| + return err |
| + } |
| + defer parent.Close() |
| + var st unix.Stat_t |
| + if err = unix.Fstat(int(parent.Fd()), &st); err != nil { |
| + return err |
| + } |
| + if id != p.Parent || st.Mode&0022 != 0 { |
| + return hostbox.ErrChanged |
| + } |
| + // An unfinished quarantine must replay before ordinary namespace opening. |
| + if _, err = f.state.Read("namespace-quarantine.json"); err == nil { |
| + _, err = f.recoverNamespace(ctx, p.Parent) |
| + return err |
| + } else if !errors.Is(err, os.ErrNotExist) { |
| + return err |
| + } |
| + body, err := f.state.Read("namespace.json") |
| + 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 { |
| + return hostbox.ErrChanged |
| + } |
| + root, rid, err := f.privateDir(parent, ".serving-parked") |
| + if err == nil { |
| + root.Close() |
| + if rid == n.Root { |
| + return nil |
| + } |
| + } |
| + } else if errors.Is(err, os.ErrNotExist) { |
| + root, err := servingParkOpenDir(parent, ".serving-parked") |
| + if errors.Is(err, os.ErrNotExist) { |
| + return nil |
| + } |
| + if err != nil { |
| + return err |
| + } |
| + root.Close() |
| + } else { |
| + return err |
| + } |
| + _, err = f.recoverNamespace(ctx, p.Parent) |
| + return err |
| +} |
| + |
| +func (p *servingParkRoot) prepareServingNamespace(ctx context.Context, a servingParkAuthorization, receipts []hostguard.FenceReceipt, legacy *servingLegacyEvidence) error { |
| + fs, ok := p.ledger.fs.(*servingParkFS) |
| + if !ok { |
| + return hostbox.ErrUnsupported |
| + } |
| + if _, err := p.ledger.read(a.ID); errors.Is(err, os.ErrNotExist) { |
| + if _, err := p.ledger.capture(ctx, a, receipts, legacy); err != nil { |
| + return err |
| + } |
| + } else if err != nil { |
| + return err |
| + } |
| + if err := fs.recordNamespaceParent(ctx, a.Successor, a.Directory); err != nil { |
| + return err |
| + } |
| + return fs.recoverRecordedNamespace(ctx) |
| +} |
diff --git a/internal/host/claimservingnamespace_linux_test.go b/internal/host/claimservingnamespace_linux_test.go| new file mode 100644 |
| index 000000000..5dc77f2ca |
| --- /dev/null |
| +++ b/internal/host/claimservingnamespace_linux_test.go |
| @@ -0,0 +1,323 @@ |
| +//go:build linux |
| + |
| +package host |
| + |
| +import ( |
| + "context" |
| + "errors" |
| + "net" |
| + "os" |
| + "path/filepath" |
| + "reflect" |
| + "testing" |
| + |
| + "github.com/carlosframework/platform/internal/hostbox" |
| + "github.com/carlosframework/platform/internal/provision" |
| + "golang.org/x/sys/unix" |
| +) |
| + |
| +func TestServingNamespaceAuthenticatedParentAndRecovery(t *testing.T) { |
| + f := newServingRootFixture(t, true) |
| + fs := f.p.ledger.fs.(*servingParkFS) |
| + path := filepath.Join(fs.data, "instances", ".serving-parked") |
| + if err := os.Mkdir(path, 0700); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := os.WriteFile(filepath.Join(path, "unknown"), []byte("keep"), 0600); err != nil { |
| + t.Fatal(err) |
| + } |
| + before := f.f.read() |
| + a, err := f.park() |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + anchor, err := fs.namespaceParent() |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + r, err := f.p.ledger.read(a.ID) |
| + if err != nil || anchor.Parent != r.Source.Parent { |
| + t.Fatal("parent was not captured from the root installation", err) |
| + } |
| + var q servingParkQuarantine |
| + body, err := f.disk.Read("namespace-quarantine.json") |
| + if err != nil || servingParkDecode(body, &q) != nil || !q.Complete { |
| + t.Fatal("authenticated recovery", err) |
| + } |
| + body, err = os.ReadFile(filepath.Join(filepath.Dir(path), q.name(), "unknown")) |
| + if err != nil || string(body) != "keep" { |
| + t.Fatal("unknown bytes changed", err) |
| + } |
| + if !reflect.DeepEqual(f.f.read(), before) { |
| + t.Fatal("namespace recovery changed the claim") |
| + } |
| +} |
| + |
| +func TestServingNamespaceParentRequiresIndependentInstallation(t *testing.T) { |
| + for _, fault := range []string{"peer", "census", "installation", "inventory", "receipt", "source", "mode"} { |
| + t.Run(fault, func(t *testing.T) { |
| + f := newServingRootFixture(t, true) |
| + fs := f.p.ledger.fs.(*servingParkFS) |
| + path := filepath.Join(fs.data, "instances", ".serving-parked") |
| + if err := os.Mkdir(path, 0700); err != nil { |
| + t.Fatal(err) |
| + } |
| + switch fault { |
| + case "peer": |
| + f.p.root.Stop.Authenticate = func(context.Context, net.Conn) (StopPeer, error) { return StopPeer{}, hostbox.ErrChanged } |
| + case "census": |
| + f.p.root.RequireNewWork = func(context.Context) error { return hostbox.ErrChanged } |
| + case "installation": |
| + f.actor.Enrollment = "changed" |
| + case "inventory": |
| + f.actor.Revision++ |
| + case "receipt": |
| + f.mutateRoot(func(m map[string]any) { m["State"].(map[string]any)["fence_journal"] = []any{} }) |
| + case "source": |
| + if err := os.Rename(filepath.Dir(f.db), filepath.Dir(f.db)+"-saved"); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := os.Mkdir(filepath.Dir(f.db), 0700); err != nil { |
| + t.Fatal(err) |
| + } |
| + case "mode": |
| + if err := os.Chmod(filepath.Dir(path), 0777); err != nil { |
| + t.Fatal(err) |
| + } |
| + } |
| + if _, err := f.park(); err == nil { |
| + t.Fatal("unverified recovery accepted") |
| + } |
| + for _, name := range []string{"namespace-parent.json", "namespace.json", "namespace-quarantine.json"} { |
| + if _, err := f.disk.Read(name); !errors.Is(err, os.ErrNotExist) { |
| + t.Fatal("unverified recovery wrote", name, err) |
| + } |
| + } |
| + if _, err := os.Stat(path); err != nil { |
| + t.Fatal("obstruction moved without authority", err) |
| + } |
| + }) |
| + } |
| +} |
| + |
| +func TestServingNamespaceStartupUsesRecordedParent(t *testing.T) { |
| + for _, boundary := range []string{"parent", "quarantine-rename", "fresh", "complete"} { |
| + t.Run(boundary, func(t *testing.T) { |
| + f := newServingRootFixture(t, true) |
| + fs := f.p.ledger.fs.(*servingParkFS) |
| + path := filepath.Join(fs.data, "instances", ".serving-parked") |
| + if err := os.Mkdir(path, 0700); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := os.WriteFile(filepath.Join(path, "unknown"), []byte("retained"), 0600); err != nil { |
| + t.Fatal(err) |
| + } |
| + fs.state = quarantineWriteFault{Store: f.disk, after: func(name string, body []byte) error { |
| + var q servingParkQuarantine |
| + if boundary == "parent" && name == "namespace-parent.json" { |
| + return unix.EIO |
| + } |
| + if name == "namespace-quarantine.json" && servingParkDecode(body, &q) == nil && (boundary == "fresh" && q.Fresh.Inode != 0 || boundary == "complete" && q.Complete) { |
| + return unix.EIO |
| + } |
| + return nil |
| + }} |
| + if boundary == "quarantine-rename" { |
| + fs.rename = func(a int, b string, c int, d string, flags uint) error { |
| + if err := unix.Renameat2(a, b, c, d, flags); err != nil { |
| + return err |
| + } |
| + return unix.EIO |
| + } |
| + } |
| + if _, err := f.park(); !errors.Is(err, unix.EIO) { |
| + t.Fatal("missing interrupted boundary", err) |
| + } |
| + before := f.f.read() |
| + restarted := *f.p |
| + ledger := *f.p.ledger |
| + restarted.ledger = &ledger |
| + freshFS := newServingParkFS(fs.data, f.disk) |
| + freshFS.owner, freshFS.mount = fs.owner, fs.mount |
| + ledger.fs = freshFS |
| + restarted.root.Stop.Authenticate = func(context.Context, net.Conn) (StopPeer, error) { panic("startup used a peer") } |
| + // Namespace recovery needs only the independently recorded parent, |
| + // not a surviving source or a freshly selected host installation. |
| + if err := os.Rename(filepath.Dir(f.db), filepath.Dir(f.db)+"-saved"); err != nil { |
| + t.Fatal(err) |
| + } |
| + page, err := restarted.recoverServingParks(t.Context(), "", servingParkRecoveryLimit) |
| + if err != nil || len(page.Results) != 0 || page.More { |
| + t.Fatalf("startup recovery: %+v %v", page, err) |
| + } |
| + var q servingParkQuarantine |
| + body, err := f.disk.Read("namespace-quarantine.json") |
| + if err != nil || servingParkDecode(body, &q) != nil || !q.Complete { |
| + t.Fatal("startup did not finish quarantine", err) |
| + } |
| + body, err = os.ReadFile(filepath.Join(filepath.Dir(path), q.name(), "unknown")) |
| + if err != nil || string(body) != "retained" || !reflect.DeepEqual(before, f.f.read()) { |
| + t.Fatal("startup changed retained bytes or claim", err) |
| + } |
| + beforeRecord, err := f.disk.Read("namespace-quarantine.json") |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if page, err = restarted.recoverServingParks(t.Context(), "", servingParkRecoveryLimit); err != nil || len(page.Results) != 0 { |
| + t.Fatal("startup replay", page, err) |
| + } |
| + afterRecord, err := f.disk.Read("namespace-quarantine.json") |
| + if err != nil || string(beforeRecord) != string(afterRecord) { |
| + t.Fatal("completed startup rewrote quarantine", err) |
| + } |
| + }) |
| + } |
| +} |
| + |
| +func TestServingNamespaceStartupCannotCaptureParent(t *testing.T) { |
| + f, _, path := quarantineFixture(t) |
| + if err := f.ledger.withNamespace(t.Context(), func() error { return f.fs.recoverRecordedNamespace(t.Context()) }); err != nil { |
| + t.Fatal(err) |
| + } |
| + if _, err := os.Stat(filepath.Join(path, "unknown")); err != nil { |
| + t.Fatal("startup moved unrecorded obstruction", err) |
| + } |
| + for _, name := range []string{"namespace-parent.json", "namespace.json", "namespace-quarantine.json"} { |
| + if _, err := f.disk.Read(name); !errors.Is(err, os.ErrNotExist) { |
| + t.Fatal("startup created authority", name, err) |
| + } |
| + } |
| +} |
| + |
| +func TestServingNamespaceParentCannotBeReplaced(t *testing.T) { |
| + for _, fault := range []string{"parent", "corrupt-anchor", "corrupt-namespace", "mount", "mode"} { |
| + t.Run(fault, func(t *testing.T) { |
| + f := newServingRootFixture(t, true) |
| + a, err := f.park() |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + fs := f.p.ledger.fs.(*servingParkFS) |
| + before, err := f.disk.Read("namespace-parent.json") |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + switch fault { |
| + case "parent": |
| + path := filepath.Join(fs.data, "instances") |
| + if err := os.Rename(path, path+"-saved"); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := os.Mkdir(path, 0700); err != nil { |
| + t.Fatal(err) |
| + } |
| + // Even moving the genuine inventoried child into the replacement |
| + // parent does not authorize refreshing the recorded parent. |
| + if err := os.Rename(filepath.Join(path+"-saved", provision.InstanceID(f.current.Host)), filepath.Dir(f.db)); err != nil { |
| + t.Fatal(err) |
| + } |
| + case "corrupt-anchor": |
| + if err := f.disk.Write("namespace-parent.json", []byte("{}")); err != nil { |
| + t.Fatal(err) |
| + } |
| + before = []byte("{}") |
| + case "corrupt-namespace": |
| + if err := f.disk.Write("namespace.json", []byte("{}")); err != nil { |
| + t.Fatal(err) |
| + } |
| + case "mount": |
| + fs.mount = func(string) (bool, error) { return false, nil } |
| + case "mode": |
| + if err := os.Chmod(filepath.Join(fs.data, "instances"), 0777); err != nil { |
| + t.Fatal(err) |
| + } |
| + } |
| + page, err := f.p.recoverServingParks(t.Context(), "", servingParkRecoveryLimit) |
| + if err != nil || len(page.Results) == 0 || page.Results[0].ID != "namespace" || page.Results[0].Err == nil { |
| + t.Fatal("startup accepted unsafe parent", page, err) |
| + } |
| + if fault == "parent" { |
| + if err := f.p.ledger.withNamespace(t.Context(), func() error { |
| + return fs.recordNamespaceParent(t.Context(), f.current, a.Directory) |
| + }); err == nil { |
| + t.Fatal("parent recorder accepted a moved genuine source") |
| + } |
| + } |
| + if _, err := f.park(); err == nil { |
| + t.Fatal("authenticated request refreshed unsafe parent") |
| + } |
| + after, err := f.disk.Read("namespace-parent.json") |
| + if err != nil || string(before) != string(after) { |
| + t.Fatal("parent authority changed", err) |
| + } |
| + if _, err := f.disk.Read("namespace-quarantine.json"); !errors.Is(err, os.ErrNotExist) { |
| + t.Fatal("unsafe parent authorized quarantine", err) |
| + } |
| + }) |
| + } |
| +} |
| + |
| +func TestServingNamespaceParentUpgradePreservesEarlierEvidence(t *testing.T) { |
| + for _, fault := range []string{"", "parent", "corrupt-namespace", "corrupt-quarantine"} { |
| + t.Run(fault, func(t *testing.T) { |
| + f, parent, _ := quarantineFixture(t) |
| + f.put("", "current database") |
| + a, _ := f.evidence(10, true, "missing") |
| + if _, err := quarantineRecover(t, f, parent); err != nil { |
| + t.Fatal(err) |
| + } |
| + // These are checkpoint-14 namespace records: no parent anchor yet. |
| + switch fault { |
| + case "parent": |
| + body, err := f.disk.Read("namespace.json") |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + var n servingParkNamespace |
| + if err := servingParkDecode(body, &n); err != nil { |
| + t.Fatal(err) |
| + } |
| + n.Parent.Inode++ |
| + if err := f.ledger.write("namespace.json", n); err != nil { |
| + t.Fatal(err) |
| + } |
| + case "corrupt-namespace": |
| + if err := f.disk.Write("namespace.json", []byte("{}")); err != nil { |
| + t.Fatal(err) |
| + } |
| + case "corrupt-quarantine": |
| + if err := f.disk.Write("namespace-quarantine.json", []byte("{}")); err != nil { |
| + t.Fatal(err) |
| + } |
| + } |
| + err := f.ledger.withNamespace(t.Context(), func() error { return f.fs.recordNamespaceParent(t.Context(), a.Successor, a.Directory) }) |
| + if (err != nil) != (fault != "") { |
| + t.Fatal("parent upgrade", err) |
| + } |
| + if fault != "" { |
| + if _, err := f.disk.Read("namespace-parent.json"); !errors.Is(err, os.ErrNotExist) { |
| + t.Fatal("upgrade blessed changed private evidence", err) |
| + } |
| + return |
| + } |
| + before, err := f.disk.Read("namespace-parent.json") |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + // A failed write seam proves an established anchor is read-only. |
| + f.fs.state = quarantineWriteFault{Store: f.disk, before: func(string, []byte) error { return unix.EIO }} |
| + if err := f.ledger.withNamespace(t.Context(), func() error { |
| + if err := f.fs.recordNamespaceParent(t.Context(), a.Successor, a.Directory); err != nil { |
| + return err |
| + } |
| + return f.fs.recoverRecordedNamespace(t.Context()) |
| + }); err != nil { |
| + t.Fatal("completed recovery wrote state", err) |
| + } |
| + after, err := f.disk.Read("namespace-parent.json") |
| + if err != nil || string(before) != string(after) { |
| + t.Fatal("parent replay changed record", err) |
| + } |
| + }) |
| + } |
| +} |
diff --git a/internal/host/claimservingpark_records.go b/internal/host/claimservingpark_records.go| index 57a0cdc4c..584991273 100644 |
| --- a/internal/host/claimservingpark_records.go |
| +++ b/internal/host/claimservingpark_records.go |
| @@ -326,6 +326,42 @@ func (l *servingParkLedger) parkLocked(ctx context.Context, a servingParkAuthori |
| return l.parkWithEvidenceLocked(ctx, a, receipts, nil) |
| } |
| +// capture validates the full source before namespace recovery may write any |
| +// authority. The ledger repeats it before saving a fresh retirement intent. |
| +func (l *servingParkLedger) capture(ctx context.Context, a servingParkAuthorization, receipts []hostguard.FenceReceipt, legacy *servingLegacyEvidence) (servingParkRecord, error) { |
| + var zero servingParkRecord |
| + source, e := l.fs.Capture(a) |
| + if e != nil { |
| + return zero, e |
| + } |
| + disposition := servingParkArtifactFree |
| + if a.Legacy { |
| + if source.Files[1].Present { |
| + return zero, hostbox.ErrChanged |
| + } |
| + if source.Files[0].Present { |
| + disposition = servingParkRetain |
| + } |
| + } else { |
| + disposition, e = servingGenerationParkDisposition(ctx, l.journal, a.Predecessor, receipts, source.Files[0].Present) |
| + if e != nil { |
| + return zero, e |
| + } |
| + } |
| + r := servingParkRecord{Version: 1, Authorization: a, Source: source, Receipts: receipts, LegacyEvidence: legacy, Disposition: disposition} |
| + if legacy != nil && legacy.Selector.Empty { |
| + for _, f := range source.Files { |
| + if f.Present { |
| + return zero, hostbox.ErrChanged |
| + } |
| + } |
| + } |
| + if e = validateServingParkRecord(r); e != nil { |
| + return zero, e |
| + } |
| + return r, nil |
| +} |
| + |
| func (l *servingParkLedger) parkWithEvidenceLocked(ctx context.Context, a servingParkAuthorization, receipts []hostguard.FenceReceipt, legacy *servingLegacyEvidence) (servingParkRecord, error) { |
| var zero servingParkRecord |
| if err := ctx.Err(); err != nil { |
| @@ -350,35 +386,12 @@ func (l *servingParkLedger) parkWithEvidenceLocked(ctx context.Context, a servin |
| defer unlockRecord() |
| r, err := l.read(a.ID) |
| if errors.Is(err, os.ErrNotExist) { |
| - source, e := l.fs.Capture(a) |
| + captured, e := l.capture(ctx, a, receipts, legacy) |
| if e != nil { |
| return zero, e |
| } |
| - disposition := servingParkArtifactFree |
| - if a.Legacy { |
| - if source.Files[1].Present { |
| - return zero, hostbox.ErrChanged |
| - } |
| - if source.Files[0].Present { |
| - disposition = servingParkRetain |
| - } |
| - } else { |
| - disposition, e = servingGenerationParkDisposition(ctx, l.journal, a.Predecessor, receipts, source.Files[0].Present) |
| - if e != nil { |
| - return zero, e |
| - } |
| - } |
| - r = servingParkRecord{Version: 1, Authorization: a, Source: source, Receipts: receipts, LegacyEvidence: legacy, Disposition: disposition} |
| - if legacy != nil && legacy.Selector.Empty { |
| - for _, f := range source.Files { |
| - if f.Present { |
| - return zero, hostbox.ErrChanged |
| - } |
| - } |
| - } |
| - if e = validateServingParkRecord(r); e != nil { |
| - return zero, e |
| - } |
| + r = captured |
| + source := r.Source |
| // Consumption survives slot replacement, expiry and park tombstones. |
| if a.Legacy { |
| key := "legacy-" + servingParkKey(a.Successor.Box, a.Successor.Host+"\x00"+a.Successor.Incarnation) + ".json" |
diff --git a/internal/host/claimservingpark_recovery_linux.go b/internal/host/claimservingpark_recovery_linux.go| index df0e58392..80d0124dc 100644 |
| --- a/internal/host/claimservingpark_recovery_linux.go |
| +++ b/internal/host/claimservingpark_recovery_linux.go |
| @@ -82,6 +82,18 @@ func (p *servingParkRoot) recoverServingParks(ctx context.Context, after string, |
| if p == nil || p.ledger == nil { |
| return out, hostbox.ErrUnsupported |
| } |
| + if after == "" { |
| + err := p.ledger.withNamespace(ctx, func() error { |
| + fs, ok := p.ledger.fs.(*servingParkFS) |
| + if !ok { |
| + return hostbox.ErrUnsupported |
| + } |
| + return fs.recoverRecordedNamespace(ctx) |
| + }) |
| + if err != nil { |
| + out.Results = append(out.Results, servingParkRecoveryResult{Host: "unknown", ID: "namespace", Err: err}) |
| + } |
| + } |
| ids, more, err := p.ledger.recoveryIDs(ctx, after, limit) |
| if err != nil { |
| return out, err |
diff --git a/internal/host/claimservingparkroot_linux.go b/internal/host/claimservingparkroot_linux.go| index 1ea58b509..406ff955a 100644 |
| --- a/internal/host/claimservingparkroot_linux.go |
| +++ b/internal/host/claimservingparkroot_linux.go |
| @@ -232,6 +232,9 @@ func (p *servingParkRoot) parkServingDatabase(ctx context.Context, conn net.Conn |
| if saved != nil && !sameServingRootStops(saved.Receipts, receipts) { |
| return ErrClaimStopReceipt |
| } |
| + if err = p.prepareServingNamespace(ctx, a, receipts, nil); err != nil { |
| + return err |
| + } |
| r, err := p.ledger.parkLocked(ctx, a, receipts) |
| if err != nil { |
| return err |
diff --git a/internal/host/claimservingpeer_rootrunner_linux_test.go b/internal/host/claimservingpeer_rootrunner_linux_test.go| index f825123c2..f1c027148 100644 |
| --- a/internal/host/claimservingpeer_rootrunner_linux_test.go |
| +++ b/internal/host/claimservingpeer_rootrunner_linux_test.go |
| @@ -26,6 +26,7 @@ import ( |
| "github.com/carlosframework/platform/internal/hostguard" |
| "github.com/carlosframework/platform/internal/instancectl" |
| "github.com/carlosframework/platform/internal/rootstate" |
| + "golang.org/x/sys/unix" |
| ) |
| // The control socket coordinates the test only. Every effect request uses the |
| @@ -198,6 +199,15 @@ func TestRootServingParkAuthenticatedHandover(t *testing.T) { |
| } |
| func runServingParkAuthenticatedPeer(t *testing.T, handover, writer bool) { |
| + t.Helper() |
| + runServingParkPeerNamespace(t, handover, writer, false) |
| +} |
| + |
| +func TestRootServingNamespaceStartup(t *testing.T) { |
| + runServingParkPeerNamespace(t, true, false, true) |
| +} |
| + |
| +func runServingParkPeerNamespace(t *testing.T, handover, writer, namespace bool) { |
| t.Helper() |
| servingRootRunner(t) |
| pid1, err := os.ReadFile("/proc/1/comm") |
| @@ -488,6 +498,54 @@ func runServingParkAuthenticatedPeer(t *testing.T, handover, writer bool) { |
| if err != nil || actual.ControllerPID != next.PID || actual.ProcessStart != next.ProcessStart || len(actual.Groups) != 1 || actual.Groups[0] != pre.Launch.Groups[0] { |
| t.Fatalf("prelaunch not retained: %+v %v", actual, err) |
| } |
| + if namespace { |
| + fs := f.p.ledger.fs.(*servingParkFS) |
| + parkPath := filepath.Join(fs.data, "instances", ".serving-parked") |
| + if err := os.Mkdir(parkPath, 0700); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := os.WriteFile(filepath.Join(parkPath, "unknown"), []byte("retained unknown bytes"), 0600); err != nil { |
| + t.Fatal(err) |
| + } |
| + // Only the reply after the real private parent write is lost. Its |
| + // identity comes from production authenticated installation checks. |
| + fs.state = quarantineWriteFault{Store: state, after: func(name string, _ []byte) error { |
| + if name == "namespace-parent.json" { |
| + return unix.EIO |
| + } |
| + return nil |
| + }} |
| + command(q, true) |
| + fs.state = state |
| + if _, err := state.Read("namespace-quarantine.json"); !errors.Is(err, os.ErrNotExist) { |
| + t.Fatal("lost parent reply did not stop before quarantine", err) |
| + } |
| + before := f.f.read() |
| + restarted := *f.p |
| + ledger := *f.p.ledger |
| + ledger.fs = newServingParkFS(fs.data, state) |
| + restarted.ledger = &ledger |
| + page, err := restarted.recoverServingParks(t.Context(), "", servingParkRecoveryLimit) |
| + if err != nil || len(page.Results) != 0 || page.More { |
| + t.Fatalf("root namespace startup: %+v %v", page, err) |
| + } |
| + var quarantine servingParkQuarantine |
| + body, err := state.Read("namespace-quarantine.json") |
| + if err != nil || servingParkDecode(body, &quarantine) != nil || !quarantine.Complete { |
| + t.Fatal("startup did not complete quarantine", err) |
| + } |
| + if quarantine.Parent.Mount != quarantine.Obstruction.Mount || quarantine.Parent.Mount != quarantine.Fresh.Mount { |
| + t.Fatal("startup crossed the bind mount") |
| + } |
| + body, err = os.ReadFile(filepath.Join(filepath.Dir(parkPath), quarantine.name(), "unknown")) |
| + if err != nil || string(body) != "retained unknown bytes" || !reflect.DeepEqual(before, f.f.read()) { |
| + t.Fatal("startup changed unknown bytes or claim", err) |
| + } |
| + body, err = os.ReadFile(f.db) |
| + if err != nil || string(body) != "served bytes" { |
| + t.Fatal("namespace recovery changed source", err) |
| + } |
| + } |
| parked := command(q, false).Authorization |
| if parked.ID == "" { |
| t.Fatal("empty park identity") |
diff --git a/internal/provision/testdata/plan14-root-reads-seed.tsv b/internal/provision/testdata/plan14-root-reads-seed.tsv| index 7736e0b23..66e209653 100644 |
| --- a/internal/provision/testdata/plan14-root-reads-seed.tsv |
| +++ b/internal/provision/testdata/plan14-root-reads-seed.tsv |
| @@ -1,4 +1,4 @@ |
| -# Files seed checkpoint 14 overlay; pending Paul’s census ruling. |
| +# Files seed checkpoint 15 overlay; pending Paul’s census ruling. |
| # Database retirement activation remains refused; serving installation transfer uses the existing root transport. |
| # Load after zddr4 and its explicit seal check. No legacy fingerprints are added or removed. |
| # Every classification below is rev3=PROPOSED-seed, not an approved disposition. |
| @@ -69,13 +69,13 @@ internal/host/claimservingparksource_linux.go servingParkStateDirectory authorit |
| 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 4a1d89b8f65ed52e1aedf00595021ec9b42bfd1fed4a3466a512fbdd6078538c |
| +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 dfdcf02e2dafb0586ecf40884a5f4265fbbd3c0fcd26498ce8851c957a24a942 |
| +@coverage internal/host 203b52f5900b66476f3ecc3cad8b8ea2526ceee7df31b8bae6652eb718158b5d |
| @coverage internal/hostbox eb1f4980ba609cc886ae4bd73aedbb6be3fb2eb104165bb1d8404da0814ed190 |
| @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 |
| @@ -87,9 +87,9 @@ internal/host/claimservinglegacy.go validateServingLegacyEvidence authority C,J4 |
| 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; current root authority, persistent consumption, immutable original stop/source and continuation stops; production first-creator empty listing and final claim recheck precede source capture file:Read@3463 4a883d8cc15d58a9a9572d08790173773f35afd46b09caaf9ac99c9ec3a39d1c |
| +internal/host/claimservinglegacyroot_linux.go servingParkRoot.parkLegacyServingDatabase authority C,J4 rev3=PROPOSED-seed; current root authority, persistent consumption, immutable original stop/source and continuation stops; production first-creator empty listing and final claim recheck precede source capture; validated legacy authority records namespace parent before bounded quarantine recovery file:Read@3463 1330ef46dadfd17cc0e31100910a86f0bc94b02118429815e696696a61963eab |
| 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 925e4f20aee042895a83ea3787f4482ae732b6c6bcd45d9d10226ff87bd37091 |
| -internal/host/claimservingpark_records.go servingParkLedger.parkWithEvidenceLocked authority C,J4 rev3=PROPOSED-seed; root source identities, exact stop bytes, launch disposition and optional session-zero evidence; persistent consumption precedes intent/rename, empty creators refuse all local artifacts file:Prepare@3632,file:Read@1957 257d2c2674dd3a781f6b1ec842d65128a8e9f3d03b5468cbf6f487c37b45e94f |
| +internal/host/claimservingpark_records.go servingParkLedger.parkWithEvidenceLocked authority C,J4 rev3=PROPOSED-seed; root source identities, exact stop bytes, launch disposition and optional session-zero evidence; persistent consumption precedes intent/rename, empty creators refuse all local artifacts; shared read-only capture validates artifacts and disposition before any new authority write file:Prepare@2947,file:Read@1272 8dce52f721fceb7e14bbc3d3078fca00a508c987159bb100aa9e0262a4583de2 |
| 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 |
| @@ -136,7 +136,7 @@ internal/host/claimservingpark_recovery.go servingParkLedger.recoverCompleted au |
| 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_linux.go sameServingLegacyConsumption authority C,J4 rev3=PROPOSED-seed; immutable legacy consumption and retained retirement source identities; excludes only per-artifact journal progress from comparison 1524b2aae315e05734a666f80596bc341a007098a84fedeaf1e32451663d4fd4 |
| 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 0839c879792469648ceb62edce21319050351cf4db0a57c1a5220282afb7808f |
| -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 c2f1d9da88c4aaea97783a6d3f3e6b78856f20402e55900cc96d88e8c88ba323 |
| +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 |
| @@ -161,3 +161,8 @@ internal/host/claimservingquarantine_linux.go servingParkQuarantine.name authori |
| internal/host/claimservingquarantine_linux.go servingParkQuarantine.validate authority C,J4 rev3=PROPOSED-seed; private quarantine shape, same-mount parent/obstruction/fresh identities and monotonic phase relationships; retention only f7fa1711feac1ebe57f0a821dcbcdb90564506ebcea7a6e267ec5943a852fac2 |
| internal/host/claimservingquarantine_linux.go type servingParkQuarantine authority C,J4 rev3=PROPOSED-seed; private versioned quarantine intent with previous namespace and exact obstruction/fresh-root identities; no artifact deletion authority 0098723e5b7bbbede18d6485a9ffb7766995c31b25fdec78997d914e6b5d18fe |
| 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 file:Read@95 4f6fa5659078157960b790e456b6af3f55de26494ae5ff6231efc5532e2c8677 |
| +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 file:Fstat@473,file:Read@1037 1f1d439eaab06b7d93822a96a962ba2d71ac0b22cdd35972a65a3919c4e3dd06 |
| +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 file:Fstat@362,file:Read@588,file:Read@777 0e0774ff8b7532889f0995f9c5c5ecdb6a4bfa7e10619f7806f76299ada44198 |
| +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 |