edges file: publish attached custom domains under their own names (#287)
An attached custom domain on an app that already had a ready route lands in route_aliases, never routes, so it appeared in no edges file: a non-home edge neither backhauled it (Home missed) nor answered its validation or let the issuer order for it (KnownHost missed). Custom domains on a multi-edge deployment therefore worked only through the home edge. The publisher now writes each alias whose primary is a served row under the alias's own name — role home, never platform-class (a customer's name derives no wildcard parent). Every reader already does the right thing with an exact name: Home dials the home box with the alias as SNI, the home box's own upstream map serves it, KnownHost admits it on every edge, the issuer orders its per-host cert wherever the validation lands. A *.parent catch-all alias goes verbatim, as the routes' catch-all rows do: Home's one-label rule dials it, KnownHost still refuses it (#180). An alias shadowed by a real route is the route's, as Reload rules.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4 files changed,
+122
−0
docs/superpowers/specs/2026-08-20-multi-region-ingress-design.md+11 −0internal/edge/wildcard_parents_test.go+26 −0internal/host/edgesfile.go+29 −0internal/host/edgesfile_test.go+56 −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 8bf68d77c..89d2f2366 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 |
| @@ -256,6 +256,17 @@ follow-up issue, with one constraint stated in advance: an alias whose |
| host is a `*.<parent>` catch-all must stay wildcard-cert-only if it is |
| ever published — the same #180 bound this amendment refuses to widen. |
| +*Amendment 2026-08-26 (#287): done.* The publisher writes each alias |
| +whose primary is a served row under the alias's own name (`role: |
| +"home"`, never `platform`), so `Home` backhauls it and `KnownHost` |
| +admits it on every edge — the issuer orders its per-host cert whichever |
| +edge the validation lands on. A `*.<parent>` catch-all alias goes |
| +verbatim, exactly as the routes' catch-all rows do: `Home`'s one-label |
| +rule dials it, `KnownHost` still refuses it. Custom domains on a |
| +multi-edge deployment therefore work through any edge — `CNAME` to |
| +`edge.<apps-domain>` inherits steering and failover; an apex needs the |
| +current edge-IP set (#299). |
| + |
| **Order amplification, accepted.** The set of names a stranger can induce |
| a fresh ACME order for at the issuer grows from one box's routes to every |
| box's routes. Each such order for a name whose DNS is dead burns that |
diff --git a/internal/edge/wildcard_parents_test.go b/internal/edge/wildcard_parents_test.go| index a97052123..6b234ee3c 100644 |
| --- a/internal/edge/wildcard_parents_test.go |
| +++ b/internal/edge/wildcard_parents_test.go |
| @@ -250,3 +250,29 @@ func TestWildcardRefreshIsSerialized(t *testing.T) { |
| t.Fatalf("parents = %v after generation %d", got, final) |
| } |
| } |
| + |
| +// #287, reader side: an attached custom domain published under its own |
| +// name (never platform-class) backhauls, passes the cert gates' exact |
| +// admission, and derives no wildcard parent; its catch-all form dials but |
| +// is never an exact admission (#180). |
| +func TestAliasEntriesBackhaulAndAdmitButDeriveNoParent(t *testing.T) { |
| + e, _, base := remoteHomedEdge(t, nil) |
| + putJSON(t, base, "control/edges/betty.json", `{"v":1,"box":"betty","eip":"2.2.2.2","hosts":{"www.customer.com":{"role":"home","platform":false},"*.customer.com":{"role":"home","platform":false},"app.acct.apps.test":{"role":"home","platform":true}}}`) |
| + hm := e.homes.Load() |
| + refreshHomeMap(t, hm) |
| + if _, box, ok := hm.Home("www.customer.com"); !ok || box != "betty" { |
| + t.Fatal("alias entry does not backhaul") |
| + } |
| + if _, _, ok := hm.Home("shop.customer.com"); !ok { |
| + t.Fatal("catch-all alias does not dial one label under it") |
| + } |
| + if !e.deploymentHost("www.customer.com") { |
| + t.Fatal("alias entry not admitted by the cert gates") |
| + } |
| + if e.deploymentHost("*.customer.com") || e.deploymentHost("shop.customer.com") { |
| + t.Fatal("a catch-all alias must never be an exact cert admission (#180)") |
| + } |
| + if got := hm.Hosts(); !reflect.DeepEqual(got, []string{"app.acct.apps.test"}) { |
| + t.Fatalf("customer names leaked into the parent input: %v", got) |
| + } |
| +} |
diff --git a/internal/host/edgesfile.go b/internal/host/edgesfile.go| index e40f4bea8..27834115c 100644 |
| --- a/internal/host/edgesfile.go |
| +++ b/internal/host/edgesfile.go |
| @@ -125,6 +125,35 @@ func edgesFilePass(ctx context.Context, s release.Store, reg *registry.Registry, |
| // (the leading dot of the first label only) is tried next. |
| hosts[r.Host] = edgeHost{Role: "home", Platform: r.Platform || r.Provisioned} |
| } |
| + // Attached custom domains (#151) live in route_aliases, not routes, and |
| + // were absent from this file (#287): a non-home edge neither backhauled |
| + // them (Home missed) nor answered their validations or let the issuer |
| + // order for them (KnownHost missed). Publish each alias whose primary is |
| + // a served row above, under the alias's own name — the reader wants |
| + // exact names, and every reader already does the right thing with one: |
| + // Home dials the home box with the alias as SNI, and the home box's own |
| + // upstream map serves it. Never platform-class: a customer's name |
| + // derives no wildcard parent. A "*.parent" catch-all alias goes verbatim, |
| + // as the routes' catch-all rows do — Home's one-label rule dials it, |
| + // KnownHost refuses it (PR #180's bound: a wildcard label is never an |
| + // exact cert admission). An alias shadowed by a real route is the |
| + // route's, as edge.Reload rules. |
| + aliases, err := reg.ListAliases() |
| + if err != nil { |
| + return fmt.Errorf("edges: list aliases: %w", err) |
| + } |
| + for _, a := range aliases { |
| + if a.Alias == "" { |
| + continue |
| + } |
| + if _, served := hosts[a.Host]; !served { |
| + continue |
| + } |
| + if _, taken := hosts[a.Alias]; taken { |
| + continue |
| + } |
| + hosts[a.Alias] = edgeHost{Role: "home"} |
| + } |
| if eip == "" && *last != nil { |
| var prior edgesFile |
diff --git a/internal/host/edgesfile_test.go b/internal/host/edgesfile_test.go| index 79966559a..d167216d3 100644 |
| --- a/internal/host/edgesfile_test.go |
| +++ b/internal/host/edgesfile_test.go |
| @@ -612,3 +612,59 @@ func TestEdgesFileMarksPlatformRows(t *testing.T) { |
| t.Errorf("mark not emitted for an unclassed row: %s", body) |
| } |
| } |
| + |
| +// #287: attached custom domains are published under their own names — |
| +// only when their primary is served, never platform-class, catch-alls |
| +// verbatim, and never over a real route. |
| +func TestEdgesFilePublishesAliasesOfServedPrimaries(t *testing.T) { |
| + s, reg := release.NewDirStore(t.TempDir()), edgesTestRegistry(t) |
| + seedRoute(t, reg, "app.acct.apps.test") |
| + if err := reg.Upsert(registry.Route{Host: "pending.acct.apps.test", Kind: "service", Addr: "10.0.0.1:3001", Status: "pending"}); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := reg.Upsert(registry.Route{Host: "real.example.net", Kind: "service", Addr: "10.0.0.1:3002", Status: "ready", Platform: true}); err != nil { |
| + t.Fatal(err) |
| + } |
| + for _, a := range []registry.RouteAlias{ |
| + {Alias: "www.customer.com", Host: "app.acct.apps.test", Account: "acct", App: "app"}, |
| + {Alias: "*.customer.com", Host: "app.acct.apps.test", Account: "acct", App: "app"}, |
| + {Alias: "orphan.example.org", Host: "pending.acct.apps.test", Account: "acct", App: "pending"}, |
| + {Alias: "real.example.net", Host: "app.acct.apps.test", Account: "acct", App: "app"}, // shadowed |
| + } { |
| + if err := reg.UpsertAlias(a); err != nil { |
| + t.Fatal(err) |
| + } |
| + } |
| + var lastBody []byte |
| + if err := edgesFilePass(context.Background(), s, reg, "annie", "1.2.3.4", "", &lastBody); err != nil { |
| + t.Fatal(err) |
| + } |
| + body, _, err := s.Get(context.Background(), "control/edges/annie.json") |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + var f struct { |
| + Hosts map[string]struct { |
| + Role string `json:"role"` |
| + Platform bool `json:"platform"` |
| + } `json:"hosts"` |
| + } |
| + if err := json.Unmarshal(body, &f); err != nil { |
| + t.Fatal(err) |
| + } |
| + for _, alias := range []string{"www.customer.com", "*.customer.com"} { |
| + h, ok := f.Hosts[alias] |
| + if !ok || h.Role != "home" { |
| + t.Errorf("%s: not published as home (%+v, %v)", alias, h, ok) |
| + } |
| + if h.Platform { |
| + t.Errorf("%s: a customer alias must never be platform-class", alias) |
| + } |
| + } |
| + if _, ok := f.Hosts["orphan.example.org"]; ok { |
| + t.Error("an alias of an unserved primary was published") |
| + } |
| + if h := f.Hosts["real.example.net"]; !h.Platform { |
| + t.Error("an alias colliding with a real route displaced the route's entry") |
| + } |
| +} |