carlos / platform Public

WIP claim Plan 14 track E checkpoint 44 (not for merge)

Protected timers: intention before materialization, exact retirement ownership; descriptor-transfer containment counterexample; item 4a sized.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev c295f30728710c30e4282128ace3a689432b4271 parent 46a13cb
12 files changed, +434 −20
  • internal/host/claimretire_transport.go +1 −0
  • internal/host/plan14_generation_timer_retirement_test.go +8 −8
  • internal/host/plan14_generation_transfer_root_linux_test.go +164 −0
  • internal/host/plan14_timer_intention_root_linux_test.go +25 −0
  • internal/host/plan14_timer_intention_test.go +185 −0
  • internal/host/rootinventory.go +3 −0
  • internal/host/rootinventory_timer.go +30 −1
  • internal/host/rootinventory_timer_test.go +2 −2
  • internal/hostbox/reconcile_adapter.go +3 −2
  • internal/hostbox/retirement_timers.go +4 −1
  • internal/provision/plan14_e_write_census_test.go +1 −1
  • internal/provision/testdata/plan14-root-reads-e.tsv +8 −5

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	306.854s
?   	github.com/carlosframework/platform/cmd/changelog	[no test files]
ok  	github.com/carlosframework/platform/cmd/console	2.983s
ok  	github.com/carlosframework/platform/cmd/moveprobe	93.264s
ok  	github.com/carlosframework/platform/cmd/zddprobe	0.707s
ok  	github.com/carlosframework/platform/internal/activator	249.174s
ok  	github.com/carlosframework/platform/internal/agentsock	0.015s
ok  	github.com/carlosframework/platform/internal/alerting	0.003s
ok  	github.com/carlosframework/platform/internal/analytics	0.020s
ok  	github.com/carlosframework/platform/internal/appconfig	2.504s
ok  	github.com/carlosframework/platform/internal/apperr	0.074s
ok  	github.com/carlosframework/platform/internal/applog	0.124s
ok  	github.com/carlosframework/platform/internal/awsinv	0.004s
ok  	github.com/carlosframework/platform/internal/boxchoice	0.004s
ok  	github.com/carlosframework/platform/internal/boxid	0.667s
?   	github.com/carlosframework/platform/internal/boxid/boxidtest	[no test files]
?   	github.com/carlosframework/platform/internal/boxid/internal/signtest	[no test files]
ok  	github.com/carlosframework/platform/internal/boxvolumes	0.008s
ok  	github.com/carlosframework/platform/internal/caps	0.012s
ok  	github.com/carlosframework/platform/internal/certwarm	0.004s
ok  	github.com/carlosframework/platform/internal/cgroup	0.700s
ok  	github.com/carlosframework/platform/internal/changelog	0.306s
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	391.846s
?   	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.902s
ok  	github.com/carlosframework/platform/internal/dbdoor/dbdoortest	0.009s
ok  	github.com/carlosframework/platform/internal/dbverify	0.004s
ok  	github.com/carlosframework/platform/internal/dnsrec	0.003s
ok  	github.com/carlosframework/platform/internal/docsite	0.005s
ok  	github.com/carlosframework/platform/internal/domains	2.368s
ok  	github.com/carlosframework/platform/internal/doorkey	0.006s
ok  	github.com/carlosframework/platform/internal/economics	0.004s
ok  	github.com/carlosframework/platform/internal/edge	26.982s
?   	github.com/carlosframework/platform/internal/edgeproof	[no test files]
ok  	github.com/carlosframework/platform/internal/edgesfile	0.008s
ok  	github.com/carlosframework/platform/internal/fleetapi	0.135s
ok  	github.com/carlosframework/platform/internal/gate	1.655s
ok  	github.com/carlosframework/platform/internal/genselect	0.015s
ok  	github.com/carlosframework/platform/internal/geo	0.015s
ok  	github.com/carlosframework/platform/internal/geoip	0.030s
ok  	github.com/carlosframework/platform/internal/historyreadback	0.157s
ok  	github.com/carlosframework/platform/internal/host	145.655s
ok  	github.com/carlosframework/platform/internal/hostactors	0.344s
ok  	github.com/carlosframework/platform/internal/hostbox	2.347s
ok  	github.com/carlosframework/platform/internal/hostclaim	0.542s
ok  	github.com/carlosframework/platform/internal/hostevidence	0.013s
ok  	github.com/carlosframework/platform/internal/hostexec	4.977s
ok  	github.com/carlosframework/platform/internal/hostfence	0.036s
ok  	github.com/carlosframework/platform/internal/hostfiles	10.556s
ok  	github.com/carlosframework/platform/internal/hostfloor	0.013s
ok  	github.com/carlosframework/platform/internal/hostguard	1.044s
ok  	github.com/carlosframework/platform/internal/hostops	1.627s
?   	github.com/carlosframework/platform/internal/hostops/fencerelease	[no test files]
ok  	github.com/carlosframework/platform/internal/hostops/internal/fencerelease	0.012s
ok  	github.com/carlosframework/platform/internal/hostquarantine	0.013s
ok  	github.com/carlosframework/platform/internal/hostrelocate	0.490s
ok  	github.com/carlosframework/platform/internal/hostresolve	0.167s
ok  	github.com/carlosframework/platform/internal/hostwrite	0.012s
ok  	github.com/carlosframework/platform/internal/infraview	0.012s
ok  	github.com/carlosframework/platform/internal/instancectl	4.177s
ok  	github.com/carlosframework/platform/internal/instanceexec	2.773s
ok  	github.com/carlosframework/platform/internal/instancefs	0.017s
ok  	github.com/carlosframework/platform/internal/instancekey	0.014s
ok  	github.com/carlosframework/platform/internal/instanceupdates	0.018s
ok  	github.com/carlosframework/platform/internal/layout	1.542s
ok  	github.com/carlosframework/platform/internal/lease	9.821s
ok  	github.com/carlosframework/platform/internal/lease/leasetest	0.017s
ok  	github.com/carlosframework/platform/internal/ledger	0.033s
ok  	github.com/carlosframework/platform/internal/mail	0.228s
ok  	github.com/carlosframework/platform/internal/maintenance	0.085s
ok  	github.com/carlosframework/platform/internal/maintenanceexec	5.596s
ok  	github.com/carlosframework/platform/internal/maintenanceguard	0.101s
ok  	github.com/carlosframework/platform/internal/metrics	0.005s
ok  	github.com/carlosframework/platform/internal/moves	21.140s
ok  	github.com/carlosframework/platform/internal/moves/archive	0.812s
ok  	github.com/carlosframework/platform/internal/objstore	0.204s
ok  	github.com/carlosframework/platform/internal/ops	0.204s
ok  	github.com/carlosframework/platform/internal/park	0.245s
ok  	github.com/carlosframework/platform/internal/privatefile	0.006s
ok  	github.com/carlosframework/platform/internal/provision	16.463s
ok  	github.com/carlosframework/platform/internal/publicrelease	0.025s
?   	github.com/carlosframework/platform/internal/regionname	[no test files]
ok  	github.com/carlosframework/platform/internal/registry	5.393s
ok  	github.com/carlosframework/platform/internal/relay	7.244s
ok  	github.com/carlosframework/platform/internal/release	22.448s
ok  	github.com/carlosframework/platform/internal/rollout	3.793s
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.010s
ok  	github.com/carlosframework/platform/internal/rootstate	0.029s
ok  	github.com/carlosframework/platform/internal/runs	0.576s
ok  	github.com/carlosframework/platform/internal/s3compat	0.005s
ok  	github.com/carlosframework/platform/internal/safefile	0.003s
ok  	github.com/carlosframework/platform/internal/schedule	2.495s
ok  	github.com/carlosframework/platform/internal/seeds	0.013s
ok  	github.com/carlosframework/platform/internal/sim/hostlife	9.966s
ok  	github.com/carlosframework/platform/internal/sim/litestreamfacts	3.510s
ok  	github.com/carlosframework/platform/internal/status	0.047s
ok  	github.com/carlosframework/platform/internal/steering	0.008s
ok  	github.com/carlosframework/platform/internal/stopevidence	0.293s
ok  	github.com/carlosframework/platform/internal/svcrole	0.002s
ok  	github.com/carlosframework/platform/internal/telemetry	0.004s
ok  	github.com/carlosframework/platform/internal/termfence	0.004s
ok  	github.com/carlosframework/platform/internal/tunnelclient	0.004s
ok  	github.com/carlosframework/platform/internal/unixdial	0.002s
ok  	github.com/carlosframework/platform/internal/updatewords	0.004s
ok  	github.com/carlosframework/platform/internal/usage	0.004s
ok  	github.com/carlosframework/platform/internal/version	0.001s
ok  	github.com/carlosframework/platform/internal/vet	1.552s
=== cross ===
--- shard 0 of 5 ---
cross-compiling darwin/arm64
--- shard 1 of 5 ---
cross-compiling darwin/amd64
--- shard 2 of 5 ---
cross-compiling linux/arm64
linux/arm64 carlos binary: 47055010 bytes (ceiling 52000000)
--- shard 3 of 5 ---
cross-compiling windows/amd64
--- shard 4 of 5 ---
cross-compiling windows/arm64
=== minio-contract ===
minio contract: arm64, minio RELEASE.2025-09-07T16-13-09Z, mc RELEASE.2025-08-13T08-35-41Z
  minio: cached, digest verified
  mc: cached, digest verified
  minio healthy on 127.0.0.1:20999
