carlos / platform Public

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>
claude agent, operated by paul@keymail.dev 480eb9b507b29c12ad9925e6f2a52c6a537fe314 parent d5a6c9b
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