carlos / platform Public

host: fix edges-file review findings — EIP hold, ready-gate, wildcard doc, wire pin

Four fixes from Task 1 review:

- Critical: readPublicIP's transient "" no longer blanks a known-good
  EIP — edgesFilePass falls back to the prior published file's EIP
  (read from *last) whenever this pass's probe comes back empty and a
  file has already been published. "" only ever ships for a box that
  has never had a discoverable IP.
- Important: rows are filtered to Status == "ready" before entering
  Hosts, mirroring edge.Reload's own definition of "serves"
  (internal/edge/edge.go) — a pending/creating/failed row is never
  dialed by any edge and must not claim role:"home".
- Important: documented (writer doc-comment + spec sentence) that
  Hosts keys are registry row hosts verbatim, including *.parent
  catch-all rows, and that an exact-match reader must apply
  lookupUpstream's one-label wildcard rule itself.
- Important: added a raw-wire-body test pinned against a hand-written
  literal of the brief's exact schema (independent of this package's
  struct tags), so a json-tag rename fails loudly instead of staying
  green under the existing round-trip tests.

Four new tests, each verified to fail against the pre-fix code by
temporarily reverting the corresponding fix and re-running. Covering
tests: go test ./internal/host/ -run EdgesFile -v (10/10 pass), go test
./internal/host/ -race -count=1, go test ./... -count=1, go build/vet
(+GOOS=darwin), gofmt -l — all clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev e964df3cffc2685ff537adada7c18d719e203b31 parent 23a2b0c
3 files changed, +179 −5
  • docs/superpowers/specs/2026-08-20-multi-region-ingress-design.md +6 −1
  • internal/host/edgesfile.go +36 −4
  • internal/host/edgesfile_test.go +137 −0