Added `local` successfully.
Bucket created successfully `local/contract-bucket`.
Bucket created successfully `local/denied-bucket`.
Created policy `contract-only` successfully.
Added user `scopeduser` successfully.
Attached Policies: [contract-only]
To User: scopeduser
  buckets and scoped user ready
=== RUN   TestDeliveredContractMinio
=== RUN   TestDeliveredContractMinio/put
=== RUN   TestDeliveredContractMinio/get_round-trips_the_body
=== RUN   TestDeliveredContractMinio/list_shows_the_key
=== RUN   TestDeliveredContractMinio/head_and_list_agree_on_the_version
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_its_own_object
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed
=== RUN   TestDeliveredContractMinio/delete_removes_it
=== RUN   TestDeliveredContractMinio/cross-bucket_denial
--- PASS: TestDeliveredContractMinio (0.02s)
    --- PASS: TestDeliveredContractMinio/put (0.00s)
    --- PASS: TestDeliveredContractMinio/get_round-trips_the_body (0.00s)
    --- PASS: TestDeliveredContractMinio/list_shows_the_key (0.00s)
    --- PASS: TestDeliveredContractMinio/head_and_list_agree_on_the_version (0.00s)
    --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload (0.00s)
    --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route (0.01s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed (0.00s)
    --- PASS: TestDeliveredContractMinio/delete_removes_it (0.00s)
    --- PASS: TestDeliveredContractMinio/cross-bucket_denial (0.00s)
PASS
ok  	github.com/carlosframework/platform/internal/objstore	0.024s
=== RUN   TestT4StorageProbeMinio
2026/10/15 14:03:31 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18DEB8E8B3001658, 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.022s
minio contract: PASS
diff --git a/internal/host/claimretire_transport.go b/internal/host/claimretire_transport.go
index 0061339f2..d53fad314 100644
--- a/internal/host/claimretire_transport.go
+++ b/internal/host/claimretire_transport.go
@@ -159,6 +159,7 @@ func (h ClaimRootHTTP) retire(ctx context.Context, target hostguard.FenceTarget)
a.DataRoot = h.Roots["data"]
a.Box = target.Box
a.Reconciler.Guard = h.Stop.Coordinator.Guard
+ a.RequireTimerInventory = a.RequireTimerInventory || a.Reconciler.Guard.Root != nil
a.Stop = func(context.Context, registry.Route) (hostguard.FenceTarget, error) { return target, nil }
a.Retirement.Filesystem = hostbox.TypedParker{Roots: h.Roots, ParkedRoots: h.Roots, Incarnation: target.Incarnation, Host: target.Host, Retirement: target.FenceID}
a.Retirement.Authority = hostbox.InstallationCleanup{Guard: a.Reconciler.Guard, Verify: verifyRetirementReceipt}
diff --git a/internal/host/plan14_generation_timer_retirement_test.go b/internal/host/plan14_generation_timer_retirement_test.go
index c41e4da8c..8233873aa 100644
--- a/internal/host/plan14_generation_timer_retirement_test.go
+++ b/internal/host/plan14_generation_timer_retirement_test.go
@@ -11,11 +11,8 @@ import (
func TestPlan14GenerationTimerRetirement(t *testing.T) {
t.Setenv("CARLOS_HOST_CLAIM", "true")
- for _, mismatch := range []bool{false, true} {
- name := "exact"
- if mismatch {
- name = "replacement"
- }
+ for _, name := range []string{"exact", "replacement", "unrecorded", "legacy-unrecorded"} {
+ mismatch := name == "replacement" || name == "unrecorded"
t.Run(name, func(t *testing.T) {
coordinator, req, _ := claimStopFixture(t)
result, err := coordinator.FenceAndStop(t.Context(), req)
@@ -40,7 +37,7 @@ func TestPlan14GenerationTimerRetirement(t *testing.T) {
}
timer := registry.Timer{Host: req.Host, Name: "backup", Kind: "cron", Registration: "timer-1", ClaimOwnership: owner}
timer.ClaimRowID = "timer-1"
- if mismatch {
+ if name == "replacement" {
timer.Registration = "replacement"
timer.ClaimRowID = "replacement"
}
@@ -51,11 +48,14 @@ func TestPlan14GenerationTimerRetirement(t *testing.T) {
{Kind: "route", ArtifactID: "route", ManagedRoot: "registry", RelativePath: req.Host, RowIdentity: owner.ClaimRowID, Ownership: owner.ClaimOwner, Materialization: "materialized"},
{Kind: "timer", ArtifactID: "timer", ManagedRoot: "registry", RelativePath: req.Host + "/backup", RowIdentity: "timer-1", Ownership: owner.ClaimOwner, Materialization: "materialized"},
}}
+ if name == "unrecorded" || name == "legacy-unrecorded" {
+ in.Entries = in.Entries[:1]
+ }
if err := inventory.WriteStopInventory(t.Context(), in); err != nil {
t.Fatal(err)
}
journal := filepath.Join(base, "retire")
- root := ClaimRootHTTP{Stop: ClaimStopHTTP{Coordinator: coordinator}, Roots: map[string]string{"data": base}, Reconcile: hostbox.ReconcileAdapter{Inventory: inventory, Registry: reg, Retirement: hostbox.TypedRetirement{JournalDir: journal}}}
+ root := ClaimRootHTTP{Stop: ClaimStopHTTP{Coordinator: coordinator}, Roots: map[string]string{"data": base}, Reconcile: hostbox.ReconcileAdapter{RequireTimerInventory: name != "legacy-unrecorded", Inventory: inventory, Registry: reg, Retirement: hostbox.TypedRetirement{JournalDir: journal}}}
// Remote acknowledgement is deliberately unavailable. Local completion
// must already be durable, and cannot be confused with the remote result.
if err := root.retire(t.Context(), result.Target); err == nil {
@@ -70,7 +70,7 @@ func TestPlan14GenerationTimerRetirement(t *testing.T) {
t.Fatal(err)
}
if mismatch {
- if got == nil || len(timers) != 1 || timers[0].Registration != "replacement" {
+ if got == nil || len(timers) != 1 || timers[0].Registration != timer.Registration {
t.Fatal("replacement changed")
}
return
diff --git a/internal/host/plan14_generation_transfer_root_linux_test.go b/internal/host/plan14_generation_transfer_root_linux_test.go
new file mode 100644
index 000000000..ba8080d0e
--- /dev/null
+++ b/internal/host/plan14_generation_transfer_root_linux_test.go
@@ -0,0 +1,164 @@
+//go:build linux && rootrunner
+
+package host
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "syscall"
+ "testing"
+
+ "github.com/carlosframework/platform/internal/release"
+ "golang.org/x/sys/unix"
+)
+
+func TestPlan14GenerationDescriptorTransferRootRunner(t *testing.T) {
+ const modeKey = "PLAN14_E44_TRANSFER"
+ path := os.Getenv("PLAN14_E44_TRANSFER_PATH")
+ switch os.Getenv(modeKey) {
+ case "mount":
+ if err := unix.Mount("tmpfs", filepath.Dir(path), "tmpfs", 0, "mode=0755"); err != nil {
+ t.Fatal(err)
+ }
+ defer unix.Unmount(filepath.Dir(path), 0)
+ cmd := exec.Command(os.Args[0], "-test.run=^TestPlan14GenerationDescriptorTransferRootRunner$", "-test.timeout=30s")
+ cmd.Env = append(os.Environ(), modeKey+"=receive")
+ cmd.Dir = "/"
+ cmd.ExtraFiles = []*os.File{os.NewFile(3, "transfer")}
+ cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
+ cmd.SysProcAttr = &syscall.SysProcAttr{Credential: &syscall.Credential{Uid: 65534, Gid: 65534, Groups: []uint32{65534}}}
+ if err := cmd.Run(); err != nil {
+ t.Fatal(err)
+ }
+ return
+ case "receive":
+ if os.Geteuid() != 65534 {
+ t.Fatal("receiver must be unprivileged")
+ }
+ if _, err := os.ReadFile(path); !errors.Is(err, os.ErrNotExist) {
+ t.Fatal("staged path not masked", err)
+ }
+ fmt.Printf("READY %d\n", os.Getpid())
+ data, control := make([]byte, 1), make([]byte, unix.CmsgSpace(4))
+ _, n, _, _, err := unix.Recvmsg(3, data, control, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ messages, err := unix.ParseSocketControlMessage(control[:n])
+ if err != nil || len(messages) != 1 {
+ t.Fatal("rights message", err)
+ }
+ fds, err := unix.ParseUnixRights(&messages[0])
+ if err != nil || len(fds) != 1 {
+ t.Fatal("rights descriptor", err)
+ }
+ file := os.NewFile(uintptr(fds[0]), "unreserved")
+ defer file.Close()
+ body := make([]byte, 10)
+ if n, err := file.Read(body); err != nil || string(body[:n]) != "unreserved" {
+ t.Fatal("received staged bytes", string(body), err)
+ }
+ fmt.Printf("HELD %d\n", fds[0])
+ if _, err := unix.Read(3, data); err != nil {
+ t.Fatal(err)
+ }
+ return
+ case "send":
+ if os.Geteuid() != 65534 {
+ t.Fatal("sender must be unprivileged")
+ }
+ file, err := os.Open(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer file.Close()
+ if err := unix.Sendmsg(3, []byte{1}, unix.UnixRights(int(file.Fd())), nil, 0); err != nil {
+ t.Fatal(err)
+ }
+ return
+ }
+ dir := generationRootRunnerDir(t)
+ body, err := os.ReadFile(os.Args[0])
+ if err != nil {
+ t.Fatal(err)
+ }
+ binary := filepath.Join(dir, "transfer.test")
+ if err := os.WriteFile(binary, body, 0755); err != nil {
+ t.Fatal(err)
+ }
+ path = filepath.Join(dir, "versions", "unreserved")
+ if err := os.Mkdir(filepath.Dir(path), 0755); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.WriteFile(path, []byte("unreserved"), 0644); err != nil {
+ t.Fatal(err)
+ }
+ sockets, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ parent, child := os.NewFile(uintptr(sockets[0]), "parent"), os.NewFile(uintptr(sockets[1]), "child")
+ defer parent.Close()
+ defer child.Close()
+ cmd := exec.CommandContext(t.Context(), "unshare", "--mount", binary, "-test.run=^TestPlan14GenerationDescriptorTransferRootRunner$", "-test.timeout=45s")
+ cmd.Dir = dir
+ cmd.Env = append(os.Environ(), modeKey+"=mount", "PLAN14_E44_TRANSFER_PATH="+path)
+ cmd.ExtraFiles = []*os.File{child}
+ cmd.Stderr = os.Stderr
+ output, err := cmd.StdoutPipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := cmd.Start(); err != nil {
+ t.Fatal(err)
+ }
+ defer func() { cmd.Process.Kill(); cmd.Wait() }()
+ scan := bufio.NewScanner(output)
+ number := func(prefix string) int {
+ t.Helper()
+ for scan.Scan() {
+ if s, ok := strings.CutPrefix(scan.Text(), prefix); ok {
+ n, err := strconv.Atoi(s)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return n
+ }
+ t.Log(scan.Text())
+ }
+ t.Fatal("missing child observation", prefix, scan.Err())
+ return 0
+ }
+ pid := number("READY ")
+ sender := exec.CommandContext(t.Context(), binary, "-test.run=^TestPlan14GenerationDescriptorTransferRootRunner$", "-test.timeout=15s")
+ sender.Dir = dir
+ sender.Env = append(os.Environ(), modeKey+"=send", "PLAN14_E44_TRANSFER_PATH="+path)
+ sender.ExtraFiles = []*os.File{parent}
+ sender.SysProcAttr = &syscall.SysProcAttr{Credential: &syscall.Credential{Uid: 65534, Gid: 65534, Groups: []uint32{65534}}}
+ if out, err := sender.CombinedOutput(); err != nil {
+ t.Fatalf("unprivileged sender: %v %s", err, out)
+ }
+ fd := number("HELD ")
+ if err := generationLinkRef(fmt.Sprintf("/proc/%d/fd/%d", pid, fd), release.GenerationBinding{Path: path, Tree: filepath.Dir(path)}); err == nil {
+ t.Fatal("transferred staged descriptor was missed")
+ }
+ t.Setenv("CARLOS_OPT_DIR", dir)
+ if _, _, err := productionGenerationKernelCensus(t.Context()); err == nil {
+ t.Fatal("uncontained descriptor recipient certified")
+ }
+ if _, err := parent.Write([]byte{1}); err != nil {
+ t.Fatal(err)
+ }
+ for scan.Scan() {
+ t.Log(scan.Text())
+ }
+ if err := cmd.Wait(); err != nil {
+ t.Fatal(err)
+ }
+}
diff --git a/internal/host/plan14_timer_intention_root_linux_test.go b/internal/host/plan14_timer_intention_root_linux_test.go
new file mode 100644
index 000000000..c7d70585a
--- /dev/null
+++ b/internal/host/plan14_timer_intention_root_linux_test.go
@@ -0,0 +1,25 @@
+//go:build linux && rootrunner
+
+package host
+
+import (
+ "path/filepath"
+ "testing"
+
+ "github.com/carlosframework/platform/internal/rootstate"
+)
+
+func TestPlan14TimerIntentionRootRunner(t *testing.T) {
+ plan14TimerIntention(t, func(t *testing.T) rootstate.Store {
+ state, err := rootstate.Open(filepath.Join(generationRootRunnerDir(t), "authority"), true, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return state
+ })
+}
+
+func TestPlan14TimerRetirementRootRunner(t *testing.T) {
+ generationRootRunnerDir(t)
+ TestPlan14GenerationTimerRetirement(t)
+}
diff --git a/internal/host/plan14_timer_intention_test.go b/internal/host/plan14_timer_intention_test.go
new file mode 100644
index 000000000..5dfa6eaca
--- /dev/null
+++ b/internal/host/plan14_timer_intention_test.go
@@ -0,0 +1,185 @@
+package host
+
+import (
+ "context"
+ "crypto/sha256"
+ "database/sql"
+ "encoding/hex"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+
+ "github.com/carlosframework/platform/internal/hostbox"
+ "github.com/carlosframework/platform/internal/hostclaim"
+ "github.com/carlosframework/platform/internal/hostguard"
+ "github.com/carlosframework/platform/internal/provision"
+ "github.com/carlosframework/platform/internal/registry"
+ "github.com/carlosframework/platform/internal/rootstate"
+)
+
+func TestPlan14TimerIntentionBeforeMaterialization(t *testing.T) {
+ plan14TimerIntention(t, func(t *testing.T) rootstate.Store { return &rollbackDisk{dir: t.TempDir()} })
+}
+
+type timerSyncFault struct {
+ rootstate.Store
+ phase string
+}
+
+func (s *timerSyncFault) Write(name string, body []byte) error {
+ if err := s.Store.Write(name, body); err != nil {
+ return err
+ }
+ pending := strings.Contains(string(body), `"PendingInstallation"`)
+ if s.phase == "pending" && pending || s.phase == "complete" && !pending {
+ s.phase = ""
+ return os.ErrInvalid
+ }
+ return nil
+}
+
+func plan14TimerIntention(t *testing.T, open func(*testing.T) rootstate.Store) {
+ for _, attack := range []string{"none", "already-materialized", "late-intention", "changed-registration", "changed-row", "omit-intention", "crash-after-intention", "crash-after-row", "intention-sync", "materialization-sync"} {
+ t.Run(attack, func(t *testing.T) {
+ t.Setenv("CARLOS_HOST_CLAIM", "1")
+ f := newRecoveryFixture(t)
+ o := f.read()
+ h := o.Claim.Holder
+ plan14PutRecord(t, f.claims, provision.Record{Schema: 1, Host: o.Host, Account: h.Account, App: h.App, Channel: "stable", CreatedAt: f.now})
+ _, etag, _ := f.claims.Get(t.Context(), h.Key)
+ journals, err := hostbox.NewJournalService(f.journal)
+ if err != nil {
+ t.Fatal(err)
+ }
+ registryPath := filepath.Join(t.TempDir(), "registry.db")
+ reg, err := registry.Open(registryPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer reg.Close()
+ sum := sha256.Sum256([]byte(fmt.Sprintf("%s\x00%s\x00%d\x00%s", o.Host, h.Incarnation, h.Stamp, "box-1")))
+ owner := registry.ClaimOwnership{ClaimIncarnation: h.Incarnation, ClaimStamp: h.Stamp, ClaimOwner: hex.EncodeToString(sum[:]), ClaimRowID: "route"}
+ if err := reg.Upsert(registry.Route{Host: o.Host, Account: h.Account, App: h.App, DBPath: provision.NestedDBPath(f.guard.DataDir, o.Host), ClaimOwnership: owner}); err != nil {
+ t.Fatal(err)
+ }
+ in := hostbox.StopInventory{Complete: true, Host: o.Host, Incarnation: h.Incarnation, InstallationStamp: h.Stamp, Box: "box-1", Enrollment: "en", DirectoryRevision: "1", Entries: []hostbox.StopInventoryEntry{{Kind: "route", ArtifactID: "route", ManagedRoot: "registry", RelativePath: o.Host, RowIdentity: owner.ClaimRowID, Ownership: owner.ClaimOwner, Materialization: "materialized"}}}
+ if err := journals.Inventory.WriteStopInventory(t.Context(), in); err != nil {
+ t.Fatal(err)
+ }
+ g := f.guard
+ state := &timerSyncFault{Store: open(t)}
+ defer state.Close()
+ g.Root = hostguard.NewRootAuthority(state)
+ if err := g.EstablishRoot(t.Context(), hostguard.RootInstallation{Artifacts: hostguard.InstallationArtifacts{Schema: 1, StateDirectory: true}, Host: o.Host, Incarnation: h.Incarnation, Stamp: h.Stamp, Account: h.Account, App: h.App, Record: h.Key, Box: "box-1", Enrollment: "en", Migration: 1, InventoryRevision: 1}, func(*hostclaim.Claim) error { return nil }); err != nil {
+ t.Fatal(err)
+ }
+ actor := hostbox.Actor{Box: "box-1", Enrollment: "en", Revision: 1}
+ root := ClaimRootHTTP{Stop: ClaimStopHTTP{Coordinator: ClaimStopCoordinator{Guard: g, Evidence: f.claims}}, Journals: journals, InventoryActor: func(context.Context) (hostbox.Actor, error) { return actor, nil }, Roots: map[string]string{"data": g.DataDir}, Reconcile: hostbox.ReconcileAdapter{Registry: reg, Census: &hostbox.EmptyInstallationCensus{}}}
+ var last hostbox.JournalRequest
+ _, client := hostbox.RemoteJournals(func(ctx context.Context, q hostbox.JournalRequest) (hostbox.JournalResponse, error) {
+ last = q
+ return root.commitRootInventory(ctx, q)
+ })
+ expect := hostbox.InventoryExpectation{Revision: 1, RecordKey: h.Key, RecordETag: etag, Target: hostguard.FenceTarget{Host: o.Host, Incarnation: h.Incarnation, Stamp: h.Stamp, Session: h.Session, Box: actor.Box, Enrollment: actor.Enrollment, ClaimETag: o.ETag, Op: o.Claim.Op.ID, Phase: o.Claim.Op.Phase}}
+ write := func() error {
+ _, err := client.WriteExpected(t.Context(), in, expect)
+ if err == nil {
+ expect.Revision++
+ }
+ return err
+ }
+ timer := registry.Timer{Host: o.Host, Name: "daily", Kind: "schedule", Spec: "every 1h0m0s", Path: "/daily", Registration: "planned", ClaimOwnership: owner}
+ timer.ClaimRowID = timer.Registration
+ row := func() {
+ t.Helper()
+ if err := reg.ReplaceTimers(o.Host, []registry.Timer{timer}); err != nil {
+ t.Fatal(err)
+ }
+ }
+ in.Entries = append(in.Entries, hostbox.StopInventoryEntry{Kind: "timer", ArtifactID: "timer", ManagedRoot: "registry", RelativePath: o.Host + "/daily", RowIdentity: timer.Registration, Ownership: owner.ClaimOwner, Materialization: "intended"})
+ if attack == "already-materialized" || attack == "late-intention" {
+ row()
+ if attack == "already-materialized" {
+ in.Entries[1].Materialization = "materialized"
+ }
+ if err := write(); err == nil {
+ t.Fatal("adopted timer without prior root intention")
+ }
+ return
+ }
+ if attack == "intention-sync" {
+ state.phase = "pending"
+ if err := write(); err == nil {
+ t.Fatal("acknowledged unsynced intention")
+ }
+ if err := root.recoverRootInventory(t.Context()); err != nil {
+ t.Fatal("recover intended write", err)
+ }
+ if _, err := root.commitRootInventory(t.Context(), last); err != nil {
+ t.Fatal("replay recovered intention", err)
+ }
+ expect.Revision++
+ } else if err := write(); err != nil {
+ t.Fatal("record intention", err)
+ }
+ if attack == "crash-after-intention" {
+ if err := write(); err != nil {
+ t.Fatal("resume absent intended row", err)
+ }
+ }
+ row()
+ if attack == "crash-after-row" {
+ if err := write(); err != nil {
+ t.Fatal("resume intended exact row", err)
+ }
+ }
+ in.Entries[1].Materialization = "materialized"
+ if attack == "materialization-sync" {
+ state.phase = "complete"
+ }
+ switch attack {
+ case "changed-registration", "changed-row":
+ db, err := sql.Open("sqlite", registryPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ column := "registration"
+ if attack == "changed-row" {
+ column = "claim_row_id"
+ }
+ _, err = db.Exec("UPDATE timers SET "+column+"='successor' WHERE host=? AND name=?", o.Host, timer.Name)
+ db.Close()
+ if err != nil {
+ t.Fatal(err)
+ }
+ case "omit-intention":
+ in.Entries = in.Entries[:1]
+ }
+ err = write()
+ if attack == "materialization-sync" {
+ if err == nil {
+ t.Fatal("acknowledged unsynced materialization")
+ }
+ _, err = root.commitRootInventory(t.Context(), last)
+ if err == nil {
+ expect.Revision++
+ }
+ }
+ valid := attack == "none" || attack == "crash-after-intention" || attack == "crash-after-row" || attack == "intention-sync" || attack == "materialization-sync"
+ if (err == nil) != valid {
+ t.Fatalf("materialization valid=%t: %v", valid, err)
+ }
+ if valid {
+ if err := write(); err != nil {
+ t.Fatal("materialized replay", err)
+ }
+ got, err := journals.Inventory.ReadStopInventory(t.Context(), o.Host)
+ if err != nil || len(got.Entries) != 2 || got.Entries[1] != in.Entries[1] {
+ t.Fatal("lost exact timer", got, err)
+ }
+ }
+ })
+ }
+}
diff --git a/internal/host/rootinventory.go b/internal/host/rootinventory.go
index f77be991e..d226e0cdf 100644
--- a/internal/host/rootinventory.go
+++ b/internal/host/rootinventory.go
@@ -231,6 +231,9 @@ func (h ClaimRootHTTP) commitRootInventoryPending(ctx context.Context, q hostbox
if revision != q.Revision {
return hostbox.ErrChanged
}
+ if err := h.verifyNewTimerIntentions(prior, in); err != nil {
+ return err
+ }
if revision == 0 {
if (!firstLanding && len(in.Entries) != 0) || rootFirstInstallation(observed.Claim, actor) != nil {
return hostbox.ErrChanged
diff --git a/internal/host/rootinventory_timer.go b/internal/host/rootinventory_timer.go
index f949ef1da..39f178e38 100644
--- a/internal/host/rootinventory_timer.go
+++ b/internal/host/rootinventory_timer.go
@@ -26,8 +26,37 @@ func (h ClaimRootHTTP) verifyInventoryTimer(host string, owner registry.ClaimOwn
}
return nil
}
- if (e.Materialization != "intended" && e.Materialization != "materialized") || timer.Host != host || timer.Name != name || timer.ClaimIncarnation != owner.ClaimIncarnation || timer.ClaimStamp != owner.ClaimStamp || timer.ClaimOwner != e.Ownership || timer.ClaimRowID != e.RowIdentity {
+ if (e.Materialization != "intended" && e.Materialization != "materialized") || timer.Host != host || timer.Name != name || timer.ClaimIncarnation != owner.ClaimIncarnation || timer.ClaimStamp != owner.ClaimStamp || timer.ClaimOwner != e.Ownership || timer.ClaimRowID != e.RowIdentity || timer.Registration != e.RowIdentity {
return hostbox.ErrTypedInventory
}
return nil
}
+
+func (h ClaimRootHTTP) verifyNewTimerIntentions(prior, next hostbox.StopInventory) error {
+ for _, e := range next.Entries {
+ if e.Kind != "timer" {
+ continue
+ }
+ known := false
+ for _, old := range prior.Entries {
+ if old.Kind == "timer" && old.ArtifactID == e.ArtifactID {
+ known = true
+ break
+ }
+ }
+ if known {
+ continue
+ }
+ if e.Materialization != "intended" {
+ return hostbox.ErrTypedInventory
+ }
+ row, err := h.Reconcile.Registry.GetTimer(next.Host, strings.TrimPrefix(e.RelativePath, next.Host+"/"))
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+ if row != nil {
+ return hostbox.ErrTypedInventory
+ }
+ }
+ return nil
+}
diff --git a/internal/host/rootinventory_timer_test.go b/internal/host/rootinventory_timer_test.go
index 4721c895b..4e195d950 100644
--- a/internal/host/rootinventory_timer_test.go
+++ b/internal/host/rootinventory_timer_test.go
@@ -15,7 +15,7 @@ import (
)
func TestPlan14RootTimerInventory(t *testing.T) {
- for _, attack := range []string{"none", "intended", "missing", "omitted-timer", "row identity", "owner", "path", "root", "shape"} {
+ for _, attack := range []string{"unrecorded", "intended", "missing", "omitted-timer", "row identity", "owner", "path", "root", "shape"} {
t.Run(attack, func(t *testing.T) {
t.Setenv("CARLOS_HOST_CLAIM", "1")
f := newRecoveryFixture(t)
@@ -80,7 +80,7 @@ func TestPlan14RootTimerInventory(t *testing.T) {
}
_, client := hostbox.RemoteJournals(root.commitRootInventory)
_, err = client.WriteExpected(t.Context(), in, hostbox.InventoryExpectation{Revision: 1, RecordKey: h.Key, RecordETag: etag, Target: hostguard.FenceTarget{Host: o.Host, Incarnation: h.Incarnation, Stamp: h.Stamp, Session: h.Session, Box: actor.Box, Enrollment: actor.Enrollment, ClaimETag: o.ETag, Op: o.Claim.Op.ID, Phase: o.Claim.Op.Phase}})
- valid := attack == "none" || attack == "intended"
+ valid := attack == "intended"
if valid && err != nil || !valid && err == nil {
t.Fatal(attack, err)
}
diff --git a/internal/hostbox/reconcile_adapter.go b/internal/hostbox/reconcile_adapter.go
index fd85509ee..51826df67 100644
--- a/internal/hostbox/reconcile_adapter.go
+++ b/internal/hostbox/reconcile_adapter.go
@@ -22,6 +22,7 @@ import (
)
type ReconcileAdapter struct {
+ RequireTimerInventory bool
DataRoot string
RemoteRetire func(context.Context, hostguard.FenceTarget) error
Census *EmptyInstallationCensus
@@ -335,7 +336,7 @@ func (a ReconcileAdapter) Retire(ctx context.Context, row registry.Route) error
timers = append(timers, x)
}
}
- combined, err := retirementWithTimers(inventory, timers)
+ combined, err := retirementWithTimers(inventory, timers, a.RequireTimerInventory)
if err != nil {
return err
}
@@ -359,7 +360,7 @@ func (a ReconcileAdapter) Retire(ctx context.Context, row registry.Route) error
if t.ClaimIncarnation != row.ClaimIncarnation || t.ClaimStamp != row.ClaimStamp || t.ClaimOwner != row.ClaimOwner || t.ClaimRowID == "" || t.Registration == "" {
return ErrTypedInventory
}
- inventory, err = retirementWithTimers(inventory, []Artifact{{Kind: ArtifactTimer, Root: "registry", Name: row.Host + "/" + t.Name, Owner: row.ClaimOwner, RowID: t.ClaimRowID + "~" + t.Registration}})
+ inventory, err = retirementWithTimers(inventory, []Artifact{{Kind: ArtifactTimer, Root: "registry", Name: row.Host + "/" + t.Name, Owner: row.ClaimOwner, RowID: t.ClaimRowID + "~" + t.Registration}}, a.RequireTimerInventory)
if err != nil {
return err
}
diff --git a/internal/hostbox/retirement_timers.go b/internal/hostbox/retirement_timers.go
index 2e3019361..aa3ac34db 100644
--- a/internal/hostbox/retirement_timers.go
+++ b/internal/hostbox/retirement_timers.go
@@ -2,7 +2,7 @@ package hostbox
import "strings"
-func retirementWithTimers(in RetirementInventory, timers []Artifact) (RetirementInventory, error) {
+func retirementWithTimers(in RetirementInventory, timers []Artifact, requireInventory bool) (RetirementInventory, error) {
in.Artifacts = append([]Artifact(nil), in.Artifacts...)
for _, timer := range timers {
found := false
@@ -20,6 +20,9 @@ func retirementWithTimers(in RetirementInventory, timers []Artifact) (Retirement
found = true
}
if !found {
+ if requireInventory {
+ return RetirementInventory{}, ErrTypedInventory
+ }
in.Artifacts = append(in.Artifacts, timer)
}
}
diff --git a/internal/provision/plan14_e_write_census_test.go b/internal/provision/plan14_e_write_census_test.go
index 1f9a0a00b..d301ee7e7 100644
--- a/internal/provision/plan14_e_write_census_test.go
+++ b/internal/provision/plan14_e_write_census_test.go
@@ -6,7 +6,7 @@ var plan14EWriteCensusOverlay = []censusEntry{
{"internal/host/rootgeneration_inventory.go", "generationReconciler.reconcile", "key-site", "key:RecordKey", "f3bcd466fa3bc92b50f93f6986b4c566db198247ef0e37fe6daf3e2fee760a92"},
{"internal/host/rootgenerations_launch.go", "rootGenerationSource.resolve", "key-site", "key:RecordKey", "d69a3c0c86b03d5469708ac8bf084f4f516c0b61a18519d46cc3c0a0616f5381"},
{"internal/historyreadback/workflow.go", "Operation.deliverGrant", "nonrecord", "sel:Put", "fb410ef6274ef7de0997a804c837ad1ecf29d8826ba1c4dcda00c2ae986fc434"},
- {"internal/host/rootinventory.go", "ClaimRootHTTP.commitRootInventoryPending", "key-site", "key:RecordKey", "12b08da25b91b0827412cfe0119bdcc9a98bda5a60c56a987e0d44f7016d3bab"},
+ {"internal/host/rootinventory.go", "ClaimRootHTTP.commitRootInventoryPending", "key-site", "key:RecordKey", "b08d3c55f02e0b9e9c39455ce37f854a254c5f65af9fc74443fee414e2756f1b"},
{"internal/host/rootinventory.go", "ClaimRootHTTP.recoverRootInventory", "key-site", "key:RecordKey", "a828684ad31a409d3e1fc306216d907681f514cb813302eb965a040e5084feda"},
{"internal/host/roothistory_creation.go", "rootRestartRequestSource.creation", "key-site", "key:RecordKey", "af79a66b746b0f42388010b38ea8130dca6a8d29c4ba5623bfc9c3cc40dd9601"},
{"internal/host/rootrestart_requests.go", "rootRestartRequests.consume", "key-site", "key:RecordKey", "0a9fbbe6f201077e276298b7e666e1ee2a345f1c420335290fec38a676b1ad84"},
diff --git a/internal/provision/testdata/plan14-root-reads-e.tsv b/internal/provision/testdata/plan14-root-reads-e.tsv
index 1a33c0ac4..3e02081d0 100644
--- a/internal/provision/testdata/plan14-root-reads-e.tsv
+++ b/internal/provision/testdata/plan14-root-reads-e.tsv
@@ -54,7 +54,7 @@ internal/release/roothistory_replace.go replacementReconstruction authority E1 S
internal/release/signedhead.go StageSignedHead authority E1 Canonical route is supplied by the root unit verifier. Verifies signed channel, digest ratchet and manifest/artifact before staging; unavailable pin census never authorizes restaging differing bytes. Fixtures: TestPlan14Incident20261006WithLedger and TestPlan14SignedNamedRecoveryAuthority. registry:Account@202,registry:Account@302,registry:Account@403,registry:App@214,registry:App@415,registry:Channel@222,registry:Host@434 ca02f04e55556ba0233dfa0848b0c533a9d9aa06cad6abd33cfd2968b3bd838a
internal/rootstate/link_linux.go ReadTrustedLink authority A,E1 Checks the full root-owned parent chain before descriptor-based live-link reading. Fixture: TestPlan14TrustedLiveLink; privileged acceptance remains required. file:Open@156 90f8e8de4e2c71f07b5d749cbe4bb1c1dfa426576a82138823449f56fc9ec5ab
internal/rootstate/link_linux.go directory.readLink authority A,E1 Pins the symlink inode with O_PATH and checks owner/type/link count before readlinkat. Fixture: TestPlan14TrustedLiveLink. file:Fstat@356,file:Openat@183,file:Readlinkat@554 a8559fe63dce48830916004ecfe7fbbe5d419568c7daf7a3073cf1b5bb12c3d7
-@coverage internal/host 826ec7eaf12152b9a877635e7a73892edaf102da8f09b0cd155b441a52d2e544
+@coverage internal/host b5495e98a2ccc36bbb3e242aabfc623afa5a5ef248f778604c23607c002b88b0
@coverage internal/release 23bf042522655b27434395d03f05f6ab5e0d86025f58431b842d286a58bda0b7
@coverage internal/rootstate a773d6dbc0e4150e0054f7a94d0e1493c88d0d57f4f4530138b3427e2c217bc5
internal/release/roothistory_status.go RootHistory.MigrationStatus authority E1 Exact completion binds the accepted authorization digest from durable private history to enrolled-root signed readback. Pending originals, changed evidence and canonical sources cannot supply retirement completion. Fixtures: TestPlan14HistoryReconstructionCompletion, TestPlan14HistoryRetirementCompletion, TestPlan14ReadReconstructionCompletion, TestPlan14ReadRetirementCompletion, TestPlan14RecoveryRetirementProductionCallback, TestPlan14RootHistoryReconstructionCrash, TestPlan14HistoryRetirementProcessCrash. file:Read@1062,file:Read@3891 f0ab7ad2d2d70f8c22ebfd575f5a1c6931f7eba30ccdba80ad43d0efffc88154
@@ -167,7 +167,7 @@ internal/host/roothistory_creation.go rootHistoryInstallation authority E2 E37:
internal/host/roothistory_creation.go rootRestartRequestSource.creation authority E1 E16: root first-registration creation evidence under release exclusion; exact installation identity is required for first-program adoption and is consumed before an attempt. Installed-program history comes from the verified claim launch. Fixtures: TestPlan14FreshInstallRegistration, TestPlan14FreshInstallProgramGate, TestPlan14FreshInstallCanonicalIdentity, TestPlan14FreshInstallLaunchObservation, TestPlan14InstalledLaunchProgramEvidence, TestPlan14FreshInstallProcessCrash, TestPlan14FreshInstallVisibleSync, TestClaimControlUnitInstallers. registry:Account@219,registry:App@243,registry:Host@133,registry:Host@194 af79a66b746b0f42388010b38ea8130dca6a8d29c4ba5623bfc9c3cc40dd9601
internal/host/roothistory_creation.go type rootHistoryAccess authority E1 E16: root first-registration creation evidence under release exclusion; exact installation identity is required for first-program adoption and is consumed before an attempt. Installed-program history comes from the verified claim launch. Fixtures: TestPlan14FreshInstallRegistration, TestPlan14FreshInstallProgramGate, TestPlan14FreshInstallCanonicalIdentity, TestPlan14FreshInstallLaunchObservation, TestPlan14InstalledLaunchProgramEvidence, TestPlan14FreshInstallProcessCrash, TestPlan14FreshInstallVisibleSync, TestClaimControlUnitInstallers. 9c97bcd7878d79022ed942d8e6c00e944a00e0fa27a1bc5c732b4f4c10f4e9dd
internal/host/roothistory_launch.go ClaimLaunchRoot.rootInstalledProgram authority E1 E16: root first-registration creation evidence under release exclusion; exact installation identity is required for first-program adoption and is consumed before an attempt. Installed-program history comes from the verified claim launch. Fixtures: TestPlan14FreshInstallRegistration, TestPlan14FreshInstallProgramGate, TestPlan14FreshInstallCanonicalIdentity, TestPlan14FreshInstallLaunchObservation, TestPlan14InstalledLaunchProgramEvidence, TestPlan14FreshInstallProcessCrash, TestPlan14FreshInstallVisibleSync, TestClaimControlUnitInstallers. file:OpenFile@541,file:Read@1043 e8d530dd1b8f613dfbd41d34a1b08b19aa3f74e0522c8f41db768ccd4e9e80d2
-internal/host/rootinventory.go ClaimRootHTTP.commitRootInventoryPending authority E2 E37: retain independently initialized state-directory and timer requirements in the root installation transaction; preserve them through crash/reopen and rebind. Unknown history cannot certify completeness. Recommit under root-host exclusion before generation use. Canonical record and root retained obligations constrain agent candidate lists; timer enumeration is deny-only. Pre-flight 33, 80, 83. Fixtures: TestPlan14GenerationRequiredArtifacts, TestPlan14GenerationRegistryRows, TestPlan14RootTimerInventory, TestPlan14InventoryRequirementsRegistration, TestPlan14InventoryRequirementsUnknownAndRatchet, TestPlan14InventoryArtifactsCrash, TestPlan14InventoryRequirementsHistoryIdentity. Changed RequiredArtifacts and ExactRetirement root-runner witnesses await acceptance; remaining E2 launch/parked/containment integration is incomplete. file:Read@1383,file:Read@3423,registry:Account@4787,registry:Account@4802,registry:App@4823,registry:App@4834,registry:ClaimIncarnation@4293,registry:ClaimOwner@4343,registry:ClaimOwner@5398,registry:ClaimOwnership@4885,registry:ClaimOwnership@4907,registry:ClaimOwnership@5712,registry:ClaimOwnership@7713,registry:ClaimRowID@4507,registry:ClaimRowID@4584,registry:ClaimStamp@4321,registry:DBPath@4851,registry:DBPath@4865,registry:Host@4757,registry:Host@4769,whole:ClaimOwnership@4889,whole:ClaimOwnership@4917,whole:ClaimOwnership@5722,whole:ClaimOwnership@7724,whole:Route@3615,whole:Route@3998,whole:Route@4014,whole:Route@4069,whole:Route@4720,whole:Route@4733,whole:Route@4742,whole:Route@4980,whole:Route@5212,whole:Route@5960,whole:Route@6096,whole:Route@6320,whole:Route@7557,whole:Route@8964,whole:Route@9025,whole:Route@9565 12b08da25b91b0827412cfe0119bdcc9a98bda5a60c56a987e0d44f7016d3bab
+internal/host/rootinventory.go ClaimRootHTTP.commitRootInventoryPending authority E2 PROPOSED E44: root timer intention must precede materialization; exact registration and row identity required; root retirement cannot infer ownership from registry enumeration. Tests: TestPlan14TimerIntentionBeforeMaterialization, TestPlan14GenerationTimerRetirement; root timer intention, sync-failure retry and retirement acceptance. Scheduler producer wiring remains incomplete. file:Read@1383,file:Read@3423,registry:Account@4787,registry:Account@4802,registry:App@4823,registry:App@4834,registry:ClaimIncarnation@4293,registry:ClaimOwner@4343,registry:ClaimOwner@5398,registry:ClaimOwnership@4885,registry:ClaimOwnership@4907,registry:ClaimOwnership@5712,registry:ClaimOwnership@7795,registry:ClaimRowID@4507,registry:ClaimRowID@4584,registry:ClaimStamp@4321,registry:DBPath@4851,registry:DBPath@4865,registry:Host@4757,registry:Host@4769,whole:ClaimOwnership@4889,whole:ClaimOwnership@4917,whole:ClaimOwnership@5722,whole:ClaimOwnership@7806,whole:Route@3615,whole:Route@3998,whole:Route@4014,whole:Route@4069,whole:Route@4720,whole:Route@4733,whole:Route@4742,whole:Route@4980,whole:Route@5212,whole:Route@5960,whole:Route@6096,whole:Route@6320,whole:Route@7639,whole:Route@9046,whole:Route@9107,whole:Route@9647 b08d3c55f02e0b9e9c39455ce37f854a254c5f65af9fc74443fee414e2756f1b
internal/release/roothistory_creation.go RootHistory.ConsumeCreation authority E1 E16: root first-registration creation evidence under release exclusion; exact installation identity is required for first-program adoption and is consumed before an attempt. Installed-program history comes from the verified claim launch. Fixtures: TestPlan14FreshInstallRegistration, TestPlan14FreshInstallProgramGate, TestPlan14FreshInstallCanonicalIdentity, TestPlan14FreshInstallLaunchObservation, TestPlan14InstalledLaunchProgramEvidence, TestPlan14FreshInstallProcessCrash, TestPlan14FreshInstallVisibleSync, TestClaimControlUnitInstallers. 7ec65199b5afc894ae4aefded6a40f485086d0f8a4c86391eca193f473dca2a1
internal/release/roothistory_creation.go RootHistory.FreshInstallation authority E1 E16: root first-registration creation evidence under release exclusion; exact installation identity is required for first-program adoption and is consumed before an attempt. Installed-program history comes from the verified claim launch. Fixtures: TestPlan14FreshInstallRegistration, TestPlan14FreshInstallProgramGate, TestPlan14FreshInstallCanonicalIdentity, TestPlan14FreshInstallLaunchObservation, TestPlan14InstalledLaunchProgramEvidence, TestPlan14FreshInstallProcessCrash, TestPlan14FreshInstallVisibleSync, TestClaimControlUnitInstallers. 71b326a36350139c9c592b0b5b4b91409831fcffdd0c61d68cf5ec2bf54bbd5b
internal/release/roothistory_creation.go RootHistory.RecordCreation authority E1 E16: root first-registration creation evidence under release exclusion; exact installation identity is required for first-program adoption and is consumed before an attempt. Installed-program history comes from the verified claim launch. Fixtures: TestPlan14FreshInstallRegistration, TestPlan14FreshInstallProgramGate, TestPlan14FreshInstallCanonicalIdentity, TestPlan14FreshInstallLaunchObservation, TestPlan14InstalledLaunchProgramEvidence, TestPlan14FreshInstallProcessCrash, TestPlan14FreshInstallVisibleSync, TestClaimControlUnitInstallers. 0b545ab63902eb8f2c8988991786c964a2af53d65eff46d8e53a0747da492b6d
@@ -295,7 +295,7 @@ internal/host/rootgeneration_census_linux.go generationKernel.census authority E
internal/host/rootgeneration_inventory.go generationInventoryAt authority E2 E26: checked read-only payload enumeration includes inventory and revision orphans; completeness binds current and retained predecessor inventories, exact current revision and both source reads. Fixtures: TestPlan14GenerationPayloadCompleteness, TestPlan14GenerationPayloadRetainedPredecessor, TestPlan14GenerationInventoryPinConsumer, TestPlan14GenerationPayloadInventory, TestPlan14GenerationPayloadReaderDoesNotInitialize. Checked root reader acceptance passed at E27; running-tenant containment remains unfinished. PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. file:Open@173,file:Open@533 57f4c32cd65afcefad93a41d5266e2b12acc624ff808f47d40e31a79bbc7a827
internal/hostbox/generationinventory.go LaunchJournal.GenerationLaunches authority E2 E24: independent root installation and launch enumeration, exact reservation reconciliation and fresh quiescent whole-version-tree kernel census; missing sources, open unreserved launches, foreign namespaces and uncontained tasks refuse. Production pin source certifies this evidence before returning pins. Fixtures: TestPlan14GenerationInventoryCompleteness, TestPlan14GenerationInventoryPinConsumer, TestPlan14GenerationInventoryLifecycle, TestPlan14GenerationInventoryProcessCrash, TestPlan14GenerationKernelCensusUnknownReference, TestPlan14GenerationLaunchInventory, TestPlan14GenerationReaderDoesNotInitialize, TestPlan14GenerationInstallationInventory. Checked production source/kernel witnesses passed on the E27 root runner. E29 physical-payload and installed-unit witnesses passed on the controller runner; E30 required-artifact witness pending. file:QueryContext@270 6694bd3a35b8d91f53e67134145c44854086f08593110ed1b905bca9587be290
internal/hostbox/generationinventory.go OpenGenerationLaunchReader authority E2 E24: independent root installation and launch enumeration, exact reservation reconciliation and fresh quiescent whole-version-tree kernel census; missing sources, open unreserved launches, foreign namespaces and uncontained tasks refuse. Production pin source certifies this evidence before returning pins. Fixtures: TestPlan14GenerationInventoryCompleteness, TestPlan14GenerationInventoryPinConsumer, TestPlan14GenerationInventoryLifecycle, TestPlan14GenerationInventoryProcessCrash, TestPlan14GenerationKernelCensusUnknownReference, TestPlan14GenerationLaunchInventory, TestPlan14GenerationReaderDoesNotInitialize, TestPlan14GenerationInstallationInventory. Checked production source/kernel witnesses passed on the E27 root runner. E29 physical-payload and installed-unit witnesses passed on the controller runner; E30 required-artifact witness pending. file:Lstat@218,file:Open@283 552d8629a7932c4ae5dca9d2beef4559f7e8a2864259d11e2ff98c699fdcd999
-@coverage internal/hostbox 3b30e41161008d1ae3f1176da62f07c2f770da3e717e437b2f0bdc44fca7869d
+@coverage internal/hostbox c3d05fdb9b9e5ed56d29df815e73bd857e8a2a6f31168deecf628c0bf82ccf1a
internal/host/rootgeneration_census_linux.go productionGenerationKernelCensus authority E2 E24: independent root installation and launch enumeration, exact reservation reconciliation and fresh quiescent whole-version-tree kernel census; missing sources, open unreserved launches, foreign namespaces and uncontained tasks refuse. Production pin source certifies this evidence before returning pins. Fixtures: TestPlan14GenerationInventoryCompleteness, TestPlan14GenerationInventoryPinConsumer, TestPlan14GenerationInventoryLifecycle, TestPlan14GenerationInventoryProcessCrash, TestPlan14GenerationKernelCensusUnknownReference, TestPlan14GenerationLaunchInventory, TestPlan14GenerationReaderDoesNotInitialize, TestPlan14GenerationInstallationInventory. Checked production source/kernel witnesses passed on the E27 root runner. E29 physical-payload and installed-unit witnesses passed on the controller runner; E30 required-artifact witness pending. 85f085029e0cd557c8ff8eb2d522fdf131c388229c14515fda0f45453d40eebb
internal/host/rootgeneration_census_other.go productionGenerationKernelCensus authority E2 E24: independent root installation and launch enumeration, exact reservation reconciliation and fresh quiescent whole-version-tree kernel census; missing sources, open unreserved launches, foreign namespaces and uncontained tasks refuse. Production pin source certifies this evidence before returning pins. Fixtures: TestPlan14GenerationInventoryCompleteness, TestPlan14GenerationInventoryPinConsumer, TestPlan14GenerationInventoryLifecycle, TestPlan14GenerationInventoryProcessCrash, TestPlan14GenerationKernelCensusUnknownReference, TestPlan14GenerationLaunchInventory, TestPlan14GenerationReaderDoesNotInitialize, TestPlan14GenerationInstallationInventory. Checked production source/kernel witnesses passed on the E27 root runner. E29 physical-payload and installed-unit witnesses passed on the controller runner; E30 required-artifact witness pending. a1217cf881e9d05336fc16d6050f7f7a1a2184a0c20ff2988d0cf0f9012eaa45
internal/host/rootgeneration_inventory.go generationInstallation authority E2 E24: independent root installation and launch enumeration, exact reservation reconciliation and fresh quiescent whole-version-tree kernel census; missing sources, open unreserved launches, foreign namespaces and uncontained tasks refuse. Production pin source certifies this evidence before returning pins. Fixtures: TestPlan14GenerationInventoryCompleteness, TestPlan14GenerationInventoryPinConsumer, TestPlan14GenerationInventoryLifecycle, TestPlan14GenerationInventoryProcessCrash, TestPlan14GenerationKernelCensusUnknownReference, TestPlan14GenerationLaunchInventory, TestPlan14GenerationReaderDoesNotInitialize, TestPlan14GenerationInstallationInventory. Checked production source/kernel witnesses passed on the E27 root runner. E29 physical-payload and installed-unit witnesses passed on the controller runner; E30 required-artifact witness pending. 3e5e15d50752510acb44b10eb29621757a87bff54ceae015f704dd7f3a848ecb
@@ -341,8 +341,8 @@ internal/host/rootgeneration_parked.go generationReadRetirements authority E2 E3
internal/host/rootgeneration_parked.go generationRetirementRecords authority E2 E38: reconcile completed root retirement against the retained stop receipt and exact inventory; inspect parked identities, require retired rows absent or an independently inventoried successor, and retain generation pins. Fixtures: TestPlan14GenerationParkedPayload, TestPlan14GenerationParkedCrash, TestPlan14GenerationParkedKeepsReservation, TestPlan14GenerationParkedReadback; TestPlan14GenerationParkedRootRunner is runner-pending. Timer producer matches retained entries once and preserves successors on replay: TestPlan14GenerationTimerRetirement. No claims-off path changes. file:Read@707 02eca3d34294974c09e93199efb37a223e7615eaa32faffdafb982b5d4ba53e7
internal/host/rootgeneration_parked.go generationRetirements authority E2 E38: reconcile completed root retirement against the retained stop receipt and exact inventory; inspect parked identities, require retired rows absent or an independently inventoried successor, and retain generation pins. Fixtures: TestPlan14GenerationParkedPayload, TestPlan14GenerationParkedCrash, TestPlan14GenerationParkedKeepsReservation, TestPlan14GenerationParkedReadback; TestPlan14GenerationParkedRootRunner is runner-pending. Timer producer matches retained entries once and preserves successors on replay: TestPlan14GenerationTimerRetirement. No claims-off path changes. be9fd54e443994c423e0e315637b8cf05a361cbb32913b9bc9f891fbb65ad59f
internal/host/rootgeneration_parked.go productionGenerationRetirements authority E2 E38: reconcile completed root retirement against the retained stop receipt and exact inventory; inspect parked identities, require retired rows absent or an independently inventoried successor, and retain generation pins. Fixtures: TestPlan14GenerationParkedPayload, TestPlan14GenerationParkedCrash, TestPlan14GenerationParkedKeepsReservation, TestPlan14GenerationParkedReadback; TestPlan14GenerationParkedRootRunner is runner-pending. Timer producer matches retained entries once and preserves successors on replay: TestPlan14GenerationTimerRetirement. No claims-off path changes. file:Open@221,file:Open@306 fe83ff81bb9966f50de7fb3a049b8acd8e87f5d86346809090fc46b5c1e65189
-internal/hostbox/reconcile_adapter.go ReconcileAdapter.Retire authority C,E2 E38: reconcile completed root retirement against the retained stop receipt and exact inventory; inspect parked identities, require retired rows absent or an independently inventoried successor, and retain generation pins. Fixtures: TestPlan14GenerationParkedPayload, TestPlan14GenerationParkedCrash, TestPlan14GenerationParkedKeepsReservation, TestPlan14GenerationParkedReadback; TestPlan14GenerationParkedRootRunner is runner-pending. Timer producer matches retained entries once and preserves successors on replay: TestPlan14GenerationTimerRetirement. No claims-off path changes. registry:ClaimIncarnation@1309,registry:ClaimIncarnation@322,registry:ClaimIncarnation@3392,registry:ClaimIncarnation@3414,registry:ClaimOwner@1366,registry:ClaimOwner@1434,registry:ClaimOwner@2190,registry:ClaimOwner@2596,registry:ClaimOwner@3472,registry:ClaimOwner@3488,registry:ClaimOwner@3726,registry:ClaimOwnership@147,registry:ClaimRowID@3506,registry:ClaimRowID@3749,registry:ClaimStamp@1338,registry:ClaimStamp@3438,registry:ClaimStamp@3454,registry:ClaimStamp@362,registry:Host@1257,registry:Host@2095,registry:Host@2641,registry:Host@2698,registry:Host@288,registry:Host@3311,registry:Host@3694,registry:Name@3711,registry:Registration@3528,registry:Registration@3770,whole:ClaimOwnership@151,whole:Route@1069,whole:Route@1152,whole:Route@231,whole:Timer@3377 bbee00fbe7b9ae791cc78fab1a06d79945b475b3a5e31d082ae37edd9c69f611
-internal/hostbox/retirement_timers.go retirementWithTimers authority C,E2 E38: reconcile completed root retirement against the retained stop receipt and exact inventory; inspect parked identities, require retired rows absent or an independently inventoried successor, and retain generation pins. Fixtures: TestPlan14GenerationParkedPayload, TestPlan14GenerationParkedCrash, TestPlan14GenerationParkedKeepsReservation, TestPlan14GenerationParkedReadback; TestPlan14GenerationParkedRootRunner is runner-pending. Timer producer matches retained entries once and preserves successors on replay: TestPlan14GenerationTimerRetirement. No claims-off path changes. b4c4d4bcf2b3de23af676655cc4aa2901c7c0fd9a189d906fbffc81cd7d1bbf7
+internal/hostbox/reconcile_adapter.go ReconcileAdapter.Retire authority C,E2 PROPOSED E44: root timer intention must precede materialization; exact registration and row identity required; root retirement cannot infer ownership from registry enumeration. Tests: TestPlan14TimerIntentionBeforeMaterialization, TestPlan14GenerationTimerRetirement; root timer intention, sync-failure retry and retirement acceptance. Scheduler producer wiring remains incomplete. registry:ClaimIncarnation@1309,registry:ClaimIncarnation@322,registry:ClaimIncarnation@3417,registry:ClaimIncarnation@3439,registry:ClaimOwner@1366,registry:ClaimOwner@1434,registry:ClaimOwner@2190,registry:ClaimOwner@2596,registry:ClaimOwner@3497,registry:ClaimOwner@3513,registry:ClaimOwner@3751,registry:ClaimOwnership@147,registry:ClaimRowID@3531,registry:ClaimRowID@3774,registry:ClaimStamp@1338,registry:ClaimStamp@3463,registry:ClaimStamp@3479,registry:ClaimStamp@362,registry:Host@1257,registry:Host@2095,registry:Host@2641,registry:Host@2698,registry:Host@288,registry:Host@3336,registry:Host@3719,registry:Name@3736,registry:Registration@3553,registry:Registration@3795,whole:ClaimOwnership@151,whole:Route@1069,whole:Route@1152,whole:Route@231,whole:Timer@3402 42d0e63edc29cf8a83f8265fb1b0260a88334a7ebc3f7b9530d938d9ffa5e4f1
+internal/hostbox/retirement_timers.go retirementWithTimers authority C,E2 PROPOSED E44: root timer intention must precede materialization; exact registration and row identity required; root retirement cannot infer ownership from registry enumeration. Tests: TestPlan14TimerIntentionBeforeMaterialization, TestPlan14GenerationTimerRetirement; root timer intention, sync-failure retry and retirement acceptance. Scheduler producer wiring remains incomplete. 67060d1e6077a5cb6a15ea2749e070e97f830177987570037564916f5ed9bda0
internal/activator/activator.go Service.respawnWorkerLocked authority E2 E39: close the unsupported claimed worker path before backend entry, replacement or supervised stop. Claims-off paths remain unchanged. This is refusal coverage, not positive root-reserved worker launch support. Fixtures: TestPlan14GenerationWorkerStartBoundary, TestPlan14GenerationWorkerRealBackendRefusal, TestPlan14GenerationWorkerReplacementBoundary, TestPlan14GenerationWorkerSupervisionBoundary. Pre-flight 29 and E39 worker launch audit. whole:Route@11828,whole:Route@13334,whole:Route@360,whole:Route@4164,whole:Route@6157,whole:Route@6338 706f2a4268a06dae2eca22093f841750139c5bd9dcaa0e91657621c5eea1bbe9
internal/activator/workerbuild.go Service.startWorker authority E2 E39: close the unsupported claimed worker path before backend entry, replacement or supervised stop. Claims-off paths remain unchanged. This is refusal coverage, not positive root-reserved worker launch support. Fixtures: TestPlan14GenerationWorkerStartBoundary, TestPlan14GenerationWorkerRealBackendRefusal, TestPlan14GenerationWorkerReplacementBoundary, TestPlan14GenerationWorkerSupervisionBoundary. Pre-flight 29 and E39 worker launch audit. registry:Host@1201,registry:Host@1379,registry:Host@2314,whole:Route@2420,whole:Route@931 be27c7d049a6009a8a22798569e457f5126f14d5e7c56aafb5b1bd260717f6f5
internal/activator/workerswap.go Service.ReplaceWorker authority E2 E39: close the unsupported claimed worker path before backend entry, replacement or supervised stop. Claims-off paths remain unchanged. This is refusal coverage, not positive root-reserved worker launch support. Fixtures: TestPlan14GenerationWorkerStartBoundary, TestPlan14GenerationWorkerRealBackendRefusal, TestPlan14GenerationWorkerReplacementBoundary, TestPlan14GenerationWorkerSupervisionBoundary. Pre-flight 29 and E39 worker launch audit. whole:Route@2163 2666d54bdc876904d9c2c3d6de5c2f82dc688f07082d92d84ddfc7e01cda6a7e
@@ -373,3 +373,6 @@ internal/host/rootlaunchverifier.go rootLaunchVerifier.verify authority E2 PROPO
internal/rootparticipation/production.go Ledger.ActivationReady authority E2 PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. 99a3a8f61a8b769da5bce734d6d8a937b861ed38ef1e4f9cc1eb2b4d473286fb
@coverage internal/hostfloor 37459f6d8d47ed5ec3892e22483e34abde2dabc0775cf0846b6fe00fb66dcd75
@coverage internal/rootparticipation 24ff9d273269e9be6c54794c23f1183ce0535c253e714158e34a9bca9e3fdd81
+internal/host/claimretire_transport.go ClaimRootHTTP.retire authority C,G,J1 PROPOSED E44: root timer intention must precede materialization; exact registration and row identity required; root retirement cannot infer ownership from registry enumeration. Tests: TestPlan14TimerIntentionBeforeMaterialization, TestPlan14GenerationTimerRetirement; root timer intention, sync-failure retry and retirement acceptance. Scheduler producer wiring remains incomplete. registry:ClaimOwnership@1081,registry:Host@1062,registry:Host@792,whole:ClaimOwnership@1085,whole:ClaimOwnership@894,whole:Route@1830,whole:Route@854 425059d65adaafa5680ad92974559fbeb56ba0a3a7ff3867bb63937648989e06
+internal/host/rootinventory_timer.go ClaimRootHTTP.verifyInventoryTimer authority C,J1 PROPOSED E44: root timer intention must precede materialization; exact registration and row identity required; root retirement cannot infer ownership from registry enumeration. Tests: TestPlan14TimerIntentionBeforeMaterialization, TestPlan14GenerationTimerRetirement; root timer intention, sync-failure retry and retirement acceptance. Scheduler producer wiring remains incomplete. registry:ClaimIncarnation@943,registry:ClaimIncarnation@969,registry:ClaimOwner@1035,registry:ClaimOwner@299,registry:ClaimRowID@1070,registry:ClaimStamp@1015,registry:ClaimStamp@995,registry:Host@899,registry:Name@921,registry:Registration@1107,whole:Timer@709 f54f9aeaf48b8a258b9102d630719552a7e3ff495a53ab53123b79420723365f
+internal/host/rootinventory_timer.go ClaimRootHTTP.verifyNewTimerIntentions authority E2 PROPOSED E44: root timer intention must precede materialization; exact registration and row identity required; root retirement cannot infer ownership from registry enumeration. Tests: TestPlan14TimerIntentionBeforeMaterialization, TestPlan14GenerationTimerRetirement; root timer intention, sync-failure retry and retirement acceptance. Scheduler producer wiring remains incomplete. whole:Timer@609 179376ffb25fdc973418cfeae238cf1c69e3ec95495d6ac145ac6fa660f8a1c0