carlos / platform Public
File too large to show in full — download it above (showing a truncated preview below).
Nearest Region by Default: Implementation Plan
> For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.
Revision: 7. A one-page summary for review (what ships per release, the task list, the copy-review gate, the rollout and the risks) is in 2026-10-02-nearest-region-default-SUMMARY.md, beside this file.
- Rev 1 (9513d52c) was reviewed by Astra (
CODEX-PLAN-NEAREST-REGION-R1.md, beside this file): not ready, with 8 P1 and 10 P2. Every finding was checked against the code and accepted. - Rev 2 (fcf15ae9) folded in spec rev 6's ruling R9: placed instances need the deployment's secrets key.
- Rev 3 answers Astra's round 2 (
CODEX-PLAN-NEAREST-REGION-R2.md). - Rev 4 answers Astra's round 3 (
CODEX-PLAN-NEAREST-REGION-R3.md) and carries spec rev 6.1's R9a note (region_known,placement_known). - Rev 5 answers Astra's round 4 (
CODEX-PLAN-NEAREST-REGION-R4.md) and carries spec rev 6.2 (instance_domain, approved by Paul 2026-10-03). - Rev 6 answers Astra's round 5 (
CODEX-PLAN-NEAREST-REGION-R5.md), and marks the flagship's secrets-key staging done (Rollout step 2). - Rev 7 answers Astra's round 6 (
CODEX-PLAN-NEAREST-REGION-R6.md). - The code blocks the findings touched were proved: extracted verbatim into a throwaway worktree at the branch base, built and vetted, and each prescribed test was run before and after its implementation step.
- The six dispositions tables, with the proof results, are at the end.
Goal: Give every new app a region at creation, preselected from a timezone-then-IP guess, and have new instances default to that region. Never place an instance automatically on a box that cannot open the deployment's secrets. Existing apps, older CLIs and DNS stay unchanged.
Architecture:
- Boxes publish four more facts in their edges file:
serves_shared_pool,accepts_inbound, andsecrets_key_fp(the fingerprint of their secrets key's public half). Together these let the console tell which regions can really run an ordinary instance. - A new package,
internal/geo, turns timezones and IPs into points and picks the great-circle nearest eligible region. - The console: - stores
regionandregion_sourceon theappsrow, resolved insideclaimAppafter it knows whether the claim is an adoption; - fills an empty instance region from the app's region before the existing checks; - refuses explicit creates into a region provably without the secrets key (R9); - addsapp_defaulttoGET …/regions, and a POST guess endpoint. - The CLI sends a timezone hint and gains
--regionwhere the spec says.
All non-home writes sit behind CONSOLE_APP_REGIONS=on together with a named home.
Tech Stack:
- Go 1.26 and SQLite (modernc).
- An embedded IANA tzdata
zone.taband its links. github.com/oschwald/maxminddb-golangv1.13.1 for the optionalCONSOLE_GEOIP_DB.crypto/ecdhP-256, as already used byinternal/appconfig.net/httpandhtml/template.
Spec: docs/superpowers/specs/2026-10-01-nearest-region-default-design.md (rev 6.2, approved by Paul 2026-10-02, including the R9 amendment, the R9a note and the rev 6.2 instance_domain note approved 2026-10-03). Read it beside this plan. Section numbers below (§N) are the spec's.
Global Constraints
- Scope is placement only (R8). No DNS writes, no
Steeringstamp, no converger change, noCARLOS_STEERING_ZONE_ID. - Nothing writes a non-home app region unless
CONSOLE_APP_REGIONS=onandCONSOLE_HOME_REGIONis set (§5.6). With the switch off, the door's fill still runs for rows that already hold a region. regionis stored in the record vocabulary, where""is the home (§5.1).region_source∈ {chosen,timezone,ip,default,""}.- Never stored, logged, audited, put in a URL, or echoed in a response or error: a timezone, a client IP, a coordinate, or any secrets key (§6.5, R9). That includes errors returned by dependencies, which are logged as fixed categories, never verbatim, wherever they could carry an address.
- Every non-null guess passes
instanceRegionAllowed(m.Regions, homeRegionIn(code))(§6.4). - Automatic choices need positive eligibility on one box: reachable;
hibernation_configured,serves_shared_poolandaccepts_inboundalltrue; andsecrets_key_fpequal to the fingerprint of the console'sCARLOS_SECRETS_PUBKEY(§6.1, R9). The home is exempt. - Explicit requests keep today's gates, plus one refusal (R9, C31): a socket-backed or replica create into a region where every reachable, hosting box is a current writer and none holds the deployment's key.
- App-derived defaults never pass on the dormant gate. An edges-read failure is a 503 with C28 (§7).
- Pooled manifests and fleet-placed apps get no instance guess (§6.1).
- Wire changes are additive (AGENTS.md).
- Copy. - Every user-facing or operator-facing string is reviewed before it is written into any product file. Task 0 runs the copy-review skill on the complete inventory and records the approved text in
docs/superpowers/plans/2026-10-02-nearest-region-copy.json. - Later tasks copy strings from that record verbatim. - Task 25 proves every recorded string is present, byte for byte, in the file the record names. - No em dashes in any of it. - Build and test gate, clean before every commit that touches Go:
go build ./...,go vet ./...,gofmt -l .(empty), andgo test ./... -count=1.make testruns all of it. - Cross-compile gate. The five CI targets, exactly as
.amadan/ci.d/20-cross@5runs them:CGO_ENABLED=0 GOOS=<os> GOARCH=<arch> go build ./...for darwin/arm64, darwin/amd64, linux/arm64, windows/amd64 and windows/arm64. Every loop below records each failure and ends with[ "$fail" = 0 ] || exit 1. Run each such block as one script (bash -cor a tool call), so that a failed target stops it before the commit lines that follow (codex plan-R2 10). - Test environment. - Use the default Go build cache (
~/.cache/go-build, writable inside the sandbox). Do not setGOCACHE,GOMODCACHEorGOTMPDIR. - Scratch goes under$TMPDIR, which is on disk. - Operator rule for this machine (socket tests). The operator instructs that ONLY the Unix-socket test packages run withTMPDIRunder/tmp/g:internal/host,internal/activator,internal/edge, and any other package whose tests bind a Unix socket (today alsointernal/agentsock,internal/instancectl,internal/instanceexec,internal/unixdialandinternal/vet). A Unix socket path is capped at 108 bytes, and the on-diskTMPDIRhere (/home/paulca/.cache/tmp/claude-1001/...) is too deep for Go's test directories plus the socket names under them. The directory is small and is deleted after the run./tmp/gis shared, so each run makes its own directory under it and deletes only that; nothing else in/tmp/gis touched. Everything else uses the on-diskTMPDIR. CI is unaffected: it has no such path limit and sets nothing here. Every socket-test command in this plan goes through this function:
``bash socktest() { # go test args...; TMPDIR is a fresh short dir, deleted after mkdir -p /tmp/g || return 1 local d rc d="$(mktemp -d /tmp/g/nr.XXXXXX)" || return 1 TMPDIR="$d" go test "$@"; rc=$? rm -rf "$d" return "$rc" } ` - Console tests need an on-disk TMPDIR (the sandbox's $TMPDIR is one), because an empty /tmp/.git` breaks Go's VCS stamping.
- CLI tests that shell out to the carlos binary build their env through
carlosTestEnvand setcmd.Dirto a scratch dir (AGENTS.md). - Flags are documented.
docs/site/cli.mddocuments every new flag. No new commands. - Commits. End every commit message with
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>. No push.git configonly with--worktree.
Review Focus
The five inputs most likely to bite a person that no spec-derived test covers. Each is pinned by a test in the task named.
- Real-machine timezone text. An
/etc/timezonewith a trailing newline,TZ=":",TZ="localtime",TZ="posixrules"and a POSIX rule string must give a clean name or nothing. Pinned in Task 19 and Task 3. - IPv6 and IPv4-mapped clients. -
::ffff:81.2.69.142locates as81.2.69.142. - A public IPv6 address looked up in an IPv4-only database is a miss whose log line never contains the address. - Private and loopback addresses never reach the reader.
Pinned in Task 5.
- A region that stops being eligible between rendering the new-app page and submitting it (for example, its box loses the secrets key). The form re-renders with C5 and no app row is written. Pinned in Task 13.
- Garbage hints at the guess endpoint.
"ip":"not-an-ip"and a 10 KB"tz"are treated as absent: a 200, never a 400 or 500. Pinned in Task 15. - A claim that fails after the region was resolved. A lost insert race writes no row and logs no
app regionline. A held apex refuses before resolution and logs nothing either. Pinned in Task 12.
---
File map
| File | Responsibility | Tasks | | --- | --- | --- | | docs/superpowers/plans/2026-10-02-nearest-region-copy.json | the approved copy record | 0 | | internal/provision/reconcile.go | ReconcileConfig.SharedInstanceEligibility() | 1 | | internal/host/edgesfile.go | publish serves_shared_pool, accepts_inbound, secrets_key_fp | 1, 1b | | internal/appconfig/fingerprint.go | KeyFingerprint, the one renderer of secrets_key_fp | 1b | | internal/geo/* | points, nearest, tz data, AWS region table, GeoIP | 2 to 5 | | scripts/update-tzgeo.sh | refresh the embedded tz files | 3 | | internal/console/regioncopy.go, regionmsgs.go | console copy and API messages | 6, 7 | | internal/console/store.go | columns, rebuild list, accessors | 8 | | internal/console/server.go (Config), regionconfig.go, cmd/console/main.go | switch, GeoIP, expected secrets fingerprint, test seams | 9 | | internal/console/regioncandidates.go | strict edges read, candidates, eligibility incl. R9, policy | 10 | | internal/console/regionguess.go | guessRegion | 11 | | internal/console/regionclaim.go, server.go, apicli.go | regionReq, explicit validation, claim resolution, API create | 12 | | internal/console/regionpicker.go, templates/app_new.html | the new-app picker and its POST and guess route | 13 | | internal/console/apiinstances.go | the fill, app-derived eligibility, the R9 explicit refusal, response fields | 14 | | internal/console/regiondefault.go, apiregions.go, authcli.go | app_default, names, POST …/regions/guess | 15 | | templates/app_instances.html, server.go | Instances tab lines | 16 | | internal/console/regiondrift.go | roll-forward discrepancy log | 17 | | region_copy.go, timezone.go, apps_region.go (root) | CLI copy, timezone, region line | 18, 19, 20 | | apimode.go, apps.go, deploy.go, canary.go, instances_cli.go | CLI wire and flags | 20 to 22 | | deploy_region_e2e_test.go, canary_region_e2e_test.go (root) | command-level proofs | 21, 22 | | scripts/region-service-gate.sh, internal/edge/servicegate_test.go | the service gate and its real-backhaul proof | 23 | | docs/site/cli.md, README.md | docs, release checklist, floor | 24 | | approved_copy_test.go (root) | proves the record is applied | 25 |
Order: Task 0 first, because no product file may hold an unreviewed string. Then 1, 1b, 2 to 25 in order.
---
Task 0: Copy inventory, review and the approved record (before any product file)
Files:
- Create:
docs/superpowers/plans/2026-10-02-nearest-region-copy.json
Interfaces:
- Produces: - the approved copy record, read by every later task and asserted by Task 25; - its shape:
{
"approved_by": "Paul",
"approved_on": "YYYY-MM-DD",
"entries": [
{"id": "C1", "file": "internal/console/regioncopy.go", "text": "Region"}
]
} text is the exact byte sequence the named file must contain. Go format verbs (%s, %v, %q, %d) and shell variables ($label) stay as written, because the file contains them.
- [ ] Step 0: Resolve the fixture commit (network, read-only)
F1 names the MaxMind-DB commit Task 5 fetches its fixtures from, so the commit is fixed BEFORE review and the reviewed text is the text Task 5 writes (codex plan-R3 3). Nothing substitutes into an approved string later.
sha=$(git ls-remote https://github.com/maxmind/MaxMind-DB refs/heads/main | cut -f1)
[ ${#sha} = 40 ] && [ -z "${sha//[0-9a-f]/}" ] || { echo "could not resolve MaxMind-DB main"; exit 1; }
echo "$sha"F1's draft, with that sha written in place of <sha> before the review:
The .mmdb files and GeoIP2-Country-Test.json are MaxMind's published test
data from github.com/maxmind/MaxMind-DB, dual licensed MIT or Apache-2.0
(LICENSE-MIT, LICENSE-APACHE beside this file). All were fetched at commit
<sha>. They contain test records only, not real geolocation data.- [ ] Step 1: Assemble the complete inventory
Every string this plan writes into a product file, with its draft. The drafts are spec §19's where an id is a C-number. The rest are added by this plan and marked "plan".
| id | file | draft | | --- | --- | --- | | C1 | internal/console/regioncopy.go | Region | | C2 | same | Where this app's instances run. We picked the region nearest to you. Choose another if most of your users are somewhere else. | | C3 | same | %s (nearest to you) | | C5 | same | That region can't take new apps right now. Choose another. | | C7 | same | New instances start in %s, this app's region. | | C8 | same | New instances can't start in %s, this app's region, right now. Pass a region to place one elsewhere. | | C21 | same | created app %s in %s | | C24 | same | IP location by DB-IP. (confirm DB-IP's required wording at <https://db-ip.com/db/lite.php>) | | R-absent | internal/console/regionmsgs.go | no box runs there now | | R-not-hosting | same | boxes there can't start instances | | R-no-secrets-key | same | boxes there can't open this deployment's secrets | | R-not-eligible | same | boxes there don't take ordinary instances | | C16 | same | this app's region %s can't take new instances right now (%s). Pass a region to place this instance elsewhere. Regions that can: %s. | | C17 | same | this app's region %s is not one its instances may use. Allowed: %s. Pass a region to choose one. | | C18 | same | region %s is not one this platform runs in. Choose from: %s. | | C19 | same | region %s can't take new apps right now. Choose from: %s. | | C16b (plan) | same | this app's region %s can't take new instances right now (%s), and no other region this app may use can either. Try again later. (C16 when no region would pass) | | C17b (plan) | same | this app's region %s is not one its instances may use, and no region they may use can take new instances right now. Try again later. (C17 when no allowed region would pass) | | C18b (plan) | same | region %s is not one this platform runs in. Run the command without --region. (C18 when no region would pass) | | C19b (plan) | same | region %s can't take new apps right now. Run the command without --region. (C19 when no region would pass) | | C20 | same | could not read this app's region. Try again. | | C26 | same | this app's region %s is also one of this instance's replica placements. Pass a region for the instance, or drop %s from replicas. | | C27 | same | could not read this app's instance policy, so there is no region suggestion. Try again. | | C28 | same | could not confirm that this app's region %s can take new instances. Try again, or pass a region. | | C29 | same | possible placement discrepancy: %s of %s has no region, but %s's region is %s. It may be a deliberate home override; review it. | | C31 | same | region %s can't run instances right now: its boxes don't hold this deployment's secrets key, so an instance there would start without its config or secrets. Choose another region. | | O1 (plan) | internal/console/regionconfig.go | console: CONSOLE_APP_REGIONS=on but CONSOLE_HOME_REGION is unset, so app regions stay off until the home is named | | O2 (plan) | same | console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically | | O3 (plan) | cmd/console/main.go | console: CONSOLE_GEOIP_DB %s is unusable, so the IP step of region guesses is off: %v | | O4 (plan) | same | console: GeoIP database loaded from %s | | O5 (plan) | internal/geo/mmdb.go | geo: a GeoIP lookup failed and was treated as a miss | | O6 (plan) | internal/console/regioncandidates.go | console: boxes for region %q disagree on their AWS region %v; using %s | | O7 (plan) | same | console: region %q has no point in the shipped AWS region table (AWS region %q), so region guesses leave it out | | O8 (plan) | internal/console/server.go | console: app region %s/%s: %s (%s) | | O9 (plan) | internal/console/regiondrift.go | console: placement discrepancy check %s/%s: only the first %d records were checked | | O10 (plan) | internal/host/edgesfile.go | agent: CARLOS_SECRETS_KEY does not parse, so no secrets_key_fp is published: %v | | O11 (plan) | internal/console/apiinstances.go | console: api instances create %s/%s: read app region: %v | | O12 (plan) | same | console: api instances create %s/%s: read edges: %v | | O13 (plan) | internal/console/regioncandidates.go | console: region policy %s/%s: read manifest: %v | | O14 (plan) | same | console: region policy %s/%s: open registry: %v | | O15 (plan) | same | console: region policy %s/%s: placement: %v | | O16 (plan) | internal/console/apicli.go | console: claim %s/%s: read back region: %v | | O17 (plan) | apimode.go | regions: unreadable response: %w | | O18 (plan) | internal/console/regiondrift.go | console: placement discrepancy check %s/%s: %v | | O19 (plan) | same | console: placement discrepancy check: %v | | O20 (plan) | apimode.go | instance: unreadable response: %w | | C9 | region_copy.go | Region: %s (from your timezone). Use --region to choose another. | | C10 | same | Region: %s (from your network location). Use --region to choose another. | | C11 | same | Region: %s (the platform default). Use --region to choose another. | | C12 | same | Region: %s. | | C13a | same | This console does not record an app region yet, so --region was not applied. | | C13b | same | This console is not assigning app regions yet, so --region was not applied. This app's instances start in %s. | | C14 | same | channel %s, %s | | C15 | same | %s already runs in %s. --region only applies when deploy creates an app. To place one instance elsewhere, use carlos instances create --region. | | C15b (plan) | same | could not read %s's region (%v), so deploy stopped before shipping anything. Try again, or run without --region. | | C15c (plan) | same | the console could not read %s's region, so deploy stopped before shipping anything. Try again, or run without --region. | | C25 | same | canary %s already runs in %s. --region applies only when canary creates the host. Run carlos canary remove first to start it somewhere else. Note for Paul: the verb is carlos canary rm (or delete); there is no remove. | | C30 | same | %s runs on your fleet %s, which decides where every instance runs, so --region does not apply. Run the command without --region. | | C30b (plan) | same | this console can't say where %s's instances run, so --region can't be checked. Update the console, or run the command without --region. | | C30c (plan) | same | could not confirm where canary %s runs, so nothing was changed. Try again, or run the command without --region. | | C22a | same | region this app's instances start in (default: the region nearest you) | | C22b | same | region for the app this deploy creates (default: the region nearest you) | | C22c | same | region for the canary instance (default: the app's region) | | C22d | same | region for this instance (default: the app's region, subject to the manifest's allow-list) | | U1 (plan) | apps.go | [--region <code>] in the apps create usage string | | U2 (plan) | deploy.go | [--region <code>] in the deploy usage string | | U3 (plan) | canary.go | [--region <code>] in canaryUsage | | S1 (plan) | scripts/update-tzgeo.sh | usage: update-tzgeo.sh <tzdata version, e.g. 2026b> | | S2 (plan) | same | gpg not found: signature NOT checked | | S3 (plan) | same | internal/geo/data refreshed to tzdata $v | | G1 (plan) | scripts/region-service-gate.sh | usage: region-service-gate.sh <probe-host> <expected-version> [<edge-eip> ...] | | G2 (plan) | same | ok $label: $status $version | | G3 (plan) | same | FAIL $label: request failed | | G4 (plan) | same | FAIL $label: status $status | | G5 (plan) | same | FAIL $label: X-Carlos-Version '$version', want '$want' | | G6 (plan) | same | FAIL $label: connected to $ip, not $eip | | C23 | internal/geo/awsregions.go | the 34 place names of spec §19, one entry each (C23-us-east-1 and so on) | | D1 to D4 | docs/site/cli.md | the four paragraphs in Task 24 Step 1 | | D5 to D10 | README.md | the six passages in Task 24 Step 2 | | F1 (plan) | internal/geo/testdata/README | the fixture provenance note below, with <sha> already replaced by the commit Step 0 resolved |
- [ ] Step 2: Run the copy review
Invoke the copy-review skill with the whole inventory: every row, with where it appears and the C-number's spec context. Do not write any of these strings into a product file before the review returns. Wait for Paul's edits.
- [ ] Step 3: Write the approved record
Write the JSON file with one entry per row. Use Paul's approved text verbatim (the draft where he left it unchanged), in each string's code form. That means Go verbs in place of {…} placeholders, and the file the row names. Set approved_on to the review date.
- [ ] Step 4: Commit
git add docs/superpowers/plans/2026-10-02-nearest-region-copy.json
git commit -m "plan: approved copy record for nearest-region (copy review)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"From here, every task that writes a string from the inventory copies it from the record. The code blocks below show the drafts. Where the record differs, the record wins, and the task's tests are updated to match.
---
Task 1: Boxes publish shared-pool and inbound eligibility
Files:
- Modify:
internal/provision/reconcile.go(besidepoolServed,:1339) - Modify:
internal/host/edgesfile.go:155-206(struct),:600-611(ident inedgesFileLoop) - Test:
internal/provision/reconcile_eligibility_test.go(create),internal/host/edgesfile_test.go
Interfaces:
- Consumes:
provision.ReconcileConfig{Pools []string; Inbound string},provision.InboundNone,poolServed, andprovisionTickConfig(getenv) provision.ReconcileConfig(internal/host/agent.go:2030). - Produces: -
func (cfg ReconcileConfig) SharedInstanceEligibility() (sharedPool, inbound bool); -edgesFile.ServesSharedPool bool(json:"serves_shared_pool", not omitempty); -edgesFile.AcceptsInbound bool(json:"accepts_inbound", not omitempty).
- [ ] Step 1: Write the failing provision test
// internal/provision/reconcile_eligibility_test.go
package provision
import "testing"
// SharedInstanceEligibility must be the reconciler's own predicates, never a
// second reading of the environment (spec §6.1, codex R3-3).
func TestSharedInstanceEligibilityIsTheMintPredicate(t *testing.T) {
cases := []struct {
pools []string
inbound string
shared, accept bool
}{
{nil, "", true, true},
{[]string{"tito"}, "", false, true},
{[]string{"", "tito"}, "", true, true},
{nil, InboundNone, true, false},
{[]string{"tito"}, InboundNone, false, false},
}
for _, c := range cases {
cfg := ReconcileConfig{Pools: c.pools, Inbound: c.inbound}
shared, inbound := cfg.SharedInstanceEligibility()
if shared != poolServed(c.pools, "") || inbound != (c.inbound != InboundNone) {
t.Fatalf("%+v: eligibility diverges from the mint predicates", c)
}
if shared != c.shared || inbound != c.accept {
t.Fatalf("%+v: got (%v, %v), want (%v, %v)", c, shared, inbound, c.shared, c.accept)
}
}
}
// spec §16, codex plan-R3 4: each published answer pairs with the reconcile
// pass's own behaviour on an ORDINARY record (the shared "" pool, routed).
// A box that publishes both true mints it; a box that publishes either
// false refuses it, so the console's eligibility never offers a region
// whose box would refuse the record.
func TestEligibilityPairsWithTheReconcileRefusal(t *testing.T) {
for _, c := range []struct {
pools []string
inbound string
}{
{nil, ""}, {[]string{"tito"}, ""}, {[]string{"", "tito"}, ""},
{nil, InboundNone}, {[]string{"tito"}, InboundNone},
} {
s, sqid := setup(t)
if err := PutManifest(ctx, s, sqid, "tito", Manifest{Schema: 1, App: "tito", Domain: "go.tito.io"}); err != nil {
t.Fatal(err)
}
rec := acmeRecord(sqid)
rec.Pool = ""
if err := CreateRecord(ctx, s, rec); err != nil {
t.Fatal(err)
}
reg := regWith(t)
cfg := ReconcileConfig{Region: "ie", Pools: c.pools, DataDir: "/data", Inbound: c.inbound}
if err := Reconcile(ctx, s, reg, cfg); err != nil {
t.Fatal(err)
}
rt, err := reg.Get(rec.Host)
minted := err == nil && rt != nil
shared, inbound := cfg.SharedInstanceEligibility()
if minted != (shared && inbound) {
t.Errorf("pools %q inbound %q: minted %v, but the box publishes serves_shared_pool=%v accepts_inbound=%v", c.pools, c.inbound, minted, shared, inbound)
}
}
}setup, acmeRecord, regWith and ctx are reconcile_test.go's (same package). setup's manifest is pooled, so the test replaces it with a shared-pool one before creating the ordinary record.
- [ ] Step 2: Run it to verify it fails
Run: go test ./internal/provision -run 'TestSharedInstanceEligibilityIsTheMintPredicate|TestEligibilityPairsWithTheReconcileRefusal' -count=1 Expected: FAIL, cfg.SharedInstanceEligibility undefined.
- [ ] Step 3: Implement it beside
poolServed
// SharedInstanceEligibility answers, from this box's own reconcile config,
// whether it mints an ORDINARY instance record: one in the shared ""
// pool, needing inbound service. The edges file publishes both answers
// (serves_shared_pool, accepts_inbound) so the console never offers an
// automatic choice of a region whose boxes would refuse the record
// (nearest-region spec §6.1). It is the reconciler's own predicates —
// poolServed and the CARLOS_INBOUND=none refusal at reconcile.go:925 — so
// publication and minting cannot disagree.
func (cfg ReconcileConfig) SharedInstanceEligibility() (sharedPool, inbound bool) {
return poolServed(cfg.Pools, ""), cfg.Inbound != InboundNone
}- [ ] Step 4: Run it to verify it passes
Run: go test ./internal/provision -run 'TestSharedInstanceEligibilityIsTheMintPredicate|TestEligibilityPairsWithTheReconcileRefusal' -count=1 Expected: PASS.
Then a mutation check on the pairing: make SharedInstanceEligibility return true, true, and re-run TestEligibilityPairsWithTheReconcileRefusal. It must FAIL on every config the reconciler refuses. Revert, and re-run: PASS.
- [ ] Step 5: Write the failing edges-file tests
Append to internal/host/edgesfile_test.go:
// The two eligibility fields are NOT omitempty: absent means "a writer
// that predates the field" (unknown), false means "no" (spec §6.1).
func TestEdgesFileRawWireBodyEligibilityFields(t *testing.T) {
for _, want := range []bool{true, false} {
s, reg := release.NewDirStore(t.TempDir()), edgesTestRegistry(t)
seedRoute(t, reg, "app.example.com")
var last []byte
ident := edgesFile{Box: "quiet-harbour", Region: "eu-west-2", Placement: "uk",
ServesSharedPool: want, AcceptsInbound: want}
if err := edgesFilePass(context.Background(), s, reg, ident, "203.0.113.10", &last); err != nil {
t.Fatal(err)
}
got, _, err := s.Get(context.Background(), "control/edges/quiet-harbour.json")
if err != nil {
t.Fatal(err)
}
var raw map[string]any
if err := json.Unmarshal(got, &raw); err != nil {
t.Fatal(err)
}
for _, k := range []string{"serves_shared_pool", "accepts_inbound"} {
v, present := raw[k]
if !present || v != want {
t.Fatalf("%s = %v (present %v), want %v always published", k, v, present, want)
}
}
}
}
// What the loop publishes is the reconcile config's own answer, for every
// spelling an operator might write. CARLOS_POOLS=",tito" parses to
// ["tito"] (agent.go's parser drops empty elements), so it does NOT serve
// the shared pool and must publish false (codex R3-3).
func TestEdgesFileEligibilityFollowsTheReconcileConfig(t *testing.T) {
cases := []struct {
pools, inbound string
shared, accept bool
}{
{"", "", true, true},
{"tito", "", false, true},
{",tito", "", false, true},
{"", "none", true, false},
{"tito", "NONE", false, false},
}
for _, c := range cases {
getenv := func(k string) string {
switch k {
case "CARLOS_POOLS":
return c.pools
case "CARLOS_INBOUND":
return c.inbound
}
return ""
}
shared, inbound := edgesFileEligibility(getenv)
cfg := provisionTickConfig(getenv)
wantShared, wantInbound := cfg.SharedInstanceEligibility()
if shared != wantShared || inbound != wantInbound {
t.Fatalf("%+v: published (%v, %v) but the reconciler says (%v, %v)", c, shared, inbound, wantShared, wantInbound)
}
if shared != c.shared || inbound != c.accept {
t.Fatalf("%+v: got (%v, %v), want (%v, %v)", c, shared, inbound, c.shared, c.accept)
}
}
}- [ ] Step 6: Run them to verify they fail
Run: socktest ./internal/host -run 'TestEdgesFile(RawWireBodyEligibilityFields|EligibilityFollowsTheReconcileConfig)' -count=1 Expected: FAIL, unknown fields ServesSharedPool and AcceptsInbound, and edgesFileEligibility undefined.
- [ ] Step 7: Implement the fields, the helper and the loop wiring
In edgesFile, after HibernationConfigured:
// ServesSharedPool and AcceptsInbound are this box's own answers to
// "would my reconcile pass mint an ORDINARY instance record" — one in
// the shared "" pool, needing inbound service (nearest-region spec
// §6.1). Deliberately NOT omitempty, HibernationConfigured's precedent:
// absent is a writer that predates the field (unknown), false is a box
// that said no. The console needs both positively true before it makes
// an AUTOMATIC choice of this box's region; explicit requests ignore
// them. Computed from the same ReconcileConfig the reconcile pass uses
// (edgesFileEligibility), never a second reading of the environment.
ServesSharedPool bool `json:"serves_shared_pool"`
AcceptsInbound bool `json:"accepts_inbound"`Below edgesFileRegion:
// edgesFileEligibility derives the two published eligibility answers from
// the reconcile config this box actually runs with — provisionTickConfig,
// the same parse the provision tick uses — so a spelling the reconciler
// reads one way can never be published another.
func edgesFileEligibility(getenv func(string) string) (sharedPool, inbound bool) {
return provisionTickConfig(getenv).SharedInstanceEligibility()
}In edgesFileLoop, build the ident with the two new fields:
sharedPool, inbound := edgesFileEligibility(os.Getenv)
ident := edgesFile{
Box: box,
Region: region,
Placement: strings.TrimSpace(os.Getenv("CARLOS_REGION")),
DataBucket: strings.TrimSpace(os.Getenv("CARLOS_HIBERNATE_BUCKET")),
DataBucketRegion: strings.TrimSpace(os.Getenv("CARLOS_HIBERNATE_REGION")),
// Same env, same trim, one line apart, deliberately: this boolean
// must never disagree with the bucket beside it, and the way to
// guarantee that is to derive both from the one read rather than to
// ask two questions that could drift.
HibernationConfigured: strings.TrimSpace(os.Getenv("CARLOS_HIBERNATE_BUCKET")) != "",
ServesSharedPool: sharedPool,
AcceptsInbound: inbound,
}- [ ] Step 8: Run the host package to verify it passes
First update the four raw-wire goldens in internal/host/edgesfile_test.go, because the schema really did grow and both keys are always published (:315, :357, :376 and :929, in TestEdgesFileRawWireBodyMatchesSchema, TestEdgesFileRawWireBodyRegionField and TestEdgesFileRawWireBodyPlacementField):
sed -i 's/"hibernation_configured":false,"generation":"` +/"hibernation_configured":false,"serves_shared_pool":false,"accepts_inbound":false,"generation":"` +/' internal/host/edgesfile_test.go
test "$(grep -c '"serves_shared_pool":false,"accepts_inbound":false' internal/host/edgesfile_test.go)" = 4Run: socktest ./internal/host -count=1 && go test ./internal/provision -count=1 Expected: PASS. Without the golden update, those three tests fail on exactly the two new keys (seen in the round-3 proof).
- [ ] Step 9: Cross-compile, then commit
Run:
fail=0
for t in darwin/arm64 darwin/amd64 linux/arm64 windows/amd64 windows/arm64; do
CGO_ENABLED=0 GOOS=${t%/*} GOARCH=${t#*/} go build ./... || { echo "FAILED $t"; fail=1; }
done
[ "$fail" = 0 ] || exit 1 # stops this block before any commit below itExpected: no FAILED line. internal/host is linked into the CLI.
git add internal/provision/reconcile.go internal/provision/reconcile_eligibility_test.go internal/host/edgesfile.go internal/host/edgesfile_test.go
git commit -m "edges file: publish serves_shared_pool and accepts_inbound (nearest-region §6.1)
Computed from the reconcile config the box mints with, so an automatic
region choice can never land on a box that refuses ordinary instances.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"---
Task 1b: Boxes publish their secrets-key fingerprint (R9)
Files:
- Create:
internal/appconfig/fingerprint.go,internal/appconfig/fingerprint_test.go - Modify:
internal/host/edgesfile.go(the struct afterAcceptsInbound,edgesFileLoop's ident) - Test:
internal/host/edgesfile_test.go
Interfaces:
- Consumes:
appconfig.ParsePrivateKey,appconfig.ParsePublicKey(internal/appconfig/crypto.go:59,:73), and Task 1'sedgesFile. - Produces: -
func KeyFingerprint(pub *ecdh.PublicKey) string, which returns"sha256:" + lowercase hex(sha256(pub.Bytes())); -func PublicFingerprint(b64 string) (string, error), the fingerprint of a base64CARLOS_SECRETS_PUBKEY; -edgesFile.SecretsKeyFP string(json:"secrets_key_fp,omitempty"); -func edgesFileSecretsFP(getenv func(string) string) string.
The field name and its omitempty shape are the move-any-app spec's (design-moves-any-region at e41c4fef, §6). The format is fixed by this spec's §6.1 for both, and KeyFingerprint is its only renderer.
- [ ] Step 1: Write the failing
appconfigtest
// internal/appconfig/fingerprint_test.go
package appconfig
import (
"bytes"
"crypto/ecdh"
"encoding/base64"
"regexp"
"testing"
)
// A box computes the fingerprint from its PRIVATE key's public half; the
// console computes it from CARLOS_SECRETS_PUBKEY. They must agree byte for
// byte (nearest-region spec §6.1, R9).
func TestKeyFingerprintAgreesAcrossHalves(t *testing.T) {
priv, err := ecdh.P256().NewPrivateKey(bytes.Repeat([]byte{1}, 32))
if err != nil {
t.Fatal(err)
}
fromPriv := KeyFingerprint(priv.PublicKey())
fromPub, err := PublicFingerprint(base64.StdEncoding.EncodeToString(priv.PublicKey().Bytes()))
if err != nil {
t.Fatal(err)
}
if fromPriv != fromPub {
t.Fatalf("private half %q != public half %q", fromPriv, fromPub)
}
if !regexp.MustCompile(`^sha256:[0-9a-f]{64}$`).MatchString(fromPriv) {
t.Fatalf("format = %q", fromPriv)
}
other, _ := ecdh.P256().NewPrivateKey(bytes.Repeat([]byte{2}, 32))
if KeyFingerprint(other.PublicKey()) == fromPriv {
t.Fatal("two keys share a fingerprint")
}
if _, err := PublicFingerprint("not base64!"); err == nil {
t.Fatal("garbage parsed")
}
}- [ ] Step 2: Run it to verify it fails
Run: go test ./internal/appconfig -run TestKeyFingerprint -count=1 Expected: FAIL, undefined KeyFingerprint.
- [ ] Step 3: Implement
fingerprint.go
package appconfig
import (
"crypto/ecdh"
"crypto/sha256"
"encoding/hex"
)
// KeyFingerprint names a secrets keypair without revealing either half:
// "sha256:" and the lowercase hex SHA-256 of the public key's uncompressed
// SEC1 bytes — the same bytes CARLOS_SECRETS_PUBKEY base64-decodes to. It
// is the ONE renderer of the edges file's secrets_key_fp, shared by the
// nearest-region spec (§6.1, R9) and the move-any-app spec (§6, T2): a box
// that can open this deployment's sealed config publishes exactly the
// string the console derives from its own public key.
func KeyFingerprint(pub *ecdh.PublicKey) string {
sum := sha256.Sum256(pub.Bytes())
return "sha256:" + hex.EncodeToString(sum[:])
}
// PublicFingerprint is KeyFingerprint of a base64 CARLOS_SECRETS_PUBKEY.
func PublicFingerprint(b64 string) (string, error) {
pub, err := ParsePublicKey(b64, "CARLOS_SECRETS_PUBKEY")
if err != nil {
return "", err
}
return KeyFingerprint(pub), nil
}- [ ] Step 4: Run it to verify it passes
Run: go test ./internal/appconfig -run TestKeyFingerprint -count=1 Expected: PASS.
- [ ] Step 5: Write the failing edges-file tests
Append to internal/host/edgesfile_test.go:
// R9: the box publishes the fingerprint of its secrets key's public half,
// never the key; no key, or a key that does not parse, publishes nothing.
func TestEdgesFileSecretsKeyFingerprint(t *testing.T) {
priv, err := ecdh.P256().NewPrivateKey(bytes.Repeat([]byte{1}, 32))
if err != nil {
t.Fatal(err)
}
privB64 := base64.StdEncoding.EncodeToString(priv.Bytes())
pubB64 := base64.StdEncoding.EncodeToString(priv.PublicKey().Bytes())
env := func(v string) func(string) string {
return func(k string) string {
if k == "CARLOS_SECRETS_KEY" {
return v
}
return ""
}
}
want := appconfig.KeyFingerprint(priv.PublicKey())
if got := edgesFileSecretsFP(env(privB64)); got != want {
t.Fatalf("with a key = %q, want %q", got, want)
}
for _, v := range []string{"", "not base64!", base64.StdEncoding.EncodeToString([]byte("short"))} {
if got := edgesFileSecretsFP(env(v)); got != "" {
t.Errorf("CARLOS_SECRETS_KEY=%q published %q, want nothing", v, got)
}
}
for _, fp := range []string{want, ""} {
s, reg := release.NewDirStore(t.TempDir()), edgesTestRegistry(t)
seedRoute(t, reg, "app.example.com")
var last []byte
ident := edgesFile{Box: "quiet-harbour", Region: "eu-west-2", Placement: "uk", SecretsKeyFP: fp}
if err := edgesFilePass(context.Background(), s, reg, ident, "203.0.113.10", &last); err != nil {
t.Fatal(err)
}
body, _, err := s.Get(context.Background(), "control/edges/quiet-harbour.json")
if err != nil {
t.Fatal(err)
}
has := strings.Contains(string(body), `"secrets_key_fp"`)
if has != (fp != "") {
t.Fatalf("fp %q: key present = %v in %s", fp, has, body)
}
for _, secret := range []string{privB64, pubB64} {
if strings.Contains(string(body), secret) {
t.Fatal("the edges file carries key material")
}
}
}
}Add bytes, crypto/ecdh, encoding/base64 and github.com/carlosframework/platform/internal/appconfig to the test file's imports.
- [ ] Step 6: Run it to verify it fails
Run: socktest ./internal/host -run TestEdgesFileSecretsKeyFingerprint -count=1 Expected: FAIL, unknown field SecretsKeyFP.
- [ ] Step 7: Implement the field, the helper and the loop wiring
In edgesFile, after AcceptsInbound:
// SecretsKeyFP is the fingerprint of this box's CARLOS_SECRETS_KEY's
// public half (appconfig.KeyFingerprint), NEVER the key. Additive and
// omitempty, the move-any-app spec's shape: absent means this box holds
// no key, or one that does not parse — so it renders no exec config,
// and an instance placed here would run with none (nearest-region spec
// R9). The console requires it to equal the fingerprint of its own
// CARLOS_SECRETS_PUBKEY before it places anything here automatically.
SecretsKeyFP string `json:"secrets_key_fp,omitempty"`Below edgesFileEligibility:
// edgesFileSecretsFP fingerprints the box's secrets key, read the same way
// the config loops read it (agent.go configSyncLoop). The key itself never
// leaves this function.
func edgesFileSecretsFP(getenv func(string) string) string {
raw := strings.TrimSpace(getenv("CARLOS_SECRETS_KEY"))
if raw == "" {
return ""
}
priv, err := appconfig.ParsePrivateKey(raw, "CARLOS_SECRETS_KEY")
if err != nil {
log.Printf("agent: CARLOS_SECRETS_KEY does not parse, so no secrets_key_fp is published: %v", err)
return ""
}
return appconfig.KeyFingerprint(priv.PublicKey())
}The log text is O10 from the copy record. ParsePrivateKey's errors name the variable and the parse failure, never the value.
Add "github.com/carlosframework/platform/internal/appconfig" to edgesfile.go's imports.
In edgesFileLoop's ident literal, add:
SecretsKeyFP: edgesFileSecretsFP(os.Getenv),- [ ] Step 8: Run the tests to verify they pass, then cross-compile
Run:
socktest ./internal/host -count=1 && go test ./internal/appconfig -count=1
fail=0
for t in darwin/arm64 darwin/amd64 linux/arm64 windows/amd64 windows/arm64; do
CGO_ENABLED=0 GOOS=${t%/*} GOARCH=${t#*/} go build ./... || { echo "FAILED $t"; fail=1; }
done
[ "$fail" = 0 ] || exit 1 # stops this block before any commit below itExpected: PASS, and no FAILED line.
- [ ] Step 9: Commit
git add internal/appconfig/fingerprint.go internal/appconfig/fingerprint_test.go internal/host/edgesfile.go internal/host/edgesfile_test.go
git commit -m "edges file: publish secrets_key_fp, the fingerprint of the box's secrets key (nearest-region R9)
Same field and shape as the move-any-app spec; appconfig.KeyFingerprint
is the one renderer both use.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"---
Task 2: internal/geo points, distance and the nearest pick
Files:
- Create:
internal/geo/point.go,internal/geo/nearest.go - Test:
internal/geo/nearest_test.go
Interfaces:
- Consumes:
LookupAWSRegionfrom Task 4. To keep this task testable on its own,Nearesttakes apointOf func(awsRegion string) (Point, bool)parameter, and Task 4 adds the exported wrapper. - Produces: -
type Point struct{ Lat, Lon float64 }; -func DistanceKm(a, b Point) float64; -type Candidate struct{ Code string; Home bool; AWSRegion string }; -func NearestWith(p Point, cands []Candidate, pointOf func(string) (Point, bool)) (code string, ok bool).
- [ ] Step 1: Write the failing tests
// internal/geo/nearest_test.go
package geo
import (
"math"
"testing"
)
var testPoints = map[string]Point{
"eu-west-1": {53.35, -6.26},
"eu-west-2": {51.51, -0.13},
"ap-southeast-2": {-33.87, 151.21},
}
func testPointOf(r string) (Point, bool) { p, ok := testPoints[r]; return p, ok }
func TestDistanceKmDublinLondon(t *testing.T) {
d := DistanceKm(testPoints["eu-west-1"], testPoints["eu-west-2"])
if math.Abs(d-464) > 5 {
t.Fatalf("Dublin to London = %.1f km, want about 464", d)
}
}
func TestNearest(t *testing.T) {
london := Point{51.5, -0.12}
sydney := Point{-33.9, 151.2}
home := Candidate{Code: "", Home: true, AWSRegion: "eu-west-1"}
uk := Candidate{Code: "uk", AWSRegion: "eu-west-2"}
au := Candidate{Code: "au", AWSRegion: "ap-southeast-2"}
cases := []struct {
name string
p Point
cands []Candidate
want string
wantOK bool
}{
{"single", london, []Candidate{home}, "", true},
{"clear winner", london, []Candidate{home, uk, au}, "uk", true},
{"far side", sydney, []Candidate{home, uk, au}, "au", true},
{"home with no point gives no answer", london,
[]Candidate{{Code: "", Home: true, AWSRegion: ""}, uk}, "", false},
{"non-home with no point is left out", london,
[]Candidate{home, {Code: "zz", AWSRegion: "xx-nowhere-1"}}, "", true},
{"empty", london, nil, "", false},
{"tie goes to the home", testPoints["eu-west-2"],
[]Candidate{{Code: "uk2", AWSRegion: "eu-west-2"}, {Code: "", Home: true, AWSRegion: "eu-west-2"}}, "", true},
{"non-home tie goes to the smaller code", testPoints["eu-west-2"],
[]Candidate{{Code: "uk2", AWSRegion: "eu-west-2"}, {Code: "uk", AWSRegion: "eu-west-2"}}, "uk", true},
}
for _, c := range cases {
got, ok := NearestWith(c.p, c.cands, testPointOf)
if got != c.want || ok != c.wantOK {
t.Errorf("%s: got (%q, %v), want (%q, %v)", c.name, got, ok, c.want, c.wantOK)
}
}
}- [ ] Step 2: Run them to verify they fail
Run: go test ./internal/geo -count=1 Expected: FAIL, because the package does not exist.
- [ ] Step 3: Implement
point.go
// Package geo turns a timezone or an IP into a point on the globe and picks
// the nearest of a set of AWS regions (nearest-region spec §6). It holds
// points only for the length of one call: nothing here stores, logs or
// returns a caller's coordinates.
package geo
import "math"
// Point is a latitude and longitude in degrees.
type Point struct{ Lat, Lon float64 }
// earthRadiusKm is the IUGG mean Earth radius.
const earthRadiusKm = 6371.0088
// DistanceKm is the great-circle (haversine) distance between a and b.
func DistanceKm(a, b Point) float64 {
rad := func(d float64) float64 { return d * math.Pi / 180 }
dLat := rad(b.Lat - a.Lat)
dLon := rad(b.Lon - a.Lon)
h := math.Sin(dLat/2)*math.Sin(dLat/2) +
math.Cos(rad(a.Lat))*math.Cos(rad(b.Lat))*math.Sin(dLon/2)*math.Sin(dLon/2)
return 2 * earthRadiusKm * math.Asin(math.Sqrt(h))
}- [ ] Step 4: Implement
nearest.go
package geo
import "math"
// Candidate is one region a guess may pick. Code is in the record
// vocabulary ("" is the deployment's home); the caller has already
// filtered to eligible, policy-allowed candidates.
type Candidate struct {
Code string
Home bool
AWSRegion string
}
// NearestWith returns the candidate nearest p, measured to each
// candidate's AWS region through pointOf.
//
// - A candidate whose region has no point is left out.
// - If the HOME is among cands and has no point, ok is false: picking
// among the rest could choose a far region over an unmeasured near
// home, and the home is where today's behaviour lives (spec §6.3).
// - Distances are compared in whole kilometres. A tie goes to the home,
// then to the lexically smallest code, so every console answers alike.
func NearestWith(p Point, cands []Candidate, pointOf func(string) (Point, bool)) (code string, ok bool) {
best := -1
bestKm := math.MaxInt
for i, c := range cands {
cp, has := pointOf(c.AWSRegion)
if !has {
if c.Home {
return "", false
}
continue
}
km := int(math.Round(DistanceKm(p, cp)))
switch {
case best < 0, km < bestKm:
case km == bestKm && better(c, cands[best]):
default:
continue
}
best, bestKm = i, km
}
if best < 0 {
return "", false
}
return cands[best].Code, true
}
// better breaks an exact tie: the home first, then the smaller code.
func better(a, b Candidate) bool {
if a.Home != b.Home {
return a.Home
}
return a.Code < b.Code
}- [ ] Step 5: Run the tests to verify they pass
Run: go test ./internal/geo -count=1 Expected: PASS.
- [ ] Step 6: Commit
git add internal/geo/point.go internal/geo/nearest.go internal/geo/nearest_test.go
git commit -m "geo: haversine and the nearest-region pick with deterministic ties (nearest-region §6.3)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"---
Task 3: internal/geo timezones and country points
Files:
- Create:
internal/geo/data/zone.tab,internal/geo/data/links.txt,internal/geo/data/VERSION - Create:
internal/geo/tz.go,scripts/update-tzgeo.sh - Test:
internal/geo/tz_test.go
Interfaces:
- Consumes:
Pointfrom Task 2. - Produces: -
func ZonePoint(name string) (Point, bool); -func CountryPoint(iso2 string) (Point, bool); -func TZDataVersion() string.
- [ ] Step 1: Write the refresh script
#!/usr/bin/env bash
# scripts/update-tzgeo.sh <version>, e.g. 2026b
#
# Refreshes internal/geo/data from one IANA tzdata release
# (nearest-region spec §6.2). zone.tab carries the coordinates; the Link
# lines resolve the old names browsers still report (Asia/Calcutta). Run it
# as a release-checklist step beside the Go toolchain bump; a stale table
# only misses zone names newer than it, and those fall through to IP.
set -euo pipefail
v="${1:?usage: update-tzgeo.sh <tzdata version, e.g. 2026b>}"
here="$(cd "$(dirname "$0")/.." && pwd)"
out="$here/internal/geo/data"
work="$(mktemp -d "${TMPDIR:-/tmp}/tzgeo.XXXXXX")"
trap 'rm -rf "$work"' EXIT
base="https://data.iana.org/time-zones/releases"
curl -fsSL -o "$work/tzdata.tar.gz" "$base/tzdata$v.tar.gz"
curl -fsSL -o "$work/tzdata.tar.gz.asc" "$base/tzdata$v.tar.gz.asc"
if command -v gpg >/dev/null; then
gpg --verify "$work/tzdata.tar.gz.asc" "$work/tzdata.tar.gz"
else
echo "gpg not found: signature NOT checked" >&2
exit 1
fi
tar -xzf "$work/tzdata.tar.gz" -C "$work"
mkdir -p "$out"
cp "$work/zone.tab" "$out/zone.tab"
awk '$1 == "Link" { print $2, $3 }' \
"$work"/africa "$work"/antarctica "$work"/asia "$work"/australasia \
"$work"/backward "$work"/etcetera "$work"/europe "$work"/northamerica \
"$work"/southamerica | sort -u > "$out/links.txt"
printf '%s\n' "$v" > "$out/VERSION"
echo "internal/geo/data refreshed to tzdata $v"chmod +x scripts/update-tzgeo.sh. Its three output lines are S1 to S3 from the copy record.
- [ ] Step 2: Generate the data
Run: scripts/update-tzgeo.sh 2026b. This needs network access to data.iana.org and gpg with the IANA key; outside the sandbox, use dangerouslyDisableSandbox for this one command.
Expected: three files in internal/geo/data/. links.txt has lines like Asia/Kolkata Asia/Calcutta, and VERSION contains 2026b.
- [ ] Step 3: Write the failing tests
// internal/geo/tz_test.go
package geo
import (
"strings"
"testing"
)
func TestZonePoint(t *testing.T) {
mapped := []string{
"Europe/London", "Europe/Dublin", "Europe/Amsterdam",
"Asia/Kolkata", "Asia/Calcutta",
"America/Argentina/Buenos_Aires", "europe/london",
}
for _, z := range mapped {
if _, ok := ZonePoint(z); !ok {
t.Errorf("ZonePoint(%q) unmapped, want a point", z)
}
}
a, _ := ZonePoint("Asia/Calcutta")
b, _ := ZonePoint("Asia/Kolkata")
if a != b {
t.Errorf("Asia/Calcutta %v != Asia/Kolkata %v; the link must resolve to one point", a, b)
}
unmapped := []string{
"", "UTC", "Etc/UTC", "Etc/GMT+5", "GMT", "UCT", "Universal", "Zulu",
"Factory", "Local", "localtime", "posixrules", "../etc/passwd",
"Europe/London/Extra/Deep", strings.Repeat("A", 65), "Nowhere/Atlantis",
}
for _, z := range unmapped {
if p, ok := ZonePoint(z); ok {
t.Errorf("ZonePoint(%q) = %v, want unmappable", z, p)
}
}
l, _ := ZonePoint("Europe/London")
if l.Lat < 51 || l.Lat > 52 || l.Lon > 0.5 || l.Lon < -0.5 {
t.Errorf("Europe/London = %v, want about 51.5, -0.12", l)
}
}
// A link cycle must terminate (at most 4 hops) rather than hang.
func TestZonePointLinkLoopTerminates(t *testing.T) {
links := map[string]string{"a/x": "a/y", "a/y": "a/x"}
if _, ok := resolveLinks("a/x", links, map[string]Point{}); ok {
t.Fatal("a link loop resolved to a point")
}
}
// Every country with more than one zone.tab row needs an override naming
// its principal zone: a tzdata refresh that gives a country a second row
// fails here instead of silently averaging a continent (spec §6.2).
func TestCountryOverridesCoverEveryMultiZoneCountry(t *testing.T) {
for cc, n := range zoneRowsPerCountry() {
if n < 2 {
continue
}
zone, ok := countryOverride[cc]
if !ok {
t.Errorf("country %s has %d zone.tab rows and no override", cc, n)
continue
}
if _, ok := ZonePoint(zone); !ok {
t.Errorf("override for %s names %q, which is not in zone.tab", cc, zone)
}
}
for cc := range countryOverride {
if zoneRowsPerCountry()[cc] < 2 {
t.Errorf("override for %s is unused: it has fewer than two rows", cc)
}
}
}
func TestCountryPoint(t *testing.T) {
for _, cc := range []string{"GB", "IE", "US", "AU", "IN", "gb"} {
if _, ok := CountryPoint(cc); !ok {
t.Errorf("CountryPoint(%q) unmapped", cc)
}
}
us, _ := CountryPoint("US")
ny, _ := ZonePoint("America/New_York")
if us != ny {
t.Errorf("US = %v, want its principal zone New York %v", us, ny)
}
if _, ok := CountryPoint("ZZ"); ok {
t.Error("CountryPoint(ZZ) mapped")
}
}
func TestTZDataVersion(t *testing.T) {
if v := TZDataVersion(); v == "" || strings.ContainsAny(v, " \n") {
t.Fatalf("TZDataVersion = %q", v)
}
}- [ ] Step 4: Run them to verify they fail
Run: go test ./internal/geo -run 'TestZonePoint|TestCountry|TestTZDataVersion' -count=1 Expected: FAIL, undefined ZonePoint.
- [ ] Step 5: Implement
tz.go
package geo
import (
_ "embed"
"regexp"
"strconv"
"strings"
"sync"
)
//go:embed data/zone.tab
var zoneTab string
//go:embed data/links.txt
var linksTxt string
//go:embed data/VERSION
var tzVersion string
// TZDataVersion is the IANA release the embedded tables came from.
func TZDataVersion() string { return strings.TrimSpace(tzVersion) }
var (
tzOnce sync.Once
zonePoints map[string]Point // lowercased zone name -> point
zoneLinks map[string]string
countryOne map[string]Point // countries with exactly one row
rowsPerCC map[string]int
)
// zoneShape is spec §6.2 step 1: at most 64 bytes, one to three
// "/"-separated segments of [A-Za-z0-9_+-].
var zoneShape = regexp.MustCompile(`^[A-Za-z0-9_+\-]+(/[A-Za-z0-9_+\-]+){0,2}$`)
// notAPlace names an offset or a machine, not a place (R3).
var notAPlace = map[string]bool{
"": true, "local": true, "localtime": true, "posixrules": true,
"utc": true, "uct": true, "gmt": true, "universal": true, "zulu": true,
"factory": true,
}
func loadTZ() {
zonePoints = map[string]Point{}
countryOne = map[string]Point{}
rowsPerCC = map[string]int{}
for _, line := range strings.Split(zoneTab, "\n") {
if line == "" || strings.HasPrefix(line, "#") {
continue
}
f := strings.Split(line, "\t")
if len(f) < 3 {
continue
}
p, ok := parseISO6709(f[1])
if !ok {
continue
}
zonePoints[strings.ToLower(f[2])] = p
cc := strings.ToUpper(f[0])
rowsPerCC[cc]++
countryOne[cc] = p
}
zoneLinks = map[string]string{}
for _, line := range strings.Split(linksTxt, "\n") {
f := strings.Fields(line)
if len(f) == 2 {
zoneLinks[strings.ToLower(f[1])] = strings.ToLower(f[0])
}
}
}
// ZonePoint maps an IANA zone name to its principal location, following
// tzdata links (at most 4 hops). It refuses anything that is not a place.
func ZonePoint(name string) (Point, bool) {
tzOnce.Do(loadTZ)
name = strings.TrimSpace(name)
if len(name) > 64 || !zoneShape.MatchString(name) {
return Point{}, false
}
low := strings.ToLower(name)
if notAPlace[low] || strings.HasPrefix(low, "etc/") {
return Point{}, false
}
return resolveLinks(low, zoneLinks, zonePoints)
}
func resolveLinks(name string, links map[string]string, points map[string]Point) (Point, bool) {
for hop := 0; hop <= 4; hop++ {
if p, ok := points[name]; ok {
return p, true
}
next, ok := links[name]
if !ok {
return Point{}, false
}
name = next
}
return Point{}, false
}
// countryOverride names the principal zone of every country with more
// than one zone.tab row (tzdata 2026b). TestCountryOverridesCoverEvery
// MultiZoneCountry fails when a refresh adds a multi-row country.
var countryOverride = map[string]string{
"AQ": "Antarctica/McMurdo", "AR": "America/Argentina/Buenos_Aires",
"AU": "Australia/Sydney", "BR": "America/Sao_Paulo", "CA": "America/Toronto",
"CD": "Africa/Kinshasa", "CL": "America/Santiago", "CN": "Asia/Shanghai",
"CY": "Asia/Nicosia", "DE": "Europe/Berlin", "EC": "America/Guayaquil",
"ES": "Europe/Madrid", "FM": "Pacific/Pohnpei", "GL": "America/Nuuk",
"ID": "Asia/Jakarta", "KI": "Pacific/Tarawa", "KZ": "Asia/Almaty",
"MH": "Pacific/Majuro", "MN": "Asia/Ulaanbaatar", "MX": "America/Mexico_City",
"MY": "Asia/Kuala_Lumpur", "NZ": "Pacific/Auckland", "PF": "Pacific/Tahiti",
"PG": "Pacific/Port_Moresby", "PS": "Asia/Gaza", "PT": "Europe/Lisbon",
"RU": "Europe/Moscow", "UA": "Europe/Kyiv", "UM": "Pacific/Wake",
"US": "America/New_York", "UZ": "Asia/Tashkent",
}
// CountryPoint maps an ISO 3166-1 alpha-2 code to a point: its only
// zone.tab row, or its override's zone.
func CountryPoint(iso2 string) (Point, bool) {
tzOnce.Do(loadTZ)
cc := strings.ToUpper(strings.TrimSpace(iso2))
if zone, ok := countryOverride[cc]; ok {
return ZonePoint(zone)
}
if rowsPerCC[cc] == 1 {
return countryOne[cc], true
}
return Point{}, false
}
func zoneRowsPerCountry() map[string]int { tzOnce.Do(loadTZ); return rowsPerCC }
// parseISO6709 reads zone.tab's ±DDMM±DDDMM or ±DDMMSS±DDDMMSS.
func parseISO6709(s string) (Point, bool) {
i := strings.IndexAny(s[1:], "+-") + 1
if i <= 0 {
return Point{}, false
}
lat, ok1 := dms(s[:i], 2)
lon, ok2 := dms(s[i:], 3)
return Point{lat, lon}, ok1 && ok2
}
func dms(s string, degDigits int) (float64, bool) {
sign := 1.0
if s[0] == '-' {
sign = -1
}
d := s[1:]
if len(d) != degDigits+2 && len(d) != degDigits+4 {
return 0, false
}
deg, err1 := strconv.Atoi(d[:degDigits])
min, err2 := strconv.Atoi(d[degDigits : degDigits+2])
sec := 0
var err3 error
if len(d) == degDigits+4 {
sec, err3 = strconv.Atoi(d[degDigits+2:])
}
if err1 != nil || err2 != nil || err3 != nil {
return 0, false
}
return sign * (float64(deg) + float64(min)/60 + float64(sec)/3600), true
}- [ ] Step 6: Run the tests to verify they pass
Run: go test ./internal/geo -count=1 Expected: PASS. If TestCountryOverridesCoverEveryMultiZoneCountry names a country, the downloaded tzdata differs from 2026b. Add or remove that override using the principal zone, which is the country's capital or largest city.
- [ ] Step 7: Commit
git add internal/geo/tz.go internal/geo/tz_test.go internal/geo/data scripts/update-tzgeo.sh
git commit -m "geo: embedded IANA zone.tab and links, zone and country points (nearest-region §6.2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"---
Task 4: AWS region table and place names (C23, from the approved record)
Files:
- Create:
internal/geo/awsregions.go - Test:
internal/geo/awsregions_test.go
Interfaces:
- Consumes: -
PointandNearestWith(Task 2); - the record'sC23-*entries (Task 0). - Produces: -
type AWSRegion struct{ Point; Place string }; -func LookupAWSRegion(code string) (AWSRegion, bool); -func Nearest(p Point, cands []Candidate) (string, bool); -func PlaceName(awsRegion, fallback string) string.
Tests elsewhere name a place through geo.PlaceName("eu-west-2", "uk"), never a literal, so the approved text can differ from the draft without breaking them.
- [ ] Step 1: Write the failing tests
// internal/geo/awsregions_test.go
package geo
import (
"regexp"
"strings"
"testing"
)
func TestAWSRegionTable(t *testing.T) {
shape := regexp.MustCompile(`^[a-z]{2}(-[a-z]+)+-\d$`)
for code, r := range awsRegions {
if !shape.MatchString(code) {
t.Errorf("%s is not an AWS region code", code)
}
if r.Lat < -90 || r.Lat > 90 || r.Lon < -180 || r.Lon > 180 {
t.Errorf("%s: %v out of range", code, r.Point)
}
if r.Place == "" || strings.Contains(r.Place, "—") {
t.Errorf("%s: place %q empty or contains an em dash", code, r.Place)
}
}
if len(awsRegions) != 34 {
t.Errorf("%d regions, want the 34 of spec C23", len(awsRegions))
}
if PlaceName("xx-nowhere-1", "zz") != "zz" || PlaceName("", "ie") != "ie" {
t.Error("PlaceName must fall back to the given code")
}
if r, ok := LookupAWSRegion("eu-west-2"); !ok || PlaceName("eu-west-2", "uk") != r.Place {
t.Error("PlaceName must read the table")
}
}
func TestNearestUsesTheTable(t *testing.T) {
london, _ := ZonePoint("Europe/London")
got, ok := Nearest(london, []Candidate{
{Code: "", Home: true, AWSRegion: "eu-west-1"},
{Code: "uk", AWSRegion: "eu-west-2"},
})
if !ok || got != "uk" {
t.Fatalf("Nearest from London = (%q, %v), want uk", got, ok)
}
}- [ ] Step 2: Run them to verify they fail
Run: go test ./internal/geo -run 'TestAWSRegionTable|TestNearestUsesTheTable' -count=1 Expected: FAIL, undefined awsRegions.
- [ ] Step 3: Implement the table
The Place strings below are the drafts Task 0 submitted. Write the record's C23-<code> text for each in their place.
package geo
// AWSRegion is one AWS commercial region's metro-area point and the place
// name every surface shows for it (spec §14, copy C23 as approved in the
// nearest-region copy record).
//
// Source: AWS's official region list
// (https://docs.aws.amazon.com/global-infrastructure/latest/regions/aws-regions.html)
// as of 2026-10-01. Coordinates are each region's metro area, checked by
// hand; AWS publishes no facility locations, and tens of kilometres never
// change a choice between regions.
type AWSRegion struct {
Point
Place string
}
var awsRegions = map[string]AWSRegion{
"us-east-1": {Point{39.04, -77.49}, "US East · N. Virginia"},
"us-east-2": {Point{39.96, -83.00}, "US East · Ohio"},
"us-west-1": {Point{37.77, -122.42}, "US West · N. California"},
"us-west-2": {Point{45.84, -119.70}, "US West · Oregon"},
"ca-central-1": {Point{45.50, -73.57}, "Canada · Montréal"},
"ca-west-1": {Point{51.05, -114.07}, "Canada · Calgary"},
"mx-central-1": {Point{20.59, -100.39}, "Mexico · Querétaro"},
"sa-east-1": {Point{-23.55, -46.63}, "Brazil · São Paulo"},
"eu-west-1": {Point{53.35, -6.26}, "Ireland · Dublin"},
"eu-west-2": {Point{51.51, -0.13}, "UK · London"},
"eu-west-3": {Point{48.86, 2.35}, "France · Paris"},
"eu-central-1": {Point{50.11, 8.68}, "Germany · Frankfurt"},
"eu-central-2": {Point{47.37, 8.54}, "Switzerland · Zurich"},
"eu-south-1": {Point{45.46, 9.19}, "Italy · Milan"},
"eu-south-2": {Point{41.65, -0.89}, "Spain · Aragón"},
"eu-north-1": {Point{59.33, 18.07}, "Sweden · Stockholm"},
"il-central-1": {Point{32.09, 34.78}, "Israel · Tel Aviv"},
"me-south-1": {Point{26.23, 50.59}, "Bahrain"},
"me-central-1": {Point{25.20, 55.27}, "UAE"},
"af-south-1": {Point{-33.92, 18.42}, "South Africa · Cape Town"},
"ap-south-1": {Point{19.08, 72.88}, "India · Mumbai"},
"ap-south-2": {Point{17.39, 78.49}, "India · Hyderabad"},
"ap-east-1": {Point{22.32, 114.17}, "Hong Kong"},
"ap-east-2": {Point{25.03, 121.57}, "Taiwan · Taipei"},
"ap-southeast-1": {Point{1.35, 103.82}, "Singapore"},
"ap-southeast-2": {Point{-33.87, 151.21}, "Australia · Sydney"},
"ap-southeast-3": {Point{-6.21, 106.85}, "Indonesia · Jakarta"},
"ap-southeast-4": {Point{-37.81, 144.96}, "Australia · Melbourne"},
"ap-southeast-5": {Point{3.14, 101.69}, "Malaysia"},
"ap-southeast-6": {Point{-36.85, 174.76}, "New Zealand · Auckland"},
"ap-southeast-7": {Point{13.76, 100.50}, "Thailand · Bangkok"},
"ap-northeast-1": {Point{35.68, 139.69}, "Japan · Tokyo"},
"ap-northeast-2": {Point{37.57, 126.98}, "South Korea · Seoul"},
"ap-northeast-3": {Point{34.69, 135.50}, "Japan · Osaka"},
}
// LookupAWSRegion finds a region in the table.
func LookupAWSRegion(code string) (AWSRegion, bool) {
r, ok := awsRegions[code]
return r, ok
}
func tablePoint(code string) (Point, bool) {
r, ok := awsRegions[code]
return r.Point, ok
}
// Nearest is NearestWith over the shipped table.
func Nearest(p Point, cands []Candidate) (string, bool) {
return NearestWith(p, cands, tablePoint)
}
// PlaceName is the table's place for awsRegion, or fallback (the bare
// placement code) for a region the table does not know.
func PlaceName(awsRegion, fallback string) string {
if r, ok := awsRegions[awsRegion]; ok {
return r.Place
}
return fallback
}- [ ] Step 4: Run the tests to verify they pass
Run: go test ./internal/geo -count=1 Expected: PASS.
- [ ] Step 5: Check the coordinates by hand
For each row, confirm the coordinates fall within the named metro area, and that the 34 codes match AWS's current list. Correct any row that does not, then re-run step 4.
- [ ] Step 6: Commit
git add internal/geo/awsregions.go internal/geo/awsregions_test.go
git commit -m "geo: AWS region table with approved place names (nearest-region §14)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"---
Task 5: Optional GeoIP locator
Files:
- Create:
internal/geo/mmdb.go - Create, under
internal/geo/testdata/: -GeoIP2-City-Test.mmdb,GeoIP2-Country-Test.mmdb,MaxMind-DB-test-ipv4-24.mmdb; -GeoIP2-Country-Test.json; -LICENSE-MIT,LICENSE-APACHE; -README. - Modify:
go.mod,go.sum - Test:
internal/geo/mmdb_test.go
Interfaces:
- Consumes:
PointandCountryPoint(Task 3), and O5 from the copy record. - Produces: -
type Locator interface{ Locate(ip net.IP) (Point, bool) }; -func OpenMMDB(path string) (*MMDB, error); -func (m *MMDB) Locate(ip net.IP) (Point, bool); -func (m *MMDB) Close() error; -func Locatable(ip net.IP) bool.
- [ ] Step 1: Add the dependency and the pinned fixtures (network)
Fetch every fixture and licence file at the commit the approved F1 text names (Task 0 Step 0 resolved it before review), so the files and their recorded provenance cannot disagree (codex plan-R3 3). Run this outside the sandbox if needed:
go get github.com/oschwald/maxminddb-golang@v1.13.1
rec=docs/superpowers/plans/2026-10-02-nearest-region-copy.json
sha=$(jq -r '.entries[] | select(.id == "F1") | .text' "$rec" | grep -oE '[0-9a-f]{40}')
[ ${#sha} = 40 ] && [ -z "${sha//[0-9a-f]/}" ] || { echo "F1 must name exactly one commit"; exit 1; }
base="https://raw.githubusercontent.com/maxmind/MaxMind-DB/$sha"
d=internal/geo/testdata
mkdir -p "$d"
for f in test-data/GeoIP2-City-Test.mmdb test-data/GeoIP2-Country-Test.mmdb \
test-data/MaxMind-DB-test-ipv4-24.mmdb source-data/GeoIP2-Country-Test.json \
LICENSE-MIT LICENSE-APACHE; do
curl -fsSL -o "$d/$(basename "$f")" "$base/$f" || { echo "missing $f at $sha"; exit 1; }
done
echo "$sha"If a licence file has a different name at that commit, list the repository root at $sha on GitHub and fetch the MIT and Apache licence files under their real names. Write internal/geo/testdata/README as the record's F1 text, byte for byte, with nothing substituted:
jq -j '.entries[] | select(.id == "F1") | .text' "$rec" > internal/geo/testdata/README- [ ] Step 2: Write the failing tests
// internal/geo/mmdb_test.go
package geo
import (
"bytes"
"errors"
"log"
"net"
"os"
"path/filepath"
"testing"
)
func TestOpenMMDBRefusesNonDatabases(t *testing.T) {
dir := t.TempDir()
missing := filepath.Join(dir, "missing.mmdb")
junk := filepath.Join(dir, "junk.mmdb")
if err := os.WriteFile(junk, []byte("not a maxmind database"), 0o600); err != nil {
t.Fatal(err)
}
city, err := os.ReadFile("testdata/GeoIP2-City-Test.mmdb")
if err != nil {
t.Fatal(err)
}
truncated := filepath.Join(dir, "truncated.mmdb")
if err := os.WriteFile(truncated, city[:len(city)/3], 0o600); err != nil {
t.Fatal(err)
}
for _, p := range []string{missing, junk, truncated} {
if m, err := OpenMMDB(p); err == nil {
m.Close()
t.Errorf("OpenMMDB(%s) succeeded, want an error", filepath.Base(p))
}
}
}
func TestMMDBCityRecordGivesItsLocation(t *testing.T) {
m, err := OpenMMDB("testdata/GeoIP2-City-Test.mmdb")
if err != nil {
t.Fatal(err)
}
defer m.Close()
p, ok := m.Locate(net.ParseIP("81.2.69.142"))
if !ok || p.Lat < 51 || p.Lat > 52 {
t.Fatalf("81.2.69.142 = (%v, %v), want a London point", p, ok)
}
// Review Focus 2: an IPv4-mapped IPv6 address is the same client.
if mapped, ok := m.Locate(net.ParseIP("::ffff:81.2.69.142")); !ok || mapped != p {
t.Fatalf("::ffff:81.2.69.142 = (%v, %v), want %v", mapped, ok, p)
}
}
func TestMMDBCountryOnlyRecordGivesTheCountryPoint(t *testing.T) {
m, err := OpenMMDB("testdata/GeoIP2-Country-Test.mmdb")
if err != nil {
t.Fatal(err)
}
defer m.Close()
p, ok := m.Locate(net.ParseIP("81.2.69.142"))
gb, _ := CountryPoint("GB")
if !ok || p != gb {
t.Fatalf("country-only 81.2.69.142 = (%v, %v), want GB's point %v", p, ok, gb)
}
}
// Review Focus 2 and spec §6.5: a public IPv6 address looked up in an
// IPv4-only database is a miss, and the reader's error — which contains
// the address — never reaches the log.
func TestMMDBLookupErrorsNeverLogTheAddress(t *testing.T) {
m, err := OpenMMDB("testdata/MaxMind-DB-test-ipv4-24.mmdb")
if err != nil {
t.Fatal(err)
}
defer m.Close()
var buf bytes.Buffer
prev := log.Writer()
log.SetOutput(&buf)
defer log.SetOutput(prev)
lookupErrLast = lookupErrLast.AddDate(-1, 0, 0) // un-throttle
if _, ok := m.Locate(net.ParseIP("2001:4860::1")); ok {
t.Fatal("an IPv6 address located in an IPv4-only database")
}
if bytes.Contains(buf.Bytes(), []byte("2001:4860")) {
t.Fatalf("the address leaked into the log: %s", buf.String())
}
if buf.Len() == 0 {
t.Fatal("a lookup failure must be logged (as a fixed category)")
}
}
// Review Focus 2: non-public addresses never reach the reader — proved
// through Locate itself, with a counting lookup in place of the reader.
func TestLocateNeverLooksUpNonPublicAddresses(t *testing.T) {
calls := 0
m := &MMDB{lookup: func(net.IP, any) error { calls++; return errors.New("unused") }}
for _, s := range []string{
"127.0.0.1", "::1", "10.1.2.3", "192.168.0.1", "172.16.0.1",
"100.64.0.1", "169.254.1.1", "fe80::1", "fd00::1", "0.0.0.0", "::",
} {
if _, ok := m.Locate(net.ParseIP(s)); ok {
t.Errorf("Locate(%s) answered", s)
}
}
if calls != 0 {
t.Fatalf("the reader was asked %d time(s) about non-public addresses", calls)
}
m.Locate(net.ParseIP("81.2.69.142"))
if calls != 1 {
t.Fatalf("a public address must reach the reader, calls = %d", calls)
}
var nilM *MMDB
if _, ok := nilM.Locate(net.ParseIP("81.2.69.142")); ok {
t.Error("a nil *MMDB located an address")
}
}If 81.2.69.142 is absent from GeoIP2-Country-Test.mmdb, choose an address that testdata/GeoIP2-Country-Test.json maps to a country, and assert that country's CountryPoint.
- [ ] Step 3: Run them to verify they fail
Run: go test ./internal/geo -run 'MMDB|Locate' -count=1 Expected: FAIL, undefined OpenMMDB.
- [ ] Step 4: Implement
mmdb.go
package geo
import (
"log"
"net"
"sync"
"time"
"github.com/oschwald/maxminddb-golang"
)
// Locator turns a client address into a point. A nil Locator, or a nil
// *MMDB, locates nothing: the IP step is optional (spec §6.2).
type Locator interface {
Locate(ip net.IP) (Point, bool)
}
// MMDB is an operator-supplied MaxMind-format database
// (CONSOLE_GEOIP_DB). lookup is the reader's Lookup, a field so a test can
// prove which addresses ever reach it.
type MMDB struct {
r *maxminddb.Reader
lookup func(ip net.IP, result any) error
}
// OpenMMDB opens and verifies the file. An error means "IP step off"; the
// console logs it once and starts anyway.
func OpenMMDB(path string) (*MMDB, error) {
r, err := maxminddb.Open(path)
if err != nil {
return nil, err
}
if err := r.Verify(); err != nil {
r.Close()
return nil, err
}
return &MMDB{r: r, lookup: r.Lookup}, nil
}
// Close releases the mapping.
func (m *MMDB) Close() error {
if m == nil || m.r == nil {
return nil
}
return m.r.Close()
}
type mmdbRecord struct {
Location struct {
Latitude *float64 `maxminddb:"latitude"`
Longitude *float64 `maxminddb:"longitude"`
} `maxminddb:"location"`
Country struct {
ISOCode string `maxminddb:"iso_code"`
} `maxminddb:"country"`
RegisteredCountry struct {
ISOCode string `maxminddb:"iso_code"`
} `maxminddb:"registered_country"`
}
var (
lookupErrMu sync.Mutex
lookupErrLast time.Time
)
// Locate returns the record's location (City databases), else its
// country's point, else its registered country's. A lookup error is a
// miss, logged as a FIXED category at most once a minute: the reader's
// own error text can contain the address (spec §6.5).
func (m *MMDB) Locate(ip net.IP) (Point, bool) {
if m == nil || m.lookup == nil || !Locatable(ip) {
return Point{}, false
}
if v4 := ip.To4(); v4 != nil {
ip = v4
}
var rec mmdbRecord
if err := m.lookup(ip, &rec); err != nil {
lookupErrMu.Lock()
if time.Since(lookupErrLast) > time.Minute {
lookupErrLast = time.Now()
log.Print("geo: a GeoIP lookup failed and was treated as a miss")
}
lookupErrMu.Unlock()
return Point{}, false
}
if rec.Location.Latitude != nil && rec.Location.Longitude != nil {
return Point{*rec.Location.Latitude, *rec.Location.Longitude}, true
}
if p, ok := CountryPoint(rec.Country.ISOCode); ok {
return p, true
}
return CountryPoint(rec.RegisteredCountry.ISOCode)
}
var cgnat = &net.IPNet{IP: net.IPv4(100, 64, 0, 0), Mask: net.CIDRMask(10, 32)}
// Locatable is false for addresses that say nothing about where a person
// is: loopback, private, link-local, CGNAT and unspecified.
func Locatable(ip net.IP) bool {
if ip == nil || ip.IsLoopback() || ip.IsPrivate() || ip.IsUnspecified() ||
ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() || cgnat.Contains(ip) {
return false
}
return true
}The log text is O5 from the copy record.
- [ ] Step 5: Run the tests to verify they pass
Run: go test ./internal/geo -count=1 && go mod tidy && git diff --stat go.mod go.sum Expected: PASS. go.mod gains github.com/oschwald/maxminddb-golang v1.13.1 and its golang.org/x/sys requirement only.
- [ ] Step 6: Cross-compile, then commit
fail=0
for t in darwin/arm64 darwin/amd64 linux/arm64 windows/amd64 windows/arm64; do
CGO_ENABLED=0 GOOS=${t%/*} GOARCH=${t#*/} go build ./... || { echo "FAILED $t"; fail=1; }
done
[ "$fail" = 0 ] || exit 1 # stops this block before any commit below it
git add go.mod go.sum internal/geo/mmdb.go internal/geo/mmdb_test.go internal/geo/testdata
git commit -m "geo: optional GeoIP locator; lookup errors logged as a fixed category (nearest-region §6.2, §6.5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"---
Task 6: Console page copy (C1, C2, C3, C5, C7, C8, C21, C24, from the record)
Files:
- Create:
internal/console/regioncopy.go - Test:
internal/console/regioncopy_test.go
Interfaces:
- Consumes: the record's C1, C2, C3, C5, C7, C8, C21 and C24 (Task 0).
- Produces: -
var regionCopy regionCopyStrings, with fieldsPickerLabel,PickerHelp,AutoOption,Unavailable,StartsIn,CantStart,AuditCreatedandAttribution; -func (p page) RegionCopy() regionCopyStrings.
- [ ] Step 1: Write the failing test
// internal/console/regioncopy_test.go
package console
import (
"fmt"
"reflect"
"strings"
"testing"
)
func TestRegionCopyHasNoEmDashesAndRendersItsVerbs(t *testing.T) {
v := reflect.ValueOf(regionCopy)
for i := 0; i < v.NumField(); i++ {
s := v.Field(i).String()
if s == "" || strings.Contains(s, "—") {
t.Errorf("%s = %q: empty or has an em dash", v.Type().Field(i).Name, s)
}
}
for _, s := range []string{
fmt.Sprintf(regionCopy.AutoOption, "P"),
fmt.Sprintf(regionCopy.StartsIn, "P"),
fmt.Sprintf(regionCopy.CantStart, "P"),
fmt.Sprintf(regionCopy.AuditCreated, "blog", "P"),
} {
if strings.Contains(s, "%!") {
t.Errorf("bad verb: %q", s)
}
}
}- [ ] Step 2: Run it to verify it fails
Run: go test ./internal/console -run TestRegionCopy -count=1 Expected: FAIL, undefined regionCopy.
- [ ] Step 3: Write the strings from the record
The values below are the drafts Task 0 submitted. Write each field from its record entry verbatim.
package console
// regionCopy is every console-page string the nearest-region feature
// shows (spec §19), as approved in the nearest-region copy record. Verbs
// are %s in the order each comment names. Change none of them without a
// new copy review.
type regionCopyStrings struct {
PickerLabel string // C1
PickerHelp string // C2
AutoOption string // C3: place
Unavailable string // C5
StartsIn string // C7: place
CantStart string // C8: place
AuditCreated string // C21: app name, place
Attribution string // C24 (linked to https://db-ip.com)
}
var regionCopy = regionCopyStrings{
PickerLabel: "Region",
PickerHelp: "Where this app's instances run. We picked the region nearest to you. Choose another if most of your users are somewhere else.",
AutoOption: "%s (nearest to you)",
Unavailable: "That region can't take new apps right now. Choose another.",
StartsIn: "New instances start in %s, this app's region.",
CantStart: "New instances can't start in %s, this app's region, right now. Pass a region to place one elsewhere.",
AuditCreated: "created app %s in %s",
Attribution: "IP location by DB-IP.",
}
func (p page) RegionCopy() regionCopyStrings { return regionCopy }- [ ] Step 4: Run it to verify it passes
Run: go test ./internal/console -run TestRegionCopy -count=1 Expected: PASS.
- [ ] Step 5: Commit
git add internal/console/regioncopy.go internal/console/regioncopy_test.go
git commit -m "console: region page copy, as approved
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"---
Task 7: Console API messages (C16 to C20, C16b to C19b, C26 to C29, C31, reasons; from the record)
Files:
- Create:
internal/console/regionmsgs.go - Test:
internal/console/regionmsgs_test.go
Interfaces:
- Consumes: the record's C16 to C20, C16b to C19b, C26 to C29, C31, and the four
R-*entries (Task 0). - Produces:
func reasonText(reason string) string // absent | not-hosting | no-secrets-key | not-eligible
func msgAppRegionUnavailable(code, reason string, can []string) string // C16; C16b when can is empty
func msgAppRegionNotAllowed(code string, allowed []string) string // C17; C17b when empty
func msgRegionUnknown(code string, choices []string) string // C18; C18b when empty
func msgRegionNotArmed(code string, choices []string) string // C19; C19b when empty
const msgAppRegionUnreadable // C20
func msgReplicaOverlap(code string) string // C26
const msgPolicyUnreadable // C27
func msgAppRegionUnconfirmed(code string) string // C28
func msgPlacementDiscrepancy(host, app, code string) string // C29
func msgNoSecretsKey(code string) string // C31Each list a refusal carries names only what the door would accept (codex plan-R4 2), so it can be empty. An empty list never renders as a list: the b variant drops the list sentence.
- [ ] Step 1: Write the failing test
// internal/console/regionmsgs_test.go
package console
import (
"strings"
"testing"
)
func TestRegionMessagesRender(t *testing.T) {
all := []string{
msgAppRegionUnavailable("uk", "absent", []string{"ie", "au"}),
msgAppRegionNotAllowed("uk", []string{"ie"}),
msgRegionUnknown("zz", []string{"ie", "uk"}),
msgRegionNotArmed("uk", []string{"ie"}),
msgAppRegionUnreadable,
msgReplicaOverlap("uk"),
msgPolicyUnreadable,
msgAppRegionUnconfirmed("uk"),
msgPlacementDiscrepancy("a.go.tito.io", "blog", "uk"),
msgNoSecretsKey("uk"),
// codex plan-R4 2: the empty-list variants, C16b to C19b.
msgAppRegionUnavailable("uk", "absent", nil),
msgAppRegionNotAllowed("uk", nil),
msgRegionUnknown("zz", nil),
msgRegionNotArmed("uk", nil),
}
for _, s := range all {
if s == "" || strings.Contains(s, "—") || strings.Contains(s, "%!") {
t.Errorf("bad message %q", s)
}
}
if !strings.Contains(all[0], reasonText("absent")) || !strings.Contains(all[0], "ie, au") {
t.Errorf("C16 = %q, want the readable reason and the list", all[0])
}
for _, s := range all[len(all)-4:] {
if strings.Contains(s, ": .") || strings.Contains(s, ": ,") {
t.Errorf("an empty list rendered as a list: %q", s)
}
}
seen := map[string]bool{}
for _, r := range []string{"absent", "not-hosting", "no-secrets-key", "not-eligible"} {
txt := reasonText(r)
if txt == r || seen[txt] {
t.Errorf("reasonText(%s) = %q: untranslated or duplicated", r, txt)
}
seen[txt] = true
}
}- [ ] Step 2: Run it to verify it fails
Run: go test ./internal/console -run TestRegionMessagesRender -count=1 Expected: FAIL, undefined.
- [ ] Step 3: Write the strings from the record
The drafts are shown. Write each from its record entry verbatim.
package console
import (
"fmt"
"strings"
)
// The nearest-region feature's API and operator messages (spec §19), as
// approved in the nearest-region copy record. Change none of them without
// a new copy review.
func reasonText(reason string) string {
switch reason {
case "absent":
return "no box runs there now"
case "not-hosting":
return "boxes there can't start instances"
case "no-secrets-key":
return "boxes there can't open this deployment's secrets"
case "not-eligible":
return "boxes there don't take ordinary instances"
}
return reason
}
// C16, or C16b when no region would pass (codex plan-R4 2: a list names
// only what the door takes, so it can be empty).
func msgAppRegionUnavailable(code, reason string, can []string) string {
if len(can) == 0 {
return fmt.Sprintf("this app's region %s can't take new instances right now (%s), and no other region this app may use can either. Try again later.",
code, reasonText(reason))
}
return fmt.Sprintf("this app's region %s can't take new instances right now (%s). Pass a region to place this instance elsewhere. Regions that can: %s.",
code, reasonText(reason), strings.Join(can, ", "))
}
// C17, or C17b when no allowed region would pass.
func msgAppRegionNotAllowed(code string, allowed []string) string {
if len(allowed) == 0 {
return fmt.Sprintf("this app's region %s is not one its instances may use, and no region they may use can take new instances right now. Try again later.", code)
}
return fmt.Sprintf("this app's region %s is not one its instances may use. Allowed: %s. Pass a region to choose one.",
code, strings.Join(allowed, ", "))
}
// C18, or C18b when no region would pass.
func msgRegionUnknown(code string, choices []string) string {
if len(choices) == 0 {
return fmt.Sprintf("region %s is not one this platform runs in. Run the command without --region.", code)
}
return fmt.Sprintf("region %s is not one this platform runs in. Choose from: %s.", code, strings.Join(choices, ", "))
}
// C19, or C19b when no region would pass.
func msgRegionNotArmed(code string, choices []string) string {
if len(choices) == 0 {
return fmt.Sprintf("region %s can't take new apps right now. Run the command without --region.", code)
}
return fmt.Sprintf("region %s can't take new apps right now. Choose from: %s.", code, strings.Join(choices, ", "))
}
// C20
const msgAppRegionUnreadable = "could not read this app's region. Try again."
// C26
func msgReplicaOverlap(code string) string {
return fmt.Sprintf("this app's region %s is also one of this instance's replica placements. Pass a region for the instance, or drop %s from replicas.", code, code)
}
// C27
const msgPolicyUnreadable = "could not read this app's instance policy, so there is no region suggestion. Try again."
// C28
func msgAppRegionUnconfirmed(code string) string {
return fmt.Sprintf("could not confirm that this app's region %s can take new instances. Try again, or pass a region.", code)
}
// C29: an operator log line.
func msgPlacementDiscrepancy(host, app, code string) string {
return fmt.Sprintf("possible placement discrepancy: %s of %s has no region, but %s's region is %s. It may be a deliberate home override; review it.", host, app, app, code)
}
// C31 (R9)
func msgNoSecretsKey(code string) string {
return fmt.Sprintf("region %s can't run instances right now: its boxes don't hold this deployment's secrets key, so an instance there would start without its config or secrets. Choose another region.", code)
}- [ ] Step 4: Run it to verify it passes
Run: go test ./internal/console -run TestRegionMessagesRender -count=1 Expected: PASS.
- [ ] Step 5: Commit
git add internal/console/regionmsgs.go internal/console/regionmsgs_test.go
git commit -m "console: region API messages, as approved
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"---
Task 8: Store columns, rebuild list and accessors
Files:
- Modify:
internal/console/store.go- the ALTER list, ending at:386; -migrateAppsKey, at:487-505; -CreateAppIn, at:1349. - Test:
internal/console/store_region_test.go(create)
Interfaces:
- Produces:
func (s *Store) CreateAppInRegion(accountID int64, app, createdBy, region, source string, now time.Time) (created bool, err error)
func (s *Store) AppRegion(accountID int64, app string) (region, source string, err error)
type nonHomeAppRegion struct{ Sqid, App, Region string; CreatedAt time.Time }
func (s *Store) NonHomeAppRegions() ([]nonHomeAppRegion, error)- [ ] Step 1: Write the failing tests
// internal/console/store_region_test.go
package console
import (
"database/sql"
"path/filepath"
"testing"
"time"
_ "modernc.org/sqlite"
)
func TestAppRegionRoundTripAndDefaults(t *testing.T) {
s := openTestStore(t)
now := time.Now()
if ok, err := s.CreateAppInRegion(OpsAccountID, "blog", "p@k.dev", "uk", "timezone", now); err != nil || !ok {
t.Fatalf("CreateAppInRegion = %v %v", ok, err)
}
if r, src, err := s.AppRegion(OpsAccountID, "blog"); err != nil || r != "uk" || src != "timezone" {
t.Fatalf("AppRegion = %q %q %v", r, src, err)
}
if ok, err := s.CreateAppIn(OpsAccountID, "old", "p@k.dev", now); err != nil || !ok {
t.Fatalf("CreateAppIn = %v %v", ok, err)
}
if r, src, _ := s.AppRegion(OpsAccountID, "old"); r != "" || src != "" {
t.Fatalf("CreateAppIn row = %q %q, want the home and no source", r, src)
}
if r, src, err := s.AppRegion(OpsAccountID, "missing"); err != nil || r != "" || src != "" {
t.Fatalf("missing row = %q %q %v", r, src, err)
}
// A lost claim race writes nothing.
if ok, _ := s.CreateAppInRegion(OpsAccountID, "blog", "x@k.dev", "au", "ip", now); ok {
t.Fatal("a second claim of the same name succeeded")
}
if r, _, _ := s.AppRegion(OpsAccountID, "blog"); r != "uk" {
t.Fatalf("a lost claim changed the region to %q", r)
}
}
// The fixture runs NewServer, whose backfill creates the ops account row
// (sqid "bab") that NonHomeAppRegions joins on.
func TestNonHomeAppRegions(t *testing.T) {
fx := newConsoleFixture(t)
now := time.Now()
if _, err := fx.st.CreateAppInRegion(OpsAccountID, "blog", "p@k.dev", "uk", "chosen", now); err != nil {
t.Fatal(err)
}
if _, err := fx.st.CreateAppInRegion(OpsAccountID, "home", "p@k.dev", "", "default", now); err != nil {
t.Fatal(err)
}
rows, err := fx.st.NonHomeAppRegions()
if err != nil || len(rows) != 1 || rows[0].App != "blog" || rows[0].Region != "uk" || rows[0].Sqid != "bab" {
t.Fatalf("NonHomeAppRegions = %+v %v", rows, err)
}
}
// spec §5.2: a database from BEFORE migrateAppsKey whose rows already
// carry an app region keeps it through the rebuild, a reopen, a rename and
// a move to another account. An empty value would not prove the copy.
func TestRegionValuesSurviveRebuildRenameAndMove(t *testing.T) {
path := filepath.Join(t.TempDir(), "console.db")
db, err := sql.Open("sqlite", path)
if err != nil {
t.Fatal(err)
}
if _, err := db.Exec(`CREATE TABLE apps (
app TEXT PRIMARY KEY, created_by TEXT NOT NULL, created_at INTEGER NOT NULL,
account_id INTEGER NOT NULL DEFAULT 1, deleted_at INTEGER NOT NULL DEFAULT 0,
placement TEXT NOT NULL DEFAULT '', control_plane INTEGER NOT NULL DEFAULT 0,
build_probe TEXT NOT NULL DEFAULT '', region TEXT NOT NULL DEFAULT '',
region_source TEXT NOT NULL DEFAULT '');
INSERT INTO apps (app, created_by, created_at, region, region_source) VALUES ('legacy', 'p@k.dev', 1, 'uk', 'timezone');`); err != nil {
t.Fatal(err)
}
db.Close()
for i := 0; i < 2; i++ { // the rebuild, then a reopen of the rebuilt table
s, err := Open(path)
if err != nil {
t.Fatalf("open %d: %v", i, err)
}
if r, src, err := s.AppRegion(OpsAccountID, "legacy"); err != nil || r != "uk" || src != "timezone" {
t.Fatalf("open %d: legacy = %q %q %v, want uk/timezone carried", i, r, src, err)
}
s.Close()
}
s, err := Open(path)
if err != nil {
t.Fatal(err)
}
defer s.Close()
// Open migrates but does not backfill accounts (NewServer does), and
// rename and move both resolve the app's account row: seed the ops
// account the legacy row belongs to, and the move's destination.
if _, err := s.db.Exec(`INSERT OR IGNORE INTO accounts (id, sqid, name, created_at) VALUES (?, 'bab', 'Ops', 0)`, OpsAccountID); err != nil {
t.Fatal(err)
}
if err := s.RenameAppIn(OpsAccountID, "legacy", "renamed"); err != nil {
t.Fatal(err)
}
if r, src, _ := s.AppRegion(OpsAccountID, "renamed"); r != "uk" || src != "timezone" {
t.Fatalf("after rename = %q %q", r, src)
}
const other = 2001
if _, err := s.db.Exec(`INSERT INTO accounts (id, sqid, name, created_at) VALUES (?, 'zzz', 'Other', 0)`, other); err != nil {
t.Fatal(err)
}
if err := s.MoveApp("renamed", OpsAccountID, other); err != nil {
t.Fatal(err)
}
if r, src, _ := s.AppRegion(other, "renamed"); r != "uk" || src != "timezone" {
t.Fatalf("after move = %q %q", r, src)
}
}- [ ] Step 2: Run them to verify they fail
Run: go test ./internal/console -run 'TestAppRegion|TestNonHomeAppRegions|TestRegionValuesSurvive' -count=1 Expected: FAIL, undefined CreateAppInRegion.
- [ ] Step 3: Add the columns to the ALTER list
Directly after the build_probe ALTER:
// App region (nearest-region spec §5.1): the placement code new
// instances default to, in the RECORD vocabulary — "" is the
// deployment's home, which is also what every existing row reads, so
// the column default IS the backfill (§5.4). region_source is a label
// for people and logs: chosen | timezone | ip | default | "" (the row
// predates the feature). No timezone, IP or coordinate is stored.
`ALTER TABLE apps ADD COLUMN region TEXT NOT NULL DEFAULT '';`,
`ALTER TABLE apps ADD COLUMN region_source TEXT NOT NULL DEFAULT '';`,- [ ] Step 4: Carry them through the rebuild
In migrateAppsKey, replace the DDL and the copy:
`CREATE TABLE apps_new (
app TEXT NOT NULL,
account_id INTEGER NOT NULL DEFAULT 1,
created_by TEXT NOT NULL,
created_at INTEGER NOT NULL,
deleted_at INTEGER NOT NULL DEFAULT 0,
placement TEXT NOT NULL DEFAULT '',
control_plane INTEGER NOT NULL DEFAULT 0,
build_probe TEXT NOT NULL DEFAULT '',
region TEXT NOT NULL DEFAULT '',
region_source TEXT NOT NULL DEFAULT '',
PRIMARY KEY (account_id, app)
);`,
// The ALTERs above run before migrateAppsKey in migrate(), so every
// column below already exists on the old table when this copies it;
// a row already carrying an app region must not lose it
// (nearest-region spec §5.2).
`INSERT INTO apps_new (app, account_id, created_by, created_at, deleted_at, placement, control_plane, build_probe, region, region_source)
SELECT app, account_id, created_by, created_at, deleted_at, placement, control_plane, build_probe, region, region_source FROM apps;`,- [ ] Step 5: Add the accessors
func (s *Store) CreateAppIn(accountID int64, app, createdBy string, now time.Time) (created bool, err error) {
return s.CreateAppInRegion(accountID, app, createdBy, "", "", now)
}
// CreateAppInRegion claims an app name with its region in the SAME insert:
// an app region has no repair verb, so a separate best-effort write that
// failed would leave the app at the home for good, silently (spec §5.3).
func (s *Store) CreateAppInRegion(accountID int64, app, createdBy, region, source string, now time.Time) (created bool, err error) {
res, err := s.db.Exec(`INSERT INTO apps (app, account_id, created_by, created_at, region, region_source)
VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(account_id, app) DO NOTHING`,
app, accountID, createdBy, now.Unix(), region, source)
if err != nil {
return false, err
}
n, err := res.RowsAffected()
return n > 0, err
}
// AppRegion reads an app's region and its source label. A missing row
// reads as the home with no source.
func (s *Store) AppRegion(accountID int64, app string) (region, source string, err error) {
err = s.db.QueryRow(`SELECT region, region_source FROM apps WHERE account_id = ? AND app = ?`, accountID, app).
Scan(®ion, &source)
if errors.Is(err, sql.ErrNoRows) {
return "", "", nil
}
return region, source, err
}
// nonHomeAppRegion is one live app holding a non-home region, for the
// roll-forward discrepancy log (spec §5.7).
type nonHomeAppRegion struct {
Sqid, App, Region string
CreatedAt time.Time
}
func (s *Store) NonHomeAppRegions() ([]nonHomeAppRegion, error) {
rows, err := s.db.Query(`SELECT a.sqid, p.app, p.region, p.created_at FROM apps p
JOIN accounts a ON a.id = p.account_id
WHERE p.region != '' AND p.deleted_at = 0 ORDER BY a.sqid, p.app`)
if err != nil {
return nil, err
}
defer rows.Close()
var out []nonHomeAppRegion
for rows.Next() {
var r nonHomeAppRegion
var created int64
if err := rows.Scan(&r.Sqid, &r.App, &r.Region, &created); err != nil {
return nil, err
}
r.CreatedAt = time.Unix(created, 0)
out = append(out, r)
}
return out, rows.Err()
}- [ ] Step 6: Run the tests to verify they pass
Run: go test ./internal/console -count=1 Expected: PASS, the whole package.
- [ ] Step 7: Commit
git add internal/console/store.go internal/console/store_region_test.go
git commit -m "console store: app region columns carried through the apps-key rebuild (nearest-region §5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"---
Task 9: Console config, the switch, the expected secrets fingerprint and the test seams
Files:
- Create:
internal/console/regionconfig.go,internal/console/regionconfig_test.go - Modify:
internal/console/server.go-Config, nearHomeRegionat:477-484; - theServerstruct; -NewServer, whose last line beforereturncallslogRegionConfig. - Modify:
cmd/console/main.go:278 - Test:
cmd/console/region_test.go(create,package main)
Interfaces:
- Consumes:
geo.Locatorandgeo.OpenMMDB(Task 5),appconfig.PublicFingerprint(Task 1b), and the record's O1 to O4. - Produces: -
Config.AppRegions boolandConfig.GeoIP geo.Locator; -Server.appRegionOf func(accountID int64, app string) (string, string, error), a test seam, nil in production; -Server.beforeClaimInsert func(), a test seam, nil in production; - the test helpermustTestSecretsKey() (pubB64, fp string), inregionconfig_test.go, used by every later R9 test; - these methods and helpers:
func (s *Server) appRegionsOn() bool
func (s *Server) expectedSecretsFP() string // "" = cannot judge (R9)
func (s *Server) appRegion(accountID int64, app string) (string, string, error)
func logRegionConfig(cfg Config)
func openGeoIP(path string) geo.Locator // cmd/console
func appRegionsSwitch(v string) bool // cmd/console- [ ] Step 1: Write the failing tests
// internal/console/regionconfig_test.go
package console
import (
"bytes"
"crypto/ecdh"
"encoding/base64"
"log"
"strings"
"testing"
"github.com/carlosframework/platform/internal/appconfig"
)
// mustTestSecretsKey is the fixed P-256 key every R9 test in this package
// uses: its public half as CARLOS_SECRETS_PUBKEY carries it, and the
// fingerprint a box holding it publishes.
func mustTestSecretsKey() (pubB64, fp string) {
priv, err := ecdh.P256().NewPrivateKey(bytes.Repeat([]byte{1}, 32))
if err != nil {
panic(err)
}
return base64.StdEncoding.EncodeToString(priv.PublicKey().Bytes()), appconfig.KeyFingerprint(priv.PublicKey())
}
func TestAppRegionsOnNeedsANamedHome(t *testing.T) {
fx := newConsoleFixture(t)
for _, c := range []struct {
on bool
home string
want bool
}{{false, "ie", false}, {true, "", false}, {true, "ie", true}} {
fx.srv.cfg.AppRegions, fx.srv.cfg.HomeRegion = c.on, c.home
if got := fx.srv.appRegionsOn(); got != c.want {
t.Errorf("AppRegions=%v HomeRegion=%q: appRegionsOn = %v, want %v", c.on, c.home, got, c.want)
}
}
}
func TestExpectedSecretsFP(t *testing.T) {
fx := newConsoleFixture(t)
pub, fp := mustTestSecretsKey()
fx.srv.cfg.SecretsPub = pub
if got := fx.srv.expectedSecretsFP(); got != fp {
t.Fatalf("expectedSecretsFP = %q, want %q", got, fp)
}
fx.srv.cfg.SecretsPub = ""
if got := fx.srv.expectedSecretsFP(); got != "" {
t.Fatalf("no CARLOS_SECRETS_PUBKEY = %q, want empty (cannot judge)", got)
}
}
func TestLogRegionConfig(t *testing.T) {
var buf bytes.Buffer
prev := log.Writer()
log.SetOutput(&buf)
defer log.SetOutput(prev)
logRegionConfig(Config{AppRegions: true})
out := buf.String()
if !strings.Contains(out, "CONSOLE_HOME_REGION is unset") || !strings.Contains(out, "CARLOS_SECRETS_PUBKEY is unset") {
t.Fatalf("missing startup lines: %s", out)
}
// Switch off: the guess endpoint still answers, so a missing secrets
// key is still worth saying (codex plan-R2 8); an unnamed home is not.
buf.Reset()
logRegionConfig(Config{})
if out := buf.String(); !strings.Contains(out, "CARLOS_SECRETS_PUBKEY is unset") || strings.Contains(out, "CONSOLE_HOME_REGION") {
t.Fatalf("switch off logged %q, want only the secrets-key line", out)
}
buf.Reset()
pub, _ := mustTestSecretsKey()
logRegionConfig(Config{AppRegions: true, HomeRegion: "ie", SecretsPub: pub})
if buf.Len() != 0 {
t.Fatalf("a complete configuration logged: %s", buf.String())
}
}The two strings.Contains fragments are substrings of O1 and O2. Use fragments of the approved text if review changed them.
Create cmd/console/region_test.go. It must be package main: main_test.go is the external package main_test and cannot reach the unexported helpers.
package main
import (
"path/filepath"
"testing"
)
func TestOpenGeoIPIsOptionalAndNeverFatal(t *testing.T) {
if openGeoIP("") != nil {
t.Fatal("unset CONSOLE_GEOIP_DB must mean no locator")
}
if openGeoIP(filepath.Join(t.TempDir(), "missing.mmdb")) != nil {
t.Fatal("an unopenable file must mean no locator, not a crash")
}
}
func TestAppRegionsSwitchParses(t *testing.T) {
for v, want := range map[string]bool{"on": true, "ON": true, " on ": true, "": false, "off": false, "true": false} {
if got := appRegionsSwitch(v); got != want {
t.Errorf("appRegionsSwitch(%q) = %v, want %v", v, got, want)
}
}
}- [ ] Step 2: Run them to verify they fail
Run: go test ./internal/console -run 'TestAppRegionsOn|TestExpectedSecretsFP|TestLogRegionConfig' -count=1; go test ./cmd/console -run 'OpenGeoIP|AppRegionsSwitch' -count=1 Expected: FAIL, undefined.
- [ ] Step 3: Add the Config fields and the Server seams
In Config, after HomeRegion:
// AppRegions is CONSOLE_APP_REGIONS=on (nearest-region spec §5.6). Off
// never stops the create door's fill for rows already holding a region.
AppRegions bool
// GeoIP is the optional CONSOLE_GEOIP_DB locator; nil = IP step off.
GeoIP geo.LocatorIn the Server struct:
// appRegionOf and beforeClaimInsert are TEST SEAMS (nil in
// production): a store failure on the app-region read (C20), and a
// competing insert landing after the region was resolved.
appRegionOf func(accountID int64, app string) (string, string, error)
beforeClaimInsert func()Import "github.com/carlosframework/platform/internal/geo".
- [ ] Step 4: Implement
regionconfig.go
package console
import (
"log"
"github.com/carlosframework/platform/internal/appconfig"
)
// appRegionsOn is spec §5.6: the switch AND a named home.
func (s *Server) appRegionsOn() bool {
return s.cfg.AppRegions && s.cfg.HomeRegion != ""
}
// expectedSecretsFP is the fingerprint a box must publish to receive
// placed instances (spec R9): the console's own CARLOS_SECRETS_PUBKEY,
// the key the CLI seals config to. "" means the console cannot judge, and
// then no coded region is eligible for automatic placement.
func (s *Server) expectedSecretsFP() string {
if s.cfg.SecretsPub == "" {
return ""
}
fp, err := appconfig.PublicFingerprint(s.cfg.SecretsPub)
if err != nil {
return ""
}
return fp
}
// appRegion reads the app's stored region through the test seam when one
// is set.
func (s *Server) appRegion(accountID int64, app string) (string, string, error) {
if s.appRegionOf != nil {
return s.appRegionOf(accountID, app)
}
return s.store.AppRegion(accountID, app)
}
// logRegionConfig names, once at startup, each configuration that leaves
// part of the feature off. The secrets-key line does not depend on the
// switch: the guess endpoint answers with the switch off too (R9).
func logRegionConfig(cfg Config) {
if cfg.AppRegions && cfg.HomeRegion == "" {
log.Print("console: CONSOLE_APP_REGIONS=on but CONSOLE_HOME_REGION is unset, so app regions stay off until the home is named")
}
if cfg.SecretsPub == "" {
log.Print("console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically")
}
}The two log texts are O1 and O2 from the record. In NewServer, call logRegionConfig(cfg) immediately before its successful return.
- [ ] Step 5: Wire the environment in
cmd/console/main.go
Beside HomeRegion::
AppRegions: appRegionsSwitch(os.Getenv("CONSOLE_APP_REGIONS")),
GeoIP: openGeoIP(strings.TrimSpace(os.Getenv("CONSOLE_GEOIP_DB"))),Near envOr:
// appRegionsSwitch reads CONSOLE_APP_REGIONS: exactly "on" (any case,
// trimmed) turns app regions on.
func appRegionsSwitch(v string) bool { return strings.EqualFold(strings.TrimSpace(v), "on") }
// openGeoIP opens the optional CONSOLE_GEOIP_DB. Unset, unopenable or not
// an mmdb means the IP step is off — logged once, never fatal. Returns an
// untyped nil on every failure, never a nil *geo.MMDB in the interface.
func openGeoIP(path string) geo.Locator {
if path == "" {
return nil
}
m, err := geo.OpenMMDB(path)
if err != nil {
log.Printf("console: CONSOLE_GEOIP_DB %s is unusable, so the IP step of region guesses is off: %v", path, err)
return nil
}
log.Printf("console: GeoIP database loaded from %s", path)
return m
}The two log texts are O3 and O4. The open error describes the file, never a client.
- [ ] Step 6: Run the tests to verify they pass
Run: go test ./internal/console -run 'TestAppRegionsOn|TestExpectedSecretsFP|TestLogRegionConfig' -count=1 && go test ./cmd/console -count=1 Expected: PASS.
- [ ] Step 7: Commit
git add internal/console/server.go internal/console/regionconfig.go internal/console/regionconfig_test.go cmd/console/main.go cmd/console/region_test.go
git commit -m "console: app-regions switch, expected secrets fingerprint, GeoIP, test seams (nearest-region §5.6, §6.2, R9)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"---
Task 10: Strict edges read, candidates, eligibility (incl. R9) and policy
Files:
- Modify:
internal/console/instancesplacement.go-edgesFilePlacementView, at:35-55; - besidereadEdgesFiles, at:83. - Create:
internal/console/regioncandidates.go - Test:
internal/console/regioncandidates_test.go
Interfaces:
- Consumes: -
provision.ValidRegionandprovision.GetManifest; -s.appPlacement(fleets.go:479) andopenRegistry; -instanceRegionAllowed(apiinstances.go:1797); -geo.LookupAWSRegion(Task 4); -s.expectedSecretsFP(Task 9); - the record's O6 and O7. - Produces:
type edgesSnapshot struct {
Files []edgesFilePlacementView
Complete bool // false when any listed file could not be read or parsed
}
func readEdgesSnapshot(ctx context.Context, s release.Store) (edgesSnapshot, error) // error only when the LISTING fails
type regionCandidate struct {
Code string // record vocabulary; "" is the home
Home bool
AWSRegion string
Reachable, Hosting, KeyOK, Eligible bool
NoKeyProven bool // R9: current writers only, and none holds the key
}
func regionCandidates(snap edgesSnapshot, homeName, expectedFP string) []regionCandidate
func (s *Server) regionCands(snap edgesSnapshot) []regionCandidate
func (s *Server) candsOrHome(ctx context.Context) []regionCandidate // a fresh snapshot, or the home alone
func homeOnlyCandidates() []regionCandidate
func (c regionCandidate) unavailableReason() string // "", absent, not-hosting, no-secrets-key, not-eligible
func findCandidate(cands []regionCandidate, code string) (regionCandidate, bool)
func eligibleCount(cands []regionCandidate) int
type policyState int // policyAbsent, policyKnown, policyUnreadable
type regionPolicy struct{ State policyState; M *provision.Manifest; Placed string }
func (s *Server) appPolicy(ctx context.Context, acct *Account, app string) regionPolicy
func (p regionPolicy) pooled() bool
func (p regionPolicy) allows(code string) boolTest helpers produced for later console tasks, in regioncandidates_test.go:
testKeyPubandtestKeyFP, from Task 9'smustTestSecretsKey;edgeJSON;- the fixture edges
homeBox,ukEligible,ukNoKey,ukWrongKey,ukOldWriter,auPoolOnly,usNoInbound,nzNoHosting,jpNoEIPandfleetBoxFile; splitCapability, the same-region box pairs no single box of which is eligible;setEdges, which creates or replaces, unlikeputEdges;failingListandcaptureLog.
- [ ] Step 1: Write the failing tests and the shared helpers
// internal/console/regioncandidates_test.go
package console
import (
"bytes"
"context"
"encoding/json"
"errors"
"io"
"log"
"os"
"strings"
"testing"
"github.com/carlosframework/platform/internal/provision"
"github.com/carlosframework/platform/internal/release"
)
// mustTestSecretsKey is Task 9's (regionconfig_test.go).
var testKeyPub, testKeyFP = mustTestSecretsKey()
func bp(b bool) *bool { return &b }
type edgeOpts struct {
hib, shared, inbound *bool // nil = the writer predates the field
fp string
}
var eligibleBox = edgeOpts{hib: bp(true), shared: bp(true), inbound: bp(true), fp: testKeyFP}
// edgeJSON writes one box's edges file the way internal/host does.
func edgeJSON(box, eip, awsRegion, placement string, o edgeOpts) string {
m := map[string]any{"v": 1, "box": box, "eip": eip, "region": awsRegion, "hosts": map[string]any{}}
if placement != "" {
m["placement"] = placement
}
if o.hib != nil {
m["hibernation_configured"] = *o.hib
}
if o.shared != nil {
m["serves_shared_pool"] = *o.shared
}
if o.inbound != nil {
m["accepts_inbound"] = *o.inbound
}
if o.fp != "" {
m["secrets_key_fp"] = o.fp
}
b, _ := json.Marshal(m)
return string(b)
}
var (
homeBox = edgeJSON("ie-box", "5.6.7.8", "eu-west-1", "", eligibleBox)
ukEligible = edgeJSON("uk-box", "1.2.3.4", "eu-west-2", "uk", eligibleBox)
ukNoKey = edgeJSON("uk-box", "1.2.3.4", "eu-west-2", "uk", edgeOpts{hib: bp(true), shared: bp(true), inbound: bp(true)})
ukWrongKey = edgeJSON("uk-box", "1.2.3.4", "eu-west-2", "uk", edgeOpts{hib: bp(true), shared: bp(true), inbound: bp(true), fp: "sha256:" + strings.Repeat("0", 64)})
ukOldWriter = edgeJSON("uk-box", "1.2.3.4", "eu-west-2", "uk", edgeOpts{})
auPoolOnly = edgeJSON("au-box", "9.9.9.9", "ap-southeast-2", "au", edgeOpts{hib: bp(true), shared: bp(false), inbound: bp(true), fp: testKeyFP})
usNoInbound = edgeJSON("us-box", "8.8.4.4", "us-east-1", "us", edgeOpts{hib: bp(true), shared: bp(true), inbound: bp(false), fp: testKeyFP})
nzNoHosting = edgeJSON("nz-box", "6.6.6.6", "ap-southeast-6", "nz", edgeOpts{hib: bp(false), shared: bp(true), inbound: bp(true), fp: testKeyFP})
jpNoEIP = edgeJSON("jp-box", "", "ap-northeast-1", "jp", eligibleBox)
fleetBoxFile = edgeJSON("fl-box", "4.4.4.4", "eu-west-2", "fleet/acme", eligibleBox)
)
// setEdges creates OR replaces edges files. putEdges is create-only (an
// empty ifMatch on DirStore and S3), so it cannot change a box's file.
func setEdges(t *testing.T, fx *consoleFixture, files map[string]string) {
t.Helper()
ctx := context.Background()
for name, body := range files {
key := "control/edges/" + name + ".json"
etag := ""
if _, cur, err := fx.rel.Get(ctx, key); err == nil {
etag = cur
} else if !errors.Is(err, os.ErrNotExist) {
t.Fatal(err)
}
if _, err := fx.rel.Put(ctx, key, []byte(body), etag); err != nil {
t.Fatalf("set %s: %v", key, err)
}
}
}
type failingList struct{ release.Store }
func (failingList) List(context.Context, string) ([]string, error) {
return nil, errors.New("bucket unreachable")
}
func captureLog(w io.Writer) (restore func()) {
prev, flags := log.Writer(), log.Flags()
log.SetOutput(w)
return func() { log.SetOutput(prev); log.SetFlags(flags) }
}
// views is a COMPLETE snapshot of the given edges-file bodies.
func views(t *testing.T, bodies ...string) edgesSnapshot {
t.Helper()
snap := edgesSnapshot{Complete: true}
for _, b := range bodies {
var f edgesFilePlacementView
if err := json.Unmarshal([]byte(b), &f); err != nil {
t.Fatal(err)
}
snap.Files = append(snap.Files, f)
}
return snap
}
func TestRegionCandidatesNeedPositiveEligibility(t *testing.T) {
ca := edgeJSON("ca-box", "7.7.7.7", "ca-central-1", "ca", edgeOpts{})
cands := regionCandidates(views(t, homeBox, ukEligible, auPoolOnly, usNoInbound, ca, nzNoHosting, jpNoEIP, fleetBoxFile), "ie", testKeyFP)
want := map[string]string{"": "", "uk": "", "au": "not-eligible", "us": "not-eligible",
"ca": "no-secrets-key", "nz": "not-hosting", "jp": "absent"}
if len(cands) != len(want) {
t.Fatalf("candidates = %+v, want exactly %v (no fleet code)", cands, want)
}
for code, reason := range want {
c, ok := findCandidate(cands, code)
if !ok {
t.Fatalf("no candidate %q", code)
}
if got := c.unavailableReason(); got != reason {
t.Errorf("%q: reason %q, want %q", code, got, reason)
}
}
if h, _ := findCandidate(cands, ""); !h.Home || h.AWSRegion != "eu-west-1" || !h.Eligible {
t.Errorf("home = %+v", h)
}
}
// R9: a box must hold the deployment's secrets key to receive placed
// instances, and only a CURRENT writer can prove it does not.
func TestRegionCandidatesSecretsKey(t *testing.T) {
cases := []struct {
name string
body string
expected string
reason string
noKeyProven bool
}{
{"matching key", ukEligible, testKeyFP, "", false},
{"no key, current writer", ukNoKey, testKeyFP, "no-secrets-key", true},
{"wrong key, current writer", ukWrongKey, testKeyFP, "no-secrets-key", true},
{"old writer: unknown", ukOldWriter, testKeyFP, "no-secrets-key", false},
{"console cannot judge", ukEligible, "", "no-secrets-key", false},
}
for _, c := range cases {
cand, _ := findCandidate(regionCandidates(views(t, homeBox, c.body), "ie", c.expected), "uk")
if got := cand.unavailableReason(); got != c.reason || cand.NoKeyProven != c.noKeyProven {
t.Errorf("%s: reason %q proven %v, want %q %v", c.name, got, cand.NoKeyProven, c.reason, c.noKeyProven)
}
}
// One box with the key and one without: the region is not PROVEN
// keyless, and is eligible through the box that holds it.
ukKeyless2 := edgeJSON("uk-box-2", "1.2.3.5", "eu-west-2", "uk", edgeOpts{hib: bp(true), shared: bp(true), inbound: bp(true)})
cand, _ := findCandidate(regionCandidates(views(t, homeBox, ukEligible, ukKeyless2), "ie", testKeyFP), "uk")
if !cand.Eligible || cand.NoKeyProven {
t.Errorf("mixed region = %+v", cand)
}
}
// codex plan-R2 1: a negative proof needs EVERY box. With one file
// unreadable or malformed, a readable keyless box proves nothing.
func TestNoKeyProvenNeedsACompleteRead(t *testing.T) {
incomplete := views(t, homeBox, ukNoKey)
incomplete.Complete = false
if c, _ := findCandidate(regionCandidates(incomplete, "ie", testKeyFP), "uk"); c.NoKeyProven {
t.Fatal("an incomplete read proved a region keyless")
}
ctx := context.Background()
for name, extra := range map[string]string{"malformed": "{not json", "unversioned": `{"box":"uk-box-2"}`} {
s := release.NewDirStore(t.TempDir())
for key, body := range map[string]string{"ie-box": homeBox, "uk-box": ukNoKey, "uk-box-2": extra} {
if _, err := s.Put(ctx, "control/edges/"+key+".json", []byte(body), ""); err != nil {
t.Fatal(err)
}
}
snap, err := readEdgesSnapshot(ctx, s)
if err != nil || snap.Complete {
t.Fatalf("%s: snapshot complete=%v err=%v, want incomplete", name, snap.Complete, err)
}
if c, _ := findCandidate(regionCandidates(snap, "ie", testKeyFP), "uk"); c.NoKeyProven {
t.Fatalf("%s: proved keyless with a box unassessed", name)
}
}
s := release.NewDirStore(t.TempDir())
for key, body := range map[string]string{"ie-box": homeBox, "uk-box": ukNoKey, "uk-box-2": ukEligible} {
if _, err := s.Put(ctx, "control/edges/"+key+".json", []byte(body), ""); err != nil {
t.Fatal(err)
}
}
snap, err := readEdgesSnapshot(ctx, failingGet{Store: s, key: "control/edges/uk-box-2.json"})
if err != nil || snap.Complete {
t.Fatalf("failed GET: complete=%v err=%v, want incomplete", snap.Complete, err)
}
if c, _ := findCandidate(regionCandidates(snap, "ie", testKeyFP), "uk"); c.NoKeyProven {
t.Fatal("a failed GET let the readable keyless box prove the region keyless")
}
}
type failingGet struct {
release.Store
key string
}
func (f failingGet) Get(ctx context.Context, key string) ([]byte, string, error) {
if key == f.key {
return nil, "", errors.New("read timeout")
}
return f.Store.Get(ctx, key)
}
// codex plan-R2 2: a candidate's coordinates come from its ELIGIBLE boxes
// only; ties among them go to the lexically smallest AWS region.
func TestCandidateLocationComesFromEligibleBoxes(t *testing.T) {
syd1 := edgeJSON("uk-syd-1", "2.2.2.1", "ap-southeast-2", "uk", edgeOpts{hib: bp(true), shared: bp(true), inbound: bp(true)})
syd2 := edgeJSON("uk-syd-2", "2.2.2.2", "ap-southeast-2", "uk", edgeOpts{hib: bp(true), shared: bp(true), inbound: bp(false), fp: testKeyFP})
c, _ := findCandidate(regionCandidates(views(t, homeBox, ukEligible, syd1, syd2), "ie", testKeyFP), "uk")
if !c.Eligible || c.AWSRegion != "eu-west-2" {
t.Fatalf("mixed eligibility located uk at %q, want the eligible box's eu-west-2", c.AWSRegion)
}
dub := edgeJSON("uk-dub", "2.2.2.3", "eu-west-1", "uk", eligibleBox)
if c, _ := findCandidate(regionCandidates(views(t, homeBox, ukEligible, dub), "ie", testKeyFP), "uk"); c.AWSRegion != "eu-west-1" {
t.Fatalf("eligible tie = %q, want the lexically smallest eu-west-1", c.AWSRegion)
}
}
func TestRegionCandidatesFoldTheHomesName(t *testing.T) {
coded := edgeJSON("ie2", "5.6.7.9", "eu-west-1", "ie", eligibleBox)
cands := regionCandidates(views(t, homeBox, coded), "ie", testKeyFP)
if len(cands) != 1 || !cands[0].Home {
t.Fatalf("candidates = %+v, want the home alone", cands)
}
}
// spec §6.3, §6.5: a candidate with no point is logged once per code, and
// the line carries no caller coordinates (there are none to carry).
func TestRegionCandidatesLogAMissingPointOnce(t *testing.T) {
var buf bytes.Buffer
restore := captureLog(&buf)
defer restore()
odd := edgeJSON("odd-box", "3.3.3.3", "xx-nowhere-1", "odd", eligibleBox)
regionCandidates(views(t, homeBox, odd), "ie", testKeyFP)
regionCandidates(views(t, homeBox, odd), "ie", testKeyFP)
if n := strings.Count(buf.String(), `region "odd"`); n != 1 {
t.Fatalf("missing-point line logged %d times, want once: %s", n, buf.String())
}
}
func TestReadEdgesSnapshotReportsListFailure(t *testing.T) {
if _, err := readEdgesSnapshot(context.Background(), failingList{}); err == nil {
t.Fatal("a failed listing must be an error, not an empty answer (codex R2-2)")
}
snap, err := readEdgesSnapshot(context.Background(), release.NewDirStore(t.TempDir()))
if err != nil || len(snap.Files) != 0 || !snap.Complete {
t.Fatalf("an empty prefix = %+v %v, want complete and empty", snap, err)
}
}
func TestAppPolicyStates(t *testing.T) {
fx, _ := instanceFixture(t, "code-policy")
acct := &Account{ID: OpsAccountID, Sqid: "bab"}
ctx := context.Background()
if p := fx.srv.appPolicy(ctx, acct, "titogo"); p.State != policyAbsent || !p.allows("uk") {
t.Fatalf("no manifest = %+v", p)
}
m := titoManifest()
m.Pool, m.Regions = "", []string{"uk"}
enableInstances(t, fx, "titogo", m)
if p := fx.srv.appPolicy(ctx, acct, "titogo"); p.State != policyKnown || p.allows("") || !p.allows("uk") || p.pooled() {
t.Fatalf("known = %+v", p)
}
m.Pool = "tito"
enableInstances(t, fx, "titogo", m)
if !fx.srv.appPolicy(ctx, acct, "titogo").pooled() {
t.Fatal("a pooled manifest must read as pooled")
}
if err := fx.st.SetAppPlacement(OpsAccountID, "titogo", "fleet/acme"); err != nil {
t.Fatal(err)
}
if p := fx.srv.appPolicy(ctx, acct, "titogo"); p.Placed != "fleet/acme" {
t.Fatalf("placed = %q", p.Placed)
}
_, etag, err := fx.rel.Get(ctx, provision.ManifestKey("bab", "titogo"))
if err != nil {
t.Fatal(err)
}
if _, err := fx.rel.Put(ctx, provision.ManifestKey("bab", "titogo"), []byte("{not json"), etag); err != nil {
t.Fatal(err)
}
if p := fx.srv.appPolicy(ctx, acct, "titogo"); p.State != policyUnreadable || p.allows("") {
t.Fatalf("unreadable = %+v, want unreadable and nothing allowed", p)
}
}
// splitCapability pairs two boxes in one region that between them publish
// every eligibility fact while neither publishes them all (codex
// plan-R3 4). Task 11 reuses it for the guess.
var splitCapability = map[string][2]string{
"keyless ordinary box beside a keyed named-pool-only box": {
edgeJSON("uk-a", "1.2.3.4", "eu-west-2", "uk", edgeOpts{hib: bp(true), shared: bp(true), inbound: bp(true)}),
edgeJSON("uk-b", "1.2.3.5", "eu-west-2", "uk", edgeOpts{hib: bp(true), shared: bp(false), inbound: bp(true), fp: testKeyFP}),
},
"keyed no-inbound box beside a keyless inbound box": {
edgeJSON("uk-a", "1.2.3.4", "eu-west-2", "uk", edgeOpts{hib: bp(true), shared: bp(true), inbound: bp(false), fp: testKeyFP}),
edgeJSON("uk-b", "1.2.3.5", "eu-west-2", "uk", edgeOpts{hib: bp(true), shared: bp(true), inbound: bp(true)}),
},
"keyed non-hosting box beside a keyless hosting box": {
edgeJSON("uk-a", "1.2.3.4", "eu-west-2", "uk", edgeOpts{hib: bp(false), shared: bp(true), inbound: bp(true), fp: testKeyFP}),
edgeJSON("uk-b", "1.2.3.5", "eu-west-2", "uk", edgeOpts{hib: bp(true), shared: bp(true), inbound: bp(true)}),
},
}
// Eligibility is ONE box's facts, never the region's union of them.
func TestEligibilityNeverCombinesBoxes(t *testing.T) {
for name, boxes := range splitCapability {
cands := regionCandidates(views(t, homeBox, boxes[0], boxes[1]), "ie", testKeyFP)
if c, _ := findCandidate(cands, "uk"); c.Eligible || c.unavailableReason() == "" {
t.Errorf("%s: uk = %+v, want ineligible", name, c)
}
if n := eligibleCount(cands); n != 1 {
t.Errorf("%s: %d eligible candidates, want the home alone", name, n)
}
}
}Task 1's TestEligibilityPairsWithTheReconcileRefusal pairs these with the reconciler: the facts a box publishes are exactly what its reconcile pass mints, so a region is offered only through one box that would mint the record.
enableInstances writes through provision.PutManifest, which replaces with the current etag (provision.go:775), so calling it twice is safe.
- [ ] Step 2: Run them to verify they fail
Run: go test ./internal/console -run 'TestRegionCandidates|TestReadEdgesSnapshot|TestAppPolicyStates|TestNoKeyProven|TestCandidateLocation|TestEligibilityNeverCombinesBoxes' -count=1 Expected: FAIL, undefined.
- [ ] Step 3: Extend the view and add the strict read
In edgesFilePlacementView, after HibernationConfigured:
// ServesSharedPool and AcceptsInbound: POINTERS, absent = a writer
// predating them (unknown). SecretsKeyFP: absent = no key, or a writer
// predating it; AcceptsInbound's presence tells the two apart
// (nearest-region spec §6.1, R9).
ServesSharedPool *bool `json:"serves_shared_pool"`
AcceptsInbound *bool `json:"accepts_inbound"`
SecretsKeyFP string `json:"secrets_key_fp"`Make readEdgesFiles a wrapper over the strict read:
func readEdgesFiles(ctx context.Context, s release.Store) []edgesFilePlacementView {
if s == nil {
return nil
}
snap, err := readEdgesSnapshot(ctx, s)
if err != nil {
log.Printf("console: api instances: could not list %s for the placement join: %v", edgesFilePrefix, err)
return nil
}
return snap.Files
}
// edgesSnapshot is one read of control/edges/. Complete is false when any
// listed file could not be read or parsed: positive facts about the boxes
// that WERE read still hold, but nothing may be concluded about "every
// box" (R9's NoKeyProven, codex plan-R2 1).
type edgesSnapshot struct {
Files []edgesFilePlacementView
Complete bool
}
// readEdgesSnapshot REPORTS a failed listing instead of answering empty:
// the door must tell "could not read" (503, C28) from "no box publishes
// this code" (codex R2-2). A single unreadable or malformed file is
// skipped, as before, and marks the snapshot incomplete.
func readEdgesSnapshot(ctx context.Context, s release.Store) (edgesSnapshot, error) {
keys, err := s.List(ctx, edgesFilePrefix)
if err != nil {
return edgesSnapshot{}, err
}
snap := edgesSnapshot{Complete: true}
for _, key := range keys {
name, ok := strings.CutPrefix(key, edgesFilePrefix)
if !ok || !strings.HasSuffix(name, ".json") || strings.Contains(name, "/") {
continue
}
body, _, err := s.Get(ctx, key)
if err != nil {
log.Printf("console: api instances: could not read %s for the placement join: %v", key, err)
snap.Complete = false
continue
}
var f edgesFilePlacementView
if err := json.Unmarshal(body, &f); err != nil || f.V < 1 || f.Box == "" {
log.Printf("console: api instances: skipping %s in the placement join: not a versioned edges file", key)
snap.Complete = false
continue
}
snap.Files = append(snap.Files, f)
}
return snap, nil
}- [ ] Step 4: Implement
regioncandidates.go
package console
import (
"context"
"errors"
"log"
"os"
"sort"
"sync"
"github.com/carlosframework/platform/internal/geo"
"github.com/carlosframework/platform/internal/provision"
)
// disagreeSaid and noPointSaid throttle two diagnostics to once per code.
var disagreeSaid, noPointSaid sync.Map
// regionCandidate is one region an automatic choice may land in (spec
// §6.1). Code is in the record vocabulary: "" is the home.
type regionCandidate struct {
Code string
Home bool
AWSRegion string
Reachable bool // some box publishing the code has a dial address
Hosting bool // ... and its activator configured (unknown counts, as in placements)
KeyOK bool // ... and holds the deployment's secrets key (R9)
Eligible bool // ONE box has all of it, every fact POSITIVELY true
NoKeyProven bool // R9: every reachable hosting box is a current writer, and none holds the key
}
// unavailableReason is "" for an eligible candidate, else the first bar it
// fails (the C16 vocabulary).
func (c regionCandidate) unavailableReason() string {
switch {
case c.Eligible:
return ""
case !c.Reachable:
return "absent"
case !c.Hosting:
return "not-hosting"
case !c.KeyOK:
return "no-secrets-key"
}
return "not-eligible"
}
func isTrue(b *bool) bool { return b != nil && *b }
// regionCandidates folds an edges snapshot into candidates. expectedFP is
// the console's own secrets fingerprint; "" means it cannot judge, and then
// no coded region is KeyOK. The home is exempt from every eligibility fact.
// A coded candidate's AWS region comes only from its ELIGIBLE boxes (codex
// plan-R2 2); NoKeyProven needs a complete snapshot (codex plan-R2 1).
func regionCandidates(snap edgesSnapshot, homeName, expectedFP string) []regionCandidate {
type acc struct {
c regionCandidate
regions map[string]int
hostingBoxes int
unknownWriter, keyFound bool
}
by := map[string]*acc{}
get := func(code string, home bool) *acc {
a, ok := by[code]
if !ok {
a = &acc{c: regionCandidate{Code: code, Home: home}, regions: map[string]int{}}
by[code] = a
}
return a
}
h := get("", true)
h.c.Reachable, h.c.Hosting, h.c.KeyOK, h.c.Eligible = true, true, true, true
for _, f := range snap.Files {
home := f.Placement == "" || (homeName != "" && f.Placement == homeName)
if !home && !provision.ValidRegion(f.Placement) {
continue // fleet/<name> and malformed codes are never candidates
}
code := f.Placement
if home {
code = ""
}
a := get(code, home)
if f.EIP == "" {
continue
}
if home {
if f.Region != "" {
a.regions[f.Region]++
}
continue
}
hosting := isTrue(f.HibernationConfigured) || f.HibernationConfigured == nil
keyOK := expectedFP != "" && f.SecretsKeyFP == expectedFP
eligible := isTrue(f.HibernationConfigured) && isTrue(f.ServesSharedPool) && isTrue(f.AcceptsInbound) && keyOK
a.c.Reachable = true
a.c.Hosting = a.c.Hosting || hosting
if hosting {
a.hostingBoxes++
a.c.KeyOK = a.c.KeyOK || keyOK
a.keyFound = a.keyFound || keyOK
a.unknownWriter = a.unknownWriter || f.AcceptsInbound == nil
}
a.c.Eligible = a.c.Eligible || eligible
if eligible && f.Region != "" {
a.regions[f.Region]++
}
}
out := make([]regionCandidate, 0, len(by))
for _, a := range by {
a.c.AWSRegion = majority(a.c.Code, a.regions)
a.c.NoKeyProven = !a.c.Home && snap.Complete && expectedFP != "" && a.hostingBoxes > 0 && !a.unknownWriter && !a.keyFound
if a.c.Eligible {
if _, ok := geo.LookupAWSRegion(a.c.AWSRegion); !ok {
if _, said := noPointSaid.LoadOrStore(a.c.Code, true); !said {
log.Printf("console: region %q has no point in the shipped AWS region table (AWS region %q), so region guesses leave it out", a.c.Code, a.c.AWSRegion)
}
}
}
out = append(out, a.c)
}
sort.Slice(out, func(i, j int) bool { return out[i].Code < out[j].Code })
return out
}
// regionCands is regionCandidates with this console's home and expected
// secrets fingerprint.
func (s *Server) regionCands(snap edgesSnapshot) []regionCandidate {
return regionCandidates(snap, s.cfg.HomeRegion, s.expectedSecretsFP())
}
// candsOrHome is the candidates from a fresh snapshot, or the home alone
// when the listing fails: for displays and guesses that must not fail.
func (s *Server) candsOrHome(ctx context.Context) []regionCandidate {
snap, err := readEdgesSnapshot(ctx, s.cfg.Releases)
if err != nil {
return homeOnlyCandidates()
}
return s.regionCands(snap)
}
// majority picks the most common AWS region, ties to the lexically
// smallest, and logs a disagreement once per code.
func majority(code string, regions map[string]int) string {
best, n := "", 0
for r, c := range regions {
if c > n || (c == n && r < best) {
best, n = r, c
}
}
if len(regions) > 1 {
if _, said := disagreeSaid.LoadOrStore(code, true); !said {
log.Printf("console: boxes for region %q disagree on their AWS region %v; using %s", code, regions, best)
}
}
return best
}
func homeOnlyCandidates() []regionCandidate {
return []regionCandidate{{Code: "", Home: true, Reachable: true, Hosting: true, KeyOK: true, Eligible: true}}
}
func findCandidate(cands []regionCandidate, code string) (regionCandidate, bool) {
for _, c := range cands {
if c.Code == code {
return c, true
}
}
return regionCandidate{}, false
}
func eligibleCount(cands []regionCandidate) int {
n := 0
for _, c := range cands {
if c.Eligible {
n++
}
}
return n
}
type policyState int
const (
policyAbsent policyState = iota
policyKnown
policyUnreadable
)
// regionPolicy is what the manifest and the fleet placement say about
// where an instance may land (spec §6.1).
type regionPolicy struct {
State policyState
M *provision.Manifest
Placed string
}
// appPolicy reads the manifest directly (never instanceManifest, which
// answers the caller itself) and the fleet placement. Any failure is
// policyUnreadable: an unrestricted policy is never invented.
func (s *Server) appPolicy(ctx context.Context, acct *Account, app string) regionPolicy {
var p regionPolicy
m, err := provision.GetManifest(ctx, s.cfg.Releases, acct.Sqid, app)
switch {
case err == nil:
p.State, p.M = policyKnown, m
case errors.Is(err, os.ErrNotExist):
p.State = policyAbsent
default:
log.Printf("console: region policy %s/%s: read manifest: %v", acct.Sqid, app, err)
return regionPolicy{State: policyUnreadable}
}
reg, err := openRegistry(s.cfg.Registry)
if err != nil {
log.Printf("console: region policy %s/%s: open registry: %v", acct.Sqid, app, err)
return regionPolicy{State: policyUnreadable}
}
placed, err := s.appPlacement(reg, acct, app)
if err != nil {
log.Printf("console: region policy %s/%s: placement: %v", acct.Sqid, app, err)
return regionPolicy{State: policyUnreadable}
}
p.Placed = placed
return p
}
func (p regionPolicy) pooled() bool { return p.State == policyKnown && p.M.Pool != "" }
// allows is the door's own allow-list predicate on a record-vocabulary code.
func (p regionPolicy) allows(code string) bool {
switch p.State {
case policyKnown:
return instanceRegionAllowed(p.M.Regions, code)
case policyAbsent:
return true
}
return false
}The two diagnostics are O6 and O7 from the record. Neither carries anything about a caller.
- [ ] Step 5: Run the tests to verify they pass
Run: go test ./internal/console -run 'TestRegionCandidates|TestReadEdgesSnapshot|TestAppPolicyStates|TestNoKeyProven|TestCandidateLocation|TestEligibilityNeverCombinesBoxes|TestPlacements' -count=1 Expected: PASS. TestPlacements* still passes after the readEdgesFiles refactor.
Then a mutation check (codex plan-R3 4): make eligibility the region's union of facts. Give the accumulator four flags (any box hosting, serving the shared pool, accepting inbound, holding the key), and set a.c.Eligible from all four instead of a.c.Eligible || eligible. Run go test ./internal/console -run TestEligibilityNeverCombinesBoxes -count=1. It must FAIL on all three pairs. Revert, and re-run: PASS.
- [ ] Step 6: Commit
git add internal/console/instancesplacement.go internal/console/regioncandidates.go internal/console/regioncandidates_test.go
git commit -m "console: region candidates with positive per-box eligibility incl. the secrets key (nearest-region §6.1, R9)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"---
Task 11: The guess and its invariant
Files:
- Create:
internal/console/regionguess.go - Test:
internal/console/regionguess_test.go
Interfaces:
- Consumes: -
regionCandidate,findCandidateandhomeOnlyCandidates(Task 10); -instanceRegionAllowed; -geo.ZonePoint,geo.Nearest,geo.Candidate,geo.LookupAWSRegionandgeo.Locator. - Produces:
type guessInput struct{ TZ string; IP net.IP }
type guessResult struct{ Code, Source string } // Code: record vocabulary
func guessRegion(cands []regionCandidate, allows func(code string) bool, in guessInput, loc geo.Locator) (guessResult, bool)- [ ] Step 1: Write the failing tests
// internal/console/regionguess_test.go
package console
import (
"math/rand/v2"
"net"
"strings"
"testing"
"github.com/carlosframework/platform/internal/geo"
)
type fakeLocator map[string]geo.Point
func (f fakeLocator) Locate(ip net.IP) (geo.Point, bool) {
p, ok := f[ip.String()]
return p, ok
}
func allowAll(string) bool { return true }
func cand(code, aws string, eligible bool) regionCandidate {
return regionCandidate{Code: code, Home: code == "", AWSRegion: aws,
Reachable: true, Hosting: true, KeyOK: eligible, Eligible: eligible}
}
var (
cHome = cand("", "eu-west-1", true)
cUK = cand("uk", "eu-west-2", true)
cAU = cand("au", "ap-southeast-2", true)
)
func TestGuessRegionOrder(t *testing.T) {
loc := fakeLocator{"81.2.69.142": {Lat: -33.9, Lon: 151.2}}
cands := []regionCandidate{cHome, cUK, cAU}
cases := []struct {
name string
in guessInput
want guessResult
}{
{"timezone first", guessInput{TZ: "Europe/London", IP: net.ParseIP("81.2.69.142")}, guessResult{"uk", "timezone"}},
{"UTC falls to IP", guessInput{TZ: "Etc/UTC", IP: net.ParseIP("81.2.69.142")}, guessResult{"au", "ip"}},
{"nothing maps", guessInput{TZ: "Etc/UTC"}, guessResult{"", "default"}},
}
for _, c := range cases {
if got, ok := guessRegion(cands, allowAll, c.in, loc); !ok || got != c.want {
t.Errorf("%s: got (%+v, %v), want %+v", c.name, got, ok, c.want)
}
}
got, _ := guessRegion([]regionCandidate{cHome, cUK, cand("au", "ap-southeast-2", false)}, allowAll, guessInput{TZ: "Australia/Sydney"}, nil)
if got.Code == "au" {
t.Fatal("picked an ineligible region")
}
}
// spec §6.4, codex R3-4.
func TestGuessRegionNeverReturnsADisallowedHome(t *testing.T) {
noPointHome := cand("", "", true)
onlyUK := func(code string) bool { return instanceRegionAllowed([]string{"uk"}, code) }
cands := []regionCandidate{noPointHome, cUK}
if got, ok := guessRegion(cands, onlyUK, guessInput{TZ: "Europe/London"}, nil); !ok || got.Code != "uk" {
t.Fatalf("mapped hint = (%+v, %v), want uk", got, ok)
}
if got, ok := guessRegion(cands, onlyUK, guessInput{TZ: "Etc/UTC"}, nil); ok {
t.Fatalf("unmappable hint = %+v, want no guess", got)
}
if got, ok := guessRegion(homeOnlyCandidates(), onlyUK, guessInput{TZ: "Europe/London"}, nil); ok {
t.Fatalf("edges-read failure = %+v, want no guess", got)
}
}
// The invariant over generated MANIFESTS, using the door's own predicate
// on the normalized code (spec §16, codex plan-R1 10):
//
// 1. every non-null guess passes instanceRegionAllowed and is an eligible
// candidate;
// 2. a guess exists EXACTLY when the order of §6.4 can reach one: the home
// is a filtered candidate, or the timezone maps and some filtered
// candidate has a point.
func TestGuessRegionInvariantProperty(t *testing.T) {
codes := []string{"", "uk", "au", "us", "jp"}
listable := []string{"", "ie", "uk", "au", "us", "jp"}
regions := []string{"eu-west-1", "eu-west-2", "ap-southeast-2", "us-east-1", "", "xx-nowhere-1"}
zones := []string{"Europe/London", "Australia/Sydney", "America/New_York", "Etc/UTC", "", "Asia/Tokyo"}
r := rand.New(rand.NewPCG(1, 2))
for i := 0; i < 20000; i++ {
var cands []regionCandidate
for _, code := range codes {
if code != "" && r.IntN(3) == 0 {
continue
}
cands = append(cands, cand(code, regions[r.IntN(len(regions))], code == "" || r.IntN(4) != 0))
}
var list []string
for _, c := range listable {
if r.IntN(3) == 0 {
list = append(list, c)
}
}
allows := func(code string) bool { return instanceRegionAllowed(list, code) }
tz := zones[r.IntN(len(zones))]
got, ok := guessRegion(cands, allows, guessInput{TZ: tz}, nil)
homeIn, anyPoint := false, false
for _, c := range cands {
if !c.Eligible || !allows(c.Code) {
continue
}
homeIn = homeIn || c.Home
if _, has := geo.LookupAWSRegion(c.AWSRegion); has {
anyPoint = true
}
}
_, tzMaps := geo.ZonePoint(tz)
want := homeIn || (tzMaps && anyPoint)
if ok != want {
t.Fatalf("case %d: guess existence %v, want %v (list %v, tz %q, cands %+v)", i, ok, want, list, tz, cands)
}
if !ok {
continue
}
if !allows(got.Code) {
t.Fatalf("case %d: guess %+v fails the manifest %v", i, got, list)
}
if c, found := findCandidate(cands, got.Code); !found || !c.Eligible {
t.Fatalf("case %d: guess %+v is not an eligible candidate", i, got)
}
}
}
// Review Focus 4's core: a malformed hint is absent.
func TestGuessRegionToleratesGarbageHints(t *testing.T) {
got, ok := guessRegion([]regionCandidate{cHome, cUK}, allowAll, guessInput{TZ: strings.Repeat("a", 10<<10)}, fakeLocator{})
if !ok || got != (guessResult{"", "default"}) {
t.Fatalf("garbage tz = (%+v, %v), want the home default", got, ok)
}
}
// codex plan-R3 4: a region whose boxes only collectively have every fact
// is never guessed, however near the caller is (Task 10's splitCapability).
func TestGuessRegionSkipsSplitCapabilityRegions(t *testing.T) {
for name, boxes := range splitCapability {
cands := regionCandidates(views(t, homeBox, boxes[0], boxes[1]), "ie", testKeyFP)
got, ok := guessRegion(cands, allowAll, guessInput{TZ: "Europe/London"}, nil)
if !ok || got.Code != "" {
t.Errorf("%s: guess = %+v %v, want the home", name, got, ok)
}
}
}Under Task 10's union-of-facts mutation this test still passes, and that is expected: a candidate's location comes only from an eligible box (codex plan-R2 2), so a split region has no point to be guessed at. Task 10's TestEligibilityNeverCombinesBoxes is the test that mutation must fail.
The existence rule is exact. When the home is filtered, every path ends in a guess: the nearest pick, or the home default. Otherwise a guess needs a mapped timezone and a filtered candidate with a point, because there is no IP in this property. An IP-only variant is covered by TestGuessRegionOrder.
- [ ] Step 2: Run them to verify they fail
Run: go test ./internal/console -run TestGuessRegion -count=1 Expected: FAIL, undefined guessRegion.
- [ ] Step 3: Implement
regionguess.go
package console
import (
"net"
"github.com/carlosframework/platform/internal/geo"
)
// guessInput carries a request's location hints for ONE call. Neither
// field is ever stored, logged, audited or echoed (spec §6.5).
type guessInput struct {
TZ string
IP net.IP
}
// guessResult is a guess in the record vocabulary and its source word.
type guessResult struct{ Code, Source string }
// guessRegion is spec §6.4:
//
// 1. filter candidates to eligible ones the policy allows;
// 2. nearest among those with a point, from the timezone, else (only
// when the timezone is unmappable) the IP;
// 3. else the home, ONLY if the home is itself a filtered candidate;
// 4. else no guess.
func guessRegion(cands []regionCandidate, allows func(code string) bool, in guessInput, loc geo.Locator) (guessResult, bool) {
var pick []geo.Candidate
homeIn := false
for _, c := range cands {
if !c.Eligible || !allows(c.Code) {
continue
}
pick = append(pick, geo.Candidate{Code: c.Code, Home: c.Home, AWSRegion: c.AWSRegion})
homeIn = homeIn || c.Home
}
if len(pick) == 0 {
return guessResult{}, false
}
if p, ok := geo.ZonePoint(in.TZ); ok {
if code, ok := geo.Nearest(p, pick); ok {
return guessResult{code, "timezone"}, true
}
} else if loc != nil && in.IP != nil {
if p, ok := loc.Locate(in.IP); ok {
if code, ok := geo.Nearest(p, pick); ok {
return guessResult{code, "ip"}, true
}
}
}
if homeIn {
return guessResult{"", "default"}, true
}
return guessResult{}, false
}- [ ] Step 4: Run the tests to verify they pass
Run: go test ./internal/console -run TestGuessRegion -count=1 Expected: PASS.
- [ ] Step 5: Mutation checks, one at a time (codex plan-R2 9)
Each mutation must fail the property test on its own, against a passing baseline. So restore and re-run between them:
- Run
go test ./internal/console -run TestGuessRegionInvariantProperty -count=1. It must PASS; this is the baseline. - Mutation A: make
guessRegion's first linereturn guessResult{}, false. Run the same command. It must FAIL with "guess existence false, want true". Revert, and re-run: PASS. - Mutation B: change the final
if homeIn {toif true {. This is a home fallback the policy does not allow. Run the same command. It must FAIL with "guess existence true, want false". Revert, and re-run: PASS. git diff --statshows only this task's two files.
- [ ] Step 6: Commit
git add internal/console/regionguess.go internal/console/regionguess_test.go
git commit -m "console: the region guess, with existence and policy invariants property-tested (nearest-region §6.4)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"---
Task 12: claimApp resolution and the API create door
Files:
- Create:
internal/console/regionclaim.go - Modify:
internal/console/server.go-claimApp, at:3466-3567; - the browser call site at:3626, which passesregionReq{}until Task 13. - Modify:
internal/console/apicli.go:330-470(handleAPICreateApp) - Modify, adding the
regionReq{}argument: -internal/console/apps_claim_test.go:118,134,150,165; -internal/console/apicli_test.go:2504. - Test:
internal/console/regionclaim_test.go
Interfaces:
- Consumes: - Tasks 8 to 11; -
s.clientIP(authcli.go:876),principalFrom,placementsandhostingSet; -homeRegionInandhomeRegionOut; - the record's O8. - Produces:
type regionReq struct {
explicit string // a person's choice, record vocabulary
hasExplicit bool
tz string
ip net.IP // request-local, person requests only
}
func (s *Server) validateExplicitRegion(ctx context.Context, raw string) (code string, ref *doorRefusal)
type explicitGate struct{ caps map[string]placementCapability; published bool; cands []regionCandidate }
func (s *Server) readExplicitGate(ctx context.Context) explicitGate
func (g explicitGate) refusal(code string) string // "", unknown, not-armed, no-key
func (s *Server) explicitChoices(g explicitGate) []string
func (s *Server) resolveNewAppRegion(ctx context.Context, rq regionReq, adopting bool) (region, source string)
func (s *Server) placeOf(code string, cands []regionCandidate) string
func (s *Server) personIP(r *http.Request) net.IP
func (s *Server) claimApp(ctx context.Context, acct *Account, name, addr, placement string, ownerClaim bool, rq regionReq) (claimOutcome, string, *apexHold)handleAPICreateApp's response gains region, region_name, region_source and regions_available.
C18 and C19 list exactly the codes validateExplicitRegion would accept now, through one predicate, explicitGate.refusal: the home's name, plus each published code that is shape-valid, armed and hosting, and not proved keyless (R9). They never list a code the same door then refuses (codex plan-R4 2), and the test submits every listed code and expects it to pass.
- [ ] Step 1: Write the failing tests
// internal/console/regionclaim_test.go
package console
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"testing"
"time"
"github.com/carlosframework/platform/internal/geo"
"github.com/carlosframework/platform/internal/registry"
"github.com/carlosframework/platform/internal/svcrole"
)
func regionFixture(t *testing.T) (*consoleFixture, string) {
t.Helper()
fx := newConsoleFixture(t)
fx.srv.cfg.AppRegions, fx.srv.cfg.HomeRegion, fx.srv.cfg.SecretsPub = true, "ie", testKeyPub
setEdges(t, fx, map[string]string{"ie-box": homeBox, "uk-box": ukEligible})
return fx, cliToken(t, fx, "paul@keymail.dev", "code-region-claim")
}
func newAppURL(fx *consoleFixture) string { return fx.ts.URL + "/api/cli/apps/bab/new" }
// postJSONFrom is postJSON with an X-Forwarded-For, which the console
// trusts from the loopback edge (authcli.go clientIP).
func postJSONFrom(t *testing.T, u string, body any, bearer, xff string) (int, map[string]any) {
t.Helper()
raw, _ := json.Marshal(body)
req, err := http.NewRequest(http.MethodPost, u, bytes.NewReader(raw))
if err != nil {
t.Fatal(err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+bearer)
req.Header.Set("X-Forwarded-For", xff)
return doJSON(t, req)
}
func errOf(res map[string]any) string { s, _ := res["error"].(string); return s }
var ukPlace = geo.PlaceName("eu-west-2", "uk")
func TestAPICreateAppResolvesRegion(t *testing.T) {
fx, token := regionFixture(t)
status, res := postJSON(t, newAppURL(fx), map[string]string{"name": "blog", "timezone": "Europe/London"}, token)
if status != http.StatusOK || res["region"] != "uk" || res["region_source"] != "timezone" ||
res["region_name"] != ukPlace || res["regions_available"] != float64(2) {
t.Fatalf("create = %d %v", status, res)
}
if r, src, _ := fx.st.AppRegion(OpsAccountID, "blog"); r != "uk" || src != "timezone" {
t.Fatalf("stored %q %q", r, src)
}
// An old CLI's body, from loopback with no forwarded address.
if _, res = postJSON(t, newAppURL(fx), map[string]string{"name": "old"}, token); res["region"] != "ie" || res["region_source"] != "default" {
t.Fatalf("old body = %v", res)
}
// An explicit home name is a choice, stored as "".
postJSON(t, newAppURL(fx), map[string]string{"name": "athome", "region": "ie"}, token)
if r, src, _ := fx.st.AppRegion(OpsAccountID, "athome"); r != "" || src != "chosen" {
t.Fatalf("explicit home = %q %q", r, src)
}
}
// The old-body path through the real door: a person's forwarded address
// is located; a service principal's never is (spec §6.2).
func TestAPICreateAppLocatesOnlyAPerson(t *testing.T) {
fx, token := regionFixture(t)
fx.srv.cfg.GeoIP = fakeLocator{"81.2.69.142": {Lat: 51.5, Lon: -0.12}}
if status, res := postJSONFrom(t, newAppURL(fx), map[string]string{"name": "person"}, token, "81.2.69.142"); status != http.StatusOK ||
res["region"] != "uk" || res["region_source"] != "ip" {
t.Fatalf("person = %d %v", status, res)
}
svc := serviceToken(t, fx.st, OpsAccountID, "hub", "", svcrole.Publish)
if status, res := postJSONFrom(t, newAppURL(fx), map[string]string{"name": "hubapp"}, svc, "81.2.69.142"); status != http.StatusOK ||
res["region"] != "ie" || res["region_source"] != "default" {
t.Fatalf("service = %d %v, want no IP step", status, res)
}
}
func TestAPICreateAppRefusesBadExplicitRegions(t *testing.T) {
fx, token := regionFixture(t)
// nz cannot host; au's current-writer box lacks the key (R9).
auNoKey := edgeJSON("au-box", "9.9.9.8", "ap-southeast-2", "au", edgeOpts{hib: bp(true), shared: bp(true), inbound: bp(true)})
setEdges(t, fx, map[string]string{"nz-box": nzNoHosting, "au-box": auNoKey})
// codex plan-R4 2: each list names exactly what this door would take.
choices := []string{"ie", "uk"}
cases := []struct{ name, region, want string }{
{"xa", "Bad/Code", msgRegionUnknown("Bad/Code", choices)},
{"xb", "nz", msgRegionNotArmed("nz", choices)},
{"xc", "au", msgNoSecretsKey("au")},
}
for _, c := range cases {
status, res := postJSON(t, newAppURL(fx), map[string]string{"name": c.name, "region": c.region}, token)
if status != http.StatusBadRequest || errOf(res) != c.want {
t.Errorf("region %q = %d %q, want 400 %q", c.region, status, errOf(res), c.want)
}
if has, _ := fx.st.HasAppIn(OpsAccountID, c.name); has {
t.Errorf("a refused region still claimed %s", c.name)
}
}
// Every advertised choice is accepted by the same door, same fixture.
for i, code := range choices {
name := fmt.Sprintf("alt%d", i)
if status, res := postJSON(t, newAppURL(fx), map[string]string{"name": name, "region": code}, token); status != http.StatusOK {
t.Errorf("advertised %q refused: %d %v", code, status, res)
}
}
// R9: a region whose current-writer boxes lack the key.
setEdges(t, fx, map[string]string{"uk-box": ukNoKey})
if status, res := postJSON(t, newAppURL(fx), map[string]string{"name": "xd", "region": "uk"}, token); status != http.StatusBadRequest || errOf(res) != msgNoSecretsKey("uk") {
t.Fatalf("keyless uk = %d %v, want C31", status, res)
}
}
func TestAPICreateAppSwitchOffStoresTheHome(t *testing.T) {
fx, token := regionFixture(t)
fx.srv.cfg.AppRegions = false
_, res := postJSON(t, newAppURL(fx), map[string]string{"name": "off", "region": "uk", "timezone": "Europe/London"}, token)
if r, src, _ := fx.st.AppRegion(OpsAccountID, "off"); r != "" || src != "default" || res["region"] != "ie" {
t.Fatalf("switch off = %q %q %v", r, src, res)
}
fx.srv.cfg.AppRegions, fx.srv.cfg.HomeRegion = true, ""
postJSON(t, newAppURL(fx), map[string]string{"name": "unnamed", "region": "uk"}, token)
if r, _, _ := fx.st.AppRegion(OpsAccountID, "unnamed"); r != "" {
t.Fatalf("unnamed home stored %q", r)
}
}
func TestClaimAdoptionDiscardsTheGuess(t *testing.T) {
fx, _ := regionFixture(t)
acct := &Account{ID: OpsAccountID, Sqid: "bab"}
seedBucketApp(t, fx, "bab", "oldsite")
if out, _, _ := fx.srv.claimApp(context.Background(), acct, "oldsite", "paul@keymail.dev", "", true, regionReq{tz: "Europe/London"}); out != claimOK {
t.Fatalf("adoption = %v", out)
}
if r, src, _ := fx.st.AppRegion(OpsAccountID, "oldsite"); r != "" || src != "default" {
t.Fatalf("adoption with a guess = %q %q", r, src)
}
seedBucketApp(t, fx, "bab", "oldsite2")
fx.srv.claimApp(context.Background(), acct, "oldsite2", "paul@keymail.dev", "", true, regionReq{explicit: "uk", hasExplicit: true})
if r, src, _ := fx.st.AppRegion(OpsAccountID, "oldsite2"); r != "uk" || src != "chosen" {
t.Fatalf("adoption with an explicit choice = %q %q", r, src)
}
}
// codex plan-R6 1, spec §6.5: every successful creation logs exactly one
// region line, naming what was stored, whatever the switch says: on, off,
// and on with an unnamed home (which keeps assignment off).
func TestClaimLogsOneRegionLineInEverySwitchState(t *testing.T) {
for _, c := range []struct {
name, app, home string
on bool
stored, source string
line string
}{
{"assignment on", "on1", "ie", true, "uk", "chosen", "console: app region bab/on1: uk (chosen)"},
{"assignment off", "off1", "ie", false, "", "default", "console: app region bab/off1: ie (default)"},
{"on, home unnamed", "unnamed1", "", true, "", "default", "console: app region bab/unnamed1: (default)"},
} {
fx, token := regionFixture(t)
fx.srv.cfg.AppRegions, fx.srv.cfg.HomeRegion = c.on, c.home
var logs bytes.Buffer
restore := captureLog(&logs)
status, res := postJSON(t, newAppURL(fx), map[string]string{"name": c.app, "region": "uk"}, token)
restore()
if status != http.StatusOK {
t.Fatalf("%s: create = %d %v", c.name, status, res)
}
if r, src, _ := fx.st.AppRegion(OpsAccountID, c.app); r != c.stored || src != c.source {
t.Fatalf("%s: stored %q %q, want %q %q", c.name, r, src, c.stored, c.source)
}
if n := strings.Count(logs.String(), "console: app region bab/"+c.app+":"); n != 1 || !strings.Contains(logs.String(), c.line+"\n") {
t.Errorf("%s: %d region line(s) in %q, want exactly %q", c.name, n, logs.String(), c.line)
}
}
}
// Review Focus 5: a competing insert that lands AFTER the region was
// resolved loses the claim: no row of ours, no region log line.
func TestClaimLostAfterResolutionWritesNothing(t *testing.T) {
fx, token := regionFixture(t)
fx.srv.beforeClaimInsert = func() {
if _, err := fx.st.CreateAppInRegion(OpsAccountID, "racy", "rival@k.dev", "", "chosen", time.Now()); err != nil {
t.Error(err)
}
}
var logs bytes.Buffer
restore := captureLog(&logs)
status, _ := postJSON(t, newAppURL(fx), map[string]string{"name": "racy", "region": "uk"}, token)
restore()
if status != http.StatusConflict {
t.Fatalf("lost race = %d, want 409", status)
}
if r, src, _ := fx.st.AppRegion(OpsAccountID, "racy"); r != "" || src != "chosen" {
t.Fatalf("the rival's row changed: %q %q", r, src)
}
if strings.Contains(logs.String(), "app region bab/racy") {
t.Fatalf("a lost claim logged a region line: %s", logs.String())
}
}
// Review Focus 5: a held apex refuses before resolution, so nothing is
// written or logged.
func TestClaimHeldApexWritesNothing(t *testing.T) {
fx, token := regionFixture(t)
fx.srv.cfg.AppsDomain = "oncarlos.test"
fx.addRoute(t, registry.Route{Host: "beta." + fx.sqid + ".oncarlos.test", App: "kass", Account: fx.sqid, Kind: "instance"})
var logs bytes.Buffer
restore := captureLog(&logs)
status, _ := postJSON(t, newAppURL(fx), map[string]string{"name": "beta", "timezone": "Europe/London"}, token)
restore()
if status != http.StatusConflict {
t.Fatalf("held apex = %d, want 409", status)
}
if has, _ := fx.st.HasAppIn(OpsAccountID, "beta"); has {
t.Fatal("beta was created")
}
if strings.Contains(logs.String(), "app region bab/beta") {
t.Fatalf("a refused claim logged a region line: %s", logs.String())
}
}fx.sqid is the fixture account's sqid, bab. fx.addRoute is apex_test.go's helper. doJSON is authcli_test.go's. serviceToken is svcfixture_test.go:14.
- [ ] Step 2: Run them to verify they fail
Run: go test ./internal/console -run 'TestAPICreateApp|TestClaim' -count=1 Expected: FAIL to compile: claimApp has the wrong arity, and regionReq is undefined.
- [ ] Step 3: Implement
regionclaim.go
package console
import (
"context"
"net"
"net/http"
"sort"
"strings"
"github.com/carlosframework/platform/internal/geo"
"github.com/carlosframework/platform/internal/provision"
)
// regionReq is what a create door hands claimApp: never a resolved region.
// The guess is resolved INSIDE claimApp, after it knows whether this is an
// adoption (spec §5.5). ip is request-local and only set for a person.
type regionReq struct {
explicit string
hasExplicit bool
tz string
ip net.IP
}
// validateExplicitRegion checks a person's explicit region as the instance
// door checks one (spec §8.1): ValidRegion; armed and hosting unless the
// gate is dormant; not provably without the secrets key (R9). The home's
// name normalizes to "". A refusal lists exactly the codes this same check
// would accept now (codex plan-R4 2).
func (s *Server) validateExplicitRegion(ctx context.Context, raw string) (string, *doorRefusal) {
code := s.homeRegionIn(strings.TrimSpace(raw))
if code == "" {
return "", nil
}
gate := s.readExplicitGate(ctx)
switch gate.refusal(code) {
case "":
return code, nil
case "unknown":
return "", &doorRefusal{http.StatusBadRequest, msgRegionUnknown(code, s.explicitChoices(gate))}
case "not-armed":
return "", &doorRefusal{http.StatusBadRequest, msgRegionNotArmed(code, s.explicitChoices(gate))}
}
return "", &doorRefusal{http.StatusBadRequest, msgNoSecretsKey(code)}
}
// explicitGate is one read of what an explicit app region must pass.
type explicitGate struct {
caps map[string]placementCapability
published bool
cands []regionCandidate // nil when the edges read failed: R9 cannot refuse
}
func (s *Server) readExplicitGate(ctx context.Context) explicitGate {
g := explicitGate{}
g.caps, g.published = placements(ctx, s.cfg.Releases)
if snap, err := readEdgesSnapshot(ctx, s.cfg.Releases); err == nil {
g.cands = s.regionCands(snap)
}
return g
}
// refusal is "" when an explicit, record-vocabulary code passes, else
// which bar it fails: "unknown", "not-armed" or "no-key".
func (g explicitGate) refusal(code string) string {
switch {
case code == "":
return ""
case !provision.ValidRegion(code):
return "unknown"
case g.published && !(g.caps[code].Reachable && g.caps[code].Hosting):
return "not-armed"
}
if c, ok := findCandidate(g.cands, code); ok && c.NoKeyProven {
return "no-key"
}
return ""
}
// explicitChoices is the home's name plus every published code that
// refusal passes, sorted: never a code this door would refuse.
func (s *Server) explicitChoices(g explicitGate) []string {
set := map[string]bool{}
if s.cfg.HomeRegion != "" {
set[s.cfg.HomeRegion] = true
}
for code := range g.caps {
if c := s.homeRegionIn(code); c != "" && g.refusal(c) == "" {
set[c] = true
}
}
out := make([]string, 0, len(set))
for c := range set {
out = append(out, c)
}
sort.Strings(out)
return out
}
// resolveNewAppRegion is spec §5.5 and §5.6 for a NEW row.
func (s *Server) resolveNewAppRegion(ctx context.Context, rq regionReq, adopting bool) (region, source string) {
if !s.appRegionsOn() {
return "", "default"
}
if rq.hasExplicit {
return rq.explicit, "chosen"
}
if adopting {
return "", "default"
}
files, err := readEdgesSnapshot(ctx, s.cfg.Releases)
if err != nil {
return "", "default"
}
g, ok := guessRegion(s.regionCands(files), func(string) bool { return true },
guessInput{TZ: rq.tz, IP: rq.ip}, s.cfg.GeoIP)
if !ok {
return "", "default"
}
return g.Code, g.Source
}
// placeOf names a record-vocabulary code: its AWS region's place, else the
// code under its name.
func (s *Server) placeOf(code string, cands []regionCandidate) string {
c, _ := findCandidate(cands, code)
return geo.PlaceName(c.AWSRegion, s.homeRegionOut(code))
}
// personIP is the request's own address, for a person only (spec §6.2).
// A service or app principal is a server; its address says nothing about
// where a person is.
func (s *Server) personIP(r *http.Request) net.IP {
if p, ok := principalFrom(r.Context()); ok && p.Kind != PrincipalUser {
return nil
}
return net.ParseIP(s.clientIP(r))
}personIP treats "no principal in the context" as a person. That is the browser door, where the session is a person by construction.
- [ ] Step 4: Thread
regionReqthroughclaimApp
Change the signature, and replace the insert at server.go:3550:
func (s *Server) claimApp(ctx context.Context, acct *Account, name, addr, placement string, ownerClaim bool, rq regionReq) (outcome claimOutcome, placed string, held *apexHold) { region, source := s.resolveNewAppRegion(ctx, rq, adoptingExistingApp)
if s.beforeClaimInsert != nil {
s.beforeClaimInsert()
}
created, err := s.store.CreateAppInRegion(acct.ID, name, addr, region, source, time.Now())
if err != nil {
return claimCreateFailed, "", nil
}
if !created { // lost a claim race after the checks
return claimTaken, "", nil
}
// Spec §6.5: one line per creation, in every switch state (codex
// plan-R6 1). Only reached once the row is ours.
log.Printf("console: app region %s/%s: %s (%s)", acct.Sqid, name, s.homeRegionOut(region), source)The log text is O8. It is logged for every successful creation, with the switch on or off and with the home named or not, exactly as spec §6.5 says: one line per creation, naming what was stored. With an unnamed home the code is the record's "", so the line reads bab/app: (default). A claim that loses its race or is refused logs nothing, because the line comes after both checks. TestClaimLogsOneRegionLineInEverySwitchState pins all three states. Add , regionReq{} to every existing call: server.go:3626, apps_claim_test.go:118,134,150,165 and apicli_test.go:2504.
- [ ] Step 5: Extend
handleAPICreateApp
Add the request fields:
Region string `json:"region"`
Timezone string `json:"timezone"`After the place validation, before ownerClaim:
rq := regionReq{tz: req.Timezone, ip: s.personIP(r)}
if strings.TrimSpace(req.Region) != "" {
code, ref := s.validateExplicitRegion(r.Context(), req.Region)
if ref != nil {
jsonError(w, ref.Status, ref.Msg)
return
}
rq.explicit, rq.hasExplicit = code, true
}Pass rq to claimApp. In claimOK:
gotRegion, gotSource, err := s.store.AppRegion(acct.ID, name)
if err != nil {
log.Printf("console: claim %s/%s: read back region: %v", acct.Sqid, name, err)
}
files, ferr := readEdgesSnapshot(r.Context(), s.cfg.Releases)
cands := homeOnlyCandidates()
if ferr == nil {
cands = s.regionCands(files)
}Add to the response map:
"region": s.homeRegionOut(gotRegion),
"region_name": s.placeOf(gotRegion, cands),
"region_source": gotSource,
"regions_available": eligibleCount(cands),- [ ] Step 6: Run the tests to verify they pass
Run: go test ./internal/console -count=1 Expected: PASS, including every existing claim test.
Then the log check (codex plan-R6 1): wrap the log.Printf back in if s.appRegionsOn() { … }, and run go test ./internal/console -run TestClaimLogsOneRegionLineInEverySwitchState -count=1. It must FAIL on assignment off and on, home unnamed (0 lines). Revert, and re-run: PASS.
Then a mutation check (codex plan-R4 2): in explicitChoices, also accept codes whose refusal is no-key. Run go test ./internal/console -run TestAPICreateAppRefusesBadExplicitRegions -count=1. It must FAIL (au is listed). Revert, and re-run: PASS.
- [ ] Step 7: Commit
git add internal/console/regionclaim.go internal/console/regionclaim_test.go internal/console/server.go internal/console/apicli.go internal/console/apps_claim_test.go internal/console/apicli_test.go
git commit -m "console: resolve the app region inside claimApp; API create takes region and timezone (nearest-region §5.5, §8.1, §9.1, R9)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"---
Task 13: The new-app page picker, POST and browser guess route
Files:
- Create:
internal/console/regionpicker.go - Modify:
internal/console/server.go- thepagestruct, at:1350; -handleNewAppForm, at:3586; -handleNewApp, at:3607; - the routes, at:1152-1153. - Modify:
internal/console/templates/app_new.html - Test:
internal/console/regionpicker_test.go
Interfaces:
- Consumes: Tasks 6, 10, 11 and 12.
- Produces:
const autoRegion = "@auto"
type regionOption struct{ Value, Label string }
type regionPickerView struct{ Auto string; Options []regionOption; Attribution bool }
func (s *Server) newAppPicker(ctx context.Context, ip net.IP) *regionPickerView // nil = no picker
func (s *Server) pickedRegion(ctx context.Context, r *http.Request) (regionReq, bool)
func (s *Server) handleNewAppRegion(w http.ResponseWriter, r *http.Request)The page struct gains the field RegionPicker *regionPickerView.
- [ ] Step 1: Write the failing tests
html/template writes ' as ', so every assertion on rendered copy compares the escaped form, both positive and negative (codex plan-R1 8).
// internal/console/regionpicker_test.go
package console
import (
"encoding/json"
"html"
"net/http"
"net/url"
"regexp"
"strings"
"testing"
)
func has(body, plain string) bool { return strings.Contains(body, html.EscapeString(plain)) }
// newAppFormGolden is the base tree's own rendering of the create-app form
// (the branch base, switch off), with the session's CSRF value blanked.
const newAppFormGolden = `<form class="signin" method="post" action="/a/bab/apps/new">
<input type="hidden" name="csrf" value="CSRF">
<label class="visually-hidden" for="app-name">App name</label>
<input id="app-name" type="text" name="name" placeholder="my-app" autofocus
required pattern="[A-Za-z][A-Za-z0-9-]{0,31}" maxlength="32">
<button type="submit">Create app</button>
</form>`
// newAppForm is the rendered <form>…</form>, CSRF value blanked.
func newAppForm(t *testing.T, body string) string {
t.Helper()
i := strings.Index(body, `<form class="signin"`)
if i < 0 {
t.Fatalf("no create-app form in %s", body)
}
j := strings.Index(body[i:], "</form>") + i + len("</form>")
return regexp.MustCompile(`name="csrf" value="[^"]*"`).ReplaceAllString(body[i:j], `name="csrf" value="CSRF"`)
}
// codex plan-R4 3, spec §10 and §16: whenever there is no picker, the form
// is today's form byte for byte: with the switch off, and with the switch
// on but only one eligible region.
func TestNewAppPageWithoutPickerIsUnchanged(t *testing.T) {
off := newConsoleFixture(t)
one, _ := regionFixture(t)
setEdges(t, one, map[string]string{"uk-box": ukNoKey}) // the home alone is eligible
for name, fx := range map[string]*consoleFixture{"switch off": off, "one eligible region": one} {
_, _, body := get(t, signInPaul(t, fx), fx.u("/apps/new"))
if got := newAppForm(t, body); got != newAppFormGolden {
t.Errorf("%s: form =\n%q\nwant\n%q", name, got, newAppFormGolden)
}
}
}
func TestNewAppPickerAndAutoChoice(t *testing.T) {
fx, _ := regionFixture(t)
c, csrf := signInAs(t, fx, "paul@keymail.dev", "code-picker")
_, _, body := get(t, c, fx.u("/apps/new"))
for _, want := range []string{`name="region"`, `value="@auto"`, `value="uk"`, `value="ie"`} {
if !strings.Contains(body, want) {
t.Fatalf("picker missing %q", want)
}
}
if !has(body, ukPlace) || !has(body, regionCopy.PickerLabel) || !has(body, regionCopy.PickerHelp) {
t.Fatal("picker missing its copy")
}
if status, _, _ := postForm(t, c, fx.u("/apps/new"), url.Values{"csrf": {csrf}, "name": {"auto1"}, "region": {"@auto"}, "tz": {"Europe/London"}}); status != http.StatusSeeOther {
t.Fatalf("@auto post = %d", status)
}
if r, src, _ := fx.st.AppRegion(OpsAccountID, "auto1"); r != "uk" || src != "timezone" {
t.Fatalf("@auto = %q %q", r, src)
}
postForm(t, c, fx.u("/apps/new"), url.Values{"csrf": {csrf}, "name": {"chose"}, "region": {"ie"}, "tz": {"Europe/London"}})
if r, src, _ := fx.st.AppRegion(OpsAccountID, "chose"); r != "" || src != "chosen" {
t.Fatalf("explicit ie = %q %q", r, src)
}
}
// codex R2-3: a home NAMED "auto" is a region, not the sentinel.
func TestRegionNamedAutoIsNotTheSentinel(t *testing.T) {
fx, _ := regionFixture(t)
fx.srv.cfg.HomeRegion = "auto"
c, csrf := signInAs(t, fx, "paul@keymail.dev", "code-auto-name")
postForm(t, c, fx.u("/apps/new"), url.Values{"csrf": {csrf}, "name": {"homeauto"}, "region": {"auto"}})
if r, src, _ := fx.st.AppRegion(OpsAccountID, "homeauto"); r != "" || src != "chosen" {
t.Fatalf("explicit home named auto = %q %q", r, src)
}
}
// Review Focus 3: uk's box loses the secrets key between render and POST.
func TestNewAppPostWithANoLongerEligibleRegion(t *testing.T) {
fx, _ := regionFixture(t)
c, csrf := signInAs(t, fx, "paul@keymail.dev", "code-stale")
if _, _, body := get(t, c, fx.u("/apps/new")); !strings.Contains(body, `value="uk"`) {
t.Fatal("precondition: uk offered")
}
setEdges(t, fx, map[string]string{"uk-box": ukNoKey})
status, loc, _ := postForm(t, c, fx.u("/apps/new"), url.Values{"csrf": {csrf}, "name": {"stale"}, "region": {"uk"}})
if status != http.StatusSeeOther || loc != fx.path("/apps/new") {
t.Fatalf("stale region = %d %q, want back to the form", status, loc)
}
if _, _, body := get(t, c, fx.u("/apps/new")); !has(body, regionCopy.Unavailable) {
t.Fatal("no C5 flash")
}
if exists, _ := fx.st.HasAppIn(OpsAccountID, "stale"); exists {
t.Fatal("a refused region still created the app")
}
}
func TestNewAppRegionRouteNeedsSessionAndCSRF(t *testing.T) {
fx, _ := regionFixture(t)
c, csrf := signInAs(t, fx, "paul@keymail.dev", "code-route")
status, _, body := postForm(t, c, fx.u("/apps/new/region"), url.Values{"csrf": {csrf}, "tz": {"Europe/London"}})
var res map[string]string
if err := json.Unmarshal([]byte(body), &res); err != nil {
t.Fatalf("guess route body %q: %v", body, err)
}
if status != http.StatusOK || res["code"] != "uk" || res["source"] != "timezone" || res["name"] != ukPlace {
t.Fatalf("guess route = %d %v", status, res)
}
if status, _, _ := postForm(t, c, fx.u("/apps/new/region"), url.Values{"csrf": {"wrong"}, "tz": {"Europe/London"}}); status != http.StatusForbidden {
t.Fatalf("bad csrf = %d, want 403", status)
}
if status, _, _ := postForm(t, newBrowser(t), fx.u("/apps/new/region"), url.Values{"tz": {"Europe/London"}}); status == http.StatusOK {
t.Fatal("no session got a guess")
}
}signInPaul (server_test.go:450) returns just the client. signInAs (svcpage_test.go:59) also returns the CSRF token.
- [ ] Step 2: Run them to verify they fail
Run: go test ./internal/console -run 'TestNewApp|TestRegionNamedAuto' -count=1 Expected: FAIL.
- [ ] Step 3: Implement
regionpicker.go
package console
import (
"context"
"crypto/subtle"
"fmt"
"net"
"net/http"
"strings"
)
// autoRegion is the picker's "let the server guess" value. "@" is outside
// the placement-code grammar, so no real region collides with it (codex
// R2-3).
const autoRegion = "@auto"
type regionOption struct{ Value, Label string }
type regionPickerView struct {
Auto string // the @auto option's label (C3)
Options []regionOption
Attribution bool // C24, when the IP step is configured
}
// newAppPicker builds the picker, or nil: switch off, an edges read
// failure, or fewer than two eligible regions (§10). The server's own
// guess is IP, then home; the page script relabels @auto with the
// browser's timezone guess.
func (s *Server) newAppPicker(ctx context.Context, ip net.IP) *regionPickerView {
if !s.appRegionsOn() {
return nil
}
files, err := readEdgesSnapshot(ctx, s.cfg.Releases)
if err != nil {
return nil
}
cands := s.regionCands(files)
if eligibleCount(cands) < 2 {
return nil
}
g, _ := guessRegion(cands, func(string) bool { return true }, guessInput{IP: ip}, s.cfg.GeoIP)
v := ®ionPickerView{
Auto: fmt.Sprintf(regionCopy.AutoOption, s.placeOf(g.Code, cands)),
Attribution: s.cfg.GeoIP != nil,
}
for _, c := range cands {
if c.Eligible {
v.Options = append(v.Options, regionOption{Value: s.homeRegionOut(c.Code), Label: s.placeOf(c.Code, cands)})
}
}
return v
}
// pickedRegion turns the form's region field into a regionReq. ok=false
// means the submitted code is not an eligible region NOW (C5).
func (s *Server) pickedRegion(ctx context.Context, r *http.Request) (regionReq, bool) {
rq := regionReq{tz: r.FormValue("tz"), ip: s.personIP(r)}
v := strings.TrimSpace(r.FormValue("region"))
if v == "" || v == autoRegion {
return rq, true
}
files, err := readEdgesSnapshot(ctx, s.cfg.Releases)
if err != nil {
return rq, false
}
code := s.homeRegionIn(v)
c, found := findCandidate(s.regionCands(files), code)
if !found || !c.Eligible {
return rq, false
}
rq.explicit, rq.hasExplicit = code, true
return rq, true
}
// handleNewAppRegion is the page script's guess (spec §9.5): POST with csrf
// and tz, session-gated, writing nothing.
func (s *Server) handleNewAppRegion(w http.ResponseWriter, r *http.Request) {
_, raw, _, _, ok := s.account(w, r)
if !ok {
return
}
if subtle.ConstantTimeCompare([]byte(r.FormValue("csrf")), []byte(raw)) != 1 {
jsonError(w, http.StatusForbidden, "Cross-site request blocked.")
return
}
files, err := readEdgesSnapshot(r.Context(), s.cfg.Releases)
cands := homeOnlyCandidates()
if err == nil {
cands = s.regionCands(files)
}
g, ok := guessRegion(cands, func(string) bool { return true }, guessInput{TZ: r.FormValue("tz"), IP: s.personIP(r)}, s.cfg.GeoIP)
if !ok {
g = guessResult{"", "default"}
}
writeJSON(w, http.StatusOK, map[string]string{
"code": s.homeRegionOut(g.Code), "name": s.placeOf(g.Code, cands), "source": g.Source,
})
}The CSRF refusal reuses handleNewApp's existing text, which is existing copy and is not new.
- [ ] Step 4: Wire the routes, the GET and the POST
Beside the apps/new routes:
s.mux.HandleFunc("POST /a/{sqid}/apps/new/region", s.handleNewAppRegion)In page, add RegionPicker *regionPickerView. In handleNewAppForm, before render:
p.RegionPicker = s.newAppPicker(r.Context(), s.personIP(r))In handleNewApp, after the CSRF check and the name read:
rq, regionOK := s.pickedRegion(r.Context(), r)
if !regionOK {
fail(regionCopy.Unavailable)
return
}
outcome, _, held := s.claimApp(r.Context(), acct, name, addr, "", role == RoleOwner, rq)In claimOK, replace the audit line:
detail := "created app " + name
if s.appRegionsOn() {
region, _, _ := s.store.AppRegion(acct.ID, name)
files, _ := readEdgesSnapshot(r.Context(), s.cfg.Releases)
detail = fmt.Sprintf(regionCopy.AuditCreated, name, s.placeOf(region, s.regionCands(files)))
}
s.audit(acct, name, addr, "create", detail)- [ ] Step 5: Add the picker to
app_new.html, leaving the no-picker form byte-identical
Replace the last line of the name <input> and the button line (two lines, app_new.html:13-14) with the block below. The {{with}} opens on the input's own line and {{end}} closes on the script's, so with no picker the template emits exactly today's bytes: no blank line, no extra spaces (codex plan-R4 3; spec §10, §16). TestNewAppPageWithoutPickerIsUnchanged compares the rendered form with the base tree's own rendering, with the switch off and with the switch on but one eligible region.
required pattern="[A-Za-z][A-Za-z0-9-]{0,31}" maxlength="32">{{with .RegionPicker}}
<label for="app-region">{{$.RegionCopy.PickerLabel}}</label>
<select id="app-region" name="region">
<option value="@auto" selected>{{.Auto}}</option>
{{range .Options}}<option value="{{.Value}}">{{.Label}}</option>{{end}}
</select>
<input type="hidden" name="tz" value="">
<p class="small">{{$.RegionCopy.PickerHelp}}</p>
{{if .Attribution}}<p class="small"><a href="https://db-ip.com">{{$.RegionCopy.Attribution}}</a></p>{{end}}
<script>
// Enhancement only: with JS off the server's IP-or-home guess stands.
// Fills the tz hint and relabels ONLY the @auto option; its value stays
// "@auto" so the server still knows the person did not choose.
(function () {
var f = document.currentScript.closest("form"), sel = f.elements.region, tz = "";
var label = {{printf $.RegionCopy.AutoOption "%PLACE%"}};
try { tz = Intl.DateTimeFormat().resolvedOptions().timeZone || ""; } catch (e) {}
f.elements.tz.value = tz;
if (!tz) return;
var body = new URLSearchParams({ csrf: f.elements.csrf.value, tz: tz });
fetch("{{$.Base}}/apps/new/region", { method: "POST", body: body, credentials: "same-origin" })
.then(function (r) { return r.ok ? r.json() : null; })
.then(function (g) {
if (!g || sel.value !== "@auto") return;
sel.options[0].text = label.replace("%PLACE%", g.name);
})
.catch(function () {});
})();
</script>{{end}}
<button type="submit">Create app</button>html/template renders the {{printf …}} in script context as a JS string literal, so the approved C3 shape is used whatever its wording.
- [ ] Step 6: Run the tests to verify they pass
Run: go test ./internal/console -count=1 Expected: PASS.
Then a mutation check: put the block on its own lines instead ( {{with .RegionPicker}} after the input, {{end}} before the button). TestNewAppPageWithoutPickerIsUnchanged must FAIL in both cases (an extra " \n"). Restore, and re-run: PASS.
- [ ] Step 7: Commit
git add internal/console/regionpicker.go internal/console/regionpicker_test.go internal/console/server.go internal/console/templates/app_new.html
git commit -m "console: new-app region picker with @auto, browser guess route (nearest-region §10, §9.5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"---
Task 14: The instance create door: fill, eligibility, the R9 refusal and the response
Files:
- Modify:
internal/console/apiinstances.go-apiRecordView, at:535; -:763-799, where the fill goes; - the armed gate, at:827-852; -:1066, where the response is written. - Create:
internal/console/regionchoices.go(instanceChoices) - Test:
internal/console/regionfill_test.go
Interfaces:
- Consumes: Tasks 7, 9 (
s.appRegion) and 10 (s.regionCands,findCandidate,NoKeyProven). - Produces:
apiRecordView.RegionFrom string(json:"region_from,omitempty") andapiRecordView.RegionName string(json:"region_name,omitempty"), andfunc (s *Server) instanceChoices(ctx context.Context, allowed []string, cands []regionCandidate, except string) []string.
C16 and C17 list what an EXPLICIT request for the same socket-backed instance would pass now, not what automatic eligibility would pick (codex plan-R4 2): the manifest's allow-list as the door reads it (every code normalized through homeRegionIn first, so a home NAME in the allow-list counts as the home only if "" is allowed), the armed gate's reachable and hosting bars, and R9's refusal. The refused region itself is left out. When nothing passes, the message is C16b or C17b.
- [ ] Step 1: Write the failing tests
Every test that inspects a record asserts the create returned 201 first, so a refusal cannot pass as a non-fill (codex plan-R1 11).
// internal/console/regionfill_test.go
package console
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"regexp"
"strings"
"testing"
"github.com/carlosframework/platform/internal/provision"
)
type fillRec struct {
provision.Record
RegionFrom string `json:"region_from"`
RegionName string `json:"region_name"`
}
// fillFixture: titogo with an unrestricted shared-pool manifest, a stored
// app region, a named home, the deployment's secrets key, and two
// eligible boxes. fx.st.db is the Store's own *sql.DB (same package).
func fillFixture(t *testing.T, appRegion string) (*consoleFixture, string, string) {
t.Helper()
fx, token := instanceFixture(t, "code-fill-"+appRegion)
fx.srv.cfg.HomeRegion, fx.srv.cfg.SecretsPub = "ie", testKeyPub
m := titoManifest()
m.Pool, m.Regions = "", nil
enableInstances(t, fx, "titogo", m)
if _, err := fx.st.db.Exec(`UPDATE apps SET region = ?, region_source = 'chosen' WHERE app = 'titogo'`, appRegion); err != nil {
t.Fatal(err)
}
setEdges(t, fx, map[string]string{"ie-box": homeBox, "uk-box": ukEligible})
return fx, token, instancesURL(fx, "bab", "titogo")
}
func create(t *testing.T, u, token, body string) (int, fillRec, string) {
t.Helper()
status, _, out := rawReq(t, http.MethodPost, u, token, body)
var rec fillRec
json.Unmarshal([]byte(out), &rec)
return status, rec, out
}
func mustCreate(t *testing.T, u, token, body string) fillRec {
t.Helper()
status, rec, out := create(t, u, token, body)
if status != http.StatusCreated {
t.Fatalf("create %s = %d %s, want 201", body, status, out)
}
return rec
}
func TestFillFromTheAppRegion(t *testing.T) {
_, token, u := fillFixture(t, "uk")
if rec := mustCreate(t, u, token, `{"host":"a.go.tito.io"}`); rec.Region != "uk" || rec.RegionFrom != "app" || rec.RegionName != ukPlace {
t.Fatalf("fill = %+v", rec)
}
if rec := mustCreate(t, u, token, `{"host":"b.go.tito.io","region":"ie"}`); rec.Region != "" || rec.RegionFrom != "request" {
t.Fatalf("explicit home = %+v", rec)
}
for _, body := range []string{
`{"host":"c.go.tito.io","kind":"static"}`,
`{"host":"d.go.tito.io","kind":"service","addrs":["10.0.0.4:8080"]}`,
} {
if rec := mustCreate(t, u, token, body); rec.Region != "" {
t.Errorf("%s was filled: %+v", body, rec)
}
}
status, _, out := create(t, u, token, `{"host":"e.go.tito.io","role":"replica"}`)
if status != http.StatusBadRequest || !strings.Contains(out, "role replica needs a placement") {
t.Errorf("replica without a region = %d %s, want the existing refusal", status, out)
}
}
func TestFillWithAHomeAppRegionChangesNothing(t *testing.T) {
_, token, u := fillFixture(t, "")
if rec := mustCreate(t, u, token, `{"host":"a.go.tito.io"}`); rec.Region != "" || rec.RegionFrom != "" {
t.Fatalf("home app = %+v", rec)
}
}
// Precedence with a NON-HOME app default in play: pool and fleet win.
func TestFillPrecedence(t *testing.T) {
fx, token, u := fillFixture(t, "uk")
m := titoManifest()
m.Pool, m.Regions = "tito", nil
enableInstances(t, fx, "titogo", m)
if rec := mustCreate(t, u, token, `{"host":"p.go.tito.io"}`); rec.Region != "" || rec.RegionFrom != "" {
t.Fatalf("pooled = %+v, want no fill", rec)
}
m.Pool = ""
enableInstances(t, fx, "titogo", m)
if err := fx.st.SetAppPlacement(OpsAccountID, "titogo", "fleet/acme"); err != nil {
t.Fatal(err)
}
if rec := mustCreate(t, u, token, `{"host":"f.go.tito.io"}`); rec.Region != "fleet/acme" || rec.RegionFrom != "fleet" {
t.Fatalf("fleet = %+v", rec)
}
}
func TestFillStoreErrorIsC20(t *testing.T) {
fx, token, u := fillFixture(t, "uk")
fx.srv.appRegionOf = func(int64, string) (string, string, error) { return "", "", errors.New("db gone") }
status, _, out := create(t, u, token, `{"host":"a.go.tito.io"}`)
if status != http.StatusBadGateway || !strings.Contains(out, msgAppRegionUnreadable) {
t.Fatalf("store error = %d %s, want 502 C20", status, out)
}
}
// codex R2-2: dormancy keeps explicit requests, never app-derived ones.
func TestAppDerivedNeedsPositiveEligibility(t *testing.T) {
fx, token, u := fillFixture(t, "uk")
for _, k := range []string{"control/edges/uk-box.json", "control/edges/ie-box.json"} {
if err := fx.rel.Delete(t.Context(), k); err != nil {
t.Fatal(err)
}
}
mustCreate(t, u, token, `{"host":"x.go.tito.io","region":"uk"}`)
status, _, out := create(t, u, token, `{"host":"y.go.tito.io"}`)
if status != http.StatusBadRequest || !strings.Contains(out, reasonText("absent")) {
t.Fatalf("app-derived uk under dormancy = %d %s, want C16 absent", status, out)
}
}
func TestAppDerivedEdgesReadFailureIs503(t *testing.T) {
fx, token, u := fillFixture(t, "uk")
fx.srv.cfg.Releases = failingList{fx.rel}
status, _, out := create(t, u, token, `{"host":"y.go.tito.io"}`)
if status != http.StatusServiceUnavailable || !strings.Contains(out, msgAppRegionUnconfirmed("uk")) {
t.Fatalf("edges failure = %d %s, want 503 C28", status, out)
}
}
func TestAppDerivedRefusals(t *testing.T) {
fx, token, u := fillFixture(t, "uk")
m := titoManifest()
m.Pool, m.Regions = "", []string{"au"}
enableInstances(t, fx, "titogo", m)
// au publishes no box, so nothing the allow-list names can pass: C17b.
if status, _, out := create(t, u, token, `{"host":"y.go.tito.io"}`); status != http.StatusBadRequest ||
!strings.Contains(out, msgAppRegionNotAllowed("uk", nil)) {
t.Fatalf("allow-list = %d %s, want C17b", status, out)
}
m.Regions = nil
enableInstances(t, fx, "titogo", m)
if status, _, out := create(t, u, token, `{"host":"z.go.tito.io","replicas":["uk"]}`); status != http.StatusBadRequest ||
!strings.Contains(out, msgReplicaOverlap("uk")) {
t.Fatalf("replica overlap = %d %s, want C26", status, out)
}
setEdges(t, fx, map[string]string{"uk-box": ukNoKey})
if status, _, out := create(t, u, token, `{"host":"k.go.tito.io"}`); status != http.StatusBadRequest ||
!strings.Contains(out, reasonText("no-secrets-key")) {
t.Fatalf("app-derived keyless uk = %d %s, want C16 no-secrets-key", status, out)
}
}
// R9's explicit half: refused only when a current writer PROVES the
// region keyless, and only for what needs config on the box.
func TestExplicitCreateIntoAKeylessRegion(t *testing.T) {
fx, token, u := fillFixture(t, "")
setEdges(t, fx, map[string]string{"uk-box": ukNoKey})
if status, _, out := create(t, u, token, `{"host":"a.go.tito.io","region":"uk"}`); status != http.StatusBadRequest || !strings.Contains(out, msgNoSecretsKey("uk")) {
t.Fatalf("keyless instance = %d %s, want C31", status, out)
}
if status, _, out := create(t, u, token, `{"host":"r.go.tito.io","region":"uk","role":"replica"}`); status != http.StatusBadRequest || !strings.Contains(out, msgNoSecretsKey("uk")) {
t.Fatalf("keyless replica = %d %s, want C31", status, out)
}
mustCreate(t, u, token, `{"host":"s.go.tito.io","region":"uk","kind":"static"}`)
// An addr-backed service proxies to a process the platform never starts,
// so it needs no config on the box either (codex plan-R2 7).
mustCreate(t, u, token, `{"host":"v.go.tito.io","region":"uk","kind":"service","addrs":["10.0.0.4:8080"]}`)
setEdges(t, fx, map[string]string{"uk-box": ukOldWriter})
mustCreate(t, u, token, `{"host":"o.go.tito.io","region":"uk"}`) // unknown keeps today's behaviour
setEdges(t, fx, map[string]string{"uk-box": ukNoKey})
fx.srv.cfg.SecretsPub = ""
mustCreate(t, u, token, `{"host":"n.go.tito.io","region":"uk"}`) // the console cannot judge
}
func TestFillRunsWithTheSwitchOff(t *testing.T) {
fx, token, u := fillFixture(t, "uk")
fx.srv.cfg.AppRegions = false
if rec := mustCreate(t, u, token, `{"host":"a.go.tito.io"}`); rec.Region != "uk" {
t.Fatalf("switch off = %+v, want the stored uk", rec)
}
}
// codex plan-R3 6: a successful replica create names its place too, so the
// CLI prints C14 for it as for any other placement.
func TestReplicaCreateNamesItsPlace(t *testing.T) {
_, token, u := fillFixture(t, "")
rec := mustCreate(t, u, token, `{"host":"r.go.tito.io","region":"uk","role":"replica"}`)
if rec.Role != "replica" || rec.Region != "uk" || rec.RegionName != ukPlace {
t.Fatalf("replica = %+v, want role replica in uk named %q", rec, ukPlace)
}
}
// persistedRecord is the record's bytes as the bucket holds them, with the
// create time blanked (the one field a golden cannot pin).
func persistedRecord(t *testing.T, fx *consoleFixture, host string) string {
t.Helper()
raw, _, err := fx.rel.Get(t.Context(), provision.RecordKey("bab", "titogo", host))
if err != nil {
t.Fatal(err)
}
return regexp.MustCompile(`"created_at":"[^"]*"`).ReplaceAllString(string(raw), `"created_at":"T"`)
}
// codex plan-R3 5, spec §16: the persisted record, byte for byte. A legacy
// home create writes exactly what the unmodified door wrote (the golden was
// taken from the base tree), and an app-defaulted non-home create differs
// only by its region: no steering stamp, nothing else (R8).
func TestPersistedRecordsMatchTheirGoldens(t *testing.T) {
const legacy = `{"schema":1,"host":"a.go.tito.io","slug":"a","app":"titogo","account":"bab","channel":"stable","created_at":"T","created_by":"alice@keymail.dev"}`
const appUK = `{"schema":1,"host":"a.go.tito.io","slug":"a","app":"titogo","account":"bab","region":"uk","channel":"stable","created_at":"T","created_by":"alice@keymail.dev"}`
for _, c := range []struct{ appRegion, want string }{{"", legacy}, {"uk", appUK}} {
fx, token, u := fillFixture(t, c.appRegion)
rec := mustCreate(t, u, token, `{"host":"a.go.tito.io"}`)
if rec.Steering != "" {
t.Fatalf("app region %q: the response carries a steering stamp %q", c.appRegion, rec.Steering)
}
if got := persistedRecord(t, fx, "a.go.tito.io"); got != c.want {
t.Fatalf("app region %q: stored\n%s\nwant\n%s", c.appRegion, got, c.want)
}
}
}
// codex plan-R4 2: C16's and C17's lists name exactly the regions an
// explicit create of this same instance would pass: the allow-list, the
// armed gate and R9 (not automatic eligibility), never the region refused.
// Each advertised region is then submitted, and must be accepted.
func TestAppRegionRefusalsAdvertiseOnlyWhatPasses(t *testing.T) {
auEligible := edgeJSON("au-box", "9.9.9.9", "ap-southeast-2", "au", eligibleBox)
for _, c := range []struct {
name string
ukBox string
regions []string
want func(choices []string) string
choices []string
}{
// uk is the app's region, keyless; the home is outside the allow-list.
{"C16", ukNoKey, []string{"uk", "au", "nz"}, func(ch []string) string { return msgAppRegionUnavailable("uk", "no-secrets-key", ch) }, []string{"au"}},
// uk is eligible but outside the allow-list; "ie" there is the
// home's NAME, which the door reads as "" and so refuses.
{"C17", ukEligible, []string{"au", "nz", "ie"}, func(ch []string) string { return msgAppRegionNotAllowed("uk", ch) }, []string{"au"}},
} {
fx, token, u := fillFixture(t, "uk")
setEdges(t, fx, map[string]string{"uk-box": c.ukBox, "au-box": auEligible, "nz-box": nzNoHosting})
m := titoManifest()
m.Pool, m.Regions = "", c.regions
enableInstances(t, fx, "titogo", m)
status, _, out := create(t, u, token, `{"host":"y.go.tito.io"}`)
if want := c.want(c.choices); status != http.StatusBadRequest || !strings.Contains(out, want) {
t.Errorf("%s = %d %s, want %q", c.name, status, out, want)
}
for i, code := range c.choices {
mustCreate(t, u, token, fmt.Sprintf(`{"host":"alt%d.go.tito.io","region":%q}`, i, code))
}
}
}The legacy golden is the base tree's own output: the same request (an unrestricted shared-pool manifest, no region, alice's token) against the unmodified door at the branch base, its stored bytes read back with created_at blanked. Re-take it the same way if the base moves.
- [ ] Step 2: Run them to verify they fail
Run: go test ./internal/console -run 'TestFill|TestAppDerived|TestExplicitCreateIntoAKeylessRegion|TestReplicaCreateNamesItsPlace|TestPersistedRecords|TestAppRegionRefusals' -count=1 Expected: FAIL.
- [ ] Step 3: Implement the fill, replacing
apiinstances.go:772
// The app's region is the default for a create that names none
// (nearest-region spec §7, R2). It runs BEFORE every check an explicit
// region meets, and never for a fleet-placed app, a pooled manifest, a
// replica, or anything but a socket-backed instance.
raw := strings.TrimSpace(req.Region)
regionFrom := ""
if raw != "" {
regionFrom = "request"
}
if raw == "" && placed == "" && m.Pool == "" && strings.TrimSpace(req.Role) == "" &&
(provision.Record{Kind: strings.TrimSpace(req.Kind), Addrs: req.Addrs}).DerivesPaths() {
appRegion, _, err := s.appRegion(acct.ID, app)
if err != nil {
log.Printf("console: api instances create %s/%s: read app region: %v", acct.Sqid, app, err)
jsonError(w, http.StatusBadGateway, msgAppRegionUnreadable)
return
}
if appRegion != "" {
raw, regionFrom = appRegion, "app"
}
}
if placed != "" {
regionFrom = "fleet"
}
region := s.homeRegionIn(raw)
// An APP-DERIVED region needs positive eligibility — the secrets key
// included (R9) — and never rides the dormant gate (codex R2-2).
if regionFrom == "app" {
files, err := readEdgesSnapshot(ctx, s.cfg.Releases)
if err != nil {
log.Printf("console: api instances create %s/%s: read edges: %v", acct.Sqid, app, err)
jsonError(w, http.StatusServiceUnavailable, msgAppRegionUnconfirmed(s.homeRegionOut(region)))
return
}
cands := s.regionCands(files)
reason := "absent"
if c, ok := findCandidate(cands, region); ok {
reason = c.unavailableReason()
}
// Each list names only what an explicit request for this same
// instance would pass (codex plan-R4 2): instanceChoices.
if reason != "" {
jsonError(w, http.StatusBadRequest, msgAppRegionUnavailable(s.homeRegionOut(region), reason, s.instanceChoices(ctx, m.Regions, cands, region)))
return
}
if !instanceRegionAllowed(m.Regions, region) {
jsonError(w, http.StatusBadRequest, msgAppRegionNotAllowed(s.homeRegionOut(region), s.instanceChoices(ctx, m.Regions, cands, region)))
return
}
for _, rep := range req.Replicas {
if s.homeRegionIn(strings.TrimSpace(rep)) == region {
jsonError(w, http.StatusBadRequest, msgReplicaOverlap(s.homeRegionOut(region)))
return
}
}
}Create internal/console/regionchoices.go:
package console
import (
"context"
"sort"
"github.com/carlosframework/platform/internal/provision"
)
// instanceChoices lists, under their names, the regions an EXPLICIT
// request for this same socket-backed instance would pass right now (codex
// plan-R4 2): the manifest's allow-list as the door reads it (each code
// normalized through homeRegionIn first), the armed gate's reachable and
// hosting bars, and R9's refusal. These are the explicit door's rules, not
// automatic eligibility. except, the region just refused, is left out.
func (s *Server) instanceChoices(ctx context.Context, allowed []string, cands []regionCandidate, except string) []string {
caps, published := placements(ctx, s.cfg.Releases)
codes := map[string]bool{"": true}
for c := range caps {
codes[s.homeRegionIn(c)] = true
}
for _, c := range allowed {
codes[s.homeRegionIn(c)] = true
}
var out []string
for code := range codes {
if code == except || !instanceRegionAllowed(allowed, code) {
continue
}
if code != "" {
if !provision.ValidRegion(code) || (published && !(caps[code].Reachable && caps[code].Hosting)) {
continue
}
if c, ok := findCandidate(cands, code); ok && c.NoKeyProven {
continue
}
}
out = append(out, s.homeRegionOut(code))
}
sort.Strings(out)
return out
}- [ ] Step 4: Add the R9 explicit refusal to the armed gate
Inside if placed == "" && region != "" { (:827), after the closing brace of its final switch (:852):
// R9: a socket-backed instance or a replica needs its config,
// secrets and store binding rendered on the box, which only a box
// holding the deployment's secrets key can do. Refused only when a
// CURRENT writer proves the region keyless; unknown keeps today's
// behaviour, so roll order never refuses a working region.
if probe.DerivesPaths() || probe.Role == "replica" {
if files, err := readEdgesSnapshot(ctx, s.cfg.Releases); err == nil {
if c, ok := findCandidate(s.regionCands(files), region); ok && c.NoKeyProven {
jsonError(w, http.StatusBadRequest, msgNoSecretsKey(s.homeRegionOut(region)))
return
}
}
}- [ ] Step 5: Fill the response fields
In apiRecordView:
// RegionFrom and RegionName say how this create chose its region and
// what the place is called (spec §7): facts about this request, not
// part of the record.
RegionFrom string `json:"region_from,omitempty"`
RegionName string `json:"region_name,omitempty"`At :1066, after out := recordView(rec):
out.RegionFrom = regionFrom
// Every non-fleet placement is named, replicas included (codex
// plan-R3 6); a fleet-placed record's Region is the fleet itself.
if placed == "" {
out.RegionName = s.placeOf(rec.Region, s.candsOrHome(ctx))
}- [ ] Step 6: Run the tests to verify they pass
Run: go test ./internal/console -count=1 Expected: PASS, including TestAPIInstanceCreate* and the placement-door tests.
Then a mutation check on the R9 gate's scope. Change if probe.DerivesPaths() || probe.Role == "replica" { to if true {, and run go test ./internal/console -run TestExplicitCreateIntoAKeylessRegion -count=1. It must FAIL on the static or addr-backed create. Revert, and re-run: PASS.
Then the list mutation (codex plan-R4 2): in instanceChoices, drop the !instanceRegionAllowed(allowed, code) condition. Run go test ./internal/console -run TestAppRegionRefusalsAdvertiseOnlyWhatPasses -count=1. It must FAIL on both C16 and C17 (the home ie is listed). Revert, and re-run: PASS.
Then the steering mutation (codex plan-R3 5): right after the rec := provision.Record{...} literal, add if regionFrom == "app" { rec.Steering = "latency" }, and run go test ./internal/console -run TestPersistedRecordsMatchTheirGoldens -count=1. It must FAIL on the app-defaulted record. Revert, and re-run: PASS.
- [ ] Step 7: Commit
git add internal/console/apiinstances.go internal/console/regionchoices.go internal/console/regionfill_test.go
git commit -m "console: instance door fills the app region, proves eligibility, refuses keyless regions (nearest-region §7, R9)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"---
Task 15: app_default, place names and the guess endpoint
Files:
- Create:
internal/console/regiondefault.go - Modify:
internal/console/apiregions.go - Modify:
internal/console/authcli.go, beside the/regionsroute at:586 - Test:
internal/console/regiondefault_test.go
Interfaces:
- Consumes: Tasks 7, 9, 10, 11 and 12.
- Produces:
type appDefaultView struct {
Region string `json:"region"` // the STORED region, under its name; "" when unknown
Name string `json:"name"`
Source string `json:"source"`
Effective string `json:"effective"` // where a no-region create lands
Available *bool `json:"available,omitempty"`
Reason string `json:"reason,omitempty"`
RegionKnown bool `json:"region_known"` // the stored region was read
PlacementKnown bool `json:"placement_known"` // the manifest and fleet placement were read
InstanceDomain string `json:"instance_domain,omitempty"` // the domain instance hosts take; "" when unknown
}
func (s *Server) appDefault(ctx context.Context, acct *Account, app string) appDefaultView
func (s *Server) platformInstanceDomain(acct *Account) string
func (s *Server) handleAPIRegionGuess(w http.ResponseWriter, r *http.Request)GET …/regions entries gain aws_region and name. The response gains app_default.
region_known and placement_known are additive fields this plan adds to spec §9.3's block (codex plan-R2 3). A response is always HTTP 200, so the CLI needs them to tell a confirmed fact from one the console could not read. deploy --region needs the stored region, and canary --region needs the fleet placement. Each refuses when its fact is unknown (Tasks 21, 22). Spec rev 6.1 records both (R9a): region_known is true exactly when the stored region was read, and placement_known exactly when the manifest and fleet placement were read; absent knowledge fails closed.
instance_domain is one more additive field, not in the spec (codex plan-R3 1): the domain instance hosts take. It is the manifest's domain when a manifest exists, else the domain the instances-manifest PUT would stamp (<sqid>.<PLATFORM_APPS_DOMAIN>), and omitted when the manifest is unreadable or the console has no valid apps domain. canary --region needs it to name the canary's host, and read that one record, before its first write.
A pooled default's available is the allow-list's verdict on the home, because a no-region pooled create lands there (codex plan-R3 2). The pooled reason stays, so the Instances tab still shows no line for it.
R9a holds in the pooled and fleet branches too (codex plan-R4 1, spec rev 6.2): with the stored region unread, effective stays empty and available omitted, and reason (pooled or fleet) is the only placement fact stated. Task 22's canary check therefore needs BOTH flags.
- [ ] Step 1: Write the failing tests
// internal/console/regiondefault_test.go
package console
import (
"context"
"encoding/json"
"errors"
"io"
"net/http"
"strings"
"testing"
"github.com/carlosframework/platform/internal/provision"
)
func regionsURL(fx *consoleFixture) string { return fx.ts.URL + "/api/cli/apps/bab/titogo/regions" }
func guessURL(fx *consoleFixture) string { return regionsURL(fx) + "/guess" }
// corruptManifest replaces the manifest with unparsable bytes, under its
// current etag (Put with "" is create-only and would not replace it).
func corruptManifest(t *testing.T, fx *consoleFixture) {
t.Helper()
key := provision.ManifestKey("bab", "titogo")
_, etag, err := fx.rel.Get(context.Background(), key)
if err != nil {
t.Fatal(err)
}
if _, err := fx.rel.Put(context.Background(), key, []byte("{not json"), etag); err != nil {
t.Fatal(err)
}
}
func TestRegionsKeepsLegacyFieldsAndAddsAppDefault(t *testing.T) {
fx, token, _ := fillFixture(t, "uk")
status, _, out := rawReq(t, http.MethodGet, regionsURL(fx), token, "")
var res struct {
Default string `json:"default"`
AppDefault appDefaultView `json:"app_default"`
}
if err := json.Unmarshal([]byte(out), &res); err != nil || status != http.StatusOK || res.Default != "ie" {
t.Fatalf("legacy default = %d %s", status, out)
}
if d := res.AppDefault; d.Region != "uk" || d.Effective != "uk" || d.Available == nil || !*d.Available || d.Name != ukPlace {
t.Fatalf("app_default = %+v", d)
}
if !strings.Contains(out, `"aws_region":"eu-west-2"`) || !strings.Contains(out, ukPlace) {
t.Fatalf("entries lack names: %s", out)
}
}
// Every reason §9.3 names, each from its own condition (codex plan-R1 12).
func TestAppDefaultReasons(t *testing.T) {
acct := &Account{ID: OpsAccountID, Sqid: "bab"}
cases := []struct {
name string
setup func(t *testing.T, fx *consoleFixture)
reason string
available *bool
effective string
}{
{"available", func(*testing.T, *consoleFixture) {}, "", bp(true), "uk"},
{"absent", func(t *testing.T, fx *consoleFixture) { fx.rel.Delete(t.Context(), "control/edges/uk-box.json") }, "absent", bp(false), "uk"},
{"not-hosting", func(t *testing.T, fx *consoleFixture) {
setEdges(t, fx, map[string]string{"uk-box": edgeJSON("uk-box", "1.2.3.4", "eu-west-2", "uk", edgeOpts{hib: bp(false), shared: bp(true), inbound: bp(true), fp: testKeyFP})})
}, "not-hosting", bp(false), "uk"},
{"no-secrets-key", func(t *testing.T, fx *consoleFixture) { setEdges(t, fx, map[string]string{"uk-box": ukNoKey}) }, "no-secrets-key", bp(false), "uk"},
{"not-eligible", func(t *testing.T, fx *consoleFixture) {
setEdges(t, fx, map[string]string{"uk-box": edgeJSON("uk-box", "1.2.3.4", "eu-west-2", "uk", edgeOpts{hib: bp(true), shared: bp(false), inbound: bp(true), fp: testKeyFP})})
}, "not-eligible", bp(false), "uk"},
{"not-allowed", func(t *testing.T, fx *consoleFixture) {
m := titoManifest()
m.Pool, m.Regions = "", []string{"au"}
enableInstances(t, fx, "titogo", m)
}, "not-allowed", bp(false), "uk"},
{"pooled", func(t *testing.T, fx *consoleFixture) {
m := titoManifest()
m.Pool, m.Regions = "tito", nil
enableInstances(t, fx, "titogo", m)
}, "pooled", bp(true), "ie"},
{"fleet", func(t *testing.T, fx *consoleFixture) { fx.st.SetAppPlacement(OpsAccountID, "titogo", "fleet/acme") }, "fleet", nil, "fleet/acme"},
{"policy-unreadable", corruptManifest, "policy-unreadable", nil, "uk"},
{"edges-unreadable", func(t *testing.T, fx *consoleFixture) { fx.srv.cfg.Releases = failingList{fx.rel} }, "edges-unreadable", nil, "uk"},
}
for _, c := range cases {
fx, _, _ := fillFixture(t, "uk")
c.setup(t, fx)
d := fx.srv.appDefault(t.Context(), acct, "titogo")
okAvail := (c.available == nil && d.Available == nil) || (c.available != nil && d.Available != nil && *c.available == *d.Available)
if d.Reason != c.reason || !okAvail || d.Effective != c.effective || d.Region != "uk" {
t.Errorf("%s: %+v, want reason %q effective %q available %v", c.name, d, c.reason, c.effective, c.available)
}
}
}
// codex plan-R2 3: a fact the console could not read is never presented as
// known: no home-valued region, no non-fleet effective.
func TestAppDefaultKnownFlags(t *testing.T) {
acct := &Account{ID: OpsAccountID, Sqid: "bab"}
fx, _, _ := fillFixture(t, "uk")
if d := fx.srv.appDefault(t.Context(), acct, "titogo"); !d.RegionKnown || !d.PlacementKnown || d.Region != "uk" {
t.Fatalf("readable = %+v", d)
}
fx.srv.appRegionOf = func(int64, string) (string, string, error) { return "", "", errors.New("db gone") }
if d := fx.srv.appDefault(t.Context(), acct, "titogo"); d.RegionKnown || !d.PlacementKnown || d.Region != "" || d.Effective != "" || d.Available != nil {
t.Fatalf("unreadable region = %+v, want region unknown and nothing presented as fact", d)
}
fx2, _, _ := fillFixture(t, "uk")
corruptManifest(t, fx2)
if d := fx2.srv.appDefault(t.Context(), acct, "titogo"); !d.RegionKnown || d.PlacementKnown || d.Reason != "policy-unreadable" {
t.Fatalf("unreadable policy = %+v, want placement unknown", d)
}
}
// codex plan-R3 2: a pooled default is available only when the create door
// would take it. A no-region pooled create lands on the home, so the home
// must pass the manifest's allow-list.
func TestPooledAvailabilityMatchesTheCreateDoor(t *testing.T) {
acct := &Account{ID: OpsAccountID, Sqid: "bab"}
for _, c := range []struct {
name string
regions []string
avail bool
}{
{"no allow-list", nil, true},
{"allow-list without the home", []string{"uk"}, false},
} {
fx, token, u := fillFixture(t, "uk")
m := titoManifest()
m.Pool, m.Regions = "tito", c.regions
enableInstances(t, fx, "titogo", m)
d := fx.srv.appDefault(t.Context(), acct, "titogo")
if d.Reason != "pooled" || d.Effective != "ie" || d.Available == nil || *d.Available != c.avail {
t.Fatalf("%s: app_default = %+v, want pooled, ie, available %v", c.name, d, c.avail)
}
status, _, out := create(t, u, token, `{"host":"p.go.tito.io"}`)
if created := status == http.StatusCreated; created != c.avail {
t.Fatalf("%s: create = %d %s, but app_default said available %v", c.name, status, out, c.avail)
}
}
}
// codex plan-R3 1: instance_domain is the domain the instances-manifest PUT
// stamps on hosts, so the CLI can name a canary's host before any write.
func TestAppDefaultInstanceDomain(t *testing.T) {
acct := &Account{ID: OpsAccountID, Sqid: "bab"}
fx, _, _ := fillFixture(t, "uk")
if d := fx.srv.appDefault(t.Context(), acct, "titogo"); d.InstanceDomain != "go.tito.io" {
t.Fatalf("with a manifest = %q, want the manifest's go.tito.io", d.InstanceDomain)
}
corruptManifest(t, fx)
if d := fx.srv.appDefault(t.Context(), acct, "titogo"); d.InstanceDomain != "" {
t.Fatalf("unreadable manifest = %q, want none", d.InstanceDomain)
}
fx2, token := instanceFixture(t, "code-inst-domain")
fx2.srv.cfg.AppsDomain = ""
if d := fx2.srv.appDefault(t.Context(), acct, "titogo"); d.InstanceDomain != "" {
t.Fatalf("no apps domain = %q, want none (the PUT refuses)", d.InstanceDomain)
}
fx2.srv.cfg.AppsDomain = "oncarlos.test"
before := fx2.srv.appDefault(t.Context(), acct, "titogo").InstanceDomain
status, _, out := rawReq(t, http.MethodPut, fx2.ts.URL+"/api/cli/apps/bab/titogo/instances-manifest", token, `{}`)
var m struct {
Domain string `json:"domain"`
}
if err := json.Unmarshal([]byte(out), &m); err != nil || status != http.StatusOK {
t.Fatalf("enable = %d %s", status, out)
}
if before == "" || before != m.Domain {
t.Fatalf("before the PUT instance_domain = %q, the PUT stamped %q", before, m.Domain)
}
}
// codex plan-R4 1, spec R9a: an unread stored region leaves region, name,
// source and effective empty and available omitted, even when a fleet or a
// pooled manifest would otherwise decide them. The placement the console
// did read stays visible in reason.
func TestAppDefaultUnreadRegionHidesEffective(t *testing.T) {
acct := &Account{ID: OpsAccountID, Sqid: "bab"}
for _, c := range []struct {
name string
setup func(t *testing.T, fx *consoleFixture)
reason string
}{
{"pooled", func(t *testing.T, fx *consoleFixture) {
m := titoManifest()
m.Pool, m.Regions = "tito", nil
enableInstances(t, fx, "titogo", m)
}, "pooled"},
{"fleet", func(t *testing.T, fx *consoleFixture) {
if err := fx.st.SetAppPlacement(OpsAccountID, "titogo", "fleet/acme"); err != nil {
t.Fatal(err)
}
}, "fleet"},
} {
fx, _, _ := fillFixture(t, "uk")
c.setup(t, fx)
fx.srv.appRegionOf = func(int64, string) (string, string, error) { return "", "", errors.New("db gone") }
d := fx.srv.appDefault(t.Context(), acct, "titogo")
if d.RegionKnown || !d.PlacementKnown || d.Region != "" || d.Name != "" || d.Source != "" ||
d.Effective != "" || d.Available != nil || d.Reason != c.reason {
t.Errorf("%s with an unread region = %+v, want only placement_known and reason %q", c.name, d, c.reason)
}
}
}
func TestRegionGuessEndpoint(t *testing.T) {
fx, token, _ := fillFixture(t, "")
status, _, out := rawReq(t, http.MethodPost, guessURL(fx), token, `{"tz":"Europe/London"}`)
if status != http.StatusOK || !strings.Contains(out, `"code":"uk"`) || !strings.Contains(out, `"source":"timezone"`) {
t.Fatalf("guess = %d %s", status, out)
}
// Review Focus 4: garbage hints are absent, never an error.
status, _, out = rawReq(t, http.MethodPost, guessURL(fx), token, `{"ip":"not-an-ip","tz":"`+strings.Repeat("a", 10<<10)+`"}`)
if status != http.StatusOK || !strings.Contains(out, `"source":"default"`) {
t.Fatalf("garbage = %d %s", status, out)
}
status, _, out = rawReq(t, http.MethodPost, guessURL(fx), token, `{not json`)
if status != http.StatusOK || !strings.Contains(out, `"source":"default"`) {
t.Fatalf("malformed body = %d %s", status, out)
}
fx.st.SetAppPlacement(OpsAccountID, "titogo", "fleet/acme")
if _, _, out := rawReq(t, http.MethodPost, guessURL(fx), token, `{"tz":"Europe/London"}`); !strings.Contains(out, `"guess":null`) {
t.Fatalf("fleet guess = %s (codex R4-1)", out)
}
}
func TestRegionGuessPooledNullAndUnreadable502(t *testing.T) {
fx, token, _ := fillFixture(t, "")
m := titoManifest()
m.Pool = "tito"
enableInstances(t, fx, "titogo", m)
if _, _, out := rawReq(t, http.MethodPost, guessURL(fx), token, `{"tz":"Europe/London"}`); !strings.Contains(out, `"guess":null`) {
t.Fatalf("pooled guess = %s", out)
}
corruptManifest(t, fx)
if status, _, out := rawReq(t, http.MethodPost, guessURL(fx), token, `{"tz":"Europe/London"}`); status != http.StatusBadGateway || !strings.Contains(out, msgPolicyUnreadable) {
t.Fatalf("unreadable manifest = %d %s, want 502 C27", status, out)
}
fx2, token2, _ := fillFixture(t, "")
fx2.srv.cfg.Registry = t.TempDir() // a directory: the registry will not open
if status, _, out := rawReq(t, http.MethodPost, guessURL(fx2), token2, `{"tz":"Europe/London"}`); status != http.StatusBadGateway || !strings.Contains(out, msgPolicyUnreadable) {
t.Fatalf("unreadable placement = %d %s, want 502 C27", status, out)
}
}
// codex 8: with a canonical-host redirect configured, a guess POST's hints
// appear in no Location, no body, no log and no audit row.
func TestRegionGuessHintsNeverLeak(t *testing.T) {
fx, token, _ := fillFixture(t, "")
fx.srv.redirects = map[string]bool{"old.example": true}
var logs strings.Builder
restore := captureLog(&logs)
defer restore()
req, _ := http.NewRequest(http.MethodPost, guessURL(fx), strings.NewReader(`{"tz":"Pacific/Chatham","ip":"203.0.113.77"}`))
req.Host = "old.example"
req.Header.Set("Authorization", "Bearer "+token)
res, err := (&http.Client{CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}).Do(req)
if err != nil {
t.Fatal(err)
}
body, _ := io.ReadAll(res.Body)
res.Body.Close()
everything := res.Header.Get("Location") + string(body)
_, _, out := rawReq(t, http.MethodPost, guessURL(fx), token, `{"tz":"Pacific/Chatham","ip":"203.0.113.77"}`)
everything += out + logs.String()
events, err := fx.st.Events(OpsAccountID, "titogo", 50)
if err != nil {
t.Fatal(err)
}
for _, e := range events {
everything += e.Detail + e.Message
}
for _, secret := range []string{"Chatham", "203.0.113.77"} {
if strings.Contains(everything, secret) {
t.Fatalf("hint %q leaked: %s", secret, everything)
}
}
}fx.srv.redirects is the normalized host set ServeHTTP reads (server.go:723, :1306). Event carries both Detail and Message (store.go:2066).
- [ ] Step 2: Run them to verify they fail
Run: go test ./internal/console -run 'TestRegionsKeeps|TestAppDefault|TestRegionGuess|TestPooledAvailability' -count=1 Expected: FAIL. TestAppDefaultUnreadRegionHidesEffective is the R9a case for pooled and fleet apps.
- [ ] Step 3: Implement
regiondefault.go
package console
import (
"context"
"encoding/json"
"net"
"net/http"
"strings"
"github.com/carlosframework/platform/internal/provision"
)
// appDefaultView is §9.3's app_default. region is the STORED region (what
// deploy --region compares against); effective is where a no-region create
// lands — the fleet itself for a placed app, never the platform home
// (codex R2-4). RegionKnown and PlacementKnown say which facts were READ:
// an unread fact is left empty, never filled with the home (codex
// plan-R2 3; spec R9a). InstanceDomain is the domain instance hosts take,
// so canary --region can name its host before any write (codex plan-R3 1).
type appDefaultView struct {
Region string `json:"region"`
Name string `json:"name"`
Source string `json:"source"`
Effective string `json:"effective"`
Available *bool `json:"available,omitempty"`
Reason string `json:"reason,omitempty"`
RegionKnown bool `json:"region_known"`
PlacementKnown bool `json:"placement_known"`
InstanceDomain string `json:"instance_domain,omitempty"`
}
func boolp(b bool) *bool { return &b }
func (s *Server) appDefault(ctx context.Context, acct *Account, app string) appDefaultView {
stored, source, rerr := s.appRegion(acct.ID, app)
snap, ferr := readEdgesSnapshot(ctx, s.cfg.Releases)
cands := homeOnlyCandidates()
if ferr == nil {
cands = s.regionCands(snap)
}
pol := s.appPolicy(ctx, acct, app)
v := appDefaultView{RegionKnown: rerr == nil, PlacementKnown: pol.State != policyUnreadable}
switch pol.State {
case policyKnown:
v.InstanceDomain = pol.M.Domain
case policyAbsent:
v.InstanceDomain = s.platformInstanceDomain(acct)
}
if rerr == nil {
v.Region, v.Name, v.Source, v.Effective = s.homeRegionOut(stored), s.placeOf(stored, cands), source, s.homeRegionOut(stored)
}
switch {
case pol.State == policyUnreadable:
v.Reason = "policy-unreadable"
return v
case pol.Placed != "":
// R9a: with the stored region unread, effective stays empty even
// though the fleet is known; reason still says fleet (codex
// plan-R4 1).
v.Reason = "fleet"
if rerr == nil {
v.Effective = pol.Placed
}
return v
case pol.pooled():
// A no-region pooled create lands on the home, so it is available
// only when the home passes the allow-list (codex plan-R3 2). R9a
// again: nothing about effective is stated when the region is unread.
v.Reason = "pooled"
if rerr == nil {
v.Effective, v.Available = s.homeRegionOut(""), boolp(pol.allows(""))
}
return v
case rerr != nil:
// The stored region is unknown, so where a create lands is too.
v.Reason = "policy-unreadable"
return v
case ferr != nil:
v.Reason = "edges-unreadable"
return v
}
reason := "absent"
if c, ok := findCandidate(cands, stored); ok {
reason = c.unavailableReason()
}
if reason == "" && !pol.allows(stored) {
reason = "not-allowed"
}
v.Reason, v.Available = reason, boolp(reason == "")
return v
}
// platformInstanceDomain is the domain the instances-manifest PUT stamps on
// a NEW manifest (handleAPIInstancesEnable): "" when this console has no
// valid platform apps domain, which that PUT refuses. With a manifest, its
// own domain governs instead.
func (s *Server) platformInstanceDomain(acct *Account) string {
if s.cfg.AppsDomain == "" {
return ""
}
d := acct.Sqid + "." + s.cfg.AppsDomain
if !provision.ValidHostShape(d) {
return ""
}
return d
}
// handleAPIRegionGuess is POST …/regions/guess (spec §9.4). Hints come only
// in the body, so no redirect can echo them. A malformed body is read as
// no hints; a person's request with no ip uses its own address, a service
// principal's never does.
func (s *Server) handleAPIRegionGuess(w http.ResponseWriter, r *http.Request) {
_, acct, app, ok := s.instanceGate(w, r)
if !ok {
return
}
var body struct {
TZ string `json:"tz"`
IP string `json:"ip"`
}
_ = json.NewDecoder(http.MaxBytesReader(w, r.Body, 64<<10)).Decode(&body)
pol := s.appPolicy(r.Context(), acct, app)
if pol.State == policyUnreadable {
jsonError(w, http.StatusBadGateway, msgPolicyUnreadable)
return
}
if pol.Placed != "" || pol.pooled() {
writeJSON(w, http.StatusOK, map[string]any{"guess": nil})
return
}
ip := net.ParseIP(strings.TrimSpace(body.IP))
if ip == nil {
ip = s.personIP(r)
}
files, err := readEdgesSnapshot(r.Context(), s.cfg.Releases)
cands := homeOnlyCandidates()
if err == nil {
cands = s.regionCands(files)
}
g, ok := guessRegion(cands, pol.allows, guessInput{TZ: body.TZ, IP: ip}, s.cfg.GeoIP)
if !ok {
writeJSON(w, http.StatusOK, map[string]any{"guess": nil})
return
}
writeJSON(w, http.StatusOK, map[string]any{"guess": map[string]string{
"code": s.homeRegionOut(g.Code), "name": s.placeOf(g.Code, cands), "source": g.Source,
}})
}- [ ] Step 4: Extend
handleAPIRegionsand register the route
In apiregions.go, change the gate line to _, acct, app, ok := s.instanceGate(w, r), and build the entries with names:
type region struct {
Code string `json:"code"`
Armed bool `json:"armed"`
AWSRegion string `json:"aws_region,omitempty"`
Name string `json:"name,omitempty"`
}
set, _ := armedPlacements(r.Context(), s.cfg.Releases)
cands := s.candsOrHome(r.Context())
named := func(code string) region {
c, _ := findCandidate(cands, s.homeRegionIn(code))
return region{Code: code, AWSRegion: c.AWSRegion, Name: s.placeOf(s.homeRegionIn(code), cands)}
}
out := make([]region, 0, len(set)+1)
if s.cfg.HomeRegion != "" {
h := named(s.cfg.HomeRegion)
h.Armed = true
out = append(out, h)
delete(set, s.cfg.HomeRegion)
}
for code, armed := range set {
e := named(code)
e.Armed = armed
out = append(out, e)
}
sort.Slice(out, func(i, j int) bool { return out[i].Code < out[j].Code })
writeJSON(w, http.StatusOK, map[string]any{
"regions": out, "default": s.cfg.HomeRegion,
"app_default": s.appDefault(r.Context(), acct, app),
})In authcli.go:
// The per-caller region guess (nearest-region spec §9.4): POST, so the
// hints never ride a URL a redirect could echo.
s.cliRoute("POST /api/cli/apps/{account}/{app}/regions/guess", svcrole.Allow(svcrole.Instance), s.handleAPIRegionGuess)Then add the new route to exemptFromLegacyPin in internal/console/svcroutes_test.go, beside GET …/regions. TestInstanceRoleMatchesTodaysAppTokenReach pins every instance-role route to what an app token reached before service principals, and the guess route did not exist then:
// The region guess (nearest-region spec §9.4) stands on GET .../regions'
// footing: the same deployment topology, filtered by the app's own
// policy, for a hint the caller supplies. It did not exist before
// service principals, and titogo's per-instance picker is its consumer.
"POST /api/cli/apps/{account}/{app}/regions/guess": true,- [ ] Step 5: Run the tests to verify they pass
Run: go test ./internal/console -count=1 Expected: PASS, including the existing /regions tests, whose regions and default must be unchanged, and TestInstanceRoleMatchesTodaysAppTokenReach.
- [ ] Step 6: Commit
git add internal/console/regiondefault.go internal/console/regiondefault_test.go internal/console/apiregions.go internal/console/authcli.go internal/console/svcroutes_test.go
git commit -m "console: app_default with every reason, place names, POST regions/guess (nearest-region §9.3, §9.4)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"---
Task 16: The Instances tab lines
Files:
- Modify:
internal/console/server.go- thepagestruct; -handleAppInstances, at:2933-2965. - Modify:
internal/console/templates/app_instances.html:60-78 - Test:
internal/console/regiondefault_test.go(append)
Interfaces:
- Consumes:
s.appDefault(Task 15) andregionCopy(Task 6). - Produces:
page.RegionLine stringandpage.RegionLineWarn bool.
- [ ] Step 1: Write the failing test
// C7 only when the default is CONFIRMED available; C8 whenever it is
// confirmed unavailable; nothing for fleet, pooled, or a default that
// could not be read (codex plan-R1 14). Assertions compare the HTML-escaped
// copy, both ways (codex plan-R1 8).
func TestInstancesTabRegionLines(t *testing.T) {
starts := html.EscapeString(fmt.Sprintf(regionCopy.StartsIn, ukPlace))
cantUK := html.EscapeString(fmt.Sprintf(regionCopy.CantStart, "uk"))
page := func(fx *consoleFixture) string {
_, _, body := get(t, signInPaul(t, fx), fx.u("/apps/titogo/instances"))
return body
}
anyLine := func(body string) bool { return strings.Contains(body, `id="app-region-line"`) }
fx, _, _ := fillFixture(t, "uk")
fx.srv.cfg.AppRegions = true
if body := page(fx); !strings.Contains(body, starts) {
t.Fatal("no C7 line for an available region")
}
fx.rel.Delete(t.Context(), "control/edges/uk-box.json")
// No box publishes uk, so its place falls back to the bare code (§14).
if body := page(fx); !strings.Contains(body, cantUK) || strings.Contains(body, starts) {
t.Fatal("want C8, not C7, when the region is absent (codex 7)")
}
for _, setup := range []func(*consoleFixture){
func(fx *consoleFixture) { fx.st.SetAppPlacement(OpsAccountID, "titogo", "fleet/acme") },
func(fx *consoleFixture) {
m := titoManifest()
m.Pool = "tito"
enableInstances(t, fx, "titogo", m)
},
func(fx *consoleFixture) { corruptManifest(t, fx) },
func(fx *consoleFixture) { fx.srv.cfg.Releases = failingList{fx.rel} },
} {
fx, _, _ := fillFixture(t, "uk")
setup(fx)
if anyLine(page(fx)) {
t.Error("a fleet, pooled or unreadable default showed a region line")
}
}
}Add "fmt" and "html" to the file's imports. anyLine looks for the line's element id, so it does not depend on the copy's wording.
- [ ] Step 2: Run it to verify it fails
Run: go test ./internal/console -run TestInstancesTabRegionLines -count=1 Expected: FAIL.
- [ ] Step 3: Implement it
In page:
// RegionLine is the Instances tab's app-region note (C7 or C8, spec
// §10); "" shows nothing. RegionLineWarn marks C8.
RegionLine string
RegionLineWarn boolIn handleAppInstances, after the placement section:
if d := s.appDefault(r.Context(), acct, p.AppName); d.Available != nil && d.Reason != "pooled" {
switch {
case !*d.Available:
p.RegionLine, p.RegionLineWarn = fmt.Sprintf(regionCopy.CantStart, d.Name), true
case d.Region != s.cfg.HomeRegion:
p.RegionLine = fmt.Sprintf(regionCopy.StartsIn, d.Name)
default:
if files, err := readEdgesSnapshot(r.Context(), s.cfg.Releases); err == nil && eligibleCount(s.regionCands(files)) >= 2 {
p.RegionLine = fmt.Sprintf(regionCopy.StartsIn, d.Name)
}
}
}Available is nil for fleet, policy-unreadable and edges-unreadable, so those show nothing. Pooled is excluded by name.
In app_instances.html, directly after <h2>Placement</h2>:
{{if .RegionLine}}<p id="app-region-line"{{if .RegionLineWarn}} class="banner error"{{end}}>{{.RegionLine}}</p>{{end}}- [ ] Step 4: Run the tests to verify they pass
Run: go test ./internal/console -count=1 Expected: PASS.
- [ ] Step 5: Commit
git add internal/console/server.go internal/console/templates/app_instances.html internal/console/regiondefault_test.go
git commit -m "console: Instances tab names where new instances start, only when confirmed (nearest-region §10)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"---
Task 17: Roll-forward discrepancy log
Files:
- Create:
internal/console/regiondrift.go - Modify:
cmd/console/main.go(call once afterNewServer) - Test:
internal/console/regiondrift_test.go
Interfaces:
- Consumes:
Store.NonHomeAppRegions(Task 8),provision.ListRecords, andmsgPlacementDiscrepancy(Task 7). - Produces:
func (s *Server) LogPlacementDiscrepancies(ctx context.Context).
- [ ] Step 1: Write the failing test
// internal/console/regiondrift_test.go
package console
import (
"context"
"strings"
"testing"
"time"
"github.com/carlosframework/platform/internal/provision"
"github.com/carlosframework/platform/internal/release"
)
// codex R2-6: shapes that never inherit the app default are not listed;
// a no-region socket-backed instance created after the app is.
func TestPlacementDiscrepancyLog(t *testing.T) {
fx := newConsoleFixture(t)
fx.srv.cfg.HomeRegion = "ie"
created := time.Now().Add(-time.Hour)
fx.st.CreateAppInRegion(OpsAccountID, "blog", "p@k.dev", "uk", "chosen", created)
ctx := context.Background()
put := func(r provision.Record) {
r.Schema, r.App, r.Account, r.Channel, r.CreatedAt = 1, "blog", release.OpsAccount, "stable", time.Now()
if err := provision.CreateRecord(ctx, fx.rel, r); err != nil {
t.Fatal(err)
}
}
put(provision.Record{Host: "blog.go.tito.io", Kind: "static"})
put(provision.Record{Host: "svc.go.tito.io", Kind: "service", Addrs: []string{"10.0.0.4:8080"}})
put(provision.Record{Host: "r.go.tito.io", Region: "au", Role: "replica"})
put(provision.Record{Host: "pooled.go.tito.io", Pool: "tito"})
put(provision.Record{Host: "uk.go.tito.io", Region: "uk"})
put(provision.Record{Host: "drift.go.tito.io"})
var logs strings.Builder
restore := captureLog(&logs)
fx.srv.LogPlacementDiscrepancies(ctx)
restore()
out := logs.String()
if !strings.Contains(out, msgPlacementDiscrepancy("drift.go.tito.io", "blog", "uk")) {
t.Fatalf("drift not listed: %s", out)
}
for _, quiet := range []string{"blog.go.tito.io", "svc.go.tito.io", "r.go.tito.io", "pooled.go.tito.io", "uk.go.tito.io"} {
if strings.Contains(out, quiet+" of") {
t.Errorf("%s listed", quiet)
}
}
}provision.CreateRecord is a plain create-only write (provision.go:820), so these fixture records need no manifest.
- [ ] Step 2: Run it to verify it fails
Run: go test ./internal/console -run TestPlacementDiscrepancyLog -count=1 Expected: FAIL.
- [ ] Step 3: Implement
regiondrift.go
package console
import (
"context"
"log"
"github.com/carlosframework/platform/internal/provision"
)
// discrepancyScanCap bounds one app's record listing. ListRecords treats
// max=0 as "list nothing", so the cap must be explicit.
const discrepancyScanCap = 10000
// LogPlacementDiscrepancies is spec §5.7 step 4, run once at console
// start. For each app holding a non-home region, it lists records with an
// empty region created after the app — the shape a console rolled back
// below the floor would have written. Shapes that never inherit the app
// default are skipped (static, addr-backed, replica, pooled, fleet). A
// deliberate home override looks the same and the record keeps no
// provenance, so each line is a CANDIDATE for review, worded as one (C29).
func (s *Server) LogPlacementDiscrepancies(ctx context.Context) {
if s.cfg.Releases == nil {
return
}
apps, err := s.store.NonHomeAppRegions()
if err != nil {
log.Printf("console: placement discrepancy check: %v", err)
return
}
for _, a := range apps {
recs, truncated, err := provision.ListRecords(ctx, s.cfg.Releases, a.Sqid, a.App, discrepancyScanCap)
if err != nil {
log.Printf("console: placement discrepancy check %s/%s: %v", a.Sqid, a.App, err)
continue
}
if truncated {
log.Printf("console: placement discrepancy check %s/%s: only the first %d records were checked", a.Sqid, a.App, discrepancyScanCap)
}
for _, r := range recs {
if r.Region != "" || r.Role != "" || r.Pool != "" || !r.DerivesPaths() || r.CreatedAt.Before(a.CreatedAt) {
continue
}
log.Print("console: " + msgPlacementDiscrepancy(r.Host, a.App, a.Region))
}
}
}- [ ] Step 4: Call it at startup
In cmd/console/main.go, after srv is built and before serving:
// Spec §5.7: after any roll, list possible placement discrepancies for
// operator review. Read-only; never blocks startup.
go srv.LogPlacementDiscrepancies(context.Background())- [ ] Step 5: Run the tests to verify they pass
Run: go test ./internal/console ./cmd/console -count=1 Expected: PASS.
- [ ] Step 6: Commit
git add internal/console/regiondrift.go internal/console/regiondrift_test.go cmd/console/main.go
git commit -m "console: list possible placement discrepancies on start (nearest-region §5.7)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"---
Task 18: CLI copy (C9 to C15, C13a, C13b, C15b, C15c, C22, C25, C30, C30b, C30c, from the record)
Files:
- Create:
region_copy.go(root, packagemain) - Test:
region_copy_test.go
Interfaces:
- Consumes: the record's C9 to C15, C13a, C13b, C15b, C15c, C22a to C22d, C25, C30, C30b and C30c (Task 0).
- Produces:
func regionCreatedLine(place, source string) string // C9 to C12
const regionNotRecordedOldConsole // C13a
func regionNotAppliedSwitchOff(place string) string // C13b
func instanceCreatedSuffix(channel, place string) string // C14
func deployRegionMismatch(app, place string) string // C15
func deployRegionUnreadable(app string, err error) string // C15b
func deployRegionUnknown(app string) string // C15c
func canaryRegionMismatch(host, place string) string // C25
func canaryRegionOnFleet(app, fleet string) string // C30
func canaryFleetUnknown(app string) string // C30b
func canaryRegionUnconfirmed(name string) string // C30c
const flagRegionAppsCreate, flagRegionDeploy, flagRegionCanary, flagRegionInstancesCreate // C22a to C22d- [ ] Step 1: Write the failing test
// region_copy_test.go
package main
import (
"errors"
"strings"
"testing"
)
func TestRegionCLICopy(t *testing.T) {
lines := map[string]string{}
for _, src := range []string{"timezone", "ip", "default", "chosen"} {
lines[src] = regionCreatedLine("P", src)
}
if len(map[string]bool{lines["timezone"]: true, lines["ip"]: true, lines["default"]: true, lines["chosen"]: true}) != 4 {
t.Fatalf("each source needs its own line: %v", lines)
}
if regionCreatedLine("P", "") != lines["default"] {
t.Fatal("an unknown source must read as the default")
}
all := []string{
regionNotRecordedOldConsole, regionNotAppliedSwitchOff("P"),
instanceCreatedSuffix("stable", "P"), deployRegionMismatch("blog", "P"),
deployRegionUnreadable("blog", errors.New("timeout")), deployRegionUnknown("blog"),
canaryRegionMismatch("c.go.tito.io", "P"), canaryRegionOnFleet("blog", "acme"),
canaryFleetUnknown("blog"), canaryRegionUnconfirmed("c"),
flagRegionAppsCreate, flagRegionDeploy, flagRegionCanary, flagRegionInstancesCreate,
}
for _, s := range append(all, lines["timezone"], lines["ip"], lines["default"], lines["chosen"]) {
if s == "" || strings.Contains(s, "—") || strings.Contains(s, "%!") {
t.Errorf("bad copy %q", s)
}
}
}- [ ] Step 2: Run it to verify it fails
Run: go test . -run TestRegionCLICopy -count=1 Expected: FAIL.
- [ ] Step 3: Write the strings from the record
The drafts are shown. Write each from its record entry verbatim.
package main
import "fmt"
// The nearest-region CLI strings (spec §19 and this plan's additions), as
// approved in the nearest-region copy record. Change none of them without a
// new copy review.
// C9 to C12, by source word; anything else reads as the default.
func regionCreatedLine(place, source string) string {
switch source {
case "timezone":
return fmt.Sprintf("Region: %s (from your timezone). Use --region to choose another.", place)
case "ip":
return fmt.Sprintf("Region: %s (from your network location). Use --region to choose another.", place)
case "chosen":
return fmt.Sprintf("Region: %s.", place)
}
return fmt.Sprintf("Region: %s (the platform default). Use --region to choose another.", place)
}
// C13a
const regionNotRecordedOldConsole = "This console does not record an app region yet, so --region was not applied."
// C13b
func regionNotAppliedSwitchOff(place string) string {
return fmt.Sprintf("This console is not assigning app regions yet, so --region was not applied. This app's instances start in %s.", place)
}
// C14: the parenthesis content of "created instance {host} (...)".
func instanceCreatedSuffix(channel, place string) string {
return fmt.Sprintf("channel %s, %s", channel, place)
}
// C15
func deployRegionMismatch(app, place string) string {
return fmt.Sprintf("%s already runs in %s. --region only applies when deploy creates an app. To place one instance elsewhere, use carlos instances create --region.", app, place)
}
// C15b: deploy STOPS when it cannot read the app's region.
func deployRegionUnreadable(app string, err error) string {
return fmt.Sprintf("could not read %s's region (%v), so deploy stopped before shipping anything. Try again, or run without --region.", app, err)
}
// C15c: the console answered, but could not read the app's region.
func deployRegionUnknown(app string) string {
return fmt.Sprintf("the console could not read %s's region, so deploy stopped before shipping anything. Try again, or run without --region.", app)
}
// C25
func canaryRegionMismatch(host, place string) string {
return fmt.Sprintf("canary %s already runs in %s. --region applies only when canary creates the host. Run carlos canary remove first to start it somewhere else.", host, place)
}
// C30
func canaryRegionOnFleet(app, fleet string) string {
return fmt.Sprintf("%s runs on your fleet %s, which decides where every instance runs, so --region does not apply. Run the command without --region.", app, fleet)
}
// C30b: an older console cannot say whether a fleet governs.
func canaryFleetUnknown(app string) string {
return fmt.Sprintf("this console can't say where %s's instances run, so --region can't be checked. Update the console, or run the command without --region.", app)
}
// C30c: the canary's own record could not be read, so whether it already
// runs elsewhere is unknown.
func canaryRegionUnconfirmed(name string) string {
return fmt.Sprintf("could not confirm where canary %s runs, so nothing was changed. Try again, or run the command without --region.", name)
}
// C22a to C22d: flag help.
const (
flagRegionAppsCreate = "region this app's instances start in (default: the region nearest you)"
flagRegionDeploy = "region for the app this deploy creates (default: the region nearest you)"
flagRegionCanary = "region for the canary instance (default: the app's region)"
flagRegionInstancesCreate = "region for this instance (default: the app's region, subject to the manifest's allow-list)"
)- [ ] Step 4: Run it to verify it passes
Run: go test . -run TestRegionCLICopy -count=1 Expected: PASS.
- [ ] Step 5: Commit
git add region_copy.go region_copy_test.go
git commit -m "cli: region copy, as approved
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"---
Task 19: Timezone detection
Files:
- Create:
timezone.go(root) - Test:
timezone_test.go
Interfaces:
- Produces: -
func localTimezone(getenv func(string) string, readlink func(string) (string, error), readFile func(string) ([]byte, error)) string; -func detectTimezone() string, which wrapslocalTimezonewith theosfunctions.
- [ ] Step 1: Write the failing tests
// timezone_test.go
package main
import (
"errors"
"testing"
)
func TestLocalTimezone(t *testing.T) {
noLink := func(string) (string, error) { return "", errors.New("no") }
noFile := func(string) ([]byte, error) { return nil, errors.New("no") }
env := func(v string) func(string) string {
return func(k string) string {
if k == "TZ" {
return v
}
return ""
}
}
cases := []struct {
name string
getenv func(string) string
readlink func(string) (string, error)
readFile func(string) ([]byte, error)
want string
}{
{"TZ plain", env("Europe/London"), noLink, noFile, "Europe/London"},
{"TZ colon", env(":Europe/London"), noLink, noFile, "Europe/London"},
{"TZ path", env("/usr/share/zoneinfo/Asia/Kolkata"), noLink, noFile, "Asia/Kolkata"},
{"localtime link (linux)", env(""), func(string) (string, error) { return "/usr/share/zoneinfo/Europe/Dublin", nil }, noFile, "Europe/Dublin"},
{"localtime link (macOS)", env(""), func(string) (string, error) { return "/var/db/timezone/zoneinfo/Australia/Sydney", nil }, noFile, "Australia/Sydney"},
{"etc/timezone", env(""), noLink, func(string) ([]byte, error) { return []byte("Europe/Paris\n"), nil }, "Europe/Paris"},
// Review Focus 1: real-machine noise is a clean name or nothing.
{"etc/timezone padded", env(""), noLink, func(string) ([]byte, error) { return []byte(" America/New_York \r\n# trailing\n"), nil }, "America/New_York"},
{"TZ bare colon", env(":"), noLink, noFile, ""},
{"TZ localtime", env("localtime"), noLink, noFile, ""},
{"TZ posixrules path", env("/usr/share/zoneinfo/posixrules"), noLink, noFile, ""},
{"TZ POSIX rule string", env("EST5EDT,M3.2.0,M11.1.0"), noLink, noFile, ""},
{"nothing", env(""), noLink, noFile, ""},
}
for _, c := range cases {
if got := localTimezone(c.getenv, c.readlink, c.readFile); got != c.want {
t.Errorf("%s: %q, want %q", c.name, got, c.want)
}
}
}- [ ] Step 2: Run them to verify they fail
Run: go test . -run TestLocalTimezone -count=1 Expected: FAIL.
- [ ] Step 3: Implement
timezone.go
package main
import (
"os"
"regexp"
"strings"
)
// tzName is the shape of an IANA zone name the console will accept (spec
// §6.2 step 1). Anything else is not sent: the console then falls back to
// the request's IP, then the home.
var tzName = regexp.MustCompile(`^[A-Za-z0-9_+\-]+(/[A-Za-z0-9_+\-]+){0,2}$`)
var notAZone = map[string]bool{"localtime": true, "posixrules": true, "Local": true}
// localTimezone finds this machine's IANA zone name, portably (no build
// tags; the 20-cross gate builds it for darwin, windows and linux-arm64):
// $TZ, else the /etc/localtime symlink target, else /etc/timezone, else "".
// Windows has none of these, so only a valid $TZ is ever sent there.
func localTimezone(getenv func(string) string, readlink func(string) (string, error), readFile func(string) ([]byte, error)) string {
if tz := clean(strings.TrimPrefix(strings.TrimSpace(getenv("TZ")), ":")); tz != "" {
return tz
}
if target, err := readlink("/etc/localtime"); err == nil {
if tz := clean(target); tz != "" {
return tz
}
}
if b, err := readFile("/etc/timezone"); err == nil {
line, _, _ := strings.Cut(string(b), "\n")
return clean(line)
}
return ""
}
func clean(s string) string {
s = strings.TrimSpace(s)
if _, after, ok := strings.Cut(s, "zoneinfo/"); ok {
s = after
}
if s == "" || strings.HasPrefix(s, "/") || notAZone[s] || len(s) > 64 || !tzName.MatchString(s) {
return ""
}
return s
}
func detectTimezone() string { return localTimezone(os.Getenv, os.Readlink, os.ReadFile) }- [ ] Step 4: Run them to verify they pass, and cross-compile
Run:
go test . -run TestLocalTimezone -count=1
fail=0
for t in darwin/arm64 darwin/amd64 linux/arm64 windows/amd64 windows/arm64; do
CGO_ENABLED=0 GOOS=${t%/*} GOARCH=${t#*/} go build ./... || { echo "FAILED $t"; fail=1; }
done
[ "$fail" = 0 ] || exit 1 # stops this block before any commit below itExpected: PASS, and no FAILED line.
- [ ] Step 5: Commit
git add timezone.go timezone_test.go
git commit -m "cli: portable timezone detection (nearest-region §11)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"---
Task 20: Create-app wire, apps create --region and the onboarding line
Files:
- Modify:
apimode.go:958-1015(apiCreateAppResult,createApp) - Modify:
apps.go:442-510(runAppsCreate) - Modify:
deploy.go:513-541(offerCreateApp),deploy.go:36-60(runDeployflags) - Modify:
apps_test.go:299-321and every othercreateApp(call site (grep -n 'createApp(' *.go) - Test:
apps_region_test.go
Interfaces:
- Consumes: Tasks 18 and 19.
- Produces: -
func (s *apiSession) createApp(ctx context.Context, a apiAcct, app, place string, controlPlane bool, buildProbe, region, timezone string) (apiCreateAppResult, error); - new result fieldsRegion,RegionName,RegionSource stringandRegionsAvailable int; -func printAppRegion(w io.Writer, res apiCreateAppResult, asked string).
- [ ] Step 1: Write the failing tests
// apps_region_test.go
package main
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
)
func TestCreateAppSendsRegionAndTimezone(t *testing.T) {
var got map[string]any
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
json.NewDecoder(r.Body).Decode(&got)
json.NewEncoder(w).Encode(map[string]any{"account": "a", "app": "blog",
"region": "uk", "region_name": "UK · London", "region_source": "timezone", "regions_available": 3})
}))
defer ts.Close()
res, err := newTestSession(ts.URL).createApp(context.Background(), apiAcct{Seg: "a"}, "blog", "", false, "", "", "Europe/London")
if err != nil {
t.Fatal(err)
}
if got["timezone"] != "Europe/London" {
t.Fatalf("body = %v", got)
}
if _, sent := got["region"]; sent {
t.Fatal("region sent without --region")
}
if res.RegionName != "UK · London" || res.RegionsAvailable != 3 {
t.Fatalf("result = %+v", res)
}
}
func TestPrintAppRegion(t *testing.T) {
cases := []struct {
res apiCreateAppResult
asked string
want string
}{
{apiCreateAppResult{Region: "uk", RegionName: "UK · London", RegionSource: "timezone", RegionsAvailable: 2}, "", regionCreatedLine("UK · London", "timezone") + "\n"},
{apiCreateAppResult{Region: "ie", RegionName: "Ireland · Dublin", RegionSource: "default", RegionsAvailable: 1}, "", ""},
{apiCreateAppResult{}, "uk", regionNotRecordedOldConsole + "\n"},
{apiCreateAppResult{Region: "ie", RegionName: "Ireland · Dublin", RegionSource: "default", RegionsAvailable: 2}, "uk", regionNotAppliedSwitchOff("Ireland · Dublin") + "\n"},
{apiCreateAppResult{}, "", ""},
}
for i, c := range cases {
var b bytes.Buffer
printAppRegion(&b, c.res, c.asked)
if b.String() != c.want {
t.Errorf("case %d: %q, want %q", i, b.String(), c.want)
}
}
}- [ ] Step 2: Run them to verify they fail
Run: go test . -run 'TestCreateAppSendsRegionAndTimezone|TestPrintAppRegion' -count=1 Expected: FAIL to compile.
- [ ] Step 3: Extend the wire
In apiCreateAppResult:
// The app region the console STORED (nearest-region spec §9.1), under
// its name. Absent from an older console, which the CLI reads as "no
// app region recorded".
Region string `json:"region"`
RegionName string `json:"region_name"`
RegionSource string `json:"region_source"`
RegionsAvailable int `json:"regions_available"`Replace createApp's signature and body struct:
func (s *apiSession) createApp(ctx context.Context, a apiAcct, app, place string, controlPlane bool, buildProbe, region, timezone string) (apiCreateAppResult, error) {
in := struct {
Name string `json:"name"`
Place string `json:"place,omitempty"`
ControlPlane bool `json:"control_plane,omitempty"`
BuildProbe string `json:"build_probe,omitempty"`
Region string `json:"region,omitempty"`
Timezone string `json:"timezone,omitempty"`
}{app, place, controlPlane, buildProbe, region, timezone}Leave the rest of the function unchanged. Update every other call site (codex plan-R2 5). The callers are:
- the tests:
apimode_test.go:3041,:3052,:3060,:3066,:3099,:3106andapps_test.go:311, each gaining, "", ""; apps.go:497anddeploy.go:536, which get the real values in steps 4 and 5.
offerCreateApp also changes signature in step 5, so update its one test caller, deploy_wizard_test.go:104, to pass "" as the new last argument:
app := offerCreateApp(context.Background(), s, apiAcct{Seg: "bdk", Exact: true},
&noAppsError{Account: "tom@keymail.dev"}, "")Run grep -n 'createApp(\|offerCreateApp(' *.go afterwards. Every match must carry the new arity.
- [ ] Step 4: Add
printAppRegionand wireapps create
In region_copy.go's neighbour, a new file apps_region.go:
package main
import (
"fmt"
"io"
)
// printAppRegion is spec §11's one line after "created app …": nothing on
// a single-region deployment, C13a when an older console ignored --region,
// C13b when a console with the switch off did, else C9 to C12 by source.
func printAppRegion(w io.Writer, res apiCreateAppResult, asked string) {
switch {
case res.Region == "" && res.RegionSource == "":
if asked != "" {
fmt.Fprintln(w, regionNotRecordedOldConsole)
}
case asked != "" && res.Region != asked:
fmt.Fprintln(w, regionNotAppliedSwitchOff(res.RegionName))
case res.RegionsAvailable >= 2:
fmt.Fprintln(w, regionCreatedLine(res.RegionName, res.RegionSource))
}
}In runAppsCreate, register the flag and pass it on:
region := fs.String("region", "", flagRegionAppsCreate) res, err := sess.createApp(context.Background(), acct, strings.TrimSpace(*app), place2, *controlPlane, probe2,
strings.TrimSpace(*region), detectTimezone())After the existing "created app" line at apps.go:502:
printAppRegion(stdout, res, strings.TrimSpace(*region))Add the record's U1 text to the usage string at apps.go:463.
- [ ] Step 5: Wire
deploy's onboarding
In runDeploy's flags:
regionFlag := fs.String("region", "", flagRegionDeploy)Pass it to offerCreateApp(ctx, sess, acct, na, strings.TrimSpace(*regionFlag)). Change that function's signature to take region string, and its create call and output to:
res, err := sess.createApp(ctx, acct, name, "", false, "", region, detectTimezone())
if err != nil {
log.Fatalf("deploy: create app: %v", err)
}
fmt.Fprintf(stdout, "%s %s in account %s\n", colorFor(stdout).ok("created app"), res.App, res.Account)
printAppRegion(stdout, res, region)
return res.AppAdd the record's U2 text to runDeploy's usage string.
- [ ] Step 6: Run the root package tests
Run: go test . -count=1 -timeout 30m Expected: PASS. The e2e tests shell out through carlosTestEnv, so nothing reaches a real console.
- [ ] Step 7: Commit
git add apimode.go apps.go apps_region.go apps_region_test.go deploy.go apps_test.go apimode_test.go deploy_wizard_test.go
git commit -m "cli: apps create and deploy onboarding send a timezone, take --region, print the region line (nearest-region §11)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"---
Task 21: deploy --region on an existing app
Files:
- Modify:
apimode.go(addappRegions) - Modify:
deploy.go(runDeploy:createdNow, and the preflight before any ship) - Test:
deploy_region_test.go,deploy_region_e2e_test.go
Interfaces:
- Consumes:
deployRegionMismatch,deployRegionUnreadableanddeployRegionUnknown(Task 18). - Produces:
type apiAppDefault struct {
Region, Name, Source, Effective, Reason, InstanceDomain string
Available *bool
RegionKnown, PlacementKnown bool
}
type apiRegionEntry struct{ Code, Name string }
type apiRegionsResult struct {
Regions []apiRegionEntry `json:"regions"`
Default string `json:"default"`
AppDefault *apiAppDefault `json:"app_default"`
}
func (s *apiSession) appRegions(ctx context.Context, a apiAcct, app string) (apiRegionsResult, error)
func (r apiRegionsResult) nameOf(code string) string // a place name, else the code
func deployRegionPreflight(res apiRegionsResult, readErr error, app, asked string) errorregionE2EConsole is a root-package test helper, reused by Task 22. It counts writes, and separately the instance-create requests (creates), so Task 22 can tell a duplicate create from the other writes.
- [ ] Step 1: Write the failing unit test
// deploy_region_test.go
package main
import (
"errors"
"testing"
)
// codex plan-R1 2: a READ failure stops deploy; only a successful response
// without app_default (an older console) skips the check.
func TestDeployRegionPreflight(t *testing.T) {
uk := &apiAppDefault{Region: "uk", Name: "P-uk", Effective: "uk", RegionKnown: true, PlacementKnown: true}
fleet := &apiAppDefault{Region: "uk", Name: "P-uk", Effective: "fleet/acme", Reason: "fleet", RegionKnown: true, PlacementKnown: true}
boom := errors.New("500 from console")
cases := []struct {
name string
res apiRegionsResult
err error
asked string
want string
}{
{"no flag", apiRegionsResult{AppDefault: uk}, boom, "", ""},
{"equal", apiRegionsResult{AppDefault: uk}, nil, "uk", ""},
{"different", apiRegionsResult{AppDefault: uk}, nil, "ie", deployRegionMismatch("blog", "P-uk")},
{"older console", apiRegionsResult{}, nil, "ie", ""},
{"read failed", apiRegionsResult{}, boom, "ie", deployRegionUnreadable("blog", boom)},
{"fleet, stored equal", apiRegionsResult{AppDefault: fleet}, nil, "uk", ""},
{"fleet, stored different", apiRegionsResult{AppDefault: fleet}, nil, "ie", deployRegionMismatch("blog", "P-uk")},
// codex plan-R2 3: a 200 whose stored region could not be read.
{"region unknown", apiRegionsResult{AppDefault: &apiAppDefault{RegionKnown: false, PlacementKnown: true}}, nil, "ie", deployRegionUnknown("blog")},
}
for _, c := range cases {
err := deployRegionPreflight(c.res, c.err, "blog", c.asked)
got := ""
if err != nil {
got = err.Error()
}
if got != c.want {
t.Errorf("%s: %q, want %q", c.name, got, c.want)
}
}
}- [ ] Step 2: Run it to verify it fails
Run: go test . -run TestDeployRegionPreflight -count=1 Expected: FAIL.
- [ ] Step 3: Implement the read and the preflight
In apimode.go:
// apiAppDefault, apiRegionEntry and apiRegionsResult are the CLI's view of
// GET …/regions (nearest-region spec §9.3). AppDefault is nil from a
// console that predates it.
type apiAppDefault struct {
Region string `json:"region"`
Name string `json:"name"`
Source string `json:"source"`
Effective string `json:"effective"`
Reason string `json:"reason"`
Available *bool `json:"available"`
RegionKnown bool `json:"region_known"`
PlacementKnown bool `json:"placement_known"`
InstanceDomain string `json:"instance_domain"`
}
type apiRegionEntry struct {
Code string `json:"code"`
Name string `json:"name"`
}
type apiRegionsResult struct {
Regions []apiRegionEntry `json:"regions"`
Default string `json:"default"`
AppDefault *apiAppDefault `json:"app_default"`
}
// nameOf is the place name the console gave code, else the code itself.
func (r apiRegionsResult) nameOf(code string) string {
for _, e := range r.Regions {
if e.Code == code && e.Name != "" {
return e.Name
}
}
return code
}
func (s *apiSession) appRegions(ctx context.Context, a apiAcct, app string) (apiRegionsResult, error) {
status, body, err := s.getJSON(ctx, apiPath(a, app, "/regions"))
if err != nil {
return apiRegionsResult{}, err
}
if status != http.StatusOK {
return apiRegionsResult{}, apiErrorFromBody(status, body)
}
var out apiRegionsResult
if err := json.Unmarshal(body, &out); err != nil {
return apiRegionsResult{}, fmt.Errorf("regions: unreadable response: %w", err)
}
return out, nil
}In deploy.go:
// deployRegionPreflight is spec §11: --region on an EXISTING app is
// compared with the app's STORED region before anything ships. A failed
// read STOPS the deploy (C15b), and so does a successful answer whose
// stored region the console could not read (C15c, codex plan-R2 3):
// either way the region is unknown, and shipping would go to whatever the
// app already is. Only a successful response without app_default — an
// older console — skips the check.
func deployRegionPreflight(res apiRegionsResult, readErr error, app, asked string) error {
if asked == "" {
return nil
}
if readErr != nil {
return errors.New(deployRegionUnreadable(app, readErr))
}
if res.AppDefault == nil {
return nil
}
if !res.AppDefault.RegionKnown {
return errors.New(deployRegionUnknown(app))
}
if res.AppDefault.Region == asked {
return nil
}
return errors.New(deployRegionMismatch(app, res.AppDefault.Name))
}In runDeploy, declare createdNow := false just before if *app == "" {, and set it in the onboarding case:
case errors.As(aerr, &na) && interactiveTerminal():
*app = offerCreateApp(ctx, sess, acct, na, strings.TrimSpace(*regionFlag))
newly["app"] = *app
createdNow = trueImmediately after that block closes, before readCapabilities and any ship:
if r := strings.TrimSpace(*regionFlag); r != "" && !createdNow {
res, err := sess.appRegions(ctx, acct, *app)
if err := deployRegionPreflight(res, err, *app, r); err != nil {
log.Fatalf("deploy: %v", err)
}
}- [ ] Step 4: Run the unit test to verify it passes
Run: go test . -run TestDeployRegionPreflight -count=1 Expected: PASS.
- [ ] Step 5: Write the command-level test (codex plan-R1 2, 12)
This test runs the built carlos binary against an in-process console. It proves that every refusal happens before any write: no POST reaches the console, and no release object lands in the store.
// deploy_region_e2e_test.go
package main
import (
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"strings"
"sync/atomic"
"testing"
"time"
"github.com/carlosframework/platform/internal/console"
"github.com/carlosframework/platform/internal/release"
)
// regionE2E is an in-process console with titogo claimed at appRegion, the
// signed-in CLI credentials saved, and a front that can fail or strip the
// /regions answer and counts every write reaching the console.
type regionE2E struct {
bin string // built before useTempCredentials changes directory
creds string
url string
store release.Store
mode atomic.Value // "", "fail", "legacy", "region-unknown", "placement-unknown"
writes atomic.Int64
// creates counts instance-create requests (POST …/instances), so a
// test can tell a duplicate create from the other writes (Task 22).
creates atomic.Int64
cst *console.Store
// failGet is a store key whose reads fail while the listing succeeds,
// the way one bucket GET can (Task 22, codex plan-R3 1).
failGet atomic.Value
}
// flakyStore fails every read of one key.
type flakyStore struct {
release.Store
key *atomic.Value
}
func (f flakyStore) Get(ctx context.Context, key string) ([]byte, string, error) {
if k, _ := f.key.Load().(string); k != "" && k == key {
return nil, "", errors.New("read timeout")
}
return f.Store.Get(ctx, key)
}
func regionE2EConsole(t *testing.T, appRegion string) *regionE2E {
t.Helper()
bin := buildCarlos(t) // first: useTempCredentials chdirs out of the module
creds := useTempCredentials(t)
clearDeploymentEnv(t)
const addr, app = "paul@keymail.dev", "titogo"
tmp := t.TempDir()
e := ®ionE2E{bin: bin, creds: creds, store: release.NewDirStore(filepath.Join(tmp, "store"))}
e.mode.Store("")
e.failGet.Store("")
seed, pubB64, err := release.GenerateKey()
if err != nil {
t.Fatal(err)
}
priv, err := release.ParseSigningKey(seed, "e2e")
if err != nil {
t.Fatal(err)
}
pub, err := release.ParsePublicKey(pubB64, "e2e")
if err != nil {
t.Fatal(err)
}
e.cst, err = console.Open(filepath.Join(tmp, "console.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { e.cst.Close() })
var srv *console.Server
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet && r.Method != http.MethodHead && !strings.Contains(r.URL.Path, "/auth/") {
e.writes.Add(1)
}
if r.Method == http.MethodPost && strings.HasSuffix(r.URL.Path, "/instances") {
e.creates.Add(1)
}
if strings.HasSuffix(r.URL.Path, "/regions") {
switch e.mode.Load().(string) {
case "fail":
http.Error(w, `{"error":"boom"}`, http.StatusInternalServerError)
return
case "legacy", "region-unknown", "placement-unknown":
// A successful 200 whose body an older console, or a
// console that could not read one fact, would send.
rec := httptest.NewRecorder()
srv.ServeHTTP(rec, r)
var m map[string]any
json.Unmarshal(rec.Body.Bytes(), &m)
d, _ := m["app_default"].(map[string]any)
switch e.mode.Load().(string) {
case "legacy":
delete(m, "app_default")
case "region-unknown":
d["region_known"], d["region"], d["effective"] = false, "", ""
case "placement-unknown":
d["placement_known"], d["reason"] = false, "policy-unreadable"
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(m)
return
}
}
srv.ServeHTTP(w, r)
}))
t.Cleanup(ts.Close)
e.url = ts.URL
srv, err = console.NewServer(console.Config{
Origin: ts.URL, Keymail: "http://keymail.invalid",
Registry: filepath.Join(tmp, "registry.db"), Releases: flakyStore{e.store, &e.failGet},
ReleasePub: pub, Key: priv, AppScheme: "http", AppsDomain: "oncarlos.test",
HomeRegion: "ie",
}, e.cst)
if err != nil {
t.Fatal(err)
}
if err := e.cst.AddMember(console.OpsAccountID, addr, console.RoleOwner, time.Now()); err != nil {
t.Fatal(err)
}
if _, err := e.cst.CreateAppInRegion(console.OpsAccountID, app, addr, appRegion, "chosen", time.Now()); err != nil {
t.Fatal(err)
}
token, expiresAt := e2eDeviceLogin(t, ts.URL, e.cst, addr, "e2e@laptop")
credFile, err := loadCredentials()
if err != nil {
t.Fatal(err)
}
credFile.put(credential{Console: ts.URL, Address: addr, Token: token, ExpiresAt: expiresAt})
if err := credFile.save(); err != nil {
t.Fatal(err)
}
e.writes.Store(0) // the login above is setup, not the command under test
return e
}
// run runs the built carlos binary as a signed-in user with no project
// config in reach.
func (e *regionE2E) run(t *testing.T, args ...string) (string, error) {
t.Helper()
cmd := exec.Command(e.bin, args...)
cmd.Env = carlosTestEnv(t, "CARLOS_CREDENTIALS="+e.creds)
cmd.Dir = t.TempDir()
out, err := cmd.CombinedOutput()
return string(out), err
}
func (e *regionE2E) releases(t *testing.T) int {
t.Helper()
keys, err := e.store.List(context.Background(), release.AppPrefix(release.OpsAccount, "titogo"))
if err != nil {
t.Fatal(err)
}
n := 0
for _, k := range keys {
if strings.Contains(k, "/releases/") {
n++
}
}
return n
}
func artifact(t *testing.T) string {
t.Helper()
p := filepath.Join(t.TempDir(), "app")
if err := os.WriteFile(p, []byte("#!/bin/sh\n"), 0o755); err != nil {
t.Fatal(err)
}
return p
}
func TestDeployRegionRefusalsShipNothing(t *testing.T) {
for _, c := range []struct{ mode, want string }{
{"", deployRegionMismatch("titogo", "")[:20]}, // C15: the stored region is uk
{"fail", "deploy stopped before shipping anything"}, // C15b
{"region-unknown", deployRegionUnknown("titogo")[:30]}, // C15c: a 200 that does not know
} {
// A subtest each: useTempCredentials chdirs, and only a subtest
// restores the directory before the next case builds the binary.
t.Run("mode="+c.mode, func(t *testing.T) {
e := regionE2EConsole(t, "uk")
e.mode.Store(c.mode)
out, err := e.run(t, "deploy", "--app", "titogo", "--region", "ie", "--console", e.url,
"--channel", "canary/e2e", "--version", "abc1234", "--timeout", "1s", artifact(t))
if err == nil || !strings.Contains(out, c.want) {
t.Fatalf("err=%v out=%s, want a refusal containing %q", err, out, c.want)
}
if n := e.writes.Load(); n != 0 {
t.Fatalf("%d write(s) reached the console before the refusal", n)
}
if n := e.releases(t); n != 0 {
t.Fatalf("%d release object(s) were shipped", n)
}
})
}
}
// An older console (a successful /regions without app_default) skips the
// check, and the deploy goes on to ship.
func TestDeployRegionSkipsAgainstAnOlderConsole(t *testing.T) {
e := regionE2EConsole(t, "uk")
e.mode.Store("legacy")
out, _ := e.run(t, "deploy", "--app", "titogo", "--region", "ie", "--console", e.url,
"--channel", "canary/e2e", "--version", "abc1234", "--timeout", "1s", artifact(t))
if strings.Contains(out, deployRegionMismatch("titogo", "")[:20]) || strings.Contains(out, "deploy stopped before shipping") {
t.Fatalf("an older console refused: %s", out)
}
if e.writes.Load() == 0 || e.releases(t) == 0 {
t.Fatalf("deploy never shipped: %s", out)
}
}The want fragments come from the copy functions themselves, so they follow the approved text. The C15b fragment is a literal substring of its draft; if review changed it, use a substring of the approved text. useTempCredentials, clearDeploymentEnv, e2eDeviceLogin, loadCredentials, credential and buildCarlos are the existing root test helpers used by TestE2EDeployThroughConsole (deploy_e2e_test.go:266-330). useTempCredentials calls t.Chdir (auth_test.go:26), which is why the binary is built first.
- [ ] Step 6: Run the command-level tests
Run: go test . -run 'TestDeployRegion' -count=1 -timeout 30m Expected: PASS.
- [ ] Step 7: Commit
git add apimode.go deploy.go deploy_region_test.go deploy_region_e2e_test.go
git commit -m "cli: deploy --region checks the stored region before shipping and stops on a failed read (nearest-region §11)
Co-