diff --git a/docs/superpowers/specs/2026-08-20-multi-region-ingress-design.md b/docs/superpowers/specs/2026-08-20-multi-region-ingress-design.md
index 95ba18281..8ce135c34 100644
--- a/docs/superpowers/specs/2026-08-20-multi-region-ingress-design.md
+++ b/docs/superpowers/specs/2026-08-20-multi-region-ingress-design.md
@@ -192,7 +192,12 @@ certs ruling; edges mirror the `control/edges/` prefix on their own
30-second poll, the same cadence the certs mirror uses. Every edge
folds these files into a deployment-wide host→home map. The file
doubles as the DR runbook's explicit statement of what a dead box was
-serving.
+serving. **Keys are registry row hosts verbatim, including `*.parent`
+catch-all rows** — this writer performs no wildcard expansion, so a
+reader that needs exact-host answers must apply `lookupUpstream`'s
+one-label wildcard rule (`internal/edge/edge.go`) itself: an exact
+match wins, and on a miss the first label's leading dot is swapped for
+`*` before trying again.
**The hop.** A remote edge dials `home-EIP:443` with ordinary SNI — the
home edge presents the same bucket-distributed cert. Two headers make it
diff --git a/internal/host/edgesfile.go b/internal/host/edgesfile.go
index 47a9b7c56..4fa38a39d 100644
--- a/internal/host/edgesfile.go
+++ b/internal/host/edgesfile.go
@@ -41,7 +41,7 @@ type edgesFile struct {
}
// edgesFilePass builds this box's edges file from the routes currently in
-// reg (every row this box serves, ALL kinds — the serving-class split
+// reg (every SERVED row on this box, ALL kinds — the serving-class split
// belongs to the reader, Task 3, not to this writer) and publishes it at
// edgesFileKey(box) when it changed.
//
@@ -53,9 +53,16 @@ type edgesFile struct {
// cleared) and is updated only after a successful write, mirroring
// registrySnapshotPass's *lastFP contract.
//
-// eip may be "" (readPublicIP's "unknown" case, e.g. not on EC2 or IMDS
-// disabled) — the file is still published, with `"eip":""`, so the DR
-// runbook's hosts list never depends on IMDS working.
+// eip may be "" (readPublicIP's "unknown" case, e.g. not on EC2, IMDS
+// disabled, or a transient metadata-endpoint failure). A blank reading is
+// NOT taken at face value: this box's public IP does not change tick to
+// tick, so "" almost always means the probe hiccuped, not that the address
+// vanished — publishing it verbatim would blank a known-good EIP for
+// every remote edge's dial address for up to one loop cadence. When eip is
+// "" and *last already holds a published file, this pass reuses that
+// file's EIP instead. "" only ever reaches the wire for a box that has
+// NEVER had a discoverable IP — the DR runbook's hosts list still gets
+// published in that case, just without a dial address.
func edgesFilePass(ctx context.Context, s release.Store, reg *registry.Registry, box, eip string, last *[]byte) error {
routes, err := reg.List()
if err != nil {
@@ -63,9 +70,34 @@ func edgesFilePass(ctx context.Context, s release.Store, reg *registry.Registry,
}
hosts := make(map[string]edgeHost, len(routes))
for _, r := range routes {
+ // edge.Reload (internal/edge/edge.go) is the deployment's own
+ // definition of "serves": it skips any row whose Status != "ready"
+ // because a pending/creating/failed row has no working backend and
+ // is never dialed by any edge, home or remote. Mirroring that gate
+ // here means this file never claims role:"home" for a host nothing
+ // actually serves. Hibernating routes stay Status "ready" (that is
+ // what makes wake-on-request possible), so nothing reachable is
+ // excluded by this filter.
+ if r.Status != "ready" {
+ continue
+ }
+ // Keys are registry row Host values verbatim, INCLUDING *.parent
+ // catch-all wildcard rows (Task 3's reconcile pass derives these
+ // from a claim's catch-all row, same as edge.go's upstream map) —
+ // this writer performs no expansion. A reader that needs exact-host
+ // answers must apply edge.go's lookupUpstream one-label wildcard
+ // rule itself: an exact match wins, and on a miss "*.<parent>"
+ // (the leading dot of the first label only) is tried next.
hosts[r.Host] = edgeHost{Role: "home"}
}
+ if eip == "" && *last != nil {
+ var prior edgesFile
+ if err := json.Unmarshal(*last, &prior); err == nil {
+ eip = prior.EIP
+ }
+ }
+
f := edgesFile{V: 1, Box: box, EIP: eip, Hosts: hosts}
gate, err := json.Marshal(f)
if err != nil {
diff --git a/internal/host/edgesfile_test.go b/internal/host/edgesfile_test.go
index 172261c80..162db2870 100644
--- a/internal/host/edgesfile_test.go
+++ b/internal/host/edgesfile_test.go
@@ -5,6 +5,7 @@ import (
"encoding/json"
"path/filepath"
"testing"
+ "time"
"github.com/carlosframework/platform/internal/registry"
"github.com/carlosframework/platform/internal/release"
@@ -131,3 +132,139 @@ func TestEdgesFileSkipsWriteWhenUnchanged(t *testing.T) {
t.Fatalf("changed third pass puts = %d, want 2", s.puts)
}
}
+
+// FINDING 1 (review, 2026-08-22): readPublicIP is a single 2s best-effort
+// IMDS attempt, re-run every tick — a transient throttle or blip returns ""
+// exactly like a box that never had a public IP at all. Without a hold, a
+// single hiccuped tick would blank a known-good EIP in the published file,
+// costing every remote edge its dial address for this box's home-boxed
+// hosts until the next successful tick (up to 5min + jitter). A pass
+// reporting "" after a pass that reported "1.2.3.4" must keep "1.2.3.4".
+func TestEdgesFileHoldsLastKnownGoodEIPOnTransientBlank(t *testing.T) {
+ s, reg := release.NewDirStore(t.TempDir()), edgesTestRegistry(t)
+ seedRoute(t, reg, "app.example.com")
+
+ var last []byte
+ if err := edgesFilePass(context.Background(), s, reg, "annie", "1.2.3.4", &last); err != nil {
+ t.Fatal(err)
+ }
+
+ // Simulate a transient IMDS blip: this tick's probe returns "".
+ if err := edgesFilePass(context.Background(), s, reg, "annie", "", &last); err != nil {
+ t.Fatal(err)
+ }
+
+ got, _, err := s.Get(context.Background(), "control/edges/annie.json")
+ if err != nil {
+ t.Fatal(err)
+ }
+ var f edgesFile
+ if err := json.Unmarshal(got, &f); err != nil {
+ t.Fatal(err)
+ }
+ if f.EIP != "1.2.3.4" {
+ t.Fatalf("eip after a blank probe = %q, want held value %q", f.EIP, "1.2.3.4")
+ }
+}
+
+// FINDING 2 (review, 2026-08-22, ruling): edge.Reload (internal/edge/edge.go)
+// skips any route whose Status != "ready" — that gate is the deployment's
+// own definition of "serves", so a pending/creating/failed row must not
+// claim role:"home" here either. Hibernating routes stay Status "ready",
+// so this filter excludes nothing a remote backhaul could actually reach.
+func TestEdgesFileExcludesNonReadyRows(t *testing.T) {
+ s, reg := release.NewDirStore(t.TempDir()), edgesTestRegistry(t)
+ seedRoute(t, reg, "ready.example.com")
+ if err := reg.Upsert(registry.Route{Host: "creating.example.com", Kind: "service", Addr: "10.0.0.2:3000", Status: "creating"}); err != nil {
+ t.Fatal(err)
+ }
+
+ var last []byte
+ if err := edgesFilePass(context.Background(), s, reg, "annie", "1.2.3.4", &last); err != nil {
+ t.Fatal(err)
+ }
+ got, _, err := s.Get(context.Background(), "control/edges/annie.json")
+ if err != nil {
+ t.Fatal(err)
+ }
+ var f edgesFile
+ if err := json.Unmarshal(got, &f); err != nil {
+ t.Fatal(err)
+ }
+ if _, ok := f.Hosts["ready.example.com"]; !ok {
+ t.Fatalf("hosts = %+v, want ready.example.com present", f.Hosts)
+ }
+ if _, ok := f.Hosts["creating.example.com"]; ok {
+ t.Fatalf("hosts = %+v, want creating.example.com ABSENT (not ready, nothing serves it yet)", f.Hosts)
+ }
+}
+
+// FINDING 3 (review, 2026-08-22): a catch-all claim's registry row is a real
+// "*.parent" host (Task 3's reconcile pass derives it the same way
+// edge.go's upstream map does). This writer performs no wildcard expansion
+// — the row's Host lands in Hosts verbatim, exactly like every other row —
+// so an exact-match reader must apply edge.go's lookupUpstream one-label
+// rule itself; that contract is documented at the call site and in the
+// spec, not enforced here.
+func TestEdgesFileWildcardRowPublishesVerbatim(t *testing.T) {
+ s, reg := release.NewDirStore(t.TempDir()), edgesTestRegistry(t)
+ if err := reg.Upsert(registry.Route{Host: "*.example.com", Kind: "service", Addr: "10.0.0.3:3000", Status: "ready"}); err != nil {
+ t.Fatal(err)
+ }
+
+ var last []byte
+ if err := edgesFilePass(context.Background(), s, reg, "annie", "1.2.3.4", &last); err != nil {
+ t.Fatal(err)
+ }
+ got, _, err := s.Get(context.Background(), "control/edges/annie.json")
+ if err != nil {
+ t.Fatal(err)
+ }
+ var f edgesFile
+ if err := json.Unmarshal(got, &f); err != nil {
+ t.Fatal(err)
+ }
+ if r, ok := f.Hosts["*.example.com"]; !ok || r.Role != "home" {
+ t.Fatalf("hosts = %+v, want \"*.example.com\" present verbatim with role home", f.Hosts)
+ }
+}
+
+// FINDING 4 (review, 2026-08-22): every other test in this file round-trips
+// through the edgesFile struct on both the write and the assert side, so a
+// json-tag rename (e.g. "eip" -> "publicIP") would stay green everywhere
+// while breaking Task 3's parser, which reads the wire bytes independently
+// of this package's Go types. This test pins the RAW body against a
+// hand-written literal matching the brief's exact schema string — every
+// byte is fixed except the generation timestamp, which this fixed
+// signature has no clock seam to inject (edgesFilePass takes no `now`
+// parameter, per the brief's pinned interface); that one field is read
+// back and re-inserted into the literal rather than relaxed by a regex.
+func TestEdgesFileRawWireBodyMatchesSchema(t *testing.T) {
+ s, reg := release.NewDirStore(t.TempDir()), edgesTestRegistry(t)
+ seedRoute(t, reg, "app.example.com")
+
+ var last []byte
+ if err := edgesFilePass(context.Background(), s, reg, "annie-welding", "99.81.104.219", &last); err != nil {
+ t.Fatal(err)
+ }
+ got, _, err := s.Get(context.Background(), "control/edges/annie-welding.json")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var probe struct {
+ Generation string `json:"generation"`
+ }
+ if err := json.Unmarshal(got, &probe); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := time.Parse(time.RFC3339, probe.Generation); err != nil {
+ t.Fatalf("generation %q is not RFC3339: %v", probe.Generation, err)
+ }
+
+ want := `{"v":1,"box":"annie-welding","eip":"99.81.104.219","generation":"` +
+ probe.Generation + `","hosts":{"app.example.com":{"role":"home"}}}`
+ if string(got) != want {
+ t.Fatalf("raw wire body =\n%s\nwant\n%s", got, want)
+ }
+}