carlos / platform Public

docs: region flags, console env and release checklist (nearest-region D1-D10)

D1's example fence gains the sh info string and D9's record file now points
at docs/site/cli.md, where it was placed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev d56580945028770d01b248c3e16585f675a87fb3 parent d6b1b83
3 files changed, +70 −2
  • README.md +46 −0
  • docs/site/cli.md +22 −0
  • docs/superpowers/plans/2026-10-02-nearest-region-copy.json +2 −2
diff --git a/README.md b/README.md
index c01f67ea8..e9a98bd3f 100644
--- a/README.md
+++ b/README.md
@@ -254,10 +254,44 @@ carlos system update --console <console> --version $v # then: carlos sys
Last, roll it: `carlos system update --console <console> --version vX.Y.Z`, then `carlos system status`. Every box should read `current` within the spread. A box that reads `rolled-back` or `refused` says why on the same line; fix that box, and do not re-cut the release.
+- Refresh the embedded timezone tables when a new IANA tzdata release is out:
+ `scripts/update-tzgeo.sh <version>`, then `go test ./internal/geo`.
+
A release-specific note goes here when a release changes something an
operator needs to check or act on before rolling it. Read the note for
the release you are about to roll.
+**App regions rollback floor.** Once any app holds a region other than the
+home, do not roll the console binary below the first release that
+understands app regions: an older console places a no-region create at the
+home. That release's version is named here, in its release notes, and in the
+console roll recipe in carloku-infrastructure; check all three name the same
+version before turning `CONSOLE_APP_REGIONS` on. If a rollback below the
+floor happens anyway, write down when it started and ended: on its next
+start the console logs "possible placement discrepancy" lines, and the
+interval tells you which to review. Turning the switch off is the feature's
+own rollback: it stops new non-home regions and keeps existing ones in force.
+
+**Turning app regions on.** In order:
+
+1. Stage the deployment's `CARLOS_SECRETS_KEY` on every box in every region
+ that should take placed instances, and set `CARLOS_SECRETS_PUBKEY` on the
+ console. A box without the key runs placed instances with no config, no
+ secrets and no store.
+2. Roll the box release that publishes `serves_shared_pool`,
+ `accepts_inbound` and `secrets_key_fp`. Check every region's
+ `control/edges/*.json` carries them, and that each `secrets_key_fp` equals
+ the console's.
+3. Roll the console with the switch off.
+4. On dev, turn the switch on. Create an app and an instance, read their
+ region, and run the service gate below for every region.
+5. On production, with the switch still off, create one probe instance per
+ eligible region with an explicit `--region`, deploy a known build, and run
+ `scripts/region-service-gate.sh <probe-host> <build> <every other edge EIP>`
+ for each. Every region must pass. Delete the probes.
+6. Turn the switch on. Re-run step 5 after any period with the switch off,
+ and whenever a new region becomes eligible.
+
### Tenant email senders, plan 2 of 5 (2026-09-21)
This release turns the one implicit per-app email sender into an explicit
@@ -493,6 +527,15 @@ publish an inflated instance count.
(guidance text), so the two can never name different addresses. Default:
the box's public IP via IMDS, best-effort; if neither is knowable the
checks stand down and health reads unknown, never a guess.
+- `CONSOLE_APP_REGIONS`: `on` lets new apps be given a region other than the
+ home. It needs `CONSOLE_HOME_REGION` set too. With the home unnamed, the
+ console logs once and leaves app regions off. Default off. Automatic
+ placement outside the home also needs `CARLOS_SECRETS_PUBKEY`, so the
+ console can check that a box holds the deployment's secrets key.
+- `CONSOLE_GEOIP_DB`: the path to an operator-supplied MaxMind-format `.mmdb`.
+ DB-IP Lite is recommended, and it requires the attribution the new-app page
+ shows. Unset or unusable turns the IP step of the region guess off, and the
+ console starts either way. Replace the file by atomic rename, then restart.
**Canonical-host caveat**, stated honestly: an app that pins its own host
(e.g. keymail's `--host keymail.dev`) will redirect or 404 on its platform
@@ -579,6 +622,9 @@ no single reader owns the whole contract:
operator-set, coarse region word (e.g. `"EU · Ireland"`) carried onto
this box's snapshot verbatim; unset omits the field. Free text the
operator chooses, never derived from cloud metadata.
+ Keep it geographically consistent with the region's place name in the
+ console's region picker (for example "EU · Ireland" beside
+ "Ireland · Dublin"); it may stay coarser.
- `STATUS_HOST` / `STATUS_PUBLIC` — not read by the agent process itself;
consumed by ops/tofu to decide whether the edge fronts the bucket's
`status/` prefix with a public hostname. `STATUS_PUBLIC=1` publishes the
diff --git a/docs/site/cli.md b/docs/site/cli.md
index 25ee987ec..0c3b5d553 100644
--- a/docs/site/cli.md
+++ b/docs/site/cli.md
@@ -119,6 +119,14 @@ uses, so a name that form would refuse is refused here too.
carlos apps create --app hello
```
+`--region <code>` sets the region the app's instances start in. Without it,
+the console picks the region nearest you from your timezone, and the command
+prints one line saying which and why:
+
+```sh
+carlos apps create --app hello --region uk
+```
+
A name is also refused when its platform apex, `<app>.<account>.<apps
domain>`, is already taken: by another app's instance, even one in the Trash,
or by any route there at all. The new app would otherwise never get an address
@@ -244,6 +252,11 @@ having no instances to resolve a channel from.
carlos deploy --app website --kind static --host www.example.com ./dist
```
+When `carlos deploy` creates your first app, it sends your timezone so the
+console can pick the region nearest you, and prints the choice. `--region
+<code>` chooses instead. On an app that already exists, `--region` must
+match the app's region, or deploy stops before shipping anything.
+
#### Deploying one checkout to several places
When one project deploys to more than one console, or as more than one app,
@@ -298,6 +311,10 @@ has to be both a channel segment and a hostname label, an explicit `--name`
is checked rather than quietly repaired — lowercase letters, digits and
dashes.
+`--region <code>` places a new canary in that region (default: the app's
+region). It is refused for an existing canary in another region, and for an
+app that runs on one of your fleets.
+
Promotion into a canary is never refused, on any app, from any rung. Nor
does a canary cost you a rung: canary channels sit outside every pipeline, so
a build that has been on one still enters the pipeline as a fresh build.
@@ -586,6 +603,8 @@ carlos instances enable --app hello
carlos instances list --app hello
```
+Without `--region`, a new instance starts in the app's region.
+
`--health <slug>` narrows the listing to one state: `running`, `asleep`,
`not-responding` and the rest. That is usually what you want when something
is wrong.
@@ -1408,6 +1427,9 @@ of its own. A person enables the app once and the credential provisions inside
those bounds afterwards, including minting a tenant's bucket it could never
have created directly.
+A manifest's `regions` allow-list compares the home as `""`, so listing the
+home's code does not admit the home.
+
The secret prints once, same as a fleet token, and the same distinction
applies at the other end: `rotate` refuses the credential's next call with
the old secret, `revoke` stops it whatever secret it holds.
diff --git a/docs/superpowers/plans/2026-10-02-nearest-region-copy.json b/docs/superpowers/plans/2026-10-02-nearest-region-copy.json
index 19c76ea5e..19e4da7cf 100644
--- a/docs/superpowers/plans/2026-10-02-nearest-region-copy.json
+++ b/docs/superpowers/plans/2026-10-02-nearest-region-copy.json
@@ -557,7 +557,7 @@
{
"id": "D1",
"file": "docs/site/cli.md",
- "text": "`--region <code>` sets the region the app's instances start in. Without it,\nthe console picks the region nearest you from your timezone, and the command\nprints one line saying which and why:\n\n```\ncarlos apps create --app hello --region uk\n```"
+ "text": "`--region <code>` sets the region the app's instances start in. Without it,\nthe console picks the region nearest you from your timezone, and the command\nprints one line saying which and why:\n\n```sh\ncarlos apps create --app hello --region uk\n```"
},
{
"id": "D2",
@@ -596,7 +596,7 @@
},
{
"id": "D9",
- "file": "README.md",
+ "file": "docs/site/cli.md",
"text": "A manifest's `regions` allow-list compares the home as `\"\"`, so listing the\nhome's code does not admit the home."
},
{