plan: claim Plan 6b rev 5, review round 4 (adopt-helper test, production census paths), refreshed onto 3407d114
Co-Authored-By: Codex <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 file changed,
+360
−60
docs/superpowers/plans/2026-10-06-host-lifecycle-claim-6b-box-assembly.md+360 −60
CI passed — run details
diff --git a/docs/superpowers/plans/2026-10-06-host-lifecycle-claim-6b-box-assembly.md b/docs/superpowers/plans/2026-10-06-host-lifecycle-claim-6b-box-assembly.md| index c14925356..a57e6d163 100644 |
| --- a/docs/superpowers/plans/2026-10-06-host-lifecycle-claim-6b-box-assembly.md |
| +++ b/docs/superpowers/plans/2026-10-06-host-lifecycle-claim-6b-box-assembly.md |
| @@ -1,6 +1,6 @@ |
| # Host lifecycle claim, Plan 6b: production box assembly |
| -Implementation plan, 2026-10-07, rev 4. Build on `feat/hostclaim-box-assembly`, cut from `origin/main` at **1da67e63e481c3a7b1768c4309c42c20dea3c254**. Plan 6 landed as `d649d8d4`; this base includes `84c80eba`, region HA plan 1b and the subsequent main changes, including temporary static sites at `f0606ae1`. This document supplies the next implementation steps. Authoring it stages nothing, commits nothing and creates no implementation branch. |
| +Implementation plan, 2026-10-07, rev 5. Build on `feat/hostclaim-box-assembly`, cut from `origin/main` at **3407d114f8c8a413214b88295457263bea50dcb7**. Plan 6 landed as `d649d8d4`; this base includes `84c80eba`, region HA plan 1b and the subsequent main changes, including temporary static sites at `f0606ae1`, GeoIP, release-proof changes and the host-claim floor at `cbc0922e` and console/backfill work at `3407d114`. This document supplies the next implementation steps. Authoring it stages nothing, commits nothing and creates no implementation branch. |
| Rev 2 answers all four round-1 findings: P2-1 moves inbound, config-gate and co-spawn refusals before admission effects (Task 4); P2-2 retains the persisted attempt's boot/controller tuple and routes restart recovery through root's dead-controller proof (Task 7); P2-3 journals recoverable pre-launch state before restore or materialization (Tasks 3, 5 and 7); P2-4 rebases every block onto current main and retains `boxid.Verifier.Enroll` and main's region HA readers (Tasks 1 and 6). Task 2 supplies regression tests; the authoring verification records the executed failures and gates. |
| @@ -8,6 +8,8 @@ Rev 3 answers the three round-2 findings. P2-1 preserves main's flag-off adoptio |
| Rev 4 answers round 3. P1 removes the agent-side tenant execution fallback and refuses every current enabled launch shape before admission (Tasks 5 and 7). Main's nested exec path uses root-owned isolated systemd units, and that protocol cannot yet carry a durable per-role reservation. Flat, foreign, systemd, seed, standalone-sidecar and co-spawn paths also refuse. This revision therefore supports no enabled tenant launch; adding a reservation-bearing root protocol is an explicit activation gate. The invariant is that claim guarding never weakens main's tenant isolation: tenant code cannot execute in the agent's mount namespace or obtain writable cgroup or journal authority. P2 authenticates the current requester separately from the exact completed predecessor receipt, retaining its recorded boot, controller and directory revision (Task 7). The invariant is that cleanup authority follows the durable predecessor, while authentication follows the live requester. The final replay refreshes the console patches onto `1da67e63`, preserving the temporary-sites additions that landed during verification. Task 2 adds refusal regressions, restart/reboot handover through the next wake's admission and restore, and an opt-in kernel isolation witness. |
| +Rev 5 answers both round-4 findings. The adoption ownership test follows `RunAdopt` through both implementation helpers and still requires ownership-converging registry opens; it is included in the focused gate. The production census classifies `hostguard`, the real guard-record directory. A real reconciler installation uses the production shared-path list, creates the guard record, persists route ownership and inventory, and repeats without changing ownership. An unknown data file still refuses before activation; a durable fence refuses installation without changing the record or creating route ownership or inventory. All blocks are refreshed onto the current main base above, preserving its GeoIP, release-proof, host-claim-floor and console/backfill changes. The instance loop retains main's claim backfill and passes the provisioning configurator alongside its write policy. Wake-retry reset joins main's existing host-resolution dispatcher with its operator and signed-floor checks; declare-lost and readmit remain intact. Authoring verification now runs the full `internal/host` and `internal/hostbox` packages. |
| + |
| > For agentic workers: apply the tasks in order, tests before implementation. Keep `CARLOS_HOST_CLAIM` unset. Do not execute branch creation or deployment commands in the design worktree. The code blocks below are complete new files or exact patches against the pinned main base. Use the extractor below to apply patches. Missing authority refuses the dependent operation before an effect. |
| The work covers review findings 1 through 4 in `.superpowers/sdd/2026-10-05-host-lifecycle-claim-6-box-wiring/review-610.md` from the `box` worktree: startup composition, durable wake retries, reconcile admission and retirement, and root evidence and role launches. It uses claim **rev 6.30**, especially section 1.1 and the ruled P6-D1 through P6-D8 in section 6.6. Plan 6 rev 4 remains implementation history; its landed code is already on main. Do not replay it. |
| @@ -55,7 +57,7 @@ git merge-base --is-ancestor 84c80eba origin/main |
| git switch -c feat/hostclaim-box-assembly origin/main |
| ``` |
| -Expected: a clean implementation checkout and the new assembly branch from the latest fetched main. These blocks were verified at `1da67e63e481c3a7b1768c4309c42c20dea3c254`. If main has moved, compare it with that base and refresh the patches before applying them. Preserve main's `boxid.Verifier.Enroll`, `termfence`, `systemparticipation.go`, `boxrecord`, `edgesclaims` and `boxvolumes`; do not replay region HA plan 1b or reintroduce its superseded readers. Preserve the existing flag-off and hostbox fixes, including main's nested-default layout and startup layout recovery. Keep the main isolated-exec design and its console layout door (`carlos system layout`) intact. These blocks support no enabled launch shape until the root protocol carries and validates the reservation. |
| +Expected: a clean implementation checkout and the new assembly branch from the latest fetched main. These blocks were verified at `3407d114f8c8a413214b88295457263bea50dcb7`. If main has moved, compare it with that base and refresh the patches before applying them. Preserve main's `boxid.Verifier.Enroll`, `termfence`, `systemparticipation.go`, `boxrecord`, `edgesclaims` and `boxvolumes`; do not replay region HA plan 1b or reintroduce its superseded readers. Preserve the existing flag-off and hostbox fixes, including main's nested-default layout and startup layout recovery. Keep the main isolated-exec design and its console layout door (`carlos system layout`) intact. These blocks support no enabled launch shape until the root protocol carries and validates the reservation. |
| Read these exact local sources before changing code: |
| @@ -109,7 +111,7 @@ for kind, name, body in blocks: |
| ```bash |
| python3 "$TMPDIR/claim6b-apply.py" docs/superpowers/plans/2026-10-06-host-lifecycle-claim-6b-box-assembly.md tests |
| -nice -n 10 go test -p 1 ./internal/cgroup ./internal/hostguard ./internal/registry ./internal/hostbox ./internal/activator ./internal/host ./internal/provision ./internal/console ./internal/instancectl ./internal/instanceexec ./internal/schedule -run '^Test(Claim|ServingRetries|Renewing|AggregateContext|Identity|AdapterSelectionLayout|Launch|Prelaunch|PreLaunch|Recover|EmptyCensus|RegistryRows|ColdHandover|HostWakeReset|RootRetirement|BeginFence|Cleanup|Fence|StartAdmitted|SameAdmission|SetupAt|RunAdoptDisabledClaims)' -count=1 |
| +nice -n 10 go test -p 1 ./internal/cgroup ./internal/hostguard ./internal/registry ./internal/hostbox ./internal/activator ./internal/host ./internal/provision ./internal/console ./internal/instancectl ./internal/instanceexec ./internal/schedule -run '^Test(Claim|ServingRetries|Renewing|AggregateContext|Identity|AdapterSelectionLayout|Launch|Prelaunch|PreLaunch|Recover|EmptyCensus|RegistryRows|ColdHandover|HostWakeReset|RootRetirement|BeginFence|Cleanup|Fence|StartAdmitted|SameAdmission|SetupAt|RunAdoptDisabledClaims|RunAdoptOpensTheRegistryThroughTheHelper)' -count=1 |
| ``` |
| Expected red: missing assembly, retry, journal, registry-ownership, delivery-admission and handover APIs. These tests must be present before the implementations below. No passing test that used an unavailable cgroup delegation counts as a kernel witness. |
| @@ -443,13 +445,16 @@ package console |
| import ( |
| "bytes" |
| "context" |
| + "crypto/ed25519" |
| "net/http" |
| "net/http/httptest" |
| "reflect" |
| + "strings" |
| "testing" |
| "time" |
| "github.com/carlosframework/platform/internal/hostclaim" |
| + "github.com/carlosframework/platform/internal/hostfloor" |
| "github.com/carlosframework/platform/internal/hostwrite" |
| "github.com/carlosframework/platform/internal/release" |
| "github.com/carlosframework/platform/internal/svcrole" |
| @@ -480,8 +485,18 @@ func TestHostWakeResetMountedOperatorGates(t *testing.T) { |
| if err != nil { |
| t.Fatal(err) |
| } |
| - s := &Server{store: st, mux: http.NewServeMux(), cfg: Config{HostClaim: true}} |
| - s.cliRoute("POST /api/cli/ops/hosts/{host}/resolve", svcrole.Set{}, s.handleAPIHostResolve) |
| + _, key, err := ed25519.GenerateKey(nil) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + store := release.NewDirStore(t.TempDir()) |
| + if err := hostfloor.Publish(t.Context(), store, release.RootSigner(key), "bucket", hostfloor.Floor{Min: 1, MinRelease: "v1.0.0"}); err != nil { |
| + t.Fatal(err) |
| + } |
| + s, err := NewServer(Config{Origin: "https://console.example", Keymail: "https://keymail.example", HostClaim: true, Key: key, Bucket: "bucket", Releases: store}, st) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| url := "/api/cli/ops/hosts/shop.example.test/resolve" |
| calls := 0 |
| s.cfg.WakeRetryReset = &WakeRetryResetDoor{Claims: release.NewDirStore(t.TempDir()), Authority: func(context.Context) error { calls++; return hostwrite.ErrUnavailable }, StopAttempt: func(context.Context, hostclaim.Observed, hostclaim.WakeRetryAttempt) error { return nil }, NewID: func() string { return "new" }} |
| @@ -503,6 +518,27 @@ func TestHostWakeResetMountedOperatorGates(t *testing.T) { |
| if status, _ := resetRequest(t, s, url, token, `{"action":"reset-wake-retry","incarnation":"i","episode":"e","request_id":"r"}`); status != 409 || calls != 1 { |
| t.Fatalf("operator status=%d calls=%d", status, calls) |
| } |
| + for _, bad := range []string{ |
| + `{"action":"reset-wake-retry","unknown":true}`, |
| + `{"action":"reset-wake-retry"} {}`, |
| + `{"action":"reset-wake-retry","request_id":"` + strings.Repeat("x", 16<<10) + `"}`, |
| + } { |
| + if status, _ := resetRequest(t, s, url, token, bad); status != 400 || calls != 1 { |
| + t.Fatalf("malformed reset status=%d calls=%d", status, calls) |
| + } |
| + } |
| + // Other resolution actions still use main's dispatcher and authority. |
| + for _, action := range []string{"declare-lost", "readmit"} { |
| + if status, _ := resetRequest(t, s, url, token, `{"action":"`+action+`"}`); status != 409 || calls != 1 { |
| + t.Fatalf("%s reached wake reset: status=%d calls=%d", action, status, calls) |
| + } |
| + } |
| + savedStore := s.cfg.Releases |
| + s.cfg.Releases = release.NewDirStore(t.TempDir()) |
| + if status, _ := resetRequest(t, s, url, token, `{"action":"reset-wake-retry"}`); status != 503 || calls != 1 { |
| + t.Fatalf("missing floor reached reset: status=%d calls=%d", status, calls) |
| + } |
| + s.cfg.Releases = savedStore |
| s.cfg.HostClaim = false |
| if status, _ := resetRequest(t, s, url, token, "{}"); status != 404 || calls != 1 { |
| t.Fatalf("disabled status=%d calls=%d", status, calls) |
| @@ -5596,6 +5632,254 @@ func TestClaimWakeRefusesEveryUnisolatedShapeBeforeAdmission(t *testing.T) { |
| - } |
| ``` |
| +#### Patch: `internal/host/registryowner_test.go` |
| + |
| +```diff |
| +--- a/internal/host/registryowner_test.go |
| ++++ b/internal/host/registryowner_test.go |
| +@@ -302 +302,3 @@ |
| +-// invariant does. |
| ++// invariant does. It follows the chain RunAdopt -> runAdopt -> |
| ++// runAdoptWithEnsureSystemUnits, so a wrapper that stops delegating, or a |
| ++// direct open in any of the three, fails here. |
| +@@ -304,19 +306,35 @@ |
| +- src, err := os.ReadFile("agent.go") |
| +- if err != nil { |
| +- t.Fatal(err) |
| +- } |
| +- i := strings.Index(string(src), "\nfunc RunAdopt(") |
| +- if i < 0 { |
| +- t.Fatal("RunAdopt is no longer in agent.go \u2014 move this test with it") |
| +- } |
| +- rest := string(src)[i+1:] |
| +- j := strings.Index(rest, "\n}\n") |
| +- if j < 0 { |
| +- t.Fatal("could not find the end of RunAdopt") |
| +- } |
| +- body := rest[:j] |
| +- if !strings.Contains(body, "openServiceRegistry(") { |
| +- t.Error("RunAdopt no longer opens the registry through openServiceRegistry \u2014 the root pass has stopped converging ownership") |
| +- } |
| +- if strings.Contains(body, "registry.Open(") { |
| +- t.Error("RunAdopt opens the registry directly; use openServiceRegistry so the chown cannot be lost ahead of the -restart-only early return") |
| ++ raw, err := os.ReadFile("agent.go") |
| ++ if err != nil { |
| ++ t.Fatal(err) |
| ++ } |
| ++ src := string(raw) |
| ++ body := func(name string) string { |
| ++ i := strings.Index(src, "\nfunc "+name+"(") |
| ++ if i < 0 { |
| ++ t.Fatalf("%s is no longer in agent.go, move this test with it", name) |
| ++ } |
| ++ rest := src[i+1:] |
| ++ end := len(rest) |
| ++ // A one-line wrapper has no closing "\n}\n" of its own, so the next |
| ++ // top-level func also ends the body. |
| ++ for _, m := range []string{"\n}\n", "\nfunc "} { |
| ++ if j := strings.Index(rest[1:], m); j >= 0 && j+1 < end { |
| ++ end = j + 1 |
| ++ } |
| ++ } |
| ++ return rest[:end] |
| ++ } |
| ++ |
| ++ chain := []struct{ name, calls string }{ |
| ++ {"RunAdopt", "runAdopt("}, |
| ++ {"runAdopt", "runAdoptWithEnsureSystemUnits("}, |
| ++ {"runAdoptWithEnsureSystemUnits", "openServiceRegistry("}, |
| ++ } |
| ++ for _, c := range chain { |
| ++ b := body(c.name) |
| ++ if !strings.Contains(b, c.calls) { |
| ++ t.Errorf("%s no longer calls %s the root pass has stopped converging ownership", c.name, c.calls) |
| ++ } |
| ++ if strings.Contains(b, "registry.Open(") { |
| ++ t.Errorf("%s opens the registry directly; use openServiceRegistry so the chown cannot be lost ahead of the -restart-only early return", c.name) |
| ++ } |
| +``` |
| + |
| +#### New file: `internal/host/claimcensus_test.go` |
| + |
| +```go |
| +//go:build unix |
| + |
| +package host |
| + |
| +import ( |
| + "bytes" |
| + "context" |
| + "database/sql" |
| + "errors" |
| + "os" |
| + "path/filepath" |
| + "testing" |
| + "time" |
| + |
| + "github.com/carlosframework/platform/internal/hostbox" |
| + "github.com/carlosframework/platform/internal/hostclaim" |
| + "github.com/carlosframework/platform/internal/hostguard" |
| + "github.com/carlosframework/platform/internal/hostwrite" |
| + "github.com/carlosframework/platform/internal/park" |
| + "github.com/carlosframework/platform/internal/provision" |
| + "github.com/carlosframework/platform/internal/registry" |
| + "github.com/carlosframework/platform/internal/release" |
| +) |
| + |
| +func TestClaimProductionCensusInstallsThroughReconciler(t *testing.T) { |
| + for _, mode := range []string{"empty", "fenced"} { |
| + t.Run(mode, func(t *testing.T) { |
| + testClaimProductionCensusInstallation(t, mode == "fenced") |
| + }) |
| + } |
| +} |
| + |
| +func testClaimProductionCensusInstallation(t *testing.T, fenced bool) { |
| + ctx := t.Context() |
| + r := ClaimRuntimeResources{DataDir: t.TempDir(), OptDir: t.TempDir(), UnitsDir: t.TempDir()} |
| + for _, name := range []string{"hostguard", ".hostclaims"} { |
| + if err := os.Mkdir(filepath.Join(r.DataDir, name), 0700); err != nil { |
| + t.Fatal(err) |
| + } |
| + } |
| + reg, err := registry.Open(filepath.Join(r.DataDir, "registry.db")) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + defer reg.Close() |
| + r.Registry = reg |
| + inventory, err := hostbox.OpenStopInventoryDB(filepath.Join(r.DataDir, ".hostclaims", "inventory.db")) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + defer inventory.Close() |
| + launches, err := hostbox.OpenLaunchJournal(filepath.Join(r.DataDir, ".hostclaims", "launches.db")) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + defer launches.Close() |
| + store := release.NewDirStore(t.TempDir()) |
| + created := time.Date(2026, 10, 7, 0, 0, 0, 0, time.UTC) |
| + record := provision.Record{Schema: 1, Host: "shop.example", Slug: "shop", App: "app", Account: "acct", Region: "ie", Channel: "stable", CreatedAt: created} |
| + if err := provision.CreateRecord(ctx, store, record); err != nil { |
| + t.Fatal(err) |
| + } |
| + key := provision.RecordKey(record.Account, record.App, record.Host) |
| + obs, err := hostclaim.Read(ctx, store, record.Host) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + obs, err = hostclaim.Update(ctx, store, obs, hostclaim.Adopt(hostclaim.NewHolder{Family: "instance", Key: key, Account: record.Account, App: record.App, Host: record.Host, Region: record.Region, CreatedAt: created}, "box", nil, nil, "adopt", func() string { return "id" })) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if fenced { |
| + obs, err = hostclaim.Update(ctx, store, obs, hostclaim.AdmitWriter(hostclaim.Admission{Box: "box", Region: "ie", Mode: "cold", NewGeneration: "generation"})) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + } |
| + guard := hostguard.Guard{DataDir: r.DataDir, LockDir: t.TempDir(), Claims: hostguard.StoreReader{Store: store}} |
| + source := hostbox.WorkSource{Guard: guard, Box: "box", Region: "ie", Now: time.Now, |
| + Policy: func(_ context.Context, c *hostclaim.Claim, s hostbox.Selection, _ hostwrite.Identity) error { |
| + return hostbox.Serving(c, s) |
| + }, |
| + Local: func(context.Context, *hostclaim.Claim, hostbox.Selection) error { return nil }, |
| + } |
| + census := &hostbox.EmptyInstallationCensus{Inventory: inventory, Registry: reg, Launches: launches, Box: "box", Enrollment: "enrollment", DirectoryRevision: "1", |
| + Roots: map[string]string{"data": r.DataDir, "opt": r.OptDir, "units": r.UnitsDir}, SharedPaths: claimGlobalSharedPaths(r), |
| + } |
| + adapter := hostbox.ReconcileAdapter{Registry: reg, Inventory: inventory, Census: census, Box: "box", Enrollment: "enrollment", DirectoryRevision: "1", |
| + Reconciler: hostbox.Reconciler{Claims: store, Guard: guard, Local: park.Local{Guard: guard, Claims: store, DataDir: r.DataDir, Box: "box"}, Writes: hostwrite.Config{Enabled: true, Selector: source}, Box: "box", Region: "ie", Now: time.Now, CheckLocal: census.CheckLocal}, |
| + } |
| + selected := provision.ClaimRecord{Host: record.Host, Key: key, Family: "instance", Account: record.Account, App: record.App, CreatedAt: created, Value: record, Route: registry.Route{Host: record.Host, App: record.App, Account: record.Account, Kind: "instance", Channel: "stable"}} |
| + guardPath := filepath.Join(r.DataDir, "hostguard", record.Host+".json") |
| + if _, err := os.Stat(guardPath); !errors.Is(err, os.ErrNotExist) { |
| + t.Fatalf("guard was active before installation: %v", err) |
| + } |
| + if fenced { |
| + if err := guard.Activate(ctx, record.Host, "box", nil); err != nil { |
| + t.Fatal(err) |
| + } |
| + h := obs.Claim.Holder |
| + target := hostguard.FenceTarget{FenceID: "fence", Host: record.Host, Incarnation: h.Incarnation, Stamp: h.Stamp, Session: h.Session, Box: "box", Region: "ie", Enrollment: "enrollment", BootID: "boot", ControllerID: "controller", ClaimETag: obs.ETag, DirectoryRevision: "1"} |
| + if _, err := guard.BeginFence(ctx, target); err != nil { |
| + t.Fatal(err) |
| + } |
| + before, err := os.ReadFile(guardPath) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := adapter.Install(ctx, selected, func(registry.ClaimOwnership) error { t.Fatal("fenced installation wrote a route"); return nil }); !errors.Is(err, hostguard.ErrFenced) { |
| + t.Fatalf("fenced installation: %v", err) |
| + } |
| + after, err := os.ReadFile(guardPath) |
| + if err != nil || !bytes.Equal(before, after) { |
| + t.Fatalf("installation changed the durable fence: %v", err) |
| + } |
| + if row, err := reg.Get(record.Host); err != nil || row != nil { |
| + t.Fatalf("fenced installation wrote a row: %+v %v", row, err) |
| + } |
| + if _, err := inventory.ReadInstallation(ctx, record.Host, h.Incarnation, h.Stamp); !errors.Is(err, sql.ErrNoRows) { |
| + t.Fatalf("fenced installation wrote inventory: %v", err) |
| + } |
| + return |
| + } |
| + // Unknown files still refuse before activation; shared metadata does not |
| + // make arbitrary data files eligible for a new ownership stamp. |
| + unknown := filepath.Join(r.DataDir, "unclassified.db") |
| + if err := os.WriteFile(unknown, []byte("old"), 0600); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := adapter.Install(ctx, selected, func(registry.ClaimOwnership) error { t.Fatal("unknown file admitted"); return nil }); !errors.Is(err, hostbox.ErrTypedInventory) { |
| + t.Fatalf("unknown file refusal: %v", err) |
| + } |
| + if _, err := os.Stat(guardPath); !errors.Is(err, os.ErrNotExist) { |
| + t.Fatalf("refused installation activated guard: %v", err) |
| + } |
| + if row, err := reg.Get(record.Host); err != nil || row != nil { |
| + t.Fatalf("refused installation wrote a row: %+v %v", row, err) |
| + } |
| + if _, err := inventory.ReadInstallation(ctx, record.Host, obs.Claim.Holder.Incarnation, obs.Claim.Holder.Stamp); !errors.Is(err, sql.ErrNoRows) { |
| + t.Fatalf("refused installation wrote inventory: %v", err) |
| + } |
| + if err := os.Remove(unknown); err != nil { |
| + t.Fatal(err) |
| + } |
| + writes := 0 |
| + install := func(owner registry.ClaimOwnership) error { |
| + writes++ |
| + if !owner.Valid() { |
| + t.Fatal("installation has no ownership") |
| + } |
| + if _, err := os.Stat(guardPath); err != nil { |
| + t.Fatalf("installation did not activate the real guard record: %v", err) |
| + } |
| + route := selected.Route |
| + route.ClaimOwnership = owner |
| + return reg.Upsert(route) |
| + } |
| + if err := adapter.Install(ctx, selected, install); err != nil { |
| + t.Fatalf("first installation with production shared paths: %v", err) |
| + } |
| + row, err := reg.Get(record.Host) |
| + if err != nil || row == nil || !row.ClaimOwnership.Valid() || writes != 1 { |
| + t.Fatalf("installed row=%+v writes=%d err=%v", row, writes, err) |
| + } |
| + in, err := inventory.ReadInstallation(ctx, record.Host, obs.Claim.Holder.Incarnation, obs.Claim.Holder.Stamp) |
| + if err != nil || !in.Complete || len(in.Entries) != 1 || in.Entries[0].Kind != "route" || in.Entries[0].Materialization != "materialized" || in.Entries[0].Ownership != row.ClaimOwner { |
| + t.Fatalf("installation inventory=%+v err=%v", in, err) |
| + } |
| + if err := adapter.Install(ctx, selected, install); err != nil { |
| + t.Fatalf("repeat installation: %v", err) |
| + } |
| + again, err := reg.Get(record.Host) |
| + if err != nil || again == nil || again.ClaimOwnership != row.ClaimOwnership || writes != 2 { |
| + t.Fatalf("repeat changed ownership: row=%+v writes=%d err=%v", again, writes, err) |
| + } |
| +} |
| +``` |
| + |
| ## Task 3: persist containment, inventory and guard recovery |
| Persist reservations before spawn and intentions before materialization. Record the admitted session's boot ID, controller ID, PID, process-start identity and directory revision with its empty parent before restore. Append executable/state file identities and role/producer reservations to that same record when they become available. Store controller PID and kernel process-start identity with each reservation. A complete inventory is an explicit validated record. Recovery never turns missing inventory into proof of emptiness. The guard's durable fence remains authoritative after agent restart, root restart and reboot. Cold handover adds a successor barrier before parking predecessor artifacts; only the exact completed handover can clear it. |
| @@ -7302,8 +7586,9 @@ type EmptyInstallationCensus struct { |
| Launches *LaunchJournal |
| Box, Enrollment, DirectoryRevision string |
| Roots map[string]string |
| - // Exact shared subtrees contain no host-specific state. The factory owns |
| - // this closed list: release archives, shared unit templates and guard metadata. |
| + // The factory owns this closed list of shared metadata and archives. |
| + // Hostguard checks its own records and fences; excluding that directory |
| + // here never assigns its contents to an installation artifact inventory. |
| SharedPaths map[string][]string |
| } |
| @@ -9000,7 +9285,7 @@ The reservation primitive below remains preparatory code for a later root integr |
| Read-only prerequisite failures spend no attempt. Reserve before writer admission, bind the new session before any spawn, and settle every failure through the exact root stop target. A failed admission also releases its held lease; it does not refund retry authority. Recovery uses the persisted attempt, including its boot and controller identity, and refuses while its former controller can still run. Persist the exact pre-launch attempt and controller identity before restore or materialization. Restore failure, materialization failure and a crash between them must remain discoverable by stop and settlement. When the root integration is implemented, prepare the concrete per-role launch only after those steps complete. Pin a verified executable descriptor before child creation. Under the captured guard and pinned parent, verify the child name is absent, durably journal its intended ownership, then create it. Promote that intention to an exact-identity reservation before any future root launch is allowed to register the producer or execute tenant code. A failed mkdir or preparation leaves an intention, never permission to execute. After a durable fence and drain, or proof of controller death, root settles an absent intended child or records an empty direct child under the same parent. Populated children, unknown descendants, changed parent identities and symlinks refuse. Reboot recovery requires the old parent hierarchy to be absent. Retain every spent attempt; a new attempt uses a new child name. |
| -Mount reset-wake-retry on POST /api/cli/ops/hosts/{host}/resolve with human operator authentication, flag gating, strict bounded JSON, conditional incarnation/episode checks and durable operator-request identity. The Plan 8b host-resolution dispatcher owns the other actions. When the branches meet, put this action in that dispatcher's action switch and retain its declare-lost/readmit routes and checks; do not register a second handler for the same pattern. This reset grants eligibility only. |
| +Mount reset-wake-retry on POST /api/cli/ops/hosts/{host}/resolve with human operator authentication, flag gating, strict bounded JSON, conditional incarnation/episode checks and durable operator-request identity. Main's Plan 8b host-resolution dispatcher owns the route. Add this action after its existing operator and signed-floor checks, retaining declare-lost/readmit and their checks. Do not register a second handler for the same pattern. This reset grants eligibility only. |
| #### Patch: `internal/activator/claimwake.go` |
| @@ -9605,13 +9890,27 @@ func (d ServingRetries) Wrap(base *ClaimRuntime) (*ClaimRuntime, error) { |
| } |
| ``` |
| -#### Patch: `internal/console/authcli.go` |
| +#### Patch: `internal/console/apihostclaim.go` |
| ```diff |
| ---- a/internal/console/authcli.go |
| -+++ b/internal/console/authcli.go |
| -@@ -500,0 +501 @@ |
| -+ s.cliRoute("POST /api/cli/ops/hosts/{host}/resolve", svcrole.Set{}, s.handleAPIHostResolve) |
| +--- a/internal/console/apihostclaim.go |
| ++++ b/internal/console/apihostclaim.go |
| +@@ -9,0 +10 @@ |
| ++ "io" |
| +@@ -87 +88,3 @@ |
| +- if json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)).Decode(&in) != nil { |
| ++ var raw json.RawMessage |
| ++ decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)) |
| ++ if decoder.Decode(&raw) != nil || json.Unmarshal(raw, &in) != nil { |
| +@@ -88,0 +92,8 @@ |
| ++ return |
| ++ } |
| ++ if in.Action == "reset-wake-retry" { |
| ++ if decoder.Decode(new(any)) != io.EOF { |
| ++ w.WriteHeader(http.StatusBadRequest) |
| ++ return |
| ++ } |
| ++ s.handleHostWakeRetryReset(w, r, actor, raw) |
| ``` |
| #### New file: `internal/console/hostwakereset.go` |
| @@ -9620,6 +9919,7 @@ func (d ServingRetries) Wrap(base *ClaimRuntime) (*ClaimRuntime, error) { |
| package console |
| import ( |
| + "bytes" |
| "context" |
| "encoding/json" |
| "io" |
| @@ -9689,18 +9989,13 @@ func (d *WakeRetryResetDoor) reset(ctx context.Context, host, actor string, in w |
| return err |
| } |
| -func (s *Server) handleAPIHostResolve(w http.ResponseWriter, r *http.Request) { |
| - actor, ok := s.cliUser(w, r) |
| - if !ok { |
| - return |
| - } |
| - yes, err := s.store.IsOperator(actor) |
| - if err != nil || !yes || !s.cfg.HostClaim { |
| - http.NotFound(w, r) |
| +func (s *Server) handleHostWakeRetryReset(w http.ResponseWriter, r *http.Request, actor string, raw json.RawMessage) { |
| + if len(raw) > 16<<10 { |
| + w.WriteHeader(http.StatusBadRequest) |
| return |
| } |
| var in wakeResetRequest |
| - decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 16<<10)) |
| + decoder := json.NewDecoder(bytes.NewReader(raw)) |
| decoder.DisallowUnknownFields() |
| if decoder.Decode(&in) != nil || decoder.Decode(new(any)) != io.EOF { |
| w.WriteHeader(http.StatusBadRequest) |
| @@ -9724,7 +10019,7 @@ func (s *Server) handleAPIHostResolve(w http.ResponseWriter, r *http.Request) { |
| ```diff |
| --- a/internal/console/server.go |
| +++ b/internal/console/server.go |
| -@@ -463,4 +463,5 @@ |
| +@@ -474,4 +474,5 @@ |
| - HostClaim bool |
| - DBVersions lease.VersionedStore |
| - Origin string // the console's public origin \u2014 the OAuth client_id |
| @@ -9918,7 +10213,7 @@ Timer delivery registers after any cold wake and immediately before backend HTTP |
| @@ -220 +214 @@ |
| - claims, err := newClaimWiring(agentCtx, os.Getenv, buildClaims) |
| + claimCtx, err := claimEntryContext(agentCtx, os.Getenv, openDeployStore) |
| -@@ -248 +242,6 @@ |
| +@@ -249 +243,6 @@ |
| - cgm := cgroup.Setup(log.Printf) |
| + var cgm *cgroup.Manager |
| + if hostwrite.Enabled(claimEntryEnv(claimCtx)) { |
| @@ -9926,13 +10221,13 @@ Timer delivery registers after any cold wake and immediately before backend HTTP |
| + } else { |
| + cgm = cgroup.Setup(log.Printf) |
| + } |
| -@@ -270,0 +270,5 @@ |
| +@@ -271,0 +271,5 @@ |
| + } |
| + claimCtx = withClaimRuntimeResources(claimCtx, ClaimRuntimeResources{Mode: "agent", Registry: reg, Manager: cgm, DataDir: dd, OptDir: release.OptDir(), UnitsDir: "/etc/systemd/system"}) |
| + claims, err := newClaimWiring(claimCtx, claimEntryEnv(claimCtx), buildClaims) |
| + if err != nil { |
| + log.Fatalf("agent: host claims: %v", err) |
| -@@ -413,23 +417,27 @@ |
| +@@ -414,23 +418,27 @@ |
| - // |
| - // Then layout recovery (design §8.2, "Recovery is the journal"): |
| - // every journal a previous agent left is completed, and the host |
| @@ -9983,27 +10278,27 @@ Timer delivery registers after any cold wake and immediately before backend HTTP |
| + }, |
| + svc.Start) |
| + } |
| -@@ -541 +549 @@ |
| +@@ -542 +550 @@ |
| - go fastConvergeLoop(reg, svc, sd, claims.Writes, serialClaims) |
| + go fastConvergeLoop(reg, svc, sd, claims.Writes, serialClaims, claims.Providers.Provision) |
| -@@ -553 +561 @@ |
| -- go instancesLoop(reg) |
| -+ go instancesLoop(reg, claims.Providers.Provision) |
| -@@ -701,0 +710 @@ |
| +@@ -554 +562 @@ |
| +- go instancesLoop(reg, claims.Writes) |
| ++ go instancesLoop(reg, claims.Writes, claims.Providers.Provision) |
| +@@ -710,0 +719 @@ |
| + sd.runner.StartDelivery = w.Providers.Delivery |
| -@@ -2047 +2056 @@ |
| +@@ -2137 +2146 @@ |
| -func fastConvergeLoop(reg *registry.Registry, svc *activator.Service, sd *scheduleDeps, writes hostwrite.Config, serialNotify chan<- struct{}) { |
| +func fastConvergeLoop(reg *registry.Registry, svc *activator.Service, sd *scheduleDeps, writes hostwrite.Config, serialNotify chan<- struct{}, configure ...func(*provision.ReconcileConfig)) { |
| -@@ -2124 +2133 @@ |
| +@@ -2225 +2234 @@ |
| - if err := reconcileSerialized(ctx, store, reg, provisionTickConfig(os.Getenv)); err != nil { |
| + if err := reconcileSerialized(ctx, store, reg, configuredClaimProvision(provisionTickConfig(os.Getenv), configure)); err != nil { |
| -@@ -2674 +2683 @@ |
| --func instancesLoop(reg *registry.Registry) { |
| -+func instancesLoop(reg *registry.Registry, configure ...func(*provision.ReconcileConfig)) { |
| -@@ -2681 +2690 @@ |
| +@@ -2775 +2784 @@ |
| +-func instancesLoop(reg *registry.Registry, writes hostwrite.Config) { |
| ++func instancesLoop(reg *registry.Registry, writes hostwrite.Config, configure ...func(*provision.ReconcileConfig)) { |
| +@@ -2782 +2791 @@ |
| - cfg := provisionTickConfig(os.Getenv) |
| + cfg := configuredClaimProvision(provisionTickConfig(os.Getenv), configure) |
| -@@ -3609 +3618,9 @@ |
| +@@ -3861 +3870,9 @@ |
| - claims, err := newClaimWiring(context.Background(), os.Getenv, buildClaims) |
| + runAdoptWithEnsureSystemUnits(args, buildClaims, func() { |
| + ensureSystemUnits("/etc", filepath.Join(systemRootDir(release.OptDir()), systemLockFile), productionSysOps().systemctl, systemctlProbe, log.Printf) |
| @@ -10014,7 +10309,7 @@ Timer delivery registers after any cold wake and immediately before backend HTTP |
| +// the rest of adopt startup, including its fatal registry diagnostics, is real. |
| +func runAdoptWithEnsureSystemUnits(args []string, buildClaims ClaimFactory, ensureUnits func()) { |
| + claimCtx, err := claimEntryContext(context.Background(), os.Getenv, openDeployStore) |
| -@@ -3611,0 +3629,10 @@ |
| +@@ -3863,0 +3881,10 @@ |
| + } |
| + claimsEnabled := hostwrite.Enabled(claimEntryEnv(claimCtx)) |
| + var claims *ClaimWiring |
| @@ -10025,7 +10320,7 @@ Timer delivery registers after any cold wake and immediately before backend HTTP |
| + if err != nil { |
| + log.Fatalf("adopt: host claims: %v", err) |
| + } |
| -@@ -3619,0 +3647,24 @@ |
| +@@ -3871,0 +3899,24 @@ |
| + var reg *registry.Registry |
| + if claimsEnabled { |
| + reg, err = openServiceRegistry(*regPath) |
| @@ -10050,10 +10345,10 @@ Timer delivery registers after any cold wake and immediately before backend HTTP |
| + } |
| + }() |
| + |
| -@@ -3639 +3690 @@ |
| +@@ -3891 +3942 @@ |
| - ensureSystemUnits("/etc", filepath.Join(systemRootDir(release.OptDir()), systemLockFile), productionSysOps().systemctl, systemctlProbe, log.Printf) |
| + ensureUnits() |
| -@@ -3647,6 +3698,9 @@ |
| +@@ -3899,6 +3950,9 @@ |
| - reg, err := openServiceRegistry(*regPath) |
| - if err != nil { |
| - if hint := registryOpenHint(*regPath, err); hint != "" { |
| @@ -10677,7 +10972,7 @@ func refusingRootWake(box string, store release.Store) *activator.ClaimRuntime { |
| } |
| func claimGlobalSharedPaths(r ClaimRuntimeResources) map[string][]string { |
| - shared := map[string][]string{"data": {".hostclaims", ".hostdir", ".hostguard", "registry.db", "registry.db-wal", "registry.db-shm"}, "opt": {"system", "releases"}, "units": {"carlos-edge.service", "carlos-adopt.service", "carlos-adopt.timer", "carlos-instance-control.service", "carlos-instance-control.socket", "carlos-stage.service", "carlos-stage.path", "carlos-restart.service", "carlos-restart.path", "carlos-config-sync.service", "carlos-system-update.service", "carlos-system-update.path", "carlos-system-recover.service"}} |
| + shared := map[string][]string{"data": {".hostclaims", ".hostdir", "hostguard", "registry.db", "registry.db-wal", "registry.db-shm"}, "opt": {"system", "releases"}, "units": {"carlos-edge.service", "carlos-adopt.service", "carlos-adopt.timer", "carlos-instance-control.service", "carlos-instance-control.socket", "carlos-stage.service", "carlos-stage.path", "carlos-restart.service", "carlos-restart.path", "carlos-config-sync.service", "carlos-system-update.service", "carlos-system-update.path", "carlos-system-recover.service"}} |
| return shared |
| } |
| ``` |
| @@ -14047,19 +14342,19 @@ func RunWithClaim(args []string, handler http.Handler) { runWithClaim(args, hand |
| ```diff |
| --- a/internal/instancectl/server.go |
| +++ b/internal/instancectl/server.go |
| -@@ -33,0 +34 @@ |
| +@@ -34,0 +35 @@ |
| + ClaimStop http.Handler |
| -@@ -95 +96,3 @@ |
| +@@ -100 +101,3 @@ |
| -func Run(args []string) { |
| +func Run(args []string) { runWithClaim(args, nil) } |
| + |
| +func runWithClaim(args []string, claim http.Handler) { |
| -@@ -102,0 +106 @@ |
| +@@ -107,0 +111 @@ |
| + ClaimStop: claim, |
| -@@ -151 +155 @@ |
| +@@ -157 +161 @@ |
| - srv := &http.Server{Handler: s.Handler(), ReadHeaderTimeout: 5 * time.Second} |
| + srv := &http.Server{Handler: s.Handler(), ReadHeaderTimeout: 5 * time.Second, ConnContext: requestConnContext} |
| -@@ -225,5 +229,10 @@ |
| +@@ -231,5 +235,10 @@ |
| - mux.HandleFunc("POST /start", s.handleStart) |
| - mux.HandleFunc("POST /seed", s.handleSeed) |
| - mux.HandleFunc("POST /stop", s.handleStop) |
| @@ -14075,7 +14370,7 @@ func RunWithClaim(args []string, handler http.Handler) { runWithClaim(args, hand |
| + mux.HandleFunc("POST /stop", s.claimAwareLegacy(s.handleStop)) |
| + mux.HandleFunc("POST /reset", s.claimAwareLegacy(s.handleReset)) |
| + mux.HandleFunc("POST /sweep", s.claimAwareLegacy(s.handleSweep)) |
| -@@ -763,0 +773,10 @@ |
| +@@ -778,0 +788,10 @@ |
| + |
| +func (s *Server) claimAwareLegacy(next http.HandlerFunc) http.HandlerFunc { |
| + return func(w http.ResponseWriter, r *http.Request) { |
| @@ -14095,7 +14390,7 @@ func RunWithClaim(args []string, handler http.Handler) { runWithClaim(args, hand |
| +++ b/main.go |
| @@ -13 +12,0 @@ |
| - "github.com/carlosframework/platform/internal/instancectl" |
| -@@ -148 +147 @@ |
| +@@ -151 +150 @@ |
| - instancectl.Run(args) |
| + host.RunInstanceControl(args) |
| ``` |
| @@ -14186,7 +14481,7 @@ Apply all implementation blocks after the tests-first failure. These tasks share |
| ```bash |
| python3 "$TMPDIR/claim6b-apply.py" docs/superpowers/plans/2026-10-06-host-lifecycle-claim-6b-box-assembly.md implementation |
| gofmt -l . |
| -nice -n 10 go test -p 1 ./internal/cgroup ./internal/hostguard ./internal/registry ./internal/hostbox ./internal/activator ./internal/host ./internal/provision ./internal/console ./internal/instancectl ./internal/instanceexec ./internal/schedule -run '^Test(Claim|ServingRetries|Renewing|AggregateContext|Identity|AdapterSelectionLayout|Launch|Prelaunch|PreLaunch|Recover|EmptyCensus|RegistryRows|ColdHandover|HostWakeReset|RootRetirement|BeginFence|Cleanup|Fence|StartAdmitted|SameAdmission|SetupAt|RunAdoptDisabledClaims)' -count=1 |
| +nice -n 10 go test -p 1 ./internal/cgroup ./internal/hostguard ./internal/registry ./internal/hostbox ./internal/activator ./internal/host ./internal/provision ./internal/console ./internal/instancectl ./internal/instanceexec ./internal/schedule -run '^Test(Claim|ServingRetries|Renewing|AggregateContext|Identity|AdapterSelectionLayout|Launch|Prelaunch|PreLaunch|Recover|EmptyCensus|RegistryRows|ColdHandover|HostWakeReset|RootRetirement|BeginFence|Cleanup|Fence|StartAdmitted|SameAdmission|SetupAt|RunAdoptDisabledClaims|RunAdoptOpensTheRegistryThroughTheHelper)' -count=1 |
| nice -n 10 go build -p 1 ./... |
| nice -n 10 go vet -p 1 ./... |
| nice -n 10 go test -p 1 ./... -count=1 |
| @@ -14195,7 +14490,7 @@ GOOS=windows GOARCH=amd64 nice -n 10 go build -p 1 ./... |
| GOOS=linux GOARCH=arm64 nice -n 10 go build -p 1 ./... |
| ``` |
| -Use the default Go build cache. For this authoring session only, the user authorized `GOCACHE=/home/paulca/.cache/tmp/claude-1001/own/gocache-6b4` if the default is read-only; remove that fallback cache when done. Put scratch under `/home/paulca/.cache/tmp/claude-1001/` and remove it after verification. Run Go gates serially with `nice -n 10` and `-p 1`. An exported source tree without Git metadata needs `-buildvcs=false`; that flag changes build stamping, not the code being checked. |
| +Use the default Go build cache. For this authoring session only, the user authorized `GOCACHE=/home/paulca/.cache/tmp/claude-1001/own/gocache-6b5` if the default is read-only; remove that fallback cache when done. Put scratch under `/home/paulca/.cache/tmp/claude-1001/` and remove it after verification. Run Go gates serially with `nice -n 10` and `-p 1`. An exported source tree without Git metadata needs `-buildvcs=false`; that flag changes build stamping, not the code being checked. |
| Review the resulting diff against the parent, including actual callers. The repository requires authority/production review by Opus before implementation lands. Plan authorship and local compilation are not that review. Land the eventual implementation only through the reviewed amadan branch and its green exact-tip gate. This document does not authorize a production rollout or infrastructure change. |
| @@ -14217,6 +14512,12 @@ Do not substitute passing filesystem fixtures for these kernel witnesses. No cur |
| ## Self-review |
| +Rev 5 integration check: |
| + |
| +- The ownership regression follows the adoption wrappers to the implementation, requires `openServiceRegistry`, and refuses direct `registry.Open` in every function in that chain. Both focused commands include it. |
| +- The census uses the production shared-path list with actual guard records and on-disk registry, inventory and launch databases. A first installation succeeds, a repeat preserves ownership, and unknown data or a durable fence refuses without creating a row or inventory. Guard metadata is excluded from artifact ownership; hostguard still enforces its fence. |
| +- Main's GeoIP, release-proof, signed-floor and console/backfill work remains in the replay. Wake-retry reset uses the existing resolution dispatcher and signed-floor gate. The instance loop keeps the backfill callback and receives the write policy and provisioning configurator together. |
| + |
| Rev 4 integration check: |
| - The direct tenant execution fallback is removed. Production preflight, `RoleLaunch.Start` and the inherited claim-spawn helper independently refuse current shapes; missing root protocol support cannot become agent-UID or agent-namespace execution. The root legacy start/seed refusals and flag-off behavior remain intact. Tenant cgroup/journal denial has an explicit kernel test, which is skipped locally and remains a deployment gate. |
| @@ -14247,18 +14548,17 @@ The reservation-bearing extension of main's isolated root launch protocol is req |
| ### Authoring verification |
| -All 120 file blocks were replayed with this document's extractor into a fresh on-disk export of `origin/main` at `1da67e63e481c3a7b1768c4309c42c20dea3c254`. Main advanced from `2b2cc879` during verification. The final replay refreshes `internal/console/authcli.go` and `internal/console/server.go` against the temporary-sites merge, preserving its changes and placing the operator reset route at registration time. The new-file blocks match the replay byte for byte. Whole-tree `gofmt -l .` and the plan's whitespace check were clean. Only this plan changed in the design worktree; no branch, index or commit was written, and Plan 5 was not touched. |
| +Rev 5: all 122 file blocks were replayed with this document's extractor into a fresh on-disk export of `origin/main` at `3407d114f8c8a413214b88295457263bea50dcb7`, the latest main observed when preparing the final run. The replayed files match the source used for verification byte for byte. Main advanced during this revision: the refreshed patches preserve GeoIP and release-proof work, the signed-floor host-resolution dispatcher from `cbc0922e`, and the console/backfill work from `3407d114`. The instance loop retains main's write policy and backfill callback alongside the provisioning configurator. Whole-tree `gofmt -l .` and the plan's whitespace check passed. Only this plan changed in the design worktree; no branch, index or commit was written, and no other plan was edited. |
| -Applying tests before implementation produced the expected missing-API compilation failures. Behavioral checks also failed against the old paths: `RoleLaunch.Start` reached initialization without an isolated root launcher, the inherited claim-spawn helper reached its old guard path, and the handover endpoint returned HTTP 400 for an exact completed predecessor after both controller restart and reboot. The corrected paths passed. The handover fixture advances to the existing retry deadline before the next attempt; it does not bypass or refund the retry policy. |
| +Tests-first replay produced the expected missing-API compilation failures. Before the round-4 fixes, `TestRunAdoptOpensTheRegistryThroughTheHelper` failed because its source inspection stopped at the wrapper. The new `TestClaimProductionCensusInstallsThroughReconciler` failed at first installation with `hostbox: invalid typed retirement inventory` while the production list still named `.hostguard`. Both pass with the corrected blocks. The census test uses the real reconciler, guard, work selector, registry and durable inventory with the production shared-path list. It verifies first installation, repeat ownership, refusal of an unknown data file before activation, and refusal of a durable fence with its bytes unchanged and no route or inventory written. Its managed roots are disposable directories, not a census of a deployed box. |
| Final gates ran serially under `nice -n 10`, with `-p 1` and `-buildvcs=false` on every Go command: |
| -- `go build ./...`, `go vet ./...` and `go test ./... -run '^$' -count=1` passed. The last command compiles every test package without executing the full suite. |
| +- Full `go test ./internal/host -count=1` and `go test ./internal/hostbox -count=1` passed, without a focused filter. |
| +- The focused command in Task 9 passed for all eleven packages. It includes the adoption ownership test, production census regression, earlier claim regressions and mounted wake-retry reset checks. The reset tests exercise the existing dispatcher, its signed-floor gate, malformed payload refusals and preservation of declare-lost/readmit dispatch. |
| +- `go build ./...`, `go vet ./...` and `go test ./... -run '^$' -count=1` passed. The last command compiles every test package; it is not full-repository test execution. |
| - Darwin/amd64, Windows/amd64 and Linux/arm64 builds passed. |
| -- The focused command in Task 9 passed for cgroup, hostguard, registry, hostbox, activator, host, provision, console, instancectl, instanceexec and schedule. It includes the new launch refusals, flag-off helper behavior and restart/reboot handover, plus the earlier admission refusals, persisted retry and retirement tuples, restore/materialization/crash recovery, role intentions, adoption ordering, enrollment, layout and operator-reset regressions. |
| - |
| -The handover test uses real durable journals, the guard, cold handover, a Unix HTTP endpoint, signed enrollment verification and the next admission/restore. Its lease, kernel and requester authentication are fixtures; it never executes tenant code. It rejects unknown requesters and forged predecessor controller, boot, revision, box and enrollment fields, preserves the old receipt bytes/time, and charges the next attempt to the current controller. The new isolation kernel test is opt-in and skipped locally. It must run as an actual isolated tenant payload against verified disposable host fixtures before it counts as a kernel witness. The existing synthetic child cleanup test remains a cgroup-primitive test and grants no claim-launch authority. |
| -The invariant review covered every integration invariant, all four round-1 corrections and all three round-2 corrections. Opus reviewed the isolation refusals, predecessor authorization, inherited helper closure and the request, scheduled, sidecar, adoption and root-service launch paths; it reported no blockers. That static plan review does not replace review and the exact-tip amadan gate for the eventual implementation. |
| +An initial host-package run used a scratch path long enough to exceed Unix socket path limits. The final run used the shorter on-disk `TMPDIR=/home/paulca/.cache/tmp` and passed without changing those tests. The default Go cache was read-only, so verification used the brief's authorized `/home/paulca/.cache/tmp/claude-1001/own/gocache-6b5`. That cache and all revision-5 verification exports, logs and scratch files were removed after the gates. |
| -No enabled tenant launch shape is implemented by these blocks. Main's isolated root protocol must carry and validate the durable role reservation before any shape can be enabled. Full-suite execution and the privileged rollout witnesses remain implementation gates. No live deployment or infrastructure change was performed. The default Go cache was read-only, so verification used the brief's authorized `/home/paulca/.cache/tmp/claude-1001/own/gocache-6b4`. That fallback cache and every scratch file created for rev 4 were removed after verification. |
| +Opus reviewed the ownership check, shared-path correction, fence regression and resolution-dispatch integration; it found no blocker in the reset door or dispatcher join. Its broader questions about enabled startup, adoption ordering and real deployment-root classification remain part of the implementation and rollout review, not claims established by these filesystem fixtures. The existing privileged and opt-in kernel witnesses remain rollout gates; a package pass does not turn a skipped witness into evidence. No enabled tenant launch shape is supplied by this plan, and the reservation-bearing isolated root protocol remains required. Full-repository test execution, authority review and the exact-tip amadan gate remain requirements before implementation lands. No live deployment or infrastructure change was performed. |