carlos / platform Public

geoip plan 1: concrete Task 9 tests (geoLookup seam), tunnel via-mark, upload+job, reconciled verdict

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 814df726f1b6941f6bb6c4b0421ca8cd54efc669 parent feef9a5
1 file changed, +262 −85
  • docs/superpowers/plans/2026-10-06-platform-geoip-plan1.md +262 −85

CI cancelled — superseded by a newer push — run details

diff --git a/docs/superpowers/plans/2026-10-06-platform-geoip-plan1.md b/docs/superpowers/plans/2026-10-06-platform-geoip-plan1.md
index 49d83f51e..d25916679 100644
--- a/docs/superpowers/plans/2026-10-06-platform-geoip-plan1.md
+++ b/docs/superpowers/plans/2026-10-06-platform-geoip-plan1.md
@@ -2770,7 +2770,7 @@ Then wire it into `Edge`:
geoTunnelClient bool
```
-In `New(...)`, initialise `e.geo = geo.NewSwappable()`. Add the option and the accessor:
+In `New(...)`, initialise `e.geo = geo.NewSwappable()` and `e.geoLookup = e.geo.Lookup`. Add the field `geoLookup func(net.IP) geo.Snapshot` beside `geo`, commented `// the lookup decideGeo calls; e.geo.Lookup in production, a spy in tests (loopback test traffic is never Locatable)`. Add the option and the accessor:
```go
// WithGeoTunnelClient marks this edge as a tunnel client's: it never mirrors
@@ -2976,152 +2976,329 @@ git commit -m "status: GeoIP block on every box heartbeat; agent marks tunnel cl
var fromTunnel = func(ctx context.Context) bool { return false } // Task 10 points it at tunnelclient.FromTunnel
```
-- [ ] **Step 1: Write the failing tests.** Use `testEdgeWithStore`-style construction with a header-echo backend, and drive it with `httptest.NewRecorder` so `RemoteAddr` can be a public IP. Read `edge_test.go:274` (`testEdgeWithStore`) and `homemap_test.go:322-387` (`hopEdge`, `hopRequest`) first, and reuse them.
+- [ ] **Step 1: Write the failing tests.** `internal/edge/geoipheaders_test.go`:
```go
package edge
import (
+ "context"
+ "encoding/json"
"net"
"net/http"
"net/http/httptest"
+ "net/http/httputil"
+ "sync/atomic"
"testing"
+ "time"
"github.com/carlosframework/platform/internal/geo"
"github.com/carlosframework/platform/internal/geoip"
+ "github.com/carlosframework/platform/internal/registry"
)
-// geoEchoEdge returns an edge serving app.test from a backend that echoes
-// the geo headers it received, with the City fixture loaded.
-func geoEchoEdge(t *testing.T) (*Edge, func(*http.Request) http.Header) {
+var geoHeaderNames = []string{geoip.HeaderCountry, geoip.HeaderSubdivision, geoip.HeaderLat, geoip.HeaderLon, geoip.HeaderSource, geoip.HeaderHop}
+
+// geoEchoBackend answers with the geo headers it received, as JSON.
+func geoEchoBackend(t *testing.T) *httptest.Server {
t.Helper()
- got := make(chan http.Header, 1)
- backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- h := http.Header{}
- for _, k := range []string{geoip.HeaderCountry, geoip.HeaderSubdivision, geoip.HeaderLat, geoip.HeaderLon, geoip.HeaderSource, geoip.HeaderHop} {
- if v, ok := r.Header[http.CanonicalHeaderKey(k)]; ok {
- h[k] = v
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ got := map[string]string{}
+ for _, k := range geoHeaderNames {
+ if v := r.Header.Get(k); v != "" {
+ got[k] = v
}
}
- select {
- case got <- h:
- default:
- }
- w.Header().Set("Content-Type", "text/plain")
- w.Write([]byte("ok"))
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(got)
}))
- t.Cleanup(backend.Close)
- e := newTCPRouteEdge(t, "app.test", backend.Listener.Addr().String()) // build from the helpers edge_test.go uses for a tcp upstream
- db, err := geo.OpenMMDB("../geo/testdata/GeoIP2-City-Test.mmdb")
+ t.Cleanup(ts.Close)
+ return ts
+}
+
+// geoEdge serves host from a geo-echo backend.
+func geoEdge(t *testing.T, host string) *Edge {
+ t.Helper()
+ backend := geoEchoBackend(t)
+ reg := openReg(t)
+ if err := reg.Upsert(registry.Route{Host: host, Kind: "service", Addr: backend.Listener.Addr().String(), Status: "ready"}); err != nil {
+ t.Fatal(err)
+ }
+ e, err := New(reg, nil)
if err != nil {
t.Fatal(err)
}
- e.geo.Swap(db)
- return e, func(r *http.Request) http.Header {
- w := httptest.NewRecorder()
- e.ServeHTTP(w, r)
- select {
- case h := <-got:
- return h
- default:
- t.Fatalf("backend not reached: %d %s", w.Code, w.Body.String())
- return nil
+ t.Cleanup(e.probers.Stop)
+ return e
+}
+
+// spyGeo makes every lookup a hit for country and counts the calls.
+func spyGeo(e *Edge, country string) *atomic.Int64 {
+ var n atomic.Int64
+ e.geoLookup = func(net.IP) geo.Snapshot {
+ n.Add(1)
+ return geo.Snapshot{Loaded: true, Found: true, Source: "TestDB-" + country,
+ Result: geo.Result{Country: country, Point: geo.Point{Lat: 1.234, Lon: 5.678}, HasPoint: true}}
+ }
+ return &n
+}
+
+// serveGeo drives one request through ServeHTTP and returns the headers the
+// app saw.
+func serveGeo(t *testing.T, e *Edge, r *http.Request) (int, map[string]string) {
+ t.Helper()
+ w := httptest.NewRecorder()
+ e.ServeHTTP(w, r)
+ got := map[string]string{}
+ if w.Code == http.StatusOK {
+ if err := json.Unmarshal(w.Body.Bytes(), &got); err != nil {
+ t.Fatalf("backend body %q: %v", w.Body.String(), err)
}
}
+ return w.Code, got
}
-func publicReq(host, from string) *http.Request {
+func geoReq(host, from string) *http.Request {
r := httptest.NewRequest("GET", "http://"+host+"/", nil)
r.RemoteAddr = net.JoinHostPort(from, "4444")
return r
}
-func TestDirectRequestGetsGeoHeaders(t *testing.T) {
- _, serve := geoEchoEdge(t)
- h := serve(publicReq("app.test", "81.2.69.142"))
- if h.Get(geoip.HeaderCountry) != "GB" || h.Get(geoip.HeaderSubdivision) != "GB-ENG" ||
- h.Get(geoip.HeaderSource) != "GeoIP2-City" || h.Get(geoip.HeaderLat) == "" {
- t.Fatalf("headers = %v", h)
+func TestDirectRequestGetsGeoHeadersFromTheRealReader(t *testing.T) {
+ e := geoEdge(t, "app.test")
+ db, err := geo.OpenMMDB("../geo/testdata/GeoIP2-City-Test.mmdb")
+ if err != nil {
+ t.Fatal(err)
+ }
+ e.geo.Swap(db)
+ code, h := serveGeo(t, e, geoReq("app.test", "81.2.69.142"))
+ if code != 200 || h[geoip.HeaderCountry] != "GB" || h[geoip.HeaderSubdivision] != "GB-ENG" ||
+ h[geoip.HeaderSource] != "GeoIP2-City" || h[geoip.HeaderLat] != "51.51" || h[geoip.HeaderLon] != "-0.09" {
+ t.Fatalf("app saw %v", h)
+ }
+ if _, leaked := h[geoip.HeaderHop]; leaked {
+ t.Fatal("X-Carlos-Hop-Geo reached the app")
}
- if h.Get(geoip.HeaderHop) != "" {
- t.Fatal("the internal hop header reached the app")
+}
+
+func TestNoDatabaseMeansNoHeaders(t *testing.T) {
+ e := geoEdge(t, "app.test")
+ r := geoReq("app.test", "81.2.69.142")
+ r.Header.Set(geoip.HeaderCountry, "KP")
+ if _, h := serveGeo(t, e, r); len(h) != 0 {
+ t.Fatalf("app saw %v with no database loaded", h)
}
}
+// Review Focus 1.
func TestForgedGeoHeadersNeverReachTheApp(t *testing.T) {
- _, serve := geoEchoEdge(t)
+ e := geoEdge(t, "app.test")
+ spyGeo(e, "GB")
for name, mutate := range map[string]func(*http.Request){
"plain forgery": func(r *http.Request) {
r.Header.Set(geoip.HeaderCountry, "KP")
- r.Header.Set(geoip.HeaderHop, "hit;src=x")
+ r.Header.Set(geoip.HeaderSource, "forged")
+ r.Header.Set(geoip.HeaderHop, "hit;src=forged")
},
"connection nomination": func(r *http.Request) {
r.Header.Set("Connection", "X-Carlos-Geo-Country, X-Carlos-Geo-Source")
},
- "forgery from a private address": func(r *http.Request) {
- r.RemoteAddr = "10.0.0.1:1"
- r.Header.Set(geoip.HeaderCountry, "KP")
- },
} {
- r := publicReq("app.test", "81.2.69.142")
+ r := geoReq("app.test", "81.2.69.142")
mutate(r)
- h := serve(r)
- if c := h.Get(geoip.HeaderCountry); c == "KP" {
- t.Errorf("%s: forged country reached the app", name)
- }
- if name == "connection nomination" && h.Get(geoip.HeaderCountry) != "GB" {
- t.Errorf("%s: the edge's own country was removed: %v", name, h)
+ _, h := serveGeo(t, e, r)
+ if h[geoip.HeaderCountry] != "GB" || h[geoip.HeaderSource] != "TestDB-GB" {
+ t.Errorf("%s: app saw %v, want the edge's own GB answer", name, h)
}
- if h.Get(geoip.HeaderHop) != "" {
+ if _, leaked := h[geoip.HeaderHop]; leaked {
t.Errorf("%s: hop header reached the app", name)
}
}
}
-func TestVerifiedHopAdoptsWithoutLookingUp(t *testing.T) {
- // Use hopEdge/hopRequest (homemap_test.go). Send hop-signed headers
- // carrying X-Carlos-Hop-Geo "hit;src=GeoLite2-City" and a country "FR";
- // with a counting lookup installed in e.geo, assert the app sees FR and
- // the lookup count is 0.
-}
-
-func TestRejectedHopGetsNoGeo(t *testing.T) {
- // hopRequest with a WRONG signature (signHop under a different key) from
- // RemoteAddr 81.2.69.142: served (status 200, as today), and the app
- // sees no X-Carlos-Geo-* at all (spec §8.2 rejected hop, codex R2-1).
-}
-
-func TestDoorRequestGetsNoGeo(t *testing.T) {
- // Build a door request the way door_test.go does (doorRequest / the
- // door context mark) with the box's public address 81.2.69.142: the app
- // sees no geo headers.
+// meshGeoEdge is geoEdge plus a minted mesh key, so hop requests can be
+// signed (homemap_test.go's hopEdge, with a geo-echo backend).
+func meshGeoEdge(t *testing.T, host string) (*Edge, []byte) {
+ t.Helper()
+ e := geoEdge(t, host)
+ s := meshTestStore(t)
+ if err := mintMeshKey(context.Background(), s); err != nil {
+ t.Fatal(err)
+ }
+ m := newHomeMap(s, "home")
+ refreshHomeMap(t, m)
+ e.homes.Store(m)
+ key, _ := m.meshKeys()
+ return e, key
+}
+
+func TestVerifiedHopAdoptsTheSendersAnswerWithoutLookingUp(t *testing.T) {
+ e, key := meshGeoEdge(t, "app.test")
+ spy := spyGeo(e, "XX") // a lookup here would show as XX
+ r := geoReq("app.test", "198.51.100.7") // the sending edge's address
+ r.Header.Set(hopHeader, "1")
+ r.Header.Set(hopAuthHeader, signHop(key, "app.test", time.Now()))
+ r.Header.Set("X-Forwarded-For", "81.2.69.142")
+ r.Header.Set(geoip.HeaderHop, "hit;src=GeoLite2-City")
+ r.Header.Set(geoip.HeaderCountry, "FR")
+ code, h := serveGeo(t, e, r)
+ if code != 200 || h[geoip.HeaderCountry] != "FR" || h[geoip.HeaderSource] != "GeoLite2-City" {
+ t.Fatalf("app saw %d %v, want the adopted FR", code, h)
+ }
+ if spy.Load() != 0 {
+ t.Fatal("the home edge looked the visitor up instead of adopting")
+ }
+}
+
+// Review Focus 2, codex R2-1.
+func TestRejectedHopIsServedWithNoGeo(t *testing.T) {
+ e, _ := meshGeoEdge(t, "app.test")
+ spy := spyGeo(e, "XX")
+ r := geoReq("app.test", "81.2.69.142")
+ r.Header.Set(hopHeader, "1")
+ r.Header.Set(hopAuthHeader, signHop([]byte("not-the-mesh-key-not-the-mesh-key"), "app.test", time.Now()))
+ r.Header.Set(geoip.HeaderHop, "hit;src=GeoLite2-City")
+ r.Header.Set(geoip.HeaderCountry, "FR")
+ code, h := serveGeo(t, e, r)
+ if code != 200 {
+ t.Fatalf("a rejected hop must still be served (as today), got %d", code)
+ }
+ if len(h) != 0 || spy.Load() != 0 {
+ t.Fatalf("rejected hop: app saw %v, lookups %d; want nothing and no lookup", h, spy.Load())
+ }
+}
+
+func TestDecideGeoDoorIsOffWithoutALookup(t *testing.T) {
+ e := geoEdge(t, "app.test")
+ spy := spyGeo(e, "GB")
+ a := e.decideGeo(geoReq("app.test", "81.2.69.142"), geoip.Captured{}, false, false, true)
+ if a.State != geoip.StateOff || spy.Load() != 0 {
+ t.Fatalf("door = %+v, lookups %d", a, spy.Load())
+ }
+}
+
+func TestDoorCallReachesTheAppWithNoGeo(t *testing.T) {
+ s := newDoorScene(t)
+ spy := spyGeo(s.e, "GB")
+ rec := s.do(t, "GET", doorOwnHost, "/", s.key, map[string]string{geoip.HeaderCountry: "KP"}, nil)
+ if rec.Code != http.StatusOK {
+ t.Fatalf("door call = %d %s", rec.Code, rec.Body.String())
+ }
+ if spy.Load() != 0 {
+ t.Fatal("a door call was looked up: it would locate the box itself")
+ }
+ // The own upstream (newDoorUpstream) records what it received; assert it
+ // saw no X-Carlos-Geo-* header. Read newDoorUpstream for its accessor
+ // (door_test.go) and assert through it.
+}
+
+func TestSetGeoForBackhaulCarriesStateAndAppsNeverSeeIt(t *testing.T) {
+ in := httptest.NewRequest("GET", "http://app.test/", nil)
+ for _, c := range []struct {
+ a geoip.Answer
+ forward bool
+ hop string
+ }{
+ {geoip.Answer{State: geoip.StateHit, Country: "GB", Source: "S"}, true, "hit;src=S"},
+ {geoip.Off(), true, "off"}, // a door call being backhauled
+ {geoip.Answer{State: geoip.StateHit, Country: "GB", Source: "S"}, false, ""},
+ } {
+ pr := &httputil.ProxyRequest{In: in.WithContext(context.WithValue(in.Context(), geoResultKey{}, c.a)), Out: in.Clone(context.Background())}
+ pr.Out.Header.Set(geoip.HeaderHop, "hit;src=forged")
+ pr.Out.Header.Set(geoip.HeaderCountry, "KP")
+ setGeo(pr, c.forward)
+ if got := pr.Out.Header.Get(geoip.HeaderHop); got != c.hop {
+ t.Errorf("%+v forward=%v: hop = %q, want %q", c.a, c.forward, got, c.hop)
+ }
+ if got := pr.Out.Header.Get(geoip.HeaderCountry); got == "KP" {
+ t.Errorf("%+v: forged country survived setGeo", c.a)
+ }
+ }
+ // A request that never passed ServeHTTP (the scheduler's UnixProxy) gets nothing.
+ pr := &httputil.ProxyRequest{In: in, Out: in.Clone(context.Background())}
+ pr.Out.Header.Set(geoip.HeaderCountry, "KP")
+ setGeo(pr, false)
+ if pr.Out.Header.Get(geoip.HeaderCountry) != "" {
+ t.Fatal("no context answer must mean no geo headers")
+ }
}
-func TestBackhaulCarriesHopGeoAndNeverAppHeadersWithoutIt(t *testing.T) {
- // Using newBackhaulScene (backhaul_e2e_test.go:373): a request from a
- // public client address to the sending edge reaches the home edge's app
- // with the sender's country; a door call backhauled reaches it with
- // none. Use e2eClientAddr to set the client address.
+func TestBackhaulDeliversTheSendersAnswer(t *testing.T) {
+ s := newBackhaulScene(t)
+ s.converge(t)
+ homeSpy, senderSpy := spyGeo(s.a.edge, "XX"), spyGeo(s.b.edge, "GB")
+ _, body := s.get(t, s.b, "https://"+e2eHomedHost+"/", nil, true)
+ seen := e2eParseApp(t, body)
+ if seen.geoCountry != "GB" || seen.hopGeo != "" {
+ t.Fatalf("home app saw country=%q hopGeo=%q, want GB and no hop header", seen.geoCountry, seen.hopGeo)
+ }
+ if homeSpy.Load() != 0 || senderSpy.Load() != 1 {
+ t.Fatalf("lookups home=%d sender=%d, want 0 and 1 (ingress computes, spec §8.2)", homeSpy.Load(), senderSpy.Load())
+ }
}
-func TestGeoSetBeforeCacheVaryMatch(t *testing.T) {
- // Using varyBackendEdge (edge_test.go:590) with Vary: X-Carlos-Geo-Country:
- // two requests from GB and US addresses produce two variants; a third
- // from GB is a hit on the GB variant (cachedVariants helper).
+// Review Focus 1: Vary on the geo header picks the right variant.
+func TestCacheVariesOnTheEdgesCountry(t *testing.T) {
+ var hits atomic.Int64
+ backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ hits.Add(1)
+ w.Header().Set("Cache-Control", "max-age=60")
+ w.Header().Set("Vary", geoip.HeaderCountry)
+ w.Write([]byte("country=" + r.Header.Get(geoip.HeaderCountry)))
+ }))
+ t.Cleanup(backend.Close)
+ reg := openReg(t)
+ reg.Upsert(registry.Route{Host: "app.test", Kind: "instance", Addr: backend.Listener.Addr().String(), Status: "ready"})
+ e, err := New(reg, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(e.probers.Stop)
+ e.EnableCache(NewCache(DefaultCacheMaxEntries, DefaultCacheMaxBodyBytes))
+ db, _ := geo.OpenMMDB("../geo/testdata/GeoIP2-City-Test.mmdb")
+ e.geo.Swap(db)
+ get := func(from string, forged string) string {
+ r := geoReq("app.test", from)
+ if forged != "" {
+ r.Header.Set(geoip.HeaderCountry, forged)
+ }
+ w := httptest.NewRecorder()
+ e.ServeHTTP(w, r)
+ return w.Body.String()
+ }
+ if b := get("81.2.69.142", ""); b != "country=GB" {
+ t.Fatalf("GB = %q", b)
+ }
+ if b := get("216.160.83.56", ""); b != "country=US" {
+ t.Fatalf("US = %q", b)
+ }
+ if b := get("81.2.69.142", "US"); b != "country=GB" {
+ t.Fatalf("a forged header picked another country's variant: %q", b)
+ }
+ if hits.Load() != 2 || cachedVariants(e, "app.test", "/") != 2 {
+ t.Fatalf("hits=%d variants=%d, want 2 and 2", hits.Load(), cachedVariants(e, "app.test", "/"))
+ }
}
func TestUpgradeRequestCarriesGeo(t *testing.T) {
- // Using upgradeEchoBackend (edge_test.go:1991): the upgrade request the
- // backend sees carries X-Carlos-Geo-Country.
+ // upgradeEchoBackend (edge_test.go ~:1991) echoes the request headers it
+ // received on the upgraded connection; route app.test to it, spyGeo(e,
+ // "GB"), send a WebSocket upgrade from 81.2.69.142 through ServeHTTP on a
+ // real httptest server (upgrades need a hijackable writer), and assert
+ // the echoed headers contain X-Carlos-Geo-Country: GB. Read
+ // TestServeHTTPUpgradeRequestBypassesRecording (~:1301) for the exact
+ // client handshake this package's tests use, and copy it.
}
```
-**The executor writes the bodies of the six stub tests,** using the named helpers. The plan names each helper's location rather than guessing its signature. Every stub must assert exactly what its comment says, and each must fail before Step 3. If a helper turns out not to exist under that name, find its replacement with `grep -n "func newBackhaulScene\|func hopEdge\|func varyBackendEdge\|func upgradeEchoBackend" internal/edge/*_test.go`. Do not invent a new harness when one exists. `newTCPRouteEdge` is likewise a stand-in for whichever helper `edge_test.go` uses to route a host to a TCP backend.
+For `TestBackhaulDeliversTheSendersAnswer`, extend `e2eApp` (`backhaul_e2e_test.go:101`) so it also prints `|geo=<X-Carlos-Geo-Country>|hopgeo=<X-Carlos-Hop-Geo>`. Extend `e2eSeen` and `e2eParseApp` with `geoCountry` and `hopGeo`. Every existing test that parses `e2eApp` output must still pass, so add the new fields at the end.
+
+Two bodies above are sketched as comments: `TestDoorCallReachesTheAppWithNoGeo`'s final assertion, and `TestUpgradeRequestCarriesGeo`. Both depend on helpers whose exact accessors the executor must read first. Write them from the named helpers, and make sure each fails before Step 3.
- [ ] **Step 2: Run them to see them fail**
-Run: `TMPDIR=/tmp/g GOFLAGS=-buildvcs=false nice -n 19 go test ./internal/edge -run 'Geo|Forged|Hop.*Geo|DoorRequestGetsNoGeo' -count=1`
-Expected: FAIL, because no geo headers are set.
+Run: `TMPDIR=/tmp/g GOFLAGS=-buildvcs=false nice -n 19 go test ./internal/edge -run 'Geo|Forged|Hop|Door.*Geo|Backhaul|Cache.*Country|Upgrade.*Geo' -count=1`
+Expected: build failure (`decideGeo`, `setGeo`, `geoResultKey`, `geoLookup` undefined), then FAIL until Step 3 is complete.
- [ ] **Step 3: Implement it.** `internal/edge/geoipheaders.go`:
@@ -3166,7 +3343,7 @@ func (e *Edge) decideGeo(r *http.Request, c geoip.Captured, hopPresent, hopped,
return c.Adopt() // the hub looked up (spec §8.4)
}
ip := net.ParseIP(clientIP(r))
- return geoip.FromSnapshot(e.geo.Lookup(ip))
+ return geoip.FromSnapshot(e.geoLookup(ip))
}
// setGeo is every Rewrite's geo step (spec §8.3): after hop-by-hop removal,