carlos / platform Public

domains: reconcile derives wildcard_domains + catch-all aliases from claims

Merges wildcard/catch-all derivation into #164's existing claims loop in
ReconcileAliases (one claims iteration, not two): a Wildcard claim whose
primary route is local + tenancy-matched gets a wildcard_domains row (cap
32/pass, logged drops); a Wildcard+Catchall claim additionally gets a
"*.<host>" route_aliases row feeding the existing alias-removal sweep.
wildcard_domains gets its own pass-owned removal sweep. Deviation from the
brief: does not import internal/provision for ValidHostShape — provision
already imports domains (reconcile.go), so that would be a cycle; uses the
existing validHostShape already duplicated in claims.go for this exact
reason.
Paul Campbell fc85b65a928b8d37979022c85bfbe4bf27ea77bb parent dfdb139
2 files changed, +221 −22
  • internal/domains/aliases.go +112 −22
  • internal/domains/aliases_test.go +109 −0
diff --git a/internal/domains/aliases.go b/internal/domains/aliases.go
index 46a90b812..9db796216 100644
--- a/internal/domains/aliases.go
+++ b/internal/domains/aliases.go
@@ -1,6 +1,11 @@
-// Alias reconcile pass — derives the registry's route_aliases table from
-// the global claims recorded in the bucket (#151). See claims.go for the
-// claim shape and registry.go's RouteAlias for the storage shape; this file
+// Alias reconcile pass — derives the registry's route_aliases table AND its
+// wildcard_domains table from the global claims recorded in the bucket
+// (#151). A claim contributes an ordinary alias row when it names a Route
+// (see the loop below); a Wildcard claim additionally contributes a
+// wildcard_domains row (a delegated-ACME wildcard-cert parent), and a
+// Wildcard+Catchall claim also contributes a "*.<host>" route_aliases row
+// pointing at the same primary. See claims.go for the claim shape and
+// registry.go's RouteAlias/WildcardDomain for the storage shapes; this file
// is the seam that turns one into the other.
package domains
@@ -13,13 +18,19 @@ import (
"github.com/carlosframework/platform/internal/release"
)
-// ReconcileAliases derives route_aliases from the global claim records.
-// The table is wholly pass-owned: every row traces to a claim, so removal
-// needs no ownership flag. The authoritative tenancy gate is HERE — the
-// door's route check is best-effort (it cannot see the registry); a claim
-// only materializes when the named route exists locally AND its
-// account/app match the claim. Routes always win a host collision: an
-// alias must never shadow a real route.
+// maxWildcardDomains caps wildcard_domains per box: every delegated parent
+// shares one ACME account's new-order budget, so an unbounded claim set
+// could starve platform issuance. Deterministic (claims come in list
+// order); drops are logged, never silent.
+const maxWildcardDomains = 32
+
+// ReconcileAliases derives route_aliases and wildcard_domains from the
+// global claim records. Both tables are wholly pass-owned: every row traces
+// to a claim, so removal needs no ownership flag. The authoritative tenancy
+// gate is HERE — the door's route check is best-effort (it cannot see the
+// registry); a claim only materializes when its primary route exists
+// locally AND its account/app match the claim. Routes always win a host
+// collision: an alias (plain or catch-all) must never shadow a real route.
func ReconcileAliases(ctx context.Context, s release.Store, reg *registry.Registry, now func() time.Time) error {
claims, err := ListClaims(ctx, s)
if err != nil {
@@ -38,29 +49,81 @@ func ReconcileAliases(ctx context.Context, s release.Store, reg *registry.Regist
return err
}
desired := map[string]bool{}
+ desiredWild := map[string]registry.WildcardDomain{}
+ wildCount := 0
for _, c := range claims {
- if c.Route == "" { // first-route claims (#82's case) are not aliases
+ // Plain alias derivation (#164): a routeless (first-route) claim is
+ // not an alias — that skip is scoped to THIS half only, so a
+ // routeless Wildcard claim still falls through to the wildcard
+ // derivation below.
+ if c.Route != "" {
+ rt, ok := byHost[c.Route]
+ if !ok {
+ // primary not on this box — another box's claim
+ } else if rt.App != c.App || release.RouteAccount(rt) != c.Account {
+ log.Printf("domains: alias %s refused: route %s belongs to %s/%s, claim says %s/%s",
+ c.Host, c.Route, release.RouteAccount(rt), rt.App, c.Account, c.App)
+ } else if _, taken := byHost[c.Host]; taken {
+ log.Printf("domains: alias %s skipped: a real route owns that host", c.Host)
+ } else {
+ desired[c.Host] = true
+ if err := reg.UpsertAlias(registry.RouteAlias{
+ Alias: c.Host, Host: c.Route, Account: c.Account, App: c.App,
+ CreatedAt: now().Unix(),
+ }); err != nil {
+ log.Printf("domains: upsert alias %s: %v", c.Host, err)
+ }
+ }
+ }
+
+ // Wildcard/catch-all derivation. A claim with Catchall but not
+ // Wildcard cannot come from the write path (SetClaimCapabilities
+ // refuses it), but a hand-written or pre-migration claim could carry
+ // it — tolerated, logged, nothing derived.
+ if c.Catchall && !c.Wildcard {
+ log.Printf("domains: catch-all %s skipped: claim has catchall without wildcard", c.Host)
+ }
+ if !c.Wildcard {
continue
}
- rt, ok := byHost[c.Route]
+ if !validHostShape(c.Host) {
+ log.Printf("domains: wildcard %s refused: invalid host shape", c.Host)
+ continue
+ }
+ primaryHost := c.Route
+ if primaryHost == "" {
+ primaryHost = c.Host // first-route claim: its own materialized primary
+ }
+ rt, ok := byHost[primaryHost]
if !ok {
continue // primary not on this box — another box's claim
}
if rt.App != c.App || release.RouteAccount(rt) != c.Account {
- log.Printf("domains: alias %s refused: route %s belongs to %s/%s, claim says %s/%s",
- c.Host, c.Route, release.RouteAccount(rt), rt.App, c.Account, c.App)
+ log.Printf("domains: wildcard %s refused: route %s belongs to %s/%s, claim says %s/%s",
+ c.Host, primaryHost, release.RouteAccount(rt), rt.App, c.Account, c.App)
continue
}
- if _, taken := byHost[c.Host]; taken {
- log.Printf("domains: alias %s skipped: a real route owns that host", c.Host)
+ if wildCount >= maxWildcardDomains {
+ log.Printf("domains: wildcard %s dropped: per-box cap %d reached", c.Host, maxWildcardDomains)
continue
}
- desired[c.Host] = true
- if err := reg.UpsertAlias(registry.RouteAlias{
- Alias: c.Host, Host: c.Route, Account: c.Account, App: c.App,
- CreatedAt: now().Unix(),
- }); err != nil {
- log.Printf("domains: upsert alias %s: %v", c.Host, err)
+ wildCount++
+ desiredWild[c.Host] = registry.WildcardDomain{
+ Host: c.Host, Account: c.Account, App: c.App, CreatedAt: now().Unix(),
+ }
+ if c.Catchall {
+ star := "*." + c.Host
+ if _, taken := byHost[star]; taken {
+ log.Printf("domains: catch-all %s skipped: a real route owns that host", star)
+ } else {
+ desired[star] = true
+ if err := reg.UpsertAlias(registry.RouteAlias{
+ Alias: star, Host: primaryHost, Account: c.Account, App: c.App,
+ CreatedAt: now().Unix(),
+ }); err != nil {
+ log.Printf("domains: upsert catch-all %s: %v", star, err)
+ }
+ }
}
}
for _, a := range existing {
@@ -70,5 +133,32 @@ func ReconcileAliases(ctx context.Context, s release.Store, reg *registry.Regist
}
}
}
+
+ // wildcard_domains is wholly pass-owned, like route_aliases.
+ existingWild, err := reg.ListWildcardDomains()
+ if err != nil {
+ return err
+ }
+ for h, w := range desiredWild {
+ cur := false
+ for _, e := range existingWild {
+ if e.Host == h && e.Account == w.Account && e.App == w.App {
+ cur = true
+ break
+ }
+ }
+ if !cur {
+ if err := reg.UpsertWildcardDomain(w); err != nil {
+ log.Printf("domains: upsert wildcard %s: %v", h, err)
+ }
+ }
+ }
+ for _, e := range existingWild {
+ if _, ok := desiredWild[e.Host]; !ok {
+ if err := reg.DeleteWildcardDomain(e.Host); err != nil {
+ log.Printf("domains: remove wildcard %s: %v", e.Host, err)
+ }
+ }
+ }
return nil
}
diff --git a/internal/domains/aliases_test.go b/internal/domains/aliases_test.go
index 3e202c599..9cf52a5d1 100644
--- a/internal/domains/aliases_test.go
+++ b/internal/domains/aliases_test.go
@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"path/filepath"
+ "strings"
"testing"
"time"
@@ -43,6 +44,26 @@ func putClaim(t *testing.T, s release.Store, c Claim) {
}
}
+// deleteClaim removes host's claim from the bucket, so a re-run of
+// ReconcileAliases sees it gone (pass-owned removal tests).
+func deleteClaim(t *testing.T, s release.Store, host string) {
+ t.Helper()
+ if err := s.Delete(context.Background(), ClaimKey(host)); err != nil {
+ t.Fatal(err)
+ }
+}
+
+// addRoute upserts one route into reg, same shape the other tests in this
+// file inline into aliasRegWith's constructor call.
+func addRoute(t *testing.T, reg *registry.Registry, host, account, app string) {
+ t.Helper()
+ if err := reg.Upsert(registry.Route{
+ Host: host, App: app, Account: account, Kind: "instance", Status: "ready",
+ }); err != nil {
+ t.Fatal(err)
+ }
+}
+
func aliasNow() func() time.Time {
return func() time.Time { return time.Unix(1755000000, 0) }
}
@@ -243,3 +264,91 @@ func TestReconcileAliasesIdempotent(t *testing.T) {
t.Fatalf("alias row changed on re-run: first=%+v second=%+v", first[0], second[0])
}
}
+
+func TestReconcileDerivesWildcardState(t *testing.T) {
+ ctx := context.Background()
+ s := release.NewDirStore(t.TempDir())
+ reg := aliasRegWith(t)
+ // Local primary route, tenancy bab/woodstar-relay.
+ addRoute(t, reg, "woodstar-relay.bab.oncarlos.com", "bab", "woodstar-relay")
+
+ putClaim(t, s, Claim{Host: "woodstar.app", Account: "bab", App: "woodstar-relay",
+ Route: "woodstar-relay.bab.oncarlos.com", CreatedAt: "2026-08-13T00:00:00Z",
+ Wildcard: true, Catchall: true})
+ // Wildcard-only (no catchall): row yes, *. alias no.
+ putClaim(t, s, Claim{Host: "certonly.example", Account: "bab", App: "woodstar-relay",
+ Route: "woodstar-relay.bab.oncarlos.com", CreatedAt: "2026-08-13T00:00:00Z", Wildcard: true})
+ // Wildcard claim whose primary is NOT on this box: no row.
+ putClaim(t, s, Claim{Host: "elsewhere.example", Account: "bab", App: "woodstar-relay",
+ Route: "not-here.bab.oncarlos.com", CreatedAt: "2026-08-13T00:00:00Z", Wildcard: true})
+ // Invalid host shape: no row, no alias.
+ putClaim(t, s, Claim{Host: "bad host.example", Account: "bab", App: "woodstar-relay",
+ Route: "woodstar-relay.bab.oncarlos.com", CreatedAt: "2026-08-13T00:00:00Z", Wildcard: true, Catchall: true})
+
+ if err := ReconcileAliases(ctx, s, reg, func() time.Time { return time.Unix(1000, 0) }); err != nil {
+ t.Fatal(err)
+ }
+
+ wds, err := reg.ListWildcardDomains()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(wds) != 2 || wds[0].Host != "certonly.example" || wds[1].Host != "woodstar.app" {
+ t.Fatalf("wildcard_domains = %+v", wds)
+ }
+ aliases, _ := reg.ListAliases()
+ var stars []string
+ for _, a := range aliases {
+ if strings.HasPrefix(a.Alias, "*.") {
+ stars = append(stars, a.Alias+"->"+a.Host)
+ }
+ }
+ if len(stars) != 1 || stars[0] != "*.woodstar.app->woodstar-relay.bab.oncarlos.com" {
+ t.Fatalf("catch-all aliases = %v", stars)
+ }
+
+ // Claim removed => derived state removed (pass-owned).
+ deleteClaim(t, s, "woodstar.app")
+ if err := ReconcileAliases(ctx, s, reg, func() time.Time { return time.Unix(2000, 0) }); err != nil {
+ t.Fatal(err)
+ }
+ wds, _ = reg.ListWildcardDomains()
+ if len(wds) != 1 || wds[0].Host != "certonly.example" {
+ t.Fatalf("after claim delete: %+v", wds)
+ }
+ aliases, _ = reg.ListAliases()
+ for _, a := range aliases {
+ if strings.HasPrefix(a.Alias, "*.") {
+ t.Fatalf("catch-all alias must be removed with its claim: %+v", a)
+ }
+ }
+}
+
+func TestReconcileWildcardEmptyRouteClaim(t *testing.T) {
+ // First-route claim (#82's shape): empty Route, the claim's own host IS
+ // the primary once materialized. Catchall aliases that primary.
+ ctx := context.Background()
+ s := release.NewDirStore(t.TempDir())
+ reg := aliasRegWith(t)
+ addRoute(t, reg, "story.example", "bab", "story")
+ putClaim(t, s, Claim{Host: "story.example", Account: "bab", App: "story",
+ CreatedAt: "2026-08-13T00:00:00Z", Wildcard: true, Catchall: true})
+
+ if err := ReconcileAliases(ctx, s, reg, func() time.Time { return time.Unix(1000, 0) }); err != nil {
+ t.Fatal(err)
+ }
+ wds, _ := reg.ListWildcardDomains()
+ if len(wds) != 1 || wds[0].Host != "story.example" {
+ t.Fatalf("wildcard_domains = %+v", wds)
+ }
+ aliases, _ := reg.ListAliases()
+ found := false
+ for _, a := range aliases {
+ if a.Alias == "*.story.example" && a.Host == "story.example" {
+ found = true
+ }
+ }
+ if !found {
+ t.Fatalf("want *.story.example -> story.example, got %+v", aliases)
+ }
+}