Skip to main content

tatara_process/
encapsulates.rs

1//! `EncapsulatesSpec` — how a Process relates to pre-existing
2//! in-cluster state.
3//!
4//! The substrate move: every long-running workload on a pleme-io
5//! cluster — raw HelmReleases, Flux Kustomizations, bare Deployments
6//! — becomes a Process without disruption. Three modes:
7//!
8//! * **Manage** (default) — Process IS the control loop. New
9//!   HR/Kustomization emitted by the reconciler use ownerRefs
10//!   pointing at the Process; cascade-delete on Reaped.
11//!
12//! * **Adopt** — Take over an existing HR/Kustomization in place.
13//!   Reconciler emits a new HR with `releaseName` matching the
14//!   running release; helm-controller adopts the existing release
15//!   under new management. **No pod restart**; no values diff
16//!   unless operator changes them. The original raw HR can be
17//!   deleted from git after the takeover confirms.
18//!
19//! * **Observe** — Read-only awareness. Process watches the existing
20//!   state for postcondition pillars + emits routing/exports/
21//!   attestation, but does NOT modify or own the underlying
22//!   HR/Kustomization. Useful for adding DNS + observability to
23//!   legacy stacks without taking over.
24//!
25//! These compose progressively: Observe an HR first to confirm
26//! shape, promote to Adopt for zero-downtime takeover, then to
27//! Manage once the Process drives values.
28//!
29//! Lisp authoring:
30//! ```lisp
31//! :encapsulates (:kind (:existing-helm-release
32//!                       :namespace    "demo-ns"
33//!                       :name         "demo-app"
34//!                       :release-name "demo-app-consolidated")
35//!                :mode Adopt)
36//! ```
37
38use schemars::JsonSchema;
39use serde::{Deserialize, Serialize};
40use std::collections::BTreeMap;
41use tatara_lisp::DeriveTataraDomain;
42
43/// How a Process wraps pre-existing in-cluster state.
44///
45/// Optional on `ProcessSpec` — None means the Process is greenfield
46/// (Manage mode applied to nothing pre-existing). The render phase
47/// branches on `kind` to decide whether to emit fresh resources or
48/// reference/adopt running ones.
49#[derive(DeriveTataraDomain, Clone, Debug, Serialize, Deserialize, JsonSchema)]
50#[serde(rename_all = "camelCase")]
51#[tatara(keyword = "defencapsulates")]
52pub struct EncapsulatesSpec {
53    /// What kind of pre-existing state.
54    pub kind: EncapsulationKind,
55
56    /// Reconciler's relationship to that state. Defaults to `Manage`
57    /// (the operational default when `encapsulates` is set without
58    /// an explicit mode).
59    #[serde(default)]
60    pub mode: EncapsulationMode,
61}
62
63impl EncapsulatesSpec {
64    /// Closed-set-driven presence probe — does this [`EncapsulatesSpec`]
65    /// carry the given [`EncapsulationMode`] discriminator on its
66    /// [`Self::mode`] slot? The ONE substrate primitive that owns the
67    /// `(EncapsulatesSpec, EncapsulationMode) -> bool` scalar-carrier walk
68    /// shape.
69    ///
70    /// # Second scalar-carrier peer on the presence-probe axis
71    ///
72    /// Peer of [`crate::spec::SignalPolicy::has_sighup_strategy`] — both
73    /// probe a scalar closed-set-discriminator field on an inner
74    /// [`crate::crd::ProcessSpec`] struct via a one-line
75    /// `self.<field> == kind` body. Together they close the SCALAR-CARRIER
76    /// stratum of the workspace-wide closed-set-driven presence-probe
77    /// algebra (the workspace-wide algebra spans three underlying
78    /// representation kinds — Option-slot, slice, scalar — see the
79    /// [`crate::spec::SignalPolicy::has_sighup_strategy`] docstring for
80    /// the full-shape rundown; this method is the second scalar-carrier
81    /// instance).
82    ///
83    /// # Semantics — VARIANT match, not POPULATED slot
84    ///
85    /// `has_mode(kind)` returns `true` iff `self.mode == kind`. On an
86    /// [`EncapsulatesSpec`] whose `mode` slot is the substrate default
87    /// ([`EncapsulationMode::default`] = [`EncapsulationMode::Manage`])
88    /// the probe returns `true` for [`EncapsulationMode::Manage`] and
89    /// `false` for every other variant — legitimate operator signal
90    /// symmetric to [`crate::signal::SighupStrategy::default`]. An
91    /// operator who opted `encapsulates` in (populating the parent
92    /// `Option<EncapsulatesSpec>`) but left `:mode` unset IS configured
93    /// for `Manage`, and a `:requires (encapsulation-mode-Manage)` check
94    /// should pass on that spec.
95    ///
96    /// # Interaction with the parent `Option` gate
97    ///
98    /// The parent `Process.spec.encapsulates` field is an
99    /// `Option<EncapsulatesSpec>` (greenfield Processes carry `None`);
100    /// downstream consumers gate this probe on the parent presence via
101    /// `spec.encapsulates.as_ref().is_some_and(|e| e.has_mode(kind))`.
102    /// A permanent Process with `encapsulates: None` returns `false` for
103    /// every kind — including the default `Manage` variant — because
104    /// the operator DECLINED the encapsulation surface entirely rather
105    /// than defaulting into it. Symmetric to the
106    /// `resolved_ephemeral().is_some_and(…)` gate the
107    /// `export-when-<kind>` / `channel-<kind>` / `report-format-<kind>` /
108    /// `artifact-<kind>` slice-level probes compose on the ephemeral
109    /// axis.
110    ///
111    /// # Compounding
112    ///
113    /// A future closed-set-discriminator scalar field on `EncapsulatesSpec`
114    /// (a hypothetical `SubmodeKind` selecting a sub-strategy inside the
115    /// Adopt/Manage modes; a `HandoffPhase` scalar selecting when the
116    /// reconciler swaps ownership) lands as ONE peer inherent method
117    /// with the same one-line `self.<field> == kind` body and routes
118    /// through the same `strip_and_classify_prefixed_kind::<K, _>` shape
119    /// in `tatara-check`. A future
120    /// [`EncapsulationMode`] variant (a hypothetical `Observe` submode,
121    /// a `Migrate` for scripted mode transitions) reaches every
122    /// downstream through ONE `ALL` entry on the closed set with the
123    /// probe body untouched.
124    ///
125    /// Theory anchor: THEORY.md §II.1 invariant 5 — composition preserves
126    /// proofs; the scalar-carrier presence-probe body lives at ONE
127    /// substrate site so every downstream (`encapsulation-mode-<kind>`
128    /// require-tag family in `tatara-check`, closed-set audit
129    /// dispatchers, future variant additions on [`EncapsulationMode`])
130    /// binds through the SAME shape rather than restating the
131    /// `encapsulates.mode == kind` closure body at each callsite.
132    /// THEORY.md §VI.1 — generation over composition; a future
133    /// [`EncapsulationMode`] variant lands at ONE `ALL` entry + ONE
134    /// `as_str` arm on the closed set and the probe picks it up
135    /// mechanically without further per-consumer edits.
136    #[must_use]
137    pub fn has_mode(&self, kind: EncapsulationMode) -> bool {
138        self.mode == kind
139    }
140}
141
142/// Three concrete kinds the substrate knows how to wrap. Exactly-
143/// one-Option pattern matching `Intent` / `Lifetime` — additive on
144/// the wire, every variant typed.
145#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
146#[serde(rename_all = "camelCase")]
147pub struct EncapsulationKind {
148    /// An existing FluxCD HelmRelease. The reconciler emits a new HR
149    /// with the SAME `release_name` — helm-controller finds + adopts
150    /// the in-cluster release without recreating Pods.
151    #[serde(default, skip_serializing_if = "Option::is_none")]
152    pub existing_helm_release: Option<ExistingHelmRelease>,
153
154    /// An existing FluxCD Kustomization. The reconciler stops emitting
155    /// its own and instead references the existing one.
156    #[serde(default, skip_serializing_if = "Option::is_none")]
157    pub existing_kustomization: Option<ExistingKustomization>,
158
159    /// Pre-existing in-cluster workload (Deployment/StatefulSet/etc)
160    /// not Flux-managed. The reconciler adds ownerRefs + emits
161    /// routing only. The workload stays where it is.
162    #[serde(default, skip_serializing_if = "Option::is_none")]
163    pub bare_workload: Option<BareWorkload>,
164}
165
166/// Resolved enum view used by the render phase.
167#[derive(Clone, Debug)]
168pub enum EncapsulationKindVariant<'a> {
169    ExistingHelmRelease(&'a ExistingHelmRelease),
170    ExistingKustomization(&'a ExistingKustomization),
171    BareWorkload(&'a BareWorkload),
172}
173
174impl EncapsulationKindVariant<'_> {
175    /// Reverse projection — every borrowed variant knows its
176    /// [`EncapsulationTarget`] discriminator. Pairs with
177    /// [`EncapsulationTarget::select`] so
178    /// `EncapsulationTarget::select(kind).map(|v| v.target())`
179    /// round-trips the closed set on the populated side; pinned by
180    /// `encapsulation_target_round_trips_through_variant_target`.
181    /// Future target-keyed consumers (metric labels like
182    /// `tatara_encapsulations_total{target="existingHelmRelease"}`,
183    /// status reason strings, audit-trail classifiers, LSP completion
184    /// lists) reach through this projection instead of pattern-matching
185    /// the payload-carrying view.
186    pub fn target(&self) -> EncapsulationTarget {
187        match self {
188            Self::ExistingHelmRelease(_) => EncapsulationTarget::ExistingHelmRelease,
189            Self::ExistingKustomization(_) => EncapsulationTarget::ExistingKustomization,
190            Self::BareWorkload(_) => EncapsulationTarget::BareWorkload,
191        }
192    }
193}
194
195/// `EncapsulationKindVariant`'s [`crate::tagged_union::VariantKind`] impl
196/// delegates to the inherent [`Self::target`] — the substrate trait names
197/// the reverse projection uniformly across every borrowed-view enum on
198/// `ProcessSpec`'s tagged-union axis, while the inherent method's
199/// domain-specific name (`.target()`, matching `EncapsulationTarget`) stays
200/// load-bearing at every consumer site. The one-line delegation IS the
201/// only per-site restatement; the ground-truth arm-to-Kind mapping lives
202/// at the inherent method above.
203impl crate::tagged_union::VariantKind<EncapsulationTarget> for EncapsulationKindVariant<'_> {
204    fn variant_kind(&self) -> EncapsulationTarget {
205        self.target()
206    }
207}
208
209/// Closed-set discriminator over `EncapsulationKind`'s three tagged-union
210/// slots. Single source of truth that drives `EncapsulationKind::variant`'s
211/// ambiguity + emptiness resolver, the `EncapsulationKindError::Empty`
212/// diagnostic message, and the reverse `EncapsulationKindVariant::target`
213/// projection. Adding a fourth encapsulation target (e.g., a future
214/// `ExistingNamespace`, `ExistingDaemonSet`, or `ExistingService`) lands
215/// at one `ALL` entry + one `as_str` arm + one `select` arm + one
216/// `EncapsulationKindVariant::target` arm — exhaustively checked by the
217/// compiler.
218///
219/// The (open authoring surface, closed typed discriminator) split mirrors
220/// every other multi-Option tagged union on this `ProcessSpec` axis:
221/// [`crate::intent::IntentKind`] discriminates [`crate::intent::Intent`];
222/// [`crate::lifetime::LifetimeKind`] discriminates
223/// [`crate::lifetime::Lifetime`];
224/// [`crate::export::ArtifactKind`] discriminates
225/// [`crate::export::ArtifactSource`];
226/// [`crate::export::ChannelKind`] discriminates
227/// [`crate::export::VectorChannel`]. The carrier here is named
228/// `EncapsulationKind` (not `Encapsulation`) because it predates the
229/// closed-set lift convention; `EncapsulationTarget` is the typed
230/// discriminator the rest of the typescape projects through, named for
231/// the semantic role each variant plays (a target of encapsulation).
232#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, tatara_closed_set::DeriveClosedSet)]
233#[closed_set(via = "as_str", generate_unknown, display)]
234pub enum EncapsulationTarget {
235    ExistingHelmRelease,
236    ExistingKustomization,
237    BareWorkload,
238}
239
240impl EncapsulationTarget {
241    /// The closed set of encapsulation targets — single source of truth
242    /// that drives `EncapsulationKind::variant`'s sweep so a variant
243    /// added without an `ALL` entry never reaches the resolver. The
244    /// `[Self; 3]` array literal forces the arity at compile time.
245    pub const ALL: [Self; 3] = [
246        Self::ExistingHelmRelease,
247        Self::ExistingKustomization,
248        Self::BareWorkload,
249    ];
250
251    /// Canonical camelCase wire-format key — matches the serde
252    /// `rename_all = "camelCase"` field name on the corresponding
253    /// `Option<…>` slot of `EncapsulationKind`. The
254    /// `EncapsulationKindError::Empty` diagnostic composes the
255    /// human-readable list from this projection so a new variant lands
256    /// in the operator-facing diagnostic automatically via the `ALL`
257    /// sweep, not via hand-maintained error-string drift. Pinned by
258    /// `encapsulation_target_as_str_matches_field_name`.
259    pub const fn as_str(self) -> &'static str {
260        match self {
261            Self::ExistingHelmRelease => "existingHelmRelease",
262            Self::ExistingKustomization => "existingKustomization",
263            Self::BareWorkload => "bareWorkload",
264        }
265    }
266
267    /// Project an `EncapsulationKind` borrow into the optional typed
268    /// variant view for this target. Returns `None` iff the matching
269    /// slot is `None`. Composes the closed-set sweep
270    /// `EncapsulationKind::variant` loops over. Mirrors
271    /// [`crate::intent::IntentKind::select`],
272    /// [`crate::lifetime::LifetimeKind::select`],
273    /// [`crate::export::ArtifactKind::select`], and
274    /// [`crate::export::ChannelKind::select`].
275    pub fn select<'a>(self, kind: &'a EncapsulationKind) -> Option<EncapsulationKindVariant<'a>> {
276        match self {
277            Self::ExistingHelmRelease => kind
278                .existing_helm_release
279                .as_ref()
280                .map(EncapsulationKindVariant::ExistingHelmRelease),
281            Self::ExistingKustomization => kind
282                .existing_kustomization
283                .as_ref()
284                .map(EncapsulationKindVariant::ExistingKustomization),
285            Self::BareWorkload => kind
286                .bare_workload
287                .as_ref()
288                .map(EncapsulationKindVariant::BareWorkload),
289        }
290    }
291}
292
293// `impl FromStr for EncapsulationTarget` + `impl tatara_lisp::ClosedSet for
294// EncapsulationTarget` + `impl std::fmt::Display for EncapsulationTarget` are
295// generated by `#[derive(tatara_closed_set::DeriveClosedSet)]` on the enum
296// declaration above. `label` delegates to the inherent
297// `EncapsulationTarget::as_str` via `#[closed_set(via = "as_str")]` so the
298// camelCase wire-format projection stays load-bearing (matches the serde
299// `rename_all = "camelCase"` field names on `EncapsulationKind` AND the
300// `ENCAPSULATION_TARGET_LIST` slash-joined operator diagnostic verbatim)
301// while generic `T: ClosedSet` consumers reach the STABLE workspace-wide
302// name (`label`). The `display` flag emits the `f.write_str(self.as_str())`
303// delegation block at the same proc-macro site rather than a hand-rolled
304// `fmt::Display` block per implementor.
305
306// `pub struct UnknownEncapsulationTarget(pub String)` is generated by
307// `#[derive(tatara_closed_set::DeriveClosedSet)]` + `#[closed_set(generate_unknown)]`
308// on the enum declaration above. The auto-derived label `"encapsulation target"`
309// matches the prior hand-rolled `#[error("unknown encapsulation target: {0}")]`
310// verbatim — pinned generically by clause (5) of
311// `tatara_closed_set::assert_closed_set_well_formed::<EncapsulationTarget>()` (called
312// from `encapsulation_target_is_well_formed_closed_set` in the test module).
313// Symmetric to [`UnknownEncapsulationMode`], [`crate::export::UnknownArtifactKind`],
314// [`crate::export::UnknownChannelKind`], and
315// [`crate::lifetime::UnknownTeardownPolicy`].
316
317crate::declare_tagged_union_error! {
318    pub EncapsulationKindError,
319    empty = "encapsulation kind has no variant set (one of {0} required)",
320    ambiguous = "encapsulation kind has multiple variants set; exactly one required",
321}
322
323/// Slash-joined list of every `EncapsulationTarget::as_str()` — composed
324/// once at compile time so `EncapsulationKindError::Empty`'s diagnostic
325/// carries the closed-set summary without per-variant string drift.
326/// Mirrors [`crate::intent::INTENT_KIND_LIST`] /
327/// [`crate::export::ARTIFACT_KIND_LIST`] in shape; pinned by
328/// `encapsulation_kind_error_empty_lists_every_target_in_canonical_order`.
329pub(crate) const ENCAPSULATION_TARGET_LIST: &str =
330    "existingHelmRelease/existingKustomization/bareWorkload";
331
332crate::declare_tagged_union_impls! {
333    parent = EncapsulationKind,
334    kind = EncapsulationTarget,
335    variant = EncapsulationKindVariant,
336    error = EncapsulationKindError,
337    kind_list = ENCAPSULATION_TARGET_LIST,
338}
339
340/// Pointer to an existing FluxCD HelmRelease the Process wraps.
341#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
342#[serde(rename_all = "camelCase")]
343pub struct ExistingHelmRelease {
344    /// Namespace of the HelmRelease CR.
345    pub namespace: String,
346    /// Name of the HelmRelease CR.
347    pub name: String,
348    /// The `spec.releaseName` Helm used for the actual chart install.
349    /// For Adopt mode, the reconciler's emitted HR matches this so
350    /// helm-controller adopts in-place. Required because the HR's
351    /// `metadata.name` and `spec.releaseName` aren't always equal.
352    pub release_name: String,
353}
354
355/// Pointer to an existing FluxCD Kustomization the Process wraps.
356#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
357#[serde(rename_all = "camelCase")]
358pub struct ExistingKustomization {
359    pub namespace: String,
360    pub name: String,
361}
362
363/// Pointer to a bare in-cluster workload (not Flux-managed). The
364/// reconciler identifies the underlying Pods by `selector` and adds
365/// ownerRefs / routing without emitting a new HR/Kustomization.
366#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
367#[serde(rename_all = "camelCase")]
368pub struct BareWorkload {
369    /// Namespace the workload lives in.
370    pub namespace: String,
371    /// Label selector. Must match a single Deployment/StatefulSet/
372    /// DaemonSet; multiple matches are a config error.
373    pub selector: BTreeMap<String, String>,
374}
375
376/// Three modes the reconciler dispatches on at render time.
377#[derive(
378    Clone,
379    Copy,
380    Debug,
381    Default,
382    Serialize,
383    Deserialize,
384    JsonSchema,
385    PartialEq,
386    Eq,
387    Hash,
388    tatara_closed_set::DeriveClosedSet,
389)]
390#[serde(rename_all = "PascalCase")]
391#[closed_set(via = "as_str", generate_unknown, display)]
392pub enum EncapsulationMode {
393    /// **Default** — Process IS the control loop for whatever is
394    /// inside. Emitted HR/Kustomization carry the Process's
395    /// ownerRefs; cascade-delete on Reaped.
396    #[default]
397    Manage,
398
399    /// **Adopt** — Take over the existing release/kustomization in
400    /// place. New HR emitted matches the existing `releaseName`;
401    /// pods don't restart. Used during migration from raw HR → Process.
402    Adopt,
403
404    /// **Observe** — Read-only. Emit routing/exports/attestation but
405    /// don't modify the underlying HR/Kustomization. Used to add
406    /// DNS + observability to legacy stacks without taking over.
407    Observe,
408}
409
410impl EncapsulationMode {
411    /// The closed set of encapsulation modes — single source of truth
412    /// that drives the `as_str` / Display / `FromStr` triad and the
413    /// typed `emits_workload` / `preserves_release_name` dispatch.
414    /// Adding a fourth variant lands at one `ALL` entry + one `as_str`
415    /// arm + one arm in each of the two boolean projections —
416    /// exhaustively checked by the compiler (the `[Self; 3]` array
417    /// literal forces the arity).
418    ///
419    /// Sibling closed-set lifts on the same `ProcessSpec` axis:
420    /// [`crate::export::ExportTrigger::ALL`],
421    /// [`crate::lifetime::TeardownPolicy::ALL`],
422    /// [`crate::intent::IntentKind::ALL`],
423    /// [`crate::lifetime::LifetimeKind::ALL`],
424    /// [`crate::boundary::ConditionKind::ALL`],
425    /// [`crate::phase::ProcessPhase::ALL`],
426    /// [`crate::signal::ProcessSignal::ALL`].
427    pub const ALL: [Self; 3] = [Self::Manage, Self::Adopt, Self::Observe];
428
429    /// Canonical PascalCase wire-format projection — matches the serde
430    /// `rename_all = "PascalCase"` output verbatim. Used by Display
431    /// (single source of truth), by `FromStr` to identify the variant
432    /// from its annotation / status-field representation, and by
433    /// operator-facing reason strings without reaching for `{:?}` Debug
434    /// formatting. Pinned by `mode_as_str_matches_serde`.
435    pub const fn as_str(self) -> &'static str {
436        match self {
437            Self::Manage => "Manage",
438            Self::Adopt => "Adopt",
439            Self::Observe => "Observe",
440        }
441    }
442
443    /// True iff the reconciler should emit (or re-emit) the
444    /// underlying HR/Kustomization at render time.
445    /// Observe ⇒ false; Manage/Adopt ⇒ true.
446    ///
447    /// Closed-set match (not `matches!`) so adding a fourth variant
448    /// triggers the compiler's exhaustiveness check at this site
449    /// rather than silently defaulting to `false`. ONE typed dispatch
450    /// over the closed set that replaces the
451    /// `mode == EncapsulationMode::Observe` hand-rolled equality at
452    /// the reconciler's render entry — the truth table for "should
453    /// this mode emit a workload?" is now owned by the typed surface,
454    /// not by a pattern fragment two crates have to keep coherent.
455    pub const fn emits_workload(self) -> bool {
456        match self {
457            Self::Manage | Self::Adopt => true,
458            Self::Observe => false,
459        }
460    }
461
462    /// True iff the reconciler should preserve the existing release
463    /// name (so helm-controller adopts in-place). Only Adopt.
464    ///
465    /// Closed-set match (not `matches!`) so adding a fourth variant
466    /// triggers the compiler's exhaustiveness check at this site.
467    /// ONE typed dispatch that replaces the
468    /// `mode == EncapsulationMode::Adopt` hand-rolled equality at the
469    /// reconciler's `render_aplicacao` adoption-annotation branch.
470    pub const fn preserves_release_name(self) -> bool {
471        match self {
472            Self::Adopt => true,
473            Self::Manage | Self::Observe => false,
474        }
475    }
476}
477
478// `impl FromStr for EncapsulationMode` + `impl tatara_lisp::ClosedSet for
479// EncapsulationMode` + `impl std::fmt::Display for EncapsulationMode` are
480// generated by `#[derive(tatara_closed_set::DeriveClosedSet)]` on the enum
481// declaration above. `label` delegates to the inherent
482// `EncapsulationMode::as_str` via `#[closed_set(via = "as_str")]` so the
483// PascalCase wire-format projection stays load-bearing (matches the serde
484// `rename_all = "PascalCase"` external-tag form on the wire AND the
485// reconciler's `mode: {Manage,Adopt,Observe}` status-condition reason
486// strings verbatim) while generic `T: ClosedSet` consumers reach the
487// STABLE workspace-wide name (`label`). The `display` flag emits the
488// `f.write_str(self.as_str())` delegation block at the same proc-macro
489// site rather than a hand-rolled `fmt::Display` block per implementor.
490
491// `pub struct UnknownEncapsulationMode(pub String)` is generated by
492// `#[derive(tatara_closed_set::DeriveClosedSet)]` + `#[closed_set(generate_unknown)]`
493// on the enum declaration above. The auto-derived label `"encapsulation mode"`
494// matches the prior hand-rolled `#[error("unknown encapsulation mode: {0}")]`
495// verbatim — pinned generically by clause (5) of
496// `tatara_closed_set::assert_closed_set_well_formed::<EncapsulationMode>()` (called
497// from `mode_is_well_formed_closed_set` in the test module).
498// Symmetric to [`UnknownEncapsulationTarget`], [`crate::export::UnknownExportTrigger`],
499// [`crate::lifetime::UnknownTeardownPolicy`],
500// [`crate::boundary::UnknownConditionKind`], and
501// [`crate::phase::UnknownPhase`].
502
503#[cfg(test)]
504mod tests {
505    use super::*;
506
507    fn demo_adopt() -> EncapsulatesSpec {
508        EncapsulatesSpec {
509            kind: EncapsulationKind {
510                existing_helm_release: Some(ExistingHelmRelease {
511                    namespace: "demo-ns".into(),
512                    name: "demo-app".into(),
513                    release_name: "demo-app-consolidated".into(),
514                }),
515                ..EncapsulationKind::default()
516            },
517            mode: EncapsulationMode::Adopt,
518        }
519    }
520
521    #[test]
522    fn kind_empty_errors() {
523        let k = EncapsulationKind::default();
524        assert_eq!(
525            k.variant().unwrap_err(),
526            EncapsulationKindError::Empty(ENCAPSULATION_TARGET_LIST)
527        );
528    }
529
530    #[test]
531    fn kind_existing_hr_resolves() {
532        let s = demo_adopt();
533        match s.kind.variant().unwrap() {
534            EncapsulationKindVariant::ExistingHelmRelease(h) => {
535                assert_eq!(h.namespace, "demo-ns");
536                assert_eq!(h.release_name, "demo-app-consolidated");
537            }
538            other => panic!("expected ExistingHelmRelease, got {other:?}"),
539        }
540    }
541
542    #[test]
543    fn kind_two_variants_ambiguous() {
544        let k = EncapsulationKind {
545            existing_helm_release: Some(ExistingHelmRelease {
546                namespace: "ns".into(),
547                name: "n".into(),
548                release_name: "r".into(),
549            }),
550            existing_kustomization: Some(ExistingKustomization {
551                namespace: "ns".into(),
552                name: "n".into(),
553            }),
554            ..EncapsulationKind::default()
555        };
556        assert_eq!(k.variant().unwrap_err(), EncapsulationKindError::Ambiguous);
557    }
558
559    #[test]
560    fn mode_dispatch() {
561        assert!(EncapsulationMode::Manage.emits_workload());
562        assert!(EncapsulationMode::Adopt.emits_workload());
563        assert!(!EncapsulationMode::Observe.emits_workload());
564
565        assert!(!EncapsulationMode::Manage.preserves_release_name());
566        assert!(EncapsulationMode::Adopt.preserves_release_name());
567        assert!(!EncapsulationMode::Observe.preserves_release_name());
568    }
569
570    #[test]
571    fn mode_default_is_manage() {
572        assert_eq!(EncapsulationMode::default(), EncapsulationMode::Manage);
573    }
574
575    // ── scalar-carrier presence probe on EncapsulatesSpec × EncapsulationMode ─
576    //
577    // Fail-before-pass-after granularity: [`EncapsulatesSpec::has_mode`]
578    // did not exist before this commit — every consumer of the
579    // `(EncapsulatesSpec, EncapsulationMode) -> bool` scalar-carrier
580    // probe shape restated the `encapsulates.mode == kind` closure body
581    // at its own callsite. Post-lift the shape lives at ONE substrate
582    // owner and every downstream (the `encapsulation-mode-<kind>`
583    // require-tag family in `tatara-check`, future audit dispatchers
584    // walking [`EncapsulationMode::ALL`], any future CRD-facing closed-
585    // set discriminator on a scalar `EncapsulatesSpec` field) binds
586    // through the SAME `has(kind)` shape the Option-slot (Intent::has,
587    // Lifetime::has), slice-level (ConditionSliceExt::has_kind,
588    // DependsOnSliceExt::has_must_reach, ComplianceBindingSliceExt::
589    // has_verification_phase, ExportSpecSliceExt::{has_when,
590    // has_channel_kind, has_report_format, has_artifact_kind}) and
591    // sister scalar-carrier ([`crate::spec::SignalPolicy::has_sighup_strategy`])
592    // primitives publish.
593
594    /// DIAGONAL — for every [`EncapsulationMode`] variant, an
595    /// [`EncapsulatesSpec`] whose `mode` field is set to that variant
596    /// returns `true` from `has_mode` on that same variant AND `false`
597    /// on every other variant. Sweep the [`EncapsulationMode::ALL`] ×
598    /// ALL cross so a regression that hard-coded the arm to a single
599    /// variant (silently returning `true` on every populated spec
600    /// regardless of query kind) or wired the equality to a fixed
601    /// unrelated field (a stray probe on `kind` — the tagged-union
602    /// carrier — instead of `mode`) fails HERE at the substrate
603    /// primitive before landing at the operator-facing checks.lisp
604    /// surface.
605    #[test]
606    fn encapsulates_spec_has_mode_returns_true_iff_variant_matches() {
607        for populated in EncapsulationMode::ALL {
608            let spec = EncapsulatesSpec {
609                kind: EncapsulationKind::default(),
610                mode: populated,
611            };
612            for query in EncapsulationMode::ALL {
613                assert_eq!(
614                    spec.has_mode(query),
615                    query == populated,
616                    "mode={populated:?}: query {query:?} classification drifted",
617                );
618            }
619        }
620    }
621
622    /// DEFAULT — an [`EncapsulatesSpec`] constructed with the
623    /// [`EncapsulationMode::default`] variant carries
624    /// `mode: EncapsulationMode::Manage`, so the scalar-carrier probe
625    /// returns `true` on [`EncapsulationMode::Manage`] and `false` on
626    /// every other variant. Symmetric to the
627    /// [`crate::spec::SignalPolicy::has_sighup_strategy`] default pin —
628    /// both scalar-carrier peers publish the SAME "default is a
629    /// legitimate operator answer" contract at the substrate boundary,
630    /// distinct from the Option-slot axis where a default carrier
631    /// returns `false` for EVERY kind.
632    #[test]
633    fn encapsulates_spec_has_mode_default_probes_manage_only() {
634        let spec = EncapsulatesSpec {
635            kind: EncapsulationKind::default(),
636            mode: EncapsulationMode::default(),
637        };
638        for kind in EncapsulationMode::ALL {
639            let expected = kind == EncapsulationMode::Manage;
640            assert_eq!(
641                spec.has_mode(kind),
642                expected,
643                "default spec (mode=Manage) must return {expected} for {kind:?}",
644            );
645        }
646    }
647
648    // ── closed-set algebra for EncapsulationMode (ALL × as_str ×
649    //    Display × FromStr × emits_workload × preserves_release_name) ─
650
651    /// Structural well-formedness of [`EncapsulationMode`] as a
652    /// [`tatara_lisp::ClosedSet`] implementor — the workspace-wide
653    /// testkit lift that pins all three structural invariants (`ALL`
654    /// is non-empty, every variant round-trips through
655    /// `label ↔ parse_label`, labels are pairwise distinct, `""` is
656    /// outside the closed set) at ONE call site. Replaces the hand-
657    /// derived `mode_all_is_unique_and_complete` +
658    /// `mode_roundtrip_via_as_str` + the empty-input arm of
659    /// `unknown_encapsulation_mode_errors`. `FromStr` delegates to
660    /// `<Self as tatara_closed_set::ClosedSet>::parse_label`, so this helper
661    /// exercises the same code path the reconciler hits when parsing a
662    /// CRD `enum:`-validated `mode` value back to the typed mode.
663    #[test]
664    fn mode_is_well_formed_closed_set() {
665        tatara_closed_set::assert_closed_set_well_formed::<EncapsulationMode>();
666    }
667
668    /// CANONICAL-KEY CONTRACT: `as_str` matches serde's PascalCase
669    /// output verbatim for every variant. A future variant rename
670    /// (or an `as_str` arm typo) lands here at one site, instead of
671    /// drifting between the typed surface and the YAML wire format
672    /// the reconciler / operator both read.
673    #[test]
674    fn mode_as_str_matches_serde() {
675        crate::tagged_union::assert_label_matches_serde_serialization::<EncapsulationMode>();
676    }
677
678    /// The Display impl IS `as_str` — pinning this lets future callers
679    /// reach for either projection without drift. If a reviewer
680    /// accidentally re-introduces an inline match in Display, this
681    /// test would fail the moment a variant rename touches one site
682    /// but not the other.
683    #[test]
684    fn mode_display_matches_as_str() {
685        crate::tagged_union::assert_display_matches_label::<EncapsulationMode>();
686    }
687
688    /// `FromStr` rejects strings that aren't in the canonical
689    /// projection — lowercased / typo / unrelated — and the error
690    /// echoes the input verbatim so the operator-facing diagnostic
691    /// carries the offending value, not a normalized form. The
692    /// empty-input arm is pinned by
693    /// [`mode_is_well_formed_closed_set`] via the
694    /// `tatara_lisp::ClosedSet` testkit; the cases here pin the
695    /// verbatim-echo contract on the [`UnknownEncapsulationMode`]
696    /// newtype, which the trait's `make_unknown` can't see.
697    #[test]
698    fn unknown_encapsulation_mode_errors() {
699        use std::str::FromStr;
700        for bad in ["manage", "ADOPT", "Observed", "Wrap"] {
701            let err = EncapsulationMode::from_str(bad).unwrap_err();
702            assert_eq!(err.0, bad, "error payload should echo input verbatim");
703        }
704    }
705
706    /// TRUTH-TABLE CONTRACT: `emits_workload` / `preserves_release_name`
707    /// agree with the documented (mode) -> (bool, bool) table for every
708    /// variant. A new variant in `EncapsulationMode` without extending
709    /// either projection's match is caught by the compiler (closed-set
710    /// match in each method); adding a variant without extending its
711    /// truth row is caught here.
712    #[test]
713    fn mode_projection_truth_table() {
714        let table: &[(EncapsulationMode, bool, bool)] = &[
715            // (mode, emits_workload, preserves_release_name)
716            (EncapsulationMode::Manage, true, false),
717            (EncapsulationMode::Adopt, true, true),
718            (EncapsulationMode::Observe, false, false),
719        ];
720        assert_eq!(table.len(), EncapsulationMode::ALL.len());
721        for (mode, emits, preserves) in table {
722            assert_eq!(
723                mode.emits_workload(),
724                *emits,
725                "emits_workload drift for {mode:?}"
726            );
727            assert_eq!(
728                mode.preserves_release_name(),
729                *preserves,
730                "preserves_release_name drift for {mode:?}"
731            );
732        }
733    }
734
735    /// DRIFT-PROOF CONTRACT: the hand-rolled
736    /// `mode == EncapsulationMode::Observe` and
737    /// `mode == EncapsulationMode::Adopt` checks the reconciler's
738    /// `render` function used pre-lift agree with the typed
739    /// projections for every variant in `ALL`. A regression that
740    /// re-introduces a raw `==` against a variant name fails here:
741    /// `!emits_workload()` IS "Observe mode" and
742    /// `preserves_release_name()` IS "Adopt mode", expressed as a
743    /// property of the typed surface rather than a pattern fragment
744    /// two crates have to keep coherent.
745    #[test]
746    fn mode_typed_projections_replace_raw_equality() {
747        for mode in EncapsulationMode::ALL {
748            assert_eq!(
749                !mode.emits_workload(),
750                mode == EncapsulationMode::Observe,
751                "!emits_workload() drift for {mode:?}"
752            );
753            assert_eq!(
754                mode.preserves_release_name(),
755                mode == EncapsulationMode::Adopt,
756                "preserves_release_name() drift for {mode:?}"
757            );
758        }
759    }
760
761    #[test]
762    fn serde_round_trip_via_yaml() {
763        let s = demo_adopt();
764        let yaml = serde_yaml::to_string(&s).unwrap();
765        assert!(yaml.contains("existingHelmRelease:"));
766        assert!(yaml.contains("releaseName: demo-app-consolidated"));
767        assert!(yaml.contains("mode: Adopt"));
768        let back: EncapsulatesSpec = serde_yaml::from_str(&yaml).unwrap();
769        assert!(back.kind.existing_helm_release.is_some());
770        assert_eq!(back.mode, EncapsulationMode::Adopt);
771    }
772
773    #[test]
774    fn bare_workload_selector_round_trips() {
775        let mut sel = BTreeMap::new();
776        sel.insert("app".into(), "demo-app".into());
777        sel.insert("tier".into(), "prod".into());
778        let s = EncapsulatesSpec {
779            kind: EncapsulationKind {
780                bare_workload: Some(BareWorkload {
781                    namespace: "legacy".into(),
782                    selector: sel,
783                }),
784                ..EncapsulationKind::default()
785            },
786            mode: EncapsulationMode::Observe,
787        };
788        let yaml = serde_yaml::to_string(&s).unwrap();
789        assert!(yaml.contains("bareWorkload:"));
790        assert!(yaml.contains("app: demo-app"));
791        assert!(yaml.contains("mode: Observe"));
792        let back: EncapsulatesSpec = serde_yaml::from_str(&yaml).unwrap();
793        match back.kind.variant().unwrap() {
794            EncapsulationKindVariant::BareWorkload(b) => {
795                assert_eq!(b.selector.len(), 2);
796                assert_eq!(b.selector.get("app").map(String::as_str), Some("demo-app"));
797            }
798            other => panic!("expected BareWorkload, got {other:?}"),
799        }
800    }
801
802    #[test]
803    fn lisp_round_trip_existing_hr() {
804        let src = r#"
805            (defencapsulates demo-adopt
806              :kind (:existing-helm-release
807                     (:namespace    "demo-ns"
808                      :name         "demo-app"
809                      :release-name "demo-app-consolidated"))
810              :mode Adopt)
811        "#;
812        let defs: Vec<tatara_lisp::NamedDefinition<EncapsulatesSpec>> =
813            tatara_lisp::compile_named::<EncapsulatesSpec>(src).expect("compile");
814        let d = &defs[0];
815        assert_eq!(d.name, "demo-adopt");
816        assert_eq!(d.spec.mode, EncapsulationMode::Adopt);
817        let h = d.spec.kind.existing_helm_release.as_ref().unwrap();
818        assert_eq!(h.namespace, "demo-ns");
819        assert_eq!(h.release_name, "demo-app-consolidated");
820    }
821
822    #[test]
823    fn lisp_default_mode_is_manage() {
824        // `:mode` omitted ⇒ Manage (Default derive).
825        let src = r#"
826            (defencapsulates greenfield
827              :kind (:existing-kustomization
828                     (:namespace "flux-system"
829                      :name      "openclaw")))
830        "#;
831        let defs: Vec<tatara_lisp::NamedDefinition<EncapsulatesSpec>> =
832            tatara_lisp::compile_named::<EncapsulatesSpec>(src).expect("compile");
833        let d = &defs[0];
834        assert_eq!(d.spec.mode, EncapsulationMode::Manage);
835    }
836
837    // ── closed-set algebra for EncapsulationTarget (ALL × as_str ×
838    //    Display × FromStr × select × EncapsulationKindVariant::target) ─
839
840    /// Construct an `EncapsulationKind` with one slot populated — the
841    /// composable construction table the closed-set property tests
842    /// loop over. Mirrors `single_slot_source` in
843    /// [`crate::export`] in shape.
844    fn single_slot_kind(target: EncapsulationTarget) -> EncapsulationKind {
845        match target {
846            EncapsulationTarget::ExistingHelmRelease => EncapsulationKind {
847                existing_helm_release: Some(ExistingHelmRelease {
848                    namespace: "ns".into(),
849                    name: "hr".into(),
850                    release_name: "rel".into(),
851                }),
852                ..EncapsulationKind::default()
853            },
854            EncapsulationTarget::ExistingKustomization => EncapsulationKind {
855                existing_kustomization: Some(ExistingKustomization {
856                    namespace: "ns".into(),
857                    name: "ks".into(),
858                }),
859                ..EncapsulationKind::default()
860            },
861            EncapsulationTarget::BareWorkload => {
862                let mut sel = BTreeMap::new();
863                sel.insert("app".into(), "x".into());
864                EncapsulationKind {
865                    bare_workload: Some(BareWorkload {
866                        namespace: "ns".into(),
867                        selector: sel,
868                    }),
869                    ..EncapsulationKind::default()
870                }
871            }
872        }
873    }
874
875    /// Construct an `EncapsulationKind` with two slots populated — drives
876    /// the pairwise `Ambiguous` sweep. Composes the single-slot
877    /// constructor on top of itself to keep one source of truth for
878    /// per-variant inner payloads.
879    fn two_slot_kind(a: EncapsulationTarget, b: EncapsulationTarget) -> EncapsulationKind {
880        let ka = single_slot_kind(a);
881        let kb = single_slot_kind(b);
882        EncapsulationKind {
883            existing_helm_release: ka.existing_helm_release.or(kb.existing_helm_release),
884            existing_kustomization: ka.existing_kustomization.or(kb.existing_kustomization),
885            bare_workload: ka.bare_workload.or(kb.bare_workload),
886        }
887    }
888
889    /// Structural well-formedness of [`EncapsulationTarget`] as a
890    /// [`tatara_lisp::ClosedSet`] implementor — the workspace-wide
891    /// testkit lift that pins all three structural invariants (`ALL`
892    /// is non-empty, every variant round-trips through
893    /// `label ↔ parse_label`, labels are pairwise distinct, `""` is
894    /// outside the closed set) at ONE call site. Replaces the hand-
895    /// derived `encapsulation_target_all_is_unique_and_complete` +
896    /// `encapsulation_target_roundtrip_via_as_str` + the empty-input
897    /// arm of `unknown_encapsulation_target_errors`. `FromStr`
898    /// delegates to `<Self as tatara_closed_set::ClosedSet>::parse_label`, so
899    /// this helper exercises the same code path the
900    /// `EncapsulationKind::variant` resolver hits when keying on a
901    /// camelCase target name back to the typed target.
902    #[test]
903    fn encapsulation_target_is_well_formed_closed_set() {
904        tatara_closed_set::assert_closed_set_well_formed::<EncapsulationTarget>();
905    }
906
907    /// CANONICAL-KEY CONTRACT: every `EncapsulationTarget::as_str()`
908    /// matches the serde `rename_all = "camelCase"` field name on the
909    /// corresponding `Option<…>` slot of `EncapsulationKind`. A future
910    /// rename of either the struct field OR the `as_str` arm lands here
911    /// at one site, instead of drifting between the typed surface, the
912    /// wire format, and the `EncapsulationKindError::Empty` diagnostic.
913    ///
914    /// Routes through the substrate primitive
915    /// [`crate::tagged_union::assert_single_slot_key_matches_label`],
916    /// which pins the exactly-one-key + name-equality projection
917    /// byte-identically for every `<T: TaggedUnion + Serialize>`
918    /// implementor — the wire-alignment testkit shared with the sibling
919    /// `intent_kind_as_str_matches_intent_field_name` /
920    /// `artifact_kind_as_str_matches_field_name` /
921    /// `channel_kind_as_str_matches_field_name` sites. Pre-lift this
922    /// site restated a weaker YAML-substring check (`yaml.contains(&format!("{key}:"))`)
923    /// which would silently pass on drift where a non-tagged-union
924    /// field was added to `EncapsulationKind`; post-lift the primitive's
925    /// JSON exactly-one form catches that drift too — at ONE substrate
926    /// site.
927    #[test]
928    fn encapsulation_target_as_str_matches_field_name() {
929        crate::tagged_union::assert_single_slot_key_matches_label::<EncapsulationKind, _>(
930            single_slot_kind,
931        );
932    }
933
934    /// CANONICAL-NAMES PIN: byte-exact camelCase wire-format pin —
935    /// renaming any of these strings IS a wire-format break that fails
936    /// this test FIRST so the rename stays a deliberate decision, not a
937    /// typo. Locks the (variant → operator-facing key) table.
938    #[test]
939    fn encapsulation_target_canonical_names_pinned() {
940        assert_eq!(
941            EncapsulationTarget::ExistingHelmRelease.as_str(),
942            "existingHelmRelease"
943        );
944        assert_eq!(
945            EncapsulationTarget::ExistingKustomization.as_str(),
946            "existingKustomization"
947        );
948        assert_eq!(EncapsulationTarget::BareWorkload.as_str(), "bareWorkload");
949    }
950
951    /// The Display impl IS `as_str` — pinning this lets future callers
952    /// reach for either projection without drift. If a reviewer
953    /// accidentally re-introduces an inline match in Display, this test
954    /// would fail the moment a variant rename touches one site but not
955    /// the other.
956    #[test]
957    fn encapsulation_target_display_matches_as_str() {
958        crate::tagged_union::assert_display_matches_label::<EncapsulationTarget>();
959    }
960
961    /// `FromStr` rejects strings that aren't in the canonical projection
962    /// — PascalCased / typo / cross-axis-leaked inputs from sibling
963    /// closed-set enums on the same `ProcessSpec` axis (`Manage`,
964    /// `Adopt`, `Observe`, `OnAttested`, …) — and the error echoes the
965    /// input verbatim so the operator-facing diagnostic carries the
966    /// offending value, not a normalized form. `EncapsulationTarget`
967    /// is its own axis, NOT a transparent reflection of any sibling.
968    /// The empty-input arm is pinned by
969    /// [`encapsulation_target_is_well_formed_closed_set`] via the
970    /// `tatara_lisp::ClosedSet` testkit; the cases here pin the
971    /// verbatim-echo contract on the [`UnknownEncapsulationTarget`]
972    /// newtype, which the trait's `make_unknown` can't see.
973    #[test]
974    fn unknown_encapsulation_target_errors() {
975        use std::str::FromStr;
976        for bad in [
977            "ExistingHelmRelease",
978            "existing_helm_release",
979            "EXISTINGHELMRELEASE",
980            "helmRelease",
981            "kustomization",
982            "Manage",
983            "Adopt",
984            "Observe",
985            "OnAttested",
986        ] {
987            let err = EncapsulationTarget::from_str(bad).unwrap_err();
988            assert_eq!(err.0, bad, "error payload should echo input verbatim");
989        }
990    }
991
992    /// ROUND-TRIP CONTRACT: every target reaches its borrowed-variant
993    /// view via `select`, and that variant projects back to the same
994    /// target via `EncapsulationKindVariant::target`. A regression that
995    /// misroutes a `select` arm (e.g.
996    /// `Self::ExistingHelmRelease => kind.existing_kustomization
997    /// .as_ref()...`) fails loudly here. Also pins that the resolver
998    /// lands on the same target.
999    ///
1000    /// Routes through the substrate primitive
1001    /// [`crate::tagged_union::assert_variant_round_trip`] shared with
1002    /// the sibling `intent_kind_round_trips_through_variant_kind` /
1003    /// `artifact_kind_round_trips_through_variant_kind` /
1004    /// `channel_kind_round_trips_through_variant_kind` sites — the
1005    /// projection lives at ONE substrate primitive and every site
1006    /// binds through a single call. The `target()` inherent method
1007    /// (semantic-specific to `EncapsulationTarget`) stays load-bearing
1008    /// on the callsite convention while the trait projection carries
1009    /// the round-trip check uniformly.
1010    #[test]
1011    fn encapsulation_target_round_trips_through_variant_target() {
1012        crate::tagged_union::assert_variant_round_trip::<EncapsulationKind, _>(single_slot_kind);
1013    }
1014
1015    /// SELECT-EMPTY CONTRACT: an unpopulated slot returns `None` from
1016    /// `select`, for every target. Pairs with the resolver's `Empty`
1017    /// path so a future target's slot defaulting wrong (e.g.
1018    /// accidentally `Some(Default::default())` instead of `None`) is
1019    /// caught here.
1020    #[test]
1021    fn encapsulation_target_select_returns_none_for_unset_slot() {
1022        let empty = EncapsulationKind::default();
1023        for t in EncapsulationTarget::ALL {
1024            assert!(
1025                t.select(&empty).is_none(),
1026                "{t:?} reported populated on a default EncapsulationKind"
1027            );
1028        }
1029    }
1030
1031    /// EMPTY-DIAGNOSTIC CONTRACT: the closed-set target list embedded
1032    /// in `EncapsulationKindError::Empty` echoes the canonical join of
1033    /// every `EncapsulationTarget::as_str()` projection. A variant
1034    /// added without updating `ENCAPSULATION_TARGET_LIST` (or a renamed
1035    /// variant) shows up here as a mismatch. Routes through the
1036    /// substrate primitive
1037    /// [`crate::tagged_union::assert_kind_list_matches_closed_set`]
1038    /// shared with the sibling `intent_error_empty_lists_every_kind_in_canonical_order`
1039    /// / `artifact_error_empty_lists_every_kind_in_canonical_order`
1040    /// / `channel_error_empty_lists_every_kind_in_canonical_order`
1041    /// sites — the projection lives at ONE substrate primitive and
1042    /// every site binds through a single call. The paired assertion
1043    /// that the diagnostic reaches operator-facing output verbatim
1044    /// stays local because the empty-arm construction differs per
1045    /// carrier.
1046    #[test]
1047    fn encapsulation_kind_error_empty_lists_every_target_in_canonical_order() {
1048        crate::tagged_union::assert_kind_list_matches_closed_set::<EncapsulationKind>();
1049        // And the diagnostic carries that exact list.
1050        let err = EncapsulationKind::default().variant().unwrap_err();
1051        assert_eq!(
1052            err,
1053            EncapsulationKindError::Empty(ENCAPSULATION_TARGET_LIST)
1054        );
1055    }
1056
1057    /// AMBIGUOUS-PATH CONTRACT: when two slots are populated the
1058    /// resolver yields `Ambiguous`, exhaustively across every pair in
1059    /// `ALL × ALL` (excluding the diagonal). A future asymmetry where
1060    /// one slot would silently shadow another (e.g. an `if-let` chain
1061    /// re-introducing first-wins ordering) is caught here. Routes
1062    /// through the substrate primitive
1063    /// [`crate::tagged_union::assert_two_slots_ambiguous`] shared with
1064    /// the sibling `artifact_source_two_slots_is_ambiguous_across_every_pair`
1065    /// / `vector_channel_two_slots_is_ambiguous_across_every_pair`
1066    /// / `intent_two_slots_is_ambiguous_across_every_pair` sites — the
1067    /// nested-`for a in K::ALL { for b in K::ALL { … } }` sweep lives
1068    /// at ONE substrate site.
1069    #[test]
1070    fn encapsulation_kind_two_slots_is_ambiguous_across_every_pair() {
1071        crate::tagged_union::assert_two_slots_ambiguous::<EncapsulationKind, _>(two_slot_kind);
1072    }
1073
1074    // Per-implementor `unknown_X_message_matches_substrate_convention`
1075    // tests removed — clause (5) of
1076    // `tatara_closed_set::assert_closed_set_well_formed::<T>()` now verifies
1077    // the substrate-wide `"unknown {SET_LABEL}: {input}"` carrier shape
1078    // generically (called above on `EncapsulationTarget` /
1079    // `EncapsulationMode` through their `*_is_well_formed_closed_set`
1080    // sites). The `SET_LABEL` projection is pinned independently by
1081    // `tatara_lisp_derive::pascal_to_spaced_lowercase_tests` —
1082    // together the two contracts guarantee the operator-facing
1083    // diagnostic without needing per-enum literal pins.
1084}