tatara_process/tagged_union.rs
1//! `tagged_union::resolve` — the typescape's "exactly-one-Option" pattern,
2//! lifted to one source of truth.
3//!
4//! Several CRD-facing types in this crate ([`crate::intent::Intent`],
5//! [`crate::lifetime::Lifetime`], [`crate::export::ArtifactSource`],
6//! [`crate::export::VectorChannel`], [`crate::encapsulates::EncapsulationKind`])
7//! carry `N` `Option<T>` fields where exactly one is expected to be
8//! populated on the wire. Each previously hand-rolled the same
9//! `count() + if-let-chain + unreachable!()` body — four parallel tables
10//! (the struct fields, an `is_some()` count array, an `if-let-else`
11//! resolution chain, and any sibling projection like `IntentVariant::kind`)
12//! kept coherent only by code review. The `unreachable!()` arm at the
13//! bottom of every chain was a sentinel that fires at runtime if the
14//! parallel tables ever drift.
15//!
16//! This module collapses the resolver to ONE typed sweep over an
17//! `IntoIterator<Item = Option<V>>` of candidate variant projections.
18//! Adding a new tagged-union variant is now ONE additional line at the
19//! callsite — no `unreachable!()` arm to update, no parallel `is_some()`
20//! count array to extend.
21
22/// Outcome of [`resolve`] when the candidate list isn't exactly-one.
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub enum ResolveError {
25 /// No candidate was populated.
26 None,
27 /// More than one candidate was populated.
28 Many,
29}
30
31/// Resolve at most one populated variant from a candidate list.
32///
33/// Each item in `candidates` is the projected borrowed-variant view for
34/// the corresponding `Option<T>` field — `None` when the field is unset,
35/// `Some(V::Variant(...))` when set.
36///
37/// Returns the single populated variant, [`ResolveError::None`] when
38/// none are populated, or [`ResolveError::Many`] when more than one are.
39///
40/// The body is one short-circuiting sweep — `Many` is returned as soon
41/// as the second populated entry is seen, without scanning the rest.
42pub fn resolve<V>(candidates: impl IntoIterator<Item = Option<V>>) -> Result<V, ResolveError> {
43 let mut found: Option<V> = None;
44 for candidate in candidates {
45 if candidate.is_some() {
46 if found.is_some() {
47 return Err(ResolveError::Many);
48 }
49 found = candidate;
50 }
51 }
52 found.ok_or(ResolveError::None)
53}
54
55/// Sibling error carriers on tagged-union `.variant()` sites all
56/// project the two [`ResolveError`] arms onto the SAME closed-set
57/// diagnostic shape — `Empty(&'static str)` for "no variant set"
58/// (carrying the closed-set kind list so the operator diagnostic
59/// names every candidate) and a payload-free `Ambiguous` for
60/// "multiple variants set". This trait names that shared shape as
61/// ONE typed contract; [`resolve_or_err`] then composes [`resolve`]
62/// with the trait so each per-carrier `.map_err(|e| match e { ... })`
63/// site collapses to a one-line typed dispatch.
64///
65/// Impls live at each error carrier's own module so the (diagnostic
66/// message, closed-set list) pair stays owned by the carrier that
67/// publishes it — the trait is the projection, not the message.
68pub trait TaggedUnionError: Sized {
69 /// Construct the "no variant set" arm with the closed-set kind
70 /// list slash-joined into the diagnostic payload.
71 fn empty(kinds: &'static str) -> Self;
72 /// Construct the "multiple variants set" arm.
73 fn ambiguous() -> Self;
74}
75
76/// Resolve at most one populated variant, mapping the two
77/// [`ResolveError`] arms onto the caller's typed carrier via
78/// [`TaggedUnionError`]. The compound-lift primitive: sweep +
79/// short-circuit + typed-error dispatch as ONE call.
80///
81/// Substrate primitive for the four sibling `Xxx::variant()` sites
82/// on `ProcessSpec` (`Intent::variant`,
83/// `EncapsulationKind::variant`, `ArtifactSource::variant`,
84/// `VectorChannel::variant`) that previously restated the SAME
85/// `.map_err(|e| match e { None => Empty(LIST), Many => Ambiguous })`
86/// two-arm dispatch at each call site — every one of them a
87/// byte-identical restatement of the (empty→list, many→ambiguous)
88/// projection whose payload identity is strictly the carrier's own
89/// diagnostic. A fifth sibling error carrier picks up the projection
90/// through ONE `impl TaggedUnionError` block + ONE `resolve_or_err`
91/// call site.
92///
93/// The [`Lifetime::variant`](crate::lifetime::Lifetime::variant)
94/// site is DELIBERATELY not routed through this primitive — its
95/// `ResolveError::None` arm resolves to a `Permanent` default
96/// variant, not to an `Empty` typed error, so the projection shape
97/// diverges at the None arm.
98pub fn resolve_or_err<V, E: TaggedUnionError>(
99 candidates: impl IntoIterator<Item = Option<V>>,
100 kinds: &'static str,
101) -> Result<V, E> {
102 resolve(candidates).map_err(|e| match e {
103 ResolveError::None => E::empty(kinds),
104 ResolveError::Many => E::ambiguous(),
105 })
106}
107
108/// Declare a sibling error carrier for a tagged-union `.variant()`
109/// site — the enum + [`TaggedUnionError`] impl in ONE authoring
110/// surface.
111///
112/// Every one of the four production `.variant()` sites on
113/// `ProcessSpec` ([`crate::intent::Intent`],
114/// [`crate::encapsulates::EncapsulationKind`],
115/// [`crate::export::ArtifactSource`],
116/// [`crate::export::VectorChannel`]) pre-lift restated the same
117/// four-piece authoring shape by hand:
118///
119/// 1. `#[derive(Clone, Copy, Debug, thiserror::Error, PartialEq,
120/// Eq)]` on the carrier — byte-identical across all four.
121/// 2. A two-variant enum body (`Empty(&'static str)`, `Ambiguous`)
122/// — structurally identical.
123/// 3. Two `#[error(...)]` messages whose only per-carrier knob is a
124/// noun-prefix (`"intent"`, `"encapsulation kind"`, ...) — every
125/// other byte of the (`"has no variant set (one of {0}
126/// required)"`, `"has multiple variants set; exactly one
127/// required"`) tails was verbatim.
128/// 4. A six-line `impl TaggedUnionError` whose two constructor
129/// bodies re-projected `Self::Empty(kinds)` / `Self::Ambiguous`
130/// onto each carrier's own typed variants.
131///
132/// The macro collapses (1) + (2) + (4) onto ONE call and takes the
133/// two per-carrier operator-facing diagnostic literals as named
134/// arguments so (3) stays visible at the callsite without re-authoring
135/// the shared derive set or trait impl. A fifth sibling carrier
136/// lands as ONE `declare_tagged_union_error!` invocation — no
137/// re-authored `#[derive(...)]`, no re-authored two-variant enum
138/// body, no re-authored `impl TaggedUnionError` block.
139///
140/// Emitted derives include `Copy` — the `Empty` arm carries only
141/// a `&'static str` and the `Ambiguous` arm is payload-free, so
142/// the carrier is always `Copy` regardless of caller.
143///
144/// # Example
145///
146/// ```ignore
147/// declare_tagged_union_error! {
148/// pub IntentError,
149/// empty = "intent has no variant set (one of {0} required)",
150/// ambiguous = "intent has multiple variants set; exactly one required",
151/// }
152/// ```
153///
154/// Expands to the enum + [`TaggedUnionError`] impl for
155/// `IntentError`; the `Empty` arm carries the caller's closed-set
156/// kind-list literal.
157#[macro_export]
158macro_rules! declare_tagged_union_error {
159 (
160 $(#[$attr:meta])*
161 $vis:vis $name:ident,
162 empty = $empty:literal,
163 ambiguous = $ambiguous:literal $(,)?
164 ) => {
165 $(#[$attr])*
166 #[derive(
167 ::std::clone::Clone,
168 ::std::marker::Copy,
169 ::std::fmt::Debug,
170 ::thiserror::Error,
171 ::std::cmp::PartialEq,
172 ::std::cmp::Eq,
173 )]
174 $vis enum $name {
175 #[error($empty)]
176 Empty(&'static str),
177 #[error($ambiguous)]
178 Ambiguous,
179 }
180
181 impl $crate::tagged_union::TaggedUnionError for $name {
182 fn empty(kinds: &'static str) -> Self {
183 Self::Empty(kinds)
184 }
185 fn ambiguous() -> Self {
186 Self::Ambiguous
187 }
188 }
189 };
190}
191
192/// Declare the three-block impl stanza a tagged-union parent type
193/// publishes to the substrate — inherent `.variant()` forwarder +
194/// [`VariantSelector<Parent>`] impl on the sibling `Kind` +
195/// [`TaggedUnion`] impl on the parent — in ONE authoring surface.
196///
197/// Every one of the four production `.variant()` sites on
198/// `ProcessSpec` ([`crate::intent::Intent`],
199/// [`crate::encapsulates::EncapsulationKind`],
200/// [`crate::export::ArtifactSource`],
201/// [`crate::export::VectorChannel`]) pre-lift restated the same three
202/// impl blocks by hand:
203///
204/// 1. `impl $parent { pub fn variant(&self) -> Result<$variant<'_>, $err> { ... } }`
205/// — a one-line delegation to the [`TaggedUnion::variant`] default
206/// body, plus 5 lines of rustdoc cross-referencing the other three
207/// sibling `.variant()` sites verbatim.
208/// 2. `impl VariantSelector<$parent> for $kind { type Variant<'a> = $variant<'a>; fn select(...) { <$kind>::select(self, parent) } }`
209/// — 6 lines whose only per-site knobs are (`$parent`, `$kind`,
210/// `$variant`); the trait method body a straight delegation to the
211/// inherent `<$kind>::select`.
212/// 3. `impl TaggedUnion for $parent { type Kind = $kind; type Error = $err; const KIND_LIST = $kind_list; }`
213/// — 3 associated-item assignments whose only per-site knobs are
214/// the (`$kind`, `$err`, `$kind_list`) tuple.
215///
216/// The macro takes the (`$parent`, `$kind`, `$variant`, `$err`,
217/// `$kind_list`) five-tuple as named arguments and emits all three
218/// blocks. A fifth sibling tagged-union parent picks up all three
219/// impls through ONE macro call — no re-authored inherent
220/// `.variant()` forwarder, no re-authored `impl VariantSelector`
221/// block, no re-authored `impl TaggedUnion` block.
222///
223/// The emitted inherent `.variant()`'s rustdoc is canonical (names
224/// the substrate primitive, not the exact set of sibling sites) so
225/// a fifth sibling doesn't drift the cross-ref count against reality
226/// merely by existing.
227///
228/// # Example
229///
230/// ```ignore
231/// declare_tagged_union_impls! {
232/// parent = Intent,
233/// kind = IntentKind,
234/// variant = IntentVariant,
235/// error = IntentError,
236/// kind_list = INTENT_KIND_LIST,
237/// }
238/// ```
239///
240/// Expands to the inherent `Intent::variant`, the
241/// `VariantSelector<Intent>` impl on `IntentKind`, and the
242/// `TaggedUnion` impl on `Intent`.
243#[macro_export]
244macro_rules! declare_tagged_union_impls {
245 (
246 parent = $parent:ty,
247 kind = $kind:ty,
248 variant = $variant:ident,
249 error = $err:ty,
250 kind_list = $kind_list:expr $(,)?
251 ) => {
252 impl $parent {
253 /// Resolve to exactly one variant. Errors on zero or many.
254 ///
255 /// One-line inherent forwarder that delegates the sweep
256 /// body to the substrate primitive
257 /// [`crate::tagged_union::TaggedUnion::variant`] — every
258 /// production `.variant()` site on `ProcessSpec` dispatches
259 /// through this ONE default body so the resolve-sweep
260 /// pattern lives at ONE substrate site. The inherent surface
261 /// stays load-bearing so consumer callsites don't need
262 /// `use TaggedUnion`.
263 pub fn variant(&self) -> ::std::result::Result<$variant<'_>, $err> {
264 <Self as $crate::tagged_union::TaggedUnion>::variant(self)
265 }
266 }
267
268 impl $crate::tagged_union::VariantSelector<$parent> for $kind {
269 type Variant<'a> = $variant<'a>;
270 fn select<'a>(self, parent: &'a $parent) -> ::std::option::Option<$variant<'a>>
271 where
272 Self: 'a,
273 {
274 <$kind>::select(self, parent)
275 }
276 }
277
278 impl $crate::tagged_union::TaggedUnion for $parent {
279 type Kind = $kind;
280 type Error = $err;
281 const KIND_LIST: &'static str = $kind_list;
282 }
283 };
284}
285
286/// Project the borrowed-view of a tagged-union variant addressed by
287/// this closed-set discriminator.
288///
289/// Companion trait to [`TaggedUnion`] — binds a `Kind` closed-set to
290/// the parent `P` it discriminates AND to the borrowed-view
291/// [`Self::Variant<'a>`] the resolver hands out. Every one of the
292/// four production `.variant()` sites on `ProcessSpec`
293/// ([`crate::intent::Intent`], [`crate::encapsulates::EncapsulationKind`],
294/// [`crate::export::ArtifactSource`], [`crate::export::VectorChannel`])
295/// pre-lift restated the same
296/// `Self::Kind::ALL.into_iter().map(|k| k.select(self))` sweep body
297/// verbatim at its inherent `.variant()`. Post-lift the trait binds
298/// `(k.select(self), Variant<'a>)` onto ONE typed contract per Kind
299/// so [`TaggedUnion::variant`]'s default body can dispatch the sweep
300/// generically — the four sibling inherent bodies collapse to
301/// one-line delegations and a fifth sibling picks up the sweep for
302/// free through ONE `impl VariantSelector` block.
303///
304/// The GAT `Variant<'a>` carries the parent's lifetime so a borrowed
305/// view projected from `&'a P` composes typed with the resolver's
306/// short-circuit — every projection stays a compile-time refinement,
307/// no `Box<dyn ...>` erasure. The GAT is additionally bound to
308/// [`VariantKind<Self>`] so every implementor's borrowed view knows
309/// its addressing Kind — the reverse projection of [`Self::select`]
310/// closed at compile-time so a fifth sibling that adds `impl
311/// VariantSelector` without opening the peer `impl VariantKind` fails
312/// at the trait bound, not later at a per-consumer round-trip test.
313pub trait VariantSelector<P: ?Sized>: Copy + 'static {
314 /// The borrowed-view enum returned by the parent's inherent
315 /// `.variant()` method — one arm per closed-set variant, each
316 /// arm carrying a `&'a` reference into the parent's populated
317 /// slot. Bound generically here so [`TaggedUnion::variant`]'s
318 /// default body can name the return type without restating it
319 /// per parent. Additionally bound to [`VariantKind<Self>`] so
320 /// the reverse projection `Variant<'a> → Self` is closed at the
321 /// trait boundary — every implementor's borrowed view knows its
322 /// addressing Kind through ONE typed contract, and the substrate
323 /// testkit [`assert_variant_round_trip`] composes `select`
324 /// (forward) with `variant_kind` (reverse) generically.
325 type Variant<'a>: VariantKind<Self>
326 where
327 P: 'a,
328 Self: 'a;
329
330 /// Project a `&'a P` borrow into the optional typed variant view
331 /// for `self` (the addressed discriminator). Returns `None` iff
332 /// the matching slot on `P` is `None`. Composes the closed-set
333 /// sweep [`TaggedUnion::variant`] loops over.
334 fn select<'a>(self, parent: &'a P) -> Option<Self::Variant<'a>>
335 where
336 Self: 'a;
337}
338
339/// Reverse projection — every borrowed-variant view enum knows its
340/// closed-set `K` discriminator.
341///
342/// Dual of [`VariantSelector<P>::select`] on the addressed Kind:
343/// where the selector projects a parent borrow forward into an
344/// optional Variant, this trait projects a populated Variant back
345/// into the Kind that addresses it. Together they compose the
346/// round-trip contract every tagged-union `.variant()` site pins
347/// via the substrate testkit [`assert_variant_round_trip`]:
348/// `k.select(&parent).map(|v| v.variant_kind()) == Some(k)` on the
349/// populated side, and `parent.variant().unwrap().variant_kind() == k`
350/// through the [`TaggedUnion::variant`] resolver's default body.
351///
352/// Every borrowed-view enum on `ProcessSpec`'s tagged-union axis
353/// ([`crate::intent::IntentVariant<'_>`],
354/// [`crate::lifetime::LifetimeVariant<'_>`],
355/// [`crate::encapsulates::EncapsulationKindVariant<'_>`],
356/// [`crate::export::ArtifactVariant<'_>`],
357/// [`crate::export::ChannelVariant<'_>`]) pre-lift restated the same
358/// `match self { Self::A(_) => K::A, Self::B(_) => K::B, ... }`
359/// per-arm mapping at its own inherent method (named `.kind()` on
360/// four of five sites; `.target()` on
361/// [`crate::encapsulates::EncapsulationKindVariant`] where the
362/// discriminator's semantic role is a target of encapsulation, not
363/// a kind of parent). The reverse-projection body must stay
364/// per-implementor — it names the ground-truth arm-to-Kind mapping
365/// only the site knows — but the CONTRACT lives at ONE typed
366/// surface so:
367///
368/// * Every downstream generic consumer binds through
369/// `<T::Variant<'_> as VariantKind<T::Kind>>::variant_kind(&v)`
370/// instead of a per-parent inherent-method restatement.
371/// * [`VariantSelector<P>::Variant<'a>`] bounds this trait — a
372/// fifth sibling that adds `impl VariantSelector<P> for XKind`
373/// without the peer `impl VariantKind<XKind> for XVariant<'_>`
374/// fails at the associated-type bound, so the reverse projection
375/// is closed at compile-time across every implementor.
376/// * The generic testkit [`assert_variant_round_trip`] composes
377/// `select` (forward) with `variant_kind` (reverse) at ONE
378/// substrate site — the four sibling
379/// `_kind_round_trips_through_variant_kind` /
380/// `_target_round_trips_through_variant_target` test bodies
381/// collapse to one-line invocations.
382///
383/// The trait method is named [`Self::variant_kind`] rather than
384/// `kind` to avoid shadowing the inherent `.kind()` (or
385/// `.target()`) methods each borrowed-view enum already publishes.
386/// Every impl body is a one-line delegation to the site's inherent
387/// method — the substrate stays the projection, not the mapping.
388pub trait VariantKind<K: Copy + 'static> {
389 /// Project a borrowed-variant view back into its addressing
390 /// closed-set `K` discriminator. Round-trips the closed set on
391 /// the populated side against [`VariantSelector::select`] — a
392 /// value returned by `k.select(&parent).unwrap()` must satisfy
393 /// `variant_kind() == k`, and a value returned by
394 /// `parent.variant().unwrap()` must satisfy `variant_kind() ==
395 /// k` for the populated slot's `k`.
396 fn variant_kind(&self) -> K;
397}
398
399/// Generic round-trip testkit — pins that
400/// [`VariantSelector::select`] (forward projection) and
401/// [`VariantKind::variant_kind`] (reverse projection) compose the
402/// closed set in both directions on the populated side.
403///
404/// Substrate primitive for the four sibling
405/// `_kind_round_trips_through_variant_kind` /
406/// `_target_round_trips_through_variant_target` tests on
407/// `ProcessSpec` ([`crate::intent::Intent`],
408/// [`crate::encapsulates::EncapsulationKind`],
409/// [`crate::export::ArtifactSource`],
410/// [`crate::export::VectorChannel`]) that pre-lift each restated the
411/// same two-arm round-trip probe at their own test bodies:
412///
413/// 1. For each `k in K::ALL`, construct a parent with only slot `k`
414/// populated (via a site-local `single_slot_X(k) -> Parent`
415/// helper).
416/// 2. Assert that `k.select(&parent).unwrap().variant_kind() == k`
417/// (the forward-then-reverse round-trip).
418/// 3. Assert that `parent.variant().unwrap().variant_kind() == k`
419/// (the resolver-then-reverse round-trip).
420///
421/// Post-lift each site's round-trip test collapses to ONE
422/// `assert_variant_round_trip::<T, _>(single_slot_X)` invocation
423/// whose body is the substrate primitive's own dispatch. A fifth
424/// sibling picks up the round-trip check through ONE call site.
425///
426/// The `make_parent` closure stays per-site — every one of the four
427/// production sites already owns a
428/// `single_slot_intent(k) / single_slot_source(k) /
429/// single_slot_channel(k) / single_slot_kind(t)` helper that
430/// constructs a minimally-valid parent with the addressed slot's
431/// inner spec populated; the closure IS the round-trip's ground
432/// truth for "populate slot k", and lifting it into the primitive
433/// would collapse the per-site construction knowledge that stays
434/// deliberately local.
435///
436/// The [`crate::lifetime::Lifetime`] site is DELIBERATELY excluded
437/// — `Lifetime` doesn't impl [`TaggedUnion`] (its `variant()` returns
438/// `Ok(Permanent)` on empty, not an `Empty` typed error), so the
439/// `<T: TaggedUnion>` bound doesn't reach it. Its per-site
440/// round-trip test binds through [`VariantKind`] directly on
441/// [`crate::lifetime::LifetimeVariant`] instead.
442#[track_caller]
443pub fn assert_variant_round_trip<T, F>(make_parent: F)
444where
445 T: TaggedUnion,
446 T::Kind: PartialEq + std::fmt::Debug,
447 F: Fn(T::Kind) -> T,
448{
449 for k in <T::Kind as tatara_closed_set::ClosedSet>::ALL
450 .iter()
451 .copied()
452 {
453 let parent = make_parent(k);
454 let selected = k.select(&parent).unwrap_or_else(|| {
455 panic!("VariantSelector::select must return Some for populated slot {k:?}")
456 });
457 assert_eq!(
458 <<T::Kind as VariantSelector<T>>::Variant<'_> as VariantKind<T::Kind>>::variant_kind(
459 &selected,
460 ),
461 k,
462 "select→variant_kind round-trip failed for {k:?}",
463 );
464 let resolved = parent.variant().ok().unwrap_or_else(|| {
465 panic!("TaggedUnion::variant must resolve exactly-one populated for {k:?}")
466 });
467 assert_eq!(
468 <<T::Kind as VariantSelector<T>>::Variant<'_> as VariantKind<T::Kind>>::variant_kind(
469 &resolved,
470 ),
471 k,
472 "variant()→variant_kind resolver disagreed on {k:?}",
473 );
474 }
475}
476
477/// Declarative surface that names the (Kind, Error, KIND_LIST) triple
478/// a tagged-union `.variant()` site publishes to the substrate — and
479/// provides the sweep body as ONE default method every implementor
480/// picks up for free.
481///
482/// Every one of the four production `.variant()` sites on `ProcessSpec`
483/// ([`crate::intent::Intent`], [`crate::encapsulates::EncapsulationKind`],
484/// [`crate::export::ArtifactSource`], [`crate::export::VectorChannel`])
485/// exposes the SAME three-piece surface: a closed-set discriminator
486/// [`Self::Kind`], a typed [`Self::Error`] carrier that projects onto
487/// the shared [`TaggedUnionError`] contract, and a slash-joined
488/// operator diagnostic literal [`Self::KIND_LIST`]. Pre-lift the
489/// triple lived on each parent type as independent inherent items —
490/// the (Kind, Error) types cross-referenced only by module-doc prose,
491/// the `KIND_LIST` `&'static str` maintained separately at each site
492/// alongside the inherent `.variant()` body. Post-lift the trait
493/// binds the three onto ONE typed contract per parent so downstream
494/// generic code binds to `<T: TaggedUnion>` instead of restating the
495/// per-parent quadruple of associated names.
496///
497/// The [`Self::variant`] default method is the substrate primitive
498/// every inherent `.variant()` on the four production sites delegates
499/// to — one-line inherent forwarders preserve the load-bearing
500/// calling convention (so no downstream callsite needs
501/// `use crate::tagged_union::TaggedUnion` to reach `.variant()`) while
502/// the resolve-sweep body lives at ONE substrate site. Adding a fifth
503/// sibling means ONE `impl TaggedUnion` block + ONE
504/// `impl VariantSelector<Self>` block on the sibling `Kind` + ONE
505/// one-line inherent forwarder — no re-authored 5-line
506/// `resolve_or_err(K::ALL.into_iter().map(|k| k.select(self)),
507/// KIND_LIST)` sweep body.
508///
509/// The `Kind` type is bound to [`tatara_closed_set::ClosedSet`] so
510/// generic testkit primitives (starting with
511/// [`assert_kind_list_matches_closed_set`]) can compose
512/// `<Self::Kind as ClosedSet>::labels_joined("/")` against
513/// [`Self::KIND_LIST`] byte-identically across every implementor —
514/// the diagnostic-stability invariant every sibling pre-lift pinned
515/// through a hand-rolled per-site test body. It is additionally
516/// bound to [`VariantSelector<Self>`] so [`Self::variant`]'s default
517/// body reaches `k.select(self)` generically.
518///
519/// The [`crate::lifetime::Lifetime`] site is DELIBERATELY not routed
520/// through this trait — its `variant()` returns `Ok(Permanent)` on
521/// empty rather than an `Empty` typed error, so its projection shape
522/// diverges from the four Empty-projecting siblings. Same reasoning
523/// as [`resolve_or_err`]'s explicit exclusion of `Lifetime`.
524pub trait TaggedUnion: Sized {
525 /// The closed-set discriminator over this tagged-union's variants.
526 /// Bound to [`tatara_closed_set::ClosedSet`] so the generic
527 /// diagnostic-stability testkit ([`assert_kind_list_matches_closed_set`])
528 /// can project `<Self::Kind as ClosedSet>::labels_joined("/")`
529 /// against [`Self::KIND_LIST`] byte-identically. Additionally
530 /// bound to [`VariantSelector<Self>`] so [`Self::variant`]'s
531 /// default body can dispatch `k.select(self)` at each
532 /// [`ClosedSet::ALL`] entry generically.
533 type Kind: tatara_closed_set::ClosedSet + VariantSelector<Self>;
534
535 /// The typed error carrier returned by the parent's inherent
536 /// `.variant()` method — projects onto the shared
537 /// [`TaggedUnionError`] contract so [`resolve_or_err`]'s two-arm
538 /// dispatch reaches every implementor uniformly.
539 type Error: TaggedUnionError;
540
541 /// Slash-joined operator diagnostic literal — the payload of
542 /// [`TaggedUnionError::empty`] when no slot is populated on this
543 /// tagged union. Pinned against
544 /// `<Self::Kind as tatara_closed_set::ClosedSet>::labels_joined("/")`
545 /// by [`assert_kind_list_matches_closed_set`] so a variant added
546 /// to `Self::Kind` without updating this constant (or a renamed
547 /// variant) fails-loudly at the testkit boundary.
548 const KIND_LIST: &'static str;
549
550 /// Sweep over every [`Self::Kind`] discriminator in
551 /// [`ClosedSet::ALL`](tatara_closed_set::ClosedSet::ALL) order,
552 /// projecting each into the parent's borrowed variant view via
553 /// [`VariantSelector::select`], and resolve to exactly one populated
554 /// variant through [`resolve_or_err`]. Errors on zero (with
555 /// [`Self::KIND_LIST`] carried on the [`TaggedUnionError::empty`]
556 /// arm) or many.
557 ///
558 /// The substrate primitive every one of the four production
559 /// `.variant()` sites on `ProcessSpec` dispatches through — the
560 /// per-parent inherent `.variant()` is a one-line delegation to
561 /// this default so the calling convention (`intent.variant()`,
562 /// `channel.variant()`, ...) stays load-bearing at the callsite
563 /// without every consumer picking up `use TaggedUnion`.
564 ///
565 /// Adding a fifth sibling picks up this body for free — no
566 /// re-authored `resolve_or_err(...)` sweep at the impl block.
567 fn variant(&self) -> Result<<Self::Kind as VariantSelector<Self>>::Variant<'_>, Self::Error> {
568 resolve_or_err(
569 <Self::Kind as tatara_closed_set::ClosedSet>::ALL
570 .iter()
571 .copied()
572 .map(|k| k.select(self)),
573 Self::KIND_LIST,
574 )
575 }
576}
577
578/// Generic diagnostic-stability testkit — pins that [`TaggedUnion::KIND_LIST`]
579/// matches `<T::Kind as tatara_closed_set::ClosedSet>::labels_joined("/")`
580/// byte-identically for every implementor.
581///
582/// Substrate primitive for the four sibling
583/// `_error_empty_lists_every_kind_in_canonical_order` tests on
584/// `ProcessSpec` ([`crate::intent::Intent`],
585/// [`crate::encapsulates::EncapsulationKind`],
586/// [`crate::export::ArtifactSource`], [`crate::export::VectorChannel`])
587/// that pre-lift each restated the same
588/// `assert_eq!(<XxxKind as ClosedSet>::labels_joined("/"),
589/// XXX_KIND_LIST)` two-argument comparison at their own test bodies —
590/// byte-identical projections whose only per-carrier knobs (the Kind
591/// type + the KIND_LIST constant) are the two associated items the
592/// [`TaggedUnion`] trait names. Post-lift each site collapses to ONE
593/// `assert_kind_list_matches_closed_set::<Xxx>()` invocation whose
594/// body is the substrate primitive's own dispatch.
595///
596/// A fifth sibling tagged-union parent picks up the diagnostic-
597/// stability check through ONE `impl TaggedUnion for X` block + ONE
598/// `assert_kind_list_matches_closed_set::<X>()` call site — no
599/// re-authored `<XKind as ClosedSet>::labels_joined("/")` composition
600/// at the test site, no re-authored per-site `assert_eq!` pair.
601#[track_caller]
602pub fn assert_kind_list_matches_closed_set<T: TaggedUnion>() {
603 let derived = <T::Kind as tatara_closed_set::ClosedSet>::labels_joined("/");
604 assert_eq!(
605 derived,
606 T::KIND_LIST,
607 "TaggedUnion KIND_LIST drift — must equal <T::Kind as ClosedSet>::labels_joined(\"/\")",
608 );
609}
610
611/// Generic ambiguity testkit — pins that [`TaggedUnion::variant`]
612/// resolves to [`TaggedUnionError::ambiguous`] on EVERY off-diagonal
613/// `(a, b)` pair in [`ClosedSet::ALL`](tatara_closed_set::ClosedSet::ALL)
614/// `× ALL`.
615///
616/// Substrate primitive for the sibling
617/// `_two_slots_is_ambiguous_across_every_pair` tests on `ProcessSpec`
618/// ([`crate::encapsulates::EncapsulationKind`],
619/// [`crate::export::ArtifactSource`], [`crate::export::VectorChannel`])
620/// that pre-lift each restated the same nested-`for a in K::ALL { for
621/// b in K::ALL { if a == b { continue; } … } }` sweep at their own
622/// test bodies — byte-identical projections whose only per-carrier
623/// knobs are the (Kind type + the `two_slot_X(a, b) -> Parent`
624/// two-slot factory) pair. Post-lift each site collapses to ONE
625/// `assert_two_slots_ambiguous::<Xxx, _>(two_slot_X)` invocation.
626///
627/// The `two_slot` closure stays per-site — every one of the three
628/// production sites already owns a `two_slot_kind /
629/// two_slot_source / two_slot_channel` helper that composes two
630/// `single_slot_X`s per-field. The closure IS the "populate both
631/// slots a and b" ground truth for the carrier's field structure;
632/// lifting it into the primitive would collapse per-site field-
633/// composition knowledge that stays deliberately local.
634///
635/// The pair sweep excludes the diagonal (`a == b`) — a single slot
636/// populated is exactly-one, not many, and the round-trip primitive
637/// [`assert_variant_round_trip`] already pins that populated slot's
638/// resolution. This primitive is the peer contract for the Many arm.
639///
640/// A fifth sibling tagged-union parent picks up the ambiguity check
641/// through ONE `impl TaggedUnion for X` block + ONE per-site
642/// `two_slot_X` helper + ONE `assert_two_slots_ambiguous::<X, _>`
643/// call site — no re-authored nested-for sweep at the test surface,
644/// no re-authored `assert_eq!(..., X::Error::Ambiguous, ...)` arm.
645///
646/// The [`crate::lifetime::Lifetime`] site is DELIBERATELY excluded
647/// — `Lifetime` doesn't impl [`TaggedUnion`] (its error carrier has
648/// no `Empty` arm; its `variant()` returns `Ok(Permanent)` on empty
649/// rather than an `Empty` typed error), so the `<T: TaggedUnion>`
650/// bound doesn't reach it. Its per-site ambiguity assertion binds
651/// through the inherent `.variant()` + hand-authored two-slot
652/// probe. Same reasoning as [`resolve_or_err`]'s and
653/// [`assert_variant_round_trip`]'s exclusions.
654#[track_caller]
655pub fn assert_two_slots_ambiguous<T, F>(two_slot: F)
656where
657 T: TaggedUnion,
658 T::Kind: PartialEq + std::fmt::Debug,
659 T::Error: PartialEq + std::fmt::Debug,
660 F: Fn(T::Kind, T::Kind) -> T,
661{
662 let expected = T::Error::ambiguous();
663 for a in <T::Kind as tatara_closed_set::ClosedSet>::ALL
664 .iter()
665 .copied()
666 {
667 for b in <T::Kind as tatara_closed_set::ClosedSet>::ALL
668 .iter()
669 .copied()
670 {
671 if a == b {
672 continue;
673 }
674 let parent = two_slot(a, b);
675 let err = parent.variant().err().unwrap_or_else(|| {
676 panic!("({a:?}, {b:?}) two-slot parent must not resolve to a variant")
677 });
678 assert_eq!(err, expected, "({a:?}, {b:?}) should resolve Ambiguous");
679 }
680 }
681}
682
683/// Generic wire-key / kind-label alignment testkit — pins that every
684/// single-slot parent serializes to a JSON object with EXACTLY ONE key
685/// whose name equals `<T::Kind as tatara_closed_set::ClosedSet>::label`
686/// on the populated slot's kind.
687///
688/// Substrate primitive for the four sibling
689/// `X_kind_as_str_matches_field_name` / `intent_kind_as_str_matches_intent_field_name`
690/// tests on `ProcessSpec` ([`crate::intent::Intent`],
691/// [`crate::encapsulates::EncapsulationKind`],
692/// [`crate::export::ArtifactSource`], [`crate::export::VectorChannel`])
693/// that pre-lift each restated the same wire-format sweep at their own
694/// test bodies:
695///
696/// 1. For each `k in K::ALL`, construct a single-slot parent via
697/// the site-local `single_slot_X(k) -> Parent` factory.
698/// 2. Serialize it to the wire format and assert that the emitted
699/// key matches `k.as_str()`.
700///
701/// Post-lift each site's alignment test collapses to ONE
702/// `assert_single_slot_key_matches_label::<T, _>(single_slot_X)`
703/// invocation whose body IS the substrate primitive's own dispatch.
704/// A fifth sibling picks up the alignment check through ONE call site.
705///
706/// The primitive projects through `serde_json::to_value` rather than
707/// `serde_yaml::to_string` for two reasons: (1) the check is
708/// structural (exactly-one-key + name equality), not textual (substring
709/// against a `"{key}:"` YAML fragment), so a future site that gains
710/// non-tagged-union metadata fields is caught HERE at the exactly-one
711/// arm — the YAML-substring check the three encapsulates / export sites
712/// carried pre-lift would silently pass on such drift. (2) serde's
713/// field-rename projection (`rename_all = "camelCase"`) is format-
714/// agnostic, so a JSON check pins the SAME invariant a YAML check
715/// would pin, byte-identically. Every one of the four production
716/// parents already emits exactly one key on a single-slot populate —
717/// their `#[serde(default, skip_serializing_if = "Option::is_none")]`
718/// annotations on every tagged-union slot guarantee it — so upgrading
719/// the three YAML sites to the JSON exactly-one check is a strict
720/// strengthening.
721///
722/// The `single_slot` closure stays per-site — every one of the four
723/// production sites already owns a `single_slot_intent /
724/// single_slot_kind / single_slot_source / single_slot_channel` helper
725/// that constructs a minimally-valid parent with the addressed slot's
726/// inner spec populated; the closure IS the "populate slot k" ground
727/// truth for the carrier's field structure. Reused verbatim from the
728/// [`assert_variant_round_trip`] primitive.
729///
730/// The [`crate::lifetime::Lifetime`] site is DELIBERATELY excluded —
731/// `Lifetime` doesn't impl [`TaggedUnion`] (its `variant()` returns
732/// `Ok(Permanent)` on empty rather than an `Empty` typed error), so
733/// the `<T: TaggedUnion>` bound doesn't reach it. Same reasoning as
734/// [`resolve_or_err`]'s and [`assert_variant_round_trip`]'s and
735/// [`assert_two_slots_ambiguous`]'s exclusions.
736#[track_caller]
737pub fn assert_single_slot_key_matches_label<T, F>(single_slot: F)
738where
739 T: TaggedUnion + serde::Serialize,
740 T::Kind: PartialEq + std::fmt::Debug,
741 F: Fn(T::Kind) -> T,
742{
743 for k in <T::Kind as tatara_closed_set::ClosedSet>::ALL
744 .iter()
745 .copied()
746 {
747 let parent = single_slot(k);
748 let value = serde_json::to_value(&parent)
749 .unwrap_or_else(|e| panic!("single_slot({k:?}) must serialize as JSON: {e}"));
750 let obj = value.as_object().unwrap_or_else(|| {
751 panic!("single_slot({k:?}) must serialize to a JSON object, got {value}")
752 });
753 let keys: Vec<&String> = obj.keys().collect();
754 assert_eq!(
755 keys.len(),
756 1,
757 "single_slot({k:?}) must serialize to exactly one populated field, got keys: {keys:?}",
758 );
759 let expected = <T::Kind as tatara_closed_set::ClosedSet>::label(k);
760 assert_eq!(
761 keys[0].as_str(),
762 expected,
763 "wire-key drift for {k:?}: single_slot's populated field '{}' must equal <T::Kind as ClosedSet>::label ({expected:?})",
764 keys[0],
765 );
766 }
767}
768
769/// Generic Display / [`ClosedSet::label`](tatara_closed_set::ClosedSet::label)
770/// alignment testkit — pins that [`core::fmt::Display`] renders each variant
771/// BYTE-IDENTICALLY to the trait-visible `ClosedSet::label` projection for
772/// every implementor.
773///
774/// Substrate primitive for the 29 sibling
775/// `X_display_matches_as_str` tests across `tatara-process`
776/// (`AllocationPhase`, `IntentKind`, `WorkloadKind`, `EncapsulationMode`,
777/// `EncapsulationTarget`, `ConditionKind`, `TerminateReasonKind`,
778/// `AutoTerminateKind`, `SighupStrategy`, `ReplacementPolicy`,
779/// `ReturnPolicy`, `MemberState`, `PoolPhase`, `VerificationPhase`,
780/// `SelectStrategyKind`, `MustReachPhase`, `ExportTrigger`,
781/// `ReportFormat`, `ReportPayloadShape`, `ArtifactKind`, `ChannelKind`,
782/// `DataClassification`, `ConvergencePointType`, `Arity`,
783/// `SubstrateType`, `CalmClassification`, `OptimizationDirection`,
784/// `HorizonKind`, `TeardownPolicy`) that pre-lift each restated the
785/// same
786/// ```text
787/// for v in K::ALL {
788/// assert_eq!(v.to_string(), v.as_str());
789/// }
790/// ```
791/// two-line probe verbatim at their own test bodies — byte-identical
792/// projections whose only per-carrier knob is the closed-set type name.
793/// Post-lift each site collapses to ONE
794/// `assert_display_matches_label::<X>()` invocation whose body IS the
795/// substrate primitive's own dispatch.
796///
797/// The primitive projects through the STABLE trait-visible name
798/// [`ClosedSet::label`](tatara_closed_set::ClosedSet::label) rather
799/// than the inherent `.as_str()` each site publishes locally. Every
800/// production implementor here derives its `label` body from `as_str`
801/// via `#[closed_set(via = "as_str", display)]` (the substrate-wide
802/// derive shape), so the two are byte-identical by construction; the
803/// primitive's projection through `label` therefore pins the SAME
804/// invariant the pre-lift bodies pinned while binding to the
805/// stable trait-visible surface. A future implementor whose inherent
806/// canonical projection is named something other than `as_str` (e.g.
807/// `.keyword()`, `.spelling()`) but still routes through
808/// `#[closed_set(via = "...", display)]` picks up the alignment check
809/// through ONE `assert_display_matches_label::<X>()` invocation with
810/// no inherent-name coupling at the test site.
811///
812/// A fifth (or thirtieth, or hundredth) implementor picks up the
813/// Display-alignment check through ONE
814/// `#[derive(tatara_closed_set::DeriveClosedSet)]` + `display`
815/// attribute + ONE `assert_display_matches_label::<X>()` call site —
816/// no re-authored two-line
817/// `for v in K::ALL { assert_eq!(v.to_string(), v.as_str()) }` body
818/// at the test surface, no per-site drift risk where 28 sibling
819/// tests carry the assertion and the 29th forgets.
820///
821/// Sibling shape to [`assert_kind_list_matches_closed_set`] on the
822/// (`T::KIND_LIST` slash-join, `Display` byte-identity) axis: both
823/// project the closed-set's label surface onto ONE typed contract
824/// and pin it against a per-implementor rendering; the former for
825/// the tagged-union parent's [`TaggedUnion::KIND_LIST`] `&'static str`,
826/// this one for the enum's `Display` byte stream. Together they close
827/// the "label surface must round-trip verbatim" invariant every
828/// closed-set-carrying implementor across the crate publishes.
829#[track_caller]
830pub fn assert_display_matches_label<T>()
831where
832 T: tatara_closed_set::ClosedSet + core::fmt::Display + PartialEq + core::fmt::Debug,
833{
834 let type_name = core::any::type_name::<T>();
835 for &v in <T as tatara_closed_set::ClosedSet>::ALL {
836 let rendered = v.to_string();
837 let expected = <T as tatara_closed_set::ClosedSet>::label(v);
838 assert_eq!(
839 rendered.as_str(),
840 expected,
841 "{type_name}: Display drifted from ClosedSet::label for {v:?} — expected {expected:?}, got {rendered:?}",
842 );
843 }
844}
845
846/// CANONICAL-KEY CONTRACT testkit — pins that each variant's serde
847/// serialization (as a JSON string value, unquoted) matches its
848/// canonical [`ClosedSet::label`](tatara_closed_set::ClosedSet::label)
849/// projection BYTE-IDENTICALLY for every implementor.
850///
851/// Substrate primitive for the 20 sibling
852/// `X_as_str_matches_serde` tests across `tatara-process`
853/// (`TeardownPolicy`, `EncapsulationMode`, `ConditionKind`,
854/// `SighupStrategy`, `ReplacementPolicy`, `ReturnPolicy`, `MemberState`,
855/// `PoolPhase`, `VerificationPhase`, `MustReachPhase`, `WorkloadKind`,
856/// `ExportTrigger`, `ReportFormat`, `DataClassification`,
857/// `ConvergencePointType`, `SubstrateType`, `CalmClassification`,
858/// `OptimizationDirection`, `HorizonKind`, `AllocationPhase`) that
859/// pre-lift each restated the same
860/// ```text
861/// for v in K::ALL {
862/// let serialized = serde_json::to_string(&v).expect("serialize");
863/// let unquoted = serialized
864/// .trim_start_matches('"')
865/// .trim_end_matches('"')
866/// .to_string();
867/// assert_eq!(unquoted, v.as_str(), "as_str drift for {v:?}: ...");
868/// }
869/// ```
870/// four-line probe verbatim at their own test bodies — byte-identical
871/// projections whose only per-carrier knob is the closed-set type name.
872/// Post-lift each site collapses to ONE
873/// `assert_label_matches_serde_serialization::<X>()` invocation whose
874/// body IS the substrate primitive's own dispatch.
875///
876/// The primitive projects through the STABLE trait-visible name
877/// [`ClosedSet::label`](tatara_closed_set::ClosedSet::label) rather
878/// than the inherent `.as_str()` each site publishes locally. Every
879/// production implementor here derives its `label` body from `as_str`
880/// via `#[closed_set(via = "as_str", display)]` + `#[serde(rename_all
881/// = "PascalCase")]` (the substrate-wide derive shape), so the two are
882/// byte-identical by construction; the primitive's projection through
883/// `label` therefore pins the SAME invariant the pre-lift bodies
884/// pinned while binding to the stable trait-visible surface. A future
885/// implementor whose canonical inherent projection is named something
886/// other than `as_str` (e.g. `.keyword()`, `.spelling()`) but still
887/// routes through `#[closed_set(via = "...")]` picks up the wire-format
888/// alignment check through ONE call with no inherent-name coupling at
889/// the test site.
890///
891/// A twenty-first (or hundredth) implementor picks up the alignment
892/// check through ONE `#[derive(tatara_closed_set::DeriveClosedSet)]` +
893/// `#[derive(serde::Serialize)]` + `#[serde(rename_all = "...")]`
894/// attribute + ONE `assert_label_matches_serde_serialization::<X>()`
895/// call site — no re-authored four-line probe body at the test surface,
896/// no per-site drift risk where 19 sibling tests carry the assertion
897/// and the 20th forgets, no `serde_json::to_string`+`trim_matches`+
898/// `assert_eq!` composition re-derived per implementor.
899///
900/// Sibling shape to [`assert_display_matches_label`] on the
901/// (Display byte-identity, serde-wire-format byte-identity) axis: both
902/// project the closed-set's label surface onto ONE typed contract and
903/// pin it against a per-implementor rendering; the former for the
904/// enum's [`Display`](core::fmt::Display) byte stream, this one for
905/// the serde JSON-string wire format. Together they close the "label
906/// surface renders verbatim across every projection consumers reach
907/// for" invariant every closed-set-carrying implementor across the
908/// crate publishes.
909#[track_caller]
910pub fn assert_label_matches_serde_serialization<T>()
911where
912 T: tatara_closed_set::ClosedSet + serde::Serialize + core::fmt::Debug,
913{
914 let type_name = core::any::type_name::<T>();
915 for &v in <T as tatara_closed_set::ClosedSet>::ALL {
916 let serialized = serde_json::to_string(&v).unwrap_or_else(|e| {
917 panic!("{type_name}: closed-set variant {v:?} must serialize: {e}")
918 });
919 let unquoted = serialized.trim_start_matches('"').trim_end_matches('"');
920 let expected = <T as tatara_closed_set::ClosedSet>::label(v);
921 assert_eq!(
922 unquoted,
923 expected,
924 "{type_name}: serde output drifted from ClosedSet::label for {v:?} — expected {expected:?}, got {unquoted:?} (full serialization {serialized:?})",
925 );
926 }
927}
928
929/// CLOSED-SET CONVENTION PANEL testkit — pins the FULL three-axis
930/// label-surface convention (parse round-trip, Display byte-identity,
931/// serde-JSON-string byte-identity) at ONE substrate call site per
932/// implementor.
933///
934/// Compound-lift of [`tatara_closed_set::assert_closed_set_well_formed`]
935/// + [`assert_display_matches_label`] + [`assert_label_matches_serde_
936/// serialization`] — every closed-set enum on `ProcessSpec` that
937/// carries the substrate-wide `#[derive(DeriveClosedSet)] +
938/// #[derive(Serialize)] + #[closed_set(via = "as_str", display)] +
939/// #[serde(rename_all = "PascalCase")]` shape publishes ALL THREE
940/// axes of the label surface, and pre-lift each production test
941/// module hand-authored three sibling one-line tests
942/// (`X_is_well_formed_closed_set`, `X_display_matches_as_str`,
943/// `X_as_str_matches_serde`) that each restated the SAME
944/// `crate::tagged_union::assert_<axis>::<X>()` invocation with only
945/// the axis name varying between siblings. Post-lift each site
946/// collapses to ONE `assert_closed_set_convention_panel::<X>()`
947/// invocation whose body IS the three-axis composition dispatched
948/// through the substrate primitive here.
949///
950/// The three sub-assertions stay independently callable — a future
951/// implementor that publishes only two of the three axes (a
952/// `Display`-less internal enum, e.g., or a `Serialize`-less
953/// runtime-only enum) still binds through the two sibling primitives
954/// individually. The compound is a strict superset: any implementor
955/// that satisfies the compound's bounds already satisfies each
956/// sub-assertion's bounds by construction, and the failure mode of
957/// each sub-assertion still surfaces with the exact-message
958/// granularity `#[track_caller]` gives the individual primitives
959/// (the compound is `#[track_caller]` too, so a sub-assertion panic
960/// surfaces at the compound's call site — a future promotion could
961/// wrap each sub-assertion in a `std::panic::catch_unwind` to
962/// aggregate all three axis failures into ONE panic message, but the
963/// pre-lift discipline is that each axis's failure surfaces with its
964/// own diagnostic).
965///
966/// The compound's bounds are the strict union of the three sub-
967/// assertions' bounds:
968/// - [`assert_closed_set_well_formed`] requires
969/// `T: ClosedSet + PartialEq + Debug` + `T::Unknown: Display`;
970/// - [`assert_display_matches_label`] requires
971/// `T: ClosedSet + Display + PartialEq + Debug`;
972/// - [`assert_label_matches_serde_serialization`] requires
973/// `T: ClosedSet + Serialize + Debug`.
974/// The union `T: ClosedSet + Serialize + Display + PartialEq + Debug`
975/// + `T::Unknown: Display` is what every 3-axis production consumer
976/// already satisfies through the substrate-wide derive shape — any
977/// implementor that fails the compound's bounds would ALSO fail the
978/// individual sub-assertions' bounds, so the compound doesn't shrink
979/// the reachable set of implementors relative to hand-authoring the
980/// three sibling calls.
981///
982/// A future FOURTH label-surface projection (e.g. a `serde_yaml`
983/// byte-identity axis if the crate gains a YAML wire form on closed-
984/// set enums, or a `kubectl_annotation` axis if the reconciler grows
985/// an annotation-carried label surface) lands as ONE new
986/// `assert_<axis>_matches_label::<T>()` substrate primitive + ONE
987/// new line inside this compound's body. Every one of the ~20
988/// production implementors of the panel picks up the fourth-axis
989/// alignment check mechanically at their sole `assert_closed_set_
990/// convention_panel::<X>()` call site — no per-implementor test-site
991/// authoring, no per-crate test-site drop pathway where 19 sibling
992/// call sites carry the check and the 20th forgets. The exact
993/// promise `e4a4eba`'s future gain #2 named after
994/// `assert_label_matches_serde_serialization` opened the wire-format
995/// axis: a workspace-wide panel with byte-identical calling shapes
996/// (`assert_X::<T>()`) that composes as freely as its sub-primitives.
997///
998/// Sibling shape to [`assert_variant_round_trip`] +
999/// [`assert_kind_list_matches_closed_set`] +
1000/// [`assert_two_slots_ambiguous`] +
1001/// [`assert_single_slot_key_matches_label`] on the tagged-union
1002/// PARENT axis: the parent-side compound would compose the four
1003/// parent-side per-axis primitives, this one composes the three
1004/// child-side per-axis primitives on the child's [`ClosedSet`]
1005/// surface. Together the two compounds close the "closed-set
1006/// convention holds across every projection consumers reach for" at
1007/// two adjacent panels — one per closed-set-carrying enum, one per
1008/// tagged-union parent.
1009///
1010/// Theory anchor: THEORY.md §V.1 (knowable platform) — the
1011/// three-axis label-surface convention becomes ONE typed theorem
1012/// provable generically over any
1013/// `T: ClosedSet + Serialize + Display + PartialEq + Debug` bound
1014/// rather than THREE hand-authored per-implementor one-line probes
1015/// held coherent by test-module convention. THEORY.md §II.1
1016/// invariant 5 (composition preserves proofs) — the three sub-
1017/// assertions compose structurally through ONE primitive here, so a
1018/// regression at ONE axis surfaces at the sub-assertion's own
1019/// panic message rather than as silent drift at every consumer that
1020/// might otherwise forget to include the axis in its per-site
1021/// author-time enumeration.
1022#[track_caller]
1023pub fn assert_closed_set_convention_panel<T>()
1024where
1025 T: tatara_closed_set::ClosedSet
1026 + serde::Serialize
1027 + core::fmt::Display
1028 + PartialEq
1029 + core::fmt::Debug,
1030 T::Unknown: core::fmt::Display,
1031{
1032 tatara_closed_set::assert_closed_set_well_formed::<T>();
1033 assert_display_matches_label::<T>();
1034 assert_label_matches_serde_serialization::<T>();
1035}
1036
1037/// TAGGED-UNION CONVENTION PANEL testkit — pins the FULL four-axis
1038/// tagged-union parent convention (KIND_LIST diagnostic-stability,
1039/// variant round-trip on the single-slot side, ALL×ALL two-slot
1040/// ambiguity, wire-key alignment on the single-slot side) at ONE
1041/// substrate call site per parent.
1042///
1043/// Parent-side compound-lift, sibling to
1044/// [`assert_closed_set_convention_panel`] on the child's
1045/// [`tatara_closed_set::ClosedSet`] axis. Composes
1046/// [`assert_kind_list_matches_closed_set`] (no fixture) +
1047/// [`assert_variant_round_trip`] (`single_slot`) +
1048/// [`assert_two_slots_ambiguous`] (`two_slot`) +
1049/// [`assert_single_slot_key_matches_label`] (`single_slot`).
1050///
1051/// Every one of the four production `.variant()` parents on
1052/// `ProcessSpec` ([`crate::intent::Intent`],
1053/// [`crate::encapsulates::EncapsulationKind`],
1054/// [`crate::export::ArtifactSource`],
1055/// [`crate::export::VectorChannel`]) publishes the four-axis
1056/// convention through the shared substrate-wide attribute-set:
1057/// `#[derive(DeriveClosedSet)]` on the addressing `Kind`,
1058/// `declare_tagged_union_impls!` for the resolver+selector+trait
1059/// triple, `#[serde(rename_all = "camelCase")]` +
1060/// `#[serde(default, skip_serializing_if = "Option::is_none")]` on
1061/// every tagged-union slot. Pre-lift each production site
1062/// hand-authored FOUR sibling per-axis tests (`X_kind_round_trips_through_variant_kind`
1063/// / `X_kind_list_matches_ClosedSet_labels` /
1064/// `X_two_slots_are_ambiguous` /
1065/// `X_kind_as_str_matches_field_name`) that each restated the
1066/// SAME `crate::tagged_union::assert_<axis>::<T, _>(fixture)`
1067/// invocation with only the axis name + fixture arity varying
1068/// between siblings. Post-lift each site's four per-axis sibling
1069/// tests can collapse to ONE
1070/// `assert_tagged_union_convention_panel::<T, _, _>(
1071/// single_slot_X, two_slot_X)` invocation whose body IS the
1072/// four-axis composition dispatched through the substrate
1073/// primitive here.
1074///
1075/// The two closures stay per-site — every one of the four
1076/// production parents already owns a `single_slot_X(k) -> Parent`
1077/// / `two_slot_X(a, b) -> Parent` pair, and the substrate-local
1078/// `{single,two}_slot_*_probe` peers (siblings to the wire-key
1079/// sweep's substrate-local probes) let the substrate-wide sweep
1080/// below bind through the compound without reaching across the
1081/// per-crate test-module boundaries. Lifting the two closures
1082/// into the primitive would collapse the per-site construction
1083/// knowledge that stays deliberately local — the closure IS the
1084/// "populate slot k" / "populate the (a, b) pair" ground truth
1085/// for the parent's field structure.
1086///
1087/// Bounds are the strict union of the four sub-assertions' bounds:
1088/// [`assert_kind_list_matches_closed_set`] requires
1089/// `T: TaggedUnion`; [`assert_variant_round_trip`] requires
1090/// `T: TaggedUnion` + `T::Kind: PartialEq + Debug`
1091/// + `F: Fn(T::Kind) -> T`; [`assert_two_slots_ambiguous`] requires
1092/// `T: TaggedUnion` + `T::Kind: PartialEq + Debug`
1093/// + `T::Error: PartialEq + Debug` + `F: Fn(T::Kind, T::Kind) -> T`;
1094/// [`assert_single_slot_key_matches_label`] requires
1095/// `T: TaggedUnion + Serialize` + `T::Kind: PartialEq + Debug`
1096/// + `F: Fn(T::Kind) -> T`. The union
1097/// `T: TaggedUnion + Serialize` + `T::Kind: PartialEq + Debug`
1098/// + `T::Error: PartialEq + Debug` + `F1: Fn(T::Kind) -> T`
1099/// + `F2: Fn(T::Kind, T::Kind) -> T` is what every one of the four
1100/// production parents already satisfies through the shared
1101/// substrate-wide impls — any implementor that fails the compound's
1102/// bounds would ALSO fail the individual sub-assertions' bounds,
1103/// so the compound doesn't shrink the reachable set of
1104/// implementors relative to hand-authoring the four sibling calls.
1105/// The `single_slot` closure is dispatched to
1106/// [`assert_variant_round_trip`] by reference so the compound can
1107/// re-dispatch it to [`assert_single_slot_key_matches_label`] by
1108/// value on the final call — a caller passes ONE `Fn(T::Kind) -> T`
1109/// factory (not `FnOnce`) at the two axes that need it.
1110///
1111/// `#[track_caller]` on both the compound and each sub-primitive,
1112/// so a sub-assertion panic surfaces at the compound's caller site
1113/// with the failing axis's exact panic-message substring
1114/// (e.g. "TaggedUnion KIND_LIST drift", "select→variant_kind
1115/// round-trip failed", "should resolve Ambiguous", "wire-key
1116/// drift"). The four sub-assertions stay independently callable —
1117/// a future parent that publishes only three of the four axes (a
1118/// wire-format-less runtime parent, e.g., or an
1119/// ambiguity-less parent whose `.variant()` short-circuits on
1120/// the first populated slot) still binds through the sibling
1121/// primitives individually.
1122///
1123/// A future FIFTH parent-side projection (e.g. a
1124/// `two_slots_have_stable_diagnostic` axis if the ambiguity error
1125/// gains a per-parent operator-facing message, or a
1126/// `variant_kind_stays_stable_across_generation` axis if the
1127/// resolver's iteration order becomes load-bearing) lands as ONE
1128/// new `assert_<axis>::<T, _>(...)` substrate primitive + ONE new
1129/// line inside this compound's body. Every one of the four
1130/// production parents picks up the fifth-axis alignment check
1131/// mechanically at their sole
1132/// `assert_tagged_union_convention_panel::<T, _, _>(single_slot,
1133/// two_slot)` call site — no per-parent test-site authoring, no
1134/// per-crate test-site drop pathway where 3 sibling call sites
1135/// carry the check and the 4th forgets. The exact promise the
1136/// child-side [`assert_closed_set_convention_panel`] compound's
1137/// docstring named on the child axis, extended here to the parent
1138/// axis: a workspace-wide panel with byte-identical calling shapes
1139/// (`assert_<compound>::<T, _, _>(single_slot, two_slot)`) that
1140/// composes as freely as its sub-primitives.
1141///
1142/// The [`crate::lifetime::Lifetime`] site is DELIBERATELY excluded
1143/// through the `T: TaggedUnion` bound — `Lifetime`'s `variant()`
1144/// returns `Ok(Permanent)` on empty rather than an `Empty` typed
1145/// error, so its projection shape diverges from the four
1146/// Empty-projecting parents. Same reasoning as [`resolve_or_err`]'s
1147/// / [`assert_variant_round_trip`]'s / [`assert_two_slots_ambiguous`]'s
1148/// / [`assert_single_slot_key_matches_label`]'s exclusions.
1149///
1150/// Theory anchor: THEORY.md §V.1 (knowable platform) — the
1151/// four-axis parent-side tagged-union convention becomes ONE typed
1152/// theorem provable generically over any
1153/// `T: TaggedUnion + Serialize` bound rather than FOUR
1154/// hand-authored per-parent tests held coherent by test-module
1155/// convention. THEORY.md §II.1 invariant 5 (composition preserves
1156/// proofs) — the four sub-assertions compose structurally through
1157/// ONE primitive here, so a regression at ONE axis surfaces at the
1158/// sub-assertion's own panic message rather than as silent drift
1159/// at every parent that might otherwise forget to include the
1160/// axis in its per-site author-time enumeration.
1161#[track_caller]
1162pub fn assert_tagged_union_convention_panel<T, F1, F2>(single_slot: F1, two_slot: F2)
1163where
1164 T: TaggedUnion + serde::Serialize,
1165 T::Kind: PartialEq + std::fmt::Debug,
1166 T::Error: PartialEq + std::fmt::Debug,
1167 F1: Fn(T::Kind) -> T,
1168 F2: Fn(T::Kind, T::Kind) -> T,
1169{
1170 assert_kind_list_matches_closed_set::<T>();
1171 assert_variant_round_trip::<T, _>(&single_slot);
1172 assert_two_slots_ambiguous::<T, _>(two_slot);
1173 assert_single_slot_key_matches_label::<T, _>(single_slot);
1174}
1175
1176#[cfg(test)]
1177mod tests {
1178 use super::*;
1179
1180 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
1181 enum V {
1182 A,
1183 B,
1184 C,
1185 }
1186
1187 #[test]
1188 fn empty_candidate_list_is_none() {
1189 let r: Result<V, _> = resolve(std::iter::empty());
1190 assert_eq!(r.unwrap_err(), ResolveError::None);
1191 }
1192
1193 #[test]
1194 fn all_none_is_none() {
1195 let r: Result<V, _> = resolve([None, None, None]);
1196 assert_eq!(r.unwrap_err(), ResolveError::None);
1197 }
1198
1199 #[test]
1200 fn single_some_is_resolved_regardless_of_position() {
1201 assert_eq!(resolve([Some(V::A), None, None]).unwrap(), V::A);
1202 assert_eq!(resolve([None, Some(V::B), None]).unwrap(), V::B);
1203 assert_eq!(resolve([None, None, Some(V::C)]).unwrap(), V::C);
1204 }
1205
1206 #[test]
1207 fn two_or_more_some_is_many() {
1208 assert_eq!(
1209 resolve([Some(V::A), Some(V::B), None]).unwrap_err(),
1210 ResolveError::Many
1211 );
1212 assert_eq!(
1213 resolve([Some(V::A), None, Some(V::C)]).unwrap_err(),
1214 ResolveError::Many
1215 );
1216 assert_eq!(
1217 resolve([None, Some(V::B), Some(V::C)]).unwrap_err(),
1218 ResolveError::Many
1219 );
1220 assert_eq!(
1221 resolve([Some(V::A), Some(V::B), Some(V::C)]).unwrap_err(),
1222 ResolveError::Many
1223 );
1224 }
1225
1226 /// Short-circuit invariant: once `Many` is decided, the sweep does
1227 /// NOT inspect further candidates. Encode it as a side-effect probe.
1228 #[test]
1229 fn many_short_circuits_after_second_some() {
1230 let mut visited = 0usize;
1231 let candidates = (0..4).map(|i| {
1232 visited += 1;
1233 // first two are Some, the rest would be Some too if we got there.
1234 Some(i)
1235 });
1236 // We can't actually consume `visited` here because it's borrowed in
1237 // the closure — fold the count via the resolver's short-circuit.
1238 let _ = resolve(candidates);
1239 // The resolver evaluates the iterator lazily up to the second
1240 // Some — index 0 (found = Some(0)), index 1 (Many → return).
1241 assert_eq!(visited, 2);
1242 }
1243
1244 /// The helper is value-agnostic — works with borrowed enum-view
1245 /// types matching the actual on-the-typescape callsites.
1246 #[test]
1247 fn works_with_borrowed_enum_view() {
1248 #[derive(Debug, PartialEq)]
1249 enum View<'a> {
1250 X(&'a u32),
1251 Y(&'a String),
1252 }
1253 let x = 7u32;
1254 let r = resolve([Some(View::X(&x)), None]).unwrap();
1255 assert_eq!(r, View::X(&7));
1256 }
1257
1258 /// Local sibling-shaped carrier used to pin the trait +
1259 /// [`resolve_or_err`] dispatch without depending on the
1260 /// crate's real error types.
1261 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
1262 enum E {
1263 Empty(&'static str),
1264 Ambiguous,
1265 }
1266
1267 impl TaggedUnionError for E {
1268 fn empty(kinds: &'static str) -> Self {
1269 E::Empty(kinds)
1270 }
1271 fn ambiguous() -> Self {
1272 E::Ambiguous
1273 }
1274 }
1275
1276 /// Four-outcome truth table at the compound-lift boundary.
1277 /// Pins that the two failure arms of [`resolve`] project onto
1278 /// the trait's two typed constructors byte-identically, and
1279 /// that the Ok arm falls through untouched.
1280 #[test]
1281 fn resolve_or_err_dispatches_each_arm_through_the_trait() {
1282 const KINDS: &str = "a/b/c";
1283
1284 assert_eq!(
1285 resolve_or_err::<V, E>([Some(V::A), None, None], KINDS).unwrap(),
1286 V::A
1287 );
1288 assert_eq!(
1289 resolve_or_err::<V, E>([None, Some(V::B), None], KINDS).unwrap(),
1290 V::B
1291 );
1292
1293 assert_eq!(
1294 resolve_or_err::<V, E>([None, None, None], KINDS).unwrap_err(),
1295 E::Empty(KINDS)
1296 );
1297
1298 assert_eq!(
1299 resolve_or_err::<V, E>([Some(V::A), Some(V::B), None], KINDS).unwrap_err(),
1300 E::Ambiguous
1301 );
1302 }
1303
1304 /// The trait's Empty arm carries the &'static str the caller
1305 /// hands `resolve_or_err`, verbatim — a rename at the caller's
1306 /// `KINDS` constant reaches the diagnostic surface intact.
1307 #[test]
1308 fn resolve_or_err_empty_carries_the_caller_kinds_verbatim() {
1309 const KINDS_ALPHA: &str = "alpha/beta";
1310 const KINDS_GAMMA: &str = "gamma/delta/epsilon";
1311
1312 assert_eq!(
1313 resolve_or_err::<V, E>([None, None], KINDS_ALPHA).unwrap_err(),
1314 E::Empty(KINDS_ALPHA)
1315 );
1316 assert_eq!(
1317 resolve_or_err::<V, E>([None, None, None], KINDS_GAMMA).unwrap_err(),
1318 E::Empty(KINDS_GAMMA)
1319 );
1320 }
1321
1322 /// The compound-lift preserves [`resolve`]'s short-circuit at
1323 /// the Many arm — a third-and-later candidate is not
1324 /// inspected once the second populated entry is seen.
1325 #[test]
1326 fn resolve_or_err_short_circuits_on_many() {
1327 let mut visited = 0usize;
1328 let candidates = (0..4).map(|i| {
1329 visited += 1;
1330 Some(i)
1331 });
1332 let _ = resolve_or_err::<i32, E>(candidates, "irrelevant");
1333 assert_eq!(visited, 2);
1334 }
1335
1336 // -------------------------------------------------------------------
1337 // `declare_tagged_union_error!` macro-emitted carrier — pins the
1338 // shape a fifth sibling would land through the macro instead of
1339 // hand-rolling the enum + `impl TaggedUnionError` block.
1340 // -------------------------------------------------------------------
1341
1342 crate::declare_tagged_union_error! {
1343 pub(super) MacroEmittedError,
1344 empty = "test carrier has no variant set (one of {0} required)",
1345 ambiguous = "test carrier has multiple variants set; exactly one required",
1346 }
1347
1348 /// The macro-emitted carrier's [`TaggedUnionError`] impl dispatches
1349 /// the same four-outcome truth table [`resolve_or_err`] pins for a
1350 /// hand-rolled carrier — pins that swapping a hand-rolled carrier
1351 /// for a macro-emitted one preserves the compound-lift's projection
1352 /// byte-identically.
1353 #[test]
1354 fn macro_emitted_carrier_projects_through_resolve_or_err() {
1355 const KINDS: &str = "one/two/three";
1356
1357 assert_eq!(
1358 resolve_or_err::<V, MacroEmittedError>([Some(V::A), None, None], KINDS).unwrap(),
1359 V::A
1360 );
1361 assert_eq!(
1362 resolve_or_err::<V, MacroEmittedError>([None, None, None], KINDS).unwrap_err(),
1363 MacroEmittedError::Empty(KINDS)
1364 );
1365 assert_eq!(
1366 resolve_or_err::<V, MacroEmittedError>([Some(V::A), Some(V::B), None], KINDS)
1367 .unwrap_err(),
1368 MacroEmittedError::Ambiguous
1369 );
1370 }
1371
1372 /// The macro-emitted carrier's `#[error(...)]` messages render the
1373 /// two operator-facing diagnostic strings the caller handed the
1374 /// macro, verbatim — a rename at the caller's literal reaches the
1375 /// operator diagnostic surface intact.
1376 #[test]
1377 fn macro_emitted_carrier_display_renders_caller_literals_verbatim() {
1378 assert_eq!(
1379 MacroEmittedError::Empty("alpha/beta").to_string(),
1380 "test carrier has no variant set (one of alpha/beta required)",
1381 );
1382 assert_eq!(
1383 MacroEmittedError::Ambiguous.to_string(),
1384 "test carrier has multiple variants set; exactly one required",
1385 );
1386 }
1387
1388 /// The macro-emitted carrier is `Copy` — a substrate-wide promise
1389 /// pinned by the macro's `#[derive(..., Copy, ...)]` header so a
1390 /// consumer treating the carrier as a value type (memcpy-cheap
1391 /// return, `.copied()` on an `Option<&E>`) stays valid across every
1392 /// carrier the macro emits.
1393 #[test]
1394 fn macro_emitted_carrier_is_copy() {
1395 fn assert_copy<T: Copy>() {}
1396 assert_copy::<MacroEmittedError>();
1397 }
1398
1399 // -------------------------------------------------------------------
1400 // `TaggedUnion` trait — declarative surface pinning the
1401 // (Kind, Error, KIND_LIST) triple. `assert_kind_list_matches_closed_set`
1402 // is the generic diagnostic-stability testkit primitive shared by
1403 // every implementor's `_error_empty_lists_every_kind_in_canonical_order`
1404 // site.
1405 // -------------------------------------------------------------------
1406
1407 /// Local sibling-shaped Kind enum used to pin the trait's
1408 /// diagnostic-stability primitive without depending on the crate's
1409 /// four production tagged unions. Uses [`tatara_closed_set::DeriveClosedSet`]
1410 /// so `<Self as ClosedSet>::labels_joined("/")` reaches the same
1411 /// substrate composition the four production sites bind through.
1412 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, tatara_closed_set::DeriveClosedSet)]
1413 #[closed_set(via = "as_str", generate_unknown, display)]
1414 enum LocalKind {
1415 Alpha,
1416 Beta,
1417 Gamma,
1418 }
1419
1420 impl LocalKind {
1421 const ALL: [Self; 3] = [Self::Alpha, Self::Beta, Self::Gamma];
1422 const fn as_str(self) -> &'static str {
1423 match self {
1424 Self::Alpha => "alpha",
1425 Self::Beta => "beta",
1426 Self::Gamma => "gamma",
1427 }
1428 }
1429 }
1430
1431 /// Local parent type — impls [`TaggedUnion`] with a `KIND_LIST`
1432 /// literal that matches the canonical `<LocalKind as
1433 /// ClosedSet>::labels_joined("/")` projection. Carries three
1434 /// `Option<u32>` slots so the substrate-primitive
1435 /// [`TaggedUnion::variant`] default method can be exercised
1436 /// directly on a sibling-shaped-but-crate-local parent, isolated
1437 /// from the four production tagged unions.
1438 ///
1439 /// Derives [`serde::Serialize`] with `skip_serializing_if =
1440 /// "Option::is_none"` on every slot so the wire-format primitive
1441 /// [`assert_single_slot_key_matches_label`] can be exercised
1442 /// directly against the sibling-shaped scaffold — mirrors the
1443 /// `#[serde(default, skip_serializing_if = "Option::is_none")]`
1444 /// annotation every one of the four production tagged unions
1445 /// carries on its own slots.
1446 #[derive(Default, serde::Serialize)]
1447 struct LocalParent {
1448 #[serde(skip_serializing_if = "Option::is_none")]
1449 alpha: Option<u32>,
1450 #[serde(skip_serializing_if = "Option::is_none")]
1451 beta: Option<u32>,
1452 #[serde(skip_serializing_if = "Option::is_none")]
1453 gamma: Option<u32>,
1454 }
1455
1456 /// Borrowed-view of a populated slot on [`LocalParent`] — the
1457 /// return type of [`LocalKind::select`] and the substrate-primitive
1458 /// [`TaggedUnion::variant`] default on `LocalParent`.
1459 #[derive(Debug, PartialEq)]
1460 enum LocalVariant<'a> {
1461 Alpha(&'a u32),
1462 Beta(&'a u32),
1463 Gamma(&'a u32),
1464 }
1465
1466 impl VariantSelector<LocalParent> for LocalKind {
1467 type Variant<'a> = LocalVariant<'a>;
1468 fn select<'a>(self, parent: &'a LocalParent) -> Option<LocalVariant<'a>>
1469 where
1470 Self: 'a,
1471 {
1472 match self {
1473 Self::Alpha => parent.alpha.as_ref().map(LocalVariant::Alpha),
1474 Self::Beta => parent.beta.as_ref().map(LocalVariant::Beta),
1475 Self::Gamma => parent.gamma.as_ref().map(LocalVariant::Gamma),
1476 }
1477 }
1478 }
1479
1480 impl VariantKind<LocalKind> for LocalVariant<'_> {
1481 fn variant_kind(&self) -> LocalKind {
1482 match self {
1483 Self::Alpha(_) => LocalKind::Alpha,
1484 Self::Beta(_) => LocalKind::Beta,
1485 Self::Gamma(_) => LocalKind::Gamma,
1486 }
1487 }
1488 }
1489
1490 crate::declare_tagged_union_error! {
1491 pub(super) LocalParentError,
1492 empty = "local carrier has no variant set (one of {0} required)",
1493 ambiguous = "local carrier has multiple variants set; exactly one required",
1494 }
1495
1496 impl TaggedUnion for LocalParent {
1497 type Kind = LocalKind;
1498 type Error = LocalParentError;
1499 const KIND_LIST: &'static str = "alpha/beta/gamma";
1500 }
1501
1502 /// The testkit primitive resolves the canonical join of every
1503 /// `LocalKind` variant's label against the trait's `KIND_LIST`
1504 /// constant byte-identically — the four production sites bind
1505 /// through this exact dispatch. The Ok arm is the "no drift"
1506 /// outcome; a divergence surfaces as a labeled assertion failure.
1507 #[test]
1508 fn assert_kind_list_matches_closed_set_accepts_coherent_impl() {
1509 assert_kind_list_matches_closed_set::<LocalParent>();
1510 }
1511
1512 /// The testkit primitive is a `#[track_caller]` compound-lift:
1513 /// a drift between `<T::Kind as ClosedSet>::labels_joined("/")`
1514 /// and `T::KIND_LIST` fails the assertion at the caller's site,
1515 /// not inside the primitive body. Pin the failing case with a
1516 /// local parent whose `KIND_LIST` is deliberately mis-authored
1517 /// (a variant reorder), so a regression that drops the drift
1518 /// detection fails-loudly here.
1519 #[test]
1520 #[should_panic(expected = "TaggedUnion KIND_LIST drift")]
1521 fn assert_kind_list_matches_closed_set_rejects_drifted_impl() {
1522 struct Drifted;
1523 // The `TaggedUnion` trait bounds `Kind: VariantSelector<Self>`
1524 // with `Variant<'a>: VariantKind<Self>`; the drift test only
1525 // exercises `assert_kind_list_matches_closed_set` (which reaches
1526 // the (Kind, KIND_LIST) pair, not the sweep body), so reusing
1527 // the sibling `LocalVariant<'a>` (with its already-load-bearing
1528 // `impl VariantKind<LocalKind>`) + always-`None` `select`
1529 // satisfies both bounds without wiring a real projection.
1530 impl VariantSelector<Drifted> for LocalKind {
1531 type Variant<'a> = LocalVariant<'a>;
1532 fn select<'a>(self, _: &'a Drifted) -> Option<LocalVariant<'a>>
1533 where
1534 Self: 'a,
1535 {
1536 None
1537 }
1538 }
1539 impl TaggedUnion for Drifted {
1540 type Kind = LocalKind;
1541 type Error = LocalParentError;
1542 // Deliberate drift — canonical join is "alpha/beta/gamma".
1543 const KIND_LIST: &'static str = "beta/alpha/gamma";
1544 }
1545 assert_kind_list_matches_closed_set::<Drifted>();
1546 }
1547
1548 /// Every one of the four production `.variant()` sites on
1549 /// `ProcessSpec` impls [`TaggedUnion`] with `KIND_LIST` reaching
1550 /// the substrate primitive `assert_kind_list_matches_closed_set`
1551 /// coherently. Sweep every production implementor at ONE
1552 /// substrate boundary so a regression that drifts a production
1553 /// site's `KIND_LIST` (or renames a `Kind` variant without
1554 /// updating the constant) fails BOTH at the per-crate test site
1555 /// AND at this substrate-wide sweep — no per-implementor test
1556 /// site can drop the check silently.
1557 #[test]
1558 fn every_production_tagged_union_binds_through_the_testkit_primitive() {
1559 assert_kind_list_matches_closed_set::<crate::intent::Intent>();
1560 assert_kind_list_matches_closed_set::<crate::encapsulates::EncapsulationKind>();
1561 assert_kind_list_matches_closed_set::<crate::export::ArtifactSource>();
1562 assert_kind_list_matches_closed_set::<crate::export::VectorChannel>();
1563 }
1564
1565 /// Every one of the four production `.variant()` sites on
1566 /// `ProcessSpec` binds through the wire-key primitive
1567 /// [`assert_single_slot_key_matches_label`] coherently — every
1568 /// per-site `single_slot_X(k)` factory serializes to a JSON object
1569 /// with EXACTLY ONE key whose name equals `k.label()` (delegating
1570 /// to each Kind's inherent `as_str`, matching the parent's serde
1571 /// `rename_all = "camelCase"` projection). Sweep every production
1572 /// implementor at ONE substrate boundary so a regression that
1573 /// drifts a production site's `single_slot_X` factory (populates
1574 /// the wrong slot; leaks residual slots between calls) OR the
1575 /// parent's field-to-kind alignment (`as_str` returns "receipts"
1576 /// but the field is named `receipt`) fails BOTH at the per-crate
1577 /// test site AND at this substrate-wide sweep — no per-implementor
1578 /// test site can drop the check silently.
1579 #[test]
1580 fn every_production_tagged_union_binds_through_the_wire_key_testkit_primitive() {
1581 assert_single_slot_key_matches_label::<crate::intent::Intent, _>(single_slot_intent_probe);
1582 assert_single_slot_key_matches_label::<crate::encapsulates::EncapsulationKind, _>(
1583 single_slot_encapsulation_kind_probe,
1584 );
1585 assert_single_slot_key_matches_label::<crate::export::ArtifactSource, _>(
1586 single_slot_artifact_source_probe,
1587 );
1588 assert_single_slot_key_matches_label::<crate::export::VectorChannel, _>(
1589 single_slot_vector_channel_probe,
1590 );
1591 }
1592
1593 /// The parent-side four-axis compound-lift dispatches Ok on a
1594 /// coherent implementor — the [`LocalParent`] scaffold publishes
1595 /// every axis (`TaggedUnion` via
1596 /// [`crate::declare_tagged_union_error`]-emitted `LocalParentError`
1597 /// + Serialize via `#[derive(serde::Serialize)]` +
1598 /// `LocalKind: PartialEq + Debug` +
1599 /// `LocalParentError: PartialEq + Debug`), matching the
1600 /// substrate-wide four-axis convention every one of the four
1601 /// production parents carries. The Ok arm is the "no drift"
1602 /// outcome; a divergence at ANY sub-assertion's composition
1603 /// inside the compound (accidentally dropped, silently reordered,
1604 /// or short-circuited) surfaces at the sub-primitive's own
1605 /// panic message (each sub-primitive is `#[track_caller]`), and
1606 /// the per-axis failing arms are pinned by the sibling
1607 /// `#[should_panic]` probes already at the per-axis primitive
1608 /// layer (`assert_kind_list_matches_closed_set_rejects_drifted_impl`,
1609 /// `assert_variant_round_trip_rejects_factory_that_leaves_slot_empty`,
1610 /// `assert_two_slots_ambiguous_rejects_factory_that_populates_only_one_slot`,
1611 /// `assert_single_slot_key_matches_label_rejects_factory_that_populates_wrong_slot`).
1612 /// Re-authoring per-axis drift probes at the compound layer
1613 /// would restate the SAME four axis-typed contracts through a
1614 /// compound wrapper without adding a new gate.
1615 #[test]
1616 fn assert_tagged_union_convention_panel_accepts_coherent_local_impl() {
1617 fn single_slot(k: LocalKind) -> LocalParent {
1618 match k {
1619 LocalKind::Alpha => LocalParent {
1620 alpha: Some(11),
1621 ..Default::default()
1622 },
1623 LocalKind::Beta => LocalParent {
1624 beta: Some(22),
1625 ..Default::default()
1626 },
1627 LocalKind::Gamma => LocalParent {
1628 gamma: Some(33),
1629 ..Default::default()
1630 },
1631 }
1632 }
1633 fn two_slot(a: LocalKind, b: LocalKind) -> LocalParent {
1634 let mut p = LocalParent::default();
1635 for k in [a, b] {
1636 match k {
1637 LocalKind::Alpha => p.alpha = Some(11),
1638 LocalKind::Beta => p.beta = Some(22),
1639 LocalKind::Gamma => p.gamma = Some(33),
1640 }
1641 }
1642 p
1643 }
1644 assert_tagged_union_convention_panel::<LocalParent, _, _>(single_slot, two_slot);
1645 }
1646
1647 /// Every one of the four production `.variant()` parents on
1648 /// `ProcessSpec` binds through the four-axis convention-panel
1649 /// primitive [`assert_tagged_union_convention_panel`] coherently.
1650 /// Sweep every production parent at ONE substrate boundary so a
1651 /// regression that (a) drops ANY of the four sub-assertions from
1652 /// the compound's body, (b) reorders them in a way that skips
1653 /// one on Ok, (c) silently binds the compound against a
1654 /// hollowed-out sub-assertion body, or (d) drifts a substrate-
1655 /// local `{single,two}_slot_*_probe` fixture (populates the
1656 /// wrong slot; leaks residual slots between calls; the `.or()`
1657 /// composition drops a slot on the two-slot side) fails BOTH at
1658 /// the per-crate test site AND at this substrate-wide sweep.
1659 ///
1660 /// Pinned in lock-step with the sibling
1661 /// `every_production_tagged_union_binds_through_the_testkit_primitive`
1662 /// (KIND_LIST axis) and
1663 /// `every_production_tagged_union_binds_through_the_wire_key_testkit_primitive`
1664 /// (wire-key axis) sweeps — every parent enumerated below is a
1665 /// member of BOTH sibling sweeps (their bounds are strict
1666 /// subsets of the compound's `T: TaggedUnion + Serialize` +
1667 /// `T::Kind: PartialEq + Debug` + `T::Error: PartialEq + Debug`
1668 /// bound), and every parent additionally publishes both a
1669 /// substrate-local `single_slot_*_probe` and a
1670 /// substrate-local `two_slot_*_probe` peer above. Post-sweep the
1671 /// substrate-wide four-axis parent-side convention-panel
1672 /// discipline is a property of the workspace, not a per-file
1673 /// convention — even before any per-site test-body sweep
1674 /// collapses the four per-parent sibling tests into ONE compound
1675 /// call each.
1676 #[test]
1677 fn every_production_tagged_union_binds_through_the_convention_panel_testkit_primitive() {
1678 assert_tagged_union_convention_panel::<crate::intent::Intent, _, _>(
1679 single_slot_intent_probe,
1680 two_slot_intent_probe,
1681 );
1682 assert_tagged_union_convention_panel::<crate::encapsulates::EncapsulationKind, _, _>(
1683 single_slot_encapsulation_kind_probe,
1684 two_slot_encapsulation_kind_probe,
1685 );
1686 assert_tagged_union_convention_panel::<crate::export::ArtifactSource, _, _>(
1687 single_slot_artifact_source_probe,
1688 two_slot_artifact_source_probe,
1689 );
1690 assert_tagged_union_convention_panel::<crate::export::VectorChannel, _, _>(
1691 single_slot_vector_channel_probe,
1692 two_slot_vector_channel_probe,
1693 );
1694 }
1695
1696 /// The Display / label alignment primitive dispatches Ok on a
1697 /// coherent implementor — the [`LocalKind`] scaffold derives
1698 /// `Display` from `label` via `#[closed_set(via = "as_str",
1699 /// display)]`, matching the substrate-wide derive shape every
1700 /// production implementor across the crate carries. The Ok arm
1701 /// is the "no drift" outcome; a divergence surfaces as a labeled
1702 /// assertion failure at the caller site (this test's own line).
1703 #[test]
1704 fn assert_display_matches_label_accepts_coherent_impl() {
1705 assert_display_matches_label::<LocalKind>();
1706 }
1707
1708 /// A local closed-set scaffold whose `Display` deliberately
1709 /// diverges from `label` — pins the failing arm of the primitive.
1710 /// The `#[closed_set(via = "as_str")]` attribute WITHOUT `display`
1711 /// leaves the `Display` impl uncovered by the derive, and the
1712 /// hand-authored `impl Display` below emits a suffixed rendering
1713 /// that no `label` projection returns. A regression that drops
1714 /// the alignment assertion inside
1715 /// [`assert_display_matches_label`] fails-loudly at this
1716 /// `#[should_panic]` probe before it can silently thread through
1717 /// the 29 production `X_display_matches_as_str` sites.
1718 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, tatara_closed_set::DeriveClosedSet)]
1719 #[closed_set(via = "as_str", generate_unknown)]
1720 enum DisplayDriftKind {
1721 Alpha,
1722 Beta,
1723 }
1724
1725 impl DisplayDriftKind {
1726 const ALL: [Self; 2] = [Self::Alpha, Self::Beta];
1727 const fn as_str(self) -> &'static str {
1728 match self {
1729 Self::Alpha => "alpha",
1730 Self::Beta => "beta",
1731 }
1732 }
1733 }
1734
1735 impl std::fmt::Display for DisplayDriftKind {
1736 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1737 // Deliberate drift — Display suffixes the label with a
1738 // marker no `label` projection returns.
1739 write!(f, "{}!", self.as_str())
1740 }
1741 }
1742
1743 #[test]
1744 #[should_panic(expected = "Display drifted from ClosedSet::label")]
1745 fn assert_display_matches_label_rejects_drifted_impl() {
1746 assert_display_matches_label::<DisplayDriftKind>();
1747 }
1748
1749 /// Every closed-set enum across `tatara-process` that carried a
1750 /// hand-rolled `X_display_matches_as_str` test pre-lift now binds
1751 /// through the substrate primitive at ONE call site each. This
1752 /// substrate-wide sweep pins every production Display-alignment
1753 /// consumer at ONE boundary so a per-crate test-site drop cannot
1754 /// silently disable the check — the sweep here catches the drift
1755 /// even when the per-site test body is removed. Mirrors the
1756 /// `every_production_tagged_union_binds_through_the_testkit_primitive`
1757 /// and `every_production_tagged_union_binds_through_the_wire_key_testkit_primitive`
1758 /// sibling sweeps on the (`KIND_LIST` slash-join, wire-key)
1759 /// axes; this one closes the (`Display` byte-identity) axis.
1760 #[test]
1761 fn every_production_display_impl_binds_through_the_testkit_primitive() {
1762 assert_display_matches_label::<crate::allocation::AllocationPhase>();
1763 assert_display_matches_label::<crate::boundary::ConditionKind>();
1764 assert_display_matches_label::<crate::classification::Arity>();
1765 assert_display_matches_label::<crate::classification::CalmClassification>();
1766 assert_display_matches_label::<crate::classification::ConvergencePointType>();
1767 assert_display_matches_label::<crate::classification::DataClassification>();
1768 assert_display_matches_label::<crate::classification::HorizonKind>();
1769 assert_display_matches_label::<crate::classification::OptimizationDirection>();
1770 assert_display_matches_label::<crate::classification::SubstrateType>();
1771 assert_display_matches_label::<crate::compliance::VerificationPhase>();
1772 assert_display_matches_label::<crate::encapsulates::EncapsulationMode>();
1773 assert_display_matches_label::<crate::encapsulates::EncapsulationTarget>();
1774 assert_display_matches_label::<crate::export::ArtifactKind>();
1775 assert_display_matches_label::<crate::export::ChannelKind>();
1776 assert_display_matches_label::<crate::export::ExportTrigger>();
1777 assert_display_matches_label::<crate::export::ReportFormat>();
1778 assert_display_matches_label::<crate::export::ReportPayloadShape>();
1779 assert_display_matches_label::<crate::intent::IntentKind>();
1780 assert_display_matches_label::<crate::intent::WorkloadKind>();
1781 assert_display_matches_label::<crate::lifetime::TeardownPolicy>();
1782 assert_display_matches_label::<crate::lifetime_clock::AutoTerminateKind>();
1783 assert_display_matches_label::<crate::lifetime_clock::TerminateReasonKind>();
1784 assert_display_matches_label::<crate::matrix::SelectStrategyKind>();
1785 assert_display_matches_label::<crate::pool::MemberState>();
1786 assert_display_matches_label::<crate::pool::PoolPhase>();
1787 assert_display_matches_label::<crate::pool::ReplacementPolicy>();
1788 assert_display_matches_label::<crate::pool::ReturnPolicy>();
1789 assert_display_matches_label::<crate::signal::SighupStrategy>();
1790 assert_display_matches_label::<crate::spec::MustReachPhase>();
1791 }
1792
1793 /// Local closed-set scaffold whose serde `rename_all = "lowercase"`
1794 /// projection matches its `via = "as_str"` label byte-identically —
1795 /// pins the Ok arm of the wire-format primitive. Every production
1796 /// implementor across the crate carries the substrate-wide
1797 /// `#[closed_set(via = "as_str")]` + `#[serde(rename_all = ...)]`
1798 /// pair whose alignment this scaffold pins on the sibling-shaped
1799 /// local surface.
1800 #[derive(
1801 Clone,
1802 Copy,
1803 Debug,
1804 PartialEq,
1805 Eq,
1806 Hash,
1807 serde::Serialize,
1808 tatara_closed_set::DeriveClosedSet,
1809 )]
1810 #[serde(rename_all = "lowercase")]
1811 #[closed_set(via = "as_str", generate_unknown)]
1812 enum SerdeAlignedKind {
1813 Alpha,
1814 Beta,
1815 }
1816
1817 impl SerdeAlignedKind {
1818 const ALL: [Self; 2] = [Self::Alpha, Self::Beta];
1819 const fn as_str(self) -> &'static str {
1820 match self {
1821 Self::Alpha => "alpha",
1822 Self::Beta => "beta",
1823 }
1824 }
1825 }
1826
1827 #[test]
1828 fn assert_label_matches_serde_serialization_accepts_coherent_impl() {
1829 assert_label_matches_serde_serialization::<SerdeAlignedKind>();
1830 }
1831
1832 /// A local closed-set scaffold whose serde output deliberately
1833 /// diverges from `label` — pins the failing arm of the wire-format
1834 /// primitive. The `#[serde(rename_all = "UPPERCASE")]` projection
1835 /// emits uppercase JSON strings while the `via = "as_str"` label
1836 /// stays lowercase. A regression that drops the alignment assertion
1837 /// inside [`assert_label_matches_serde_serialization`] fails-loudly
1838 /// at this `#[should_panic]` probe before it can silently thread
1839 /// through the 20 production `X_as_str_matches_serde` sites.
1840 #[derive(
1841 Clone,
1842 Copy,
1843 Debug,
1844 PartialEq,
1845 Eq,
1846 Hash,
1847 serde::Serialize,
1848 tatara_closed_set::DeriveClosedSet,
1849 )]
1850 #[serde(rename_all = "UPPERCASE")]
1851 #[closed_set(via = "as_str", generate_unknown)]
1852 enum SerdeDriftKind {
1853 Alpha,
1854 Beta,
1855 }
1856
1857 impl SerdeDriftKind {
1858 const ALL: [Self; 2] = [Self::Alpha, Self::Beta];
1859 const fn as_str(self) -> &'static str {
1860 match self {
1861 Self::Alpha => "alpha",
1862 Self::Beta => "beta",
1863 }
1864 }
1865 }
1866
1867 #[test]
1868 #[should_panic(expected = "serde output drifted from ClosedSet::label")]
1869 fn assert_label_matches_serde_serialization_rejects_drifted_impl() {
1870 assert_label_matches_serde_serialization::<SerdeDriftKind>();
1871 }
1872
1873 /// Local closed-set scaffold whose ALL THREE axes of the label-
1874 /// surface convention align by construction — pins the Ok arm of
1875 /// the compound-panel primitive.
1876 ///
1877 /// `#[serde(rename_all = "lowercase")]` matches the `via = "as_str"`
1878 /// labels byte-identically (the serde-alignment axis). The
1879 /// `display` sub-attribute on `#[closed_set(via = "as_str",
1880 /// display)]` derives `impl Display` from the same `as_str`
1881 /// projection (the Display-alignment axis). The `generate_unknown`
1882 /// sub-attribute emits the `T::Unknown` carrier the round-trip
1883 /// axis's `parse_label` returns on unknown input. Together these
1884 /// three attributes stamp the substrate-wide derive shape every
1885 /// production 3-axis-panel consumer carries; a caller that lands
1886 /// through this scaffold satisfies EVERY bound the compound's
1887 /// where-clause names.
1888 ///
1889 /// Peer to the sibling per-axis fixtures [`LocalKind`] (Display
1890 /// axis, no serde) and [`SerdeAlignedKind`] (serde axis, no
1891 /// Display) on the label-surface primitive family; this fixture
1892 /// closes the diagonal by carrying both attribute-sets at once,
1893 /// so a regression at ANY sub-assertion's composition inside the
1894 /// compound (the compound accidentally dropping the well-formed
1895 /// call, silently reordering the three calls, wrapping them in a
1896 /// short-circuit that skips the middle one on Ok, …) fails the
1897 /// compound's happy-path pin below rather than as silent drift at
1898 /// every 3-axis consumer.
1899 #[derive(
1900 Clone,
1901 Copy,
1902 Debug,
1903 PartialEq,
1904 Eq,
1905 Hash,
1906 serde::Serialize,
1907 tatara_closed_set::DeriveClosedSet,
1908 )]
1909 #[serde(rename_all = "lowercase")]
1910 #[closed_set(via = "as_str", generate_unknown, display)]
1911 enum PanelAlignedKind {
1912 Alpha,
1913 Beta,
1914 }
1915
1916 impl PanelAlignedKind {
1917 const ALL: [Self; 2] = [Self::Alpha, Self::Beta];
1918 const fn as_str(self) -> &'static str {
1919 match self {
1920 Self::Alpha => "alpha",
1921 Self::Beta => "beta",
1922 }
1923 }
1924 }
1925
1926 /// The compound-panel primitive dispatches Ok on a coherent
1927 /// implementor — [`PanelAlignedKind`] carries every attribute the
1928 /// substrate-wide 3-axis derive shape publishes, so all three
1929 /// sub-assertions the compound composes (well-formed, Display /
1930 /// label, serde / label) pass by construction. The Ok arm is the
1931 /// "no drift on any axis" outcome; a divergence at any single
1932 /// sub-assertion surfaces as that sub-assertion's own labeled
1933 /// panic message (with the caller-attributed line via
1934 /// `#[track_caller]` on both the compound and its sub-
1935 /// primitives), NOT as a silent pass.
1936 ///
1937 /// The per-axis failing arms are pinned by the sibling per-axis
1938 /// #[should_panic] probes above:
1939 /// - the round-trip axis's failing arm is pinned by
1940 /// [`tatara_closed_set::assert_closed_set_well_formed`]'s own
1941 /// `#[should_panic]` probe in the `tatara-closed-set` crate;
1942 /// - the Display axis's failing arm is pinned by
1943 /// [`assert_display_matches_label_rejects_drifted_impl`] on
1944 /// [`DisplayDriftKind`];
1945 /// - the serde axis's failing arm is pinned by
1946 /// [`assert_label_matches_serde_serialization_rejects_drifted_impl`]
1947 /// on [`SerdeDriftKind`].
1948 /// Each per-axis drift fixture already surfaces its axis's exact
1949 /// panic-message substring, so re-authoring per-axis
1950 /// `#[should_panic]` probes at the compound layer would restate
1951 /// the SAME three axis-typed contracts through a compound
1952 /// wrapper — one more copy of the same three pins, not a new
1953 /// gate. The compound's happy-path pin here suffices to verify
1954 /// the composition doesn't lose ANY sub-assertion (a regression
1955 /// that swallows one axis silently would still fail the sibling
1956 /// sub-assertion's own drift probe on the drift fixture).
1957 #[test]
1958 fn assert_closed_set_convention_panel_accepts_coherent_impl() {
1959 assert_closed_set_convention_panel::<PanelAlignedKind>();
1960 }
1961
1962 /// Every closed-set enum across `tatara-process` that publishes
1963 /// ALL THREE axes of the label-surface convention (well-formed +
1964 /// Display-alignment + serde-alignment) now binds through the
1965 /// substrate compound-panel primitive at ONE call site each in
1966 /// this sweep. Pinned in lock-step with the sibling
1967 /// `every_production_serde_serialization_binds_through_the_testkit_primitive`
1968 /// sweep — every enum enumerated below is a member of BOTH sweeps
1969 /// (the compound's `T: Serialize + Display + ClosedSet + ...`
1970 /// bound is a strict superset of `assert_label_matches_serde_
1971 /// serialization`'s `T: ClosedSet + Serialize + Debug` bound, and
1972 /// the 20 wire-format consumers all additionally impl Display via
1973 /// `#[closed_set(via = "as_str", display)]`).
1974 ///
1975 /// A regression that (a) drops the compound's `assert_closed_set_
1976 /// well_formed` dispatch, (b) reorders the three sub-assertions
1977 /// in a way that skips one on Ok, or (c) silently binds the
1978 /// compound against a hollowed-out sub-assertion body catches
1979 /// here at the substrate-wide boundary — the sweep pins every
1980 /// production 3-axis consumer's compound-panel discipline through
1981 /// ONE test even before any per-site test-body sweep collapses
1982 /// the three per-enum sibling tests into ONE compound call each.
1983 /// Post-sweep the substrate-wide compound-panel discipline is a
1984 /// property of the workspace, not a per-file convention.
1985 #[test]
1986 fn every_production_convention_panel_binds_through_the_testkit_primitive() {
1987 assert_closed_set_convention_panel::<crate::allocation::AllocationPhase>();
1988 assert_closed_set_convention_panel::<crate::boundary::ConditionKind>();
1989 assert_closed_set_convention_panel::<crate::classification::CalmClassification>();
1990 assert_closed_set_convention_panel::<crate::classification::ConvergencePointType>();
1991 assert_closed_set_convention_panel::<crate::classification::DataClassification>();
1992 assert_closed_set_convention_panel::<crate::classification::HorizonKind>();
1993 assert_closed_set_convention_panel::<crate::classification::OptimizationDirection>();
1994 assert_closed_set_convention_panel::<crate::classification::SubstrateType>();
1995 assert_closed_set_convention_panel::<crate::compliance::VerificationPhase>();
1996 assert_closed_set_convention_panel::<crate::encapsulates::EncapsulationMode>();
1997 assert_closed_set_convention_panel::<crate::export::ExportTrigger>();
1998 assert_closed_set_convention_panel::<crate::export::ReportFormat>();
1999 assert_closed_set_convention_panel::<crate::intent::WorkloadKind>();
2000 assert_closed_set_convention_panel::<crate::lifetime::TeardownPolicy>();
2001 assert_closed_set_convention_panel::<crate::pool::MemberState>();
2002 assert_closed_set_convention_panel::<crate::pool::PoolPhase>();
2003 assert_closed_set_convention_panel::<crate::pool::ReplacementPolicy>();
2004 assert_closed_set_convention_panel::<crate::pool::ReturnPolicy>();
2005 assert_closed_set_convention_panel::<crate::signal::SighupStrategy>();
2006 assert_closed_set_convention_panel::<crate::spec::MustReachPhase>();
2007 }
2008
2009 /// Every closed-set enum across `tatara-process` that carried a
2010 /// hand-rolled `X_as_str_matches_serde` test pre-lift now binds
2011 /// through the substrate primitive at ONE call site each. This
2012 /// substrate-wide sweep pins every production wire-format alignment
2013 /// consumer at ONE boundary so a per-crate test-site drop cannot
2014 /// silently disable the check — the sweep here catches the drift
2015 /// even when the per-site test body is removed. Mirrors the sibling
2016 /// `every_production_display_impl_binds_through_the_testkit_primitive`
2017 /// sweep on the (Display byte-identity) axis; this one closes the
2018 /// (serde JSON-string byte-identity) axis.
2019 #[test]
2020 fn every_production_serde_serialization_binds_through_the_testkit_primitive() {
2021 assert_label_matches_serde_serialization::<crate::allocation::AllocationPhase>();
2022 assert_label_matches_serde_serialization::<crate::boundary::ConditionKind>();
2023 assert_label_matches_serde_serialization::<crate::classification::CalmClassification>();
2024 assert_label_matches_serde_serialization::<crate::classification::ConvergencePointType>();
2025 assert_label_matches_serde_serialization::<crate::classification::DataClassification>();
2026 assert_label_matches_serde_serialization::<crate::classification::HorizonKind>();
2027 assert_label_matches_serde_serialization::<crate::classification::OptimizationDirection>();
2028 assert_label_matches_serde_serialization::<crate::classification::SubstrateType>();
2029 assert_label_matches_serde_serialization::<crate::compliance::VerificationPhase>();
2030 assert_label_matches_serde_serialization::<crate::encapsulates::EncapsulationMode>();
2031 assert_label_matches_serde_serialization::<crate::export::ExportTrigger>();
2032 assert_label_matches_serde_serialization::<crate::export::ReportFormat>();
2033 assert_label_matches_serde_serialization::<crate::intent::WorkloadKind>();
2034 assert_label_matches_serde_serialization::<crate::lifetime::TeardownPolicy>();
2035 assert_label_matches_serde_serialization::<crate::pool::MemberState>();
2036 assert_label_matches_serde_serialization::<crate::pool::PoolPhase>();
2037 assert_label_matches_serde_serialization::<crate::pool::ReplacementPolicy>();
2038 assert_label_matches_serde_serialization::<crate::pool::ReturnPolicy>();
2039 assert_label_matches_serde_serialization::<crate::signal::SighupStrategy>();
2040 assert_label_matches_serde_serialization::<crate::spec::MustReachPhase>();
2041 }
2042
2043 // Substrate-local single-slot factories — mirror the per-site
2044 // `single_slot_X` test helpers each production site owns, so the
2045 // substrate-wide sweep above binds through the wire-key primitive
2046 // without reaching across the per-crate test-module boundaries the
2047 // per-site helpers are scoped to. The primitive only requires that
2048 // the addressed slot on the parent is populated; the inner spec's
2049 // exact field values are irrelevant to the wire-key check.
2050
2051 fn single_slot_intent_probe(kind: crate::intent::IntentKind) -> crate::intent::Intent {
2052 use crate::intent::{
2053 AplicacaoIntent, ContainerIntent, FluxIntent, GuestIntent, Intent, IntentKind,
2054 LispIntent, NixIntent, WorkloadKind,
2055 };
2056 match kind {
2057 IntentKind::Nix => Intent {
2058 nix: Some(NixIntent {
2059 flake_ref: "f".into(),
2060 attribute: "a".into(),
2061 system: None,
2062 attic_cache: None,
2063 extra_args: vec![],
2064 delegate_to_nix_build: false,
2065 }),
2066 ..Intent::default()
2067 },
2068 IntentKind::Flux => Intent {
2069 flux: Some(FluxIntent {
2070 git_repository: "g".into(),
2071 path: "p".into(),
2072 git_repository_namespace: None,
2073 target_namespace: None,
2074 decrypt_sops: true,
2075 helm_chart: None,
2076 helm_values: None,
2077 }),
2078 ..Intent::default()
2079 },
2080 IntentKind::Lisp => Intent {
2081 lisp: Some(LispIntent {
2082 source: "()".into(),
2083 reader: "tatara-lisp".into(),
2084 version: "v1".into(),
2085 bindings: std::collections::BTreeMap::new(),
2086 }),
2087 ..Intent::default()
2088 },
2089 IntentKind::Container => Intent {
2090 container: Some(ContainerIntent {
2091 image: "x".into(),
2092 replicas: None,
2093 command: vec![],
2094 args: vec![],
2095 env: std::collections::BTreeMap::new(),
2096 workload_kind: WorkloadKind::default(),
2097 }),
2098 ..Intent::default()
2099 },
2100 IntentKind::Aplicacao => Intent {
2101 aplicacao: Some(AplicacaoIntent {
2102 chart_ref: "x".into(),
2103 version: "1".into(),
2104 profile: String::new(),
2105 values_overlay: serde_json::Value::Null,
2106 release_name: None,
2107 target_namespace: None,
2108 install_timeout: None,
2109 }),
2110 ..Intent::default()
2111 },
2112 IntentKind::Guest => Intent {
2113 guest: Some(GuestIntent {
2114 spec: serde_json::json!({"name": "x"}),
2115 state_dir: None,
2116 allow_remote_build: None,
2117 }),
2118 ..Intent::default()
2119 },
2120 }
2121 }
2122
2123 fn single_slot_encapsulation_kind_probe(
2124 target: crate::encapsulates::EncapsulationTarget,
2125 ) -> crate::encapsulates::EncapsulationKind {
2126 use crate::encapsulates::{
2127 BareWorkload, EncapsulationKind, EncapsulationTarget, ExistingHelmRelease,
2128 ExistingKustomization,
2129 };
2130 match target {
2131 EncapsulationTarget::ExistingHelmRelease => EncapsulationKind {
2132 existing_helm_release: Some(ExistingHelmRelease {
2133 namespace: "ns".into(),
2134 name: "hr".into(),
2135 release_name: "rel".into(),
2136 }),
2137 ..EncapsulationKind::default()
2138 },
2139 EncapsulationTarget::ExistingKustomization => EncapsulationKind {
2140 existing_kustomization: Some(ExistingKustomization {
2141 namespace: "ns".into(),
2142 name: "ks".into(),
2143 }),
2144 ..EncapsulationKind::default()
2145 },
2146 EncapsulationTarget::BareWorkload => {
2147 let mut sel = std::collections::BTreeMap::new();
2148 sel.insert("app".into(), "x".into());
2149 EncapsulationKind {
2150 bare_workload: Some(BareWorkload {
2151 namespace: "ns".into(),
2152 selector: sel,
2153 }),
2154 ..EncapsulationKind::default()
2155 }
2156 }
2157 }
2158 }
2159
2160 fn single_slot_artifact_source_probe(
2161 kind: crate::export::ArtifactKind,
2162 ) -> crate::export::ArtifactSource {
2163 use crate::export::{
2164 ArtifactKind, ArtifactSource, ProcessSnapshotSource, ReceiptsSource, ReportFormat,
2165 RunMarkerSource, TestReportSource,
2166 };
2167 match kind {
2168 ArtifactKind::Receipts => ArtifactSource {
2169 receipts: Some(ReceiptsSource::default()),
2170 ..ArtifactSource::default()
2171 },
2172 ArtifactKind::TestReport => ArtifactSource {
2173 test_report: Some(TestReportSource {
2174 configmap: "cm".into(),
2175 key: "k".into(),
2176 format: ReportFormat::Junit,
2177 namespace: None,
2178 }),
2179 ..ArtifactSource::default()
2180 },
2181 ArtifactKind::ProcessSnapshot => ArtifactSource {
2182 process_snapshot: Some(ProcessSnapshotSource::default()),
2183 ..ArtifactSource::default()
2184 },
2185 ArtifactKind::RunMarker => ArtifactSource {
2186 run_marker: Some(RunMarkerSource::default()),
2187 ..ArtifactSource::default()
2188 },
2189 }
2190 }
2191
2192 fn single_slot_vector_channel_probe(
2193 kind: crate::export::ChannelKind,
2194 ) -> crate::export::VectorChannel {
2195 use crate::export::{
2196 ChannelKind, HttpEventChannel, NatsSubjectChannel, StdoutChannel, VectorChannel,
2197 };
2198 match kind {
2199 ChannelKind::HttpEvent => VectorChannel {
2200 http_event: Some(HttpEventChannel {
2201 endpoint: None,
2202 signal_type: "x".into(),
2203 }),
2204 ..VectorChannel::default()
2205 },
2206 ChannelKind::NatsSubject => VectorChannel {
2207 nats_subject: Some(NatsSubjectChannel {
2208 subject: "s".into(),
2209 stream: "S".into(),
2210 url: None,
2211 }),
2212 ..VectorChannel::default()
2213 },
2214 ChannelKind::Stdout => VectorChannel {
2215 stdout: Some(StdoutChannel::default()),
2216 ..VectorChannel::default()
2217 },
2218 }
2219 }
2220
2221 // Substrate-local two-slot factories — peers to the sibling
2222 // `single_slot_*_probe` block above. Each composes
2223 // `single_slot_*_probe(a)` with `single_slot_*_probe(b)`
2224 // through per-field `Option::or` on the parent's tagged-union
2225 // slots, matching the shape every per-site `two_slot_X(a, b)`
2226 // helper across the four production parents already carries.
2227 // The ambiguity-primitive only requires that BOTH addressed
2228 // slots on the parent are populated; the inner spec's exact
2229 // field values are irrelevant to the two-slot ambiguity check.
2230
2231 fn two_slot_intent_probe(
2232 a: crate::intent::IntentKind,
2233 b: crate::intent::IntentKind,
2234 ) -> crate::intent::Intent {
2235 let ia = single_slot_intent_probe(a);
2236 let ib = single_slot_intent_probe(b);
2237 crate::intent::Intent {
2238 nix: ia.nix.or(ib.nix),
2239 flux: ia.flux.or(ib.flux),
2240 lisp: ia.lisp.or(ib.lisp),
2241 container: ia.container.or(ib.container),
2242 aplicacao: ia.aplicacao.or(ib.aplicacao),
2243 guest: ia.guest.or(ib.guest),
2244 }
2245 }
2246
2247 fn two_slot_encapsulation_kind_probe(
2248 a: crate::encapsulates::EncapsulationTarget,
2249 b: crate::encapsulates::EncapsulationTarget,
2250 ) -> crate::encapsulates::EncapsulationKind {
2251 let ka = single_slot_encapsulation_kind_probe(a);
2252 let kb = single_slot_encapsulation_kind_probe(b);
2253 crate::encapsulates::EncapsulationKind {
2254 existing_helm_release: ka.existing_helm_release.or(kb.existing_helm_release),
2255 existing_kustomization: ka.existing_kustomization.or(kb.existing_kustomization),
2256 bare_workload: ka.bare_workload.or(kb.bare_workload),
2257 }
2258 }
2259
2260 fn two_slot_artifact_source_probe(
2261 a: crate::export::ArtifactKind,
2262 b: crate::export::ArtifactKind,
2263 ) -> crate::export::ArtifactSource {
2264 let sa = single_slot_artifact_source_probe(a);
2265 let sb = single_slot_artifact_source_probe(b);
2266 crate::export::ArtifactSource {
2267 receipts: sa.receipts.or(sb.receipts),
2268 test_report: sa.test_report.or(sb.test_report),
2269 process_snapshot: sa.process_snapshot.or(sb.process_snapshot),
2270 run_marker: sa.run_marker.or(sb.run_marker),
2271 }
2272 }
2273
2274 fn two_slot_vector_channel_probe(
2275 a: crate::export::ChannelKind,
2276 b: crate::export::ChannelKind,
2277 ) -> crate::export::VectorChannel {
2278 let ca = single_slot_vector_channel_probe(a);
2279 let cb = single_slot_vector_channel_probe(b);
2280 crate::export::VectorChannel {
2281 http_event: ca.http_event.or(cb.http_event),
2282 nats_subject: ca.nats_subject.or(cb.nats_subject),
2283 stdout: ca.stdout.or(cb.stdout),
2284 }
2285 }
2286
2287 /// The trait's `KIND_LIST` associated const IS the same
2288 /// `&'static str` the inherent `_LIST` constant publishes at
2289 /// each production site — pin identity via `std::ptr::eq` so a
2290 /// future silent copy (e.g. `const KIND_LIST: &'static str =
2291 /// "...literal...";` at the impl block) is caught here.
2292 #[test]
2293 fn production_tagged_union_kind_list_borrows_the_inherent_constant() {
2294 assert!(std::ptr::eq(
2295 <crate::intent::Intent as TaggedUnion>::KIND_LIST,
2296 crate::intent::INTENT_KIND_LIST,
2297 ));
2298 assert!(std::ptr::eq(
2299 <crate::encapsulates::EncapsulationKind as TaggedUnion>::KIND_LIST,
2300 crate::encapsulates::ENCAPSULATION_TARGET_LIST,
2301 ));
2302 assert!(std::ptr::eq(
2303 <crate::export::ArtifactSource as TaggedUnion>::KIND_LIST,
2304 crate::export::ARTIFACT_KIND_LIST,
2305 ));
2306 assert!(std::ptr::eq(
2307 <crate::export::VectorChannel as TaggedUnion>::KIND_LIST,
2308 crate::export::CHANNEL_KIND_LIST,
2309 ));
2310 }
2311
2312 // -------------------------------------------------------------------
2313 // `TaggedUnion::variant` default method — substrate primitive every
2314 // production `.variant()` inherent method delegates to. Pin the
2315 // four-outcome truth table (Empty on all-none, Ambiguous on many,
2316 // Ok on exactly-one at every position) directly on the sibling-
2317 // shaped local parent + local kind + local variant scaffold, so a
2318 // regression on the default body's short-circuit or
2319 // ClosedSet::ALL iteration shape fails here — before any per-parent
2320 // inherent test surfaces the drift.
2321 // -------------------------------------------------------------------
2322
2323 /// Every populated position across [`LocalKind::ALL`] resolves to
2324 /// its own [`LocalVariant`] arm through the default body's
2325 /// `resolve_or_err(<Kind as ClosedSet>::ALL.iter().copied()
2326 /// .map(|k| k.select(self)), KIND_LIST)` sweep. Pin every position
2327 /// so a regression that drifts the iteration order (or drops the
2328 /// `.iter().copied()` bridge to owned-`Copy` Kinds) fails at ONE
2329 /// substrate boundary rather than at four per-parent inherent test
2330 /// sites.
2331 #[test]
2332 fn tagged_union_default_variant_resolves_each_populated_slot() {
2333 let mut p = LocalParent {
2334 alpha: Some(11),
2335 ..Default::default()
2336 };
2337 assert_eq!(
2338 <LocalParent as TaggedUnion>::variant(&p).unwrap(),
2339 LocalVariant::Alpha(&11)
2340 );
2341 p = LocalParent {
2342 beta: Some(22),
2343 ..Default::default()
2344 };
2345 assert_eq!(
2346 <LocalParent as TaggedUnion>::variant(&p).unwrap(),
2347 LocalVariant::Beta(&22)
2348 );
2349 p = LocalParent {
2350 gamma: Some(33),
2351 ..Default::default()
2352 };
2353 assert_eq!(
2354 <LocalParent as TaggedUnion>::variant(&p).unwrap(),
2355 LocalVariant::Gamma(&33)
2356 );
2357 }
2358
2359 /// A [`LocalParent`] with no populated slot resolves through the
2360 /// default body to a [`TaggedUnionError::empty`] carrier whose
2361 /// payload IS the trait's [`TaggedUnion::KIND_LIST`] constant —
2362 /// pin identity via [`std::ptr::eq`] so a regression that
2363 /// composes a fresh `&'static str` at the empty arm (instead of
2364 /// carrying the trait's constant verbatim) is caught here. This
2365 /// is the substrate-wide guarantee the four production sites'
2366 /// operator diagnostics depend on: a rename at
2367 /// `<Parent as TaggedUnion>::KIND_LIST` reaches the error surface
2368 /// intact through ONE `&'static str` handoff.
2369 #[test]
2370 fn tagged_union_default_variant_empty_carries_kind_list_by_pointer() {
2371 let empty = LocalParent::default();
2372 let err = <LocalParent as TaggedUnion>::variant(&empty).unwrap_err();
2373 match err {
2374 LocalParentError::Empty(list) => {
2375 assert!(
2376 std::ptr::eq(list, <LocalParent as TaggedUnion>::KIND_LIST),
2377 "TaggedUnion::variant default must carry KIND_LIST by pointer, not by re-composition",
2378 );
2379 }
2380 LocalParentError::Ambiguous => {
2381 panic!("expected Empty carrier, got Ambiguous");
2382 }
2383 }
2384 }
2385
2386 /// A [`LocalParent`] with two populated slots resolves through
2387 /// the default body to a [`TaggedUnionError::ambiguous`] carrier —
2388 /// pin the Many arm at the substrate boundary so a regression
2389 /// that drops the short-circuit (or misroutes the Many arm to
2390 /// Empty) is caught here.
2391 #[test]
2392 fn tagged_union_default_variant_ambiguous_on_multiple_populated_slots() {
2393 let p = LocalParent {
2394 alpha: Some(1),
2395 beta: Some(2),
2396 gamma: None,
2397 };
2398 assert_eq!(
2399 <LocalParent as TaggedUnion>::variant(&p).unwrap_err(),
2400 LocalParentError::Ambiguous
2401 );
2402 }
2403
2404 /// Every one of the four production `.variant()` inherent methods
2405 /// dispatches through the trait's default body byte-identically —
2406 /// pin the delegation shape (inherent forwarder → trait default)
2407 /// on a probe per parent so a regression that copies the pre-lift
2408 /// hand-rolled `resolve_or_err(...)` body back into the inherent
2409 /// method (instead of the `<Self as TaggedUnion>::variant(self)`
2410 /// one-line delegation) reaches this substrate boundary before it
2411 /// reaches any operator diagnostic.
2412 #[test]
2413 fn every_production_inherent_variant_dispatches_through_trait_default() {
2414 use crate::encapsulates::{EncapsulationKind, EncapsulationKindError};
2415 use crate::export::{ArtifactError, ArtifactSource, ChannelError, VectorChannel};
2416 use crate::intent::{Intent, IntentError};
2417
2418 // Intent: default of all-None resolves to Empty via the delegation.
2419 let i = Intent::default();
2420 match (i.variant(), <Intent as TaggedUnion>::variant(&i)) {
2421 (Err(IntentError::Empty(a)), Err(IntentError::Empty(b))) => assert!(
2422 std::ptr::eq(a, b),
2423 "Intent inherent and trait dispatch must return the same &'static str",
2424 ),
2425 (a, b) => panic!("Intent inherent/trait mismatch: inherent={a:?}, trait={b:?}"),
2426 }
2427
2428 // EncapsulationKind: same Empty projection through both dispatch paths.
2429 let k = EncapsulationKind::default();
2430 match (k.variant(), <EncapsulationKind as TaggedUnion>::variant(&k)) {
2431 (Err(EncapsulationKindError::Empty(a)), Err(EncapsulationKindError::Empty(b))) => {
2432 assert!(
2433 std::ptr::eq(a, b),
2434 "EncapsulationKind inherent and trait dispatch must return the same &'static str",
2435 )
2436 }
2437 (a, b) => {
2438 panic!("EncapsulationKind inherent/trait mismatch: inherent={a:?}, trait={b:?}")
2439 }
2440 }
2441
2442 // ArtifactSource: same Empty projection through both dispatch paths.
2443 let s = ArtifactSource::default();
2444 match (s.variant(), <ArtifactSource as TaggedUnion>::variant(&s)) {
2445 (Err(ArtifactError::Empty(a)), Err(ArtifactError::Empty(b))) => assert!(
2446 std::ptr::eq(a, b),
2447 "ArtifactSource inherent and trait dispatch must return the same &'static str",
2448 ),
2449 (a, b) => panic!("ArtifactSource inherent/trait mismatch: inherent={a:?}, trait={b:?}"),
2450 }
2451
2452 // VectorChannel: same Empty projection through both dispatch paths.
2453 let c = VectorChannel::default();
2454 match (c.variant(), <VectorChannel as TaggedUnion>::variant(&c)) {
2455 (Err(ChannelError::Empty(a)), Err(ChannelError::Empty(b))) => assert!(
2456 std::ptr::eq(a, b),
2457 "VectorChannel inherent and trait dispatch must return the same &'static str",
2458 ),
2459 (a, b) => panic!("VectorChannel inherent/trait mismatch: inherent={a:?}, trait={b:?}"),
2460 }
2461 }
2462
2463 // -------------------------------------------------------------------
2464 // `declare_tagged_union_impls!` macro — the three-block impl stanza
2465 // (inherent `.variant()` forwarder + `VariantSelector<Parent>` on
2466 // the Kind + `TaggedUnion` on the parent) as ONE authoring surface.
2467 // Pin the macro's shape against a sibling-shaped local family so a
2468 // regression on any of the three emitted blocks fails here before
2469 // it reaches the four production sites.
2470 // -------------------------------------------------------------------
2471
2472 /// Local sibling-shaped Kind for the macro-emitted-impls test — a
2473 /// dedicated closed set so this test can't share substrate with the
2474 /// hand-rolled [`LocalKind`] block above. Uses
2475 /// [`tatara_closed_set::DeriveClosedSet`] so the macro's
2476 /// `TaggedUnion` bound (`Kind: ClosedSet + VariantSelector<Self>`)
2477 /// is satisfied through the derive.
2478 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, tatara_closed_set::DeriveClosedSet)]
2479 #[closed_set(via = "as_str", generate_unknown)]
2480 enum MacroLocalKind {
2481 Foo,
2482 Bar,
2483 }
2484
2485 impl MacroLocalKind {
2486 const ALL: [Self; 2] = [Self::Foo, Self::Bar];
2487 const fn as_str(self) -> &'static str {
2488 match self {
2489 Self::Foo => "foo",
2490 Self::Bar => "bar",
2491 }
2492 }
2493 fn select<'a>(self, parent: &'a MacroLocalParent) -> Option<MacroLocalVariant<'a>> {
2494 match self {
2495 Self::Foo => parent.foo.as_ref().map(MacroLocalVariant::Foo),
2496 Self::Bar => parent.bar.as_ref().map(MacroLocalVariant::Bar),
2497 }
2498 }
2499 }
2500
2501 /// Local sibling-shaped parent for the macro-emitted-impls test —
2502 /// distinct from [`LocalParent`] so the macro's emitted impls
2503 /// don't collide with the hand-rolled trait impls above.
2504 ///
2505 /// Derives [`serde::Serialize`] with `skip_serializing_if =
2506 /// "Option::is_none"` on every slot so the wire-format primitive
2507 /// [`assert_single_slot_key_matches_label`] can be exercised
2508 /// through the macro-emitted `TaggedUnion` impl path — pins the
2509 /// substrate-wide guarantee that a fifth sibling landing through
2510 /// [`declare_tagged_union_impls!`] picks up the wire-alignment
2511 /// check for free.
2512 #[derive(Default, serde::Serialize)]
2513 struct MacroLocalParent {
2514 #[serde(skip_serializing_if = "Option::is_none")]
2515 foo: Option<u32>,
2516 #[serde(skip_serializing_if = "Option::is_none")]
2517 bar: Option<u32>,
2518 }
2519
2520 /// Borrowed-view of a populated slot on [`MacroLocalParent`] — the
2521 /// return type of the macro-emitted inherent `.variant()`.
2522 #[derive(Debug, PartialEq)]
2523 enum MacroLocalVariant<'a> {
2524 Foo(&'a u32),
2525 Bar(&'a u32),
2526 }
2527
2528 impl VariantKind<MacroLocalKind> for MacroLocalVariant<'_> {
2529 fn variant_kind(&self) -> MacroLocalKind {
2530 match self {
2531 Self::Foo(_) => MacroLocalKind::Foo,
2532 Self::Bar(_) => MacroLocalKind::Bar,
2533 }
2534 }
2535 }
2536
2537 crate::declare_tagged_union_error! {
2538 pub(super) MacroLocalError,
2539 empty = "macro-local parent has no variant set (one of {0} required)",
2540 ambiguous = "macro-local parent has multiple variants set; exactly one required",
2541 }
2542
2543 /// Slash-joined kind list — literal peer of
2544 /// [`crate::intent::INTENT_KIND_LIST`] etc. that the macro's
2545 /// `KIND_LIST` associated const borrows verbatim.
2546 const MACRO_LOCAL_KIND_LIST: &str = "foo/bar";
2547
2548 // ONE macro call emits: inherent `MacroLocalParent::variant`,
2549 // `impl VariantSelector<MacroLocalParent> for MacroLocalKind`, and
2550 // `impl TaggedUnion for MacroLocalParent`. The four production
2551 // sites bind through this exact same call shape.
2552 crate::declare_tagged_union_impls! {
2553 parent = MacroLocalParent,
2554 kind = MacroLocalKind,
2555 variant = MacroLocalVariant,
2556 error = MacroLocalError,
2557 kind_list = MACRO_LOCAL_KIND_LIST,
2558 }
2559
2560 /// The macro-emitted `impl TaggedUnion` binds the (Kind, Error,
2561 /// KIND_LIST) triple exactly as a hand-rolled block would — pin
2562 /// the diagnostic-stability testkit primitive through the macro's
2563 /// output so a regression on any of the three associated items
2564 /// (say the macro pulling `KIND_LIST` from the wrong argument
2565 /// slot) fails here.
2566 #[test]
2567 fn macro_emitted_tagged_union_impl_binds_kind_list_coherently() {
2568 assert_kind_list_matches_closed_set::<MacroLocalParent>();
2569 assert!(std::ptr::eq(
2570 <MacroLocalParent as TaggedUnion>::KIND_LIST,
2571 MACRO_LOCAL_KIND_LIST,
2572 ));
2573 }
2574
2575 /// The macro-emitted inherent `.variant()` forwarder dispatches
2576 /// through the trait default body — every populated slot resolves
2577 /// to its own [`MacroLocalVariant`] arm, all-none resolves to
2578 /// [`TaggedUnionError::empty`] carrying the trait's `KIND_LIST`
2579 /// by pointer, two-populated resolves to
2580 /// [`TaggedUnionError::ambiguous`]. The four production sites
2581 /// exercise the same four-outcome truth table through the same
2582 /// macro-emitted delegation shape.
2583 #[test]
2584 fn macro_emitted_inherent_variant_dispatches_the_four_outcome_truth_table() {
2585 // Foo populated.
2586 let p = MacroLocalParent {
2587 foo: Some(11),
2588 bar: None,
2589 };
2590 assert_eq!(p.variant().unwrap(), MacroLocalVariant::Foo(&11));
2591
2592 // Bar populated.
2593 let p = MacroLocalParent {
2594 foo: None,
2595 bar: Some(22),
2596 };
2597 assert_eq!(p.variant().unwrap(), MacroLocalVariant::Bar(&22));
2598
2599 // All none — Empty arm carries the trait's KIND_LIST value.
2600 // The by-pointer preservation across the trait default body is
2601 // pinned substrate-wide by
2602 // `tagged_union_default_variant_empty_carries_kind_list_by_pointer`
2603 // on the sibling hand-rolled `LocalParent`; this test only pins
2604 // that the macro-emitted `KIND_LIST = MACRO_LOCAL_KIND_LIST`
2605 // assignment reaches the operator diagnostic value-identically.
2606 let p = MacroLocalParent::default();
2607 match p.variant().unwrap_err() {
2608 MacroLocalError::Empty(list) => assert_eq!(list, MACRO_LOCAL_KIND_LIST),
2609 MacroLocalError::Ambiguous => panic!("expected Empty, got Ambiguous"),
2610 }
2611
2612 // Two populated — Ambiguous.
2613 let p = MacroLocalParent {
2614 foo: Some(1),
2615 bar: Some(2),
2616 };
2617 assert_eq!(p.variant().unwrap_err(), MacroLocalError::Ambiguous);
2618 }
2619
2620 /// The macro-emitted `VariantSelector` impl's `select` body
2621 /// delegates to the Kind's inherent `<Kind>::select(self, parent)`
2622 /// — pin the delegation via `std::ptr::eq` on the returned
2623 /// borrowed view so a regression that inlines a divergent select
2624 /// body (rather than reaching the inherent method) is caught here.
2625 #[test]
2626 fn macro_emitted_variant_selector_delegates_to_inherent_select() {
2627 let p = MacroLocalParent {
2628 foo: Some(7),
2629 bar: None,
2630 };
2631 // Trait-dispatched select projects through the macro-emitted body.
2632 let via_trait =
2633 <MacroLocalKind as VariantSelector<MacroLocalParent>>::select(MacroLocalKind::Foo, &p)
2634 .unwrap();
2635 // Inherent select projects through the direct impl.
2636 let via_inherent = MacroLocalKind::Foo.select(&p).unwrap();
2637 match (via_trait, via_inherent) {
2638 (MacroLocalVariant::Foo(a), MacroLocalVariant::Foo(b)) => {
2639 assert!(
2640 std::ptr::eq(a, b),
2641 "macro-emitted VariantSelector::select must delegate to <Kind>::select — same borrow, not a copy",
2642 );
2643 }
2644 _ => panic!("expected Foo arm on both dispatch paths"),
2645 }
2646 }
2647
2648 /// The trait's default body sweeps `<Kind as ClosedSet>::ALL` in
2649 /// declaration order — pin the iteration order against the
2650 /// production `Kind::ALL` inherent const on every implementor so
2651 /// a regression on `DeriveClosedSet`'s ALL-projection (or a
2652 /// silent reorder of the enum's variant declarations that drifts
2653 /// only ONE of the two arrays) fails at ONE substrate boundary.
2654 #[test]
2655 fn every_production_kind_closedset_all_matches_inherent_all() {
2656 use crate::encapsulates::EncapsulationTarget;
2657 use crate::export::{ArtifactKind, ChannelKind};
2658 use crate::intent::IntentKind;
2659
2660 assert_eq!(
2661 <IntentKind as tatara_closed_set::ClosedSet>::ALL,
2662 IntentKind::ALL.as_slice(),
2663 );
2664 assert_eq!(
2665 <EncapsulationTarget as tatara_closed_set::ClosedSet>::ALL,
2666 EncapsulationTarget::ALL.as_slice(),
2667 );
2668 assert_eq!(
2669 <ArtifactKind as tatara_closed_set::ClosedSet>::ALL,
2670 ArtifactKind::ALL.as_slice(),
2671 );
2672 assert_eq!(
2673 <ChannelKind as tatara_closed_set::ClosedSet>::ALL,
2674 ChannelKind::ALL.as_slice(),
2675 );
2676 }
2677
2678 // -------------------------------------------------------------------
2679 // `VariantKind<K>` trait — reverse projection from a borrowed-variant
2680 // view back into its addressing Kind, and `assert_variant_round_trip`
2681 // as the substrate testkit primitive that composes it with
2682 // `VariantSelector::select` on the populated side. Pin the four-arm
2683 // truth table (every position round-trips through select→variant_kind
2684 // AND through variant()→variant_kind) directly on the sibling-shaped
2685 // local scaffold, so a regression on either projection or on the
2686 // resolver default body fails here — before any per-parent inherent
2687 // test surfaces the drift.
2688 // -------------------------------------------------------------------
2689
2690 /// Every populated position across [`LocalKind::ALL`] round-trips
2691 /// through both `select→variant_kind` AND `variant()→variant_kind`
2692 /// on the sibling-shaped local scaffold. Pins the substrate
2693 /// primitive's four-arm truth table at ONE boundary — a regression
2694 /// on either projection direction (or on the resolver default
2695 /// short-circuit / iteration order) fails here before any per-parent
2696 /// inherent test surfaces the drift.
2697 #[test]
2698 fn assert_variant_round_trip_accepts_coherent_local_impl() {
2699 fn make_local(k: LocalKind) -> LocalParent {
2700 match k {
2701 LocalKind::Alpha => LocalParent {
2702 alpha: Some(11),
2703 ..Default::default()
2704 },
2705 LocalKind::Beta => LocalParent {
2706 beta: Some(22),
2707 ..Default::default()
2708 },
2709 LocalKind::Gamma => LocalParent {
2710 gamma: Some(33),
2711 ..Default::default()
2712 },
2713 }
2714 }
2715 assert_variant_round_trip::<LocalParent, _>(make_local);
2716 }
2717
2718 /// The testkit primitive is a `#[track_caller]` compound-lift: a
2719 /// factory that fails to populate the addressed slot fails at the
2720 /// caller's site with a labeled panic message, not silently. Pin
2721 /// the failing case with a deliberately empty parent factory so a
2722 /// regression that drops the "select must return Some" check
2723 /// fails-loudly here — the missing-slot arm is the substrate
2724 /// primitive's first failure mode.
2725 #[test]
2726 #[should_panic(expected = "VariantSelector::select must return Some for populated slot")]
2727 fn assert_variant_round_trip_rejects_factory_that_leaves_slot_empty() {
2728 // Factory that returns an all-empty parent regardless of k —
2729 // every `k.select(&parent)` returns None, so the primitive
2730 // panics at the "must return Some" arm.
2731 fn empty_factory(_: LocalKind) -> LocalParent {
2732 LocalParent::default()
2733 }
2734 assert_variant_round_trip::<LocalParent, _>(empty_factory);
2735 }
2736
2737 // -------------------------------------------------------------------
2738 // `assert_two_slots_ambiguous` — the ALL×ALL ambiguity sweep as ONE
2739 // substrate primitive. Pin the truth table (every off-diagonal pair
2740 // resolves to `TaggedUnionError::ambiguous`, diagonal pairs are
2741 // skipped, a factory that yields a non-Ambiguous parent fails-loudly
2742 // at the caller's site) directly on the sibling-shaped `LocalParent`
2743 // scaffold — a regression on either the pair-iteration order or the
2744 // expected-carrier composition fails here before any per-parent test
2745 // surfaces the drift.
2746 // -------------------------------------------------------------------
2747
2748 /// Every off-diagonal pair across [`LocalKind::ALL`] × `ALL`
2749 /// resolves through the substrate primitive to
2750 /// [`LocalParentError::Ambiguous`] on the sibling-shaped local
2751 /// scaffold. Pins the primitive's Ok arm (no false positives on the
2752 /// coherent-impl side) at ONE boundary — a regression that drops
2753 /// the diagonal skip, mis-iterates `ClosedSet::ALL`, or composes a
2754 /// divergent expected carrier fails here before any per-parent
2755 /// inherent test surfaces the drift.
2756 #[test]
2757 fn assert_two_slots_ambiguous_accepts_coherent_local_impl() {
2758 fn two_local(a: LocalKind, b: LocalKind) -> LocalParent {
2759 let mut p = LocalParent::default();
2760 for k in [a, b] {
2761 match k {
2762 LocalKind::Alpha => p.alpha = Some(11),
2763 LocalKind::Beta => p.beta = Some(22),
2764 LocalKind::Gamma => p.gamma = Some(33),
2765 }
2766 }
2767 p
2768 }
2769 assert_two_slots_ambiguous::<LocalParent, _>(two_local);
2770 }
2771
2772 /// A factory that yields a single-slot parent for the FIRST kind
2773 /// (ignoring the second) — every off-diagonal pair resolves to
2774 /// exactly-one Ok(Variant), NOT Ambiguous — MUST fail-loudly at
2775 /// the caller's site through the primitive's "two-slot parent
2776 /// must not resolve to a variant" arm. Pin the Ok-side failure
2777 /// mode so a regression that mis-routes the substrate primitive's
2778 /// resolved-Ok arm past the assertion (silently succeeding on a
2779 /// single-slot factory) is caught here.
2780 #[test]
2781 #[should_panic(expected = "two-slot parent must not resolve to a variant")]
2782 fn assert_two_slots_ambiguous_rejects_factory_that_populates_only_one_slot() {
2783 fn single_only(a: LocalKind, _: LocalKind) -> LocalParent {
2784 let mut p = LocalParent::default();
2785 match a {
2786 LocalKind::Alpha => p.alpha = Some(11),
2787 LocalKind::Beta => p.beta = Some(22),
2788 LocalKind::Gamma => p.gamma = Some(33),
2789 }
2790 p
2791 }
2792 assert_two_slots_ambiguous::<LocalParent, _>(single_only);
2793 }
2794
2795 /// A factory that yields an all-empty parent (so `.variant()`
2796 /// resolves to the `Empty` carrier, NOT `Ambiguous`) MUST
2797 /// fail-loudly at the caller's site through the primitive's
2798 /// `assert_eq!` arm — the composed expected carrier
2799 /// [`TaggedUnionError::ambiguous`] mismatches the resolved
2800 /// [`TaggedUnionError::empty`] carrier. Pin the Empty-arm failure
2801 /// mode so a regression that mis-projects the None arm of
2802 /// [`ResolveError`] onto Ambiguous (silently succeeding on an
2803 /// empty factory) is caught here.
2804 #[test]
2805 #[should_panic(expected = "should resolve Ambiguous")]
2806 fn assert_two_slots_ambiguous_rejects_factory_that_populates_no_slots() {
2807 fn empty_factory(_: LocalKind, _: LocalKind) -> LocalParent {
2808 LocalParent::default()
2809 }
2810 assert_two_slots_ambiguous::<LocalParent, _>(empty_factory);
2811 }
2812
2813 // -------------------------------------------------------------------
2814 // `assert_single_slot_key_matches_label` — the wire-key / kind-label
2815 // alignment sweep as ONE substrate primitive. Pin the truth table
2816 // (every populated slot serializes to exactly one JSON key whose
2817 // name equals the addressing kind's ClosedSet label; a factory that
2818 // populates the wrong slot / no slot / multiple slots fails-loudly
2819 // at the caller's site) directly on the sibling-shaped `LocalParent`
2820 // scaffold — a regression on either the exactly-one arm or the
2821 // name-equality arm fails here before any per-parent inherent test
2822 // surfaces the drift.
2823 // -------------------------------------------------------------------
2824
2825 /// Every kind across [`LocalKind::ALL`] serializes through the
2826 /// substrate primitive to a JSON object with EXACTLY ONE key whose
2827 /// name equals `<LocalKind as ClosedSet>::label` on the addressed
2828 /// kind. Pins the primitive's Ok arm (no false positives on the
2829 /// coherent-impl side) at ONE boundary — a regression that inspects
2830 /// the wrong serde value (e.g. `to_string` instead of `to_value`),
2831 /// counts fields off-by-one, or projects the wrong `ClosedSet`
2832 /// method (`labels_joined` instead of `label`) fails here before any
2833 /// per-parent inherent test surfaces the drift.
2834 #[test]
2835 fn assert_single_slot_key_matches_label_accepts_coherent_local_impl() {
2836 fn make_local(k: LocalKind) -> LocalParent {
2837 match k {
2838 LocalKind::Alpha => LocalParent {
2839 alpha: Some(11),
2840 ..Default::default()
2841 },
2842 LocalKind::Beta => LocalParent {
2843 beta: Some(22),
2844 ..Default::default()
2845 },
2846 LocalKind::Gamma => LocalParent {
2847 gamma: Some(33),
2848 ..Default::default()
2849 },
2850 }
2851 }
2852 assert_single_slot_key_matches_label::<LocalParent, _>(make_local);
2853 }
2854
2855 /// A factory that returns a single-slot parent for the WRONG kind
2856 /// (populates `beta` regardless of what kind is asked for) MUST
2857 /// fail-loudly at the caller's site through the primitive's
2858 /// name-equality arm — the emitted key does not match the addressed
2859 /// kind's label. Pins the drift-detection failure mode so a
2860 /// regression that drops the `assert_eq!(keys[0], label)` arm
2861 /// (silently succeeding on any-key-at-all) is caught here. The
2862 /// caller's site is the `#[should_panic]` boundary through the
2863 /// primitive's `#[track_caller]` compound-lift.
2864 #[test]
2865 #[should_panic(expected = "wire-key drift")]
2866 fn assert_single_slot_key_matches_label_rejects_factory_that_populates_wrong_slot() {
2867 fn always_beta(_: LocalKind) -> LocalParent {
2868 LocalParent {
2869 beta: Some(22),
2870 ..Default::default()
2871 }
2872 }
2873 assert_single_slot_key_matches_label::<LocalParent, _>(always_beta);
2874 }
2875
2876 /// A factory that returns an all-empty parent (so serializing
2877 /// yields ZERO keys, not exactly-one) MUST fail-loudly at the
2878 /// caller's site through the primitive's exactly-one arm. Pins the
2879 /// zero-key failure mode so a regression that projects
2880 /// `obj.keys().count() >= 1` (rather than `== 1`) is caught here.
2881 #[test]
2882 #[should_panic(expected = "exactly one populated field")]
2883 fn assert_single_slot_key_matches_label_rejects_factory_that_populates_no_slots() {
2884 fn empty_factory(_: LocalKind) -> LocalParent {
2885 LocalParent::default()
2886 }
2887 assert_single_slot_key_matches_label::<LocalParent, _>(empty_factory);
2888 }
2889
2890 /// A factory that returns a parent with TWO populated slots (so
2891 /// serializing yields two keys, not exactly-one) MUST fail-loudly
2892 /// at the caller's site through the primitive's exactly-one arm.
2893 /// Pins the many-keys failure mode so a regression that projects
2894 /// `obj.keys().count() <= 1` (rather than `== 1`) is caught here.
2895 /// Cross-pins the substrate promise that a single-slot factory
2896 /// truly populates ONE slot — a future factory bug that leaks
2897 /// residual populated slots between calls (e.g. via shared mutable
2898 /// state) is caught HERE at the primitive boundary.
2899 #[test]
2900 #[should_panic(expected = "exactly one populated field")]
2901 fn assert_single_slot_key_matches_label_rejects_factory_that_populates_two_slots() {
2902 fn two_slot_factory(_: LocalKind) -> LocalParent {
2903 LocalParent {
2904 alpha: Some(1),
2905 beta: Some(2),
2906 gamma: None,
2907 }
2908 }
2909 assert_single_slot_key_matches_label::<LocalParent, _>(two_slot_factory);
2910 }
2911
2912 /// The macro-emitted [`MacroLocalParent`] scaffold impls
2913 /// [`TaggedUnion`] through the [`declare_tagged_union_impls!`]
2914 /// three-block macro AND additionally derives `serde::Serialize` +
2915 /// `#[serde(skip_serializing_if = "Option::is_none")]` on every
2916 /// slot — so the wire-key primitive dispatches on the MACRO-emitted
2917 /// impl path byte-identically with the hand-rolled [`LocalParent`]
2918 /// path above. Pins the substrate-wide guarantee that a fifth
2919 /// sibling landing through the macro picks up the wire-alignment
2920 /// check for free, without a hand-rolled `TaggedUnion` block, so
2921 /// long as its serde derives match the substrate-wide
2922 /// `skip_serializing_if = "Option::is_none"` shape every production
2923 /// site already carries. A regression that mis-routes the
2924 /// primitive's serialize call through the WRONG entry point (e.g.
2925 /// calling a bespoke `to_json` that bypasses serde) is caught here.
2926 #[test]
2927 fn assert_single_slot_key_matches_label_accepts_macro_emitted_impl() {
2928 fn make_macro_local(k: MacroLocalKind) -> MacroLocalParent {
2929 match k {
2930 MacroLocalKind::Foo => MacroLocalParent {
2931 foo: Some(7),
2932 bar: None,
2933 },
2934 MacroLocalKind::Bar => MacroLocalParent {
2935 foo: None,
2936 bar: Some(8),
2937 },
2938 }
2939 }
2940 assert_single_slot_key_matches_label::<MacroLocalParent, _>(make_macro_local);
2941 }
2942
2943 /// Every one of the five production borrowed-view enums impls
2944 /// [`VariantKind`] byte-identically with its inherent `.kind()`
2945 /// (or `.target()` on `EncapsulationKindVariant`) — pin the
2946 /// delegation shape at ONE substrate boundary so a regression that
2947 /// inlines a divergent match body into the trait impl (rather than
2948 /// the one-line delegation) is caught here. `Lifetime`'s
2949 /// borrowed-view is included even though `Lifetime` isn't a
2950 /// [`TaggedUnion`] impl — the reverse projection applies uniformly.
2951 #[test]
2952 fn every_production_variant_kind_impl_matches_inherent_projection() {
2953 use crate::encapsulates::{EncapsulationKindVariant, ExistingHelmRelease};
2954 use crate::export::{ArtifactVariant, ChannelVariant, HttpEventChannel, ReceiptsSource};
2955 use crate::intent::{IntentVariant, NixIntent};
2956 use crate::lifetime::{LifetimeVariant, PermanentLifetime};
2957
2958 let nix = NixIntent {
2959 flake_ref: "github:a/b".into(),
2960 attribute: "x".into(),
2961 system: None,
2962 attic_cache: None,
2963 extra_args: vec![],
2964 delegate_to_nix_build: false,
2965 };
2966 let iv = IntentVariant::Nix(&nix);
2967 assert_eq!(iv.kind(), iv.variant_kind());
2968
2969 let perm = PermanentLifetime::default();
2970 let lv = LifetimeVariant::Permanent(&perm);
2971 assert_eq!(lv.kind(), lv.variant_kind());
2972
2973 let hr = ExistingHelmRelease {
2974 namespace: "ns".into(),
2975 name: "n".into(),
2976 release_name: "r".into(),
2977 };
2978 let ev = EncapsulationKindVariant::ExistingHelmRelease(&hr);
2979 assert_eq!(ev.target(), ev.variant_kind());
2980
2981 let rs = ReceiptsSource {};
2982 let av = ArtifactVariant::Receipts(&rs);
2983 assert_eq!(av.kind(), av.variant_kind());
2984
2985 let ch = HttpEventChannel {
2986 endpoint: None,
2987 signal_type: "s".into(),
2988 };
2989 let cv = ChannelVariant::HttpEvent(&ch);
2990 assert_eq!(cv.kind(), cv.variant_kind());
2991 }
2992}