| 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) | |