carlos / platform Public

WIP files seed checkpoint 19 (not for merge)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev 757266c56e6e38c0f7aaf85e55583a2186f0a820 parent 384e0fd
8 files changed, +388 −28
  • internal/host/claimservingboot_linux.go +57 −0
  • internal/host/claimservingboot_linux_test.go +196 −0
  • internal/host/claimservingnamespace_linux.go +13 −5
  • internal/host/claimservingpark_linux.go +16 −3
  • internal/host/claimservingpark_rootrunner_linux_test.go +64 −0
  • internal/host/claimservingquarantine_linux.go +19 −7
  • internal/host/claimservingquarantine_linux_test.go +6 −2
  • internal/provision/testdata/plan14-root-reads-seed.tsv +17 −11

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	337.860s
?   	github.com/carlosframework/platform/cmd/changelog	[no test files]
ok  	github.com/carlosframework/platform/cmd/console	3.215s
ok  	github.com/carlosframework/platform/cmd/moveprobe	93.215s
ok  	github.com/carlosframework/platform/cmd/zddprobe	0.705s
ok  	github.com/carlosframework/platform/internal/activator	235.088s
ok  	github.com/carlosframework/platform/internal/agentsock	0.017s
ok  	github.com/carlosframework/platform/internal/alerting	0.010s
ok  	github.com/carlosframework/platform/internal/analytics	0.021s
ok  	github.com/carlosframework/platform/internal/appconfig	2.324s
ok  	github.com/carlosframework/platform/internal/apperr	0.076s
ok  	github.com/carlosframework/platform/internal/applog	0.138s
ok  	github.com/carlosframework/platform/internal/awsinv	0.005s
ok  	github.com/carlosframework/platform/internal/boxchoice	0.004s
ok  	github.com/carlosframework/platform/internal/boxid	0.976s
?   	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.010s
ok  	github.com/carlosframework/platform/internal/caps	0.013s
ok  	github.com/carlosframework/platform/internal/certwarm	0.004s
ok  	github.com/carlosframework/platform/internal/cgroup	0.694s
ok  	github.com/carlosframework/platform/internal/changelog	0.311s
ok  	github.com/carlosframework/platform/internal/clamd	0.054s
?   	github.com/carlosframework/platform/internal/cliflag	[no test files]
ok  	github.com/carlosframework/platform/internal/console	362.722s
?   	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.144s
ok  	github.com/carlosframework/platform/internal/dbdoor/dbdoortest	0.010s
ok  	github.com/carlosframework/platform/internal/dbverify	0.005s
ok  	github.com/carlosframework/platform/internal/dnsrec	0.004s
ok  	github.com/carlosframework/platform/internal/docsite	0.005s
ok  	github.com/carlosframework/platform/internal/domains	2.106s
ok  	github.com/carlosframework/platform/internal/doorkey	0.007s
ok  	github.com/carlosframework/platform/internal/economics	0.005s
ok  	github.com/carlosframework/platform/internal/edge	26.199s
?   	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.131s
ok  	github.com/carlosframework/platform/internal/gate	1.666s
ok  	github.com/carlosframework/platform/internal/genselect	0.016s
ok  	github.com/carlosframework/platform/internal/geo	0.013s
ok  	github.com/carlosframework/platform/internal/geoip	0.036s
ok  	github.com/carlosframework/platform/internal/host	147.226s
ok  	github.com/carlosframework/platform/internal/hostactors	0.421s
ok  	github.com/carlosframework/platform/internal/hostbox	2.496s
ok  	github.com/carlosframework/platform/internal/hostclaim	0.508s
ok  	github.com/carlosframework/platform/internal/hostevidence	0.005s
ok  	github.com/carlosframework/platform/internal/hostexec	4.812s
ok  	github.com/carlosframework/platform/internal/hostfence	0.043s
ok  	github.com/carlosframework/platform/internal/hostfiles	10.497s
ok  	github.com/carlosframework/platform/internal/hostfloor	0.012s
ok  	github.com/carlosframework/platform/internal/hostguard	1.006s
ok  	github.com/carlosframework/platform/internal/hostops	1.437s
?   	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.005s
ok  	github.com/carlosframework/platform/internal/hostrelocate	0.460s
ok  	github.com/carlosframework/platform/internal/hostresolve	0.150s
ok  	github.com/carlosframework/platform/internal/hostwrite	0.001s
ok  	github.com/carlosframework/platform/internal/infraview	0.005s
ok  	github.com/carlosframework/platform/internal/instancectl	1.813s
ok  	github.com/carlosframework/platform/internal/instanceexec	2.714s
ok  	github.com/carlosframework/platform/internal/instancefs	0.005s
ok  	github.com/carlosframework/platform/internal/instancekey	0.002s
ok  	github.com/carlosframework/platform/internal/instanceupdates	0.008s
ok  	github.com/carlosframework/platform/internal/layout	1.422s
ok  	github.com/carlosframework/platform/internal/lease	9.445s
ok  	github.com/carlosframework/platform/internal/lease/leasetest	0.006s
ok  	github.com/carlosframework/platform/internal/ledger	0.016s
ok  	github.com/carlosframework/platform/internal/mail	0.180s
ok  	github.com/carlosframework/platform/internal/maintenance	0.092s
ok  	github.com/carlosframework/platform/internal/maintenanceexec	5.509s
ok  	github.com/carlosframework/platform/internal/maintenanceguard	0.077s
ok  	github.com/carlosframework/platform/internal/metrics	0.005s
ok  	github.com/carlosframework/platform/internal/moves	21.062s
ok  	github.com/carlosframework/platform/internal/moves/archive	0.770s
ok  	github.com/carlosframework/platform/internal/objstore	0.175s
ok  	github.com/carlosframework/platform/internal/ops	0.167s
ok  	github.com/carlosframework/platform/internal/park	0.200s
ok  	github.com/carlosframework/platform/internal/privatefile	0.005s
ok  	github.com/carlosframework/platform/internal/provision	10.911s
ok  	github.com/carlosframework/platform/internal/publicrelease	0.008s
?   	github.com/carlosframework/platform/internal/regionname	[no test files]
ok  	github.com/carlosframework/platform/internal/registry	4.050s
ok  	github.com/carlosframework/platform/internal/relay	4.202s
ok  	github.com/carlosframework/platform/internal/release	19.310s
ok  	github.com/carlosframework/platform/internal/rollout	3.660s
ok  	github.com/carlosframework/platform/internal/rolloutwords	0.003s
ok  	github.com/carlosframework/platform/internal/rootaws	0.007s
ok  	github.com/carlosframework/platform/internal/rootparticipation	0.007s
ok  	github.com/carlosframework/platform/internal/rootstate	0.025s
ok  	github.com/carlosframework/platform/internal/s3compat	0.005s
ok  	github.com/carlosframework/platform/internal/safefile	0.004s
ok  	github.com/carlosframework/platform/internal/schedule	2.282s
ok  	github.com/carlosframework/platform/internal/sim/hostlife	8.850s
ok  	github.com/carlosframework/platform/internal/sim/litestreamfacts	3.856s
ok  	github.com/carlosframework/platform/internal/status	0.039s
ok  	github.com/carlosframework/platform/internal/steering	0.006s
ok  	github.com/carlosframework/platform/internal/stopevidence	0.292s
ok  	github.com/carlosframework/platform/internal/svcrole	0.002s
ok  	github.com/carlosframework/platform/internal/telemetry	0.003s
ok  	github.com/carlosframework/platform/internal/termfence	0.004s
ok  	github.com/carlosframework/platform/internal/tunnelclient	0.003s
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.001s
ok  	github.com/carlosframework/platform/internal/vet	1.544s
=== 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.022s
=== RUN   TestT4StorageProbeMinio
2026/10/14 20:58:13 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18DE80F57E1FD6BC, 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.021s
minio contract: PASS
diff --git a/internal/host/claimservingboot_linux.go b/internal/host/claimservingboot_linux.go
new file mode 100644
index 000000000..9ad30c1b6
--- /dev/null
+++ b/internal/host/claimservingboot_linux.go
@@ -0,0 +1,57 @@
+//go:build linux
+
+package host
+
+import (
+ "encoding/hex"
+ "io"
+ "os"
+ "strings"
+
+ "github.com/carlosframework/platform/internal/hostbox"
+)
+
+func servingParkBootID() (string, error) {
+ f, err := os.Open("/proc/sys/kernel/random/boot_id")
+ if err != nil {
+ return "", err
+ }
+ defer f.Close()
+ b, err := io.ReadAll(io.LimitReader(f, 38))
+ if err != nil || len(b) != 37 || b[36] != '\n' {
+ return "", hostbox.ErrChanged
+ }
+ return string(b[:36]), nil
+}
+
+func (f *servingParkFS) currentBoot() (string, error) {
+ if f == nil || f.boot == nil {
+ return "", hostbox.ErrUnsupported
+ }
+ id, err := f.boot()
+ if err != nil {
+ return "", err
+ }
+ if len(id) != 36 || id[8] != '-' || id[13] != '-' || id[18] != '-' || id[23] != '-' {
+ return "", hostbox.ErrChanged
+ }
+ raw := strings.ReplaceAll(id, "-", "")
+ b, err := hex.DecodeString(raw)
+ if err != nil || len(b) != 16 || hex.EncodeToString(b) != raw || raw == strings.Repeat("0", 32) {
+ return "", hostbox.ErrChanged
+ }
+ return id, nil
+}
+
+// A mount number is meaningful only in the boot that supplied it. Old records
+// without a boot remain readable, but cannot authorize namespace mutations.
+func (f *servingParkFS) checkBoot(expected string) error {
+ current, err := f.currentBoot()
+ if err != nil {
+ return err
+ }
+ if expected == "" || current != expected {
+ return hostbox.ErrChanged
+ }
+ return nil
+}
diff --git a/internal/host/claimservingboot_linux_test.go b/internal/host/claimservingboot_linux_test.go
new file mode 100644
index 000000000..4d91d9eb7
--- /dev/null
+++ b/internal/host/claimservingboot_linux_test.go
@@ -0,0 +1,196 @@
+//go:build linux
+
+package host
+
+import (
+ "bytes"
+ "errors"
+ "os"
+ "path/filepath"
+ "reflect"
+ "strings"
+ "testing"
+
+ "github.com/carlosframework/platform/internal/hostbox"
+ "golang.org/x/sys/unix"
+)
+
+const servingOtherBoot = "01234567-89ab-4cde-8fab-0123456789ab"
+
+func TestServingParkBootIdentity(t *testing.T) {
+ f := newServingParkFS("/data", nil)
+ id, err := f.currentBoot()
+ if err != nil || id == "" || f.checkBoot(id) != nil {
+ t.Fatal("kernel boot identity", id, err)
+ }
+ for _, id := range []string{"", "not-a-boot", strings.Repeat("0", 36), "00000000-0000-0000-0000-000000000000", strings.ToUpper(servingOtherBoot), servingOtherBoot + "\n", "01234567-89ab-4cde-8fab-0123456789ag"} {
+ t.Run(id, func(t *testing.T) {
+ f.boot = func() (string, error) { return id, nil }
+ if _, err := f.currentBoot(); !errors.Is(err, hostbox.ErrChanged) {
+ t.Fatal("malformed boot accepted", err)
+ }
+ })
+ }
+ f.boot = func() (string, error) { return "", unix.EIO }
+ if _, err := f.currentBoot(); !errors.Is(err, unix.EIO) {
+ t.Fatal("boot read failure hidden", err)
+ }
+ f.boot = nil
+ if _, err := f.currentBoot(); !errors.Is(err, hostbox.ErrUnsupported) {
+ t.Fatal("missing boot dependency accepted", err)
+ }
+}
+
+// Change only the boot observation. Device, inode and mount number stay equal,
+// reproducing the equality that a recycled mount ID can produce after reboot.
+func TestServingParkBootReuseRefusesEffects(t *testing.T) {
+ for _, fault := range []string{"reboot", "missing-boot", "read-error", "malformed"} {
+ t.Run(fault, func(t *testing.T) {
+ f := newServingParkDiskFixture(t)
+ a, receipts := f.evidence(1, true, "missing")
+ f.put("", "last writer")
+ r, err := f.park(a, receipts)
+ if err != nil {
+ t.Fatal(err)
+ }
+ f.complete(a)
+ before, err := f.disk.Read("namespace.json")
+ if err != nil {
+ t.Fatal(err)
+ }
+ var n servingParkNamespace
+ if servingParkDecode(before, &n) != nil || n.BootID == "" || n.BootID == servingOtherBoot {
+ t.Fatal("fixture did not capture the current boot")
+ }
+ parent, id, err := f.fs.parent()
+ if err != nil {
+ t.Fatal(err)
+ }
+ parent.Close()
+ if id != r.Source.Parent {
+ t.Fatal("test changed more than the boot")
+ }
+ switch fault {
+ case "reboot":
+ f.fs.boot = func() (string, error) { return servingOtherBoot, nil }
+ case "missing-boot":
+ n.BootID = ""
+ if err := f.ledger.write("namespace.json", n); err != nil {
+ t.Fatal(err)
+ }
+ case "read-error":
+ f.fs.boot = func() (string, error) { return "", unix.EIO }
+ case "malformed":
+ f.fs.boot = func() (string, error) { return "invalid", nil }
+ }
+ before, err = f.disk.Read("namespace.json")
+ if err != nil {
+ t.Fatal(err)
+ }
+ writes := 0
+ save := func() error { writes++; return nil }
+ for name, call := range map[string]func(*servingParkRecord) error{
+ "inspect": f.fs.CheckParked,
+ "prepare": func(r *servingParkRecord) error { return f.fs.Prepare(r, save) },
+ "move": func(r *servingParkRecord) error { return f.fs.Move(r, save) },
+ "delete": func(r *servingParkRecord) error { return f.fs.Delete(r, save) },
+ } {
+ copy := r
+ if err := call(&copy); err == nil || !reflect.DeepEqual(r, copy) || writes != 0 {
+ t.Fatal(name, "reused boot authority", err, writes)
+ }
+ }
+ after, err := f.disk.Read("namespace.json")
+ if err != nil || !bytes.Equal(before, after) {
+ t.Fatal("refusal rewrote namespace", err)
+ }
+ body, err := os.ReadFile(f.path(a, ""))
+ if err != nil || string(body) != "last writer" {
+ t.Fatal("refusal changed database", err)
+ }
+ })
+ }
+}
+
+func TestServingNamespaceBootRecoveryRefusesAdoption(t *testing.T) {
+ for _, boundary := range []string{"parent", "namespace", "quarantine", "old-parent", "old-namespace", "old-quarantine"} {
+ 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 strings.Contains(boundary, "quarantine") {
+ if err := os.Mkdir(path, 0700); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.WriteFile(filepath.Join(path, "unknown"), []byte("retain"), 0600); err != nil {
+ t.Fatal(err)
+ }
+ }
+ a, err := f.park()
+ if err != nil {
+ t.Fatal(err)
+ }
+ anchor, err := fs.namespaceParent()
+ if err != nil || anchor.BootID == "" {
+ t.Fatal("missing boot anchor", err)
+ }
+ name := "namespace-parent.json"
+ switch boundary {
+ case "namespace", "old-namespace":
+ name = "namespace.json"
+ case "quarantine", "old-quarantine":
+ name = "namespace-quarantine.json"
+ }
+ if strings.HasPrefix(boundary, "old-") {
+ body, err := f.disk.Read(name)
+ if err != nil {
+ t.Fatal(err)
+ }
+ body = bytes.ReplaceAll(body, []byte(`"BootID":"`+anchor.BootID+`",`), nil)
+ if err := f.disk.Write(name, body); err != nil {
+ t.Fatal(err)
+ }
+ } else {
+ fs.boot = func() (string, error) { return servingOtherBoot, nil }
+ }
+ before := servingBootSnapshot(t, f.disk.dir)
+ claim := f.f.read()
+ if err := fs.recoverRecordedNamespace(t.Context()); err == nil {
+ t.Fatal("startup accepted missing or changed boot")
+ }
+ if _, err := fs.recoverNamespace(t.Context(), anchor.Parent); err == nil {
+ t.Fatal("quarantine accepted missing or changed boot")
+ }
+ if err := fs.recordNamespaceParent(t.Context(), a.Successor, a.Directory); err == nil {
+ // A valid existing anchor need not examine the namespace, but it
+ // must never overwrite the earlier boot or infer a missing one.
+ if boundary != "old-namespace" && boundary != "old-quarantine" {
+ t.Fatal("authenticated parent adopted old boot")
+ }
+ }
+ if after := servingBootSnapshot(t, f.disk.dir); !reflect.DeepEqual(before, after) || !reflect.DeepEqual(claim, f.f.read()) {
+ t.Fatal("recovery refusal changed private evidence or claim")
+ }
+ })
+ }
+}
+
+func servingBootSnapshot(t *testing.T, root string) map[string]string {
+ t.Helper()
+ names, err := os.ReadDir(root)
+ if err != nil {
+ t.Fatal(err)
+ }
+ out := make(map[string]string)
+ for _, name := range names {
+ if name.IsDir() {
+ continue
+ }
+ body, err := os.ReadFile(filepath.Join(root, name.Name()))
+ if err != nil {
+ t.Fatal(err)
+ }
+ out[name.Name()] = string(body)
+ }
+ return out
+}
diff --git a/internal/host/claimservingnamespace_linux.go b/internal/host/claimservingnamespace_linux.go
index 3856b7f69..cdaa6a090 100644
--- a/internal/host/claimservingnamespace_linux.go
+++ b/internal/host/claimservingnamespace_linux.go
@@ -16,6 +16,7 @@ import (
// 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 {
+ BootID string `json:",omitempty"`
Version int
Parent servingArtifactDirectory
}
@@ -38,6 +39,10 @@ func (f *servingParkFS) recordNamespaceParent(ctx context.Context, current servi
if err := ctx.Err(); err != nil {
return err
}
+ boot, err := f.currentBoot()
+ if err != nil {
+ return err
+ }
source, expected, err := f.source(servingParkAuthorization{Successor: current, Directory: directory})
if err != nil {
return err
@@ -57,7 +62,7 @@ func (f *servingParkFS) recordNamespaceParent(ctx context.Context, current servi
}
p, err := f.namespaceParent()
if err == nil {
- if p.Parent != expected {
+ if p.Parent != expected || f.checkBoot(p.BootID) != nil {
return hostbox.ErrChanged
}
return nil
@@ -77,12 +82,12 @@ func (f *servingParkFS) recordNamespaceParent(ctx context.Context, current servi
}
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 {
+ 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 || f.checkBoot(n.BootID) != nil {
return hostbox.ErrChanged
}
} else {
var q servingParkQuarantine
- if servingParkDecode(body, &q) != nil || q.validate() != nil || q.Parent != expected {
+ if servingParkDecode(body, &q) != nil || q.validate() != nil || q.Parent != expected || f.checkBoot(q.BootID) != nil {
return hostbox.ErrChanged
}
}
@@ -90,7 +95,7 @@ func (f *servingParkFS) recordNamespaceParent(ctx context.Context, current servi
if err = ctx.Err(); err != nil {
return err
}
- return (&servingParkLedger{state: f.state}).write("namespace-parent.json", servingParkParent{Version: 1, Parent: expected})
+ return (&servingParkLedger{state: f.state}).write("namespace-parent.json", servingParkParent{Version: 1, Parent: expected, BootID: boot})
}
// Called under the namespace lock. Absence of parent evidence never permits
@@ -106,6 +111,9 @@ func (f *servingParkFS) recoverRecordedNamespace(ctx context.Context) error {
if err != nil {
return err
}
+ if err := f.checkBoot(p.BootID); err != nil {
+ return err
+ }
parent, id, err := f.parent()
if err != nil {
return err
@@ -128,7 +136,7 @@ func (f *servingParkFS) recoverRecordedNamespace(ctx context.Context) error {
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 {
+ if servingParkDecode(body, &n) != nil || n.Version != 1 || n.Parent != p.Parent || n.Root.Inode == 0 || n.Root.Device != id.Device || n.Root.Mount != id.Mount || f.checkBoot(n.BootID) != nil {
return hostbox.ErrChanged
}
root, rid, err := f.privateDir(parent, ".serving-parked")
diff --git a/internal/host/claimservingpark_linux.go b/internal/host/claimservingpark_linux.go
index 46a9bd802..366166b5e 100644
--- a/internal/host/claimservingpark_linux.go
+++ b/internal/host/claimservingpark_linux.go
@@ -17,6 +17,7 @@ import (
)
type servingParkNamespace struct {
+ BootID string `json:",omitempty"`
Version int
Parent, Root servingArtifactDirectory
}
@@ -26,6 +27,7 @@ type servingParkFS struct {
state rootstate.Store
owner uint32
mount func(string) (bool, error)
+ boot func() (string, error)
rename func(int, string, int, string, uint) error
sync func(int) error
unlink func(int, string, int) error
@@ -34,7 +36,7 @@ type servingParkFS struct {
// Production construction always requires root and a nosymfollow mount. Tests
// use the private fields to exercise filesystems without mount privileges.
func newServingParkFS(data string, state rootstate.Store) *servingParkFS {
- return &servingParkFS{data: data, state: state, owner: 0, mount: instancefs.MountNoSymfollow, rename: unix.Renameat2, sync: unix.Fsync, unlink: unix.Unlinkat}
+ return &servingParkFS{data: data, state: state, owner: 0, mount: instancefs.MountNoSymfollow, boot: servingParkBootID, rename: unix.Renameat2, sync: unix.Fsync, unlink: unix.Unlinkat}
}
func servingParkDirID(f *os.File) (servingArtifactDirectory, error) {
@@ -118,6 +120,10 @@ func (f *servingParkFS) privateDir(parent *os.File, name string) (*os.File, serv
// completed before a production caller can recover an unknown park root.
func (f *servingParkFS) namespace() (*os.File, servingParkNamespace, error) {
var n servingParkNamespace
+ boot, err := f.currentBoot()
+ if err != nil {
+ return nil, n, err
+ }
parent, id, err := f.parent()
if err != nil {
return nil, n, err
@@ -125,6 +131,13 @@ func (f *servingParkFS) namespace() (*os.File, servingParkNamespace, error) {
defer parent.Close()
b, err := f.state.Read("namespace.json")
if errors.Is(err, os.ErrNotExist) {
+ p, e := f.namespaceParent()
+ if e == nil && (p.Parent != id || f.checkBoot(p.BootID) != nil) {
+ return nil, n, hostbox.ErrChanged
+ }
+ if e != nil && !errors.Is(e, os.ErrNotExist) {
+ return nil, n, e
+ }
if err = f.checkNamespaceQuarantine(n); err != nil {
return nil, n, err
}
@@ -140,7 +153,7 @@ func (f *servingParkFS) namespace() (*os.File, servingParkNamespace, error) {
d.Close()
return nil, n, hostbox.ErrChanged
}
- n = servingParkNamespace{Version: 1, Parent: id, Root: rid}
+ n = servingParkNamespace{Version: 1, Parent: id, Root: rid, BootID: boot}
if e = f.sync(int(parent.Fd())); e == nil {
l := servingParkLedger{state: f.state}
e = l.write("namespace.json", n)
@@ -154,7 +167,7 @@ func (f *servingParkFS) namespace() (*os.File, servingParkNamespace, error) {
if err != nil {
return nil, n, err
}
- if servingParkDecode(b, &n) != nil || n.Version != 1 || n.Parent != id {
+ if servingParkDecode(b, &n) != nil || n.Version != 1 || n.Parent != id || f.checkBoot(n.BootID) != nil {
return nil, n, hostbox.ErrChanged
}
if err = f.checkNamespaceQuarantine(n); err != nil {
diff --git a/internal/host/claimservingpark_rootrunner_linux_test.go b/internal/host/claimservingpark_rootrunner_linux_test.go
index eee0c1a9e..58391eb6a 100644
--- a/internal/host/claimservingpark_rootrunner_linux_test.go
+++ b/internal/host/claimservingpark_rootrunner_linux_test.go
@@ -411,4 +411,68 @@ func TestRootRebootServingParkChangedMountPhase2(t *testing.T) {
t.Fatalf("reboot lost retained %s: %q %v", suffix, got, err)
}
}
+
+ // The kernel need not recycle the number in this run. Normalize only this
+ // fixture's saved mount numbers to the actual new mount, leaving the real
+ // pre-reboot BootID untouched. A mount-number-only guard would now accept it.
+ parent, actual, err := fs.parent()
+ if err != nil {
+ t.Fatal(err)
+ }
+ parent.Close()
+ var namespace servingParkNamespace
+ if servingParkDecode(before, &namespace) != nil || namespace.BootID != saved.Boot {
+ t.Fatal("phase 1 did not persist its actual boot")
+ }
+ if actual.Device != saved.Record.Source.Parent.Device || actual.Inode != saved.Record.Source.Parent.Inode {
+ t.Fatal("reboot fixture changed its underlying parent identity")
+ }
+ namespace.Parent.Mount, namespace.Root.Mount = actual.Mount, actual.Mount
+ if err := (&servingParkLedger{state: state}).write("namespace.json", namespace); err != nil {
+ t.Fatal(err)
+ }
+ r = saved.Record
+ r.Authorization.Directory.Mount = actual.Mount
+ r.Source.Directory.Mount, r.Source.Parent.Mount = actual.Mount, actual.Mount
+ r.Event.Mount, r.Namespace.Mount = actual.Mount, actual.Mount
+ for i := range r.Source.Files {
+ if r.Source.Files[i].Present {
+ r.Source.Files[i].Mount = actual.Mount
+ }
+ }
+ before, err = state.Read("namespace.json")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := fs.CheckParked(&r); !errors.Is(err, hostbox.ErrChanged) {
+ t.Fatal("recycled mount number accepted across actual reboot", err)
+ }
+ writes = 0
+ if err := fs.Delete(&r, func() error { writes++; return nil }); !errors.Is(err, hostbox.ErrChanged) || writes != 0 {
+ t.Fatal("recycled mount number authorized deletion", writes, err)
+ }
+ after, err = state.Read("namespace.json")
+ if err != nil || !bytes.Equal(before, after) {
+ t.Fatal("recycled-number refusal rewrote authority", err)
+ }
+ for suffix, want := range map[string]string{"": "database", "-wal": "WAL", "-shm": "SHM"} {
+ body, err := os.ReadFile(filepath.Join(data, "instances", ".serving-parked", r.Authorization.ID, r.Authorization.Successor.Host+".db"+suffix))
+ if err != nil || string(body) != want {
+ t.Fatal("recycled-number refusal changed parked data", suffix, err)
+ }
+ }
+ // Read-only control: with only the fixture boot changed, every physical
+ // identity must pass. Otherwise the previous refusals could have another
+ // cause and would not witness the boot boundary. Never delete this control.
+ namespace.BootID = strings.TrimSpace(string(boot))
+ if err := (&servingParkLedger{state: state}).write("namespace.json", namespace); err != nil {
+ t.Fatal(err)
+ }
+ controlErr := fs.CheckParked(&r)
+ if err := state.Write("namespace.json", before); err != nil {
+ t.Fatal(err)
+ }
+ if controlErr != nil {
+ t.Fatal("numeric-reuse control has another identity mismatch", controlErr)
+ }
}
diff --git a/internal/host/claimservingquarantine_linux.go b/internal/host/claimservingquarantine_linux.go
index d2a29dfa0..2b26a53e4 100644
--- a/internal/host/claimservingquarantine_linux.go
+++ b/internal/host/claimservingquarantine_linux.go
@@ -18,6 +18,7 @@ import (
// Retain one obstruction and, if creation loses its identity write, one
// unrecorded replacement. Further replacements require investigation.
type servingParkQuarantine struct {
+ BootID string `json:",omitempty"`
Version int
ID string
Parent, Obstruction servingArtifactDirectory
@@ -42,7 +43,7 @@ func (q servingParkQuarantine) validate() error {
if err != nil || len(id) != 16 || hex.EncodeToString(id) != q.ID || q.Version != 1 || q.Parent.Inode == 0 || q.Parent.Device == 0 || q.Parent.Mount == 0 || q.Obstruction.Inode == 0 || q.Obstruction.Device != q.Parent.Device || q.Obstruction.Mount != q.Parent.Mount {
return hostbox.ErrChanged
}
- if q.Previous != nil && (q.Previous.Version != 1 || q.Previous.Parent != q.Parent || q.Previous.Root.Inode == 0 || q.Previous.Root.Device != q.Parent.Device || q.Previous.Root.Mount != q.Parent.Mount || q.Previous.Root == q.Obstruction) {
+ if q.Previous != nil && (q.Previous.Version != 1 || q.Previous.BootID != q.BootID || q.Previous.Parent != q.Parent || q.Previous.Root.Inode == 0 || q.Previous.Root.Device != q.Parent.Device || q.Previous.Root.Mount != q.Parent.Mount || q.Previous.Root == q.Obstruction) {
return hostbox.ErrChanged
}
if q.Fresh != (servingArtifactDirectory{}) && (!q.Moved || q.Fresh.Inode == 0 || q.Fresh.Device != q.Parent.Device || q.Fresh.Mount != q.Parent.Mount || q.Fresh == q.Obstruction || q.Previous != nil && q.Fresh == q.Previous.Root) {
@@ -72,7 +73,7 @@ func (f *servingParkFS) checkNamespaceQuarantine(n servingParkNamespace) error {
return err
}
var q servingParkQuarantine
- if servingParkDecode(body, &q) != nil || q.validate() != nil || !q.Complete || n.Parent != q.Parent || n.Root != q.Fresh {
+ if servingParkDecode(body, &q) != nil || q.validate() != nil || !q.Complete || n.Parent != q.Parent || n.Root != q.Fresh || n.BootID != q.BootID || f.checkBoot(q.BootID) != nil {
return hostbox.ErrChanged
}
return nil
@@ -89,6 +90,10 @@ func (f *servingParkFS) recoverNamespace(ctx context.Context, expected servingAr
if err := ctx.Err(); err != nil {
return q, err
}
+ boot, err := f.currentBoot()
+ if err != nil {
+ return q, err
+ }
parent, pid, err := f.parent()
if err != nil {
return q, err
@@ -102,6 +107,13 @@ func (f *servingParkFS) recoverNamespace(ctx context.Context, expected servingAr
return q, hostbox.ErrChanged
}
ledger := servingParkLedger{state: f.state}
+ anchor, err := f.namespaceParent()
+ if err == nil && (anchor.Parent != expected || f.checkBoot(anchor.BootID) != nil) {
+ return q, hostbox.ErrChanged
+ }
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
+ return q, err
+ }
save := func() error {
if err := ctx.Err(); err != nil {
return err
@@ -113,15 +125,15 @@ func (f *servingParkFS) recoverNamespace(ctx context.Context, expected servingAr
}
body, err := f.state.Read("namespace-quarantine.json")
if err == nil {
- if servingParkDecode(body, &q) != nil || q.validate() != nil || q.Parent != pid {
+ if servingParkDecode(body, &q) != nil || q.validate() != nil || q.Parent != pid || f.checkBoot(q.BootID) != nil {
return q, hostbox.ErrChanged
}
} else if errors.Is(err, os.ErrNotExist) {
- q.Version, q.Parent = 1, pid
+ q.Version, q.Parent, q.BootID = 1, pid, boot
body, err = f.state.Read("namespace.json")
if err == nil {
var prior servingParkNamespace
- if servingParkDecode(body, &prior) != nil || prior.Version != 1 || prior.Parent != pid {
+ if servingParkDecode(body, &prior) != nil || prior.Version != 1 || prior.Parent != pid || f.checkBoot(prior.BootID) != nil {
return q, hostbox.ErrChanged
}
q.Previous = &prior
@@ -157,7 +169,7 @@ func (f *servingParkFS) recoverNamespace(ctx context.Context, expected servingAr
return q, hostbox.ErrChanged
}
old := q.Previous != nil && reflect.DeepEqual(current, *q.Previous)
- fresh := q.Fresh.Inode != 0 && current == (servingParkNamespace{Version: 1, Parent: q.Parent, Root: q.Fresh})
+ fresh := q.Fresh.Inode != 0 && current == (servingParkNamespace{Version: 1, Parent: q.Parent, Root: q.Fresh, BootID: q.BootID})
if !old && !fresh || q.Complete && !fresh {
return q, hostbox.ErrChanged
}
@@ -218,7 +230,7 @@ func (f *servingParkFS) recoverNamespace(ctx context.Context, expected servingAr
if err = f.sync(int(parent.Fd())); err != nil {
return q, err
}
- if err = ledger.write("namespace.json", servingParkNamespace{Version: 1, Parent: q.Parent, Root: q.Fresh}); err != nil {
+ if err = ledger.write("namespace.json", servingParkNamespace{Version: 1, Parent: q.Parent, Root: q.Fresh, BootID: q.BootID}); err != nil {
return q, err
}
q.Complete = true
diff --git a/internal/host/claimservingquarantine_linux_test.go b/internal/host/claimservingquarantine_linux_test.go
index 7514e9472..7cab7c29d 100644
--- a/internal/host/claimservingquarantine_linux_test.go
+++ b/internal/host/claimservingquarantine_linux_test.go
@@ -392,7 +392,7 @@ func TestServingParkQuarantineValidatesUnrecordedPhases(t *testing.T) {
case "fresh":
broken.Unrecorded = q.Fresh
case "previous":
- broken.Previous = &servingParkNamespace{Version: 1, Parent: parent, Root: q.Unrecorded}
+ broken.Previous = &servingParkNamespace{Version: 1, Parent: parent, Root: q.Unrecorded, BootID: q.BootID}
}
if !errors.Is(broken.validate(), hostbox.ErrChanged) {
t.Fatal("accepted invalid retained replacement", fault)
@@ -520,7 +520,11 @@ func TestServingParkQuarantineRefusesUnsafeAuthority(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- if err := f.ledger.write("namespace.json", servingParkNamespace{Version: 1, Parent: parent, Root: id}); err != nil {
+ boot, err := f.fs.currentBoot()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := f.ledger.write("namespace.json", servingParkNamespace{Version: 1, Parent: parent, Root: id, BootID: boot}); err != nil {
t.Fatal(err)
}
}
diff --git a/internal/provision/testdata/plan14-root-reads-seed.tsv b/internal/provision/testdata/plan14-root-reads-seed.tsv
index 1b990d8e8..3abc876eb 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 18 overlay; pending Paul’s census ruling.
+# Files seed checkpoint 19 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.
@@ -31,7 +31,7 @@ internal/host/claimservingretire.go servingPredecessor authority C,J4 rev3=PROPO
internal/host/claimservingretire.go servingRetirementID authority C,J4 rev3=PROPOSED-seed; immutable predecessor and original successor tuples; deterministic journal identity independent of claim operation IDs c984974d17e621d7fe9c276a03ae789b86789abbccbb82a09d06f55c3bf13819
internal/host/claimservingretire.go servingWriterIdentity authority C,J4 rev3=PROPOSED-seed; authoritative serving holder and unique cold admission; exact local executor, stamp, session, generation, incarnation, location and region file:Exec@403 f7d8c868ce9856eab448aa72bdf20ee02cb70179ce0f435d45888f72e0c3b7bf
internal/hostbox/servingpark.go LaunchJournal.RootSessionLaunches authority C,J4 rev3=PROPOSED-seed; root-local persisted launch records for exact host/incarnation/stamp/session/box/enrollment; refuses agent journal transport cbbb9378bc53ac499972bed5fb654e05856a9afd7256d55323a717140ffcba18
-internal/host/claimservingpark_linux.go newServingParkFS authority C,J4 rev3=PROPOSED-seed; root-only filesystem dependencies, fixed nosymfollow validation and private state store; bound through private root service construction 9a72f9bb7c62e85199e303411b42e01b0b016a2a5cf2d9c84e2588b5f36b52e2
+internal/host/claimservingpark_linux.go newServingParkFS authority C,J4 rev3=PROPOSED-seed; root-only filesystem dependencies, fixed nosymfollow validation and private state store; bound through private root service construction; production boot identity reader is fixed at construction 333b3529ffbe801730ec31a62ff96d9b5c6ba931f00c79a34f00f46de2c1fccc
internal/host/claimservingpark_linux.go sameServingParkFile authority C,J4 rev3=PROPOSED-seed; recorded basename, presence, mount, device, inode, regular type, single link and allocated-byte identity d5d684356e793dcfd55fe1ec9c609f2320e0155e052caca42475348075e8fd68
internal/host/claimservingpark_linux.go servingParkDirID authority C,J4 rev3=PROPOSED-seed; descriptor statx directory type, device, inode and mount identity file:Statx@102 3480dad6d65fedcc17e678df38cf13352ad3c4a8a9d0a09a395973d92b98094e
internal/host/claimservingpark_linux.go servingParkFS.Capture authority C,J4 rev3=PROPOSED-seed; canonical nested source directory, four fixed basenames, source mount and bounded generation-marker bytes through the captured descriptor file:Fstat@920,file:Openat@694,file:ReadAll@1138 c3a9348ddd9548f3fcfdcd45abc42b6a758a6399ed34e92481785e26c110763d
@@ -40,13 +40,13 @@ internal/host/claimservingpark_linux.go servingParkFS.Delete authority C,J4 rev3
internal/host/claimservingpark_linux.go servingParkFS.Move authority C,J4 rev3=PROPOSED-seed; recorded source/destination inodes and explicit absences; no-replace same-mount rename, post-rename identity and both parent fsyncs before progress 6e43ae58b6a02d167135fabdde7ade06502511c5237b313e6ca6c2a8e9a2c8d0
internal/host/claimservingpark_linux.go servingParkFS.Prepare authority C,J4 rev3=PROPOSED-seed; recorded private namespace and empty event; persist namespace and event identities together; old prepared events gain a namespace only after exact identity proof file:Prepare@24,file:Readdirnames@967 1fddbfb85e6381113db4807f41890dac36a104997094a139408eca75b4572eab
internal/host/claimservingpark_linux.go servingParkFS.event authority C,J4 rev3=PROPOSED-seed; current or proven displaced namespace and exact private event mount/device/inode; old records may fall back only to the previous root recorded before quarantine 625ed442442a9b07c57392f72dea64e9e91eba65d2e2ef0e9899275756336788
-internal/host/claimservingpark_linux.go servingParkFS.namespace authority C,J4 rev3=PROPOSED-seed; private rootstate namespace and quarantine completion records, effective parent mount and root-owned park directory; pending quarantine blocks namespace creation and use; unknown roots refused without adoption file:Read@211 ac3dafd74eeab38012de643179c320d63a023d100a4617321a9e017c1249c9af
+internal/host/claimservingpark_linux.go servingParkFS.namespace authority C,J4 rev3=PROPOSED-seed; private rootstate namespace and quarantine completion records, effective parent mount and root-owned park directory; pending quarantine blocks namespace creation and use; unknown roots refused without adoption; the saved namespace boot must equal the current kernel boot before opening or changing park contents file:Read@282 0fc681872fb332c569c2164a6089ab590d4274bdb021800b97b0fdeed86ad6f6
internal/host/claimservingpark_linux.go servingParkFS.parent authority C,J4 rev3=PROPOSED-seed; deployment-controlled instances path, descriptor mount identity and effective nosymfollow topology; path replacement refuses 1d241a7d446bbbade6e460efd911863f8a5246b8ac1d47b2548034e3feb2f216
internal/host/claimservingpark_linux.go servingParkFS.privateDir authority C,J4 rev3=PROPOSED-seed; no-follow directory descriptor, exact private owner and mode plus device/inode/mount identity file:Fstat@261 8a4a33c6871fa88cd53f9922f99e173102966827bfea3998b2b3bd749a2ad11b
internal/host/claimservingpark_linux.go servingParkFS.source authority C,J4 rev3=PROPOSED-seed; canonical host-derived nested state directory checked against independently supplied root directory identity; no registry path b7a30539c735e811c366ebb7c853a962500e82305ba1caa64f73402ae6edbce5
internal/host/claimservingpark_linux.go servingParkOpenDir authority C,J4 rev3=PROPOSED-seed; single basename opened relative to a pinned parent with no-follow directory semantics file:Openat@210 665ff3adc3e1cd1c6c4b92439160a0ef8013ea8e35375bcdfa90747888751ff9
internal/host/claimservingpark_linux.go servingParkStat authority C,J4 rev3=PROPOSED-seed; no-follow artifact statx identity, single links, explicit absence and checked allocated-byte count file:Statx@143 2c5d25be8581568148d13e3cad6f4b5f861fec8bff2545502fbdea0d5d949bb6
-internal/host/claimservingpark_other.go newServingParkFS authority C,J4 rev3=PROPOSED-seed; non-Linux refusal or construction of refusal-only filesystem; no filesystem authority 69baa4c747e687578e73b8369f2df9987fe6fb5836b78796cc41c776ba12c802
+internal/host/claimservingpark_other.go newServingParkFS authority C,J4 rev3=PROPOSED-seed; non-Linux refusal or construction of refusal-only filesystem; no filesystem authority; production boot identity reader is fixed at construction 69baa4c747e687578e73b8369f2df9987fe6fb5836b78796cc41c776ba12c802
internal/host/claimservingpark_other.go servingParkFS.Capture authority C,J4 rev3=PROPOSED-seed; non-Linux refusal or construction of refusal-only filesystem; no filesystem authority a5560b338356792386a5a1b2e27cb08ca0d5aa024cd03b567a78dfd986ce7871
internal/host/claimservingpark_other.go servingParkFS.CheckParked authority C,J4 rev3=PROPOSED-seed; non-Linux refusal or construction of refusal-only filesystem; no filesystem authority 3a8cb4d8052bbdea8cf7a300944de72c68012e6efa252c293fc1dc06a3a2150e
internal/host/claimservingpark_other.go servingParkFS.Delete authority C,J4 rev3=PROPOSED-seed; recorded event and artifact identities; validates complete child set before journaled unlink, then removes only the recorded empty event with durable intent and progress 8c169e8428b913d2f05b502ce971be49d5febaad95d8ae811aae21c81f3434ce
@@ -75,7 +75,7 @@ internal/host/claimservingparkroot_linux.go validateServingRootHistory authority
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 ea7df99b46e476a0a7261f0a8311827f92ce366cb1066342c6afbf32775128d2
+@coverage internal/host 5f5aa5f26594bf4db662e0da8b386341c966101242b6bdcd2af7fe1d936b4134
@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
@@ -156,14 +156,14 @@ internal/hostguard/coldhandover.go Guard.beginColdHandover authority C,J4 rev3=P
internal/host/claimservinghandover.go servingPriorRevision authority C,J4 rev3=PROPOSED-seed; canonical recorded directory revision bounded by the current authenticated enrollment revision; never rewrites retained stop evidence 8007e188e2ad600cf36d8eed0fb5adb591f08f52be52285c85db348e0780cb84
internal/host/claimservingquarantine_linux.go servingParkFS.checkQuarantine authority C,J4 rev3=PROPOSED-seed; no-follow descriptor identity of the root-chosen quarantine basename; does not inspect children or authorize deletion bbf7d5600fc6fcf0c60d2b7c9e83d8ac57f9edaca3367c9c6e3fadffab303d8d
internal/host/claimservingquarantine_linux.go servingParkFS.moveQuarantine authority C,J4 rev3=PROPOSED-seed; recorded obstruction device, inode and mount before and after same-mount NOREPLACE rename; lost-reply replay requires exact destination identity 12801fc3c906d28c41919fed8eb425dd01a480c42c7cd51653f686dc1f76a794
-internal/host/claimservingquarantine_linux.go servingParkFS.recoverNamespace authority C,J4 rev3=PROPOSED-seed; independently recorded parent, effective mount and strict private quarantine records; retains one obstruction plus one unrecorded replacement; completed replay is read-only; no content deletion file:Fstat@388,file:Read@1051,file:Read@1610,file:Read@1951,file:Read@783,file:Readdirnames@3579 b157e0e79b663f5af04a01b3b8a5e3ab807d1fff78cb3f6ec4028f17f976ce5c
+internal/host/claimservingquarantine_linux.go servingParkFS.recoverNamespace authority C,J4 rev3=PROPOSED-seed; independently recorded parent, effective mount and strict private quarantine records; retains one obstruction plus one unrecorded replacement; completed replay is read-only; no content deletion; parent, prior namespace and quarantine boot identities must match the current boot; no mount-number refresh file:Fstat@454,file:Read@1075,file:Read@1391,file:Read@1986,file:Read@2327,file:Readdirnames@3973 9624383d3a20c040fa9dceacf13b04c2f5f3cfe7d19528f0318c02065c37ce95
internal/host/claimservingquarantine_linux.go servingParkQuarantine.name authority C,J4 rev3=PROPOSED-seed; root-generated 128-bit quarantine identity converted to a fixed basename; no caller pathname 08554ada2338c42eb0c53d7b39709583f6af12e51f5a005f42dc3bd44906b4ee
-internal/host/claimservingquarantine_linux.go servingParkQuarantine.validate authority C,J4 rev3=PROPOSED-seed; private quarantine shape and phase checks; original, prior, unrecorded and fresh directories must have distinct same-mount identities; retention only 86901fcae59cac1b613b2953723fb2fe79c6f38f88903a28d166ef4e0936200f
-internal/host/claimservingquarantine_linux.go type servingParkQuarantine authority C,J4 rev3=PROPOSED-seed; private versioned quarantine intent; additive unrecorded-replacement identity and rename progress bounded to one spare; no artifact deletion authority cc8ec82f24c038110d21a141003ba0093b57921fb3f92d426ad8c2cab2109687
-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/claimservingquarantine_linux.go servingParkQuarantine.validate authority C,J4 rev3=PROPOSED-seed; private quarantine shape and phase checks; original, prior, unrecorded and fresh directories must have distinct same-mount identities; retention only; prior namespace and quarantine must carry the same boot scope 285f4c277e15af5001ff7d69723d1ff84a6137c8d08bedf5a7c8580d2e246398
+internal/host/claimservingquarantine_linux.go type servingParkQuarantine authority C,J4 rev3=PROPOSED-seed; private versioned quarantine intent; additive unrecorded-replacement identity and rename progress bounded to one spare; no artifact deletion authority; additive BootID scopes parent, obstruction, prior root and replacement identities 602321aa523aa4d4b9bade287b70abf2c86f4daeb6bb245bc7f5412b688a1680
+internal/host/claimservingquarantine_linux.go servingParkFS.checkNamespaceQuarantine authority C,J4 rev3=PROPOSED-seed; strict private quarantine completion record and exact published namespace identity; ordinary parking cannot use a replacement before durable quarantine completion; completed quarantine and namespace must share the current kernel boot file:Read@95 236143830da52e9b0d8778691d2cd1ae0e55c970fe6ed1ccc4750dbc8c0e3937
internal/host/claimservingnamespace_linux.go servingParkFS.namespaceParent authority A,C,J4 rev3=PROPOSED-seed; strict private parent record with nonzero device, inode and mount; missing authority differs from corrupt state file:Read@107 0b2be4cfd1acf24c2e031cbd6d7bb013832db708031a4b107987b9c652269ebe
-internal/host/claimservingnamespace_linux.go servingParkFS.recordNamespaceParent authority A,C,J4 rev3=PROPOSED-seed; root-validated source directory under namespace and host locks; current nosymfollow parent and safe permissions; existing parent, namespace and quarantine evidence constrain first capture and replay 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 servingParkFS.recordNamespaceParent authority A,C,J4 rev3=PROPOSED-seed; root-validated source directory under namespace and host locks; current nosymfollow parent and safe permissions; existing parent, namespace and quarantine evidence constrain first capture and replay; capture current kernel boot with the authenticated parent; never infer missing historical boot evidence file:Fstat@536,file:Read@1132 a8cbc51a05a00ca6cfba4bc920673334ab21c651edf56f30e46066a3df585c7b
+internal/host/claimservingnamespace_linux.go servingParkFS.recoverRecordedNamespace authority A,C,J4 rev3=PROPOSED-seed; separate private parent identity, current mount and permissions, namespace and quarantine records; no parent capture at startup; bounded quarantine retention without artifact deletion; startup refuses missing or changed parent/namespace boot identity before quarantine work file:Fstat@425,file:Read@651,file:Read@840 08cb520599e196fbd90610b315cdc55e853ad5fe9f6cb187c2bc206517579090
internal/host/claimservingnamespace_linux.go servingParkRoot.prepareServingNamespace authority C,J4 rev3=PROPOSED-seed; root-authorized retirement identity and existing record; full source/disposition validation precedes first parent write and bounded quarantine recovery; final ledger capture revalidates source 6411305a91f27caf14805823eb57d36b0549beef39541324e1f814c922ec5cfe
internal/host/claimservingpark_records.go servingParkLedger.capture authority C,J4 rev3=PROPOSED-seed; no-follow source snapshot, root launch/stop disposition, legacy marker and empty-bootstrap refusal, and complete retirement-record validation; read-only prerequisite before parent or retirement writes e3f3c2c919d96ae0a02fe0ac1a4e8694851dce7b73b3a9968f93f4445f3f35b3
internal/host/claimservingquarantine_linux.go servingParkQuarantine.unrecordedName authority C,J4 rev3=PROPOSED-seed; fixed retention basename from the existing private quarantine nonce; no caller path or new nonce on retry e037771d8e939a426c26104c10b92a2b205376d14574a92015f6f5a4bff90375
@@ -176,3 +176,9 @@ internal/host/claimservingdisplaced_linux.go servingParkFS.displacedRoot authori
internal/host/claimservingdisplaced_linux.go servingParkFS.recordEventRoot authority C,J4 rev3=PROPOSED-seed; descriptor identity of an already verified event root; persist old-record namespace before first deletion; existing namespace must match exactly b4febb444cd09cd5ed45fc616d172966038d71026024c89dfce9e8c530435756
internal/host/claimservingcollection.go servingParkLedger.collectClosedLocked authority C,J4 rev3=PROPOSED-seed; private completed retirement, durable replay deadline, slot references and legacy-consumption evidence; preserve referenced records, mark permanent legacy consumption, then remove only obsolete private record and lock after 24 hours; no data deletion authority 97201d6a0b793715b5e8c0c0abb36d1c303e3b38f3d0e1a97aadd49063735984
internal/host/claimservingpark_records.go type servingLegacyConsumption authority C,J4 rev3=PROPOSED-seed; permanent legacy source and authorization evidence; additive Collected flag distinguishes garbage collection from interrupted first-intent creation 1f7d2cd2baaad00efda56e7c5637e8b386d80ad7e5f2b311487ebcf109c4861b
+internal/host/claimservingboot_linux.go servingParkBootID authority C,J4 rev3=PROPOSED-seed; bounded kernel boot-ID read from /proc; no caller-supplied path or identity file:Open@54,file:ReadAll@162 af8e5641ef60fcd307dec013df6eba3e50a29d2cb811c5b2cddf10c5c9404e30
+internal/host/claimservingboot_linux.go servingParkFS.currentBoot authority C,J4 rev3=PROPOSED-seed; production boot reader and canonical nonzero UUID validation; missing or unreadable boot evidence refuses fbc9dde6b4b8c3a7135f145e7544639be65779cb0253f4d47206522076a2f2e5
+internal/host/claimservingboot_linux.go servingParkFS.checkBoot authority C,J4 rev3=PROPOSED-seed; private namespace boot identity compared with the current kernel boot; missing old identity or a reboot refuses without refreshing mount numbers 3d380a2fa685f3be883f9c595a01ee437d51597a1b05ab09fb9c50dfead2cac1
+internal/host/claimservingpark_linux.go type servingParkNamespace authority C,J4 rev3=PROPOSED-seed; durable parent and private-root identities with additive kernel boot scope; an old record is decodable but lacks current mutation authority 9c4690a2490610e64ceb7a472f213e8abd9457e5c27ecca3916484d60268d43e
+internal/host/claimservingpark_linux.go type servingParkFS authority C,J4 rev3=PROPOSED-seed; private filesystem dependencies including production kernel boot reader; requests cannot supply boot identity 44839ce2b07d361ab9d39f6063be879026f184071832a0848191397d76705320
+internal/host/claimservingnamespace_linux.go type servingParkParent authority C,J4 rev3=PROPOSED-seed; root-installation parent identity and additive boot scope; startup never infers an earlier boot from a matching mount number c58afd9dda038b4adb9ebf67fe0a42f1f3dd48f21d11b8f5660bad278e4dfb87