carlos / platform Public

domains: a resolver failure never overwrites a DNS verdict (#303 review)

Unknown ("") used to be stamped unconditionally, so one bad DNS tick
flipped every route and alias to never-checked — and with #287's
publisher gating on pointing, would unpublish every custom domain
fleet-wide for a cadence. The last verdict and its stamp time now hold;
a never-checked row still records unknown.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev 7cd83094ee06ba284000fd1d85f54edd3ecc9bee parent 2aa2930
2 files changed, +50 −0
  • internal/domains/reconcile.go +13 −0
  • internal/domains/reconcile_test.go +37 −0
diff --git a/internal/domains/reconcile.go b/internal/domains/reconcile.go
index 46252c86f..b15752bdb 100644
--- a/internal/domains/reconcile.go
+++ b/internal/domains/reconcile.go
@@ -456,14 +456,27 @@ func StampHealth(ctx context.Context, reg *registry.Registry, cfg Config, routes
edge[ip] = true
}
now := cfg.Now().Unix()
+ // A resolver failure is UNKNOWN (""), and unknown never overwrites a
+ // verdict: one bad DNS tick must not flip a working domain to
+ // "never checked" — the edges-file publisher gates on "pointing"
+ // (#287), so that flip would unpublish every alias fleet-wide for a
+ // cadence. The stamp time is left alone too, so staleness stays
+ // visible (dns_checked_at stops advancing) rather than being
+ // laundered into a fresh unknown.
for _, rt := range routes {
status := dnsStatus(cfg.Resolve, rt.Host, edge)
+ if status == "" && rt.DNSStatus != "" {
+ continue
+ }
if err := reg.SetDNSStatus(rt.Host, status, now); err != nil {
log.Printf("domains: stamp dns on %s: %v", rt.Host, err)
}
}
for _, a := range aliases {
status := dnsStatus(cfg.Resolve, a.Alias, edge)
+ if status == "" && a.DNSStatus != "" {
+ continue
+ }
if err := reg.SetAliasDNSStatus(a.Alias, status, now); err != nil {
log.Printf("domains: stamp dns on alias %s: %v", a.Alias, err)
}
diff --git a/internal/domains/reconcile_test.go b/internal/domains/reconcile_test.go
index b5b475b2f..86b868151 100644
--- a/internal/domains/reconcile_test.go
+++ b/internal/domains/reconcile_test.go
@@ -1221,3 +1221,40 @@ func TestStampHealthStandsAlone(t *testing.T) {
t.Errorf("unconfigured pass stamped %+v", got)
}
}
+
+// A resolver failure never overwrites a verdict: the last known verdict
+// and its stamp time stay, so a bad DNS tick cannot unpublish a working
+// custom domain (#287's gate) or launder staleness into "never checked".
+func TestStampHealthHoldsLastVerdictOnResolverFailure(t *testing.T) {
+ reg := regWith(t, registry.Route{Host: "app.acct.oncarlos.test", Kind: "service", Addr: "10.0.0.1:1", Status: "ready"})
+ if err := reg.UpsertAlias(registry.RouteAlias{Alias: "www.customer.test", Host: "app.acct.oncarlos.test", Account: "acct", App: "app"}); err != nil {
+ t.Fatal(err)
+ }
+ good := domains.Config{AppsDomain: "oncarlos.test", EdgeIPs: []string{"203.0.113.7"},
+ Resolve: func(string) ([]string, error) { return []string{"203.0.113.7"}, nil },
+ Now: func() time.Time { return time.Unix(1000, 0) }}
+ if err := domains.StampHealth(context.Background(), reg, good, nil); err != nil {
+ t.Fatal(err)
+ }
+ broken := good
+ broken.Resolve = func(string) ([]string, error) { return nil, errors.New("SERVFAIL") }
+ broken.Now = func() time.Time { return time.Unix(2000, 0) }
+ if err := domains.StampHealth(context.Background(), reg, broken, nil); err != nil {
+ t.Fatal(err)
+ }
+ if got := mustGet(t, reg, "app.acct.oncarlos.test"); got.DNSStatus != "pointing" || got.DNSCheckedAt != 1000 {
+ t.Errorf("route after resolver failure = %q @%d; want pointing @1000", got.DNSStatus, got.DNSCheckedAt)
+ }
+ aliases, _ := reg.ListAliases()
+ if len(aliases) != 1 || aliases[0].DNSStatus != "pointing" || aliases[0].DNSCheckedAt != 1000 {
+ t.Errorf("alias after resolver failure = %+v; want pointing @1000", aliases)
+ }
+ // A never-checked row still records unknown, so "unknown" stays honest.
+ fresh := regWith(t, registry.Route{Host: "new.acct.oncarlos.test", Kind: "service", Addr: "10.0.0.1:1", Status: "ready"})
+ if err := domains.StampHealth(context.Background(), fresh, broken, nil); err != nil {
+ t.Fatal(err)
+ }
+ if got := mustGet(t, fresh, "new.acct.oncarlos.test"); got.DNSStatus != "" || got.DNSCheckedAt != 2000 {
+ t.Errorf("fresh row = %q @%d; want \"\" @2000", got.DNSStatus, got.DNSCheckedAt)
+ }
+}