carlos / platform Public

docs: nearest-region plan rev 4, after Astra's plan round 3, proved in scratch

canary --region reads its own record by host (new instance_domain on
app_default, instanceGet) before any write, so a capped or partial list
cannot let it through; pooled availability follows the allow-list; F1's
fixture commit is fixed before review; split-capability, reconcile-pairing,
persisted-golden and replica-name tests added. Each fix was run failing
then passing in a throwaway worktree, with mutation checks. Round-3
dispositions table at the end; R1-17 declined with its reason.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 5600586a46a376691588cf9aa670474f3057ba5f parent 8baddba
1 file changed, +652 −104
  • docs/superpowers/plans/2026-10-02-nearest-region-default.md +652 −104
diff --git a/docs/superpowers/plans/2026-10-02-nearest-region-default.md b/docs/superpowers/plans/2026-10-02-nearest-region-default.md
index ecbd495e4..1307f13ef 100644
--- a/docs/superpowers/plans/2026-10-02-nearest-region-default.md
+++ b/docs/superpowers/plans/2026-10-02-nearest-region-default.md
@@ -2,7 +2,7 @@
> **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:** 3.
+**Revision:** 4.
- Rev 1 (9513d52c) was reviewed by Astra
(`CODEX-PLAN-NEAREST-REGION-R1.md`, beside this file): not ready, with
@@ -10,10 +10,12 @@
- 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`).
- 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.
-- Both dispositions tables, with the proof results, are at the end.
+- The three 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.
@@ -48,7 +50,8 @@ home.
- `net/http` and `html/template`.
**Spec:** `docs/superpowers/specs/2026-10-01-nearest-region-default-design.md`
-(rev 6, approved by Paul 2026-10-02, including the R9 amendment). Read it
+(rev 6.1, approved by Paul 2026-10-02, including the R9 amendment and the
+R9a note). Read it
beside this plan. Section numbers below (§N) are the spec's.
## Global Constraints
@@ -104,7 +107,10 @@ beside this plan. Section numbers below (§N) are the spec's.
- The one exception: Unix-socket tests (`internal/host`, `internal/edge`)
need a short socket path. Run those with `TMPDIR=/tmp/g`, creating
`/tmp/g` if it is absent. Never delete it or anything in it you did not
- create.
+ create. This is the brief's explicit instruction, not an invention: a
+ Unix socket path is capped at 108 bytes, and the on-disk `$TMPDIR` here
+ (`/home/paulca/.cache/tmp/claude-1001/...`) leaves too little room for
+ Go's test directories and the socket names under them.
- 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
@@ -200,6 +206,27 @@ Then 1, 1b, 2 to 25 in order.
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.
+
+```bash
+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
@@ -248,6 +275,8 @@ plan and marked "plan".
| 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.` |
@@ -261,6 +290,7 @@ plan and marked "plan".
| 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)` |
@@ -280,7 +310,7 @@ plan and marked "plan".
| 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 in Task 5 Step 1 |
+| 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**
@@ -362,11 +392,51 @@ func TestSharedInstanceEligibilityIsTheMintPredicate(t *testing.T) {
}
}
}
+
+// 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 -count=1`
+Run: `go test ./internal/provision -run 'TestSharedInstanceEligibilityIsTheMintPredicate|TestEligibilityPairsWithTheReconcileRefusal' -count=1`
Expected: FAIL, `cfg.SharedInstanceEligibility undefined`.
- [ ] **Step 3: Implement it beside `poolServed`**
@@ -387,9 +457,14 @@ func (cfg ReconcileConfig) SharedInstanceEligibility() (sharedPool, inbound bool
- [ ] **Step 4: Run it to verify it passes**
-Run: `go test ./internal/provision -run TestSharedInstanceEligibilityIsTheMintPredicate -count=1`
+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`:
@@ -520,10 +595,20 @@ In `edgesFileLoop`, build the ident with the two new fields:
- [ ] **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`):
+
+```bash
+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)" = 4
+```
+
Run: `mkdir -p /tmp/g && TMPDIR=/tmp/g go test ./internal/host ./internal/provision -count=1`
-Expected: PASS. In particular, `TestEdgesFileRawWireBodyMatchesSchema` still
-passes. If it pins the full key set, add the two keys to its expected set,
-because the schema really did grow.
+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**
@@ -1546,14 +1631,16 @@ Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
- [ ] **Step 1: Add the dependency and the pinned fixtures (network)**
-Resolve one immutable commit first. Then fetch every fixture and licence
-file at that commit, so the files and their recorded provenance cannot
-disagree. Run this outside the sandbox if needed:
+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:
```bash
go get github.com/oschwald/maxminddb-golang@v1.13.1
-sha=$(git ls-remote https://github.com/maxmind/MaxMind-DB refs/heads/main | cut -f1)
-test -n "$sha" || { echo "could not resolve MaxMind-DB main"; exit 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"
@@ -1567,14 +1654,11 @@ 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` (record entry F1),
-with the printed sha in place of `<sha>`:
+their real names. Write `internal/geo/testdata/README` as the record's F1
+text, byte for byte, with nothing substituted:
-```
-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.
+```bash
+jq -j '.entries[] | select(.id == "F1") | .text' "$rec" > internal/geo/testdata/README
```
- [ ] **Step 2: Write the failing tests**
@@ -2712,6 +2796,8 @@ Test helpers produced for later console tasks, in
- the fixture edges `homeBox`, `ukEligible`, `ukNoKey`, `ukWrongKey`,
`ukOldWriter`, `auPoolOnly`, `usNoInbound`, `nzNoHosting`, `jpNoEIP` and
`fleetBoxFile`;
+- `splitCapability`, the same-region box pairs no single box of which is
+ eligible;
- `setEdges`, which creates or replaces, unlike `putEdges`;
- `failingList` and `captureLog`.
@@ -3014,14 +3100,50 @@ func TestAppPolicyStates(t *testing.T) {
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' -count=1`
+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**
@@ -3341,10 +3463,17 @@ about a caller.
- [ ] **Step 5: Run the tests to verify they pass**
-Run: `go test ./internal/console -run 'TestRegionCandidates|TestReadEdgesFilesStrict|TestAppPolicyStates|TestPlacements' -count=1`
+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**
```bash
@@ -3516,8 +3645,25 @@ func TestGuessRegionToleratesGarbageHints(t *testing.T) {
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
@@ -4437,6 +4583,7 @@ import (
"encoding/json"
"errors"
"net/http"
+ "regexp"
"strings"
"testing"
@@ -4615,11 +4762,56 @@ func TestFillRunsWithTheSwitchOff(t *testing.T) {
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)
+ }
+ }
+}
```
+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' -count=1`
+Run: `go test ./internal/console -run 'TestFill|TestAppDerived|TestExplicitCreateIntoAKeylessRegion|TestReplicaCreateNamesItsPlace|TestPersistedRecords' -count=1`
Expected: FAIL.
- [ ] **Step 3: Implement the fill, replacing `apiinstances.go:772`**
@@ -4724,7 +4916,9 @@ At `:1066`, after `out := recordView(rec)`:
```go
out.RegionFrom = regionFrom
- if rec.Role == "" && placed == "" {
+ // 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))
}
```
@@ -4740,6 +4934,12 @@ Then a mutation check on the R9 gate's scope. Change
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 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**
```bash
@@ -4773,8 +4973,10 @@ type appDefaultView struct {
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)
```
@@ -4786,6 +4988,21 @@ 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.
- [ ] **Step 1: Write the failing tests**
@@ -4903,6 +5120,65 @@ func TestAppDefaultKnownFlags(t *testing.T) {
}
}
+// 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)
+ }
+}
+
func TestRegionGuessEndpoint(t *testing.T) {
fx, token, _ := fillFixture(t, "")
status, _, out := rawReq(t, http.MethodPost, guessURL(fx), token, `{"tz":"Europe/London"}`)
@@ -4984,7 +5260,7 @@ func TestRegionGuessHintsNeverLeak(t *testing.T) {
- [ ] **Step 2: Run them to verify they fail**
-Run: `go test ./internal/console -run 'TestRegionsKeeps|TestAppDefault|TestRegionGuess' -count=1`
+Run: `go test ./internal/console -run 'TestRegionsKeeps|TestAppDefault|TestRegionGuess|TestPooledAvailability' -count=1`
Expected: FAIL.
- [ ] **Step 3: Implement `regiondefault.go`**
@@ -4998,6 +5274,8 @@ import (
"net"
"net/http"
"strings"
+
+ "github.com/carlosframework/platform/internal/provision"
)
// appDefaultView is §9.3's app_default. region is the STORED region (what
@@ -5005,7 +5283,8 @@ import (
// 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).
+// 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"`
@@ -5015,6 +5294,7 @@ type appDefaultView struct {
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 }
@@ -5028,6 +5308,12 @@ func (s *Server) appDefault(ctx context.Context, acct *Account, app string) appD
}
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)
}
@@ -5039,7 +5325,9 @@ func (s *Server) appDefault(ctx context.Context, acct *Account, app string) appD
v.Effective, v.Reason = pol.Placed, "fleet"
return v
case pol.pooled():
- v.Effective, v.Reason, v.Available = s.homeRegionOut(""), "pooled", boolp(true)
+ // 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).
+ v.Effective, v.Reason, v.Available = s.homeRegionOut(""), "pooled", boolp(pol.allows(""))
return v
case rerr != nil:
// The stored region is unknown, so where a create lands is too.
@@ -5060,6 +5348,21 @@ func (s *Server) appDefault(ctx context.Context, acct *Account, app string) appD
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
@@ -5448,15 +5751,15 @@ 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, from the record)
+### 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, package `main`)
- Test: `region_copy_test.go`
**Interfaces:**
-- Consumes: the record's C9 to C15, C13a, C13b, C15b, C15c, C22a to C22d, C25, C30
- and C30b (Task 0).
+- Consumes: the record's C9 to C15, C13a, C13b, C15b, C15c, C22a to C22d, C25, C30,
+ C30b and C30c (Task 0).
- Produces:
```go
@@ -5470,6 +5773,7 @@ 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
```
@@ -5501,7 +5805,7 @@ func TestRegionCLICopy(t *testing.T) {
instanceCreatedSuffix("stable", "P"), deployRegionMismatch("blog", "P"),
deployRegionUnreadable("blog", errors.New("timeout")), deployRegionUnknown("blog"),
canaryRegionMismatch("c.go.tito.io", "P"), canaryRegionOnFleet("blog", "acme"),
- canaryFleetUnknown("blog"),
+ canaryFleetUnknown("blog"), canaryRegionUnconfirmed("c"),
flagRegionAppsCreate, flagRegionDeploy, flagRegionCanary, flagRegionInstancesCreate,
}
for _, s := range append(all, lines["timezone"], lines["ip"], lines["default"], lines["chosen"]) {
@@ -5586,6 +5890,12 @@ 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)"
@@ -5989,9 +6299,9 @@ Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
```go
type apiAppDefault struct {
- Region, Name, Source, Effective, Reason string
- Available *bool
- RegionKnown, PlacementKnown bool
+ Region, Name, Source, Effective, Reason, InstanceDomain string
+ Available *bool
+ RegionKnown, PlacementKnown bool
}
type apiRegionEntry struct{ Code, Name string }
type apiRegionsResult struct {
@@ -6075,6 +6385,7 @@ type apiAppDefault struct {
Available *bool `json:"available"`
RegionKnown bool `json:"region_known"`
PlacementKnown bool `json:"placement_known"`
+ InstanceDomain string `json:"instance_domain"`
}
type apiRegionEntry struct {
@@ -6183,6 +6494,7 @@ package main
import (
"context"
"encoding/json"
+ "errors"
"net/http"
"net/http/httptest"
"os"
@@ -6208,6 +6520,22 @@ type regionE2E struct {
mode atomic.Value // "", "fail", "legacy", "region-unknown", "placement-unknown"
writes 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 {
@@ -6219,6 +6547,7 @@ func regionE2EConsole(t *testing.T, appRegion string) *regionE2E {
tmp := t.TempDir()
e := &regionE2E{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)
@@ -6273,7 +6602,7 @@ func regionE2EConsole(t *testing.T, appRegion string) *regionE2E {
e.url = ts.URL
srv, err = console.NewServer(console.Config{
Origin: ts.URL, Keymail: "http://keymail.invalid",
- Registry: filepath.Join(tmp, "registry.db"), Releases: e.store,
+ 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)
@@ -6408,25 +6737,41 @@ Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
- `runCanaryDeploy`, at `:338-470`;
- `canaryUsage`, at `:38`, with record entry U3.
- Modify: `instances_cli.go:288-330` (flag help and output)
-- Modify: `apimode.go` (`apiInstanceRecord`: add `RegionName`, `RegionFrom`)
+- Modify: `apimode.go` (`apiInstanceRecord`: add `RegionName`, `RegionFrom`;
+ add `instanceGet`)
- Test: `canary_region_test.go`, `canary_region_e2e_test.go`
**Interfaces:**
- Consumes:
- Task 18: `canaryRegionOnFleet`, `canaryFleetUnknown`,
`canaryRegionMismatch` and `instanceCreatedSuffix`;
+ - Task 18: `canaryRegionUnconfirmed` (C30c);
- Task 21: `appRegions`, `apiRegionsResult.nameOf`, `apiAppDefault`
- with `PlacementKnown`, and `regionE2EConsole`.
+ with `PlacementKnown` and `InstanceDomain`, and `regionE2EConsole`
+ with its `failGet` store;
+ - Task 15: `instance_domain` on `app_default`.
- Produces:
- - `func canaryRegionCheck(res apiRegionsResult, readErr error, recs []apiInstanceRecord, app, hostPrefix, asked string) error`;
+ - `func canaryRegionCheck(res apiRegionsResult, readErr error, app, name, asked string, get func(host string) (apiInstanceRecord, bool, error)) (string, error)`;
+ - `func (s *apiSession) instanceGet(ctx context.Context, a apiAcct, app, host string) (apiInstanceRecord, bool, error)`;
- `func instanceCreatedLine(rec apiInstanceRecord, envSuffix string) string`.
The check runs once, before `instancesEnable` (`canary.go:381`), which is a
-PUT. It uses only reads: `GET …/regions`, and `GET …/instances`, which lists
-records with or without a manifest (`apiinstances.go:409-420`). An existing
-canary is found by its host prefix, `<name>.<app>.` (`canaryHost`,
-`canary.go:210`), so the domain the PUT would return is not needed (codex
-plan-R2 4).
+PUT. It uses only reads: `GET …/regions`, then ONE
+`GET …/instances/{host}` (`handleAPIInstanceGet`, `apiinstances.go:485`,
+route `authcli.go:614`) for the canary's own host. The host is
+`canaryHost(name, app, instance_domain)` (`canary.go:210`), named from
+Task 15's `instance_domain` before any write (codex plan-R3 1).
+
+The list (`GET …/instances`) cannot decide this. It stops at 500 records
+(`instanceListMax`), sorted by host, and `provision.ListRecords` skips any
+record it cannot read (`provision.go:1192-1196`), so a canary past the cap,
+or one whose read fails, is invisible to it. The single GET answers 404 only
+when the record does not exist (`GetRecord`'s `os.ErrNotExist`); any other
+failure is a 502, which refuses with C30c.
+
+After the PUT, the host built from the PUT's domain must equal the host the
+check read. If it does not (an operator re-homed the manifest between the
+two), the command stops with C30c before any record write.
- [ ] **Step 1: Write the failing unit tests**
@@ -6439,42 +6784,70 @@ import (
"testing"
)
+// records is a fake single-record GET: a host it holds is found, any other
+// is a 404 (found false), and err fails every read.
+func records(err error, recs ...apiInstanceRecord) (func(string) (apiInstanceRecord, bool, error), *[]string) {
+ var asked []string
+ return func(host string) (apiInstanceRecord, bool, error) {
+ asked = append(asked, host)
+ if err != nil {
+ return apiInstanceRecord{}, false, err
+ }
+ for _, r := range recs {
+ if r.Host == host {
+ return r, true, nil
+ }
+ }
+ return apiInstanceRecord{}, false, nil
+ }, &asked
+}
+
func TestCanaryRegionCheck(t *testing.T) {
names := []apiRegionEntry{{Code: "ie", Name: "P-ie"}, {Code: "uk", Name: "P-uk"}}
- known := func(d apiAppDefault) *apiAppDefault { d.RegionKnown, d.PlacementKnown = true, true; return &d }
+ known := func(d apiAppDefault) *apiAppDefault {
+ d.RegionKnown, d.PlacementKnown, d.InstanceDomain = true, true, "go.tito.io"
+ return &d
+ }
plain := apiRegionsResult{Regions: names, Default: "ie", AppDefault: known(apiAppDefault{Region: "uk", Effective: "uk"})}
fleet := apiRegionsResult{Regions: names, Default: "ie", AppDefault: known(apiAppDefault{Region: "uk", Effective: "fleet/acme", Reason: "fleet"})}
older := apiRegionsResult{Regions: names, Default: "ie"} // no app_default
unread := apiRegionsResult{Regions: names, Default: "ie", AppDefault: &apiAppDefault{Region: "uk", Effective: "uk", RegionKnown: true, Reason: "policy-unreadable"}}
+ noDomain := apiRegionsResult{Regions: names, Default: "ie", AppDefault: &apiAppDefault{Region: "uk", Effective: "uk", RegionKnown: true, PlacementKnown: true}}
boom := errors.New("timeout")
- const prefix = "c.blog."
- existingUK := []apiInstanceRecord{{Host: "c.blog.go.tito.io", Region: "uk"}}
- existingHome := []apiInstanceRecord{{Host: "c.blog.go.tito.io", Region: ""}}
- other := []apiInstanceRecord{{Host: "d.blog.go.tito.io", Region: "uk"}}
+ const host = "c.blog.go.tito.io"
+ existingUK := apiInstanceRecord{Host: host, Region: "uk"}
+ existingHome := apiInstanceRecord{Host: host, Region: ""}
+ other := apiInstanceRecord{Host: "d.blog.go.tito.io", Region: "uk"}
cases := []struct {
- name string
- res apiRegionsResult
- err error
- recs []apiInstanceRecord
- asked string
- want string
+ name string
+ res apiRegionsResult
+ err error
+ recs []apiInstanceRecord
+ readErr error
+ asked string
+ want string
}{
- {"no flag", fleet, boom, existingUK, "", ""},
- {"new canary", plain, nil, nil, "uk", ""},
- {"another canary is not this one", plain, nil, other, "ie", ""},
- {"existing, same", plain, nil, existingUK, "uk", ""},
- {"existing home read as its name", plain, nil, existingHome, "ie", ""},
- {"existing, different, named by place", plain, nil, existingUK, "ie", canaryRegionMismatch("c.blog.go.tito.io", "P-uk")},
- {"fleet, first run", fleet, nil, nil, "uk", canaryRegionOnFleet("blog", "acme")},
- {"fleet, repeat", fleet, nil, existingUK, "uk", canaryRegionOnFleet("blog", "acme")},
- {"older console, first run", older, nil, nil, "uk", canaryFleetUnknown("blog")},
- {"older console, repeat", older, nil, existingUK, "uk", canaryFleetUnknown("blog")},
- {"read failed", plain, boom, nil, "uk", canaryFleetUnknown("blog")},
+ {"no flag", fleet, boom, []apiInstanceRecord{existingUK}, nil, "", ""},
+ {"new canary", plain, nil, nil, nil, "uk", ""},
+ {"another canary is not this one", plain, nil, []apiInstanceRecord{other}, nil, "ie", ""},
+ {"existing, same", plain, nil, []apiInstanceRecord{existingUK}, nil, "uk", ""},
+ {"existing home read as its name", plain, nil, []apiInstanceRecord{existingHome}, nil, "ie", ""},
+ {"existing, different, named by place", plain, nil, []apiInstanceRecord{existingUK}, nil, "ie", canaryRegionMismatch(host, "P-uk")},
+ {"fleet, first run", fleet, nil, nil, nil, "uk", canaryRegionOnFleet("blog", "acme")},
+ {"fleet, repeat", fleet, nil, []apiInstanceRecord{existingUK}, nil, "uk", canaryRegionOnFleet("blog", "acme")},
+ {"older console, first run", older, nil, nil, nil, "uk", canaryFleetUnknown("blog")},
+ {"older console, repeat", older, nil, []apiInstanceRecord{existingUK}, nil, "uk", canaryFleetUnknown("blog")},
+ {"read failed", plain, boom, nil, nil, "uk", canaryFleetUnknown("blog")},
// codex plan-R2 3: a 200 whose placement the console could not read.
- {"placement unknown", unread, nil, nil, "uk", canaryFleetUnknown("blog")},
+ {"placement unknown", unread, nil, nil, nil, "uk", canaryFleetUnknown("blog")},
+ // codex plan-R3 1: the canary's own record is read, never inferred
+ // from a list; a failed read or an unknown domain refuses.
+ {"record read failed", plain, nil, []apiInstanceRecord{existingUK}, boom, "uk", canaryRegionUnconfirmed("c")},
+ {"no instance domain", noDomain, nil, nil, nil, "uk", canaryRegionUnconfirmed("c")},
}
for _, c := range cases {
- err := canaryRegionCheck(c.res, c.err, c.recs, "blog", prefix, c.asked)
+ get, asked := records(c.readErr, c.recs...)
+ checked, err := canaryRegionCheck(c.res, c.err, "blog", "c", c.asked, get)
got := ""
if err != nil {
got = err.Error()
@@ -6482,6 +6855,12 @@ func TestCanaryRegionCheck(t *testing.T) {
if got != c.want {
t.Errorf("%s: %q, want %q", c.name, got, c.want)
}
+ if len(*asked) > 1 || (len(*asked) == 1 && (*asked)[0] != host) {
+ t.Errorf("%s: read %v, want at most one read of %s", c.name, *asked, host)
+ }
+ if c.want == "" && c.asked != "" && checked != host {
+ t.Errorf("%s: checked %q, want %q", c.name, checked, host)
+ }
}
}
@@ -6515,42 +6894,81 @@ Add to `apiInstanceRecord`:
RegionFrom string `json:"region_from,omitempty"`
```
+In `apimode.go`, beside `instanceDelete`:
+
+```go
+// instanceGet reads ONE instance record (GET …/instances/{host}). found is
+// false only on a 404, the console's answer for a host with no record; any
+// other failure is an error, never "absent" (canary --region relies on
+// that, nearest-region plan-R3 1).
+func (s *apiSession) instanceGet(ctx context.Context, a apiAcct, app, host string) (apiInstanceRecord, bool, error) {
+ status, body, err := s.getJSON(ctx, apiPath(a, app, "/instances/"+url.PathEscape(host)))
+ if err != nil {
+ return apiInstanceRecord{}, false, err
+ }
+ if status == http.StatusNotFound {
+ return apiInstanceRecord{}, false, nil
+ }
+ if status != http.StatusOK {
+ return apiInstanceRecord{}, false, apiErrorFromBody(status, body)
+ }
+ var out apiInstanceRecord
+ if err := json.Unmarshal(body, &out); err != nil {
+ return apiInstanceRecord{}, false, fmt.Errorf("instance: unreadable response: %w", err)
+ }
+ return out, true, nil
+}
+```
+
+The error text is the record's O20.
+
In `canary.go`:
```go
// canaryRegionCheck is spec §11's canary --region rule, run before ANY
// write, so the answer never depends on whether the canary exists yet
-// (codex R4-2, plan-R2 4):
+// (codex R4-2, plan-R2 4, plan-R3 1):
// - the flag needs a console that can say whether a fleet governs; a
// failed read, an older console with no app_default, or one that
// could not read the placement, refuses it on every run (C30b),
// because the create door would silently replace it with the fleet;
// - a fleet-placed app refuses the flag every time (C30);
-// - an existing canary (a record under hostPrefix, "<name>.<app>.") in
+// - the canary's OWN record is then read by host, through get (one
+// GET …/instances/{host}), never inferred from a capped list that
+// skips records it cannot read. A host the console cannot name (no
+// instance_domain) or a read that fails refuses (C30c); a record in
// another region refuses (C25), named by place.
-func canaryRegionCheck(res apiRegionsResult, readErr error, recs []apiInstanceRecord, app, hostPrefix, asked string) error {
+//
+// It returns the host it checked, which the caller holds the later
+// instancesEnable answer to.
+func canaryRegionCheck(res apiRegionsResult, readErr error, app, name, asked string, get func(host string) (apiInstanceRecord, bool, error)) (string, error) {
if asked == "" {
- return nil
+ return "", nil
}
if readErr != nil || res.AppDefault == nil || !res.AppDefault.PlacementKnown {
- return errors.New(canaryFleetUnknown(app))
+ return "", errors.New(canaryFleetUnknown(app))
}
if strings.HasPrefix(res.AppDefault.Effective, "fleet/") {
- return errors.New(canaryRegionOnFleet(app, strings.TrimPrefix(res.AppDefault.Effective, "fleet/")))
+ return "", errors.New(canaryRegionOnFleet(app, strings.TrimPrefix(res.AppDefault.Effective, "fleet/")))
}
- for _, r := range recs {
- if !strings.HasPrefix(r.Host, hostPrefix) {
- continue
- }
- have := r.Region
+ if res.AppDefault.InstanceDomain == "" {
+ return "", errors.New(canaryRegionUnconfirmed(name))
+ }
+ host := canaryHost(name, app, res.AppDefault.InstanceDomain)
+ rec, found, err := get(host)
+ if err != nil {
+ return "", errors.New(canaryRegionUnconfirmed(name))
+ }
+ if found {
+ have := rec.Region
if have == "" {
have = res.Default
}
if have != asked {
- return errors.New(canaryRegionMismatch(r.Host, res.nameOf(have)))
+ return "", errors.New(canaryRegionMismatch(host, res.nameOf(have)))
}
}
- return nil
+ return host, nil
}
```
@@ -6582,13 +7000,12 @@ After `ctx := context.Background()` and `channel := canaryChannel(canary)`
```go
// --region is checked with reads only, before the first write (the
// instancesEnable PUT below): nothing changes when it is refused.
+ checked := ""
if asked := strings.TrimSpace(*regionFlag); asked != "" {
res, rerr := sess.appRegions(ctx, acct, app2)
- existing, _, _, lerr := sess.instanceList(ctx, acct, app2, "", "")
- if lerr != nil {
- log.Fatalf("canary: list instances for %s: %v", app2, lerr)
- }
- if err := canaryRegionCheck(res, rerr, existing, app2, canary+"."+app2+".", asked); err != nil {
+ get := func(h string) (apiInstanceRecord, bool, error) { return sess.instanceGet(ctx, acct, app2, h) }
+ var err error
+ if checked, err = canaryRegionCheck(res, rerr, app2, canary, asked, get); err != nil {
log.Fatalf("canary: %v", err)
}
}
@@ -6597,6 +7014,20 @@ After `ctx := context.Background()` and `channel := canaryChannel(canary)`
`ctx` is declared at `canary.go:375`, so the block must come after it (codex
plan-R2 5).
+Right after `host := canaryHost(canary, app2, m.Domain)`:
+
+```go
+ if checked != "" && host != checked {
+ // The PUT named a different domain from the one the check read
+ // under: the canary checked is not the one about to be written.
+ log.Fatalf("canary: %s", canaryRegionUnconfirmed(canary))
+ }
+```
+
+The comment above the PUT ("the ONLY source of the domain suffix") stays
+true for every WRITE: the record is still created under the PUT's domain,
+and the check's host is held to it.
+
In the create branch, pass the flag:
`sess.instanceCreate(ctx, acct, app2, host, strings.TrimSpace(*regionFlag), channel, recKind, env, nil, nil, nil)`.
Print with:
@@ -6614,7 +7045,7 @@ the output to:
instanceCreatedLine(rec, environmentSuffix(rec.Environment)))
```
-- [ ] **Step 5: Write the command-level test (codex plan-R1 3, 12; plan-R2 3, 4)**
+- [ ] **Step 5: Write the command-level tests (codex plan-R1 3, 12; plan-R2 3, 4; plan-R3 1, 6)**
```go
// canary_region_e2e_test.go
@@ -6623,6 +7054,7 @@ package main
import (
"context"
"errors"
+ "fmt"
"os"
"strings"
"testing"
@@ -6640,12 +7072,18 @@ func TestCanaryRegionRefusesBeforeAnyWrite(t *testing.T) {
cases := []struct {
name, mode, region string
fleet, canary bool
+ ahead int // records whose hosts sort before the canary's
+ unreadable bool // the canary's record cannot be read
want string
}{
- {"fleet-placed app", "", "uk", true, false, canaryRegionOnFleet("titogo", "acme")},
- {"older console", "legacy", "uk", true, false, canaryFleetUnknown("titogo")},
- {"placement unknown", "placement-unknown", "uk", false, false, canaryFleetUnknown("titogo")},
- {"existing canary elsewhere", "", "ie", false, true, canaryRegionMismatch(existing, "uk")},
+ {"fleet-placed app", "", "uk", true, false, 0, false, canaryRegionOnFleet("titogo", "acme")},
+ {"older console", "legacy", "uk", true, false, 0, false, canaryFleetUnknown("titogo")},
+ {"placement unknown", "placement-unknown", "uk", false, false, 0, false, canaryFleetUnknown("titogo")},
+ {"existing canary elsewhere", "", "ie", false, true, 0, false, canaryRegionMismatch(existing, "uk")},
+ // codex plan-R3 1: past the list's 500-record cap, and unreadable
+ // (a list skips it), the canary's own record still decides.
+ {"existing canary past the list cap", "", "ie", false, true, 520, false, canaryRegionMismatch(existing, "uk")},
+ {"existing canary unreadable", "", "ie", false, true, 0, true, canaryRegionUnconfirmed("probe")},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
@@ -6656,6 +7094,14 @@ func TestCanaryRegionRefusesBeforeAnyWrite(t *testing.T) {
t.Fatal(err)
}
}
+ for i := 0; i < c.ahead; i++ {
+ h := fmt.Sprintf("a%03d.titogo.bab.oncarlos.test", i)
+ rec := provision.Record{Schema: 1, Host: h, App: "titogo", Account: release.OpsAccount,
+ Channel: "stable", CreatedAt: time.Now().UTC()}
+ if err := provision.CreateRecord(ctx, e.store, rec); err != nil {
+ t.Fatal(err)
+ }
+ }
if c.canary {
rec := provision.Record{Schema: 1, Host: existing, App: "titogo", Account: release.OpsAccount,
Region: "uk", Channel: "canary/probe", CreatedAt: time.Now().UTC()}
@@ -6663,6 +7109,9 @@ func TestCanaryRegionRefusesBeforeAnyWrite(t *testing.T) {
t.Fatal(err)
}
}
+ if c.unreadable {
+ e.failGet.Store(provision.RecordKey(release.OpsAccount, "titogo", existing))
+ }
e.mode.Store(c.mode)
for run := 0; run < 2; run++ {
out, err := e.run(t, "canary", "deploy", "--app", "titogo", "--name", "probe", "--region", c.region,
@@ -6675,15 +7124,18 @@ func TestCanaryRegionRefusesBeforeAnyWrite(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- wantRecs := 0
+ wantRecs := c.ahead
if c.canary {
- wantRecs = 1
+ wantRecs++
}
if len(recs) != wantRecs || e.writes.Load() != 0 || e.releases(t) != 0 {
t.Fatalf("records %d (want %d), writes %d, releases %d; want nothing written", len(recs), wantRecs, e.writes.Load(), e.releases(t))
}
- if c.canary && recs[0].Region != "uk" {
- t.Fatalf("the existing canary moved to %q", recs[0].Region)
+ if c.canary {
+ rec, err := provision.GetRecord(ctx, e.store, release.OpsAccount, "titogo", existing)
+ if err != nil || rec.Region != "uk" {
+ t.Fatalf("the existing canary = %+v %v, want it still in uk", rec, err)
+ }
}
if _, err := provision.GetManifest(ctx, e.store, release.OpsAccount, "titogo"); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("an instances manifest was written: %v", err)
@@ -6691,11 +7143,43 @@ func TestCanaryRegionRefusesBeforeAnyWrite(t *testing.T) {
})
}
}
+
+// codex plan-R3 6: a successful replica create prints C14 with its place,
+// not an older console's "(channel X)". This console has no secrets key,
+// so no box is eligible and the place falls back to its code.
+func TestInstancesCreateReplicaPrintsItsPlace(t *testing.T) {
+ e := regionE2EConsole(t, "")
+ ctx := context.Background()
+ m := provision.Manifest{Schema: 1, App: "titogo", Domain: "bab.oncarlos.test", Channel: "beta"}
+ if err := provision.PutManifest(ctx, e.store, release.OpsAccount, "titogo", m); err != nil {
+ t.Fatal(err)
+ }
+ box := `{"v":1,"box":"uk-box","eip":"1.2.3.4","region":"eu-west-2","placement":"uk","hibernation_configured":true,"hosts":{}}`
+ if _, err := e.store.Put(ctx, "control/edges/uk-box.json", []byte(box), ""); err != nil {
+ t.Fatal(err)
+ }
+ const host = "r.bab.oncarlos.test"
+ out, err := e.run(t, "instances", "create", "--app", "titogo", "--host", host, "--region", "uk", "--role", "replica", "--console", e.url)
+ if err != nil {
+ t.Fatalf("create: %v\n%s", err, out)
+ }
+ if want := "created instance " + host + " (" + instanceCreatedSuffix("beta", "uk") + ")"; !strings.Contains(out, want) {
+ t.Fatalf("stdout = %q, want %q", out, want)
+ }
+}
```
-For the existing-canary case, C25 names the region `uk` by its code: the
+For the existing-canary cases, C25 names the region `uk` by its code: the
test console has no box publishing `uk`, so `/regions` gives it no place
-name, and `nameOf` falls back to the code.
+name, and `nameOf` falls back to the code. The 520 records ahead of the
+canary sort before `probe.` and fill the list's 500-record page. The
+unreadable case fails only the store GET of the canary's record key, so the
+listing still names it and `ListRecords` skips it.
+
+`TestInstancesCreateReplicaPrintsItsPlace` is codex plan-R3 6's CLI half:
+a successful replica create prints C14 (`channel beta, uk`), where an older
+console's response gives today's `(channel beta)`. The harness console has
+no secrets key, so no box is eligible and the place falls back to its code.
- [ ] **Step 6: Run the root tests**
@@ -6708,6 +7192,12 @@ Then the ordering check: move the `--region` block from step 4 to after the
`go test . -run TestCanaryRegionRefusesBeforeAnyWrite -count=1`. It must
FAIL on writes. Move it back, and re-run: PASS.
+Then the authoritative-read check (codex plan-R3 1): replace the
+`canaryRegionCheck` call with a scan of `instanceList`'s records for the
+canary's host (rev 3's design). The `past the list cap` and `unreadable`
+cases must FAIL: the command passes the check and the PUT is sent. Restore,
+and re-run: PASS.
+
- [ ] **Step 7: Cross-compile, then commit**
```bash
@@ -7261,7 +7751,7 @@ to the follow-on spec.
## Self-review
-1. **Spec coverage (rev 6).**
+1. **Spec coverage (rev 6.1).**
| Spec | Covered in |
| --- | --- |
@@ -7275,14 +7765,14 @@ to the follow-on spec.
| §6.5 | Task 5's log test, Task 12's log tests, Task 15's redirect test |
| §7, including the R9 explicit refusal | Task 14 |
| §8 | Tasks 12, 13, 14, 20, 22 |
- | §9 | Tasks 12, 14, 15, 13 |
+ | §9, including R9a's knowledge flags | Tasks 12, 14, 15, 13 |
| §9.6 | Tasks 21, 22, with the older-console e2e cases |
| §10 | Tasks 13, 16 |
| §11 | Tasks 19 to 22 |
| §12 | Tasks 14, 15 |
| §13 (no stamp) | Task 25 step 3 |
| §14 | Tasks 4, 24 |
- | §15, §16 | the named tests in each task |
+ | §15, §16 | the named tests in each task; the persisted-record goldens and the reconcile pairing in Tasks 14 and 1 |
| §17 | Task 23, Task 24 (D6, D7), Rollout |
| §19 | Task 0, then Tasks 4, 6, 7, 18 and 24, verified by Task 25 |
@@ -7296,11 +7786,12 @@ to the follow-on spec.
- `Event.Detail` and `Event.Message`;
- the backhaul test helpers.
3. **Type consistency.**
- - `regionCandidates(files, homeName, expectedFP)` is defined in Task 10,
- and every console call site uses `s.regionCands(files)`.
+ - `regionCandidates(snap, homeName, expectedFP)` is defined in Task 10,
+ and every console call site uses `s.regionCands(snap)`.
- These are defined once and used consistently: `regionCandidate`
(`KeyOK`, `NoKeyProven`), `regionReq`, `appDefaultView`/`apiAppDefault`,
- `apiRegionsResult.nameOf`, `canaryRegionCheck(res, readErr, recs, …)`,
+ `apiRegionsResult.nameOf`, `canaryRegionCheck(res, readErr, app, name, asked, get)`,
+ `instanceGet`, `platformInstanceDomain`,
`deployRegionPreflight(res, readErr, …)`,
`instanceCreatedLine(rec, envSuffix)` and `KeyFingerprint`.
4. **Review Focus.** Five items, each with a test in its owning task: Tasks
@@ -7401,3 +7892,60 @@ Whole-package results after all of the above:
`apimode_test.go`) were run one at a time on both trees. Outcomes were
identical: 173 PASS on each, and the same 38 blocked on each by that
config. Task 25 step 2 runs them all in CI, where no such config exists.
+
+## Review dispositions (Astra, plan round 3, on b7c5b60d with spec 9ccfaca6)
+
+Astra judged `region_known` and `placement_known` sound, and Paul accepted
+them. Spec rev 6.1 (8baddbaa) records them as R9a with Astra's semantics.
+
+Every code-touching fix was proved the same way as round 2. A throwaway
+worktree was cut at the branch base (e8406ce6), and rev 3's blocks were
+replayed into it and committed there as the baseline (build, vet and the
+region tests all green). Each fix's tests were written into it first and
+run failing, then the fix was applied and they were run passing. The final
+rev 4 blocks were then copied back out of this file into the scratch tree
+and re-run, so what is written here is what was run. The scratch worktrees
+and branch were deleted afterwards.
+
+| # | sev | finding | disposition | proof |
+| --- | --- | --- | --- | --- |
+| R1-13 | P2 (partly) | Task 17's `placement discrepancy check: %v` outside the inventory | **accepted.** O19 added. This round's new strings are in too: O20 (`instance: unreadable response: %w`) and C30c. | a scan of every prose literal the rev 4 scratch diff added found exactly C30c and O20 |
+| R1-17 | P2 (partly) | `/tmp/g` invents an exception to the scratch rule | **declined.** It is the brief's explicit instruction for socket tests, and it is needed: a Unix socket path is capped at 108 bytes, and the on-disk `$TMPDIR` here is too deep for Go's test directories plus socket names. Global Constraints now states this reason. Nothing else uses `/tmp/g`, and nothing in it is deleted. | `TMPDIR=/tmp/g go test ./internal/host`: `ok` |
+| R3-1 | P1 | A capped or partial instance list let `canary --region` write | **accepted.** `GET …/regions` gains an additive `instance_domain` (Task 15). The CLI names the canary's host from it and reads that ONE record with a new `instanceGet` (`GET …/instances/{host}`, found only on a 404 being false) before any write. A failed read, or no domain, refuses with C30c. After the PUT, the host built from the PUT's domain must equal the host checked, or the command stops before any record write. | `TestAppDefaultInstanceDomain` FAIL before, PASS after (and its domain equals the PUT's own). `TestCanaryRegionRefusesBeforeAnyWrite`: with rev 3's list scan, `past_the_list_cap` (520 records ahead) and `unreadable` (the canary's store GET fails) FAIL, the command reaching the PUT. With rev 4, 6/6 PASS with 0 writes, no manifest, and the canary unchanged. Moving the check after the PUT: 6/6 FAIL with "writes 2". |
+| R3-2 | P1 | Pooled defaults declared available when the door refuses them | **accepted.** A pooled `available` is `pol.allows("")`, the allow-list's verdict on the home where a no-region pooled create lands. The `pooled` reason stays, so the Instances tab still shows nothing. | `TestPooledAvailabilityMatchesTheCreateDoor` FAIL before ("available" true for `Regions:["uk"]`), PASS after; the create door agrees in both cases |
+| R3-3 | P2 | F1's `<sha>` substitution breaks the byte-for-byte check | **accepted.** Task 0 Step 0 resolves the MaxMind-DB commit before review, so F1 is reviewed with the real sha. Task 5 reads the sha from the approved record and writes the README with `jq -j` of F1's text, substituting nothing. | round trip on a sample record: README equals F1's text byte for byte; a text naming two commits is refused |
+| R3-4 | P2 | Nothing proved eligibility is one box's facts | **accepted.** Task 10's `splitCapability` has three same-region pairs that between them publish every fact (including a keyless ordinary box beside a keyed named-pool-only box). Task 10 asserts each is ineligible, Task 11 that none is guessed, and Task 1's `TestEligibilityPairsWithTheReconcileRefusal` runs the real `Reconcile` on an ordinary record for each published answer. | all PASS. Union-of-facts mutation: `TestEligibilityNeverCombinesBoxes` FAILs on all three pairs (the guess test still passes, because a split region has no eligible box to locate it). Publishing `true, true` always: the pairing test FAILs on the three configs the reconciler refuses. |
+| R3-5 | P2 | No persisted-record compatibility tests | **accepted.** `TestPersistedRecordsMatchTheirGoldens` compares the stored bytes (`created_at` blanked). The legacy golden was taken from the base tree's own door for the same request; the app-defaulted `uk` golden differs only by `"region":"uk"`. It also asserts no steering stamp in the response. | PASS. Stamping `Steering` on app-defaulted records FAILs it. |
+| R3-6 | P2 | Replica creates lost their place name | **accepted.** `region_name` is filled for every non-fleet placement, replicas included. | `TestReplicaCreateNamesItsPlace` FAIL before, PASS after. CLI: `TestInstancesCreateReplicaPrintsItsPlace` prints `(channel beta)` before and `(channel beta, uk)` (C14) after. |
+
+A fix the proof surfaced, beyond Astra's findings:
+
+- **Task 1 Step 8:** three existing `internal/host` tests pin the raw
+ edges-file body. With `serves_shared_pool` and `accepts_inbound` always
+ published they fail on exactly those two keys. The step now updates the
+ four golden literals with a checked `sed`. Task 10's Step 5 also named a
+ test that does not exist; it now runs the real names.
+
+Whole-package results on the rev 4 scratch tree:
+
+- `go build ./...`, `go vet ./...` and `gofmt -l` clean; all five
+ cross-compile targets built.
+- `go test ./internal/console`: `ok` (383.8s). `internal/geo`,
+ `internal/appconfig`, `internal/provision` and `cmd/console`: `ok`.
+ `TMPDIR=/tmp/g go test ./internal/host`: `ok` after the Task 1 Step 8
+ update.
+- Every new and changed root test, run by name: PASS.
+- The 137 existing tests in the changed CLI files (`canary_test.go`,
+ `instances_environment_test.go`, `instances_cli_test.go`,
+ `instances_e2e_test.go`, `apimode_test.go`) were run one at a time on the
+ rev 3 and rev 4 trees. Outcomes were identical: 112 PASS on each, and the
+ same 25 blocked on each by the operator config described in round 2.
+
+For Paul, outside the findings:
+
+- `instance_domain` is a new additive field on `app_default`, not in the
+ spec. It exists only so `canary --region` can read its own record before
+ writing. Spec §9.3 may want a line for it.
+- C30c is new copy, in Task 0's inventory for the copy review.
+- C25's draft still says `carlos canary remove`; the real verb is
+ `carlos canary rm` (noted in Task 0).