Skip to main content

tatara_process/
hostname.rs

1//! Hostname helpers — typed FQDN formatting matching `nix/lib/fleet-
2//! domains.nix`'s `mkHostname` pattern.
3//!
4//! The substrate move: every FQDN this codebase emits is computed
5//! here. Two functions ([`fmt_fqdn`] for the per-instance form +
6//! [`fmt_fqdn_stable`] for the unprefixed stable-claim form) and one
7//! deterministic ephemeral-id derivation ([`ephemeral_id_from_spec`])
8//! are the single source of truth — no string `format!()` of DNS
9//! syntax anywhere else in the tree.
10//!
11//! Forms:
12//!
13//! ```text
14//!   Per-instance: ${app}.${ephemeral_id}.${cluster}.${location}.${domain}
15//!   Stable:       ${app}.${cluster}.${location}.${domain}
16//! ```
17//!
18//! Where `${ephemeral_id}` is:
19//!
20//! * `RoutingHostname.instance` when set — a named slot like
21//!   `demo-prod` or `pr-1234`.
22//! * `EPHEMERAL_ID_HASH_LEN` (= 8) hex chars of
23//!   `BLAKE3(canonical_spec_json)` when unset — a content-hash slot
24//!   that changes only when the Process's spec changes.
25//!
26//! All four FQDN segments are validated as RFC 1123 DNS labels at
27//! the boundary — lowercase alphanumeric + hyphen, 1–63 chars, no
28//! leading/trailing hyphen. Validation errors surface as typed
29//! [`HostnameError`] variants so callers can render targeted
30//! operator messages.
31
32use serde::Serialize;
33
34use crate::routing::RoutingHostname;
35
36/// Number of hex chars from BLAKE3 to use as the content-hash form
37/// of `ephemeral_id`. 8 = 32 bits of entropy; collision probability
38/// at 1k concurrent Processes ≈ 1 in 8.5 million. Comfortable for
39/// any single cluster's working set, room to grow.
40pub const EPHEMERAL_ID_HASH_LEN: usize = 8;
41
42/// Reserved 2-part forms forbidden as `app` values (saguão control
43/// plane — see pleme-io CLAUDE.md §Fleet hostname pattern).
44const RESERVED_APP_LABELS: &[&str] = &["auth", "cracha"];
45
46/// Why a hostname can't be formatted. Typed so callers can branch.
47#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)]
48pub enum HostnameError {
49    #[error("invalid DNS label {label:?} for segment {segment}: {reason}")]
50    InvalidLabel {
51        segment: &'static str,
52        label: String,
53        reason: &'static str,
54    },
55    #[error("app label {0:?} is reserved for the saguão control plane")]
56    ReservedApp(String),
57}
58
59/// Substrate extension trait over `Result<T, HostnameError>` — the ONE
60/// substrate owner of the `.map_err(|e| anyhow::anyhow!("<ctx>: {e}"))`
61/// wrap-shape every reconciler consumer restated by hand at the
62/// hostname-formatter → anyhow error boundary. Peer of
63/// [`crate::kube_error::KubeResultExt`] on the wrap-shape axis; the two
64/// traits partition the flatten-wrap space by underlying error type
65/// (`kube::Error` on that peer, [`HostnameError`] on this one).
66///
67/// Pre-lift the shape was hand-authored at THREE sites in
68/// `tatara-reconciler::render::render_routing` — each of the three
69/// `HostnameError`-returning hostname primitives ([`ephemeral_id_from_spec`],
70/// [`fmt_fqdn`], [`fmt_fqdn_stable`]) had ITS consumer restate the
71/// SAME closure at the R9 routing-edge render — capture the
72/// [`HostnameError`], prepend a static context slug identifying which
73/// hostname primitive faulted, delegate the tail to [`HostnameError`]'s
74/// `Display` impl via the `{e}` slot — differing only in the context
75/// slug prefix each callsite stamped (`"ephemeral_id_from_spec"` /
76/// `"fmt_fqdn (per-instance)"` / `"fmt_fqdn_stable"`). Three
77/// hand-authored callsites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication
78/// threshold.
79///
80/// Post-lift each callsite reads
81/// `<hostname-primitive>().hostname_ctx("<slug>")?` and the wrap-shape
82/// lives at ONE substrate owner here. The composed [`anyhow::Error`]'s
83/// `Display` is byte-identical to the pre-lift chain
84/// (`format!("{ctx}: {e}")`, threading the [`HostnameError`]'s own
85/// `Display` verbatim into the `{e}` slot), so operator-facing log
86/// output and any error-chain greps still match bytewise. A regression
87/// that drifts the separator, swaps the two slots, or wraps the
88/// [`HostnameError`] with a chain-form `source` (which would change
89/// `Display` output on the `err` slot) surfaces at
90/// [`tests::hostname_ctx_static_str_context_matches_pre_lift_format_bytewise`]
91/// rather than as silent operator-facing drift across the three
92/// pre-lift consumers.
93///
94/// ### Naming — `hostname_ctx`, not `anyhow::Context::context`
95///
96/// Same discipline as [`crate::kube_error::KubeResultExt::kube_ctx`] —
97/// `anyhow::Context::context` wraps the source in a chain (so `Display`
98/// emits only the context slug and callers reach the [`HostnameError`]
99/// via [`std::error::Error::source`] traversal), while this trait's
100/// `hostname_ctx` FLATTENS to a display-prefix shape (`"<ctx>: <HostnameError
101/// display>"`) — the pre-lift wire format every consumer's log output
102/// already encoded. Sharing the name would let a caller who has
103/// `anyhow::Context` in scope resolve to the WRONG method (a chain-wrap
104/// instead of the display-prefix flatten) and silently change every
105/// operator log message.
106///
107/// ### Static-slug only (no `_with` peer yet)
108///
109/// Every current callsite composes its slug at compile time
110/// (`"ephemeral_id_from_spec"`, `"fmt_fqdn (per-instance)"`,
111/// `"fmt_fqdn_stable"`); no consumer needs a `format!`-composed
112/// runtime slug. The static-`&'static str` binding keeps the substrate
113/// contract minimal — a future dynamic-slug consumer would add a
114/// `hostname_ctx_with` peer here matching the `kube_ctx_with` shape,
115/// but until then this trait exposes only the static peer.
116///
117/// ### `#[must_use]`
118///
119/// Every consumer threads the `?` short-circuit onto its handler's
120/// `Result<_, anyhow::Error>` return — dropping the wrap swallows the
121/// hostname-format failure entirely, which is never the intended
122/// semantic (a rejected DNS label at emit time silently produces a
123/// resource with a `""` FQDN slot that the K8s API server accepts and
124/// then no downstream Ingress / DNSEndpoint dispatcher can route to).
125/// The attribute surfaces that as a warning at every call site.
126///
127/// Theory anchor: THEORY.md §VI.1 (generation over composition — the
128/// [`HostnameError`] → anyhow-with-display-prefix wrap-shape recurred
129/// at three hand-authored sites past the ★★ PRIME-DIRECTIVE ≥ 2
130/// duplication trigger, and is lifted to ONE substrate owner here).
131/// THEORY.md §II.1 invariant 5 (composition preserves proofs — a
132/// regression that drifts the display-prefix separator or the byte-
133/// shape at ONE site surfaces here at the substrate pin rather than
134/// as silent operator-facing skew across every render_routing tick).
135pub trait HostnameResultExt<T>: Sized {
136    /// Wrap the [`HostnameError`] (if any) with a static context
137    /// prefix, producing an [`anyhow::Result`] whose error `Display`
138    /// reads exactly `"<context>: <HostnameError display>"`.
139    #[must_use = "an error wrap that isn't threaded via `?` swallows the hostname-format failure"]
140    fn hostname_ctx(self, context: &'static str) -> anyhow::Result<T>;
141}
142
143impl<T> HostnameResultExt<T> for Result<T, HostnameError> {
144    // Delegates the display-prefix wrap-shape body to the generic
145    // substrate owner [`crate::err_ctx::ErrCtxExt`]. Pre-lift the body
146    // restated the `.map_err(|e| anyhow::anyhow!("{context}: {e}"))`
147    // closure by hand, byte-identical to the three sibling specialized
148    // peers ([`crate::kube_error::KubeResultExt`],
149    // [`crate::anyhow_flatten::FlattenCtxExt`],
150    // [`crate::err_ctx::ErrCtxExt`] itself). Post-lift the byte-shape
151    // body lives at ONE substrate owner + this impl is a naming-layer
152    // delegate — [`HostnameError`] impls `Display` via `thiserror` so
153    // the generic [`crate::err_ctx::ErrCtxExt`] impl applies to
154    // `Result<T, HostnameError>` directly. Pinned by
155    // [`crate::err_ctx::tests::err_ctx_agrees_with_hostname_ctx_on_hostname_error_result`]
156    // so a regression that re-open-coded the body would surface there
157    // rather than as silent operator-facing skew between the
158    // hostname-side consumer (`render_routing`) and the sibling peer
159    // families.
160
161    #[inline]
162    fn hostname_ctx(self, context: &'static str) -> anyhow::Result<T> {
163        use crate::err_ctx::ErrCtxExt;
164        self.err_ctx(context)
165    }
166}
167
168/// Format the per-instance FQDN.
169///
170/// ```
171/// use tatara_process::hostname::fmt_fqdn;
172/// let fqdn = fmt_fqdn("api", "demo-prod", "pleme-dev", "use1", "quero.lol").unwrap();
173/// assert_eq!(fqdn, "api.demo-prod.pleme-dev.use1.quero.lol");
174/// ```
175pub fn fmt_fqdn(
176    app: &str,
177    ephemeral_id: &str,
178    cluster: &str,
179    location: &str,
180    domain: &str,
181) -> Result<String, HostnameError> {
182    validate_app(app)?;
183    validate_label("ephemeral_id", ephemeral_id)?;
184    validate_fqdn_suffix(cluster, location, domain)?;
185    Ok(format!(
186        "{app}.{ephemeral_id}.{cluster}.{location}.{domain}"
187    ))
188}
189
190/// Format the stable-claim FQDN (no `ephemeral_id` segment).
191///
192/// ```
193/// use tatara_process::hostname::fmt_fqdn_stable;
194/// let fqdn = fmt_fqdn_stable("api", "pleme-dev", "use1", "quero.lol").unwrap();
195/// assert_eq!(fqdn, "api.pleme-dev.use1.quero.lol");
196/// ```
197pub fn fmt_fqdn_stable(
198    app: &str,
199    cluster: &str,
200    location: &str,
201    domain: &str,
202) -> Result<String, HostnameError> {
203    validate_app(app)?;
204    validate_fqdn_suffix(cluster, location, domain)?;
205    Ok(format!("{app}.{cluster}.{location}.{domain}"))
206}
207
208/// Compute the content-hash form of `ephemeral_id` for a given
209/// `ProcessSpec`. Stable across reconciles of the same spec; new
210/// spec content ⇒ new hash ⇒ new DNS slot.
211///
212/// Uses [`EPHEMERAL_ID_HASH_LEN`] hex chars of BLAKE3 over the
213/// canonical JSON of the spec.
214pub fn ephemeral_id_from_spec<T: Serialize>(spec: &T) -> Result<String, HostnameError> {
215    // Canonical-bytes projection rides through the ONE substrate
216    // primitive [`crate::three_pillar::canonical_bytes`] — the
217    // strict, error-propagating peer of `three_pillar::pillar_bytes`
218    // that owns the 2-link `serde_json::to_value → serde_json::to_vec`
219    // canonicalization chain. Pre-lift this site read through a
220    // module-private `canonical_json` helper (removed) that restated
221    // the same 2-link chain byte-for-byte alongside the peer at
222    // `tatara-export-worker::canonical_json` — two hand-authored
223    // sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold.
224    // Post-lift both consumers name the payload ONCE and route
225    // through the ONE substrate owner; the discard of the concrete
226    // `serde_json::Error` diagnostic rides through the local
227    // `HostnameError::InvalidLabel` projection at this callsite so
228    // the operator-facing wording stays byte-identical to the
229    // pre-lift shape.
230    let bytes =
231        crate::three_pillar::canonical_bytes(spec).map_err(|_| HostnameError::InvalidLabel {
232            segment: "spec",
233            label: "<unserializable>".into(),
234            reason: "spec failed to canonicalize",
235        })?;
236    Ok(short_hex_blake3(&bytes, EPHEMERAL_ID_HASH_LEN))
237}
238
239/// Resolve the `ephemeral_id` for a single [`RoutingHostname`]
240/// entry. Named slot wins if set; otherwise the content-hash form
241/// is computed from the surrounding `ProcessSpec` (caller passes
242/// in via `fallback_hash`).
243///
244/// The split-arg design keeps this pure — the spec hash is computed
245/// once by the caller (via [`ephemeral_id_from_spec`]) and reused
246/// across every hostname on the same Process.
247pub fn resolve_ephemeral_id<'a>(hostname: &'a RoutingHostname, fallback_hash: &'a str) -> &'a str {
248    match &hostname.instance {
249        Some(s) if !s.is_empty() => s.as_str(),
250        _ => fallback_hash,
251    }
252}
253
254// ─── Validation ────────────────────────────────────────────────────
255
256fn validate_label(segment: &'static str, label: &str) -> Result<(), HostnameError> {
257    if label.is_empty() || label.len() > 63 {
258        return Err(HostnameError::InvalidLabel {
259            segment,
260            label: label.to_string(),
261            reason: "must be 1–63 characters",
262        });
263    }
264    if label.starts_with('-') || label.ends_with('-') {
265        return Err(HostnameError::InvalidLabel {
266            segment,
267            label: label.to_string(),
268            reason: "must not start or end with a hyphen",
269        });
270    }
271    if !label
272        .chars()
273        .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
274    {
275        return Err(HostnameError::InvalidLabel {
276            segment,
277            label: label.to_string(),
278            reason: "must contain only [a-z0-9-]",
279        });
280    }
281    Ok(())
282}
283
284/// Validate a caller-supplied `app` label at the fleet-hostname
285/// boundary — the ONE substrate primitive owning the two-step (RFC 1123
286/// DNS label + saguão-reservation reject) check every hostname composer
287/// runs on its `app` slot BEFORE stamping it into an emitted FQDN.
288///
289/// Pre-lift the two-step check was hand-authored at TWO adjacent public
290/// FQDN composers in this module past the ★★ PRIME-DIRECTIVE ≥ 2
291/// duplication threshold:
292///
293/// * [`fmt_fqdn`] — the per-instance form; the 4-line prelude
294///   preceded the sibling `validate_label("ephemeral_id", …)` +
295///   cluster / location / domain checks.
296/// * [`fmt_fqdn_stable`] — the unprefixed stable-claim form; the same
297///   4-line prelude preceded the cluster / location / domain checks
298///   with no `ephemeral_id` slot in between.
299///
300/// Both restated the SAME 4-line prelude verbatim: (1)
301/// `validate_label("app", app)?` to enforce the RFC 1123 shape (1–63
302/// chars, lowercase alphanumeric + hyphen, no leading / trailing
303/// hyphen), then (2) an early-return
304/// `HostnameError::ReservedApp(app.to_string())` when the label
305/// appears in the module-private [`RESERVED_APP_LABELS`] set
306/// (currently `"auth"` / `"cracha"` — the saguão control-plane
307/// reservations declared in pleme-io CLAUDE.md § Fleet hostname
308/// pattern).
309///
310/// Post-lift each callsite reads `validate_app(app)?` and the ordered
311/// two-step check lives at ONE substrate owner. The step ORDER is
312/// load-bearing: `validate_label` runs first so a reserved label whose
313/// spelling ALSO violates RFC 1123 (an operator who typed `"AUTH"`
314/// instead of `"auth"`) surfaces as
315/// [`HostnameError::InvalidLabel`] (the underlying shape defect),
316/// not as [`HostnameError::ReservedApp`] (the higher-level policy
317/// gate) — matching the pre-lift order both composers hand-authored.
318/// A regression that swapped the two steps would silently re-classify
319/// every such input and callers pattern-matching on the two variants
320/// would branch differently.
321///
322/// A future extension to the reserved set (adding a third saguão name,
323/// a per-cluster reservation surface, a normalized-form lookup that
324/// treats `"Auth"` and `"auth"` as the same reservation) lands at THIS
325/// ONE substrate primitive and both [`fmt_fqdn`] + [`fmt_fqdn_stable`]
326/// inherit the upgrade mechanically — no per-composer edit at either
327/// call site, no drift risk for a third future FQDN-shape composer
328/// that plugs into the same reservation policy.
329///
330/// Theory anchor: THEORY.md §VI.1 (generation over composition — the
331/// 4-line two-step check recurred at two hand-authored composer
332/// preludes past the ★★ PRIME-DIRECTIVE ≥ 2 duplication trigger and
333/// lifts to ONE substrate owner here). THEORY.md §II.1 invariant 5
334/// (composition preserves proofs — the pin block below binds the
335/// primitive at fail-before-pass-after granularity so a regression
336/// that reorders the two steps, drops one, or drifts the typed error
337/// variant surfaces at THESE pins rather than as silent fleet-
338/// hostname skew across every downstream FQDN emit).
339fn validate_app(app: &str) -> Result<(), HostnameError> {
340    validate_label("app", app)?;
341    if RESERVED_APP_LABELS.contains(&app) {
342        return Err(HostnameError::ReservedApp(app.to_string()));
343    }
344    Ok(())
345}
346
347fn validate_domain(segment: &'static str, domain: &str) -> Result<(), HostnameError> {
348    if domain.is_empty() {
349        return Err(HostnameError::InvalidLabel {
350            segment,
351            label: domain.to_string(),
352            reason: "must not be empty",
353        });
354    }
355    // Multi-label domain — every dot-separated piece must be a valid label.
356    for piece in domain.split('.') {
357        validate_label(segment, piece)?;
358    }
359    Ok(())
360}
361
362/// Validate the shared 3-segment `${cluster}.${location}.${domain}` FQDN
363/// suffix — the ONE substrate primitive owning the ordered
364/// `validate_label("cluster", …) → validate_label("location", …) →
365/// validate_domain("domain", …)` prelude every fleet-hostname composer
366/// runs on the trailing suffix common to BOTH forms
367/// (`${app}.${ephemeral_id}.<suffix>` per-instance and `${app}.<suffix>`
368/// stable) BEFORE stamping it into an emitted FQDN.
369///
370/// Pre-lift the 3-line ordered check was hand-authored at TWO adjacent
371/// public FQDN composers in this module past the ★★ PRIME-DIRECTIVE
372/// ≥ 2 duplication threshold:
373///
374/// * [`fmt_fqdn`] — the per-instance form; the 3-line suffix prelude
375///   followed the sibling `validate_app(app)?` +
376///   `validate_label("ephemeral_id", …)?` head checks and preceded the
377///   `format!("{app}.{ephemeral_id}.{cluster}.{location}.{domain}")`
378///   emission.
379/// * [`fmt_fqdn_stable`] — the unprefixed stable-claim form; the same
380///   3-line suffix prelude followed the sibling `validate_app(app)?`
381///   check with no `ephemeral_id` slot in between and preceded the
382///   `format!("{app}.{cluster}.{location}.{domain}")` emission.
383///
384/// Both restated the SAME 3-line prelude verbatim: (1)
385/// `validate_label("cluster", cluster)?` to enforce the RFC 1123 shape
386/// on the cluster segment, then (2)
387/// `validate_label("location", location)?` for the location segment,
388/// then (3) `validate_domain("domain", domain)?` to enforce the
389/// multi-label domain shape (non-empty AND every dot-split piece a
390/// valid RFC 1123 label).
391///
392/// Post-lift each callsite reads `validate_fqdn_suffix(cluster,
393/// location, domain)?` and the ordered 3-step suffix check lives at
394/// ONE substrate owner. The step ORDER is load-bearing on the typed-
395/// variant surface: `cluster` is checked first so a bad-cluster-and-
396/// bad-location input surfaces as `InvalidLabel { segment: "cluster", .. }`
397/// (matching the pre-lift order both composers hand-authored) rather
398/// than `InvalidLabel { segment: "location", .. }` — callers pattern-
399/// matching on the `segment` slot to render targeted operator messages
400/// branch differently, so a swap of the two steps would silently
401/// re-classify every such input.
402///
403/// Peer to [`validate_app`] on the "ordered validation prelude" axis —
404/// `validate_app` owns the 2-step head check for the `app` segment,
405/// `validate_fqdn_suffix` owns the 3-step trailing suffix check for the
406/// `cluster` / `location` / `domain` segments; together they cover the
407/// full validation surface both FQDN composers walk BEFORE the terminal
408/// `format!(...)` emission.
409///
410/// A future extension to the suffix check (a per-cluster reserved-name
411/// gate mirroring [`RESERVED_APP_LABELS`], a stricter per-location DNS
412/// label check, a per-domain TLD allowlist gate, a per-fleet
413/// normalization of the cluster segment) lands at THIS ONE substrate
414/// primitive and both [`fmt_fqdn`] + [`fmt_fqdn_stable`] inherit the
415/// upgrade mechanically — no per-composer edit at either callsite, no
416/// drift risk for a third future FQDN-shape composer (a per-region
417/// gateway form, a wildcard-cert-issuer probe form) that plugs into the
418/// same suffix policy.
419///
420/// Theory anchor: THEORY.md §VI.1 (generation over composition — the
421/// 3-line three-step check recurred at two hand-authored composer
422/// preludes past the ★★ PRIME-DIRECTIVE ≥ 2 duplication trigger and
423/// lifts to ONE substrate owner here, matching the discipline
424/// [`validate_app`] already carries on the peer head-check axis).
425/// THEORY.md §II.1 invariant 5 (composition preserves proofs — the
426/// pin block below binds the primitive at fail-before-pass-after
427/// granularity so a regression that reorders the three steps, drops
428/// one, or drifts the typed `segment` slot surfaces at THESE pins
429/// rather than as silent fleet-hostname skew across every downstream
430/// FQDN emit).
431fn validate_fqdn_suffix(cluster: &str, location: &str, domain: &str) -> Result<(), HostnameError> {
432    validate_label("cluster", cluster)?;
433    validate_label("location", location)?;
434    validate_domain("domain", domain)?;
435    Ok(())
436}
437
438fn short_hex_blake3(bytes: &[u8], len: usize) -> String {
439    // Delegate the 2-link `blake3::hash → hex` step to the substrate
440    // primitive so the ephemeral-id prefix stays byte-identical to
441    // every receipt/attestation hex-digest workspace-wide; take a
442    // stable prefix of the shared full-length hex.
443    crate::hash::hex_blake3(bytes).chars().take(len).collect()
444}
445
446#[cfg(test)]
447mod tests {
448    use super::*;
449    use serde::Deserialize;
450
451    #[test]
452    fn fmt_fqdn_per_instance() {
453        let f = fmt_fqdn("api", "demo-prod", "pleme-dev", "use1", "quero.lol").unwrap();
454        assert_eq!(f, "api.demo-prod.pleme-dev.use1.quero.lol");
455    }
456
457    #[test]
458    fn fmt_fqdn_stable_form() {
459        let f = fmt_fqdn_stable("api", "pleme-dev", "use1", "quero.lol").unwrap();
460        assert_eq!(f, "api.pleme-dev.use1.quero.lol");
461    }
462
463    #[test]
464    fn fmt_fqdn_with_multilevel_domain() {
465        let f = fmt_fqdn("api", "env-a", "rio", "us", "internal.example.com").unwrap();
466        assert_eq!(f, "api.env-a.rio.us.internal.example.com");
467    }
468
469    #[test]
470    fn reserved_app_rejected() {
471        let r = fmt_fqdn("auth", "x", "y", "z", "example.com");
472        assert!(matches!(r, Err(HostnameError::ReservedApp(_))));
473        let r = fmt_fqdn_stable("cracha", "y", "z", "example.com");
474        assert!(matches!(r, Err(HostnameError::ReservedApp(_))));
475    }
476
477    #[test]
478    fn empty_label_rejected() {
479        let r = fmt_fqdn("", "x", "y", "z", "example.com");
480        assert!(matches!(
481            r,
482            Err(HostnameError::InvalidLabel { segment: "app", .. })
483        ));
484    }
485
486    #[test]
487    fn too_long_label_rejected() {
488        let long = "a".repeat(64);
489        let r = fmt_fqdn(&long, "x", "y", "z", "example.com");
490        assert!(matches!(r, Err(HostnameError::InvalidLabel { .. })));
491    }
492
493    #[test]
494    fn uppercase_label_rejected() {
495        let r = fmt_fqdn("API", "x", "y", "z", "example.com");
496        assert!(matches!(r, Err(HostnameError::InvalidLabel { .. })));
497    }
498
499    #[test]
500    fn leading_hyphen_label_rejected() {
501        let r = fmt_fqdn("api", "-bad", "y", "z", "example.com");
502        assert!(matches!(r, Err(HostnameError::InvalidLabel { .. })));
503    }
504
505    #[test]
506    fn underscore_label_rejected() {
507        let r = fmt_fqdn("api", "x_y", "z", "w", "example.com");
508        assert!(matches!(r, Err(HostnameError::InvalidLabel { .. })));
509    }
510
511    #[test]
512    fn empty_domain_rejected() {
513        let r = fmt_fqdn("api", "x", "y", "z", "");
514        assert!(matches!(r, Err(HostnameError::InvalidLabel { .. })));
515    }
516
517    // ─── Content-hash derivation ─────────────────────────────────
518
519    #[derive(Serialize, Deserialize)]
520    struct TestSpec {
521        a: u32,
522        b: String,
523    }
524
525    #[test]
526    fn ephemeral_id_is_8_hex_chars() {
527        let spec = TestSpec {
528            a: 1,
529            b: "x".into(),
530        };
531        let id = ephemeral_id_from_spec(&spec).unwrap();
532        assert_eq!(id.len(), EPHEMERAL_ID_HASH_LEN);
533        assert!(id.chars().all(|c| c.is_ascii_hexdigit()));
534    }
535
536    #[test]
537    fn ephemeral_id_is_deterministic() {
538        let s1 = TestSpec {
539            a: 1,
540            b: "x".into(),
541        };
542        let s2 = TestSpec {
543            a: 1,
544            b: "x".into(),
545        };
546        assert_eq!(
547            ephemeral_id_from_spec(&s1).unwrap(),
548            ephemeral_id_from_spec(&s2).unwrap()
549        );
550    }
551
552    #[test]
553    fn ephemeral_id_changes_with_spec() {
554        let s1 = TestSpec {
555            a: 1,
556            b: "x".into(),
557        };
558        let s2 = TestSpec {
559            a: 2,
560            b: "x".into(),
561        };
562        let s3 = TestSpec {
563            a: 1,
564            b: "y".into(),
565        };
566        let id1 = ephemeral_id_from_spec(&s1).unwrap();
567        let id2 = ephemeral_id_from_spec(&s2).unwrap();
568        let id3 = ephemeral_id_from_spec(&s3).unwrap();
569        assert_ne!(id1, id2);
570        assert_ne!(id1, id3);
571        assert_ne!(id2, id3);
572    }
573
574    #[test]
575    fn ephemeral_id_lowercase_valid_dns_label() {
576        // BLAKE3 hex is lowercase by design; the validator must
577        // accept the output as a valid DNS label.
578        let spec = TestSpec {
579            a: 42,
580            b: "anything".into(),
581        };
582        let id = ephemeral_id_from_spec(&spec).unwrap();
583        validate_label("ephemeral_id", &id).unwrap();
584    }
585
586    // ─── resolve_ephemeral_id ────────────────────────────────────
587
588    #[test]
589    fn resolve_named_slot_wins() {
590        let h = RoutingHostname::instanced("api", "demo-prod");
591        assert_eq!(resolve_ephemeral_id(&h, "fallback"), "demo-prod");
592    }
593
594    #[test]
595    fn resolve_empty_named_falls_back() {
596        let h = RoutingHostname {
597            app: "api".into(),
598            instance: Some(String::new()),
599            cluster: None,
600        };
601        assert_eq!(resolve_ephemeral_id(&h, "abc123de"), "abc123de");
602    }
603
604    #[test]
605    fn resolve_unset_named_falls_back() {
606        let h = RoutingHostname::content_hashed("api");
607        assert_eq!(resolve_ephemeral_id(&h, "abc123de"), "abc123de");
608    }
609
610    // ─── End-to-end ──────────────────────────────────────────────
611
612    // ─── HostnameResultExt::hostname_ctx substrate pins ──────────
613    //
614    // Fail-before-pass-after granularity: the `HostnameResultExt::
615    // hostname_ctx` trait method did not exist before this commit,
616    // so each test below fails to compile pre-lift. Post-lift they
617    // collectively pin the display-prefix wrap-shape at ONE substrate
618    // owner — a regression that drifts the separator, swaps the two
619    // slots, wraps the `HostnameError` with a chain-form `source`, or
620    // promotes the pass-through arm to a synthesis (an empty `Ok(())`,
621    // a mutated context slug) surfaces HERE rather than as silent
622    // operator-facing skew across the three pre-lift consumers whose
623    // log output already encoded the flat `"<ctx>: <HostnameError
624    // display>"` shape.
625
626    fn sample_err() -> HostnameError {
627        HostnameError::InvalidLabel {
628            segment: "app",
629            label: "BAD".into(),
630            reason: "must contain only [a-z0-9-]",
631        }
632    }
633
634    #[test]
635    fn hostname_ctx_static_str_context_matches_pre_lift_format_bytewise() {
636        // Byte-shape parity pin: the wrap output of `hostname_ctx
637        // ("<slug>")` MUST be `Display`-identical to the pre-lift
638        // hand-authored `.map_err(|e| anyhow!("<slug>: {e}"))` chain.
639        // A regression that inserted a separator character (`"<slug>::
640        // <hostname>"`), dropped the space after the colon, or swapped
641        // the two slots (`"<hostname>: <slug>"`) surfaces HERE rather
642        // than as silent drift at every downstream log-output consumer.
643        let raw: Result<(), HostnameError> = Err(sample_err());
644        let via_trait = raw.hostname_ctx("fmt_fqdn (per-instance)").unwrap_err();
645        let pre_lift = anyhow::anyhow!("fmt_fqdn (per-instance): {}", sample_err());
646        assert_eq!(
647            format!("{via_trait}"),
648            format!("{pre_lift}"),
649            "hostname_ctx wrap must be Display-identical to pre-lift anyhow! chain"
650        );
651    }
652
653    #[test]
654    fn hostname_ctx_ok_arm_is_a_pure_passthrough() {
655        // Ok-arm invariant: `hostname_ctx` on `Ok(t)` MUST return
656        // `Ok(t)` verbatim — no side-effect on the payload, no
657        // synthesis of a context-tagged error, no allocation. Peer to
658        // the Err-arm byte-shape pin; a regression that promoted the
659        // Ok arm to ALWAYS produce a synthesis Error would silently
660        // break every successful hostname-format call in the pre-lift
661        // consumer set.
662        let raw: Result<&'static str, HostnameError> = Ok("api.demo-prod.pleme-dev.use1.quero.lol");
663        assert_eq!(
664            raw.hostname_ctx("noop").unwrap(),
665            "api.demo-prod.pleme-dev.use1.quero.lol"
666        );
667    }
668
669    #[test]
670    fn hostname_ctx_threads_the_underlying_hostname_error_display_verbatim() {
671        // Display-tail invariant: the wrapped `anyhow::Error`'s
672        // `Display` output MUST contain the `HostnameError`'s own
673        // `Display` output verbatim as the tail past `"<ctx>: "`. A
674        // regression that inserted a normalization (uppercase, JSON
675        // encoding, truncation) between the composed `{e}` slot and
676        // the underlying thiserror-derived Display impl would surface
677        // HERE rather than as silent operator-facing skew across the
678        // three consumers whose grep patterns already encoded the
679        // canonical `HostnameError` variant wordings ("invalid DNS
680        // label ...", "app label ... is reserved").
681        let raw: Result<(), HostnameError> = Err(HostnameError::ReservedApp("auth".into()));
682        let wrapped = raw.hostname_ctx("fmt_fqdn_stable").unwrap_err();
683        let expected_tail = format!("{}", HostnameError::ReservedApp("auth".into()));
684        let expected = format!("fmt_fqdn_stable: {expected_tail}");
685        assert_eq!(format!("{wrapped}"), expected);
686        // Also assert the tail appears verbatim as a suffix — a change
687        // in the thiserror-derived Display for ReservedApp would fail
688        // both this assertion and the RECEIPT_VERSION-in-tail invariant
689        // its docstring pins.
690        assert!(
691            format!("{wrapped}").ends_with(&expected_tail),
692            "wrap must end with the HostnameError Display verbatim"
693        );
694    }
695
696    #[test]
697    fn hostname_ctx_composes_over_ephemeral_id_from_spec_call_shape() {
698        // End-to-end composition pin: the substrate trait method
699        // composes cleanly over the `ephemeral_id_from_spec` return
700        // shape at a real callsite (the `render_routing` R9 seed).
701        // A regression that specialized the trait bound to only one
702        // hostname primitive's Result shape would surface HERE.
703        #[derive(Serialize)]
704        struct NoSuchThingAsAnUnserializableStruct {
705            a: u32,
706        }
707        let v = NoSuchThingAsAnUnserializableStruct { a: 1 };
708        let composed: anyhow::Result<String> =
709            ephemeral_id_from_spec(&v).hostname_ctx("ephemeral_id_from_spec");
710        assert!(composed.is_ok());
711        assert_eq!(composed.unwrap().len(), EPHEMERAL_ID_HASH_LEN);
712    }
713
714    // ─── validate_app substrate pins ─────────────────────────────
715    //
716    // Fail-before-pass-after granularity: the `validate_app` helper
717    // did not exist pre-lift — both [`fmt_fqdn`] and [`fmt_fqdn_stable`]
718    // hand-authored the two-step (RFC 1123 label + reserved-name reject)
719    // check inline. Post-lift the two composers thread the same
720    // primitive, so the pins below pin the primitive's SHAPE + STEP
721    // ORDER + typed-variant surface at the substrate — a regression
722    // that (a) reorders the two steps, (b) drops the reserved-name
723    // gate silently, or (c) promotes the `HostnameError::ReservedApp`
724    // arm to a generic `InvalidLabel` surfaces HERE rather than as
725    // silent skew at every downstream FQDN emit.
726
727    #[test]
728    fn validate_app_accepts_valid_lowercase_alphanumeric_label() {
729        // Happy-path pin: a valid `app` label passes the two-step
730        // check with `Ok(())`. A regression that inverted the return
731        // arm (rejected everything, matched no reserved) surfaces
732        // HERE rather than as every FQDN emit refusing every input.
733        validate_app("api").unwrap();
734        validate_app("gateway").unwrap();
735        validate_app("demo-app").unwrap();
736        validate_app("a").unwrap();
737    }
738
739    #[test]
740    fn validate_app_rejects_empty_label_with_invalid_label_variant() {
741        // Step-1 delegation pin: an empty `app` MUST surface as
742        // `HostnameError::InvalidLabel { segment: "app", .. }` from
743        // the underlying `validate_label("app", app)?` call — NOT as
744        // `ReservedApp` (which would silently reclassify the shape
745        // defect as a policy rejection).
746        assert!(matches!(
747            validate_app(""),
748            Err(HostnameError::InvalidLabel { segment: "app", .. })
749        ));
750    }
751
752    #[test]
753    fn validate_app_rejects_uppercase_label_with_invalid_label_variant() {
754        // Step-1 delegation pin: casing-invalid labels reach through
755        // to `validate_label`'s [a-z0-9-] check. A regression that
756        // short-circuited the reserved-check on a case-insensitive
757        // match ("AUTH" reads as reserved without going through the
758        // RFC 1123 gate first) would surface HERE.
759        assert!(matches!(
760            validate_app("API"),
761            Err(HostnameError::InvalidLabel { segment: "app", .. })
762        ));
763    }
764
765    #[test]
766    fn validate_app_rejects_too_long_label_with_invalid_label_variant() {
767        // Step-1 delegation pin: 64-char labels violate the RFC 1123
768        // upper bound and surface at the `validate_label` gate.
769        let long = "a".repeat(64);
770        assert!(matches!(
771            validate_app(&long),
772            Err(HostnameError::InvalidLabel { segment: "app", .. })
773        ));
774    }
775
776    #[test]
777    fn validate_app_rejects_reserved_auth_label_with_reserved_app_variant() {
778        // Step-2 pin: the currently-reserved `"auth"` slot surfaces
779        // as `HostnameError::ReservedApp("auth")` — the typed
780        // control-plane rejection callers pattern-match on. A
781        // regression that dropped this variant would silently
782        // accept the reservation and let a tenant deploy under the
783        // saguão namespace.
784        assert!(matches!(
785            validate_app("auth"),
786            Err(HostnameError::ReservedApp(ref s)) if s == "auth"
787        ));
788    }
789
790    #[test]
791    fn validate_app_rejects_reserved_cracha_label_with_reserved_app_variant() {
792        // Sibling pin to the `"auth"` reservation — pins the second
793        // currently-reserved label. A regression that dropped one
794        // reservation but not the other would surface HERE.
795        assert!(matches!(
796            validate_app("cracha"),
797            Err(HostnameError::ReservedApp(ref s)) if s == "cracha"
798        ));
799    }
800
801    #[test]
802    fn validate_app_step_order_puts_rfc_1123_check_before_reserved_check() {
803        // Load-bearing order pin: `validate_label` runs FIRST so a
804        // reserved label whose spelling ALSO violates RFC 1123
805        // (uppercase, hyphen at end, etc.) surfaces as
806        // `InvalidLabel` — the underlying SHAPE defect — not as
807        // `ReservedApp` (the higher-level POLICY gate). Callers who
808        // pattern-match on the two variants branch DIFFERENTLY on
809        // shape defects vs policy rejections, so a swap of the two
810        // steps would silently re-route every uppercase-reserved
811        // input into the wrong error arm.
812        assert!(matches!(
813            validate_app("AUTH"),
814            Err(HostnameError::InvalidLabel { segment: "app", .. })
815        ));
816        assert!(matches!(
817            validate_app("Cracha"),
818            Err(HostnameError::InvalidLabel { segment: "app", .. })
819        ));
820    }
821
822    #[test]
823    fn validate_app_matches_pre_lift_two_step_chain_bytewise_across_every_variant_shape() {
824        // Byte-shape parity pin: the substrate primitive's return
825        // MUST equal the pre-lift 4-line hand-authored chain for
826        // every representative input shape. A regression that
827        // drifted the primitive's semantics away from the pre-lift
828        // composer preludes surfaces HERE rather than as silent
829        // skew at either `fmt_fqdn` / `fmt_fqdn_stable` consumer.
830        fn pre_lift(app: &str) -> Result<(), HostnameError> {
831            validate_label("app", app)?;
832            if RESERVED_APP_LABELS.contains(&app) {
833                return Err(HostnameError::ReservedApp(app.to_string()));
834            }
835            Ok(())
836        }
837        for input in [
838            // Happy path.
839            "api",
840            "gateway",
841            "demo-app",
842            "a",
843            // Step-1 rejections.
844            "",
845            "API",
846            "-bad",
847            "bad-",
848            "with_underscore",
849            // Step-2 rejections.
850            "auth",
851            "cracha",
852            // Step-1 wins over step-2 (uppercase reserved).
853            "AUTH",
854            "Cracha",
855        ] {
856            let via_primitive = validate_app(input);
857            let via_pre_lift = pre_lift(input);
858            match (via_primitive, via_pre_lift) {
859                (Ok(()), Ok(())) => {}
860                (Err(a), Err(b)) => assert_eq!(a, b, "variant mismatch for {input:?}"),
861                (a, b) => panic!("arm mismatch for {input:?}: primitive={a:?} pre_lift={b:?}"),
862            }
863        }
864    }
865
866    #[test]
867    fn validate_app_covers_every_currently_reserved_label_at_the_primitive() {
868        // Coherence sweep: iterate the RESERVED_APP_LABELS set and
869        // verify each element rejects at the substrate. A future
870        // addition to the reserved set that forgets to update the
871        // primitive would surface HERE rather than as silent
872        // acceptance at every FQDN emit.
873        for reserved in RESERVED_APP_LABELS {
874            assert!(
875                matches!(validate_app(reserved), Err(HostnameError::ReservedApp(ref s)) if s == reserved),
876                "RESERVED_APP_LABELS entry {reserved:?} must surface as ReservedApp at the substrate"
877            );
878        }
879    }
880
881    // ─── validate_fqdn_suffix substrate pins ─────────────────────
882    //
883    // Fail-before-pass-after granularity: the `validate_fqdn_suffix`
884    // helper did not exist pre-lift — both [`fmt_fqdn`] and
885    // [`fmt_fqdn_stable`] hand-authored the three-step (`validate_label
886    // ("cluster") → validate_label("location") → validate_domain
887    // ("domain")`) suffix check inline. Post-lift the two composers
888    // thread the same primitive, so the pins below pin the primitive's
889    // SHAPE + STEP ORDER + typed-`segment` slot at the substrate — a
890    // regression that (a) reorders the three steps (silently re-
891    // classifying every multi-slot rejection into the wrong `segment`
892    // arm), (b) drops one of the three checks, or (c) swaps the
893    // `validate_domain` primitive for a `validate_label` on the domain
894    // slot (silently accepting a single-label `example` in place of
895    // the multi-label `example.com` shape) surfaces HERE rather than
896    // as silent skew at every downstream FQDN emit.
897
898    #[test]
899    fn validate_fqdn_suffix_accepts_valid_three_segment_suffix() {
900        // Happy-path pin: a valid `cluster.location.domain` triple
901        // passes the three-step check with `Ok(())`. A regression that
902        // inverted the return arm (rejected everything) surfaces HERE
903        // rather than as every FQDN emit refusing every input.
904        validate_fqdn_suffix("pleme-dev", "use1", "quero.lol").unwrap();
905        validate_fqdn_suffix("prod", "eu-west-1", "example.com").unwrap();
906        validate_fqdn_suffix("a", "b", "c.d.e").unwrap();
907    }
908
909    #[test]
910    fn validate_fqdn_suffix_rejects_empty_cluster_with_cluster_segment_slot() {
911        // Step-1 delegation pin: an empty `cluster` MUST surface as
912        // `HostnameError::InvalidLabel { segment: "cluster", .. }`
913        // from the underlying `validate_label("cluster", cluster)?`
914        // call — NOT as `segment: "location"` or `segment: "domain"`
915        // (which would silently re-classify the shape defect into a
916        // trailing-slot rejection and route callers who pattern-match
917        // on the `segment` slot to render targeted operator messages
918        // to the wrong branch).
919        assert!(matches!(
920            validate_fqdn_suffix("", "use1", "quero.lol"),
921            Err(HostnameError::InvalidLabel {
922                segment: "cluster",
923                ..
924            })
925        ));
926    }
927
928    #[test]
929    fn validate_fqdn_suffix_rejects_empty_location_with_location_segment_slot() {
930        // Step-2 delegation pin — sibling to the cluster-slot pin. A
931        // valid cluster + empty location MUST surface as `segment:
932        // "location"` (step 2 fired), NOT as `segment: "domain"`
933        // (which would mean step 3 short-circuited past step 2).
934        assert!(matches!(
935            validate_fqdn_suffix("pleme-dev", "", "quero.lol"),
936            Err(HostnameError::InvalidLabel {
937                segment: "location",
938                ..
939            })
940        ));
941    }
942
943    #[test]
944    fn validate_fqdn_suffix_rejects_empty_domain_with_domain_segment_slot() {
945        // Step-3 delegation pin — the terminal step. A valid cluster
946        // + valid location + empty domain MUST surface as `segment:
947        // "domain"` (from `validate_domain`'s empty-domain gate). A
948        // regression that swapped `validate_domain` for
949        // `validate_label` on the domain slot would silently accept
950        // an empty string with a DIFFERENT `reason` slot or reject a
951        // multi-label domain (`example.com`) that `validate_label`
952        // alone forbids (dots).
953        assert!(matches!(
954            validate_fqdn_suffix("pleme-dev", "use1", ""),
955            Err(HostnameError::InvalidLabel {
956                segment: "domain",
957                ..
958            })
959        ));
960    }
961
962    #[test]
963    fn validate_fqdn_suffix_rejects_multilabel_cluster_with_invalid_label_variant() {
964        // Cluster-shape pin: `cluster` reaches through `validate_label`
965        // (single-label check), NOT `validate_domain` (multi-label
966        // check). A dot-containing cluster MUST reject at the RFC 1123
967        // gate. A regression that widened the cluster gate to
968        // `validate_domain` would silently accept a multi-label
969        // cluster like `pleme.dev` (folding two segments into one
970        // slot at emit time and drifting every downstream Ingress /
971        // DNSEndpoint dispatcher).
972        assert!(matches!(
973            validate_fqdn_suffix("pleme.dev", "use1", "quero.lol"),
974            Err(HostnameError::InvalidLabel {
975                segment: "cluster",
976                ..
977            })
978        ));
979    }
980
981    #[test]
982    fn validate_fqdn_suffix_accepts_multilabel_domain_via_validate_domain_split() {
983        // Domain-shape pin: `domain` reaches through `validate_domain`
984        // (multi-label check via `domain.split('.')`), NOT
985        // `validate_label` (single-label check that would reject any
986        // dot). A regression that narrowed the domain gate to
987        // `validate_label` would surface HERE — every real-world
988        // domain (`quero.lol`, `example.com`, `internal.example.com`)
989        // contains at least one dot and would fail at the RFC 1123
990        // single-label check.
991        validate_fqdn_suffix("pleme-dev", "use1", "internal.example.com").unwrap();
992        validate_fqdn_suffix("pleme-dev", "use1", "a.b.c.d.e.f").unwrap();
993    }
994
995    #[test]
996    fn validate_fqdn_suffix_step_order_puts_cluster_before_location_before_domain() {
997        // Load-bearing order pin: the three steps fire in the SAME
998        // order the pre-lift composer preludes hand-authored (cluster
999        // → location → domain), so an input that violates MULTIPLE
1000        // slots surfaces at the FIRST violated slot on the ordered
1001        // walk. Callers who pattern-match on the `segment` slot to
1002        // render targeted operator messages branch differently, so a
1003        // swap of the three steps would silently re-classify every
1004        // multi-slot-invalid input.
1005        //
1006        // All three slots invalid → surfaces at `cluster` (step 1).
1007        assert!(matches!(
1008            validate_fqdn_suffix("", "", ""),
1009            Err(HostnameError::InvalidLabel {
1010                segment: "cluster",
1011                ..
1012            })
1013        ));
1014        // Valid cluster + invalid location + invalid domain → surfaces
1015        // at `location` (step 2), NOT `domain` (step 3).
1016        assert!(matches!(
1017            validate_fqdn_suffix("pleme-dev", "", ""),
1018            Err(HostnameError::InvalidLabel {
1019                segment: "location",
1020                ..
1021            })
1022        ));
1023    }
1024
1025    #[test]
1026    fn validate_fqdn_suffix_matches_pre_lift_three_step_chain_bytewise_across_every_variant_shape()
1027    {
1028        // Byte-shape parity pin: the substrate primitive's return
1029        // MUST equal the pre-lift 3-line hand-authored chain for
1030        // every representative input shape. A regression that
1031        // drifted the primitive's semantics away from the pre-lift
1032        // composer preludes surfaces HERE rather than as silent skew
1033        // at either `fmt_fqdn` / `fmt_fqdn_stable` consumer.
1034        fn pre_lift(cluster: &str, location: &str, domain: &str) -> Result<(), HostnameError> {
1035            validate_label("cluster", cluster)?;
1036            validate_label("location", location)?;
1037            validate_domain("domain", domain)?;
1038            Ok(())
1039        }
1040        for (cluster, location, domain) in [
1041            // Happy path — every corner both composers walk in
1042            // production.
1043            ("pleme-dev", "use1", "quero.lol"),
1044            ("prod", "eu-west-1", "example.com"),
1045            ("a", "b", "c.d.e"),
1046            ("cluster-1", "loc-2", "internal.example.com"),
1047            // Step-1 rejections — cluster slot fails.
1048            ("", "use1", "quero.lol"),
1049            ("BAD", "use1", "quero.lol"),
1050            ("-lead", "use1", "quero.lol"),
1051            ("with_underscore", "use1", "quero.lol"),
1052            ("pleme.dev", "use1", "quero.lol"),
1053            // Step-2 rejections — cluster ok, location fails.
1054            ("pleme-dev", "", "quero.lol"),
1055            ("pleme-dev", "USE1", "quero.lol"),
1056            ("pleme-dev", "loc_1", "quero.lol"),
1057            // Step-3 rejections — cluster + location ok, domain fails.
1058            ("pleme-dev", "use1", ""),
1059            ("pleme-dev", "use1", "-bad.com"),
1060            ("pleme-dev", "use1", "BAD.com"),
1061            // Multi-slot rejection — step 1 wins over 2 and 3.
1062            ("", "", ""),
1063            ("BAD", "USE1", ""),
1064        ] {
1065            let via_primitive = validate_fqdn_suffix(cluster, location, domain);
1066            let via_pre_lift = pre_lift(cluster, location, domain);
1067            match (via_primitive, via_pre_lift) {
1068                (Ok(()), Ok(())) => {}
1069                (Err(a), Err(b)) => assert_eq!(
1070                    a, b,
1071                    "variant mismatch for ({cluster:?}, {location:?}, {domain:?})"
1072                ),
1073                (a, b) => panic!(
1074                    "arm mismatch for ({cluster:?}, {location:?}, {domain:?}): primitive={a:?} pre_lift={b:?}"
1075                ),
1076            }
1077        }
1078    }
1079
1080    #[test]
1081    fn fmt_fqdn_routes_suffix_slots_through_validate_fqdn_suffix_primitive() {
1082        // Delegation pin: the per-instance composer routes its
1083        // trailing suffix check through `validate_fqdn_suffix`, NOT
1084        // through a re-open-coded restatement of the three-step
1085        // chain. A regression that re-inlined the pre-lift check at
1086        // the composer prelude would reintroduce the duplication the
1087        // lift removed; this pin catches it by asserting the composer
1088        // surfaces the SAME typed `segment` slot the primitive would
1089        // for a representative rejection in each of the three suffix
1090        // slots (cluster, location, domain).
1091        assert!(matches!(
1092            fmt_fqdn("api", "x", "BAD", "use1", "quero.lol"),
1093            Err(HostnameError::InvalidLabel {
1094                segment: "cluster",
1095                ..
1096            })
1097        ));
1098        assert!(matches!(
1099            fmt_fqdn("api", "x", "pleme-dev", "", "quero.lol"),
1100            Err(HostnameError::InvalidLabel {
1101                segment: "location",
1102                ..
1103            })
1104        ));
1105        assert!(matches!(
1106            fmt_fqdn("api", "x", "pleme-dev", "use1", ""),
1107            Err(HostnameError::InvalidLabel {
1108                segment: "domain",
1109                ..
1110            })
1111        ));
1112    }
1113
1114    #[test]
1115    fn fmt_fqdn_stable_routes_suffix_slots_through_validate_fqdn_suffix_primitive() {
1116        // Sibling delegation pin — same shape as the per-instance pin
1117        // above but for the stable-claim composer. Both composers now
1118        // share the primitive; a regression that re-inlined the chain
1119        // at either site surfaces at ONE of the two pins rather than
1120        // at every downstream FQDN emit.
1121        assert!(matches!(
1122            fmt_fqdn_stable("api", "BAD", "use1", "quero.lol"),
1123            Err(HostnameError::InvalidLabel {
1124                segment: "cluster",
1125                ..
1126            })
1127        ));
1128        assert!(matches!(
1129            fmt_fqdn_stable("api", "pleme-dev", "", "quero.lol"),
1130            Err(HostnameError::InvalidLabel {
1131                segment: "location",
1132                ..
1133            })
1134        ));
1135        assert!(matches!(
1136            fmt_fqdn_stable("api", "pleme-dev", "use1", ""),
1137            Err(HostnameError::InvalidLabel {
1138                segment: "domain",
1139                ..
1140            })
1141        ));
1142    }
1143
1144    #[test]
1145    fn fmt_fqdn_and_fmt_fqdn_stable_agree_on_suffix_rejection_bytewise() {
1146        // Cross-composer coherence pin: post-lift both composers route
1147        // their suffix check through the ONE substrate primitive, so
1148        // the SAME suffix-slot violation surfaces byte-identically at
1149        // BOTH composers (differing only in the `ephemeral_id` arg
1150        // presence). A regression that re-inlined the chain at one
1151        // composer but not the other would silently drift the two
1152        // consumers' typed-`segment` slot; this pin binds them to the
1153        // ONE substrate primitive so any such drift surfaces HERE.
1154        for (cluster, location, domain, expected_segment) in [
1155            ("BAD", "use1", "quero.lol", "cluster"),
1156            ("pleme-dev", "", "quero.lol", "location"),
1157            ("pleme-dev", "use1", "", "domain"),
1158            ("pleme.dev", "use1", "quero.lol", "cluster"),
1159        ] {
1160            let via_per_instance = fmt_fqdn("api", "x", cluster, location, domain);
1161            let via_stable = fmt_fqdn_stable("api", cluster, location, domain);
1162            assert!(
1163                matches!(
1164                    &via_per_instance,
1165                    Err(HostnameError::InvalidLabel { segment, .. }) if *segment == expected_segment
1166                ),
1167                "fmt_fqdn must surface segment={expected_segment:?} for ({cluster:?}, {location:?}, {domain:?}); got {via_per_instance:?}"
1168            );
1169            assert!(
1170                matches!(
1171                    &via_stable,
1172                    Err(HostnameError::InvalidLabel { segment, .. }) if *segment == expected_segment
1173                ),
1174                "fmt_fqdn_stable must surface segment={expected_segment:?} for ({cluster:?}, {location:?}, {domain:?}); got {via_stable:?}"
1175            );
1176            // And the two composers' error variants agree bytewise on
1177            // the suffix rejection — they should, since both route
1178            // through the SAME primitive.
1179            match (via_per_instance, via_stable) {
1180                (Err(a), Err(b)) => assert_eq!(
1181                    a, b,
1182                    "fmt_fqdn and fmt_fqdn_stable must agree on suffix rejection for ({cluster:?}, {location:?}, {domain:?})"
1183                ),
1184                pair => panic!(
1185                    "expected both composers to reject ({cluster:?}, {location:?}, {domain:?}) with the SAME variant; got {pair:?}"
1186                ),
1187            }
1188        }
1189    }
1190
1191    #[test]
1192    fn fmt_fqdn_routes_app_slot_through_validate_app_primitive() {
1193        // Delegation pin: the per-instance composer routes its `app`
1194        // slot check through `validate_app`, NOT through a re-open-
1195        // coded restatement of the two-step chain. A regression that
1196        // inlined the pre-lift check at the composer prelude would
1197        // reintroduce the duplication the lift removed; this pin
1198        // catches it by asserting the composer surfaces the SAME
1199        // typed error the primitive would for a representative
1200        // input in each of the two rejection arms.
1201        assert!(matches!(
1202            fmt_fqdn("AUTH", "x", "y", "z", "example.com"),
1203            Err(HostnameError::InvalidLabel { segment: "app", .. })
1204        ));
1205        assert!(matches!(
1206            fmt_fqdn("auth", "x", "y", "z", "example.com"),
1207            Err(HostnameError::ReservedApp(ref s)) if s == "auth"
1208        ));
1209    }
1210
1211    #[test]
1212    fn fmt_fqdn_stable_routes_app_slot_through_validate_app_primitive() {
1213        // Sibling delegation pin — same shape as the per-instance
1214        // pin above but for the stable-claim composer. Both
1215        // composers now share the primitive; a regression that
1216        // re-inlined the chain at either site surfaces at ONE of
1217        // the two pins rather than at every downstream FQDN emit.
1218        assert!(matches!(
1219            fmt_fqdn_stable("Cracha", "y", "z", "example.com"),
1220            Err(HostnameError::InvalidLabel { segment: "app", .. })
1221        ));
1222        assert!(matches!(
1223            fmt_fqdn_stable("cracha", "y", "z", "example.com"),
1224            Err(HostnameError::ReservedApp(ref s)) if s == "cracha"
1225        ));
1226    }
1227
1228    #[test]
1229    fn end_to_end_named_and_unnamed_for_same_process() {
1230        let spec = TestSpec {
1231            a: 1,
1232            b: "x".into(),
1233        };
1234        let hash = ephemeral_id_from_spec(&spec).unwrap();
1235
1236        let h_named = RoutingHostname::instanced("api", "demo-prod");
1237        let h_anon = RoutingHostname::content_hashed("gateway");
1238
1239        let id_named = resolve_ephemeral_id(&h_named, &hash);
1240        let id_anon = resolve_ephemeral_id(&h_anon, &hash);
1241
1242        let fqdn_named =
1243            fmt_fqdn(&h_named.app, id_named, "pleme-dev", "use1", "quero.lol").unwrap();
1244        let fqdn_anon = fmt_fqdn(&h_anon.app, id_anon, "pleme-dev", "use1", "quero.lol").unwrap();
1245
1246        assert_eq!(fqdn_named, "api.demo-prod.pleme-dev.use1.quero.lol");
1247        assert!(fqdn_anon.starts_with("gateway."));
1248        assert!(fqdn_anon.ends_with(".pleme-dev.use1.quero.lol"));
1249        // 5 named segments (app + eph_id + cluster + location + domain),
1250        // but `domain` itself splits as `quero.lol` ⇒ 6 dot-delimited
1251        // pieces. The shape, not the count, is the invariant.
1252        assert_eq!(fqdn_anon.matches('.').count(), 5);
1253    }
1254}