spec: host lifecycle claim rev 6.17, PROPOSED contracts for leases, lost boxes and the floor
§7.3 (D2-D6: fencing evidence, global quarantine and readmission, durable loss records, lost-box transition scope, lease failures) and §10.1 (D1 release identity and pre-exec guard, D7 census, actor directory and reservations), drafted by Codex from Plan 8's gaps. Marked proposed: waits on Paul's P1-P3 and joint review.
Co-Authored-By: Codex <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 file changed,
+580
−2
docs/superpowers/specs/2026-10-02-host-lifecycle-claim-design.md+580 −2
CI cancelled — superseded by a newer push — run details
diff --git a/docs/superpowers/specs/2026-10-02-host-lifecycle-claim-design.md b/docs/superpowers/specs/2026-10-02-host-lifecycle-claim-design.md| index 30b6584af..935deae16 100644 |
| --- a/docs/superpowers/specs/2026-10-02-host-lifecycle-claim-design.md |
| +++ b/docs/superpowers/specs/2026-10-02-host-lifecycle-claim-design.md |
| @@ -1,6 +1,6 @@ |
| # The host lifecycle claim |
| -Design, 2026-10-05, **rev 6.16**. Joint rounds 6 and 7 judged the claim ready for primitive planning. Rev |
| +Design, 2026-10-06, **rev 6.17**. Joint rounds 6 and 7 judged the claim ready for primitive planning. Rev |
| 6.5 added the R6-4 rule. Rev 6.6 added writer epochs (R7-1, amadan #225). |
| Rev 6.7 answered the moves session's notes on §6.4a. Rev 6.8 rewrote §6.4a |
| around writer sessions, for joint round 8. Rev 6.10 answers joint round 10 |
| @@ -11,7 +11,8 @@ answers the moves spec's rev 7.12 notes (coverage is per region). Rev |
| rule set, and the readings sections are history. Rev 6.15 adds the |
| persisted park scope and the holderless sweep (§2, §6.2a), found while |
| planning the park engine. Rev 6.16 states what a box reference is |
| -(amadan #212). Rev 6.9 answered |
| +(amadan #212). Rev 6.17 adds the PROPOSED contracts §7.3 and §10.1 |
| +(Plan 8's D1 to D7), awaiting Paul's rulings P1 to P3 and joint review. Rev 6.9 answered |
| joint round 9 |
| (R9-1 to R9-4) and takes **Paul's ruling R6 (2026-10-04): a box never |
| reuses its local database. Every writer start restores from the |
| @@ -1457,6 +1458,344 @@ only while an operation is running. A host that is simply serving holds |
| no execution leases, so a control-bucket outage cannot stop serving |
| traffic. |
| +### 7.3 Contracts for leases, lost boxes and the floor (rev 6.17, PROPOSED) |
| + |
| +**Status: proposed, not yet ruled.** These contracts close the gaps Plan 8's draft found (D2 to D6). They are under joint review and wait on Paul's rulings P1 to P3 in §10.1; nothing in production relies on them until then. Missing safety evidence always means unsupported or unknown, never success; all new JSON fields are additive. |
| + |
| +#### D2. Fencing evidence binding and transport |
| + |
| +**Authority and identity.** Declare-lost and readmit require a person session and a successful `Store.IsOperator` check on every request. Service credentials, tenant ownership, ops-account ownership and `apiSystemRoller` membership do not independently grant this authority. Authorization errors refuse the request. |
| + |
| +The shared actor directory defined in D7 binds each box reference to: |
| + |
| +```text |
| +enrollment |
| +namespace |
| +box_id |
| +label |
| +region |
| +pool |
| +cloud: {provider, account, region, instance_id} |
| +``` |
| + |
| +`enrollment` is an immutable random identifier for that machine enrollment. Cloud account, region and instance ID are enrolled facts, not values selected by the resolve request. Missing or ambiguous binding refuses declare-lost. |
| + |
| +Platform box references use #212’s collision-resistant ID. Empty IDs and the twin latch refuse claim work. Machine-ID uniqueness remains a deployment prerequisite. |
| + |
| +A fleet label is unique only within its fleet and active enrollment. In deployment-wide claims, its reference is `fleet-<enrollment>`, where `enrollment` is 32 lowercase hexadecimal characters. The directory binds that reference to the fleet and its label. Existing fleet heartbeat keys remain unchanged. This is the necessary qualification of #212’s fleet-label exception for shared claim objects. |
| + |
| +Labels are accepted at the CLI only when they resolve to exactly one enrolled reference. Every claim field, lease key and loss record uses the resolved reference. |
| + |
| +**Request binding.** Inspection returns a console-signed challenge containing: |
| + |
| +```text |
| +id, actor, issued_at, expires_at |
| +host, holder_incarnation, holder_stamp, holder_session |
| +claim_gen, claim_etag |
| +op: {id, kind, phase} |
| +record: {key, etag, commit_state} |
| +box, enrollment, cloud |
| +consequences |
| +``` |
| + |
| +The challenge expires after **120 seconds**. `consequences` names the exact requested transition, affected chores, possible write/timer loss and any disk-destruction assertion. |
| + |
| +After inspection, the CLI makes a new `DescribeInstances` call for the exact enrolled instance ID, in the enrolled account and region, using the operator’s credentials. It verifies the reservation owner account and requires a positive result of `stopped` or `terminated`. Partial results, API errors, missing instances and all other states refuse the request. |
| + |
| +The submitted observation contains: |
| + |
| +```text |
| +challenge |
| +observed_at |
| +account, region, instance_id, state |
| +request_id |
| +attestation: |
| + maintain_compute_fence |
| + cold_quarantine_start_only |
| + disks |
| +confirmation |
| +``` |
| + |
| +The cloud observation must be made after challenge issuance, be no more than **60 seconds** old when consumed, and not be more than **5 seconds** in the console’s future. Expired requests require a new observation and confirmation. |
| + |
| +The console independently validates the person, operator bit, challenge signature and age, enrollment, expected claim/record observations, legal transition and consequence confirmation. It does **not** independently validate the cloud response. The cloud fields are an authenticated operator assertion checked by the CLI, not AWS-signed evidence. A request ID or console signature must not be described as cloud attestation. |
| + |
| +The operator attests that: |
| + |
| +- They have fenced the exact enrolled compute instance. |
| +- They will prevent it, including automation acting on it, from resuming execution until the permitted quarantine startup. |
| +- Any subsequent startup is a cold startup through the installed quarantine gate, with no resumed application processes. |
| +- Any claim that local storage was destroyed covers the named storage and artifacts, rather than merely the instance state. |
| + |
| +A hibernated instance can be `stopped` and later resume its previous processes. Therefore `stopped` alone does not establish that a future start will run startup retirement. The cold-start assertion is required. [AWS hibernation behavior](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Hibernate.html) |
| + |
| +Likewise, `InvalidInstanceID.NotFound` is not termination evidence. [AWS eventual-consistency guidance](https://docs.aws.amazon.com/ec2/latest/devguide/eventual-consistency.html) |
| + |
| +The command never stops, terminates or restarts compute. It gives the console no EC2 permissions. |
| + |
| +The console records an immutable `fenced_at` when accepting the bound evidence. It is a conservative evidence time, later than the asserted stop. Retries of this accepted declaration retain that original time. The evidence is usable only for that declaration and its documented recovery effects, never for a different operation or enrollment. |
| + |
| +#### D3. Deployment-wide quarantine and readmission |
| + |
| +**Global denial.** Each box entry in `control/host-actors.json` carries: |
| + |
| +```text |
| +state: "active" | "quarantined" | "retired" |
| +quarantine_revision: monotonically increasing integer |
| +loss_ids: declaration IDs |
| +``` |
| + |
| +The directory is console-authored, root-signed and replaced with `If-Match`. Box processes may read it but may not author its authority. |
| + |
| +Before revoking claim authority, the console conditionally sets the exact enrolled box to `quarantined`, increments its quarantine revision and records the declaration ID. A partial declaration leaves this deny in force. It never automatically restores eligibility because a later claim write failed. |
| + |
| +Every new host admission and guarded host-state write reads and verifies this exact object alongside the claim. It refuses a missing enrollment, identity mismatch, quarantine, retirement or unreadable directory. This is an exact-object read, not a claim listing or a console-cache lookup. |
| + |
| +The deny covers every host, including hosts absent from the lost claim’s inventory. Already-running ordinary serving processes acquire no execution lease and are not stopped merely because a directory read or heartbeat fails. The declared-lost machine is safe because its external compute fence remains held. |
| + |
| +Claims retain `lost []string` and gain: |
| + |
| +```text |
| +lost_revisions: map[box reference]quarantine revision |
| +loss_refs: declaration IDs |
| +``` |
| + |
| +Loss references survive `free`, operation replacement and successor holders. Removing one claim’s `lost` entry never clears the global deny. |
| + |
| +**Returning-machine startup.** Before starting the edge’s tenant work, root/background passes, timers or replicators, the returning machine verifies its enrollment and global quarantine state. Unreadable authority means no startup admission. |
| + |
| +While quarantined, startup closes all host admission, drains existing work and constructs the union of: |
| + |
| +- Local routes, timer rows and installation records. |
| +- Configured flat and nested artifact roots. |
| +- Incomplete retirement journals. |
| +- Claim inventory entries naming this enrolled machine, found through a complete paginated scan. |
| + |
| +This scan is a startup/recovery operation, never a hot-path admission scan. Missing inventory entries do not imply an empty disk. Unreadable directories, ambiguous artifact ownership, incomplete listings or unexplained residue prevent completion. |
| + |
| +The box retires every affected old installation using §6.3. It holds the host guard and names the captured incarnation and stamp. Cleanup must work when the claim is free or belongs to a successor, without granting ordinary `Guard.With` authority and without touching that successor’s installation. |
| + |
| +Completion requires drained processes and replicators, retired installation stamps, parked artifacts, retired matching registry/timer state, and durable artifact journals. A crash resumes retirement. |
| + |
| +The box publishes: |
| + |
| +```text |
| +control/host-retirement/<box>/<enrollment>/<quarantine_revision>.json |
| +``` |
| + |
| +with: |
| + |
| +```text |
| +v, box, enrollment, instance_id, quarantine_revision, boot_id |
| +completed_at, complete |
| +inventory_sha256 |
| +entries: |
| + host, incarnation, stamp, retirement_id, journal_sha256 |
| +``` |
| + |
| +`complete` is true only after the full scan and all retirement work succeed. Root writes the local completion durably before the box publishes it. A reboot rechecks retirement before reporting completion for the new boot. |
| + |
| +The current private box report names the completion object’s digest. The console requires a fresh report under D7, matching enrollment, instance ID, boot ID and quarantine revision, and matching completion bytes. The receipt is durable retirement evidence; the fresh report establishes that this returning machine still reports that completed state. It is not hardware attestation and retains the existing trusted-box-writer model. |
| + |
| +**Readmission ordering.** Readmit binds to one enrollment and quarantine revision. The console: |
| + |
| +1. Confirms every accepted loss declaration and affected host has completed retirement or the explicitly accepted destroyed-local disposition. |
| +2. Removes per-host quarantine entries only where `lost_revisions[box]` equals the revision being cleared. |
| +3. Preserves `loss_refs` and declaration history. |
| +4. Clears global quarantine last, with a conditional directory write requiring the same enrollment, quarantine revision and loss set. |
| + |
| +An interrupted readmission leaves the global deny active. A concurrent declaration increments the revision and defeats the older final write. An older readmission cannot remove a newer per-host quarantine entry. |
| + |
| +Readmission grants no executor, installation stamp or writer session. Any subsequent writer uses fresh claim authority, a new admission and certified-remote restoration. A retired installation stamp cannot be reactivated through readmission. |
| + |
| +A replacement machine cannot complete the old enrollment’s retirement merely by reusing its label. It receives a distinct identity/enrollment; old quarantine and retirement obligations remain attached to the original machine. |
| + |
| +#### D4. Durable loss records, timers and multiple write losses |
| + |
| +The console owns one durable record per declaration: |
| + |
| +```text |
| +control/host-loss/<declaration_id>.json |
| +``` |
| + |
| +It uses the existing root-signed envelope and conditional writes. Its payload contains: |
| + |
| +```text |
| +v, id, state |
| +actor, accepted_at |
| +host, holder_incarnation |
| +expected_claim_gen, expected_claim_etag |
| +op_id, op_kind, op_phase |
| +box, enrollment, quarantine_revision |
| +cloud_observation, operator_attestation, fenced_at |
| +confirmed_consequences |
| +timers |
| +write_sources |
| +result_claim_gen |
| +``` |
| + |
| +`state` is `prepared`, `committed` or `conflicted`. The accepted evidence and confirmed consequences become immutable when prepared. Delivery bookkeeping does not alter them. |
| + |
| +The record is durable before claim authority changes. The successful claim CAS adds its ID to `loss_refs`. A retry determines success from that claim reference, even if the HTTP response or the later `committed` update was lost. A declaration against a different operation is stale, not a new invocation of the old intent. |
| + |
| +The record and its claim references survive operation release, `free`, holder replacement and readmission. |
| + |
| +**Timers.** `timers` contains: |
| + |
| +```text |
| +knowledge: "complete" | "partial" | "unknown" |
| +source: optional {bucket, key, version_id, sha256, captured_at} |
| +entries: recovered timer registrations |
| +``` |
| + |
| +Reuse a version-pinned moves timer snapshot or a verified registry snapshot bound to the lost machine. An older snapshot is partial unless the operation’s stop evidence proves it covers the relevant registration state. A label-only snapshot with ambiguous identity cannot establish completeness. |
| + |
| +No usable source means `knowledge:"unknown"`. It must not be serialized or presented as an empty complete inventory. The declaration explicitly covers potentially lost registrations not present in a partial snapshot. This does not require a new synchronous timer-backup protocol. |
| + |
| +**Write losses.** Preserve the existing admission field: |
| + |
| +```go |
| +WritesLost *WritesLost `json:"writes_lost,omitempty"` |
| +``` |
| + |
| +Add: |
| + |
| +```go |
| +AdditionalWritesLost []WritesLost `json:"additional_writes_lost,omitempty"` |
| +LossID string `json:"loss_id,omitempty"` |
| +``` |
| + |
| +`writes_lost` remains the ordinary predecessor declaration. For a lost-target return with `src_covered == false`, the same `AdmitWriter` CAS also records the source cut session in `additional_writes_lost`. It does not create a synthetic writer session. |
| + |
| +The durable declaration’s `write_sources` freezes the operation-derived source tuples: |
| + |
| +```text |
| +reason: "target" | "source-cut" |
| +box, generation, session, region |
| +baseline reference |
| +``` |
| + |
| +The recovery admission supplies each `remote_max_txid` from the baseline it actually validated. It must not substitute the current holder’s session for the journalled source cut session, or invent a zero for unreadable remote data. |
| + |
| +Retries deduplicate by declaration ID and source session tuple. Before a loss-bearing admission is trimmed or its holder removed, its completed declarations must be copied into the durable loss record with the admission’s claim generation. Failure retains the admission evidence and blocks that removal. |
| + |
| +**Alerts.** Prepared accepted losses create durable pending alert work. Committed records drive timer-loss and write-loss alerts, including unknown timer inventory. Delivery retries per declaration and recipient. Missing recipients, missing delivery configuration or a send failure remain visible and pending; they do not count as notification. |
| + |
| +A lost response must not cause a second declaration. Notification failure must not undo fencing or permit unrecorded loss. |
| + |
| +#### D5. Lost-box transition scope, obligations and stamps |
| + |
| +Declare-lost changes only the consequences confirmed under D2 and committed through the owning operation’s legal transition. It is not a general takeover or “make free” operation. |
| + |
| +The successful claim CAS must: |
| + |
| +- Bind the exact holder, operation, phase, enrolled box and accepted declaration. |
| +- Add the box to `lost`, record its quarantine revision and retain the durable loss reference. |
| +- Remove only that box’s participation or execution authority as the operation permits. |
| +- Preserve writer-session provenance, admissions, inventory, touched history, unrelated obligations and quiet times. |
| +- Record the operation’s recovery intent before any recovery effects run. |
| + |
| +If the lost box is `op.exec`, the revocation sets `op.exec = "none"`. This is a non-installing transition: it explicitly preserves the existing `holder.stamp` in the same CAS. It does not issue a replacement stamp, session or writer. |
| + |
| +A subsequent execution handover belongs to the operation owner. Its CAS writes the corresponding stamp under R6-4: a new resulting generation for a new installation, or the exact journalled old installation stamp for an authorized return. Declare-lost must not guess the recovery destination. |
| + |
| +**Obligations.** |
| + |
| +- Remote `park-replica`, `park-generation` and moves staging chores remain owed even when their former executor is lost. |
| +- Regional work is reassigned to `region:<r>` while retaining its chore ID, original operation, retirement ID and immutable park scope. A suitable box in that region performs it. No available regional executor means the chore remains pending. |
| +- Only an exact `park-local` chore whose entire covered local storage is proven destroyed may be discharged without retirement. |
| +- A stopped instance retains its local retirement obligation. |
| +- `terminated` alone is insufficient for the destroyed-local exception because EBS volumes may survive termination. [AWS volume-preservation behavior](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/preserving-volumes-on-termination.html) |
| + |
| +Discharging a chore uses `CompleteObligation` semantics, including discovery completion. It never removes all obligations merely because they name a box. |
| + |
| +New remote retirement scopes justified by the compute fence use the declaration’s persisted `fenced_at` and Plan 4’s normal late-completion horizon. Existing scopes retain their original evidence time. Retries do not restart the horizon, and compute fencing does not cancel already accepted object-store requests. |
| + |
| +**Moves.** Preserve all restrictions in moves §7.10: |
| + |
| +- Only the exact target may take either target-loss path. |
| +- Source loss is refused. |
| +- Both target-loss paths are refused after the instance record’s commit CAS, even if the claim still appears precommit. |
| +- Rollback races commit through the existing record CAS. |
| +- A lost dirty target queues retirement of any partial return lane under its staging horizon. |
| +- Validated durable target data is recovered where available. |
| +- The destroyed-local-only exception may discharge exact local chores after commit without reopening data authority. |
| + |
| +#### D6. Lease failures, local state and phase acknowledgement |
| + |
| +The execution-lease wire contract remains exactly: |
| + |
| +```text |
| +control/hosts/<host>/exec/<box>.json |
| +``` |
| + |
| +```json |
| +{"op":"<operation ID>","expires":"<RFC3339 timestamp>"} |
| +``` |
| + |
| +TTL is **90 seconds**. Renewal is every **20 seconds**. These records are distinct from the replica lease at `<replica-prefix>/lock.json`. |
| + |
| +An active operation means a non-serving operation with an operation ID. Retained participants in `serving` do not cause lease writes. |
| + |
| +**One controller.** A box has one controller per host, serialized across processes by a dedicated lifetime lock: |
| + |
| +```text |
| +/run/carlos/hostexec/<host>.lock |
| +``` |
| + |
| +It keeps durable local state at: |
| + |
| +```text |
| +<DataDir>/.hostexec/<host>.json |
| +``` |
| + |
| +with: |
| + |
| +```text |
| +v, box, enrollment, host, incarnation, op |
| +boot_id, controller_id |
| +lease_etag, expires, safe_until_boottime_ns |
| +state: "pending" | "active" | "lost" |
| +``` |
| + |
| +Record updates use the host guard and durable-write protocol. A different boot never inherits an acknowledged lease deadline. Missing, malformed or pending state refuses active-operation work. |
| + |
| +Each PUT proposes `expires = request_start + 90s`. The local deadline is derived from that request’s start, never its response time. A five-second termination margin, matching the existing lease health machinery, starts denial and cleanup early. Linux uses a suspend-aware boot clock for cross-process deadline checks. Wall-clock rollback cannot extend authority. |
| + |
| +Only an acknowledged conditional PUT whose response arrives before the existing safety deadline can extend the local deadline. The new local state must be durable before work uses the extension. |
| + |
| +An ambiguous PUT, conditional ownership conflict or local persistence failure latches loss. Reading back a plausible body does not turn an unacknowledged PUT into an extension. A late success may supply an ETag for conditional cleanup, but cannot reopen a lost controller. |
| + |
| +Every renewal response is checked against its captured enrollment, incarnation, operation and controller ID. Responses for replaced operations or controllers are ignored. |
| + |
| +Renewal requests and claim acknowledgements must not hold the host guard while waiting on the network. The watchdog runs independently of renewal. Every root/background host writer also checks the local state and deadline under the host guard; a watchdog notification alone is insufficient. |
| + |
| +At loss, the box closes admission, cancels host work and invokes cleanup-only termination. Cleanup failure remains a separate deny and produces no stop evidence. Whole-machine suspension still defeats timely watchdog execution, so none of these states proves compute fencing. |
| + |
| +**First-phase acknowledgement.** After its first acknowledged PUT and durable local state, a participant conditionally records: |
| + |
| +```text |
| +op.journal["exec_ack.<box>"] |
| +``` |
| + |
| +The map value is a JSON-encoded string containing: |
| + |
| +```text |
| +op, enrollment, boot_id, controller_id, lease_etag, expires |
| +``` |
| + |
| +This fits the existing `map[string]string` journal. |
| + |
| +The first transition out of the operation’s initial phase requires an acknowledgement from every required participant and a fresh read of each corresponding lease with the same operation and an unexpired deadline. A later successful renewal need not retain the first acknowledgement’s ETag. |
| + |
| +The claim CAS that crosses the barrier preserves the acknowledgements. They are historical evidence that participants observed the operation, not permanent lease authority. Subsequent phases continue checking current lease health. A participant added later must acknowledge before its phase permits work. |
| + |
| +**Restart and clearing.** A new controller first joins or excludes the previous controller and completes required cleanup. It never revives a lost handle. Rejoining the same operation requires fresh claim/quarantine checks and a newly acknowledged lease. |
| + |
| +A fresh claim read showing `serving`, `free` or that the box no longer participates clears only the stale execution-lease state, as CC9 requires. It does not clear quarantine, retired stamps, another operation’s fence or failed cleanup. |
| + |
| +Release cancels and joins renewal before conditionally deleting the ETag owned by that controller. Unknown ownership or a changed ETag leaves the object alone. Unconditional delete is forbidden. |
| + |
| ## 8. Recovery owners, summarised |
| | State | Owner | Replay | |
| @@ -1538,6 +1877,245 @@ because the instance is new. |
| `carlos ops host resolve` runs. That is visible and safe. |
| - **IAM:** the versioned-object grants in §6.2 and §6.5. |
| +### 10.1 Release identity, the census and actor membership (rev 6.17, PROPOSED) |
| + |
| +**Status: proposed, not yet ruled.** D1 and D7 extend the floor from a protocol number to authenticated release identities, a persistent pre-exec guard on the system update path, and a root-signed actor directory with lifecycle and executable-activation reservations. They change the platform's update and membership machinery beyond the claim, so they wait on Paul: |
| + |
| +- **P1 (D1):** which real release becomes the floor (recommended: the first tagged release containing the completed claim and moves integrations, box identity, the census and the guarded boot and recovery paths). |
| +- **P2 (D2):** the operator attestation boundary for declare-lost (recommended: operators attest the maintained compute fence, cold quarantine start and destroyed storage; the console does not independently verify cloud state). |
| +- **P3 (D7):** an explicit, operator-approved roster of boxes and consoles as the membership authority (recommended: enrollment controls eligibility; offline actors stay required until fenced or retired). |
| + |
| +#### D1. Release identity and offline floor enforcement |
| + |
| +**Release identity.** A build string is an identifier, not an ordering. `version.Build`, `SystemRelease.Version`, `systemApplied.Version` and journal build strings remain unchanged. Neither short SHAs nor timestamps may be compared to determine release precedence. |
| + |
| +Extend `release.SystemRelease` with: |
| + |
| +```go |
| +ReleaseTag string `json:"release_tag,omitempty"` |
| +MovesProtocol int `json:"moves_protocol,omitempty"` |
| +``` |
| + |
| +`release_tag` is the approved platform release containing the exact signed artifacts. It must be canonical `vMAJOR.MINOR.PATCH`. Comparison removes exactly one leading `v`, then uses `release.ParseSemVer` and `SemVer.Compare`. Prereleases, `dev`, malformed tags and an absent authenticated mapping refuse protected work. |
| + |
| +The release workflow must authenticate the mapping: |
| + |
| +```text |
| +release_tag |
| + → version |
| + → platform |
| + → artifact SHA-256 |
| + → moves_protocol |
| +``` |
| + |
| +It must not assign this mapping merely because an upload request names a release tag. A local upload is eligible only if its bytes match an already approved artifact identity. Supporting newly certified local builds requires the same release approval and signing workflow. |
| + |
| +The deployment retains an immutable signed system-pointer snapshot at: |
| + |
| +```text |
| +control/system/router/releases/<release_tag>.json |
| +``` |
| + |
| +Its `current` entry supplies the mapping above. It uses the existing root-only system-pointer signature and validation. Its channel identifies the snapshot’s origin; it is not a second channel head. Publication is create-only. An existing different release identity is a conflict. Current pointers and their history preserve the new fields. |
| + |
| +A running process reports its actual `version.Build` and the SHA-256 of its executing image. It must not hash a replacement that now happens to occupy `/opt/carlos/carlos`. The report’s build, platform and image hash must match the signed release identity. `System.Outcome == "applied"` and a channel’s target are not proof of the running image. |
| + |
| +**Floor publication.** The shared object remains: |
| + |
| +```text |
| +control/moves-protocol.json |
| +``` |
| + |
| +with exactly the existing policy fields: |
| + |
| +```json |
| +{"min":1,"min_release":"<Paul-selected real release tag>"} |
| +``` |
| + |
| +The bracketed value is a selection requirement, not a publishable default. |
| + |
| +Before publishing it, the approved release workflow creates: |
| + |
| +```text |
| +control/moves-protocol-auth.json |
| +``` |
| + |
| +using the existing root-signed payload envelope. Its payload is: |
| + |
| +```text |
| +schema: 1 |
| +purpose: "moves-protocol" |
| +deployment_bucket: exact control bucket |
| +key: "control/moves-protocol.json" |
| +sha256: SHA-256 of the exact floor object bytes |
| +``` |
| + |
| +Scoped signing grants are refused. The authorization is written first, then the floor, both create-only. An existing identical pair is an idempotent success; any difference is a conflict. An incomplete pair never authorizes lifecycle starts. |
| + |
| +This is one floor with an authentication companion. It does not introduce a claim-only floor. |
| + |
| +**Root persistence and executable selection.** Before publication, each box installs a verified, floor-capable copy of the platform binary at: |
| + |
| +```text |
| +/opt/carlos/system/guard/carlos |
| +``` |
| + |
| +This copy supplies a new `system guard-exec` entry point. Both system-update unit shims invoke this checker before it selects either the current executable or `.prev`. The checker must work when either candidate is broken. |
| + |
| +Root maintains: |
| + |
| +```text |
| +/opt/carlos/system/guard/policy.json |
| +/opt/carlos/system/guard/identities/<release_tag>.json |
| +``` |
| + |
| +`policy.json` contains: |
| + |
| +```text |
| +schema, deployment_bucket, mode, floor, authorization |
| +``` |
| + |
| +`mode` is `unpublished`, `armed` or `enforced`. `armed` already enforces the approved prospective floor locally. Only `unpublished`, established before publication, permits legacy behavior. Missing, malformed or unreadable policy is not `unpublished`. |
| + |
| +Root verifies the signatures against its configured deployment public key and persists the policy and identity snapshots with the existing durable-write protocol: file fsync, rename and directory fsync. The agent’s request file remains a hint. Agent-written release names, hashes or floor files are not root authority. |
| + |
| +These files, the checker and its systemd integration must be outside the update journal’s restore set. Ordinary rollback must never restore an older policy, checker or unguarded launch command. |
| + |
| +Before the floor is published, every enrolled box must: |
| + |
| +1. Validate its current executable, `.prev` and outstanding journal against the proposed floor. |
| +2. Finish or safely recover incompatible outstanding updates. |
| +3. Persist the `armed` policy and compatible recovery identities. |
| +4. Report the matching floor digest and readiness. |
| + |
| +Publication waits for all required acknowledgements. This closes the interval in which a box could miss publication and boot offline using an old recovery binary. |
| + |
| +Once armed, policy does not expire. Removing `CARLOS_HOST_CLAIM`, losing the network, deleting the remote floor or presenting an older floor cannot lower it. Offline recovery may use the durable authenticated policy and identity snapshots. An unreadable local policy, missing identity or invalid signature refuses activation. A reachable but malformed or conflicting remote policy also refuses activation. |
| + |
| +The checker verifies the selected bytes before executing them, including execution for `version` or bootstrap inspection. Verification and execution must use the same protected file, without a path-replacement race. Root-owned paths and the existing system-update lock remain part of that boundary. |
| + |
| +The same check applies before: |
| + |
| +- Installing a candidate or retaining/refilling `.prev`. |
| +- Running either executable’s bootstrap plan. |
| +- Restoring a binary or unit set. |
| +- Recovering pre-gate, gated or legacy `backing-up` journals. |
| +- Starting the edge after recovery. |
| +- Activating a console or replacing a bootstrap binary through deployment scripts. |
| + |
| +A rejected recovery preserves its journal and evidence. It must return failure and prevent unsafe edge startup. `Before=carlos-edge.service` alone is insufficient: the edge needs a required successful gate and a guarded executable launch. No shell fallback may bypass refusal. |
| + |
| +Executable update and rollback reservations use D7. Offline recovery may complete an already authorized transaction from its durable reservation; it cannot invent authorization for a new downgrade. |
| + |
| +#### D7. Complete floor census and admission ordering |
| + |
| +**Membership authority.** The deployment maintains: |
| + |
| +```text |
| +control/host-actors.json |
| +``` |
| + |
| +as a root-signed, conditionally replaced directory. Its payload contains: |
| + |
| +```text |
| +v: 1 |
| +deployment_bucket |
| +revision |
| +boxes: map[box reference]box enrollment |
| +consoles: map[console ID]console enrollment |
| +holds: map[reservation ID]lifecycle reservation |
| +activations: map[reservation ID]executable activation reservation |
| +``` |
| + |
| +Box enrollments contain D2’s immutable identity fields and D3’s quarantine state. Console enrollments contain an immutable enrollment ID, a distinct console-instance ID, its serving namespaces and its deployment identity. |
| + |
| +This directory is the authoritative set of actors that may participate. Heartbeats describe members; they do not create or remove membership. |
| + |
| +The initial directory must account for every machine that can serve deployment hosts and every console process that can mutate their control state, including offline machines and independent console replicas. It is reconciled against deployment configuration, pool membership, fleet registrations and existing route/claim assignments. A missing registry, unreadable namespace or unexplained actor is incomplete evidence. |
| + |
| +Enrollment, namespace expansion and reactivation require the current floor and identity checks before authority is granted. Removal requires explicit retirement or a retained compute-fence/quarantine record. Missing heartbeats and inventory expiry never remove an actor. |
| + |
| +**Reports.** Platform boxes continue reporting at #212’s ID-keyed private status path. Fleet boxes retain: |
| + |
| +```text |
| +fleets/<fleet>/status/boxes/<label>.json |
| +``` |
| + |
| +Add these private `Box.Metrics` fields: |
| + |
| +```text |
| +enrollment |
| +moves_protocol |
| +release_tag |
| +binary_sha256 |
| +boot_id |
| +floor_sha256 |
| +floor_guard_ready |
| +host_claim_enabled |
| +retirement_receipt_sha256, when applicable |
| +``` |
| + |
| +`Metrics.Release` remains the actual running `version.Build`. The new identity fields must match D1’s signed release mapping and the enrolled machine. They must not enter `fleet.json`, public status pages or member-facing box identifiers. |
| + |
| +Every independently running console publishes: |
| + |
| +```text |
| +status/consoles/<console_id>.json |
| +``` |
| + |
| +with: |
| + |
| +```text |
| +generated_at, console_id, enrollment, process_id |
| +release, release_tag, platform, binary_sha256 |
| +moves_protocol, floor_sha256, floor_guard_ready, host_claim_enabled |
| +``` |
| + |
| +The console reports on startup and every five minutes, using the same freshness policy as boxes. A load-balanced `/health` response is not a census of its backends. |
| + |
| +A report is fresh for at most **15 minutes**, matching `3 × status.SnapshotPeriod`, and must not be more than **5 seconds** in the reader’s future. Missing fields mean unsupported. An absent, stale, malformed or identity-mismatched report makes the required census fail. |
| + |
| +**Required actors.** For a protected start, the console checks: |
| + |
| +- Every active box that could serve the host under its fleet, pool and placement rules. |
| +- Every explicitly required participant, even if outside that candidate set. |
| +- Both source and target regions for a move. |
| +- Every console able to mutate the relevant host namespace. |
| +- Its own running process directly, in addition to the published console reports. |
| + |
| +Unknown placement scope refuses the start rather than narrowing the set. Offline enrolled actors remain required. An actor may be excluded as fenced only through its durable quarantine/retirement authority and maintained compute fence, never because it stopped reporting. |
| + |
| +Every required actor must meet the protocol minimum, authenticated release minimum, root enforcement readiness and matching floor digest. Protected starts also require the lifecycle flag to be enabled in the participating processes. |
| + |
| +These checks govern lifecycle starts, not continued ordinary serving traffic. |
| + |
| +**Serializing starts and executable changes.** Separate object reads cannot atomically exclude a concurrent downgrade. The directory therefore records reservations. |
| + |
| +A lifecycle reservation contains: |
| + |
| +```text |
| +id, host, op_id, expected_claim_etag |
| +floor_sha256 |
| +actors: exact enrollment references |
| +releases: authenticated actor release identities |
| +``` |
| + |
| +The console creates it with a directory CAS after the complete census. That CAS refuses an overlapping executable activation reservation. The claim’s start CAS records the reservation ID in `op.intent["actors_hold"]`. The first-phase lease barrier verifies that reservation before proceeding. |
| + |
| +Reservations persist while the operation is held. A timeout does not release them. An ambiguous failed start is reconciled against the exact claim: if cancellation is needed, its expected claim revision must first be invalidated by a conditional claim write so a delayed start cannot subsequently succeed. |
| + |
| +An executable activation reservation names the affected enrollments, authorized current/target/recovery hashes and floor digest. It excludes overlapping lifecycle reservations and blocks new ones for those actors until the executable transaction finishes. The console creates it through the existing authorized system-update workflow; it is not an agent-written permission. |
| + |
| +For rev 6.17, the complete update transaction, including automatic rollback, holds this reservation. This prevents an operation from starting against the candidate and then being downgraded by its prepared recovery path. |
| + |
| +Root verifies and durably retains the signed reservation before beginning the transaction. Offline recovery may finish that exact transaction. Missing authorization, changed identities or a different target refuse activation. Releasing the reservation requires verified completion and fresh running reports. A crash leaves it conservatively held. |
| + |
| +Private console/bootstrap replacement workflows must use the same reservation and floor boundary before the deployment is enabled. |
| + |
| +The floor, actor identities and reservation are revalidated at the operation’s start boundary. A failed gate writes no lifecycle intent or effects. If reservation creation succeeded but later work failed, reconciliation releases only that reservation through the rules above. |
| + |
| +Before activation, legacy delete behavior remains only where §10 explicitly permits it. A caller must never begin a claim-based delete and then fall through to legacy cleanup. |
| + |
| ## 11. Simulation |
| The joint executable model, being built by the moves session, decides |