// Global hostname claims — the desired-state object behind attaching an // extra hostname to a route (#151). A claim lives at ClaimKey(host), the // SAME key regardless of which account or app is asking, so the bucket key // itself is the cross-account uniqueness gate: two accounts racing for the // same host cannot both win, because there is only ever one object to Put. // Later tasks derive registry rows from these claims; nothing here talks to // the registry. package domains import ( "context" "crypto/sha256" "encoding/hex" "encoding/json" "errors" "fmt" "log" "os" "regexp" "strings" "time" "github.com/carlosframework/platform/internal/release" ) // ClaimsPrefix is where every hostname claim lives. const ClaimsPrefix = "domains/" // ClaimKey is the bucket key for host's claim. func ClaimKey(host string) string { return ClaimsPrefix + host + ".json" } // Claim is the wire shape at ClaimKey(Host) — the global record of who owns // an extra hostname attached to a route. Route is omitempty because a claim // can exist ahead of the route it will back (reserved but not yet wired); // CreatedBy is omitempty because a claim minted by a box-side process, not a // human door, has no operator to name. type Claim struct { Acquisition string `json:"acq,omitempty"` RecordIncarnation string `json:"incarnation,omitempty"` OwnershipState string `json:"ownership_state,omitempty"` Schema int `json:"schema"` // 1 Host string `json:"host"` Account string `json:"account"` App string `json:"app"` Route string `json:"route,omitempty"` CreatedAt string `json:"created_at"` CreatedBy string `json:"created_by,omitempty"` // Wildcard and Catchall are the woodstar-carloku branch's two claim // capabilities (its migration spec §4-5 gives them meaning; nothing in // this package acts on either). They are deliberately orthogonal: // Wildcard opts the domain into a delegated-ACME wildcard cert (one // *. cert covering named aliases, no per-host CT-logged certs); // Catchall derives the *. catch-all alias entry to the primary // route, and the attach surface refuses it without Wildcard (a // catch-all nobody can serve TLS for is a config error, not a state). // Both are declared here so a converge rewrite by this binary // round-trips them instead of stripping fields it doesn't know — the // additive-wire rule applied to writers, not just readers. Converge // preserves only DECLARED fields (json.Unmarshal drops unknown ones), // which is exactly why any future claim field must be added here in // the same change that first writes it. Wildcard bool `json:"wildcard,omitempty"` Catchall bool `json:"catchall,omitempty"` // Paths splits this hostname's traffic by leading URL segment: a bare // first segment ("paulca", never "/paulca") mapped to the HOSTNAME of the // route that serves it. Absent or empty means this host routes by // hostname alone — the behaviour of every host today, and the reason this // field is additive in the strongest sense: no row, no map, no change. // // This is a THIRD claim capability alongside Wildcard/Catchall, and it is // declared here for the reason stated above them — converge preserves only // DECLARED fields, so an undeclared field would be stripped by any binary // that rewrote the claim. // // The writer is the tenant's own platform, publishing the whole map // regenerated from its own database. Deltas were rejected deliberately: a // lost delta desyncs the two systems silently, whereas a wholesale // republish is always a full repair. Paths map[string]string `json:"paths,omitempty"` // Instances marks an instance catch-all: a claim whose Host is // "*." (InstanceCatchallHost) and which serves each of its // app's instances at