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    #[inline]
145    fn hostname_ctx(self, context: &'static str) -> anyhow::Result<T> {
146        self.map_err(|e| anyhow::anyhow!("{context}: {e}"))
147    }
148}
149
150/// Format the per-instance FQDN.
151///
152/// ```
153/// use tatara_process::hostname::fmt_fqdn;
154/// let fqdn = fmt_fqdn("api", "demo-prod", "pleme-dev", "use1", "quero.lol").unwrap();
155/// assert_eq!(fqdn, "api.demo-prod.pleme-dev.use1.quero.lol");
156/// ```
157pub fn fmt_fqdn(
158    app: &str,
159    ephemeral_id: &str,
160    cluster: &str,
161    location: &str,
162    domain: &str,
163) -> Result<String, HostnameError> {
164    validate_app(app)?;
165    validate_label("ephemeral_id", ephemeral_id)?;
166    validate_label("cluster", cluster)?;
167    validate_label("location", location)?;
168    validate_domain("domain", domain)?;
169    Ok(format!(
170        "{app}.{ephemeral_id}.{cluster}.{location}.{domain}"
171    ))
172}
173
174/// Format the stable-claim FQDN (no `ephemeral_id` segment).
175///
176/// ```
177/// use tatara_process::hostname::fmt_fqdn_stable;
178/// let fqdn = fmt_fqdn_stable("api", "pleme-dev", "use1", "quero.lol").unwrap();
179/// assert_eq!(fqdn, "api.pleme-dev.use1.quero.lol");
180/// ```
181pub fn fmt_fqdn_stable(
182    app: &str,
183    cluster: &str,
184    location: &str,
185    domain: &str,
186) -> Result<String, HostnameError> {
187    validate_app(app)?;
188    validate_label("cluster", cluster)?;
189    validate_label("location", location)?;
190    validate_domain("domain", domain)?;
191    Ok(format!("{app}.{cluster}.{location}.{domain}"))
192}
193
194/// Compute the content-hash form of `ephemeral_id` for a given
195/// `ProcessSpec`. Stable across reconciles of the same spec; new
196/// spec content ⇒ new hash ⇒ new DNS slot.
197///
198/// Uses [`EPHEMERAL_ID_HASH_LEN`] hex chars of BLAKE3 over the
199/// canonical JSON of the spec.
200pub fn ephemeral_id_from_spec<T: Serialize>(spec: &T) -> Result<String, HostnameError> {
201    // Canonical-bytes projection rides through the ONE substrate
202    // primitive [`crate::three_pillar::canonical_bytes`] — the
203    // strict, error-propagating peer of `three_pillar::pillar_bytes`
204    // that owns the 2-link `serde_json::to_value → serde_json::to_vec`
205    // canonicalization chain. Pre-lift this site read through a
206    // module-private `canonical_json` helper (removed) that restated
207    // the same 2-link chain byte-for-byte alongside the peer at
208    // `tatara-export-worker::canonical_json` — two hand-authored
209    // sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold.
210    // Post-lift both consumers name the payload ONCE and route
211    // through the ONE substrate owner; the discard of the concrete
212    // `serde_json::Error` diagnostic rides through the local
213    // `HostnameError::InvalidLabel` projection at this callsite so
214    // the operator-facing wording stays byte-identical to the
215    // pre-lift shape.
216    let bytes =
217        crate::three_pillar::canonical_bytes(spec).map_err(|_| HostnameError::InvalidLabel {
218            segment: "spec",
219            label: "<unserializable>".into(),
220            reason: "spec failed to canonicalize",
221        })?;
222    Ok(short_hex_blake3(&bytes, EPHEMERAL_ID_HASH_LEN))
223}
224
225/// Resolve the `ephemeral_id` for a single [`RoutingHostname`]
226/// entry. Named slot wins if set; otherwise the content-hash form
227/// is computed from the surrounding `ProcessSpec` (caller passes
228/// in via `fallback_hash`).
229///
230/// The split-arg design keeps this pure — the spec hash is computed
231/// once by the caller (via [`ephemeral_id_from_spec`]) and reused
232/// across every hostname on the same Process.
233pub fn resolve_ephemeral_id<'a>(hostname: &'a RoutingHostname, fallback_hash: &'a str) -> &'a str {
234    match &hostname.instance {
235        Some(s) if !s.is_empty() => s.as_str(),
236        _ => fallback_hash,
237    }
238}
239
240// ─── Validation ────────────────────────────────────────────────────
241
242fn validate_label(segment: &'static str, label: &str) -> Result<(), HostnameError> {
243    if label.is_empty() || label.len() > 63 {
244        return Err(HostnameError::InvalidLabel {
245            segment,
246            label: label.to_string(),
247            reason: "must be 1–63 characters",
248        });
249    }
250    if label.starts_with('-') || label.ends_with('-') {
251        return Err(HostnameError::InvalidLabel {
252            segment,
253            label: label.to_string(),
254            reason: "must not start or end with a hyphen",
255        });
256    }
257    if !label
258        .chars()
259        .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
260    {
261        return Err(HostnameError::InvalidLabel {
262            segment,
263            label: label.to_string(),
264            reason: "must contain only [a-z0-9-]",
265        });
266    }
267    Ok(())
268}
269
270/// Validate a caller-supplied `app` label at the fleet-hostname
271/// boundary — the ONE substrate primitive owning the two-step (RFC 1123
272/// DNS label + saguão-reservation reject) check every hostname composer
273/// runs on its `app` slot BEFORE stamping it into an emitted FQDN.
274///
275/// Pre-lift the two-step check was hand-authored at TWO adjacent public
276/// FQDN composers in this module past the ★★ PRIME-DIRECTIVE ≥ 2
277/// duplication threshold:
278///
279/// * [`fmt_fqdn`] — the per-instance form; the 4-line prelude
280///   preceded the sibling `validate_label("ephemeral_id", …)` +
281///   cluster / location / domain checks.
282/// * [`fmt_fqdn_stable`] — the unprefixed stable-claim form; the same
283///   4-line prelude preceded the cluster / location / domain checks
284///   with no `ephemeral_id` slot in between.
285///
286/// Both restated the SAME 4-line prelude verbatim: (1)
287/// `validate_label("app", app)?` to enforce the RFC 1123 shape (1–63
288/// chars, lowercase alphanumeric + hyphen, no leading / trailing
289/// hyphen), then (2) an early-return
290/// `HostnameError::ReservedApp(app.to_string())` when the label
291/// appears in the module-private [`RESERVED_APP_LABELS`] set
292/// (currently `"auth"` / `"cracha"` — the saguão control-plane
293/// reservations declared in pleme-io CLAUDE.md § Fleet hostname
294/// pattern).
295///
296/// Post-lift each callsite reads `validate_app(app)?` and the ordered
297/// two-step check lives at ONE substrate owner. The step ORDER is
298/// load-bearing: `validate_label` runs first so a reserved label whose
299/// spelling ALSO violates RFC 1123 (an operator who typed `"AUTH"`
300/// instead of `"auth"`) surfaces as
301/// [`HostnameError::InvalidLabel`] (the underlying shape defect),
302/// not as [`HostnameError::ReservedApp`] (the higher-level policy
303/// gate) — matching the pre-lift order both composers hand-authored.
304/// A regression that swapped the two steps would silently re-classify
305/// every such input and callers pattern-matching on the two variants
306/// would branch differently.
307///
308/// A future extension to the reserved set (adding a third saguão name,
309/// a per-cluster reservation surface, a normalized-form lookup that
310/// treats `"Auth"` and `"auth"` as the same reservation) lands at THIS
311/// ONE substrate primitive and both [`fmt_fqdn`] + [`fmt_fqdn_stable`]
312/// inherit the upgrade mechanically — no per-composer edit at either
313/// call site, no drift risk for a third future FQDN-shape composer
314/// that plugs into the same reservation policy.
315///
316/// Theory anchor: THEORY.md §VI.1 (generation over composition — the
317/// 4-line two-step check recurred at two hand-authored composer
318/// preludes past the ★★ PRIME-DIRECTIVE ≥ 2 duplication trigger and
319/// lifts to ONE substrate owner here). THEORY.md §II.1 invariant 5
320/// (composition preserves proofs — the pin block below binds the
321/// primitive at fail-before-pass-after granularity so a regression
322/// that reorders the two steps, drops one, or drifts the typed error
323/// variant surfaces at THESE pins rather than as silent fleet-
324/// hostname skew across every downstream FQDN emit).
325fn validate_app(app: &str) -> Result<(), HostnameError> {
326    validate_label("app", app)?;
327    if RESERVED_APP_LABELS.contains(&app) {
328        return Err(HostnameError::ReservedApp(app.to_string()));
329    }
330    Ok(())
331}
332
333fn validate_domain(segment: &'static str, domain: &str) -> Result<(), HostnameError> {
334    if domain.is_empty() {
335        return Err(HostnameError::InvalidLabel {
336            segment,
337            label: domain.to_string(),
338            reason: "must not be empty",
339        });
340    }
341    // Multi-label domain — every dot-separated piece must be a valid label.
342    for piece in domain.split('.') {
343        validate_label(segment, piece)?;
344    }
345    Ok(())
346}
347
348fn short_hex_blake3(bytes: &[u8], len: usize) -> String {
349    // Delegate the 2-link `blake3::hash → hex` step to the substrate
350    // primitive so the ephemeral-id prefix stays byte-identical to
351    // every receipt/attestation hex-digest workspace-wide; take a
352    // stable prefix of the shared full-length hex.
353    crate::hash::hex_blake3(bytes).chars().take(len).collect()
354}
355
356#[cfg(test)]
357mod tests {
358    use super::*;
359    use serde::Deserialize;
360
361    #[test]
362    fn fmt_fqdn_per_instance() {
363        let f = fmt_fqdn("api", "demo-prod", "pleme-dev", "use1", "quero.lol").unwrap();
364        assert_eq!(f, "api.demo-prod.pleme-dev.use1.quero.lol");
365    }
366
367    #[test]
368    fn fmt_fqdn_stable_form() {
369        let f = fmt_fqdn_stable("api", "pleme-dev", "use1", "quero.lol").unwrap();
370        assert_eq!(f, "api.pleme-dev.use1.quero.lol");
371    }
372
373    #[test]
374    fn fmt_fqdn_with_multilevel_domain() {
375        let f = fmt_fqdn("api", "env-a", "rio", "us", "internal.example.com").unwrap();
376        assert_eq!(f, "api.env-a.rio.us.internal.example.com");
377    }
378
379    #[test]
380    fn reserved_app_rejected() {
381        let r = fmt_fqdn("auth", "x", "y", "z", "example.com");
382        assert!(matches!(r, Err(HostnameError::ReservedApp(_))));
383        let r = fmt_fqdn_stable("cracha", "y", "z", "example.com");
384        assert!(matches!(r, Err(HostnameError::ReservedApp(_))));
385    }
386
387    #[test]
388    fn empty_label_rejected() {
389        let r = fmt_fqdn("", "x", "y", "z", "example.com");
390        assert!(matches!(
391            r,
392            Err(HostnameError::InvalidLabel { segment: "app", .. })
393        ));
394    }
395
396    #[test]
397    fn too_long_label_rejected() {
398        let long = "a".repeat(64);
399        let r = fmt_fqdn(&long, "x", "y", "z", "example.com");
400        assert!(matches!(r, Err(HostnameError::InvalidLabel { .. })));
401    }
402
403    #[test]
404    fn uppercase_label_rejected() {
405        let r = fmt_fqdn("API", "x", "y", "z", "example.com");
406        assert!(matches!(r, Err(HostnameError::InvalidLabel { .. })));
407    }
408
409    #[test]
410    fn leading_hyphen_label_rejected() {
411        let r = fmt_fqdn("api", "-bad", "y", "z", "example.com");
412        assert!(matches!(r, Err(HostnameError::InvalidLabel { .. })));
413    }
414
415    #[test]
416    fn underscore_label_rejected() {
417        let r = fmt_fqdn("api", "x_y", "z", "w", "example.com");
418        assert!(matches!(r, Err(HostnameError::InvalidLabel { .. })));
419    }
420
421    #[test]
422    fn empty_domain_rejected() {
423        let r = fmt_fqdn("api", "x", "y", "z", "");
424        assert!(matches!(r, Err(HostnameError::InvalidLabel { .. })));
425    }
426
427    // ─── Content-hash derivation ─────────────────────────────────
428
429    #[derive(Serialize, Deserialize)]
430    struct TestSpec {
431        a: u32,
432        b: String,
433    }
434
435    #[test]
436    fn ephemeral_id_is_8_hex_chars() {
437        let spec = TestSpec {
438            a: 1,
439            b: "x".into(),
440        };
441        let id = ephemeral_id_from_spec(&spec).unwrap();
442        assert_eq!(id.len(), EPHEMERAL_ID_HASH_LEN);
443        assert!(id.chars().all(|c| c.is_ascii_hexdigit()));
444    }
445
446    #[test]
447    fn ephemeral_id_is_deterministic() {
448        let s1 = TestSpec {
449            a: 1,
450            b: "x".into(),
451        };
452        let s2 = TestSpec {
453            a: 1,
454            b: "x".into(),
455        };
456        assert_eq!(
457            ephemeral_id_from_spec(&s1).unwrap(),
458            ephemeral_id_from_spec(&s2).unwrap()
459        );
460    }
461
462    #[test]
463    fn ephemeral_id_changes_with_spec() {
464        let s1 = TestSpec {
465            a: 1,
466            b: "x".into(),
467        };
468        let s2 = TestSpec {
469            a: 2,
470            b: "x".into(),
471        };
472        let s3 = TestSpec {
473            a: 1,
474            b: "y".into(),
475        };
476        let id1 = ephemeral_id_from_spec(&s1).unwrap();
477        let id2 = ephemeral_id_from_spec(&s2).unwrap();
478        let id3 = ephemeral_id_from_spec(&s3).unwrap();
479        assert_ne!(id1, id2);
480        assert_ne!(id1, id3);
481        assert_ne!(id2, id3);
482    }
483
484    #[test]
485    fn ephemeral_id_lowercase_valid_dns_label() {
486        // BLAKE3 hex is lowercase by design; the validator must
487        // accept the output as a valid DNS label.
488        let spec = TestSpec {
489            a: 42,
490            b: "anything".into(),
491        };
492        let id = ephemeral_id_from_spec(&spec).unwrap();
493        validate_label("ephemeral_id", &id).unwrap();
494    }
495
496    // ─── resolve_ephemeral_id ────────────────────────────────────
497
498    #[test]
499    fn resolve_named_slot_wins() {
500        let h = RoutingHostname::instanced("api", "demo-prod");
501        assert_eq!(resolve_ephemeral_id(&h, "fallback"), "demo-prod");
502    }
503
504    #[test]
505    fn resolve_empty_named_falls_back() {
506        let h = RoutingHostname {
507            app: "api".into(),
508            instance: Some(String::new()),
509            cluster: None,
510        };
511        assert_eq!(resolve_ephemeral_id(&h, "abc123de"), "abc123de");
512    }
513
514    #[test]
515    fn resolve_unset_named_falls_back() {
516        let h = RoutingHostname::content_hashed("api");
517        assert_eq!(resolve_ephemeral_id(&h, "abc123de"), "abc123de");
518    }
519
520    // ─── End-to-end ──────────────────────────────────────────────
521
522    // ─── HostnameResultExt::hostname_ctx substrate pins ──────────
523    //
524    // Fail-before-pass-after granularity: the `HostnameResultExt::
525    // hostname_ctx` trait method did not exist before this commit,
526    // so each test below fails to compile pre-lift. Post-lift they
527    // collectively pin the display-prefix wrap-shape at ONE substrate
528    // owner — a regression that drifts the separator, swaps the two
529    // slots, wraps the `HostnameError` with a chain-form `source`, or
530    // promotes the pass-through arm to a synthesis (an empty `Ok(())`,
531    // a mutated context slug) surfaces HERE rather than as silent
532    // operator-facing skew across the three pre-lift consumers whose
533    // log output already encoded the flat `"<ctx>: <HostnameError
534    // display>"` shape.
535
536    fn sample_err() -> HostnameError {
537        HostnameError::InvalidLabel {
538            segment: "app",
539            label: "BAD".into(),
540            reason: "must contain only [a-z0-9-]",
541        }
542    }
543
544    #[test]
545    fn hostname_ctx_static_str_context_matches_pre_lift_format_bytewise() {
546        // Byte-shape parity pin: the wrap output of `hostname_ctx
547        // ("<slug>")` MUST be `Display`-identical to the pre-lift
548        // hand-authored `.map_err(|e| anyhow!("<slug>: {e}"))` chain.
549        // A regression that inserted a separator character (`"<slug>::
550        // <hostname>"`), dropped the space after the colon, or swapped
551        // the two slots (`"<hostname>: <slug>"`) surfaces HERE rather
552        // than as silent drift at every downstream log-output consumer.
553        let raw: Result<(), HostnameError> = Err(sample_err());
554        let via_trait = raw.hostname_ctx("fmt_fqdn (per-instance)").unwrap_err();
555        let pre_lift = anyhow::anyhow!("fmt_fqdn (per-instance): {}", sample_err());
556        assert_eq!(
557            format!("{via_trait}"),
558            format!("{pre_lift}"),
559            "hostname_ctx wrap must be Display-identical to pre-lift anyhow! chain"
560        );
561    }
562
563    #[test]
564    fn hostname_ctx_ok_arm_is_a_pure_passthrough() {
565        // Ok-arm invariant: `hostname_ctx` on `Ok(t)` MUST return
566        // `Ok(t)` verbatim — no side-effect on the payload, no
567        // synthesis of a context-tagged error, no allocation. Peer to
568        // the Err-arm byte-shape pin; a regression that promoted the
569        // Ok arm to ALWAYS produce a synthesis Error would silently
570        // break every successful hostname-format call in the pre-lift
571        // consumer set.
572        let raw: Result<&'static str, HostnameError> = Ok("api.demo-prod.pleme-dev.use1.quero.lol");
573        assert_eq!(
574            raw.hostname_ctx("noop").unwrap(),
575            "api.demo-prod.pleme-dev.use1.quero.lol"
576        );
577    }
578
579    #[test]
580    fn hostname_ctx_threads_the_underlying_hostname_error_display_verbatim() {
581        // Display-tail invariant: the wrapped `anyhow::Error`'s
582        // `Display` output MUST contain the `HostnameError`'s own
583        // `Display` output verbatim as the tail past `"<ctx>: "`. A
584        // regression that inserted a normalization (uppercase, JSON
585        // encoding, truncation) between the composed `{e}` slot and
586        // the underlying thiserror-derived Display impl would surface
587        // HERE rather than as silent operator-facing skew across the
588        // three consumers whose grep patterns already encoded the
589        // canonical `HostnameError` variant wordings ("invalid DNS
590        // label ...", "app label ... is reserved").
591        let raw: Result<(), HostnameError> = Err(HostnameError::ReservedApp("auth".into()));
592        let wrapped = raw.hostname_ctx("fmt_fqdn_stable").unwrap_err();
593        let expected_tail = format!("{}", HostnameError::ReservedApp("auth".into()));
594        let expected = format!("fmt_fqdn_stable: {expected_tail}");
595        assert_eq!(format!("{wrapped}"), expected);
596        // Also assert the tail appears verbatim as a suffix — a change
597        // in the thiserror-derived Display for ReservedApp would fail
598        // both this assertion and the RECEIPT_VERSION-in-tail invariant
599        // its docstring pins.
600        assert!(
601            format!("{wrapped}").ends_with(&expected_tail),
602            "wrap must end with the HostnameError Display verbatim"
603        );
604    }
605
606    #[test]
607    fn hostname_ctx_composes_over_ephemeral_id_from_spec_call_shape() {
608        // End-to-end composition pin: the substrate trait method
609        // composes cleanly over the `ephemeral_id_from_spec` return
610        // shape at a real callsite (the `render_routing` R9 seed).
611        // A regression that specialized the trait bound to only one
612        // hostname primitive's Result shape would surface HERE.
613        #[derive(Serialize)]
614        struct NoSuchThingAsAnUnserializableStruct {
615            a: u32,
616        }
617        let v = NoSuchThingAsAnUnserializableStruct { a: 1 };
618        let composed: anyhow::Result<String> =
619            ephemeral_id_from_spec(&v).hostname_ctx("ephemeral_id_from_spec");
620        assert!(composed.is_ok());
621        assert_eq!(composed.unwrap().len(), EPHEMERAL_ID_HASH_LEN);
622    }
623
624    // ─── validate_app substrate pins ─────────────────────────────
625    //
626    // Fail-before-pass-after granularity: the `validate_app` helper
627    // did not exist pre-lift — both [`fmt_fqdn`] and [`fmt_fqdn_stable`]
628    // hand-authored the two-step (RFC 1123 label + reserved-name reject)
629    // check inline. Post-lift the two composers thread the same
630    // primitive, so the pins below pin the primitive's SHAPE + STEP
631    // ORDER + typed-variant surface at the substrate — a regression
632    // that (a) reorders the two steps, (b) drops the reserved-name
633    // gate silently, or (c) promotes the `HostnameError::ReservedApp`
634    // arm to a generic `InvalidLabel` surfaces HERE rather than as
635    // silent skew at every downstream FQDN emit.
636
637    #[test]
638    fn validate_app_accepts_valid_lowercase_alphanumeric_label() {
639        // Happy-path pin: a valid `app` label passes the two-step
640        // check with `Ok(())`. A regression that inverted the return
641        // arm (rejected everything, matched no reserved) surfaces
642        // HERE rather than as every FQDN emit refusing every input.
643        validate_app("api").unwrap();
644        validate_app("gateway").unwrap();
645        validate_app("demo-app").unwrap();
646        validate_app("a").unwrap();
647    }
648
649    #[test]
650    fn validate_app_rejects_empty_label_with_invalid_label_variant() {
651        // Step-1 delegation pin: an empty `app` MUST surface as
652        // `HostnameError::InvalidLabel { segment: "app", .. }` from
653        // the underlying `validate_label("app", app)?` call — NOT as
654        // `ReservedApp` (which would silently reclassify the shape
655        // defect as a policy rejection).
656        assert!(matches!(
657            validate_app(""),
658            Err(HostnameError::InvalidLabel { segment: "app", .. })
659        ));
660    }
661
662    #[test]
663    fn validate_app_rejects_uppercase_label_with_invalid_label_variant() {
664        // Step-1 delegation pin: casing-invalid labels reach through
665        // to `validate_label`'s [a-z0-9-] check. A regression that
666        // short-circuited the reserved-check on a case-insensitive
667        // match ("AUTH" reads as reserved without going through the
668        // RFC 1123 gate first) would surface HERE.
669        assert!(matches!(
670            validate_app("API"),
671            Err(HostnameError::InvalidLabel { segment: "app", .. })
672        ));
673    }
674
675    #[test]
676    fn validate_app_rejects_too_long_label_with_invalid_label_variant() {
677        // Step-1 delegation pin: 64-char labels violate the RFC 1123
678        // upper bound and surface at the `validate_label` gate.
679        let long = "a".repeat(64);
680        assert!(matches!(
681            validate_app(&long),
682            Err(HostnameError::InvalidLabel { segment: "app", .. })
683        ));
684    }
685
686    #[test]
687    fn validate_app_rejects_reserved_auth_label_with_reserved_app_variant() {
688        // Step-2 pin: the currently-reserved `"auth"` slot surfaces
689        // as `HostnameError::ReservedApp("auth")` — the typed
690        // control-plane rejection callers pattern-match on. A
691        // regression that dropped this variant would silently
692        // accept the reservation and let a tenant deploy under the
693        // saguão namespace.
694        assert!(matches!(
695            validate_app("auth"),
696            Err(HostnameError::ReservedApp(ref s)) if s == "auth"
697        ));
698    }
699
700    #[test]
701    fn validate_app_rejects_reserved_cracha_label_with_reserved_app_variant() {
702        // Sibling pin to the `"auth"` reservation — pins the second
703        // currently-reserved label. A regression that dropped one
704        // reservation but not the other would surface HERE.
705        assert!(matches!(
706            validate_app("cracha"),
707            Err(HostnameError::ReservedApp(ref s)) if s == "cracha"
708        ));
709    }
710
711    #[test]
712    fn validate_app_step_order_puts_rfc_1123_check_before_reserved_check() {
713        // Load-bearing order pin: `validate_label` runs FIRST so a
714        // reserved label whose spelling ALSO violates RFC 1123
715        // (uppercase, hyphen at end, etc.) surfaces as
716        // `InvalidLabel` — the underlying SHAPE defect — not as
717        // `ReservedApp` (the higher-level POLICY gate). Callers who
718        // pattern-match on the two variants branch DIFFERENTLY on
719        // shape defects vs policy rejections, so a swap of the two
720        // steps would silently re-route every uppercase-reserved
721        // input into the wrong error arm.
722        assert!(matches!(
723            validate_app("AUTH"),
724            Err(HostnameError::InvalidLabel { segment: "app", .. })
725        ));
726        assert!(matches!(
727            validate_app("Cracha"),
728            Err(HostnameError::InvalidLabel { segment: "app", .. })
729        ));
730    }
731
732    #[test]
733    fn validate_app_matches_pre_lift_two_step_chain_bytewise_across_every_variant_shape() {
734        // Byte-shape parity pin: the substrate primitive's return
735        // MUST equal the pre-lift 4-line hand-authored chain for
736        // every representative input shape. A regression that
737        // drifted the primitive's semantics away from the pre-lift
738        // composer preludes surfaces HERE rather than as silent
739        // skew at either `fmt_fqdn` / `fmt_fqdn_stable` consumer.
740        fn pre_lift(app: &str) -> Result<(), HostnameError> {
741            validate_label("app", app)?;
742            if RESERVED_APP_LABELS.contains(&app) {
743                return Err(HostnameError::ReservedApp(app.to_string()));
744            }
745            Ok(())
746        }
747        for input in [
748            // Happy path.
749            "api",
750            "gateway",
751            "demo-app",
752            "a",
753            // Step-1 rejections.
754            "",
755            "API",
756            "-bad",
757            "bad-",
758            "with_underscore",
759            // Step-2 rejections.
760            "auth",
761            "cracha",
762            // Step-1 wins over step-2 (uppercase reserved).
763            "AUTH",
764            "Cracha",
765        ] {
766            let via_primitive = validate_app(input);
767            let via_pre_lift = pre_lift(input);
768            match (via_primitive, via_pre_lift) {
769                (Ok(()), Ok(())) => {}
770                (Err(a), Err(b)) => assert_eq!(a, b, "variant mismatch for {input:?}"),
771                (a, b) => panic!("arm mismatch for {input:?}: primitive={a:?} pre_lift={b:?}"),
772            }
773        }
774    }
775
776    #[test]
777    fn validate_app_covers_every_currently_reserved_label_at_the_primitive() {
778        // Coherence sweep: iterate the RESERVED_APP_LABELS set and
779        // verify each element rejects at the substrate. A future
780        // addition to the reserved set that forgets to update the
781        // primitive would surface HERE rather than as silent
782        // acceptance at every FQDN emit.
783        for reserved in RESERVED_APP_LABELS {
784            assert!(
785                matches!(validate_app(reserved), Err(HostnameError::ReservedApp(ref s)) if s == reserved),
786                "RESERVED_APP_LABELS entry {reserved:?} must surface as ReservedApp at the substrate"
787            );
788        }
789    }
790
791    #[test]
792    fn fmt_fqdn_routes_app_slot_through_validate_app_primitive() {
793        // Delegation pin: the per-instance composer routes its `app`
794        // slot check through `validate_app`, NOT through a re-open-
795        // coded restatement of the two-step chain. A regression that
796        // inlined the pre-lift check at the composer prelude would
797        // reintroduce the duplication the lift removed; this pin
798        // catches it by asserting the composer surfaces the SAME
799        // typed error the primitive would for a representative
800        // input in each of the two rejection arms.
801        assert!(matches!(
802            fmt_fqdn("AUTH", "x", "y", "z", "example.com"),
803            Err(HostnameError::InvalidLabel { segment: "app", .. })
804        ));
805        assert!(matches!(
806            fmt_fqdn("auth", "x", "y", "z", "example.com"),
807            Err(HostnameError::ReservedApp(ref s)) if s == "auth"
808        ));
809    }
810
811    #[test]
812    fn fmt_fqdn_stable_routes_app_slot_through_validate_app_primitive() {
813        // Sibling delegation pin — same shape as the per-instance
814        // pin above but for the stable-claim composer. Both
815        // composers now share the primitive; a regression that
816        // re-inlined the chain at either site surfaces at ONE of
817        // the two pins rather than at every downstream FQDN emit.
818        assert!(matches!(
819            fmt_fqdn_stable("Cracha", "y", "z", "example.com"),
820            Err(HostnameError::InvalidLabel { segment: "app", .. })
821        ));
822        assert!(matches!(
823            fmt_fqdn_stable("cracha", "y", "z", "example.com"),
824            Err(HostnameError::ReservedApp(ref s)) if s == "cracha"
825        ));
826    }
827
828    #[test]
829    fn end_to_end_named_and_unnamed_for_same_process() {
830        let spec = TestSpec {
831            a: 1,
832            b: "x".into(),
833        };
834        let hash = ephemeral_id_from_spec(&spec).unwrap();
835
836        let h_named = RoutingHostname::instanced("api", "demo-prod");
837        let h_anon = RoutingHostname::content_hashed("gateway");
838
839        let id_named = resolve_ephemeral_id(&h_named, &hash);
840        let id_anon = resolve_ephemeral_id(&h_anon, &hash);
841
842        let fqdn_named =
843            fmt_fqdn(&h_named.app, id_named, "pleme-dev", "use1", "quero.lol").unwrap();
844        let fqdn_anon = fmt_fqdn(&h_anon.app, id_anon, "pleme-dev", "use1", "quero.lol").unwrap();
845
846        assert_eq!(fqdn_named, "api.demo-prod.pleme-dev.use1.quero.lol");
847        assert!(fqdn_anon.starts_with("gateway."));
848        assert!(fqdn_anon.ends_with(".pleme-dev.use1.quero.lol"));
849        // 5 named segments (app + eph_id + cluster + location + domain),
850        // but `domain` itself splits as `quero.lol` ⇒ 6 dot-delimited
851        // pieces. The shape, not the count, is the invariant.
852        assert_eq!(fqdn_anon.matches('.').count(), 5);
853    }
854}