spec: host lifecycle claim rev 4, after joint round 2
Per-incarnation replica locations held in the claim (no cross-incarnation reliance on a completion bound); app admission closed and drained by purge; a cross-process flock guard with fresh claim reads; door reads bracketed by two claim observations; all-version archive with separate archive and deletion sets; cancelled creates cleaned up; adoption discovery covers every other box. Joint R2 findings copied unedited.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 files changed,
+329
−24
docs/superpowers/specs/2026-10-02-host-lifecycle-claim-design.md+121 −24docs/superpowers/specs/CODEX-FINDINGS-JOINT-R2.md+208 −0
CI passed — 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 2f79d7115..7bcf696ab 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-02, **rev 3.1** (rev 3 plus CC14). A small primitive cited by: |
| +Design, 2026-10-03, **rev 4**. A small primitive cited by: |
| - **#199**, deleting an instance and parking its data: |
| `2026-10-02-replica-ownership-design.md`, which this spec supersedes |
| @@ -17,7 +17,11 @@ I-7, adopts its replacement for D4, and answers N1, N3 to N10, N12 and the |
| carried-forward R3-12. The moves-only findings (N2's move side, N11, N13, |
| N14, N15) are the moves spec's. §12 maps every finding to its section. |
| -**Rev 3** folds in CC1 to CC13, the claim changes required by the joint |
| +**Rev 4** answers the claim-side findings of joint round 2 |
| +(`CODEX-FINDINGS-JOINT-R2.md`, cited `J2-…`): R2-1, R2-2, R2-3, R2-4, |
| +R2-6, R2-7, and the legacy local-coverage gap noted under N5. |
| + |
| +**Rev 3** folded in CC1 to CC13, the claim changes required by the joint |
| executable model (`internal/sim/hostlife/` at 1486345c on |
| `design-moves-any-region`, moves spec §19.4). §12 maps them too. |
| @@ -66,7 +70,8 @@ replaced whole with conditional writes (`If-Match` on the ETag read). |
| "family": "instance", |
| "key": "apps/bab/jam/instances/shop.bab.oncarlos.com.json", |
| "account": "bab", "app": "jam", "name": "", |
| - "incarnation": "<hex>", "created_at": "<record CreatedAt>" |
| + "incarnation": "<hex>", "created_at": "<record CreatedAt>", |
| + "location": "instances/shop.bab.oncarlos.com/<incarnation>" |
| }, |
| "op": { |
| "kind": "serving", |
| @@ -92,7 +97,8 @@ replaced whole with conditional writes (`If-Match` on the ETag read). |
| ], |
| "lost": [], |
| "retired_incarnations": ["<hex>", "…"], |
| - "quiet_after": {"ie": "<RFC3339>"} |
| + "quiet_after": {"ie": "<RFC3339>"}, |
| + "cancelled_creates": [{"key": "…", "incarnation": "…", "until": "…"}] |
| } |
| ``` |
| @@ -102,6 +108,13 @@ Field rules: |
| successful claim write. It is not an installed-data generation (J-D2). |
| - **`holder`** names the exact record, so a sidecar is never looked up as |
| an instance. It is absent only while the host is **free**. |
| +- **`holder.location`** is the replica location of this incarnation |
| + (J2-R2-1, §3a). A new incarnation gets |
| + `instances/<host>/<incarnation>`. An adopted legacy host gets |
| + `instances/<host>`, its existing location. It is set when the holder is |
| + set and never changes while that holder holds the host. Same-incarnation |
| + generations within it (moves, push) are the moves spec's, scoped by |
| + operation id. |
| - **`op.intent`** holds the operation's reconstruction inputs. It is |
| written in the **same CAS** that starts the operation, before any other |
| object is touched (J-N4). |
| @@ -128,6 +141,9 @@ Field rules: |
| - **`quiet_after`**, one entry per region, is the earliest time a new |
| install may write the host's namespace in that region (§6.2a, CC1). |
| Like `retired_incarnations`, it survives `free`. |
| +- **`cancelled_creates`** lists creates that recovery cancelled, with the |
| + record key and incarnation to clean up if a late write lands (§5.1, |
| + J2-R2-7). It survives `free`. |
| ## 3. Invariants |
| @@ -203,6 +219,28 @@ The staging cleanup's horizon (§6.2a) covers that bound. The moves spec |
| calls this its third staging rule (rev 5 §7.3). The joint model fails 156 |
| of 30,000 seeds without it. |
| +**C3a. No storage location is shared between incarnations (J2-R2-1).** |
| +Fencing by source version stops a late call from touching the wrong |
| +**source** object. It doesn't stop a late copy or PUT from creating a new |
| +object at a **destination**. AWS separates accepting a copy from |
| +completing it, and S3's 15-minute signature limit bounds acceptance, not |
| +how long an accepted request keeps running. |
| + |
| +So the claim does not rely on a completion bound for anything that |
| +crosses incarnations. Every incarnation writes only under its own |
| +`holder.location`, and a successor's location is a different prefix. A |
| +request from an old incarnation that completes arbitrarily late therefore |
| +lands in that old incarnation's location, which nothing admitted will |
| +ever read again. Any such straggler is found and parked by the next |
| +retirement listing of that location. |
| + |
| +Restore reads **only** `holder.location`, plus the moves spec's |
| +generation within it, taken from the claim in the same fresh admission |
| +read (C4). Within one incarnation, the moves spec owns operation-scoped |
| +generation identities and their restore contract. The §6.2a horizon |
| +stays, as defence in depth and for the legacy location, which is shared |
| +by construction: an adopted host's `instances/<host>`. |
| + |
| **C4. Fresh admission.** Admission reads the claim directly, never from a |
| listing snapshot. Admission checks happen at: |
| @@ -258,6 +296,11 @@ new operation will write (CC11). A pending chore there must finish first. |
| ### 5.1 Create (R3-12, J-N4) |
| +0. **App admission (J2-R2-2).** Register the create in |
| + `apps/<acct>/<app>/admission.json` with a CAS, as `{create id, host}`, |
| + which succeeds only while the app's admission is `open`. The |
| + registration is removed when the create reaches `serving` or is |
| + cancelled. |
| 1. The console assigns `CreatedAt` = now (UTC, nanoseconds). A caller |
| cannot supply it. `CreateRecord` and `CreateSidecarRecord` refuse a |
| zero or caller-supplied time on this path. |
| @@ -271,7 +314,12 @@ new operation will write (CC11). A pending chore there must finish first. |
| `control/hosts/` every few minutes and handles claims whose `op` is not |
| `serving` and older than five minutes: |
| - the record exists with this incarnation: do step 4; |
| - - it is authoritatively absent: CAS back to free; |
| + - it is authoritatively absent: CAS back to free, adding the |
| + incarnation to `retired_incarnations` and `{key, incarnation, until: |
| + now + 1h}` to `cancelled_creates`. A late record PUT that lands |
| + anyway is an **orphan**. It is never admitted (C1). The pass |
| + deletes it with `DeleteIfMatch` when its incarnation matches a |
| + `cancelled_creates` entry, and retries until `until` (J2-R2-7); |
| - anything else: wait. |
| ### 5.2 Delete and park (#199) |
| @@ -316,6 +364,13 @@ site now uses: |
| recovery pass drives those phases in order: `reserved → adopted → |
| claimed → torn-down → retired → done`. Only the purge's own completion |
| removes it. A host claim going `free` never does. |
| +1b. **Close and drain admission (J2-R2-2).** CAS the app's |
| + `admission.json` to `closed`, so no new create can register. The purge's |
| + host set is then the app's records **plus** every host still |
| + registered as an in-flight create. Purge waits for each registered |
| + create to reach `serving` (then it is purged like the others) or to be |
| + cancelled by recovery, before fixing its host set. This is tested with |
| + at least two hosts. |
| 1a. **Adopt first (CC6).** Run the one adoption routine (§9) for every |
| record of the app that has no claim, before deciding which hosts the |
| app holds. |
| @@ -383,24 +438,29 @@ A manifest belongs to exactly one retirement event and is never reused. |
| 1. **Lease.** Take the replica lease on each location the owner can read. |
| 2. **Snapshot.** List the location's object versions |
| - (`ListObjectVersions`). From that listing, take every current version |
| - except the live `lock.json`. A coordination lock is never data and |
| - never enters a manifest (J-N6). |
| -3. **Copy** each current version by `versionId` to its version-keyed |
| - destination. **Skip any destination that already exists**, so progress |
| + (`ListObjectVersions`), every page, and refuse on any listing error. |
| + From it, form two sets (J2-R2-6), both excluding the live `lock.json` |
| + (J-N6): |
| + - the **archive set**: every non-marker version, current and |
| + noncurrent; |
| + - the **deletion set**: every version in the snapshot, delete markers |
| + included. |
| +3. **Copy** each archive-set version by `versionId` to its |
| + version-keyed destination. **Skip any destination that already exists**, so progress |
| accumulates across retries (CC2). |
| -4. **Write the manifest only once every copy exists.** It records each |
| - entry's source key, source version id, size, and destination key and |
| - version. A manifest that already exists is never rewritten. |
| -5. **Delete** every version of each manifest key whose version id is in |
| - the snapshot, **by version id**. That includes noncurrent versions, so |
| - deleting the current one cannot resurrect an older one. A version that |
| +4. **Write the manifest only once every copy exists.** It records the |
| + archive set (each source key, source version id, size, and destination |
| + key and version) **and the deletion set**, delete markers included. A manifest that already exists is never rewritten. |
| +5. **Delete** every version in the manifest's deletion set, **by version |
| + id**, markers included. A noncurrent version is never left to |
| + resurface, and the head of a key is never a marker hiding data that |
| + wasn't archived. A version that |
| is not in the snapshot is left alone, which keeps a later |
| incarnation's object at the same key safe even from a late delete. |
| 6. **Release** this worker's own `lock.json` version, conditionally, after |
| the obligation's completion CAS. |
| 7. **Retry:** if the manifest exists, go straight to deletes of its |
| - recorded versions. If it doesn't, re-list (step 2) and continue. The |
| + recorded deletion set. If it doesn't, re-list (step 2) and continue. The |
| copies already made are kept, because their destinations are keyed by |
| version. |
| @@ -461,9 +521,20 @@ Every host-specific local write on a box goes through |
| - the admin token; |
| - the timer registry. |
| -The guard is a per-host in-process lock. Under it, the box re-reads the |
| -admission state it caches (C4) and refuses if the incarnation or the |
| -operation has moved on. |
| +The guard is an **exclusive `flock` on `/run/carlos/hostguard/<host>.lock`**, |
| +shared by every OS process that writes host state (J2-R2-3): |
| + |
| +- the agent; |
| +- `carlos-adopt.service`; |
| +- `carlos-config-sync.service`; |
| +- any later root command. |
| + |
| +Under it, the writer does a **fresh** claim read, never a cached one, in |
| +line with C4. It refuses if the incarnation or the operation has moved |
| +on, or if the config or release identity it selected is no longer the |
| +held one. Only then does it write. A process that validated, then paused, |
| +re-validates under the lock, so a pause before taking the lock is |
| +harmless. |
| Local retirement (§6.3) takes the same guard, then writes a retired |
| marker for the incarnation, so any later writer for that incarnation is |
| @@ -472,11 +543,22 @@ either finishes before retirement takes the guard, or fails at its next |
| write. |
| The guard is local because these writers are local. The claim decides |
| -what each writer is allowed to do. |
| +what each writer is allowed to do. The model must run these as |
| +independent processes, with pauses between validation and the write. |
| ### 6.5 Door reads pinned to versions (J-N10) |
| -The pull door's listing resolves each LTX coordinate to an exact object |
| +The pull door's listing: |
| + |
| +1. reads the claim (observation O1); |
| +2. authorizes the caller against O1's holder; |
| +3. selects versions **only under `O1.holder.location`**; |
| +4. reads the claim again (O2); |
| +5. refuses if O2's `gen` differs from O1's in holder or op |
| + (J2-R2-4). |
| + |
| +Each handle it signs carries the selected version, the location and O1's |
| +holder incarnation. So it resolves each LTX coordinate to an exact object |
| version and returns an opaque, console-signed handle. A file request |
| names that handle, and the console opens `key?versionId=`. A handler that |
| stalls between authorization and open can therefore only ever read the |
| @@ -582,8 +664,16 @@ that needs one. That claim is written create-only, with: |
| - `touched.regions` set to the record's region; |
| - `touched.boxes` set to the box, if it has local state. |
| -**Discovery at adoption (CC4).** The adoption CAS also adds a |
| -`park-replica` obligation for every deployment region other than the |
| +**Discovery at adoption (CC4, J2 N5 note).** The adoption CAS also adds |
| +a `park-replica` obligation for every deployment region other than the |
| +record's, **and a `park-local` obligation for every deployment box other |
| +than the record region's serving box.** Local state on any other box |
| +cannot be the live instance's. `touched.complete` becomes true only once |
| +**all** of those acknowledge, regions and boxes alike. A rev 3 rule let an |
| +adopted host become complete while an old box still held its database, |
| +key or env files. |
| + |
| +The adoption obligations cover every deployment region other than the |
| record's. Data there cannot be the live lineage. Each region's owner parks |
| it (§6.2, §6.2a). `touched.complete` becomes true once they all |
| acknowledge, so an adopted host stops carrying another region's residue |
| @@ -669,6 +759,13 @@ the model catches it. |
| | CC11 | §4 | |
| | CC13 | §6.1 | |
| | CC14 | C3, the staging-work lease exception | |
| +| J2-R2-1 | C3a, §2 `holder.location` | |
| +| J2-R2-2 | §5.1 step 0, §5.3 step 1b | |
| +| J2-R2-3 | §6.4 | |
| +| J2-R2-4 | §6.5 | |
| +| J2-R2-6 | §6.2 | |
| +| J2-R2-7 | §5.1 step 5, §2 `cancelled_creates` | |
| +| J2 N5 note | §9 | |
| ## 13. S3 behaviour this relies on, verified |
diff --git a/docs/superpowers/specs/CODEX-FINDINGS-JOINT-R2.md b/docs/superpowers/specs/CODEX-FINDINGS-JOINT-R2.md| new file mode 100644 |
| index 000000000..e411ff3e7 |
| --- /dev/null |
| +++ b/docs/superpowers/specs/CODEX-FINDINGS-JOINT-R2.md |
| @@ -0,0 +1,208 @@ |
| +**A, B and C are not ready to support Phase 1 implementation planning.** The revisions resolve many round-1 findings, but several remaining safety and recovery gaps are hidden by stronger or more atomic behavior in the model. |
| + |
| +I read AGENTS.md first and reviewed the requested revisions with three independent GPT-6 reviewers; Opus was unavailable. The worktree matches B at `4ec3282b` and C at `7a3e94e7`. Nothing was changed. |
| + |
| +Both requested test commands failed before compilation: |
| + |
| +```text |
| +go test ./internal/sim/hostlife/ -count=1 |
| +HOSTLIFE_SEEDS=30000 go test ./internal/sim/hostlife/ -run TestHostLifecycle -count=1 |
| + |
| +go: creating work dir: mkdir /home/paulca/.cache/tmp/go-build…: read-only file system |
| +``` |
| + |
| +The conclusions below are therefore **static-review findings**, not reproduced failing seeds. I inspected `mutate.sh` but could not independently confirm its published counts. |
| + |
| +**1. Round-1 dispositions** |
| + |
| +“Resolved” means the specific contract or modeled behavior is addressed—not that implementation correctness is established. Model dispositions include fidelity to the specified behavior. |
| + |
| +| Finding | Spec | Model | Why | |
| +|---|---|---|---| |
| +| D1: box/phase admission | Resolved | Resolved | A C4 and B §19.1 require exact executor and phase; model implements these, although role and executor checks are combined. | |
| +| D2: revision versus generation | Resolved | Resolved | Claim revision is separate from installed operation/incarnation identity. | |
| +| D3: reconstructible stop evidence | Resolved | Partly | A §6.1/B §5.3 require containment and draining; model atomically removes known processes without spawn-in-flight, descriptor or cleanup-failure states. | |
| +| D4: staging permissions/exclusion | Partly | Partly | Shared lease and version seal address the original race; timing proof remains incomplete, and model lease acquisition does not exclude workers of the same operation. | |
| +| D5: retirement-event identity | Resolved | Resolved | Separate retirement IDs prevent initial-target, failed-target, source and staging manifests colliding. | |
| +| D6: independent execution fencing | Resolved | Partly | Compute fencing replaces lease expiry; model represents verification as a `Destroyed` Boolean, omitting cloud-identity/proof handling. | |
| +| D7: transitions and recovery writer | Partly | Partly | B §7.5 lets the source complete target handoff; K4 still reserves that transition to the receiving participant. Model also uses a different claim/record ordering. | |
| +| D8: obligations block release | Resolved | Resolved | Operation-owned obligations block serving release; all obligations block free. | |
| +| I-7: local retirement inventory | Resolved | Partly | A §6.3 names flat siblings and nested state; model clears aggregate fields without path-level retirement or durability steps. | |
| +| N1: late storage completion | Partly | Partly | Version deletes are sound; source-version copies do not fence destinations, and request completion within `H` is assumed by the model. | |
| +| N2: delayed staging rounds | Partly | Partly | Forward seals address content corruption; cleanup depends on timing assumptions. Return-lane paused signers are not modeled. | |
| +| N3: declare-lost double writer | Resolved | Partly | Compute fencing and quarantine address the original failure; external fencing verification remains abstract. | |
| +| N4: cross-object transaction gaps | Partly | Partly | Durable intent helps, but late create publication is unspecified and the model collapses/reverses B’s record/claim transactions. | |
| +| N5: incomplete legacy discovery | Partly | Partly | Remote replica acknowledgements set `complete=true` without discovering historical local files; the model never seeds that local residue. | |
| +| N6: parking live coordination locks | Resolved | Resolved | Live locks are excluded from manifests, emptiness checks and subsequent retirement scans. | |
| +| N7: same-operation manifest collision | Resolved | Resolved | Per-retirement destinations address the collision. | |
| +| N8: stale config/adoption writers | Partly | Partly | An in-process guard cannot cover separate root commands; model lacks operation and config-version identity for delayed writes. | |
| +| N9: purge teardown ordering | Partly | Partly | Reservation precedes teardown, but reservation checking and create admission are not serialized across objects. | |
| +| N10: stalled door reads | Partly | Partly | Version handles protect a stalled open; authorization-to-version-selection remains a separate unprotected interval. | |
| +| N11: pre-flip recovery rechecks its writes | Resolved | Resolved | Durable recovery phases prohibit baseline verification after entering `probing`. | |
| +| N12: automatic old-binary restoration | Resolved | Partly | Automatic recovery and rollout ordering are covered in prose; model uses a Boolean floor rather than enrollment and executable activation paths. | |
| +| N13: abort actor lacks permissions | Resolved | Resolved | Target-region actor owns staging retirement; M17 remains push-only. | |
| +| N14: timer runner’s prune | Resolved | Resolved | Canonical digests exclude completed history while preserving active timer state. | |
| +| N15: cut budget | Partly | Partly | Deadline now bounds commit, not the promised stop-to-open interval; T11 also budgets one settle despite permitting retries. | |
| + |
| +For N5 specifically, A §9’s new completion rule defeats its own fallback: once foreign **bucket** obligations finish, §5.2 no longer requires every box to acknowledge. An old box’s database, key or sibling env files can remain unretired when the host becomes free. |
| + |
| +**2. Model fidelity** |
| + |
| +| Abstraction | Fault it can hide | Concrete model change | |
| +|---|---|---| |
| +| Permissions checked on modeled mutations; reads/listings access maps directly | A worker depends on a read or listing its credentials cannot perform | Route Get, Head, ListVersions, control-bucket access and copy-source reads through principal-aware APIs. | |
| +| Exact base-version pointer plus historical LTX selection | Actual prefix restore selects a stale current object after a late destination write | Represent real replica keys, regional generation markers and the actual restore-selection algorithm. | |
| +| Every request completes by `issue + H` | An accepted request completes after retirement has acknowledged | Separate signing, server acceptance, timeout and completion; allow completion beyond `H` unless an enforced bound exists. | |
| +| Check/sign and check/local-write are atomic | A process pauses after authorization or lease validation, then acts after revocation | Split these into independently scheduled events, including independent root processes. | |
| +| Replica “lease acquisition” is unconditional PUT; staging lease accepts the same op | Two same-operation workers execute conflicting work simultaneously | Give each worker a lease identity and model conditional acquire, renewal, expiry and release. | |
| +| Claim transitions followed immediately by `fixRecord` | Crashes between B’s record-first transition and claim update are missed | Model both objects, conditional writes, ambiguous responses and recovery in B’s specified order. | |
| +| Atomic `localPark` | Crash between rename, journal, directory fsync and acknowledgement loses replay information | Represent individual artifacts and durability barriers; inject crashes between them. | |
| +| Complete map listings | A failed/truncated/paginated inventory is treated as complete | Model pages, continuation tokens, failures after successful pages, and mutations between pages. | |
| +| Fixed clock skew; one global time | Clock steps or pause margins invalidate signing horizons and cross-box deadlines | Separate service time, wall time and monotonic time; exercise clock adjustment and post-check pauses. | |
| +| Old executable is a Boolean fault | Old console, stale enrollment report or pre-floor updater bypasses the floor | Model enrollment and distinct console, agent and boot-updater versions and activation decisions. | |
| +| Config is an incarnation integer; first failed probe never creates a process | Same-incarnation config changes and writes before first readiness failure disappear | Track bundle/release/environment versions; separate spawn from readiness success/failure. | |
| + |
| +Two additional scope gaps matter: |
| + |
| +- **One host** prevents testing purge’s multi-host enumeration and admission protocol. |
| +- **Return staging** bypasses the forward path’s lease and stalled-signer machinery at [moves_test.go:808](/home/paulca/amadan.net/carlos/platform-moves-design/internal/sim/hostlife/moves_test.go:808), and uses `now + H` rather than the forward staging horizon. |
| + |
| +The time scaling also reverses an important relationship: model `H=60` is shorter than `cutBudget=320`; production `H` exceeds fifteen minutes while the default cut budget is 120 seconds. Successful schedules in one configuration cannot establish the other’s timing behavior. |
| + |
| +**Mutation results: useful, but not trustworthy as protocol-wide proof.** |
| + |
| +- **37 caught alone:** plausible evidence that these switches affect this model. The counts are unverified here, and some detections are non-convergence rather than the claimed safety failure. |
| +- **9 with partners:** useful interaction evidence, but additional failing seeds do not by themselves establish which guard prevented which fault. Preserve and compare minimized traces. |
| +- **2 liveness-only:** supported only within the represented state space. In particular, configuration is too coarse to establish a general claim about real config failures. |
| +- **The admission/hold/start-check triple:** materially misleading. Its third fence is stronger than B specifies; finding R2-8 below explains why. |
| +- **`restore-lineage-filter`:** correctly described as adding no detection beyond its partner. That is unproven protection, not proof of redundancy. |
| +- **`readiness-quiet`:** its unreachability is credible under the encoded ordering: acknowledgements already wait for the horizon and conflicting obligations block new operations. It does not validate the horizon itself. |
| +- **Harness classification:** build failures and timeouts can be counted as `CAUGHT`; finding R2-10 below prevents accepting the summary without underlying logs. |
| + |
| +**3. New failure traces and model defects** |
| + |
| +These include failures in the proposed round-1 remedies. They do not reopen M1–M18. |
| + |
| +**R2-1 — P1: storage fencing remains incomplete at the destination and completion boundary.** |
| +**A C3/§6.2a; B §§6.4, 7.6, 15.** |
| + |
| +A copy naming an exact **source** version still creates a new destination version. Consider an accepted install-copy request whose client times out: a retry succeeds, the operation advances, and the earlier request completes after retirement or namespace reuse. The source-version condition does not protect the destination. |
| + |
| +The proposed horizon does not yet exclude that schedule. The 14/16-minute experiment tests whether S3 accepts an old signature, not how long an already accepted operation may continue. AWS also distinguishes copy acceptance from completion and provides separate destination conditions. [AWS CopyObject documentation](https://docs.aws.amazon.com/AmazonS3/latest/API/API_CopyObject.html). |
| + |
| +The model assumes completion within `H`, then further protects restore with exact `BaseVer` and historical lineage selection at [protocol_test.go:178](/home/paulca/amadan.net/carlos/platform-moves-design/internal/sim/hostlife/protocol_test.go:178). B specifies neither that persistent restore pointer nor that selection algorithm. |
| + |
| +**Fix:** define destination-side fencing or immutable generation identities and the corresponding restore contract. If correctness still depends on `H`, establish an enforceable server-completion bound covering every request type; client timeout is insufficient. Model destination writes after publication and retirement. |
| + |
| +**R2-2 — P1: purge can miss a creator already past its reservation check.** |
| +**A §§5.1–5.3; [console_test.go:76](/home/paulca/amadan.net/carlos/platform-moves-design/internal/sim/hostlife/console_test.go:76).** |
| + |
| +A creator reads “no purge reservation” and pauses. Purge reserves the app, enumerates its hosts and claims them. The creator then claims a previously unused hostname and publishes its record while purge tears down stores or deletes the app prefix. |
| + |
| +The reservation check and host CAS concern different objects. The model performs both atomically inside `userCreate`. |
| + |
| +**Fix:** register in-flight creators through an app-level admission protocol. Purge must close admission and drain or reconcile those creators before fixing its host set and beginning teardown. Test at least two hosts. |
| + |
| +**R2-3 — P1: the local host guard excludes only threads in one process.** |
| +**A §6.4; [background_test.go:222](/home/paulca/amadan.net/carlos/platform-moves-design/internal/sim/hostlife/background_test.go:222).** |
| + |
| +Release adoption and config work also run through separate root commands: [carlos-adopt.service:17](/home/paulca/amadan.net/carlos/platform-moves-design/internal/host/units/carlos-adopt.service:17) and [carlos-config-sync.service:25](/home/paulca/amadan.net/carlos/platform-moves-design/internal/host/units/carlos-config-sync.service:25). |
| + |
| +One process can validate, pause, then write an old env file or release pointer after another process retires or holds the host. An in-process mutex cannot serialize them. A’s cached-admission wording further conflicts with C4. |
| + |
| +**Fix:** use a guard shared by all relevant OS processes, with fresh durable incarnation/operation checks under it. Include the selected config/release identity. Model independent processes and pauses between validation and the filesystem mutation. |
| + |
| +**R2-4 — P1: pinning the door’s read leaves authorization-to-version-selection exposed.** |
| +**A §6.5; [console_test.go:436](/home/paulca/amadan.net/carlos/platform-moves-design/internal/sim/hostlife/console_test.go:436).** |
| + |
| +The console authorizes a listing for incarnation A, then pauses before resolving object versions. The host is deleted and recreated for B. The listing resumes and signs a handle for B’s current version. Opening that exact version preserves the wrong authorization decision. |
| + |
| +The model selects versions atomically with authorization and filters on an incarnation tag that ordinary legacy LTX objects need not have. |
| + |
| +**Fix:** bind authorization and version selection to the same holder/operation observation—for example, validate the claim around selection and reject if it changed. Sign the selected identity and authorization scope. Inject the pause **before version selection**, not only before object open. |
| + |
| +**R2-5 — P1: a config update can make automatic rollback permanently fail.** |
| +**B §§7.6–7.8, 9.1.** |
| + |
| +Admission pins bundle V1. A normal config/secrets update publishes V2 after the cut. Target `configured` rejects the changed ETag and starts rollback. Source recovery also renders current configuration, but every recovery probe must satisfy the pinned V1 inputs. Each retry encounters the same mismatch. |
| + |
| +No specified mechanism preserves/render-selects V1, serializes its publication, or authorizes a recovery-specific replacement. Model assignments such as [moves_test.go:892](/home/paulca/amadan.net/carlos/platform-moves-design/internal/sim/hostlife/moves_test.go:892) cannot represent V1 versus V2. |
| + |
| +**Fix:** choose one complete policy for updates during a move and for recovery: durable pinned inputs, serialized publication, or explicitly validated recovery inputs. Test a change after admission and after a writable probe. |
| + |
| +**R2-6 — P2: A’s park manifest cannot replay its stated deletion set.** |
| +**A §6.2 steps 2–7; [protocol_test.go:379](/home/paulca/amadan.net/carlos/platform-moves-design/internal/sim/hostlife/protocol_test.go:379).** |
| + |
| +A selects/copies current versions and records those copies, then requires deleting noncurrent snapshot versions too. After a crash, retry knows only the manifest’s recorded versions. Historical data behind a delete marker is particularly ambiguous. |
| + |
| +B §6.4 and the model instead snapshot every version; the model archives every non-marker version. They validate a different algorithm. |
| + |
| +**Fix:** persist separate archive and deletion sets, including the policy for delete markers, or explicitly adopt all-version archiving in A. Test crashes after manifest publication with several historical versions and a marker at the head. |
| + |
| +**R2-7 — P2: create recovery can free the claim before record publication completes.** |
| +**A §5.1 step 5 and §8; [console_test.go:498](/home/paulca/amadan.net/carlos/platform-moves-design/internal/sim/hostlife/console_test.go:498).** |
| + |
| +Recovery observes an absent record and frees the creating claim. An earlier submitted record PUT completes afterward. Admission refuses the orphan, but the record also blocks ordinary recreation; A supplies no explicit replay rule for removing it safely. |
| + |
| +The model’s record creation is synchronous, and its recovery pass contains additional orphan deletion absent from A’s operation table. |
| + |
| +**Fix:** specify publication/cancellation recovery, including conditional orphan cleanup or roll-forward from durable intent. Model late control-bucket writes and ambiguous responses. |
| + |
| +**R2-8 — P2: the advertised third admission fence does not exist in B’s start check.** |
| +**B §§5.1, 14.2.5; [background_test.go:77](/home/paulca/amadan.net/carlos/platform-moves-design/internal/sim/hostlife/background_test.go:77).** |
| + |
| +After target commit, before source retirement, the source’s old DB marker, files marker and **source-region** bucket marker still agree. That satisfies B’s specified start check. |
| + |
| +The model instead compares the source with the globally committed **target** lineage, updated at [moves_test.go:658](/home/paulca/amadan.net/carlos/platform-moves-design/internal/sim/hostlife/moves_test.go:658). |
| + |
| +Thus, with only `exec-admission` and `hold-op-open` removed, a delayed open can make the source ready and let it wake beside the target under B’s rules. The model invents a third barrier and requires the triple mutation. |
| + |
| +**Fix:** model regional generation markers faithfully and run the pair against this exact schedule. Correct the redundancy claim, or explicitly add a global committed-generation check to the protocol. The unmutated admission/open protections still prevent this trace. |
| + |
| +**R2-9 — P2: the final cold-restore check repairs storage before checking it.** |
| +**[sim_test.go:290](/home/paulca/amadan.net/carlos/platform-moves-design/internal/sim/hostlife/sim_test.go:290).** |
| + |
| +Before cold restore, the checker directly writes the complete surviving local DB into the bucket. A missing or stale replica can therefore pass because the checker just supplied the missing data. |
| + |
| +Moreover, write-loss checks run only for the final holder. A bad move followed by deletion/recreation can escape that final assertion. |
| + |
| +**Fix:** check storage at explicit promised durability boundaries without modifying it. Check each terminal operation before a later deletion can erase the evidence. Exercise cold restart with local state unavailable after a completed checkpoint. |
| + |
| +**R2-10 — P2: mutation infrastructure errors count as detections.** |
| +**[mutate.sh:40](/home/paulca/amadan.net/carlos/platform-moves-design/internal/sim/hostlife/mutate.sh:40).** |
| + |
| +Any nonzero Go exit becomes `CAUGHT`, including compilation failure, read-only cache failure and timeout. `MISSED` also does not make the sweep fail. |
| + |
| +**Fix:** count only a parsed invariant violation with a reproducible seed as detection. Classify infrastructure failures as errors, preserve logs, and fail the sweep on errors. Declare expected redundant/liveness-only outcomes separately. |
| + |
| +There are no additional material P3 findings. |
| + |
| +**4. Verdicts** |
| + |
| +| Artifact | Ready for Phase 1 planning? | Reason | |
| +|---|---|---| |
| +| **A — claim rev 3.1** | **No** | Purge/create admission, cross-process local exclusion, authorization/version selection, legacy local coverage and storage-completion guarantees remain incomplete. | |
| +| **B — move rev 5** | **No** | Inherits A’s blockers; config-update recovery is incomplete; record/claim ordering and handoff authority remain inconsistent; the cut promise still differs from the enforced deadline. | |
| +| **C — model** | **No, as the planning gate** | Useful for finding faults, but stronger restore/admission behavior, atomic transaction boundaries and a repairing final oracle prevent its green results from validating the specified protocol. | |
| + |
| +The next round should begin with deterministic regressions for these traces, faithful record/claim and regional-marker behavior, and a corrected mutation harness. Then rerun the seed sweep. Increasing seed counts alone will not exercise states the model cannot represent. |
| + |
| +--- |
| + |
| +**FIRST PROMPT:** |
| +Joint independent, read-only round-2 review of the two specs and executable model. |
| + |
| +**WHAT:** |
| +Assess prior fixes, model fidelity, new faults and Phase 1 readiness. |
| + |
| +**STATUS:** |
| +Review complete. All three verdicts are not ready. Go execution was sandbox-blocked. |
| + |
| +**NEEDED:** |
| +Nothing for this review. |
| + |
| +**RECOMMENDED:** |
| +Resolve the protocol gaps and model mismatches, then rerun deterministic regressions and mutations. |
| + |
| +**SESSION:** |
| +Safe to close. No files changed; nothing remains running. |
| \ No newline at end of file |