docs: nearest-region plan rev 5, after Astra's plan round 4, proved in scratch
R9a holds in the pooled and fleet branches, and canary --region needs both knowledge flags; refusal lists name only what the same door accepts (one predicate for app regions, instanceChoices for instances, b variants when empty); the new-app form stays byte-identical with no picker, tested against the base tree's rendering. /tmp/g is written as the operator's rule for socket-test packages, through a socktest function that deletes its own run directory. Each fix was run failing then passing in a throwaway worktree.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 file changed,
+442
−95
docs/superpowers/plans/2026-10-02-nearest-region-default.md+442 −95
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 1307f13ef..8d4bebf9e 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:** 4. |
| +**Revision:** 5. |
| - Rev 1 (9513d52c) was reviewed by Astra |
| (`CODEX-PLAN-NEAREST-REGION-R1.md`, beside this file): not ready, with |
| @@ -12,10 +12,12 @@ |
| - 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). |
| - 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 three dispositions tables, with the proof results, are at the end. |
| +- The four 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. |
| @@ -50,8 +52,8 @@ home. |
| - `net/http` and `html/template`. |
| **Spec:** `docs/superpowers/specs/2026-10-01-nearest-region-default-design.md` |
| -(rev 6.1, approved by Paul 2026-10-02, including the R9 amendment and the |
| -R9a note). Read it |
| +(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 |
| @@ -104,13 +106,31 @@ beside this plan. Section numbers below (§N) are the spec's. |
| - Use the default Go build cache (`~/.cache/go-build`, writable inside the |
| sandbox). Do not set `GOCACHE`, `GOMODCACHE` or `GOTMPDIR`. |
| - Scratch goes under `$TMPDIR`, which is on disk. |
| - - 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. 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. |
| + - **Operator rule for this machine (socket tests).** The operator |
| + instructs that ONLY the Unix-socket test packages run with `TMPDIR` |
| + under `/tmp/g`: `internal/host`, `internal/activator`, |
| + `internal/edge`, and any other package whose tests bind a Unix socket |
| + (today also `internal/agentsock`, `internal/instancectl`, |
| + `internal/instanceexec`, `internal/unixdial` and `internal/vet`). A |
| + Unix socket path is capped at 108 bytes, and the on-disk `TMPDIR` here |
| + (`/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/g` is shared, so each run makes its |
| + own directory under it and deletes only that; nothing else in `/tmp/g` |
| + is touched. Everything else uses the on-disk `TMPDIR`. 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 |
| @@ -251,6 +271,10 @@ plan and marked "plan". |
| | 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.` | |
| @@ -539,7 +563,7 @@ func TestEdgesFileEligibilityFollowsTheReconcileConfig(t *testing.T) { |
| - [ ] **Step 6: Run them to verify they fail** |
| -Run: `TMPDIR=/tmp/g go test ./internal/host -run 'TestEdgesFile(RawWireBodyEligibilityFields|EligibilityFollowsTheReconcileConfig)' -count=1` |
| +Run: `socktest ./internal/host -run 'TestEdgesFile(RawWireBodyEligibilityFields|EligibilityFollowsTheReconcileConfig)' -count=1` |
| Expected: FAIL, unknown fields `ServesSharedPool` and `AcceptsInbound`, and |
| `edgesFileEligibility` undefined. |
| @@ -606,7 +630,7 @@ sed -i 's/"hibernation_configured":false,"generation":"` +/"hibernation_configur |
| 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` |
| +Run: `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). |
| @@ -807,7 +831,7 @@ imports. |
| - [ ] **Step 6: Run it to verify it fails** |
| -Run: `mkdir -p /tmp/g && TMPDIR=/tmp/g go test ./internal/host -run TestEdgesFileSecretsKeyFingerprint -count=1` |
| +Run: `socktest ./internal/host -run TestEdgesFileSecretsKeyFingerprint -count=1` |
| Expected: FAIL, unknown field `SecretsKeyFP`. |
| - [ ] **Step 7: Implement the field, the helper and the loop wiring** |
| @@ -862,7 +886,7 @@ In `edgesFileLoop`'s ident literal, add: |
| Run: |
| ```bash |
| -mkdir -p /tmp/g && TMPDIR=/tmp/g go test ./internal/host ./internal/appconfig -count=1 |
| +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; } |
| @@ -2031,23 +2055,23 @@ Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>" |
| --- |
| -### Task 7: Console API messages (C16 to C20, C26 to C29, C31, reasons; from the record) |
| +### 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, C26 to C29, C31, and the four `R-*` |
| - entries (Task 0). |
| +- Consumes: the record's C16 to C20, C16b to C19b, C26 to C29, C31, and |
| + the four `R-*` entries (Task 0). |
| - Produces: |
| ```go |
| func reasonText(reason string) string // absent | not-hosting | no-secrets-key | not-eligible |
| -func msgAppRegionUnavailable(code, reason string, can []string) string // C16 |
| -func msgAppRegionNotAllowed(code string, allowed []string) string // C17 |
| -func msgRegionUnknown(code string, choices []string) string // C18 |
| -func msgRegionNotArmed(code string, choices []string) string // C19 |
| +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 |
| @@ -2056,6 +2080,10 @@ func msgPlacementDiscrepancy(host, app, code string) string // C29 |
| func msgNoSecretsKey(code string) string // C31 |
| ``` |
| +Each 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** |
| ```go |
| @@ -2079,6 +2107,11 @@ func TestRegionMessagesRender(t *testing.T) { |
| 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, "%!") { |
| @@ -2088,6 +2121,11 @@ func TestRegionMessagesRender(t *testing.T) { |
| 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) |
| @@ -2134,25 +2172,39 @@ func reasonText(reason string) string { |
| return reason |
| } |
| -// C16 |
| +// 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 |
| +// 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 |
| +// 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 |
| +// 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, ", ")) |
| } |
| @@ -3796,6 +3848,10 @@ type regionReq struct { |
| 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 |
| @@ -3805,6 +3861,12 @@ func (s *Server) claimApp(ctx context.Context, acct *Account, name, addr, placem |
| `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** |
| ```go |
| @@ -3815,6 +3877,7 @@ import ( |
| "bytes" |
| "context" |
| "encoding/json" |
| + "fmt" |
| "net/http" |
| "strings" |
| "testing" |
| @@ -3893,10 +3956,15 @@ func TestAPICreateAppLocatesOnlyAPerson(t *testing.T) { |
| func TestAPICreateAppRefusesBadExplicitRegions(t *testing.T) { |
| fx, token := regionFixture(t) |
| - setEdges(t, fx, map[string]string{"nz-box": nzNoHosting}) |
| + // 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", []string{"ie", "nz", "uk"})}, |
| - {"xb", "nz", msgRegionNotArmed("nz", []string{"ie", "uk"})}, |
| + {"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) |
| @@ -3907,9 +3975,16 @@ func TestAPICreateAppRefusesBadExplicitRegions(t *testing.T) { |
| 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": "xc", "region": "uk"}, token); status != http.StatusBadRequest || errOf(res) != msgNoSecretsKey("uk") { |
| + 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) |
| } |
| } |
| @@ -4030,36 +4105,68 @@ type regionReq struct { |
| // 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 "". |
| +// 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 |
| } |
| - caps, published := placements(ctx, s.cfg.Releases) |
| - if !provision.ValidRegion(code) { |
| - return "", &doorRefusal{http.StatusBadRequest, msgRegionUnknown(code, s.namedCodes(caps))} |
| + 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) |
| } |
| - if published && !(caps[code].Reachable && caps[code].Hosting) { |
| - return "", &doorRefusal{http.StatusBadRequest, msgRegionNotArmed(code, s.namedCodes(hostingCaps(caps)))} |
| + 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 files, err := readEdgesSnapshot(ctx, s.cfg.Releases); err == nil { |
| - if c, ok := findCandidate(s.regionCands(files), code); ok && c.NoKeyProven { |
| - return "", &doorRefusal{http.StatusBadRequest, msgNoSecretsKey(code)} |
| - } |
| + if c, ok := findCandidate(g.cands, code); ok && c.NoKeyProven { |
| + return "no-key" |
| } |
| - return code, nil |
| + return "" |
| } |
| -// namedCodes lists the home's name plus every valid code in caps, sorted. |
| -func (s *Server) namedCodes(caps map[string]placementCapability) []string { |
| +// 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 caps { |
| - if provision.ValidRegion(code) { |
| - set[code] = true |
| + for code := range g.caps { |
| + if c := s.homeRegionIn(code); c != "" && g.refusal(c) == "" { |
| + set[c] = true |
| } |
| } |
| out := make([]string, 0, len(set)) |
| @@ -4070,16 +4177,6 @@ func (s *Server) namedCodes(caps map[string]placementCapability) []string { |
| return out |
| } |
| -func hostingCaps(caps map[string]placementCapability) map[string]placementCapability { |
| - out := map[string]placementCapability{} |
| - for code, c := range caps { |
| - if c.Reachable && c.Hosting { |
| - out[code] = c |
| - } |
| - } |
| - 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() { |
| @@ -4204,6 +4301,11 @@ Add to the response map: |
| Run: `go test ./internal/console -count=1` |
| Expected: PASS, including every existing claim test. |
| +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** |
| ```bash |
| @@ -4256,17 +4358,46 @@ import ( |
| "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) { |
| - fx := newConsoleFixture(t) // switch off |
| - _, _, body := get(t, signInPaul(t, fx), fx.u("/apps/new")) |
| - if strings.Contains(body, `name="region"`) { |
| - t.Fatal("switch off rendered a region picker") |
| + 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) |
| + } |
| } |
| } |
| @@ -4497,12 +4628,18 @@ In `claimOK`, replace the audit line: |
| s.audit(acct, name, addr, "create", detail) |
| ``` |
| -- [ ] **Step 5: Add the picker to `app_new.html`** |
| +- [ ] **Step 5: Add the picker to `app_new.html`, leaving the no-picker form byte-identical** |
| -Inside the `<form>`, after the name `<input>` and before the button: |
| +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. |
| ```html |
| - {{with .RegionPicker}} |
| + 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> |
| @@ -4530,8 +4667,8 @@ Inside the `<form>`, after the name `<input>` and before the button: |
| }) |
| .catch(function () {}); |
| })(); |
| - </script> |
| - {{end}} |
| + </script>{{end}} |
| + <button type="submit">Create app</button> |
| ``` |
| `html/template` renders the `{{printf …}}` in script context as a JS string |
| @@ -4542,6 +4679,11 @@ literal, so the approved C3 shape is used whatever its wording. |
| 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** |
| ```bash |
| @@ -4561,6 +4703,7 @@ Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>" |
| - `: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:** |
| @@ -4568,7 +4711,16 @@ Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>" |
| `findCandidate`, `NoKeyProven`). |
| - Produces: `apiRecordView.RegionFrom string` |
| (`json:"region_from,omitempty"`) and `apiRecordView.RegionName string` |
| - (`json:"region_name,omitempty"`). |
| + (`json:"region_name,omitempty"`), and |
| + `func (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** |
| @@ -4582,6 +4734,7 @@ package console |
| import ( |
| "encoding/json" |
| "errors" |
| + "fmt" |
| "net/http" |
| "regexp" |
| "strings" |
| @@ -4716,9 +4869,10 @@ func TestAppDerivedRefusals(t *testing.T) { |
| 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", []string{"au"})) { |
| - t.Fatalf("allow-list = %d %s, want C17", status, out) |
| + !strings.Contains(out, msgAppRegionNotAllowed("uk", nil)) { |
| + t.Fatalf("allow-list = %d %s, want C17b", status, out) |
| } |
| m.Regions = nil |
| enableInstances(t, fx, "titogo", m) |
| @@ -4802,6 +4956,40 @@ func TestPersistedRecordsMatchTheirGoldens(t *testing.T) { |
| } |
| } |
| } |
| + |
| +// 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 |
| @@ -4811,7 +4999,7 @@ unmodified door at the branch base, its stored bytes read back with |
| - [ ] **Step 2: Run them to verify they fail** |
| -Run: `go test ./internal/console -run 'TestFill|TestAppDerived|TestExplicitCreateIntoAKeylessRegion|TestReplicaCreateNamesItsPlace|TestPersistedRecords' -count=1` |
| +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`** |
| @@ -4856,18 +5044,14 @@ Expected: FAIL. |
| 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 != "" { |
| - var can []string |
| - for _, c := range cands { |
| - if c.Eligible { |
| - can = append(can, s.homeRegionOut(c.Code)) |
| - } |
| - } |
| - jsonError(w, http.StatusBadRequest, msgAppRegionUnavailable(s.homeRegionOut(region), reason, can)) |
| + 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), m.Regions)) |
| + jsonError(w, http.StatusBadRequest, msgAppRegionNotAllowed(s.homeRegionOut(region), s.instanceChoices(ctx, m.Regions, cands, region))) |
| return |
| } |
| for _, rep := range req.Replicas { |
| @@ -4879,6 +5063,53 @@ Expected: FAIL. |
| } |
| ``` |
| +Create `internal/console/regionchoices.go`: |
| + |
| +```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 |
| @@ -4934,6 +5165,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 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 |
| @@ -4943,7 +5180,7 @@ It must FAIL on the app-defaulted record. Revert, and re-run: PASS. |
| - [ ] **Step 7: Commit** |
| ```bash |
| -git add internal/console/apiinstances.go internal/console/regionfill_test.go |
| +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>" |
| @@ -5004,6 +5241,11 @@ 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** |
| ```go |
| @@ -5179,6 +5421,39 @@ func TestAppDefaultInstanceDomain(t *testing.T) { |
| } |
| } |
| +// 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"}`) |
| @@ -5261,7 +5536,8 @@ func TestRegionGuessHintsNeverLeak(t *testing.T) { |
| - [ ] **Step 2: Run them to verify they fail** |
| Run: `go test ./internal/console -run 'TestRegionsKeeps|TestAppDefault|TestRegionGuess|TestPooledAvailability' -count=1` |
| -Expected: FAIL. |
| +Expected: FAIL. `TestAppDefaultUnreadRegionHidesEffective` is the R9a case |
| +for pooled and fleet apps. |
| - [ ] **Step 3: Implement `regiondefault.go`** |
| @@ -5322,12 +5598,22 @@ func (s *Server) appDefault(ctx context.Context, acct *Account, app string) appD |
| v.Reason = "policy-unreadable" |
| return v |
| case pol.Placed != "": |
| - v.Effective, v.Reason = pol.Placed, "fleet" |
| + // 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). |
| - v.Effective, v.Reason, v.Available = s.homeRegionOut(""), "pooled", boolp(pol.allows("")) |
| + // 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. |
| @@ -6769,6 +7055,12 @@ 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. |
| +`--region` needs both knowledge flags (spec R9a, rev 6.2): the fleet check |
| +reads `effective`, which is empty when the stored region was not read. A |
| +false `region_known` refuses with C30b like a false `placement_known` |
| +(codex plan-R4 1), and the e2e has a fleet-placed case with the region |
| +unread that must refuse with zero writes. |
| + |
| 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. |
| @@ -6813,6 +7105,8 @@ func TestCanaryRegionCheck(t *testing.T) { |
| 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}} |
| + regionUnknownFleet := apiRegionsResult{Regions: names, Default: "ie", AppDefault: &apiAppDefault{PlacementKnown: true, Reason: "fleet", InstanceDomain: "go.tito.io"}} |
| + regionUnknown := apiRegionsResult{Regions: names, Default: "ie", AppDefault: &apiAppDefault{PlacementKnown: true, InstanceDomain: "go.tito.io"}} |
| boom := errors.New("timeout") |
| const host = "c.blog.go.tito.io" |
| existingUK := apiInstanceRecord{Host: host, Region: "uk"} |
| @@ -6844,6 +7138,10 @@ func TestCanaryRegionCheck(t *testing.T) { |
| // 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")}, |
| + // codex plan-R4 1, R9a: with the stored region unread, effective is |
| + // empty, so a fleet cannot be seen; the flag is refused either way. |
| + {"region unknown, fleet", regionUnknownFleet, nil, nil, nil, "uk", canaryFleetUnknown("blog")}, |
| + {"region unknown", regionUnknown, nil, nil, nil, "uk", canaryFleetUnknown("blog")}, |
| } |
| for _, c := range cases { |
| get, asked := records(c.readErr, c.recs...) |
| @@ -6930,7 +7228,8 @@ In `canary.go`: |
| // (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), |
| +// could not read the placement or the stored region (either flag |
| +// false, R9a), 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); |
| // - the canary's OWN record is then read by host, through get (one |
| @@ -6945,7 +7244,9 @@ func canaryRegionCheck(res apiRegionsResult, readErr error, app, name, asked str |
| if asked == "" { |
| return "", nil |
| } |
| - if readErr != nil || res.AppDefault == nil || !res.AppDefault.PlacementKnown { |
| + // Both flags: effective is only stated when the stored region was read |
| + // too (R9a), and the fleet check below reads effective (codex plan-R4 1). |
| + if readErr != nil || res.AppDefault == nil || !res.AppDefault.PlacementKnown || !res.AppDefault.RegionKnown { |
| return "", errors.New(canaryFleetUnknown(app)) |
| } |
| if strings.HasPrefix(res.AppDefault.Effective, "fleet/") { |
| @@ -7084,6 +7385,9 @@ func TestCanaryRegionRefusesBeforeAnyWrite(t *testing.T) { |
| // (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")}, |
| + // codex plan-R4 1: a fleet-placed app whose stored region the console |
| + // could not read; R9a leaves effective empty, and the flag is refused. |
| + {"fleet, region unknown", "region-unknown", "uk", true, false, 0, false, canaryFleetUnknown("titogo")}, |
| } |
| for _, c := range cases { |
| t.Run(c.name, func(t *testing.T) { |
| @@ -7405,7 +7709,7 @@ func TestServiceGateAgainstARealBackhaul(t *testing.T) { |
| } |
| ``` |
| -Run: `mkdir -p /tmp/g && TMPDIR=/tmp/g go test ./internal/edge -run TestServiceGateAgainstARealBackhaul -count=1` |
| +Run: `socktest ./internal/edge -run TestServiceGateAgainstARealBackhaul -count=1` |
| Expected: PASS. If the first gate fails because the backhaul does not carry |
| `X-Carlos-Version` through, the gate is right and the probe instance must set |
| that header. Read `internal/edge`'s response copy path before changing |
| @@ -7646,13 +7950,13 @@ for t in darwin/arm64 darwin/amd64 linux/arm64 windows/amd64 windows/arm64; do |
| done |
| [ "$fail" = 0 ] || exit 1 |
| (cd launchctx && go vet ./... && go test ./... -count=1) |
| -# Unix-socket packages need a short path; /tmp/g is the one place this plan |
| -# uses /tmp, and only for that (it is RAM-backed tmpfs, not disk). |
| -mkdir -p /tmp/g |
| -TMPDIR=/tmp/g go test ./internal/host/... ./internal/edge/... -count=1 |
| -# Everything else on the on-disk TMPDIR the environment provides (inside |
| -# the sandbox, $TMPDIR is on disk): console tests need it. |
| -go test $(go list ./... | grep -v -e /internal/host -e /internal/edge) -count=1 -timeout 30m |
| +# The operator rule (Global Constraints): the Unix-socket test packages run |
| +# through socktest, in a fresh short directory under /tmp/g that is deleted |
| +# after the run; everything else uses the on-disk TMPDIR, which the console |
| +# tests need. socktest is the function defined there. |
| +sock='/internal/(host|activator|edge|agentsock|instancectl|instanceexec|unixdial|vet)(/|$)' |
| +socktest $(go list ./... | grep -E "$sock") -count=1 |
| +go test $(go list ./... | grep -vE "$sock") -count=1 -timeout 30m |
| ``` |
| Expected: the script exits 0, with no `FAILED` line, and |
| @@ -7751,7 +8055,7 @@ to the follow-on spec. |
| ## Self-review |
| -1. **Spec coverage (rev 6.1).** |
| +1. **Spec coverage (rev 6.2).** |
| | Spec | Covered in | |
| | --- | --- | |
| @@ -7765,7 +8069,7 @@ 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, including R9a's knowledge flags | Tasks 12, 14, 15, 13 | |
| + | §9, including R9a's knowledge flags and rev 6.2's `instance_domain` | Tasks 12, 14, 15, 13, 22 | |
| | §9.6 | Tasks 21, 22, with the older-console e2e cases | |
| | §10 | Tasks 13, 16 | |
| | §11 | Tasks 19 to 22 | |
| @@ -7791,7 +8095,8 @@ to the follow-on spec. |
| - These are defined once and used consistently: `regionCandidate` |
| (`KeyOK`, `NoKeyProven`), `regionReq`, `appDefaultView`/`apiAppDefault`, |
| `apiRegionsResult.nameOf`, `canaryRegionCheck(res, readErr, app, name, asked, get)`, |
| - `instanceGet`, `platformInstanceDomain`, |
| + `instanceGet`, `platformInstanceDomain`, `explicitGate`, |
| + `instanceChoices`, |
| `deployRegionPreflight(res, readErr, …)`, |
| `instanceCreatedLine(rec, envSuffix)` and `KeyFingerprint`. |
| 4. **Review Focus.** Five items, each with a test in its owning task: Tasks |
| @@ -7949,3 +8254,45 @@ For Paul, outside the findings: |
| - 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). |
| + |
| +## Review dispositions (Astra, plan round 4, on 5600586a with spec 8baddbaa) |
| + |
| +Astra judged `instance_domain` sound, including the check of the PUT's |
| +answer against the preflight host, and Paul approved it on 2026-10-03. Spec |
| +rev 6.2 (56756f09) records it in §9.3 beside R9a, with the R9a consequence |
| +finding 1 found. |
| + |
| +The proof was done as in rounds 2 and 3. A throwaway worktree was cut at the |
| +branch base (e8406ce6), rev 4's blocks were replayed and committed there as |
| +the baseline (Task 13 included this time), and the region tests were green |
| +on it. Each fix's tests were written first and run failing, then the fix |
| +was applied and they were run passing. The changed blocks were then |
| +compared with the scratch files byte for byte; every one matched. The |
| +scratch worktrees and branch were deleted afterwards. |
| + |
| +| # | sev | finding | disposition | proof | |
| +| --- | --- | --- | --- | --- | |
| +| R1-17 | P2 (partly) | `/tmp/g` exception, and the scripts' `/tmp` fallbacks | **resolved as an operator rule.** Global Constraints now states the operator's rule for this machine: only the Unix-socket test packages (`internal/host`, `internal/activator`, `internal/edge`, and the other packages whose tests bind a Unix socket, listed there) run with `TMPDIR` under `/tmp/g`, because socket paths are capped at 108 bytes and the on-disk `TMPDIR` is too deep. The directory is small and is deleted after the run, and everything else uses the on-disk `TMPDIR`. CI is unaffected. Every socket-test command goes through one `socktest` function, which makes a fresh `/tmp/g/nr.XXXXXX` and deletes it after the run, so nothing else in the shared `/tmp/g` is touched. Task 25 splits the packages the same way. The two shipped scripts keep `${TMPDIR:-/tmp}`: they run on operator machines and in CI, where an unset `TMPDIR` means the system default, and on this machine `TMPDIR` is always set, so they never reach `/tmp` here. | `socktest` over the eight socket packages: `/tmp/g` listing identical before and after. `host`, `instancectl`, `instanceexec`, `unixdial`, `vet`, `agentsock` `ok`. `edge` (1 test) and `activator` (2 to 3 exec-backend tests, 2s timeouts) fail identically on the unmodified base, so they are this machine's, not the branch's | |
| +| R4-1 | P1 | Pooled and fleet branches broke R9a when the stored region was unread | **accepted.** Task 15: in both branches `effective` and `available` are set only after a successful region read; `reason` (`pooled`, `fleet`) still says what the placement is. Task 22: `canary --region` needs both flags, because its fleet check reads `effective`; a false `region_known` refuses with C30b. Spec rev 6.2 states this. | `TestAppDefaultUnreadRegionHidesEffective` FAIL before (pooled: `effective:ie`, `available` set; fleet: `effective:fleet/acme`), PASS after. `TestCanaryRegionCheck`'s two region-unknown cases FAIL before, PASS after. e2e `fleet, region unknown`: rev 4's CLI goes past the check; after, it refuses with C30b twice, 0 writes, no manifest | |
| +| R4-2 | P2 | Refusals listed choices the door then refused | **accepted.** Task 12: one predicate, `explicitGate.refusal`, decides both acceptance and the C18/C19 lists (home's name, plus published codes that are valid, armed and hosting, and not proved keyless). Task 14: a new `instanceChoices` lists, for C16 and C17, what an explicit request for the same socket-backed instance would pass: the allow-list as the door reads it, the armed gate and R9, minus the refused region. An empty list gets a new `b` variant (C16b to C19b, in Task 0 for copy review) instead of "Choose from: .". | Both tests submit every advertised code under the same fixture and expect acceptance. Before: C18/C19 listed `au` (keyless) and `nz` (not hosting); C16 listed `ie, au` with `Regions:["uk","au","nz"]`; C17 listed `au, nz, ie`. After: `ie, uk`; `au`; `au`, each accepted. Mutations: an allow-list-blind `instanceChoices`, and an app list that takes `no-key` codes, each FAIL. `TestAppDerivedRefusals` now expects C17b (its `au` publishes no box) | |
| +| R4-3 | P2 | The unchanged-form contract was untested and broken | **accepted.** The picker block now opens on the name input's line and closes on the script's line, so with no picker the template emits today's bytes. `TestNewAppPageWithoutPickerIsUnchanged` compares the rendered form with the base tree's own rendering (CSRF blanked), with the switch off and with one eligible region. | FAIL before in both cases (an extra `" \n"` before the button); PASS after. The picker tests still pass | |
| + |
| +Whole-package results on the rev 5 scratch tree: |
| + |
| +- `go build ./...`, `go vet ./...` and `gofmt -l` clean; all five |
| + cross-compile targets built. |
| +- `go test ./internal/console`: `ok` (142.0s). `internal/geo`, |
| + `internal/appconfig`, `internal/provision` and `cmd/console`: `ok`. |
| + `internal/host` through `socktest`: `ok`. |
| +- Every new and changed root test, run by name: PASS, including the 7 |
| + canary e2e cases and the replica output test. |
| +- A scan of the prose literals rev 5 adds to product code found exactly |
| + C16b to C19b. |
| + |
| +For Paul, outside the findings: |
| + |
| +- C16b, C17b, C18b and C19b are new copy, in Task 0's inventory. |
| +- Spec rev 6.2 also writes down that `canary --region` needs |
| + `region_known` as well as `placement_known`. It follows from R9a, which |
| + leaves `effective` empty without the stored region, rather than being a |
| + new ruling. |