plan: claim Plan 8 rev 0 (draft), leases, lost boxes and the floor
Blocked on spec contracts D1-D7 listed in the plan; tasks 4-7 are prose until the claim spec closes them.
Co-Authored-By: Codex <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 file changed,
+407
−0
docs/superpowers/plans/2026-10-05-host-lifecycle-claim-8-leases-floor.md+407 −0
diff --git a/docs/superpowers/plans/2026-10-05-host-lifecycle-claim-8-leases-floor.md b/docs/superpowers/plans/2026-10-05-host-lifecycle-claim-8-leases-floor.md| new file mode 100644 |
| index 000000000..15b8990b4 |
| --- /dev/null |
| +++ b/docs/superpowers/plans/2026-10-05-host-lifecycle-claim-8-leases-floor.md |
| @@ -0,0 +1,407 @@ |
| +# Host Lifecycle Claim, Plan 8: execution leases, lost boxes and the protocol floor |
| + |
| +> **For agentic workers:** implement this plan task by task. Steps use checkbox (`- [ ]`) syntax. Commands below belong to the implementation session. Authoring this plan changes only this file, without staging, committing, creating a branch or changing production. |
| + |
| +**Goal:** implement the claim's execution leases and self-fence, the operator's compute-fenced declare-lost and readmission paths, and the shared claim/moves version floor at every executable activation path. Keep lifecycle callers inert until Paul completes the rollout runbook. |
| + |
| +**Architecture:** `internal/lease` supplies the conditional object transport for the spec's execution-lease record. A separate `internal/hostexec` controller owns its clock, renewal and local admission state; it never grants claim authority. Lost-box transitions use `hostclaim.Update`, exact operation observations and durable fencing evidence. The remote CLI uses the console for authorization and claim mutation and the operator's credentials for a fresh, read-only cloud check. One `internal/hostfloor` reader/checker serves console admission and root executable restoration. Plans 5 and 6 remain responsible for their lifecycle state machines and guarded callers. |
| + |
| +**Tech stack:** Go 1.26, existing AWS SDK v2 clients, `release.Store`, `release.DeleteIfMatch`, `hostclaim`, `hostguard`, `stopevidence`, `cgroup`, existing status and system-update machinery. No new cloud write integration, console EC2 grant, infrastructure apply or live fencing test. |
| + |
| +**Depends on:** Plans **1 and 3**. Cut `feat/hostclaim-leases` from `feat/hostclaim-boxguard`, then `git merge origin/main`. Integration with Plans 4–7, Plan 7b and moves is a rollout prerequisite, not permission to implement their state machines here. |
| + |
| +## Sources and actual interfaces |
| + |
| +Read before editing: |
| + |
| +- Claim design `docs/superpowers/specs/2026-10-02-host-lifecycle-claim-design.md`, **rev 6.15**: §2 (`lost`, execution authority and stamps), C1–C6, §6.1, §6.3, §6.4a's **operative** admissions rules, §7, §10–§12. Historical readings in §6.4a do not override the operative subsection. |
| +- Plans 2, 3, 4, 7 and 7b in this directory for task structure and consumer boundaries; Plan 1's roadmap and activation boundary. |
| +- `feat/hostclaim-library` at `47426cc1`, also present in the sibling `hostclaim` worktree: `Claim.Lost` is `[]string`; `Op.Intent` and `Op.Journal` are `map[string]string`; `AdmissionRecord.WritesLost` is a **single** `*WritesLost`. `Read` uses strict decoding. `Update(ctx, store, observed, transition)` clones the observation, enforces its ETag and assigns `Gen` centrally. `CompleteObligation` matches the chore's own operation and maintains discovery completion. `FreeAfterPark` preserves `Lost`. |
| +- `feat/hostclaim-boxguard` at `01ed2bd9`: `Guard.With`, `Activate` and `RetireInstallation`; `stopevidence.Collect` and `Cleanup`; `cgroup.OpenCleanup`. This branch already includes `origin/main` at the authoring baseline. `Guard.With` needs a fresh holder and active installation. It cannot be used unchanged to retire a lost installation after the host became free or changed holder. |
| +- All other source references below are **`origin/main` at `04cd2d78`**, not the older sources in this planning checkout. |
| +- The shared contract requires the companion moves spec too: `/home/paulca/amadan.net/carlos/platform-moves-design/docs/superpowers/specs/2026-10-02-move-any-app-design.md`, §7.10 and §11. It specifies `control/moves-protocol.json`, `{"min":1,"min_release":"vX.Y.Z"}`, create-only publication, and `moves_protocol: 1` reports. It also supplies the dirty-return cleanup and record-commit restrictions cited below. Do not introduce a second claim-only floor. |
| + |
| +| Existing source | What is available; what is missing | |
| +|---|---| |
| +| `internal/lease/lease.go`, `health.go`, `endpoint.go` | `Acquire` returns `*Held`; `Replicate` consumes that handle; `Lost`, `Err` and `SafeUntil` expose health. The replica lease uses `<prefix>/lock.json`, a **15-minute** TTL and TTL/3 renewal. `newLeaserClient` supplies the existing S3 configuration. This is not the execution lease's key, body or timing. | |
| +| Pinned Litestream v0.5.14 `s3/leaser.go` | Conditional creation/replacement, ETag ownership and conditional release. Its lock suffix and `litestream.Lease` encoding are fixed; its private methods cannot serialize the claim's `op`/`expires` record. | |
| +| `internal/release/store.go`, `conditional_delete.go` | `Put(..., "")` is create-only; nonempty ETag is replace-only. `DeleteIfMatch` refuses missing ETags and unsupported stores. `S3Store{Client, Bucket}` can wrap the lease client's configured S3 client. Never fall back to `Store.Delete`. | |
| +| `main.go`, `internal/ops/ops.go` | `main` sends all `ops` arguments to the box-side package. Only `litestream`, `restore-verify`, `hibernate-verify` exist. Remote host resolution needs root CLI dispatch so it can use API sessions. | |
| +| `apimode.go`, `deploy.go`, `system.go` | `requireDeployAPISession`, `apiSession.getJSON/postJSON`, console selection and destructive confirmation patterns. The new host command must work off-box. | |
| +| `internal/console/authcli.go`, `store.go`, `apisystemupdate.go` | `cliRoute` with empty `svcrole.Set` excludes service credentials. `Store.IsOperator` is the fleet-operator bit; `apiSystemRoller` is a different authority (ops owner or configured system roller). Reuse session/routing mechanisms without widening declare-lost to system rollers or tenant owners. | |
| +| `internal/awsinv/clients.go`, `ec2.go` | Existing **read-only** `DescribeInstances` seam and AWS configuration. No `StopInstances` or `TerminateInstances` call exists. `infra/inventory.json` is hourly inventory, not current fencing evidence. | |
| +| `internal/status/instanceid.go`, `snapshot.go`, `internal/host/agent.go` | Private box metrics carry IMDS `InstanceID`, `Release = version.Build`, platform, system channel and last root update result. The running release is generally a short SHA, not ordered semver. | |
| +| `internal/console/apisystemupdate.go`, `server.go` | System status exposes box releases and outcomes. Console `/health` reports its own `version.Build`; there is no complete console-version census or implemented protocol floor. | |
| +| `internal/host/systemapply.go`, `systemstate.go`, `system.go` | Signed pointer verification, root staging, `.prev`, durable journals, rollback and boot recovery. No protocol check. The exact activation inventory is in Task 7. | |
| + |
| +## Global constraints |
| + |
| +- **Inert by default (R1).** With `CARLOS_HOST_CLAIM` unset and no published floor, current production behavior stays unchanged. Read the flag once at process construction and pass it through configuration; use the existing Plan 7 convention: trimmed nonempty enables, including `0`. Do not write the flag into infrastructure, unit defaults or deployment configuration. Plans 5–7 stay inert until this plan and their integrations have landed and the floor has been published. Publishing a floor and enabling callers are separate operator steps. |
| +- **One specified execution record.** `control/hosts/<host>/exec/<box>.json` contains exactly `op` and `expires`. TTL **90 seconds**, renewal **20 seconds**, only for participants in an active operation. A serving claim can retain `Op.Participants`; that does **not** mean an operation is running. Serving/free hosts have no execution-lease traffic. |
| +- **Leases are liveness, never stop evidence.** Whole-VM suspension also suspends the watchdog. No lease expiry, old heartbeat, empty activator memory, failed SSH connection or missing cloud inventory row authorizes revocation. C5 requires complete §6.1 evidence or the verified §7.2 compute fence. |
| +- **Fresh authority.** Read the exact host claim, check holder, operation ID/kind/phase, participant role and quarantine before a lease is established and before host work. Lease ownership does not substitute for `op.exec`, an installation stamp, a replica lease or writer-session admission. No claim listing on a hot admission path. |
| +- **Loss refuses writes.** By the acknowledged expiry, a participant closes host admission, cancels host work and runs cleanup-only termination. Renewing cannot resurrect a latched lost handle. Ordinary background writes, timers, replication and probes use the same host denial. Cleanup remains possible while the control bucket is unreachable. |
| +- **CC9 clearing is narrow.** A successful fresh claim read showing serving/free or this box no longer participating removes stale local execution-lease state. It clears that lease fence only. It never clears quarantine, another operation's fence, retirement or a failed process cleanup, and never grants a new spawn by itself. |
| +- **Conditional cleanup.** Release only the ETag owned by this lease controller. Join/cancel renewal before release. An old controller must not erase a successor's record. Claims are never deleted; version-bound data retirement remains Plan 4's job. |
| +- **Compute fencing.** The operator first stops or terminates the instance through their authorized infrastructure workflow. This command verifies the exact instance through a fresh cloud API read with the operator's credentials. It does not invent a stop/terminate API. The console receives no new EC2 rights; an inventory page is not the check. |
| +- **Quarantine survives release and reuse.** Preserve `Lost` through serving, free, adoption and successor creates. A listed box may serve **no host** until its old state is retired and an operator readmits it. Host-local `Lost` checks alone cannot implement this deployment-wide rule; close the contract in Decision D3 before enabling. |
| +- **Recovery preserves data obligations.** Lost compute does not destroy remote replicas or excuse regional parking/horizon work. Only the proved destroyed disk's local chores are discharged. A stopped instance's disk must not be described as gone. Recovery of validated durable data and cleanup of partial return lanes follow moves §7.10. |
| +- **Floor and downgrade.** One shared floor guards lifecycle starts and all executable activations. Unknown versions or failed reads refuse protected work. Removing the lifecycle flag must not provide a route around an already published floor; the persistent enforcement contract is Decision D1. Flag-off deployments with no floor keep their current behavior. |
| +- **Portability and scope.** Linux implements cleanup, with refusing stubs elsewhere; CLI, lease and floor packages build on Darwin and Windows. Use the default Go cache; if sandbox read-only, use the existing shared on-disk `$TMPDIR/gocache`. Never use a cache under `/tmp`. All tests use local fakes; no production resources are changed. |
| + |
| +## Decisions the specifications do not finish |
| + |
| +These are explicit implementation/activation gates. Implement and test the defined pieces below, but do not replace a missing contract with a permissive default or claim the flag is safe to enable. Record the resolution in the owning design before writing its production authority path. |
| + |
| +1. **D1 — release identity and offline floor enforcement.** The shared object and protocol number are specified. The actual minimum release is not: `vX.Y.Z` is a placeholder. Running boxes report short SHAs; signed system pointers and journals also accept non-semver builds. The specs do not define an authenticated mapping from those builds to ordered releases, local root persistence for offline recovery, the trusted pre-exec checker used by the `.prev` shell shims, or floor persistence when the feature flag is removed. Choose these jointly with the moves/release owners. Do not compare hashes lexically, use timestamps as precedence, trust an agent-written file as root policy, or replace `min_release` with the protocol number alone. |
| +2. **D2 — fencing evidence binding and transport.** The CLI must perform a fresh `DescribeInstances`, but the specs give no evidence schema, validity interval, CLI-to-console trust contract, or authoritative binding for box label → account/region/instance incarnation. Current IMDS metrics are a useful input, not a durable enrollment record. A signed console challenge can bind a request, but cannot make a client assertion into a cloud attestation. Define what the operator attests, what the console independently validates, how a stopped instance stays fenced until quarantine startup, and what survives an operation's release. Missing/aged-out instance IDs are not proof of termination. |
| +3. **D3 — deployment-wide quarantine and readmission.** `Lost` is per-host, but denial applies to every host. Specify how that deny reaches all admission callers without hot-path claim listings, how completeness of startup local inventory is established, how the returning box reports durable §6.3 retirement, and what `--readmit` removes across hosts. Define concurrent declare-lost/readmit ordering and replacement compute with a reused label. A host with no local inventory entry must not bypass a global quarantine. A console cache alone is not fresh admission authority. |
| +4. **D4 — durable loss records.** The console must declare registered timers and alert, but the record key/schema and source for a destroyed box's timer inventory are unspecified. Mark unknown inventory explicitly; never call it an empty set. A lost-target return with `src_covered == false` requires a second `writes_lost`; the current library has one pointer per admission and no source-cut loss entry API. Agree its additive representation, provenance and retry ownership with moves. Preserve existing admission records; never fabricate an extra `AdmitWriter` session to fit a second loss. |
| +5. **D5 — lost-box transition scope.** §7.2 says remove the box's obligations; the destroyed-local-only exception does not authorize dropping remotely stored data chores owned by that box. Define reassignment of regional work, persistence of the fence time for Plan 4, and the legal executor/stamp result when the current executor is lost. Revoke the lost executor atomically without minting a replacement writer. Recovery's destination/stamp is the owning operation's decision, including R6-4's journalled old stamp on a return. |
| +6. **D6 — lease failure and acknowledgement details.** §7.1 defines the wire record and timings, but not persistence of local lease state, handling an ambiguous PUT, same-box overlapping controllers, or how first-phase acknowledgement survives phase changes. Keep those choices conservative: no unacknowledged extension, one serialized local controller, expiry loss latched, stale operation responses ignored. Get the cross-process fence/local-state contract reviewed with Plan 6. A watchdog alone cannot deny writes in independently running root passes. |
| +7. **D7 — full floor census.** The specs require every eligible box and console, including both move regions, but existing status covers boxes and only individual console health. Define the authoritative eligible membership and freshness rules, fleet/pool namespaces and console reporting. Do not infer the entire serving set from currently visible heartbeat files or from the one current executor. Until a complete census is available, refuse protected starts. |
| + |
| +D1–D7 do not change the specified timings, payloads, CLI shape, compute-only fence, quarantine, floor or rollout order. They identify contracts that cannot be honestly inferred from current code. A primitive-only implementation can land inert while these gates remain open; completion of Plan 8's production wiring requires closing them. |
| + |
| +## Review focus |
| + |
| +1. A blocked renewal cannot prevent expiry handling, and a late success cannot reopen a lost handle. Lease expiry never satisfies a declare-lost request. |
| +2. Cleanup can kill a legacy populated cgroup during a control outage without requiring a successful fresh claim read or granting spawn authority. |
| +3. A stale resolve request cannot revoke a new operation or installation. A stopped unrelated instance cannot stand in for the named box. |
| +4. Quarantine covers a second host and a reused host, not only the claim on which the operator acted. |
| +5. A lost dirty target cannot silently erase unreturned writes, registered timers or a partial return lane. Destruction of local disks cannot erase remote obligations. |
| +6. Floor tests begin **before** selecting `.prev` in systemd, and include gated recovery. Testing only `restoreFiles` misses an older executable running the recovery itself. |
| +7. Flag-off parity, an ordinary serving host during a bucket outage, and disabled legacy CLI verbs remain unchanged. |
| + |
| +## File structure |
| + |
| +New filenames below are planned, not claims that these interfaces already exist. D1–D7 control wire additions and integration details. |
| + |
| +| Files | Responsibility | |
| +|---|---| |
| +| `internal/lease/execution_store.go`, `execution_store_test.go` | Narrow conditional object adapter using existing S3 configuration and conditional storage implementation | |
| +| `internal/hostexec/record.go`, `record_test.go`, `controller.go`, `controller_test.go` | Exact execution record, injected clock, acknowledgement barrier, renewal, loss and release | |
| +| `internal/host/hostexec.go`, `hostexec_test.go`; `internal/host/agent.go` | Flag-gated observation and cleanup adapter; independent renewal/deadline work; startup sequencing | |
| +| `internal/activator/maintenance.go` and Plan 6 admission callers | Fence ownership and lease-denial integration; no replacement for existing stop evidence | |
| +| `internal/hostclaim/lost.go`, `lost_test.go` | Validated evidence-backed transition, quarantine preservation, conditional readmission | |
| +| `internal/console/apihostresolve.go`, `apihostresolve_test.go`, `authcli.go`, `server.go` | Operator-only inspect/resolve API, durable loss/audit records and retry handling | |
| +| `opshost.go`, `opshost_test.go`, `main.go`, `internal/ops/ops.go` | Remote CLI dispatch, exact positional host syntax, explicit confirmation, existing ops help integration | |
| +| `internal/awsinv/fence.go`, `fence_test.go` | Fresh read-only verification through the existing EC2 client seam, used only by the CLI | |
| +| `internal/host/hostquarantine.go`, `hostquarantine_test.go` and Plan 4/6 startup hooks | Startup retirement and global admission denial, after D3 is settled | |
| +| `internal/hostfloor/floor.go`, `floor_test.go` | Shared floor record and version/census policy; no publishing as a side effect of reads | |
| +| `internal/status/snapshot.go`, `contract_test.go`; `internal/host/agent.go`; console status/startup | Additive protocol reporting, using existing release identities and private status scope | |
| +| `internal/console/hostfloor.go`, `hostfloor_test.go`, `apisystemupdate.go` | Lifecycle preflight and platform update/rollback refusals | |
| +| `internal/host/systemapply.go`, `systemstate.go`, new `systemfloor.go`, `systemfloor_test.go` | Candidate, `.prev`, journal, recovery and offline enforcement | |
| +| `internal/host/units/carlos-system-{update,recover}.service`, `units_isolation_test.go`, system unit tests | Floor-aware executable selection before launching a recovery binary | |
| +| `docs/site/cli.md` | Document `ops host resolve`, fencing prerequisite and `--readmit`; review final user-facing copy during implementation | |
| + |
| +### Task 1: Establish the implementation base and refusal contracts |
| + |
| +- [ ] **Step 1: Create the branch in a clean implementation checkout.** Do not run these commands while merely authoring this plan. |
| + |
| +```bash |
| +git switch -c feat/hostclaim-leases feat/hostclaim-boxguard |
| +git merge origin/main |
| +git merge-base --is-ancestor feat/hostclaim-boxguard HEAD |
| +git merge-base --is-ancestor origin/main HEAD |
| +``` |
| + |
| +Expected: Plans 1 and 3 and main's conditional release implementation are present. Do not copy uncommitted sibling work into the branch. |
| + |
| +- [ ] **Step 2: Recheck the source table.** Inspect branch commits, not only worktree files. Reconcile Plan 4's guarded retirement extension and Plans 5/6's construction seams before editing them. Keep this branch independently buildable when those integrations are not yet landed. |
| +- [ ] **Step 3: Turn D1–D7 into explicit dependency checks.** Missing policy/proof providers return a refusal, not a successful no-op. A compiled interface with no production provider is not completed runtime wiring. Keep the integration completion checklist in Task 8. |
| +- [ ] **Step 4: Write the flag-off regression harness first.** Inject spies for execution storage, claim reads, cleanup, cloud reads and floor admission. With no flag and no published floor, run existing serving/wake, local ops and console lifecycle fixtures: no new execution-lease writes, no cleanup invocation and unchanged legacy results. A serving host with the flag on still makes no execution renewals. Floor persistence after publication gets a separate test under D1. |
| + |
| +### Task 2: The specified execution object and conditional transport |
| + |
| +**Files:** `internal/lease/execution_store.go`, `execution_store_test.go`; `internal/hostexec/record.go`, `record_test.go`. |
| + |
| +- [ ] **Step 1: Add this narrow adapter.** It reuses the lease package's client configuration and the existing conditional implementation instead of copying an S3 lease engine. The replica lease's `Acquire`, `Held`, `Replicate`, TTL and format stay unchanged. |
| + |
| +```go |
| +package lease |
| + |
| +import ( |
| + "context" |
| + "errors" |
| + |
| + "github.com/carlosframework/platform/internal/release" |
| +) |
| + |
| +type ExecutionStore interface { |
| + Get(context.Context, string) ([]byte, string, error) |
| + Put(context.Context, string, []byte, string) (string, error) |
| + DeleteIfMatch(context.Context, string, string) error |
| +} |
| + |
| +type executionStore struct{ store release.Store } |
| + |
| +func ExecutionStoreFrom(s release.Store) (ExecutionStore, error) { |
| + if s == nil { |
| + return nil, errors.New("lease: execution store required") |
| + } |
| + if _, ok := s.(release.ConditionalDeleter); !ok { |
| + return nil, release.ErrConditionalDeleteUnsupported |
| + } |
| + return executionStore{store: s}, nil |
| +} |
| + |
| +func NewExecutionStore(ctx context.Context, bucket, region string) (ExecutionStore, error) { |
| + if bucket == "" { |
| + return nil, errors.New("lease: execution bucket required") |
| + } |
| + c, _, err := newLeaserClient(ctx, region) |
| + if err != nil { |
| + return nil, err |
| + } |
| + return ExecutionStoreFrom(&release.S3Store{Client: c, Bucket: bucket}) |
| +} |
| + |
| +func (s executionStore) Get(ctx context.Context, key string) ([]byte, string, error) { |
| + return s.store.Get(ctx, key) |
| +} |
| + |
| +func (s executionStore) Put(ctx context.Context, key string, body []byte, etag string) (string, error) { |
| + return s.store.Put(ctx, key, body, etag) |
| +} |
| + |
| +func (s executionStore) DeleteIfMatch(ctx context.Context, key, etag string) error { |
| + return release.DeleteIfMatch(ctx, s.store, key, etag) |
| +} |
| +``` |
| + |
| +Use the deployment/control bucket, never the host's data bucket. A configured `release.Store` can be adapted directly, including existing prefix wrappers; do not add a fleet prefix unless that deployment's authoritative claim namespace uses it. This interface exposes no unconditional deletion or listing. |
| + |
| +- [ ] **Step 2: Add the wire definition and key validation.** This is the whole specified payload, not Litestream's `Lease` with renamed fields. |
| + |
| +```go |
| +package hostexec |
| + |
| +import ( |
| + "errors" |
| + "time" |
| + |
| + "github.com/carlosframework/platform/internal/controlname" |
| +) |
| + |
| +const ( |
| + TTL = 90 * time.Second |
| + RenewEvery = 20 * time.Second |
| +) |
| + |
| +type Record struct { |
| + Op string `json:"op"` |
| + Expires time.Time `json:"expires"` |
| +} |
| + |
| +func Key(host, box string) (string, error) { |
| + if !controlname.ValidHostShape(host) || !controlname.ValidLabel(box) { |
| + return "", errors.New("hostexec: invalid host or box") |
| + } |
| + return "control/hosts/" + host + "/exec/" + box + ".json", nil |
| +} |
| +``` |
| + |
| +Add a strict decoder: require a nonempty operation ID, nonzero expiry, one JSON value and nonempty ETag on an existing object. Null, malformed timestamps, trailing data and unknown fields refuse. Encode UTC RFC3339 through `time.Time`. Only `os.ErrNotExist` is absence; missing buckets, access denied and network errors remain errors. Never serialize a monotonic clock deadline as recoverable ownership. |
| + |
| +- [ ] **Step 3: Write the conditional contract tests.** Use `release.DirStore` for deterministic object races and the existing conditional-store wire test pattern for HTTP. Two creators contend; exactly one succeeds. A stale renewal cannot replace the winner, and a stale release cannot delete it. Empty ETag release and unsupported conditional-delete stores send no delete. A control object key is exact; invalid hosts/box labels send no request. The same tests exercise a prefixed store. Verify existing replica lease tests still pass. |
| +- [ ] **Step 4: Observe failure, then implement the decoder.** Run `go test ./internal/lease ./internal/hostexec -run 'Execution|Record|Key' -count=1`. Expected: all defined contract tests pass; the runtime controller is the next task, not implied by passing transport tests. |
| + |
| +### Task 3: Renewal, expiry and the first-phase acknowledgement barrier |
| + |
| +**Files:** `internal/hostexec/controller.go`, `controller_test.go`. |
| + |
| +**Interfaces:** consume `lease.ExecutionStore`, an injected clock/timer factory, a fresh claim reader, a serialized local-state owner and a cleanup callback. Expose lease health, cancellation and a bounded release operation. Keep the clock and timer together; replacing `Now` while retaining real timers does not make expiry deterministic. |
| + |
| +- [ ] **Step 1: Write the fake-clock state-machine tests before the controller.** Use channels to block storage calls. Never sleep for the real 90-second TTL. |
| + |
| +| Witness | Required result | |
| +|---|---| |
| +| Acquire at t=0 | Fresh permitted participant; exact record has expiry t=90; keep returned ETag | |
| +| Renew at t=20 | Conditional PUT for the same operation; acknowledged new expiry t=110 | |
| +| Renewal blocked through t=90 | Independent deadline callback closes admission and starts cleanup without waiting for the PUT | |
| +| Renew response arrives at/after old expiry | Handle remains lost; returned ETag may be retained only for conditional cleanup | |
| +| Failed/ambiguous renewal | No extension from the attempted expiry; certainly fence by the last acknowledged expiry | |
| +| Explicit conflict or current claim excludes this operation | Refuse host work immediately; no blind reacquire | |
| +| Old controller resumes after successor owns object | Old PUT/DELETE fails conditionally; successor remains intact | |
| +| Agent restarts with saved future expiry | Saved timestamp grants no ownership; reconcile with fresh claim and a new acknowledged lease | |
| +| Free/serving or nonparticipant fresh read | Clear only old local execution state; no renewal and no new admission from that action | |
| +| Claim unreadable/malformed/absent unexpectedly | Refuse new authority; do not infer no operation | |
| +| Serving host loses bucket access | No execution lease to expire; this controller does not kill serving traffic | |
| +| Lease acquired, claim changes before work | Fresh operation/phase validation refuses the work | |
| + |
| +- [ ] **Step 2: Implement establishment and renewal.** A local controller captures host, box and operation. Serialize controllers for that host/box using the reviewed D6 mechanism. Read the claim directly and refuse a lost box. Create an absent execution object conditionally; an existing live foreign operation record is not silently stolen. Resolve superseded records only against a fresh claim with conditional replacement. Initial/renewal expiry is based on the attempt's start, not a late response's arrival. A successful response that arrived too late never grants usable time. |
| + |
| +Keep renewal every 20 seconds distinct from TTL/3 (which would be 30 seconds). Schedule a separate expiry watcher from the last acknowledged deadline so one blocked network call cannot block self-fencing. At the deadline, atomically latch denial before dispatching cleanup. Cancel requests on loss, tolerate SDK calls returning late, and never use their completion to reopen the latch. On process shutdown, retain denial until host cleanup or an authorized handover completes; stopping the renewer alone is not cleanup. |
| + |
| +- [ ] **Step 3: Add the barrier.** Every operation owner must leave its initial phase only after reading the exact current execution object for **each participant**, with matching operation ID and unexpired `expires`. Capture the participant set from one claim observation, then revalidate that observation's operation, phase and participant set before its conditional transition. Missing, expired, malformed or foreign-op records leave the first phase unchanged. Do not infer acknowledgement from a LIST result. A genuinely empty participant set is vacuous, never inferred after a failed read. A declare-lost change to participants invalidates a pending barrier decision and requires a fresh attempt. |
| + |
| +The barrier proves observation of the operation, not absence of a writer. Keep it separate from stop-evidence collection and from permission to start a writer. Later phases continue lease renewal. Changes to the participant set must establish the newly required acknowledgements before that participant is relied on; do not reset a lease merely because the operation's phase changed. |
| + |
| +- [ ] **Step 4: Test barrier races.** Block one participant's PUT, replace the operation while its response is pending, and attempt first-phase advance. Repeat with an expired object, ETag conflict, participant added/removed and whole-VM suspension. Assert zero phase advances without the exact required acknowledgements and zero compute-fence evidence manufactured from lease state. |
| +- [ ] **Step 5: Run** `go test ./internal/hostexec -count=1 -race` and the unchanged `internal/lease` tests. Check for leaked renewal goroutines and release bounded independently of a dead request context. |
| + |
| +### Task 4: Minimal box self-fence wiring |
| + |
| +**Files:** `internal/host/hostexec.go`, `hostexec_test.go`, `agent.go`; Plan 6's concrete guarded admission seams and `internal/activator/maintenance.go` where necessary. |
| + |
| +- [ ] **Step 1: Construct only behind the flag.** Observe active claims through Plan 6's reconciliation work; start one controller for each local participant, including participants that are not `op.exec`. Do not wait for the five-minute status tick. Plan 5 and moves' first-phase transitions consume Task 3's barrier. Plan 6 still owns all original holder, phase, stamp, config and release checks. |
| +- [ ] **Step 2: Fence before stopping.** On loss, latch host denial shared by wake/Seed/probes, background local writers, schedule delivery and root passes. Cancel host-specific work. Use `activator.Service.FenceAndStop` to close and drain known admission attempts; reopen the actual per-host cgroup with `cgroup.OpenCleanup`, then `stopevidence.Cleanup`/`KillAndEmpty`. Handle already-running processes absent from activator memory. Never call `PrepareStrict` as a way of cleaning a populated group. |
| + |
| +Do not put the emergency kill behind `Guard.With`: that helper reads the control claim and requires a current holder, precisely what an outage or retirement may remove. Cleanup is a narrowly scoped stop-only action on an already identified host tree. It grants no write, stamp activation or replacement spawn. The cross-process denial and lock ordering must allow cleanup while an ordinary guarded operation is stuck in I/O; implement D6 before claiming this property. |
| + |
| +- [ ] **Step 3: Stop replication and retain evidence boundaries.** Cancel generation-bound replication and close/join its worker. Existing `Replicator.Close` performs a final sync; it is not proof that no more writes were sent. Integrate the cancellation/closure contract with Plan 6's writer session, including in-flight requests and the existing late-completion horizon. Do not publish a clean certificate on an ambiguous close. Do not call test-only `Abandon` as production fencing. |
| + |
| +When a lifecycle phase needs stop proof, collect it separately with `stopevidence.Collect`: correct admission fence, drained spawns, `populated 0`, empty members, closed replicators and complete DB/WAL/SHM descriptor scan. Missing delegation, absent cgroup with possible escaped children, unreadable `/proc` or cleanup failure keeps admission closed and the operation pending. Alert and retry cleanup. A self-fence attempt does not create a lost-box fence. |
| + |
| +- [ ] **Step 4: Implement CC9 recovery.** A fresh no-operation/nonparticipant read ends only the stale execution-lease requirement, removes its local state and conditionally releases its object if still owned. The local state removal must not wait for remote release success. If a new operation requires this box, acquire its new lease before allowing its work. Keep every unrelated activator fence and the quarantine deny. Recover a new writer through the normal admission and certified-remote restore path; never reuse the old database or reattach an old agent session. |
| +- [ ] **Step 5: Write integration witnesses.** Lease failure while the claim store is blocked still kills the whole tree. Include a surviving child, empty activator state, stalled renewal, independent root writer, scheduled wake and sidecar membership. Denial is visible before cleanup; cleanup failure never reports success. A fresh serving claim clears stale lease state, while a `Lost` box stays denied. Unsupported cleanup platforms refuse safely and still compile. |
| + |
| +### Task 5: Operator-only declare-lost and evidence-backed claim changes |
| + |
| +**Files:** `opshost.go`, `opshost_test.go`, `main.go`, `internal/ops/ops.go`; `internal/awsinv/fence.go`, `fence_test.go`; `internal/console/apihostresolve.go`, `apihostresolve_test.go`, `authcli.go`; `internal/hostclaim/lost.go`, `lost_test.go`. |
| + |
| +- [ ] **Step 1: Add remote dispatch without changing local verbs.** Route `ops host` to root-package `runOpsHost`; forward other `ops` invocations to existing `ops.Run`. Parse `host resolve` and its positional host explicitly, then parse following flags: standard `flag.FlagSet` stops at the first positional argument. Test the spec's exact order: |
| + |
| +```text |
| +carlos ops host resolve <host> --declare-lost <box> --instance-id <id> --yes |
| +carlos ops host resolve <host> --readmit <box> --yes |
| +``` |
| + |
| +The host/declare-lost shape is specified. `--instance-id` and mandatory `--yes` are this plan's option names for the required operator inputs. Obtain deployment/account/region binding from the authorized inspect response under D2; do not guess a region from the operator's default profile. `--declare-lost` and `--readmit` are mutually exclusive. Missing confirmation produces the preview and exits without a mutation; there is no implicit noninteractive approval. Require an API session through `requireDeployAPISession`; there is no box-local registry or direct-bucket fallback for claim changes. |
| + |
| +- [ ] **Step 2: Add authenticated inspect and resolve routes.** Planned endpoints are `GET /api/cli/ops/hosts/{host}` and `POST /api/cli/ops/hosts/{host}/resolve`. Register with `svcrole.Set{}`. Require a person principal and `Store.IsOperator`; reject failed role reads. Nonoperators, service credentials and instance tokens receive the normal byte-identical 404 before host existence is disclosed. Reuse API session, mutation budget and audit plumbing. Do not substitute `apiSystemRoller`: permission to roll a binary is not permission to declare host data lost. |
| + |
| +The inspect response supplies the fresh claim identity, operation/phase, relevant record commit state, executor, participant, bound cloud instance and the consequences to confirm. Carry that exact observation into resolve. The mutation handler re-reads all authority-bearing state and refuses a changed operation, phase, incarnation, record commit or box identity; it does not silently retry against a new operation. D2 defines the durable evidence and request binding before this endpoint may mutate anything. |
| + |
| +- [ ] **Step 3: Verify the operator-completed compute fence.** Add a narrow read helper alongside `awsinv`'s existing EC2 reader, using the same SDK configuration and injectable `DescribeInstances` interface. Load **the invoking operator's** credential chain in the CLI, never console credentials. Query the supplied exact instance ID in the bound account/region and require exactly one matching instance in `stopped` or `terminated`. Refuse `pending`, `running`, `stopping`, `shutting-down`, missing/ambiguous results, mismatched identity, denied credentials and network failure. Do not reuse `infra/inventory.json` for state. Do not call stop/terminate, create credentials, or widen console IAM. |
| + |
| +Print the host, box, bound instance/account/region, current operation and phase, execution authority to revoke, local chores to discharge, quarantine and applicable write/timer loss **before** the POST. For a move include the flip time and dirty-return condition. The confirmation is bound to this preview, including D2's operator fencing attestation. Changing state invalidates it and requires a fresh preview. The command verifies completion through the returned claim and the inspect API, with no follow-up on-box action. |
| + |
| +- [ ] **Step 4: Implement pure transitions after D2/D5.** A transition takes a validated evidence value, exact expected operation/kind/phase and the approved consequence set. `hostclaim.Update` supplies the ETag and resulting `Gen`; the transition must also compare operation identity. The same CAS records quarantine and loss-recovery intent before any cross-object effect, revokes only the lost box's execution/participation authority and records the matching holder stamp when authority changes (R6-4). Never issue a new writer session here. |
| + |
| +Use `CompleteObligation` semantics for discharged chores so adoption discovery completion is not stranded. Match exact chore ID and its own operation, not just `Owner == box`. Preserve unrelated obligations, holder/admission history, region scope, quiet times and retired identities. A retry whose exact result is already durably recorded is idempotent; a retry against another operation is stale. Persist fence identity/time and consequences in the D2/D4 record so releasing `Op.Journal` cannot erase the evidence or timer-loss declaration. Cross-object audit/alert delivery retries from that durable state; a lost HTTP response cannot cause a second revocation. |
| + |
| +- [ ] **Step 5: Cover the legal recovery cases.** Delegate move-specific transitions to moves' recovery owner; do not recreate them using a generic `op = serving` assignment. |
| + |
| +| State | Permitted action after verified compute fencing | |
| +|---|---| |
| +| Exact move target owes stop evidence before commit | Apply moves' recovery/rollback transition; recover validated durable target data when available. Confirmation names operation, phase, flip and write loss. Never accept the source. | |
| +| Exact target released dirty but did not return writes | Same precommit restrictions. Account for unreturned data and queue moves' partial-return-lane `park-staging` obligation in the source region in the same recovery CAS. Respect its staging horizon. | |
| +| Destroyed box owes only `park-local`, including after commit | Discharge only those local chores and quarantine the box; no data/executor reassignment. A stopped instance alone does not prove the disk was destroyed. | |
| +| Other lifecycle recovery | Only the operation owner's defined evidence-backed transition. No automatic takeover, no dropped remote chore and no generic emergency free. | |
| + |
| +For moves, check **the instance record's commit CAS**, not only the claim phase: the record may be committed before the claim follows. Use the same conditional record rollback as the normal move path so resolve cannot race a commit. The postcommit local-only case is the explicit exception. Current Plan 1 validation does not implement moves' `park-staging`; integrate that with the moves owner, not by relabelling it `park-local`. |
| + |
| +- [ ] **Step 6: Preserve loss accounting.** A remote-only baseline is not `sealed`, and a lost-target return is not `clean-return`. Let the next genuine `AdmitWriter` record its predecessor's `writes_lost`, using the last admission (holder fallback only when admissions are empty), real remote maximum and alert. Implement the additional source cut-session loss when `src_covered` is false using D4's agreed representation. Record registered timer loss on the console's durable declaration even if no replacement writer is ever admitted. Do not report unknown counts or TXIDs as zero. |
| +- [ ] **Step 7: Test refusals before any mutation.** Include no `--yes`, no cloud proof, expired lease only, live/stopping box, wrong instance/account/region, stale evidence, partial cloud response, unauthorized caller, source, nonparticipant, record-committed target and claim ETag race. Snapshot claims, records, chores and remote data and assert no changes. Positive tests cover both target-loss cases, destroyed-local-only after commit, stale retry, durable audit replay, timer loss, second write loss and preservation of regional obligations. |
| + |
| +### Task 6: Returning-box quarantine and readmission |
| + |
| +**Files:** `internal/host/hostquarantine.go`, `hostquarantine_test.go`; claim/console readmission transitions and Plan 4/6 startup integration. |
| + |
| +- [ ] **Step 1: Implement D3's global deny before startup work.** The startup gate precedes reconcile, wake, Seed, unit reattachment, timer delivery, replication and config/release writes. Read the claim for every local inventory host. Build the required inventory from the canonical persisted inventory and local artifacts; an empty activator map is not inventory. Unknown/malformed inventory or claim reads keep admission closed. A serving host's no-lease exception does not bypass quarantine. |
| +- [ ] **Step 2: Retire locally on the returning box.** For each affected host run §6.3 local retirement under Plan 4's extended guard, including DB/WAL/SHM, keys, config/env, markers, registry and timers. Preserve the captured old incarnation/stamp. It must work after the claim becomes free or names another holder, without retiring the successor's installation. Use the same lock and durable artifact journal; do not relax `Guard.With` to admit ordinary writes without a holder. Retrying a crash midway resumes retirement and never restores old files. The console cannot retire another machine's disk by deleting an inventory row. |
| +- [ ] **Step 3: Publish and verify retirement completion.** D3 defines the evidence's identity and durability. The operator's `--readmit` must inspect that completion through the console, refuse missing/partial/stale proof and remove only the matching quarantine revision after **every** affected old state is retired. Retry cross-host updates conservatively: an incomplete readmission leaves the global deny in force. A concurrent loss declaration wins over an older readmission attempt. Removing quarantine grants no stale executor/stamp/session; subsequent starts use fresh admission and certified-remote restore. |
| +- [ ] **Step 4: Test the scope.** Declare box A lost for host H1, then attempt H2 admission and successor H1 creation/serving. All A admissions refuse. Release/free/recreate H1 and verify `Lost` persists. Reboot A with stale files, missing inventory entries, a new holder, malformed claims and a half-finished park journal. Readmit refuses until complete retirement. A fresh serving read clears an execution fence but not quarantine. A reused box label with a different instance ID cannot supply proof for the old instance. |
| + |
| +### Task 7: One floor for lifecycle admission and every executable activation |
| + |
| +**Files:** `internal/hostfloor/*`; status reporting and console gates; `internal/host/systemapply.go`, `systemstate.go`, `systemfloor.go`; both system update/recovery unit files and their tests. |
| + |
| +- [ ] **Step 1: Parse the existing shared contract.** Implement a typed floor reader over `release.Store` with key `control/moves-protocol.json`, fields `min` and `min_release`. Distinguish absent, malformed and unreadable. Require a supported positive protocol and valid specified release identity. Leave `vX.Y.Z` out of production defaults. Reads never publish the object. Initial publication is create-only by the operator; an existing different object is a conflict, not overwritten by startup. |
| + |
| +Resolve D1 before wiring comparison. Use `release.ParseSemVer`/`SemVer.Compare` for actual release tags, with deliberate leading-`v` normalization; they do not order a short SHA. Match existing `version.Build`, signed `SystemPointer.Current.Version`/artifact hashes, `systemApplied` and journal values through the approved release identity contract. `System.Outcome == applied` is not proof of the currently running binary. Unknown/dev builds refuse protected activation unless D1 supplies authenticated identity; no `force` bypass. |
| + |
| +- [ ] **Step 2: Publish capability additively and gate a complete census.** Add `moves_protocol` to the private box report and report the console's own capability and running identity. Missing fields from old reports mean unsupported. Update `status` schema/privacy tests so private identity does not leak into public fleet output. Bind reports to existing instance/machine identity and apply the D7 freshness/completeness rules. Reuse `Metrics.Release` for the running build; do not infer it from the channel target. |
| + |
| +Plan 5's create/delete/purge/placement starts and moves' request door/T1 call the shared admission check before writing intent or effects. Check all boxes that could serve the host, every required participant and every relevant console; include both source and target regions for moves. Include offline/unknown enrolled boxes rather than dropping them from the set. Below-floor/unknown actors refuse protected operations. Preserve today's delete behavior plus the stopgap where §10 explicitly keeps legacy behavior; never start a partial claim-based delete and then fall through to legacy cleanup. Recheck authority at the operation's start CAS. A held participant cannot downgrade, even to another build numerically above the floor, until its operation permits it. |
| + |
| +- [ ] **Step 3: Install the root floor check before executable selection.** D1 must give root a durable, trusted floor and a checker usable when the network or candidate binary is broken. Persist it using the existing root durability/ownership boundary, never as an agent assertion. Validate `.prev` and any outstanding journal before allowing the floor to become active. Keep it outside files restored from the old update journal. Read failure after publication refuses activation; it does not mean no floor. Refusal preserves evidence/journal and prevents a later shell fallback from selecting an older executable. |
| + |
| +Both existing unit shims currently execute `.prev` directly on an open journal. Change them to use the D1-approved trusted pre-exec enforcement path. A check inside the new binary alone is insufficient: the shim may choose an older binary that contains no check. The checker must not depend solely on executing the failed candidate. Review the bootstrap/recovery design and trust boundary before adding its executable or persisted metadata; this plan does not invent that missing release protocol. |
| + |
| +- [ ] **Step 4: Apply one check at all paths in this matrix.** Where identity probes execute a binary's `version`, distinguish inspection from activating it as bootstrap, updater or serving process. Verify the selected bytes against the approved identity before activation and prevent a path replacement between verification and use. |
| + |
| +| Entry / function | Required floor boundary | |
| +|---|---| |
| +| Console `handleAPISystemUpdate` and `handleAPISystemRollback` | Refuse publishing a below-floor target or prohibited participant downgrade, including explicit rollback into pointer history and owner-uploaded local builds. Check before pointer mutation. | |
| +| `systemApplier.Run` | Load persistent policy before recovery or no-op shortcuts; inspect an outstanding journal before executing either side. Recover-mode is not an exemption. | |
| +| `stageCandidate` → `install` | Keep signed pointer/SHA checks; check the verified staged candidate before `stagePlan` or rename to the live path. Revalidate before activation if policy changed. | |
| +| `decidePrev` | A retained/refreshed `.prev` must be floor-compatible before it is treated as a rollback target. Do not overwrite a valid recovery choice with an incompatible current binary. Before first publication, old behavior may prepare the upgrade; the rollout must fix `.prev` before publication. | |
| +| `stagePlan` | This executes a binary's bootstrap code. Check its identity for both `new` and `prev`; validate any reused staged plan against that same approved binary. | |
| +| `rollback` → `restoreFiles` | Check actual `.prev`, its approved identity and the journal before quiescing, copying, restoring units, restarting or arming. A journal's `PrevBuild` string alone is not proof of the file. | |
| +| `recoverIfNeeded`, pre-gate | Check `.prev` and journal before the same restore; cover boot (no restart) and runtime (`restart --no-block`). Preserve refusal state and the journal. | |
| +| `recoverIfNeeded`, `systemPhaseGated` | Check the actual current target before `stagePlan`/`finish`; a previous health gate is not a floor exemption. If target differs, the fallback restore receives the same check. | |
| +| Legacy `systemPhaseBackingUp` journal | Validate recovery inputs and floor implications before clearing it or treating a partly prepared backup as safe. Cover this branch explicitly. | |
| +| `carlos-system-update.service` and `carlos-system-recover.service` | Check the executable **before** either shell branch invokes `.prev` or the current binary. No unguarded fallback. | |
| +| Edge boot following failed recovery | A refused recovery must not leave systemd free to launch a below-floor current binary. Wire the approved boot gate/dependency and test it; `Before=` alone orders units but does not make a failure block the edge. | |
| +| Console roll/restore recipe and any bootstrap binary replacement | Include in the deployment rollout inventory. The platform repository cannot certify uninspected private scripts. Require those paths to enforce the same published floor before enabling. | |
| + |
| +Search again for copies/renames to `systemBinaryPath`, `systemPrevPath`, `/opt/carlos/carlos`, shell `exec` of `.prev`, and staged bootstrap execution when implementing. Update the matrix if a new path has landed; listing only exported `system apply` is insufficient. |
| + |
| +- [ ] **Step 5: Run a table-driven activation test over the matrix.** For each path inject: below-floor, exact-floor, newer compatible, unknown build, unsupported protocol, malformed/unreadable floor, forged agent metadata and mismatched journal/file identity. Assert forbidden paths make **zero** binary-copy/rename, bootstrap, restart, unit-enable or edge-launch calls. Assert the known-good compatible path works. Cover a floor appearing between preparation and activation, network loss on boot with a persisted floor, a stale lower remote object, flag removal after publication, an old `.prev` with an open journal, a gated-but-below-floor current binary, and a held participant downgrade. |
| + |
| +Use executable fixtures and a fake command runner to exercise the actual unit-selection logic, not just string containment in a unit file. Test that recovery failure blocks unsafe edge startup and does not erase the journal or enter an automatic older-binary loop. No live systemd or production AWS calls are needed. |
| + |
| +### Task 8: Integration, review and repository gate |
| + |
| +- [ ] **Step 1: Finish the wiring checklist.** Each item needs a concrete caller and an integration test, not merely an interface declaration: |
| + |
| +| Consumer | Completion evidence | |
| +|---|---| |
| +| Plan 5 and moves operation starts | Full floor census before intent; per-participant lease barrier before leaving the first phase | |
| +| Plan 6 host work | Lease denial reaches activator, root/background writes, timers and replicators; emergency cleanup works without control reads | |
| +| Plan 4 | Durable lost-box fence time feeds retirement scope/horizon; returning local retirement works for free/successor holders | |
| +| Lost-box CLI/API | Operator permission, live cloud verification, exact binding, durable loss record, both target cases and local-only exception | |
| +| Moves | Record-commit race, partial-return-lane retirement, lost target/source-cut loss and recovery delegation | |
| +| Quarantine/readmission | All-host denial, complete startup retirement, safe multi-host readmission and reused-label refusal | |
| +| Shared floor | D1/D7 resolved, actual release selected, every executable path tested including both unit shims and boot failure | |
| +| Plan 7 + 7b | Generation-aware selection integrated into the door and restore callers; root-only Plan 7 refusal is not considered full rollout readiness | |
| + |
| +- [ ] **Step 2: Add remote CLI documentation.** `docs/site/cli.md` must name `ops host resolve` and both actions, explicit confirmation, external fencing prerequisite, current-state verification and refusal/recovery results. Reuse existing docs headings so `internal/docsite` remains authoritative. Final CLI/UI/error copy goes through the repository's writing/copy-review workflow in the implementation session; this plan specifies behavior, not final copy. |
| +- [ ] **Step 3: Run focused tests and the required whole-tree gate.** Use the repository's normal cache. In this sandbox the existing shared `$TMPDIR/gocache` is the on-disk fallback; no new cache per task. |
| + |
| +```bash |
| +go test ./internal/lease ./internal/hostclaim ./internal/hostexec ./internal/hostfloor -count=1 -race |
| +go test ./internal/host ./internal/activator ./internal/console ./internal/awsinv -count=1 |
| +go test . -run 'OpsHost|System' -count=1 |
| +go build ./... |
| +go vet ./... |
| +go test ./... -count=1 |
| +gofmt -l . |
| +GOOS=darwin GOARCH=amd64 go build ./... |
| +GOOS=windows GOARCH=amd64 go build ./... |
| +GOOS=linux GOARCH=arm64 go build ./... |
| +``` |
| + |
| +Expected: all checks pass and `gofmt -l .` prints nothing. Binary-invoking tests use `carlosTestEnv` and a scratch `cmd.Dir`; no inherited real credentials or project config. Cross-compiles do not execute platform-specific cleanup. A socket permission failure is an environment limitation to report, not a passing test. |
| + |
| +- [ ] **Step 4: Check the joint simulation.** Port the lease/compute/quarantine and `floor-boot-recovery` witnesses from the moves model and run its stated 30k+ seed gate. Mutations that accept lease expiry as fencing, omit quarantine, skip first-phase acknowledgement or restore a below-floor executable must be caught. The model's own notes say cloud proof and executable activation need real integration witnesses too; simulation alone is not verification of those paths. |
| +- [ ] **Step 5: Obtain the required authority/production-path review.** Use Opus under the repository's delegation policy, with explicit attention to D1–D7 and the tests above. Resolve findings before landing. Push the implementation branch to amadan, describe the final scope and evidence, squash to one change commit, wait for CI on that exact tip, then use `amadan branch merge ... -expect <tip>`. Read main's CI afterward. None of those branch operations is part of authoring this Markdown plan. |
| + |
| +## Rollout runbook — Paul performs activation |
| + |
| +This runbook describes later operator work. It publishes nothing and flips no flag during implementation or plan authoring. Apply the brief's **boxes, then consoles, then flag** order even where an older companion rollout note says consoles first. |
| + |
| +1. **Close prerequisites.** Resolve D1–D7, integrate Plans 5/6 and generation-aware Plan 7/7b callers, pass the authority review and CI, and select the real release that satisfies the shared floor. Check the exact deployment inventory, including all serving/eligible boxes, consoles, fleet/pool namespaces, private bootstrap/console restore scripts and IAM version grants from earlier plans. Keep `CARLOS_HOST_CLAIM` unset throughout preparation. |
| +2. **Upgrade boxes to at least that release.** Use the deployment's existing approved update workflow. Verify the actual running build and `moves_protocol`, the root floor/boot enforcement path, compatible `.prev` and outstanding journals on **every** box. A target pointer or successful upload is not confirmation of the running build. Finish/recover incomplete journals and establish a compatible recovery binary before publication; an old `.prev` cannot be exempted because the live binary is new. |
| +3. **Upgrade all consoles.** Verify their actual running identities/capabilities and the complete floor census. Include CLI clients that use the new declare-lost or v2 pull-door protocol. An old client talking to a flagged v2 door will be refused; make this visible before enabling. Keep the flag unset. |
| +4. **Publish the shared floor, create-only.** Paul publishes `control/moves-protocol.json` with protocol minimum **1** and the chosen real `min_release`, through the approved release/deployment workflow defined in D1. Confirm that every box/root recovery path and every console has adopted the same floor. If an existing floor differs or any box cannot prove safe `.prev`/journal handling, stop before activation. Do this **before** any protected production rehearsal. |
| +5. **Enable `CARLOS_HOST_CLAIM` as Paul's operator step.** Only after the preceding checks, enable and restart the relevant processes through the deployment's approved rollout. Verify flag uptake, full floor admission, an ordinary serving host with no execution-lease writes, and operation acknowledgement/renewal behavior. Code does not infer activation from publication or set the flag itself. |
| +6. **Rehearse within explicit scope.** Start with local deterministic witnesses, then the approved deployment rehearsal sequence. A live stop/terminate or production resource change requires its own existing scoped authorization; this plan supplies none. Verify declare-lost refusal on unfenced compute before testing an authorized fence, quarantine across another host, retirement/readmit, and a below-floor rollback/boot refusal. Use the exact built release and preserve the evidence. |
| +7. **Recovery after activation.** Pause new lifecycle starts while preserving claim/quarantine/floor enforcement if a rollout fails. Use a compatible fixed release or an above-floor rollback permitted by operation state. Do not unset the flag as an excuse to run claim-unaware paths over active claims, erase the floor, force an old `.prev`, or label remote data lost merely to clear a stuck operation. |
| + |
| +## Authoring verification |
| + |
| +The source baseline and the two complete Go blocks in Task 2 were checked in a disposable on-disk source export, outside this repository. The export used `feat/hostclaim-boxguard` at `01ed2bd9`, which already contains `origin/main` at `04cd2d78`; this is the base the branch instructions produce at authoring time. Build commands used `-buildvcs=false` because an archive is not a Git checkout. The default cache was attempted first and is read-only in this sandbox; the existing shared `/home/paulca/.cache/tmp/gocache` was the fallback. |
| + |
| +Results on 2026-10-05: |
| + |
| +- The prerequisite source export passed `go build -buildvcs=false ./...`. |
| +- After extracting both complete Task 2 Go blocks into that export, the whole repository built for Linux/amd64, Darwin/amd64, Windows/amd64 and Linux/arm64. |
| +- `go test -buildvcs=false ./internal/hostclaim ./internal/hostguard ./internal/stopevidence ./internal/hostexec -count=1` passed. The first three packages ran their existing tests; the new record-only `hostexec` package had no tests yet. |
| +- An explicitly requested Opus review through the installed CLI did not complete within 45 seconds in this network-restricted sandbox. No Opus verdict was obtained; Task 8's required review remains outstanding for implementation/landing. |
| +- The disposable export was removed after verification. No production actions, branch changes, staging or commits were performed. |
| + |
| +Compiling the blocks verifies their real dependency interfaces. It does not claim that the planned controller, authority transitions or floor integrations have been implemented or tested. Their required behavioral tests and unresolved contracts are listed above. |