spec: move-any-app rev 7, part 1: claim rev 5, the sweep's staging scope, CC20 and CC21
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 file changed,
+63
−33
docs/superpowers/specs/2026-10-02-move-any-app-design.md+63 −33
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 b912dc89c..949a8cd4c 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 |
| @@ -1,9 +1,25 @@ |
| # Move any app or instance to another region: Phase 1, a safe single-instance move |
| **Date:** 2026-10-03 |
| -**Revision:** 6. |
| +**Revision:** 7. |
| + |
| +- **Rev 7** is written against **claim spec rev 5** (4a458a9c on |
| + `design-replica-ownership`), which adopted CC15 to CC19. It: |
| + - closes the model's last gap: a **biased schedule** beside the uniform |
| + one recreates a host where it last served while the previous |
| + incarnation's requests are still in flight, and races door listings |
| + against the delete and the recreate. `location-per-incarnation` |
| + (claim C3a) is now caught alone, and both door rules with it |
| + (§14.2.5); |
| + - re-encodes claim rev 5: the at-rest sweep under an obligation the |
| + sweeping box owns, registration leases, orphan records at any time, |
| + `holder.generation`, signatures taken at the deadline check with an |
| + SDK retry that must not re-sign; |
| + - asks for two more claim changes, CC20 and CC21 (§19.4). |
| -- **Rev 6** answers joint Astra round 2 (`CODEX-FINDINGS-JOINT-R2.md`, |
| +Earlier revisions: |
| + |
| +- **Rev 6** (7ac666fe) answers joint Astra round 2 (`CODEX-FINDINGS-JOINT-R2.md`, |
| df41156c; cited "J2-…"), whose three verdicts were "not ready", and is |
| written against **claim spec rev 4** (a3c86fa5 on |
| `design-replica-ownership`), which answers the claim-side findings. Rev 6: |
| @@ -35,8 +51,6 @@ |
| Rev 6's changes are marked "(J2-…)" for the review's findings and |
| "(model N)" for the stricter model's. |
| -Earlier revisions: |
| - |
| - **Rev 5** (4ec3282b) was written against **claim spec rev 3** (8b5bccb2 on |
| `design-replica-ownership`), which adopted CC1 to CC13. It answers the |
| coordinator's round on rev 4: |
| @@ -116,9 +130,9 @@ branch `design-nearest-region`. |
| from its §2 onward (tombstones become the claim's `deleting` and |
| `parking` operations). |
| - The claim spec, `docs/superpowers/specs/2026-10-02-host-lifecycle-claim-design.md`, |
| - **rev 4 at a3c86fa5** on `design-replica-ownership`. Rev 6 is written |
| - against rev 4; rev 5 against rev 3 (8b5bccb2); rev 4 against rev 2 |
| - (cf3a7861). |
| + **rev 5 at 4a458a9c** on `design-replica-ownership`. Rev 7 is written |
| + against rev 5; rev 6 against rev 4 (a3c86fa5); rev 5 against rev 3 |
| + (8b5bccb2); rev 4 against rev 2 (cf3a7861). |
| Citations are to this worktree at 7edd4b33 unless they name another branch |
| or repo. |
| @@ -167,7 +181,7 @@ traffic is undone automatically (§7.8); after the target opens, R4 stands. |
| ### 0.4 The host lifecycle claim |
| -From claim spec rev 4 (§2 to §10 there), the parts this spec relies on: |
| +From claim spec rev 5 (§2 to §10 there), the parts this spec relies on: |
| - **The object** `control/hosts/<host>.json`, rewritten whole by CAS: a |
| per-write revision `gen` (not a data generation, J-D2); a `holder`; |
| @@ -214,9 +228,10 @@ means the claim's `op.exec` (rev 2 made it a first-class field, as rev 3 |
| asked). |
| Rev 4's model required CC1 to CC13 of claim rev 2, and claim rev 3 |
| -adopted them; rev 5's CC14 is in claim rev 3.1 and rev 4. Rev 6's stricter |
| -model requires CC15 to CC19 (§19.4). Until platform-1e adopts them, this |
| -spec states each as the behaviour it relies on. |
| +adopted them; rev 5's CC14 is in claim rev 3.1 and rev 4; rev 6's CC15 to |
| +CC19 are in claim rev 5. Rev 7's model requires CC20 and CC21 (§19.4). |
| +Until platform-1e adopts them, this spec states each as the behaviour it |
| +relies on. |
| ## 1. What happened in production |
| @@ -643,26 +658,27 @@ No park destination is an ancestor of a live prefix (codex 8). **A park is |
| its own journaled phase**, complete before any install into the vacated |
| namespace begins (codex R2-6). |
| -**The straggler sweep (J2-R2-1, CC15, model 17).** S3's signature limit |
| -bounds when a request is accepted, not when it completes (claim C3a), so a |
| -request accepted before a retirement's horizon listing can still land |
| -after it. Within one incarnation the restore contract (§5.1) makes such a |
| -straggler harmless to data. It is still residue, in a region the instance |
| -left, in a dead generation, or under a freed host, and no later retirement |
| -of that location may ever run. So each region's box sweeps: |
| - |
| -- **when:** only while the host is **at rest**: the claim free, or |
| - `serving` with no operation and no obligation. It re-reads the claim on |
| - every step and abandons the sweep the moment that stops being true; |
| -- **what:** everything under `instances/<host>/` and `moves/<host>/` in its |
| - own bucket except the **live set**: in the executor's region, |
| - `holder.location`'s current generation and `generation.json`; everywhere, |
| - the parkers' own `lock.json`. Under a free host the live set is empty; |
| -- **how:** as a retirement event of its own, with the park's machinery: |
| - snapshot, archive set copied, manifest, deletion set replayed by version |
| - id, under `parked/sweep/<host>/<sweep id>/`. Every delete names a |
| - snapshot version, so nothing written after the snapshot is touched, even |
| - if an operation starts mid-sweep. |
| +**The straggler sweep (J2-R2-1, CC15, model 17; claim rev 5 §6.2a).** S3's |
| +signature limit bounds when a request is accepted, not when it completes |
| +(claim C3a), so a request accepted before a retirement's horizon listing |
| +can still land after it. Within one incarnation the restore contract |
| +(§5.1) makes such a straggler harmless to data. It is still residue, and |
| +no later retirement of that location may ever run. Claim rev 5's at-rest |
| +sweep owns it: for a host with no operation running, each region's box |
| +lists the locations of the host's retired incarnations and every |
| +generation within `holder.location` other than `holder.generation`, and |
| +parks whatever it finds as a further retirement event, under a |
| +`park-sweep` obligation the sweeping box owns. Being an obligation, it |
| +holds off any new operation over that region until it acknowledges (CC11), |
| +and every delete names a snapshot version, so nothing written later is |
| +touched. |
| + |
| +**This spec adds `moves/<host>/` (CC20).** Claim rev 5's list leaves out |
| +staging. A staging request accepted before its lease ended can complete |
| +after the staging cleanup, and after the move's release nothing lists |
| +`moves/<host>/<move id>/` again. The sweep also parks everything under |
| +`moves/<host>/` while no operation runs, when no move is staging there. |
| +The model drops this and leaves staging residue for good (§14.2.5). |
| A periodic listing per host and region is the cost (rate: once a day, and |
| after each operation's release). The horizon of §6.2a stays: it is what |
| @@ -765,7 +781,9 @@ further rules: |
| the time has passed. Revalidating at the start of the round is not |
| enough: a goroutine that revalidated and then paused can sign long after |
| its lease ended. **The signature is computed at that check**, with the |
| - check's time as its `X-Amz-Date`, and never recomputed: a pause between |
| + check's time as its `X-Amz-Date`, and never recomputed, including by the |
| + SDK's automatic retry, which must resend the original signature (claim |
| + rev 5, CC19): a pause between |
| the check and the send, even a whole-VM freeze that no in-process bound |
| can promise against, then ends in S3 refusing the request once its |
| signature is older than the signature-age limit (403 |
| @@ -1768,7 +1786,7 @@ region that lost. |
| box; config sync armed in every region. |
| 2. **Release with `secrets_key_fp`.** Verify every box against the console. |
| 3. **The host lifecycle claim** (claim spec §10) ships and every box and |
| - console honours it: claim rev 4, with CC15 to CC19 (§19.4) adopted. |
| + console honours it: claim rev 5, with CC20 and CC21 (§19.4) adopted. |
| 4. **Phase 1 release, dormant.** Consoles first, then every box. |
| 5. **Infra (M17, approved):** every region's `sibling_staging_arns` lists |
| every other region's data bucket. Applied per deployment as scoped work. |
| @@ -1937,6 +1955,18 @@ included now (model 15). |
| | **CC18** | **Name `holder.generation`.** C3a says restore reads "the moves spec's generation within it, taken from the claim". Add the field to §2's schema: written by the commit's claim CAS of a move, by a push's probe CAS, and by a dirty rollback's source probe; origin is `""`. The start check (moves §5.1) compares markers against it | J2-R2-8: the third admission fence exists only if the claim names the generation | |
| | **CC19** | **CC14, amended: sign at the check.** "Check `SafeUntil` immediately before every request signature" still leaves the pause between the signature and the send, and a whole-VM freeze can stretch that without bound. Compute the signature at the check, with the check's time as `X-Amz-Date`, never recompute it, and set `SafeUntil = Until − skew − 15 min`: S3 then refuses the request once the pause outlives the signature | model 18: a box frozen between a passed check and the send lands staging after the cleanup (`sign-at-check`, 1 / 100,000) | |
| +**CC15 to CC19: all adopted in claim rev 5** (4a458a9c), which maps them |
| +in its §12: CC15 to §6.2a's at-rest sweep; CC16 to §5.1 step 0 and §5.3 |
| +step 1b; CC17 to §5.1 step 5; CC18 to §2 `holder.generation`; CC19 to C3's |
| +staging-work lease exception. Rev 7's model encodes that text. |
| + |
| +**Rev 7's requests, against claim rev 5 (4a458a9c):** |
| + |
| +| id | change to claim rev 5 | why (model) | |
| +|---|---|---| |
| +| **CC20** | **The at-rest sweep also lists `moves/<host>/`.** §6.2a's sweep lists retired incarnations' locations and dead generations, both under `instances/<host>/`. A staging request accepted before its lease ended can complete after the staging cleanup, and after the move releases nothing lists `moves/<host>/<move id>/` again. Add `moves/<host>/` to the sweep's list for a host with no operation running | model 25: staging residue that nothing ever retires (`sweep-staging`) | |
| +| **CC21** | **A create checks its registration immediately before its claim CAS.** Renewal keeps a live create's registration from expiring, but a console that pauses between renewing and its claim CAS can resume after the drain removed the expired, unclaimed registration and purge fixed its host set; its claim CAS then succeeds, and the host serves after its store was torn down. Re-read the registration (still present, renewed within the lease) as the last step before the claim CAS, with the §3.1 margin, and abandon the create if it is gone | model 26: a host serving after its app's store was torn down (`registration-check`) | |
| + |
| **Not a change, a note for platform-1e (model, rev 5).** Under rev 3 the |
| install wait on `quiet_after` never bites: every retirement acknowledges |
| only after its own `evidence time + H`, so the `quiet_after` it writes has |