carlos / platform Public

spec: move-any-app rev 7, part 2: the gap closed, the final mutation table

The biased schedule, rev 7's results (30,000 and 100,000 seeds clean on
claim rev 5), model faults 25 to 27, the 64-rule mutation table with no
rule left unexercised, §7.10's return-lane park, and §22.6.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 7e0c025ee6765f0e4023879c3f49ab73d6f0ef40 parent b26731c
1 file changed, +117 −82
  • docs/superpowers/specs/2026-10-02-move-any-app-design.md +117 −82
diff --git a/docs/superpowers/specs/2026-10-02-move-any-app-design.md b/docs/superpowers/specs/2026-10-02-move-any-app-design.md
index 949a8cd4c..16cf185a2 100644
--- a/docs/superpowers/specs/2026-10-02-move-any-app-design.md
+++ b/docs/superpowers/specs/2026-10-02-move-any-app-design.md
@@ -1151,7 +1151,12 @@ runs anywhere. It converges when the target returns (INV-3).
- **the target released dirty but had not returned its writes** (model
6): the same, and the confirmation says so. Rev 3 missed this state;
the model finds a move waiting forever on a return that a destroyed box
- can never send;
+ can never send. **The same CAS adds a `park-staging` obligation for the
+ return lane, `moves/<host>/<rollback id>/` in the source's region,
+ owned by the source region's box, not before the lane's staging
+ horizon** (model 27): the lost target may have published part of its
+ return, and the rollback now finishes as a clean one, which parks
+ nothing else there;
- **a destroyed box owes only local chores** (`park-local`): the
obligation is removed and nothing else changes. This one is legal
after commit, because no data authority is involved (model 6);
@@ -1492,6 +1497,22 @@ the text. What changed, and what did not:
| mutation infrastructure errors counted as detections | strict classification (§14.2.1) |
| the return lane outside the lease | the return lane takes the staging-work lease of the source's region, with paused signers and the forward path's horizon (§7.8) |
+**Rev 7: a biased schedule beside the uniform one.** In half the seeds the
+scheduler aims at the interleavings C3a and the door exist for, which the
+uniform schedule never reached (deleting and recreating a host took longer
+than any straggler):
+
+- a freed host is recreated at once, in the region where it last served;
+- at the delete, the executor's last replication PUT is accepted and then
+ completes at its worst moment: when another incarnation holds the host
+ with an executor, or a create, in that region. Any other long-tail
+ request of an earlier incarnation's replica data may complete at that
+ moment too, within `H` or after it, and the early-completion fault
+ leaves such requests alone;
+- a door listing is authorized at the delete, and stalls, before
+ selecting or between signing and opening, until the object its key
+ names belongs to another incarnation.
+
Not changed, and listed in §14.2.6: principal-aware reads and listings,
paginated listings, separate service, wall and monotonic clocks,
enrollment and console and updater versions, spawn separate from
@@ -1594,14 +1615,16 @@ adds and one it makes stricter:
app is idle. Writes past the last completed checkpoint are lost by
design and declared so; every durable write must come back.
-**Rev 6, on the final model: 30,000 seeds, zero violations (43 s);
-100,000 seeds, zero violations (144 s).** Across the 100,000 seeds:
-180,311 completed operations, among them 1,734 completed moves, 2,341
-clean and 71 dirty rollbacks, 528 pre-flip reverts, 343 declared losses,
-33,614 pushes, 103,353 delete-park-free cycles and 1,430 lost disks. 14,373
-requests completed after `H`; 51 staging signatures were refused as stale;
-33 sweeps retired stragglers. 670 landings rolled back because a config
-bundle was published during the move.
+**Rev 7, on the final model (claim rev 5, the biased schedule in half the
+seeds): 30,000 seeds, zero violations (28 s); 100,000 seeds, zero
+violations (117 s).** Across the 100,000 seeds: 180,848 completed
+operations, among them 1,774 completed moves, 2,277 clean and 77 dirty
+rollbacks, 565 pre-flip reverts, 345 declared losses, 33,722 pushes,
+103,652 delete-park-free cycles and 1,493 lost disks. 24,973 requests
+completed after `H`; 30,551 staging signatures were refused as stale
+(most after an SDK retry that waited out a long pause). 666 landings rolled
+back because a config bundle was published during the move. Rev 6's
+figures were 30,000 and 100,000 seeds clean on claim rev 4.
There are fewer completed moves than in rev 5 (3,887) for two reasons the
stricter model makes real: bundle publications during a move now roll it
@@ -1638,96 +1661,94 @@ Each was a failing seed first, then a rule; each rule's mutation is in
| model 22 (rev 6) | A bundle published after admission rolled the landing back, and then every recovery probe failed against the pinned inputs: stuck forever (J2-R2-5) | recovery probes run on current inputs (§7.7) | moves |
| model 23 (rev 6) | A staging round or a push that took its writes from the local database alone, on a box that had lost it, staged an empty snapshot and parked the durable replica | a staging round is the replica plus the final checkpoint (§7.3); a push likewise (F7) | moves, push |
| model 24 (rev 6) | The files directory missing on a box whose markers agree: rev 5's model refused it, rev 5's spec does not (`.files-gen` "when the files directory exists"), and the app mints a new `instance.key` | already covered: `files-carried` is now caught alone (§14.2.5); the start check is unchanged | moves |
+| model 25 (rev 7) | Claim rev 5's at-rest sweep lists retired locations and dead generations, both under `instances/<host>/`; a staging request completing after the move's cleanup left `moves/<host>/<move id>/` residue for good | the sweep also lists `moves/<host>/` (§6.4); CC20 | claim |
+| model 26 (rev 7) | With registrations renewed and drained as claim rev 5 says, a console that paused between renewing and its claim CAS resumed after the drain; the host served after its store was torn down | the create re-checks its registration as the last step before its claim CAS; CC21 | claim |
+| model 27 (rev 7) | A target that had begun its dirty return was declared lost; the rollback finished as a clean one and nothing parked the partial return lane in the source's region (1 seed in 100,000) | declare-lost parks the lane (§7.10) | moves |
#### 14.2.5 Mutation results
-Every rule was dropped in turn, in a scratch copy, on the final rev 6
+Every rule was dropped in turn, in a scratch copy, on the final rev 7
model, with the strict classification of §14.2.1: 3,000 seeds each; 30,000
for every rule not caught at 3,000; 30,000 for each combination below, and
-100,000 for the rarest. The model has **60 rules**: rev 5's 50, less
-`restore-lineage-filter` (now `restore-by-generation`), plus
-`location-per-incarnation`, `start-check-claim-gen`, `door-reread`,
-`admission-registration`, `registration-recovery`, `cancelled-creates`,
-`orphan-no-deadline`, `straggler-sweep`, `recovery-current-inputs` and
-`sign-at-check`. No run was an ERROR. Seed numbers reproduce any line with
-`HOSTLIFE_SEED`.
+100,000 for the rarest. The model has **64 rules**: rev 6's 60 plus
+`sweep-staging` (CC20), `registration-check` (CC21), `sdk-no-resign`
+(claim rev 5, CC19) and `lost-return-park` (model 27). No run was an
+ERROR. Seed numbers reproduce any line with `HOSTLIFE_SEED`.
| | rules |
|---|---|
-| caught alone | 44 |
-| caught only in a stated combination | 10 (five pairs and two triples; one of them, `commit-before-open`, adds nothing to its partner) |
+| caught alone | 49 |
+| caught only in a stated combination | 12 |
| liveness only | 2 (`readiness-key`, `config-before-probe`) |
-| unreachable under claim rev 4 | 1 (`readiness-quiet`) |
-| unexercised | 3 (`location-per-incarnation`, `door-by-version`, `door-reread`) |
+| unreachable under claim rev 5 | 1 (`readiness-quiet`) |
+| unexercised | **none** (rev 6 had 3) |
-**Caught alone: 44 of 60.** First the rules new or changed in rev 6:
+**Rev 6's unexercised three, now caught (the biased schedule, §14.2.2).**
-| rule | seeds failing | what the model finds |
+| rule | result | what the model finds |
|---|---|---|
-| `restore-by-generation` (§5.1, J2-R2-1) | 10 / 30,000 | acknowledged writes lost: the newest object under the location was a late or stale one |
-| `straggler-sweep` (§6.4, CC15) | 3 / 3,000 | residue nobody retires, after requests that completed past `H` |
-| `sign-at-check` (§7.3, CC19) | 1 / 100,000 | staging lands after its cleanup from a box frozen between check and send |
-| `recovery-current-inputs` (§7.7, J2-R2-5) | 69 / 3,000 | recovery stuck forever after a bundle published during the move |
-| `admission-registration` (claim §5.1, §5.3, J2-R2-2) | 3 / 3,000 | a second host serving after its app's store was torn down |
-| `registration-recovery` (CC16) | 894 / 3,000 | purges that never finish |
-| `cancelled-creates` (claim §5.1, J2-R2-7) | 365 / 3,000 | a late record that blocks the host forever |
-| `orphan-no-deadline` (CC17) | 31 / 3,000 | the same, for a record that lands after `until` |
-| `files-carried` (§8, the kpmg outage) | 64 / 3,000 | the app mints a new `instance.key` over sealed data. Rev 5 caught this only with `start-check`; that pairing was an artifact of rev 5's stronger start check (model 24) |
-
-and the rest, rev 5's rules on the stricter model:
+| `location-per-incarnation` (claim C3a) | **caught alone**: 3 / 3,000, 38 / 30,000 | an incarnation's database holds writes of the previous incarnation: a replication PUT accepted before the delete completes after the recreate into the shared location, and the new incarnation's restore replays it; the cold-restore check at a later release and the start check both see it |
+| `door-reread` (claim §6.5 O2) | with `location-per-incarnation`: 47 / 30,000, of which 22 are door violations | a listing authorized for the old incarnation selects, after the recreate, the new incarnation's object in the shared location, and signs it |
+| `door-by-version` (claim §6.5) | with `door-reread` and `location-per-incarnation`: 77 / 30,000, of which 51 are door violations (22 without it) | a handle signed before the delete opens the location's current object after the recreate |
+
+Neither door rule can be violated while C3a holds, under any schedule: a
+listing selects only under O1's location, and no later incarnation ever
+writes there. They are defence in depth behind C3a, and each is shown
+load-bearing once C3a is dropped.
+
+**Caught alone: 49 of 64.**
| rule | seeds failing | | rule | seeds failing |
|---|---|---|---|---|
-| `recovery-pass` | 2,512 / 3,000 | | `fresh-incarnation` | 72 / 3,000 |
-| `legacy-discovery` | 558 / 3,000 | | `park-by-version` | 2,200 / 3,000 |
-| `park-all-versions` | 745 / 3,000 | | `park-skip-lock` | 2,994 / 3,000 |
-| `retirement-ids` | 284 / 3,000 | | `quiet-before-ack` | 187 / 3,000 |
-| `hostguard` | 9 / 3,000 | | `purge-reserve-first` | 2,362 / 3,000 |
-| `purge-adopts` | 8 / 3,000 | | `purge-journal` | 2,149 / 3,000 |
-| `evidence-whole-tree` | 2 / 3,000 | | `evidence-from-state` | 2,940 / 3,000 |
-| `floor-boot-recovery` | 2,679 / 3,000 | | `participant-rows` | 7 / 3,000 |
-| `revalidate-before-effect` | 11 / 3,000 | | `lost-quarantine` | 4 / 30,000 |
-| `cut-deadline` | 9 / 3,000 | | `revert-journal` | 17 / 3,000 |
-| `rollback-journal` | 1 / 3,000 | | `dirty-return` | 1 / 3,000 |
-| `target-park` | 49 / 3,000 | | `abort-target-owner` | 1,129 / 3,000 |
-| `timer-freeze` | 10 / 3,000 | | `timer-canonical` | 2 / 3,000 |
-| `markers-last` | 1 / 3,000 | | `source-readiness` | 10 / 3,000 |
-| `seal-by-version` | 1 / 3,000 | | `staging-quiet` | 2 / 3,000 |
-| `sign-before-safeuntil` | 9 / 3,000 | | `fresh-admission` | 4 / 3,000 |
-| `stale-hold-release` | 6 / 3,000 | | `obligations-block-ops` | 22 / 3,000 |
-| `claim-op-exclusive` | 93 / 3,000 | | | |
-
-**Caught only in a combination: 10 rules.** Each partner's own count is
-in the second column, so the combination's contribution shows.
+| `recovery-pass` | 2,513 / 3,000 | | `fresh-incarnation` | 55 / 3,000 |
+| `legacy-discovery` | 627 / 3,000 | | `location-per-incarnation` | 3 / 3,000 |
+| `park-by-version` | 2,236 / 3,000 | | `park-all-versions` | 748 / 3,000 |
+| `park-skip-lock` | 2,997 / 3,000 | | `retirement-ids` | 274 / 3,000 |
+| `quiet-before-ack` | 188 / 3,000 | | `straggler-sweep` | 156 / 3,000 |
+| `sweep-staging` | 44 / 3,000 | | `restore-by-generation` | 1 / 3,000 |
+| `hostguard` | 16 / 3,000 | | `purge-reserve-first` | 2,383 / 3,000 |
+| `purge-adopts` | 13 / 3,000 | | `purge-journal` | 2,128 / 3,000 |
+| `admission-registration` | 4 / 3,000 | | `registration-recovery` | 880 / 3,000 |
+| `registration-check` | 2 / 3,000 | | `cancelled-creates` | 367 / 3,000 |
+| `orphan-no-deadline` | 32 / 3,000 | | `evidence-whole-tree` | 5 / 30,000 |
+| `evidence-from-state` | 2,934 / 3,000 | | `floor-boot-recovery` | 2,657 / 3,000 |
+| `participant-rows` | 2 / 3,000 | | `revalidate-before-effect` | 15 / 3,000 |
+| `sign-before-safeuntil` | 17 / 3,000 | | `sign-at-check` | 10 / 3,000 |
+| `sdk-no-resign` | 7 / 3,000 | | `lost-quarantine` | 1 / 3,000 |
+| `cut-deadline` | 10 / 3,000 | | `revert-journal` | 22 / 3,000 |
+| `rollback-journal` | 1 / 3,000 | | `dirty-return` | 5 / 30,000 |
+| `lost-return-park` | 1 / 100,000 | | `target-park` | 45 / 3,000 |
+| `abort-target-owner` | 1,150 / 3,000 | | `timer-freeze` | 2 / 3,000 |
+| `timer-canonical` | 1 / 3,000 | | `markers-last` | 2 / 3,000 |
+| `source-readiness` | 15 / 3,000 | | `seal-by-version` | 8 / 30,000 |
+| `staging-quiet` | 4 / 30,000 | | `fresh-admission` | 10 / 3,000 |
+| `stale-hold-release` | 4 / 3,000 | | `obligations-block-ops` | 18 / 3,000 |
+| `claim-op-exclusive` | 92 / 3,000 | | `recovery-current-inputs` | 58 / 3,000 |
+| `files-carried` | 65 / 3,000 | | | |
+
+**Caught only in a combination: 12 rules.** Each partner's own count, on
+the same seeds, shows what the combination adds.
| combination | fails | partners alone | what the model finds |
|---|---|---|---|
-| `exec-admission` + `hold-op-open` + `start-check-claim-gen` | 19 / 100,000 | each pair of the three: 0 / 30,000 | two writers: **Astra's J2-R2-8 trace.** With the start check as rev 5 wrote it (the third rule dropped), losing the exec check and the conditional open lets a paused open make the old source ready after the commit, and it serves beside the target. Rev 6's start check is the third fence the rev 5 model invented and the rev 5 text lacked |
-| `exec-admission` + `hold-op-open` + `start-check` | 4 / 30,000 | (above) | the same trace, dropping the whole start check |
-| `claim-cas-opid` + `revalidate-before-effect` | 1,079 / 30,000 | `revalidate-before-effect` alone 162 / 30,000 | a stale purge step destroys a store |
-| `strict-spawn` + `evidence-whole-tree` | 133 / 30,000 | `evidence-whole-tree` alone 8 / 30,000 | two writers |
-| `touched-incomplete` + `legacy-discovery` | 8,670 / 30,000 | `legacy-discovery` alone 5,714 / 30,000 | a freed host's residue |
-| `declare-lost-compute` + `watchdog-independent` | 5 / 100,000 | 0 / 100,000 each | two writers after a frozen target resumes |
-| `commit-before-open` + `dirty-return` | 5 / 30,000 | `dirty-return` alone 7 / 30,000 | lost writes; fewer than the partner alone (seed variation), so the model does not separate `commit-before-open` |
-
-**Liveness only: 2.** `readiness-key` (T2) and `config-before-probe`:
-without them a target that cannot render config fails its probe and rolls
-back.
-
-**Unreachable: `readiness-quiet` (T12).** As in rev 5: every retirement
-acknowledges only after its own horizon, and CC11 holds.
-
-**Unexercised: 3.** `location-per-incarnation` (claim C3a) protects against
-a request from an old incarnation that completes after the host was
-deleted and recreated. In the model, deleting and recreating a host takes
-longer than the longest straggler (11 `H`; a 40 `H` tail was also tried),
-and the sweep at rest retires stragglers in between; 0 / 100,000. The door
-rules are subsumed by C3a, and are not caught with it dropped either
-(0 / 30,000 each). These are honest gaps: C3a guards a production case
-the model's timing does not reach, where AWS gives no completion bound.
+| `exec-admission` + `hold-op-open` + `start-check-claim-gen` | 19 / 100,000 | each pair of the three: 0 / 30,000 | two writers, Astra's J2-R2-8 trace (rev 6) |
+| `exec-admission` + `hold-op-open` + `start-check` | 12 / 30,000 | (above) | the same, dropping the whole start check |
+| `door-reread` + `location-per-incarnation` | 47 / 30,000 | `location-per-incarnation` 38 | door reads of another incarnation's data (22) |
+| `door-by-version` + `door-reread` + `location-per-incarnation` | 77 / 30,000 | `door-reread` + `location` 47 | more door violations (51 against 22) |
+| `claim-cas-opid` + `revalidate-before-effect` | 1,118 / 30,000 | `revalidate-before-effect` 184 | a stale purge step destroys a store |
+| `strict-spawn` + `evidence-whole-tree` | 139 / 30,000 | `evidence-whole-tree` 5 | two writers |
+| `touched-incomplete` + `legacy-discovery` | 8,996 / 30,000 | `legacy-discovery` 6,025 | a freed host's residue |
+| `declare-lost-compute` + `watchdog-independent` | 7 / 100,000 | 0 / 100,000 each | two writers after a frozen target resumes |
+| `commit-before-open` + `dirty-return` | 4 / 30,000 | `dirty-return` 5 | lost writes; adds nothing over the partner, so the model does not separate `commit-before-open` |
+
+**Liveness only: 2.** `readiness-key` (T2) and `config-before-probe`.
+
+**Unreachable: `readiness-quiet` (T12).** Every retirement acknowledges
+only after its own horizon, and CC11 holds; the biased schedule does not
+change that, because it delays only data writes, never an acknowledgement.
**Seeds:** 300 in the gate (about a second); the 30,000- and 100,000-seed
-baselines and this sweep are the evidence for rev 6.
+baselines and this sweep are the evidence for rev 7.
#### 14.2.6 What the model does not prove
@@ -1751,9 +1772,11 @@ baselines and this sweep are the evidence for rev 6.
budget (320), the reverse of production (over 15 minutes against 120 s).
Results about orderings carry over; results about which wait fits inside
which window do not (J2 §2).
-- **Adversarial scheduling is partial.** Paused opens, door reads and
- staging signers wake at the worst moment the model knows of; everything
- else wakes at random. The admission triple depends on the targeted wake.
+- **Adversarial scheduling is partial.** Paused opens, door reads,
+ staging signers and an earlier incarnation's replica writes wake at the
+ worst moment the model knows of; everything else wakes at random. The
+ admission triple, C3a and the door rules depend on those targeted
+ wake-ups and on the biased recreate.
- **Not modelled:** placement (`placing`), sidecars, nested-to-flat path
translation beyond the files directory, the db door's multi-console
drain, app moves (F5), and `open_budget` (the model's deadline is the
@@ -2154,3 +2177,15 @@ that revision and adds CC15 to CC19.
| the cut promise against the enforced deadline (N15) | moves | the deadline is the stop-to-open promise less `open_budget`, enforced on the commit's record CAS; T11 budgets every attempt's settle (§7.4) |
| D3, D6, I-7, N12 "partly" in the model | moves | per-artifact local parks and independent guard processes taken; spawn-in-flight, cloud fencing proof and executable activation paths remain model gaps (§14.2.6) |
| N5 local coverage | claim | claim rev 4 §9's local discovery, modelled with seeded local residue; `legacy-discovery` caught |
+
+### 22.6 The coordinator's round on rev 6 (claim rev 5, 4a458a9c)
+
+| request | rev 7 |
+|---|---|
+| close the never-caught gap: `location-per-incarnation`, `door-by-version`, `door-reread` | a biased schedule in half the seeds: an immediate recreate where the host last served, the old executor's last replication PUT completing at its worst moment within or past `H`, door listings racing the delete and recreate (§14.2.2). C3a is caught alone (38 / 30,000); `door-reread` with C3a dropped; `door-by-version` with both. Neither door rule can fail while C3a holds |
+| re-encode CC15 to CC19 as claim rev 5 states them | done: the sweep under a `park-sweep` obligation, renewed registrations with the drain rule, orphans at any time removed by the pass or by a create, `holder.generation`, signatures at the check with an SDK retry that would re-sign (§14.2.2) |
+| 30,000 and 100,000 seeds, the full sweep | both clean; 64 rules: 49 alone, 12 in combinations, 2 liveness, 1 unreachable, none unexercised (§14.2.5) |
+
+Found on the way: CC20 (the sweep must list `moves/<host>/`), CC21 (a
+create re-checks its registration right before its claim CAS), and model
+27 (declare-lost must park a partial return lane, §7.10).