docs: nearest-region spec rev 5, after Astra's round 4
Three bounded P2s, verified and accepted, marked "(codex R4-N)": fleet-placed apps get no instance guess (the door overrides any submitted region with the fleet); canary --region on a fleet-placed app refuses identically before and after the canary exists (C30); and a service gate before the production switch, with a fresh probe per eligible region reached from outside through the platform host and through every other edge via --resolve, checking 2xx and the deployed X-Carlos-Version, plus a missing-mesh-key integration test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 file changed,
+86
−11
docs/superpowers/specs/2026-10-01-nearest-region-default-design.md+86 −11
diff --git a/docs/superpowers/specs/2026-10-01-nearest-region-default-design.md b/docs/superpowers/specs/2026-10-01-nearest-region-default-design.md| index bc6b813e3..3ba95f956 100644 |
| --- a/docs/superpowers/specs/2026-10-01-nearest-region-default-design.md |
| +++ b/docs/superpowers/specs/2026-10-01-nearest-region-default-design.md |
| @@ -1,7 +1,7 @@ |
| # Nearest region by default: each app picks its region at creation |
| **Date:** 2026-10-01 |
| -**Revision:** 4.1. |
| +**Revision:** 5. |
| - Rev 1 (7fbc9479) went to Astra (GPT-6, via codex), and the findings are in |
| `CODEX-FINDINGS-NEAREST-REGION-R1.md`. |
| @@ -17,6 +17,8 @@ |
| existing steering converger is not safe to arm (§13). That spec gets |
| invariants to meet, and placement ships first. |
| - Rev 4.1 records Paul's ruling on that split as R8 and removes the question. |
| +- Rev 5 answers Astra's round 4 (`CODEX-FINDINGS-NEAREST-REGION-R4.md`): three |
| + P2s, marked "(codex R4-N)". |
| **Status:** DRAFT for review. The rulings in §3 are Paul's and are not |
| reopened here. Everything marked *(decision)* is this spec's and open to |
| @@ -398,6 +400,14 @@ Candidate rules: |
| (`apiinstances.go:1797`) on the **normalized** code. **A pooled manifest |
| yields no candidates and no guess** (codex 1). |
| - *unreadable*: no guess, and an explicit error at the guess endpoint. |
| +- **Fleet placement** (codex R4-1). The instance guess endpoint (§9.4) |
| + resolves `appPlacement` (`internal/console/fleets.go:479`) first. **A |
| + fleet-placed app gets `"guess": null`**, because the door replaces any |
| + submitted region with the fleet (`apiinstances.go:798`), so a guess could |
| + only mislead a picker. A failed placement read is an explicit error there |
| + (C27), never an unrestricted guess. App-creation inference (§5.5) is |
| + unaffected: it chooses the stored app region, which stays dormant while the |
| + app is placed. |
| - **Read failure** (codex R2-2): the listing failing is distinct from "no |
| file publishes a placement". `readEdgesFiles` (`instancesplacement.go:83`) |
| swallows the first today. A new `readEdgesFilesStrict` returns the error. |
| @@ -704,7 +714,9 @@ The response gains one block: |
| - The body is `{"tz": "...", "ip": "..."}`, both optional. A person's request |
| with no `ip` uses its own address, and a service principal's never does. |
| - The answer is `{"guess": {"code", "name", "source"}}` or `{"guess": null}`. |
| -- An unreadable manifest gets a 502 with C27. |
| + It is `null` for a pooled or fleet-placed app (§6.1) (codex R4-1). |
| +- An unreadable manifest, or an unreadable fleet placement, gets a 502 with |
| + C27. |
| - It writes nothing. |
| **9.5 Browser: `POST /a/{sqid}/apps/new/region`.** |
| @@ -778,7 +790,7 @@ IP, then the home. |
| | --- | --- | --- | |
| | `carlos apps create` | `--region <code>` | The app's region. The default is resolved by the console. | |
| | `carlos deploy` | `--region <code>` | The region of the app this deploy creates. On an existing app, it compares against `app_default.region` (the stored region) (codex R2-4): equal is silent, and **different is refused before anything ships** (C15). It is skipped against a console with no `app_default`. A fleet-placed app is judged by its stored region too, because the flag names a region, not a fleet. | |
| -| `carlos canary` | `--region <code>` | The canary's region. The default is the app's. For an existing canary host, the record's region is compared with the flag, reading `""` as the home's name: equal proceeds, and **different is refused before any record write, rechannel or upload** (C25) (codex 5). | |
| +| `carlos canary` | `--region <code>` | The canary's region. The default is the app's. Before either branch, the CLI reads `app_default.effective` (§9.3). **If the app is fleet-placed, `--region` is refused before anything is created, rechannelled or uploaded, on the first run and on every repeat** (C30) (codex R4-2): the fleet governs every instance's region, so the flag could never be honoured, and refusing it the same way both times keeps the command's answer independent of whether the canary exists yet. Otherwise, for an existing canary host, the record's region is compared with the flag, reading `""` as the home's name: equal proceeds, and **different is refused before any record write, rechannel or upload** (C25) (codex 5). Without `--region`, a fleet-placed app's canary is created and reused exactly as today. | |
| | `carlos instances create` | `--region <code>` | Unchanged. The help says the default is now the app's region. | |
| Output: |
| @@ -797,7 +809,9 @@ command. |
| overrides the request's region (`apiinstances.go:785-797`). The precedence |
| is explicit: **fleet placement, then the request's region, then the app's |
| region, then the home.** A placed app's region is stored, stays dormant, |
| - and is reported separately from its placement (§9.3). |
| + and is reported separately from its placement (§9.3). A placed app gets no |
| + instance guess (§6.1), and `canary --region` refuses on it consistently |
| + (§11) (codex R4-1, R4-2). |
| - **Pools** (`Manifest.Pool`). There is no default fill and no guess, and |
| `app_default` reports `pooled`. An explicit `--region` |
| behaves as today. |
| @@ -1094,6 +1108,22 @@ Named cases: |
| **Pools, end to end.** A pooled manifest gives `guess: null` and |
| `app_default.reason: pooled`. |
| +**Fleets, end to end** (codex R4-1). For an app placed on `fleet/acme` with |
| +an unrestricted shared-pool manifest: |
| + |
| +- `/regions/guess` returns `null`; |
| +- a create that submits `uk` anyway produces a record whose region is |
| + `fleet/acme`, which pins why the guess is withheld; |
| +- a failed placement read gives 502 C27. |
| + |
| +**Service through the real path** (codex R4-3), as an integration test with |
| +two edges and a backhaul between them: |
| + |
| +- a non-home instance answers a request that enters through the other edge; |
| +- with no mesh key mirrored, the same request gets the unknown-host answer |
| + (`internal/edge/edge.go:2699`, `:1428`), and the Phase P gate (§17) treats |
| + that as a failure, not a pass. |
| + |
| **App create.** |
| - An old body from a person resolves by IP, then home. This needs `ip` passed |
| @@ -1153,6 +1183,9 @@ and `ip` appear in none of: |
| is silent. |
| - `canary --region` against an existing canary in another region is refused |
| before any write or upload. |
| +- `canary --region uk` on a fleet-placed app, run twice: both runs refuse |
| + with C30 before any create, rechannel or upload. The first run creates |
| + nothing, so the second sees the same state (codex R4-2). |
| - Tests use `carlosTestEnv` with a scratch `cmd.Dir`. |
| - `20-cross` covers the CLI changes. |
| @@ -1175,13 +1208,44 @@ and `ip` appear in none of: |
| 4. **Verify on dev.** Turn the switch on with two eligible regions. Then: |
| - create an app in the browser, and read its row; |
| - create an instance with no region, and read its record; |
| - - diff `/regions` for an old app against the pre-roll answer. |
| -5. **Flagship.** Roll the console, then turn the switch on. **From here the |
| + - diff `/regions` for an old app against the pre-roll answer; |
| + - run the service gate below. |
| +5. **Service gate on the flagship, with the switch still off** (codex R4-3). |
| + |
| + Eligibility proves configuration, not a working request path. |
| + Reachability is only a non-empty EIP (`instancesplacement.go:185`), and |
| + backhaul refuses to dial without a mirrored mesh key (`edge.go:2699`). |
| + So before the switch goes on, **every eligible region must serve a fresh |
| + instance through the real public path.** |
| + |
| + For each eligible region R, create one probe instance of a test app with |
| + an explicit `--region R`. That is ordinary API use and needs the switch |
| + off. Deploy a known build to it, then check: |
| + |
| + - **From outside, through the platform host:** `curl -s -D - -o /dev/null |
| + https://<probe host>/` from a machine outside the deployment, using |
| + public DNS as it stands. On the flagship that enters through the Ireland |
| + edge, so every non-home region is exercised over backhaul. Expect 2xx, |
| + and an `X-Carlos-Version` equal to the deployed build. |
| + - **Through an edge that is not the instance's box:** for every armed |
| + edge E other than R's box, `curl -s -D - -o /dev/null --resolve <probe |
| + host>:443:<E's EIP> https://<probe host>/`, with the same expectations. |
| + This covers the home region too, entered from a far edge, and every |
| + edge-to-box pair the static DNS could someday route. |
| + |
| + A 404 with the unknown-host answer, a TLS failure or a version mismatch |
| + fails the gate. **The switch is turned on only when every eligible region |
| + passes.** A region that fails is fixed first (mesh key, certificate, |
| + activator), or its boxes are made ineligible, before the switch goes on. |
| + The probe instances are deleted afterwards. The same gate is re-run before |
| + the switch is turned on again after any period off, and whenever a new |
| + region becomes eligible. |
| +6. **Flagship.** Roll the console, then turn the switch on. **From here the |
| binary rollback floor applies (R7).** That release's notes and the roll |
| recipe in carloku-infrastructure name the floor version. Optionally, set |
| `CONSOLE_GEOIP_DB` to a DB-IP Lite file. |
| -6. **CLI release.** It is safe in either order (§9.6). |
| -7. **titogo**, in its own repo, adopts `/regions/guess` and the place names. |
| +7. **CLI release.** It is safe in either order (§9.6). |
| +8. **titogo**, in its own repo, adopts `/regions/guess` and the place names. |
| No step of Phase P depends on Phase L. Phase P writes nothing to DNS, stamps |
| no `Steering` field, and needs neither `CARLOS_STEERING_ZONE_ID` nor any |
| @@ -1191,7 +1255,7 @@ change to the zone. |
| (§13.3). Its gate is that spec landing with its simulation passing, plus |
| scoped approval for the infrastructure. Until then, **no deployment arms |
| `CARLOS_STEERING_ZONE_ID`**, including for the static-site steering that |
| -already exists. The latency baseline is taken once Phase P step 5 has run. |
| +already exists. The latency baseline is taken once Phase P step 6 has run. |
| That makes Phase L depend on Phase P, never the other way round. |
| Copy review (§19) happens before Phase P step 3 ships any string. |
| @@ -1229,6 +1293,7 @@ review. There are no em dashes. `{…}` is filled at runtime. |
| | C21 | activity feed | created app {name} in {place} | |
| | C22 | flag help | apps create: "region this app's instances start in (default: the region nearest you)". deploy: "region for the app this deploy creates (default: the region nearest you)". canary: "region for the canary instance (default: the app's region)". instances create: "region for this instance (default: the app's region, subject to the manifest's allow-list)". | |
| | C24 | new-app page, IP step configured | IP location by DB-IP. (Linked to db-ip.com; DB-IP's required wording to be confirmed against its licence page.) | |
| +| C30 | CLI, canary --region on a fleet-placed app | {app} runs on your fleet {fleet}, which decides where every instance runs, so --region does not apply. Run the command without --region. | |
| | C25 | CLI, canary --region on an existing canary | canary {host} already runs in {place}. --region applies only when canary creates the host. Run carlos canary remove first to start it somewhere else. | |
| | C26 | API, defaulted home inside its replica set | this app's region {code} is also one of this instance's replica placements. Pass a region for the instance, or drop {code} from replicas. | |
| | C27 | API, guess with unreadable policy | could not read this app's instance policy, so there is no region suggestion. Try again. | |
| @@ -1349,6 +1414,16 @@ The `docs/site/cli.md`, README and release-note additions (§5.7, §11, §12, |
| | former question | disposition | |
| | --- | --- | |
| -| Q1: apply R5, titogo separate | adopted (§2, §17 step 7) | |
| +| Q1: apply R5, titogo separate | adopted (§2, §17 step 8) | |
| | Q2: implement R6 with the DNS corrections, keep the separate approved Phase L rollout and measurements | adopted in substance. The corrections are larger than a patch, so they move to the follow-on spec with invariants (§13); the sequencing is ruled by R8. | |
| -| Q3: name the exact floor version before activation; keep region-aware reads when disabling | adopted (§5.6, §5.7, §17 step 5) | |
| +| Q3: name the exact floor version before activation; keep region-aware reads when disabling | adopted (§5.6, §5.7, §17 step 6) | |
| + |
| +### Round 4 (rev 4.1, 4ad64f7c) |
| + |
| +| # | sev | finding | disposition | |
| +| --- | --- | --- | --- | |
| +| R4-1 | P2 | Fleet-placed apps still get instance guesses the door will override | **accepted.** Verified at `fleets.go:479` and `apiinstances.go:798`. The guess endpoint resolves fleet placement first and returns `null`; app-creation inference is unchanged; end-to-end test (§6.1, §9.4, §12, §16). | |
| +| R4-2 | P2 | `canary --region` on a fleet behaves differently before and after creation | **accepted.** Verified at `canary.go:405` and `apiinstances.go:798`. Fleet precedence is resolved before either branch, and `--region` on a fleet-placed app refuses identically every time with C30, never suggesting a remove (§11, §12, §16). | |
| +| R4-3 | P2 | Phase P verification can pass while non-home instances are unreachable | **accepted.** Verified at `instancesplacement.go:185`, `edge.go:2699` and `:1428`. A service gate before the production switch: a fresh probe per eligible region, reached from outside through the platform host and through every other edge with `--resolve`, checking 2xx and `X-Carlos-Version`. All regions must pass. Plus an integration test for the missing-mesh-key case (§16, §17 step 5). | |
| + |
| +Astra raised no open questions in round 4. §18 stands at none. |