host: mark a served catch-all as an alias, so it stops refusing itself (#382)
A box that serves a delegated wildcard's catch-all published the
"*.<parent>" row with role home and no alias mark. edgesfile.
DeploymentRoutes reads exactly `Role == RoleHome && !Alias` as "some live
box serves this hostname as a real route", so the box's own edges file
became evidence against its own claim: the next domains.ReconcileAliases
pass refused the catch-all ("another box serves that hostname as a
route") and deleted the alias row, which turned the dial half off, which
dropped the row, which let the pass after that recreate it.
The result is a name that flaps rather than one that breaks, so nothing
alerts. *.woodstar.app has been cycling every 5-6 minutes for at least a
day, taking the PDS endpoint and AT Proto handle resolution under it down
about half of every hour while the apex — which has its own route — stayed
green throughout.
The catch-all key never reaches the alias loop above (it skips "*." rows
deliberately, #302/#305), so the wildcard_domains loop is the only place
its class can be set. A catch-all we actually serve IS a materialized
claim, which is what the mark means. The merge branch just above already
preserved Alias when a real route owned the key; only the fresh write was
wrong, and a real route still publishes unmarked and keeps its authority.
The end-to-end test is the one that matters: neither half is wrong on its
own, so it publishes with the real publisher and folds with the real
reader. Reverted against this commit it reproduces the production set,
`map[*.customer.example:true app.acct.apps.test:true]`.
Claude-Session: https://claude.ai/code/session_01LX3AD7SrzYYBbeiFswMoqm
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2 files changed,
+75
−10
internal/host/edgesfile.go+17 −3internal/host/edgesfile_test.go+58 −7
CI passed — run details
diff --git a/internal/host/edgesfile.go b/internal/host/edgesfile.go| index faad8b53f..e6b89b944 100644 |
| --- a/internal/host/edgesfile.go |
| +++ b/internal/host/edgesfile.go |
| @@ -258,11 +258,25 @@ func edgesFilePass(ctx context.Context, s release.Store, reg *registry.Registry, |
| hosts[key] = prev |
| continue |
| } |
| - role := "wildcard" |
| + // Marked alias whenever the DIAL half is on, and the mark is |
| + // load-bearing rather than cosmetic. The catch-all key never reaches |
| + // the alias loop above (it skips "*." rows on purpose), so this is |
| + // the only place its class can be set — and edgesfile.DeploymentRoutes |
| + // reads exactly `Role == RoleHome && !Alias` as "some live box serves |
| + // this hostname as a real route". Published unmarked, a served |
| + // catch-all is therefore evidence against itself: the next |
| + // domains.ReconcileAliases pass reads our own file back, refuses the |
| + // claim that produced the row, and drops the alias — which turns the |
| + // dial half off, unmarks nothing, and lets the pass after that |
| + // recreate it. That loop is not theoretical; it ran on *.woodstar.app |
| + // every 5-6 minutes, taking the name down about half of every hour. |
| + // A catch-all we serve IS a materialized claim, which is what the |
| + // mark means, so say so. |
| + role, alias := "wildcard", false |
| if catchallServed[wd.Host] { |
| - role = "home" |
| + role, alias = "home", true |
| } |
| - hosts[key] = edgeHost{Role: role, Wildcard: true} |
| + hosts[key] = edgeHost{Role: role, Wildcard: true, Alias: alias} |
| } |
| if eip == "" && *last != nil { |
diff --git a/internal/host/edgesfile_test.go b/internal/host/edgesfile_test.go| index 96fd900ff..d43ea1c99 100644 |
| --- a/internal/host/edgesfile_test.go |
| +++ b/internal/host/edgesfile_test.go |
| @@ -746,28 +746,79 @@ func TestEdgesFilePublishesDelegatedWildcardParents(t *testing.T) { |
| Role string `json:"role"` |
| Platform bool `json:"platform"` |
| Wildcard bool `json:"wildcard"` |
| + Alias bool `json:"alias"` |
| } `json:"hosts"` |
| } |
| if err := json.Unmarshal(body, &f); err != nil { |
| t.Fatal(err) |
| } |
| + // Marked alias BECAUSE the dial half is on: a catch-all we serve is a |
| + // materialized claim, and DeploymentRoutes keys the whole "is this |
| + // somebody's real route" question on that mark. See the end-to-end |
| + // tripwire below for what an unmarked row does to the claim. |
| h, ok := f.Hosts["*.customer.example"] |
| - if !ok || h.Role != "home" || !h.Wildcard || h.Platform { |
| - t.Fatalf("*.customer.example = %+v, %v; want home, wildcard, not platform", h, ok) |
| + if !ok || h.Role != "home" || !h.Wildcard || h.Platform || !h.Alias { |
| + t.Fatalf("*.customer.example = %+v, %v; want home, wildcard, alias, not platform", h, ok) |
| } |
| for _, name := range []string{"*.certonly.example", "*.pending.example"} { |
| h, ok := f.Hosts[name] |
| - if !ok || h.Role != "wildcard" || !h.Wildcard || h.Platform { |
| - t.Errorf("%s = %+v, %v; want the cert half only (role wildcard)", name, h, ok) |
| + if !ok || h.Role != "wildcard" || !h.Wildcard || h.Platform || h.Alias { |
| + t.Errorf("%s = %+v, %v; want the cert half only (role wildcard, unmarked)", name, h, ok) |
| } |
| } |
| if _, ok := f.Hosts["*.*.odd.example"]; ok { |
| t.Error("a malformed wildcard_domains row was published") |
| } |
| // A real route owns the key: it keeps role and class, and the parent's |
| - // class is added — the reader must still load the PEM everywhere. |
| - if h := f.Hosts["*.taken.example"]; !h.Platform || !h.Wildcard || h.Role != "home" { |
| - t.Errorf("route-owned key = %+v; want platform AND wildcard, role home", h) |
| + // class is added — the reader must still load the PEM everywhere. It |
| + // stays UNMARKED, which is the other half of the mark's meaning: this |
| + // hostname really is served as a route, so it must go on outranking any |
| + // claim another box makes on the same name. |
| + if h := f.Hosts["*.taken.example"]; !h.Platform || !h.Wildcard || h.Role != "home" || h.Alias { |
| + t.Errorf("route-owned key = %+v; want platform AND wildcard, role home, unmarked", h) |
| + } |
| +} |
| + |
| +// The tripwire on a loop that ran in production: a served catch-all published |
| +// without the alias mark is read back by DeploymentRoutes as evidence that |
| +// some live box serves that hostname as a REAL route, so the next |
| +// domains.ReconcileAliases pass refuses the very claim that produced the row |
| +// and deletes the alias. Deleting it turns the dial half off, which drops the |
| +// row, which lets the pass after that recreate it — and the name flaps |
| +// on/off forever (*.woodstar.app, every 5-6 minutes, down about half the |
| +// time). Neither half is wrong alone, so only an end-to-end check catches it: |
| +// publish with the real publisher, then fold with the real reader. |
| +func TestEdgesFileServedCatchAllIsNotItsOwnDeploymentRoute(t *testing.T) { |
| + s, reg := release.NewDirStore(t.TempDir()), edgesTestRegistry(t) |
| + seedRoute(t, reg, "app.acct.apps.test") |
| + if err := reg.UpsertAlias(registry.RouteAlias{ |
| + Alias: "*.customer.example", Host: "app.acct.apps.test", Account: "acct", App: "app", |
| + }); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := reg.UpsertWildcardDomain(registry.WildcardDomain{ |
| + Host: "customer.example", Account: "acct", App: "app", |
| + }); err != nil { |
| + t.Fatal(err) |
| + } |
| + var lastBody []byte |
| + if err := edgesFilePass(context.Background(), s, reg, edgesFile{Box: "annie"}, "1.2.3.4", &lastBody); err != nil { |
| + t.Fatal(err) |
| + } |
| + now := time.Now() |
| + if err := status.WriteBox(context.Background(), s, status.Box{Label: "annie", GeneratedAt: now}); err != nil { |
| + t.Fatal(err) |
| + } |
| + |
| + routes := edgesfile.DeploymentRoutes(context.Background(), s, now) |
| + if routes["*.customer.example"] { |
| + t.Errorf("routes = %v, want the served catch-all NOT counted as a real route — counting it makes the claim refuse itself every pass", routes) |
| + } |
| + // The join really happened: without this, an empty set would pass the |
| + // assertion above for the wrong reason (a snapshot/label mismatch reads |
| + // as "nothing known", which is permissive by contract). |
| + if !routes["app.acct.apps.test"] { |
| + t.Fatalf("routes = %v, want the box's real route counted — the fold did not join", routes) |
| } |
| } |