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}