carlos / platform Public

region HA spec 2, rev 11: answers Astra round 10 (3 P2, 1 P3, accepted); rollback never destroys a group that hosted instances; round 10 findings committed unedited

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev 2c6aa9da26031a98bd2e93e2aa8f26b689821311 parent 9c26d97
2 files changed, +85 −8
  • docs/superpowers/specs/2026-10-15-region-ha-two-box-regions-design.md +24 −8
  • docs/superpowers/specs/CODEX-FINDINGS-REGION-HA-SPEC2-10.md +61 −0
diff --git a/docs/superpowers/specs/2026-10-15-region-ha-two-box-regions-design.md b/docs/superpowers/specs/2026-10-15-region-ha-two-box-regions-design.md
index e844a5a70..2649be21f 100644
--- a/docs/superpowers/specs/2026-10-15-region-ha-two-box-regions-design.md
+++ b/docs/superpowers/specs/2026-10-15-region-ha-two-box-regions-design.md
@@ -1,6 +1,8 @@
# Region HA, spec 2: two-box regions
-Design, 2026-10-15, **rev 10**. DRAFT. Nothing here is built.
+Design, 2026-10-15, **rev 11**. DRAFT. Nothing here is built.
+
+**Rev 11** answers Astra's round 10, a full pass (`CODEX-FINDINGS-REGION-HA-SPEC2-10.md`, cited **A10-n**): three P2s and one P3, all accepted. Rollback now restores only the ingress and never destroys a group that has hosted an instance, so retiring a group is spec 4's drain too. Moves take their source from the claim, not from placement eligibility. §23 maps them.
**Rev 10** follows Paul's ruling **H25**: spec 2 no longer empties the old boxes. Rounds 6 to 9 each found another gap in draining a live box, because that was spec 4's drain pulled forward. Instead, each region's old box keeps its existing instances, takes no new ones, and swaps to a new public address of its own at the cut-over. The live drain returns to spec 4, with rounds 6 to 9's findings recorded as its inputs. §22 maps them.
@@ -237,7 +239,7 @@ Anything else is joining mode. Joining resumes from whatever it finds: no invita
- from the NLB's box to the home box in the same region, privately;
- from any box in another region to the home box's own public address.
- No box ever needs to forward a forwarded request.
-- **The one transition** is the old box's address at the cut-over. §9 moves every instance off the old box first, so when the region's EIP leaves it, nothing is reached through that address. The exception is IE's platform routes, covered in §9 step 3.
+- **The one transition** is the old box's address at the cut-over (A10-4). Its instances stay (H25), so §9 step 3 swaps it to a new EIP of its own and republishes at once. Cross-region readers pick up the new address within a minute, inside the cut-over window. Until then, a cross-region forward to one of its instances lands on the NLB and is refused, because a forwarded request isn't forwarded again.
- The security group gains 443 from the region's VPC CIDR, which is already exported as `vpc_cidr`.
**Platform routes.**
@@ -310,7 +312,7 @@ Each is closed by a code change, or by showing it's safe under claim mode, befor
| Where | Assumption | Plan |
|---|---|---|
-| `internal/console/apiinstancemove.go:614-618`, `boxselect.go:114-118`, `internal/host/movesphase1.go:517` | The move door refuses a region with more than one box | Moves' code: ask on #236 for multi-box support, choosing the destination box with §7's choice |
+| `internal/console/apiinstancemove.go:614-618, 671`, `boxselect.go:114-118`, `internal/host/movesphase1.go:517` | The move door refuses a region with more than one box, and `selectMoveBoxes` applies the placement predicate to both regions | Moves' code; ask on #236. **The source comes from the claim's executor**, validated only as enrolled, active and unfenced, never against placement eligibility, so instances on `legacy` and `platform` boxes can still move out (A10-3). **Only the destination** gets §7's choice. Tests cover outbound moves from both excluded roles |
| `internal/console/seedfloor.go:25-29, 88` | Seed import refuses a region with more than one box | Ask the seeded-instances owner: import onto §7's choice |
| `internal/hostfence/onebox.go:9-30` | `CheckOneBoxRegion` | Read its callers. Each caller either requires one box, and is refused in two-box regions by design, or gets the region's box set |
| `internal/activator/activator.go:2264-2272` | The local database copy is a warm cache only while a region has one box | H13: under the claim every admission is cold, and the warm path is never used. Verify that the claim-mode activator never reads this cache, and add a test |
@@ -348,13 +350,17 @@ Each is closed by a code change, or by showing it's safe under claim mode, befor
5. In IE, before the window closes: check that mail arrives on the new address, and that a TURN allocation advertises it and carries media.
4. **Accept.** After seven days with no rollback, the region is done. The old box stays, serving its instances, until spec 4's drain.
-**Rollback** (A1-13). The region's EIP is a standalone `aws_eip` with `prevent_destroy`, so the allocation is never lost.
-- **Before step 3:** set the old box's role back to `default`, and destroy the group and NLB. Any instance created on the group boxes meanwhile is relocated to the old box first (it's eligible again). The region is back to today's state.
+**Rollback** (A1-13, A10-1, A10-2). Rollback restores the ingress. It **never destroys a group that has hosted an instance**. Retiring a group means moving its instances, which is a drain, and that's spec 4's. The region's EIP is a standalone `aws_eip` with `prevent_destroy`, so the allocation is never lost.
+- **Before any group box reaches hosting mode** (step 1 failing): destroy the group and NLB freely. Nothing has been placed on them.
+- **After that, before step 3:**
+ - set the old box's role back to `default`, so new instances can land on it again;
+ - set both group boxes to `legacy`, so nothing new lands on them.
+ - The group boxes keep whatever they host. They stay in service, behind the old box's forwarding, until spec 4's drain retires them.
- **From step 3 until acceptance:**
- 1. Remove zone A's mapping from the NLB, or delete the NLB. An address on a requester-managed interface can't be disassociated directly.
+ 1. Remove zone A's mapping from the NLB. An address on a requester-managed interface can't be disassociated directly. The NLB itself is kept, because the group boxes are its targets.
2. Wait until the EIP shows as unassociated.
3. Swap the old box's addresses back: disassociate its new EIP, associate the region's EIP, and force a republication. In IE, switch the relay and MX records back and restart the relay advertising the region's address (A2-9).
- 4. The group boxes keep their instances, reached through the old box privately. To remove the group as well, continue as for "before step 3".
+ 4. Set the roles as for "before step 3". Group instances stay where they are, reached through the old box privately.
- **After acceptance:** recovery is forward, within the group.
**Cost.**
@@ -395,7 +401,8 @@ Each is closed by a code change, or by showing it's safe under claim mode, befor
- Platform routes forwarded from a group box to the platform box.
- **Placement.** Unit tests for each eligibility rule, the spread preference, the headroom ordering, the stale-headroom cut-off, the `platform` and `legacy` roles, and unplaced with its retry triggers.
- **Rehearsal on dev:**
- - the full §9 sequence, with the cut-over window measured, the zone-B rehearsal, and both rollback sequences run once;
+ - the full §9 sequence, with the cut-over window measured, the zone-B rehearsal, and both rollback sequences run once. Rollback leaves the group in service with its instances, including one that was never woken;
+ - outbound moves from a `legacy` box and from the `platform` box;
- the old box's address swap at the cut-over: forced republication, and cross-region forwards to its instances recovering within a minute;
- the `legacy` and `platform` roles excluding a box from creates, relocation targets and move targets, persisting across console and box restarts, and an operation already heading to the box finishing there;
- one group box terminated by hand, with replacement and re-enrolment observed;
@@ -510,3 +517,12 @@ All three were answered in Astra's round 1 and folded in:
| A9-3 P2: the barrier needs migration to have finished | Moot under H25. Recorded for spec 4's drain |
| A9-4 P2: historical box references never clear | Moot under H25. Recorded for spec 4's drain |
| A6-1, A7-1, A7-2, A8-1, A8-2 | Their machinery (the drain control, the settlement barrier, the one-off wake) is removed from spec 2 and recorded as spec 4's inputs. A6-2, IE rollback, is met differently: the IE box isn't drained, so rollback moves no instances back to it |
+
+## 23. Astra round 10 dispositions (rev 11)
+
+| Finding | Disposition |
+|---|---|
+| A10-1 P2: destroying the group during rollback needs settlement | Accepted: rollback never destroys a group that has hosted an instance. It sets roles and reverts ingress, and retiring the group is spec 4's drain (§9) |
+| A10-2 P2: rollback can't relocate a never-woken group instance | Accepted by the same change: rollback moves nothing (§9) |
+| A10-3 P2: moves need source validation separate from destination eligibility | Accepted: the source comes from the claim and is checked only as enrolled, active and unfenced. Only the destination uses §7 (§8) |
+| A10-4 P3: §6 still requires emptying the old box | Accepted: §6 describes the address swap (§6) |
diff --git a/docs/superpowers/specs/CODEX-FINDINGS-REGION-HA-SPEC2-10.md b/docs/superpowers/specs/CODEX-FINDINGS-REGION-HA-SPEC2-10.md
new file mode 100644
index 000000000..aea93b369
--- /dev/null
+++ b/docs/superpowers/specs/CODEX-FINDINGS-REGION-HA-SPEC2-10.md
@@ -0,0 +1,61 @@
+H25 removes the old-box drain requirement from the forward rollout, but **not from the rollback as written**. Three P2 findings and one P3:
+
+1. **P2 — Destroying the group during rollback still needs settlement.**
+ [Spec:352](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/design-region-ha-two-box/docs/superpowers/specs/2026-10-15-region-ha-two-box-regions-design.md:352)
+
+ Rollback restores the old box’s eligibility and relocates group residents before destroying the group. It never excludes the group boxes from new destinations or settles outstanding selections.
+
+ **Failure scenario:** a create selects a group box before rollback, pauses, then completes after its residents have been relocated. Recovery [retains that executor](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/design-region-ha-two-box/internal/hostops/production_create.go:257). Destroying the group strands the instance. The post-cut-over rollback inherits this problem through step 4.
+
+ Either retain the group until spec 4, or provide the exclusion and acknowledged settlement needed to retire it safely.
+
+2. **P2 — Rollback cannot relocate a never-woken group instance.**
+ [Spec:352](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/design-region-ha-two-box/docs/superpowers/specs/2026-10-15-region-ha-two-box-regions-design.md:352)
+
+ A newly created group instance can still have `Holder.Session == 0`. [Relocation refuses that state](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/design-region-ha-two-box/internal/hostrelocate/worker.go:329), and the ordinary wake path [requires `Holder.WriterBox` to name the local box](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/design-region-ha-two-box/internal/activator/claimwake.go:182).
+
+ **Failure scenario:** an instance is created during rehearsal but receives no traffic. Rollback cannot return it to the old box, so it cannot safely destroy the group. A9-2 remains relevant to this sequence despite [§22’s moot disposition](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/design-region-ha-two-box/docs/superpowers/specs/2026-10-15-region-ha-two-box-regions-design.md:509).
+
+ Retain the group, or explicitly include support for relocating this state.
+
+3. **P2 — Moves need separate source validation and destination eligibility.**
+ [Spec:313](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/design-region-ha-two-box/docs/superpowers/specs/2026-10-15-region-ha-two-box-regions-design.md:313)
+
+ The proposed move change describes destination selection. Today, [`selectMoveBoxes`](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/design-region-ha-two-box/internal/console/boxselect.go:114) applies the placement predicate to **both** regions, and the move door [requires the selected source to equal the claim’s executor](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/design-region-ha-two-box/internal/console/apiinstancemove.go:671).
+
+ **Failure scenario:** after enforcing `legacy`/`platform` exclusion, an existing customer instance on the old box cannot move to another region: its executor is absent from the source candidates. Removing the multiple-box refusal alone does not fix this.
+
+ Specify that the source comes from the claim and is validated as active and unfenced; only the destination gets §7’s placement policy. Test outbound moves from both excluded roles.
+
+4. **P3 — The active forwarding section still requires emptying the old box.**
+ [Spec:240](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/design-region-ha-two-box/docs/superpowers/specs/2026-10-15-region-ha-two-box-regions-design.md:240)
+
+ This clause says §9 moves every instance off before transferring the EIP. H25 and the revised §9 explicitly retain those instances.
+
+ **Failure scenario:** a plan following §6 reinstates the drain prerequisite that this revision removes. Replace it with the new-address, forced-publication and reader-refresh transition.
+
+The revised address swap covers the previously identified forwarding and relay requirements at design level. I found no additional blocker in those steps.
+
+Reviewed platform `9c26d974` and flagship `7c99ff8f`. No files changed; no builds, tests or infrastructure commands run.
+
+---
+
+**FIRST PROMPT:**
+Review spec 2 round 10 at the pinned HEAD against flagship tofu, read-only.
+
+**WHAT:**
+Check H25, placement exclusion, address switching, rollback and the full spec.
+
+**STATUS:**
+Complete; three P2 findings and one P3.
+
+**NEEDED:**
+Nothing.
+
+**RECOMMENDED:**
+Resolve group retirement during rollback and distinguish move-source validation from destination selection.
+
+**SESSION:**
+Safe to close; nothing modified or left running.
+
+**Verdict: not ready for a plan.**
\ No newline at end of file