Skip to main content

tatara_process/
export.rs

1//! `ExportSpec` — what an ephemeral Process is allowed to leave behind.
2//!
3//! The compounding move: ephemeral envs default to **leaving nothing
4//! behind**. Anything that must survive teardown is named explicitly via
5//! one or more `ExportSpec`s on `:lifetime (:ephemeral … :exports …)`.
6//! Each export declares:
7//!
8//!   * what artifact to ship out (a [`ArtifactSource`] variant)
9//!   * where to ship it through (a [`VectorChannel`] variant — Vector
10//!     ingest endpoint, NATS JetStream subject, or stdout)
11//!   * when to ship it (an [`ExportTrigger`] variant)
12//!
13//! The pleme-io convention is that **everything emitted from a
14//! workload flows through the Vector + NATS layer**, never to ad-hoc
15//! sinks. `VectorChannel` enforces that at the type level — there is
16//! no `S3Bucket` or `RawFileSystem` variant. Vector's downstream
17//! sink graph (file, VictoriaLogs, VictoriaMetrics, Loki, …) handles
18//! durability + analytics; this primitive only names the *ingestion*
19//! shape.
20//!
21//! The reconciler's `Releasing` phase (between `Attested`/`Failed` and
22//! `Exiting`) reads `lifetime.ephemeral.exports`, filters by
23//! [`ExportTrigger`] against the terminal phase, and emits one
24//! tatara-export-worker Job per surviving spec. Each Job emits a
25//! receipt of its own export action so the export itself participates
26//! in the BLAKE3 attestation chain.
27//!
28//! Lisp authoring:
29//! ```lisp
30//! (defephemeral closed-loop-attest
31//!   :aplicacao  (…)
32//!   :ttl        "1h"
33//!   :teardown   OnAttested
34//!   :exports
35//!     (;; Receipts — tier-1 guaranteed delivery via NATS JetStream
36//!      (:source  (:receipts)
37//!       :channel (:nats-subject :subject "pleme.pleme-dev.ephemeral.{{run_id}}.receipt"
38//!                               :stream  "EPHEMERAL_RECEIPTS")
39//!       :when    OnAttested)
40//!      ;; Test report — best-effort via Vector HTTP ingest
41//!      (:source  (:test-report :configmap "demo-test-results"
42//!                              :key       "junit.xml"
43//!                              :format    Junit)
44//!       :channel (:http-event :signal-type "test-report")
45//!       :when    Always)
46//!      ;; Run marker — small synthetic event for shinryu cohort math
47//!      (:source  (:run-marker :labels (:run-id "{{run_id}}"
48//!                                       :phase "end"))
49//!       :channel (:http-event :signal-type "ephemeral-marker")
50//!       :when    Always)))
51//! ```
52
53use schemars::JsonSchema;
54use serde::{Deserialize, Serialize};
55use std::collections::BTreeMap;
56use std::fmt;
57
58use crate::phase::ProcessPhase;
59
60// ─── ExportSpec ────────────────────────────────────────────────────
61
62/// One declared export from an ephemeral Process.
63///
64/// Multiple `ExportSpec`s can be attached to a single ephemeral
65/// lifetime — each fires independently during the `Releasing` phase
66/// when its [`ExportTrigger`] matches the terminal phase reached.
67#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
68#[serde(rename_all = "camelCase")]
69pub struct ExportSpec {
70    /// What artifact to ship.
71    pub source: ArtifactSource,
72
73    /// Where to ship it. Always Vector-native — pleme-io routes every
74    /// emission through one of the four canonical channels.
75    pub channel: VectorChannel,
76
77    /// When to ship. Defaults to `OnAttested`.
78    #[serde(default)]
79    pub when: ExportTrigger,
80
81    /// Override the run-id label that templates into channel subjects
82    /// / signal-type metadata. Defaults to the Process's PID-derived
83    /// run id when unset.
84    #[serde(default, skip_serializing_if = "Option::is_none")]
85    pub experiment_id_override: Option<String>,
86}
87
88// ─── ArtifactSource ────────────────────────────────────────────────
89
90/// What artifact this export ships out.
91///
92/// Exactly-one-Option pattern, matching the rest of the typescape
93/// (`Intent`, `Lifetime`). Adding a new artifact kind is additive on
94/// the wire — existing JSON keeps deserializing unchanged.
95#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
96#[serde(rename_all = "camelCase")]
97pub struct ArtifactSource {
98    /// Every `ReceiptEnvelope` emitted by this Process during its
99    /// lifetime — the BLAKE3-chained typed attestation stream.
100    #[serde(default, skip_serializing_if = "Option::is_none")]
101    pub receipts: Option<ReceiptsSource>,
102
103    /// A test report stored in a ConfigMap by an in-cluster test
104    /// runner (Job, test harness, closed-loop probe). Worker reads the
105    /// ConfigMap, packages it per `format`, and forwards.
106    #[serde(default, skip_serializing_if = "Option::is_none")]
107    pub test_report: Option<TestReportSource>,
108
109    /// The Process's own `ProcessSpec` + `ProcessStatus` snapshot at
110    /// teardown time, as canonical JSON. Useful for post-mortems on
111    /// failed ephemeral runs.
112    #[serde(default, skip_serializing_if = "Option::is_none")]
113    pub process_snapshot: Option<ProcessSnapshotSource>,
114
115    /// A small synthetic event — start/end of run markers, cohort
116    /// tags, experiment correlation. Worker emits a single
117    /// timestamped event with the declared `labels`.
118    #[serde(default, skip_serializing_if = "Option::is_none")]
119    pub run_marker: Option<RunMarkerSource>,
120}
121
122/// Resolved enum view used by the worker.
123#[derive(Clone, Debug)]
124pub enum ArtifactVariant<'a> {
125    Receipts(&'a ReceiptsSource),
126    TestReport(&'a TestReportSource),
127    ProcessSnapshot(&'a ProcessSnapshotSource),
128    RunMarker(&'a RunMarkerSource),
129}
130
131impl ArtifactVariant<'_> {
132    /// Reverse projection — every borrowed variant knows its
133    /// `ArtifactKind` discriminator. Pairs with `ArtifactKind::select`
134    /// so `ArtifactKind::select(source).map(|v| v.kind())` round-trips
135    /// the closed set on the populated side; pinned by
136    /// `artifact_kind_round_trips_through_variant_kind`. Future
137    /// kind-keyed consumers (metric labels like
138    /// `tatara_exports_total{artifact="receipts"}`, status-condition
139    /// reason strings, audit-trail classifiers, LSP completion) reach
140    /// through this projection instead of pattern-matching the
141    /// payload-carrying view.
142    pub fn kind(&self) -> ArtifactKind {
143        match self {
144            Self::Receipts(_) => ArtifactKind::Receipts,
145            Self::TestReport(_) => ArtifactKind::TestReport,
146            Self::ProcessSnapshot(_) => ArtifactKind::ProcessSnapshot,
147            Self::RunMarker(_) => ArtifactKind::RunMarker,
148        }
149    }
150}
151
152impl crate::tagged_union::VariantKind<ArtifactKind> for ArtifactVariant<'_> {
153    fn variant_kind(&self) -> ArtifactKind {
154        self.kind()
155    }
156}
157
158/// Closed-set discriminator over `ArtifactSource`'s four tagged-union
159/// slots. Single source of truth that drives `ArtifactSource::variant`'s
160/// ambiguity + emptiness resolver, the `ArtifactError::Empty` message,
161/// and the reverse `ArtifactVariant::kind` projection. Adding a fifth
162/// artifact variant lands at one `ALL` entry + one `as_str` arm + one
163/// `select` arm + one `ArtifactVariant::kind` arm — exhaustively
164/// checked by the compiler.
165///
166/// Sibling closed-set lifts on the same `ExportSpec` axis:
167/// [`crate::intent::IntentKind::ALL`], [`crate::lifetime::LifetimeKind::ALL`],
168/// [`ExportTrigger::ALL`], [`ReportFormat::ALL`],
169/// [`crate::lifetime::TeardownPolicy::ALL`].
170#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, tatara_closed_set::DeriveClosedSet)]
171#[closed_set(via = "as_str", display, generate_unknown)]
172pub enum ArtifactKind {
173    Receipts,
174    TestReport,
175    ProcessSnapshot,
176    RunMarker,
177}
178
179impl ArtifactKind {
180    /// The closed set of artifact kinds — single source of truth that
181    /// drives `ArtifactSource::variant`'s sweep so a variant added
182    /// without an `ALL` entry never reaches the resolver. The `[Self; 4]`
183    /// array literal forces the arity at compile time.
184    pub const ALL: [Self; 4] = [
185        Self::Receipts,
186        Self::TestReport,
187        Self::ProcessSnapshot,
188        Self::RunMarker,
189    ];
190
191    /// Canonical camelCase wire-format key — matches the serde
192    /// `rename_all = "camelCase"` field name on `ArtifactSource`. The
193    /// `ArtifactError::Empty` message composes the human-readable list
194    /// from this projection so a new variant lands in the
195    /// operator-facing diagnostic automatically via the `ALL` sweep,
196    /// not via hand-maintained error-string drift. Pinned by
197    /// `artifact_kind_as_str_matches_field_name`.
198    pub const fn as_str(self) -> &'static str {
199        match self {
200            Self::Receipts => "receipts",
201            Self::TestReport => "testReport",
202            Self::ProcessSnapshot => "processSnapshot",
203            Self::RunMarker => "runMarker",
204        }
205    }
206
207    /// Project an `ArtifactSource` borrow into the optional typed variant
208    /// view for this kind. Returns `None` iff the matching slot is
209    /// `None`. Composes the closed-set sweep `ArtifactSource::variant`
210    /// loops over. Mirrors [`crate::intent::IntentKind::select`].
211    pub fn select<'a>(self, source: &'a ArtifactSource) -> Option<ArtifactVariant<'a>> {
212        match self {
213            Self::Receipts => source.receipts.as_ref().map(ArtifactVariant::Receipts),
214            Self::TestReport => source.test_report.as_ref().map(ArtifactVariant::TestReport),
215            Self::ProcessSnapshot => source
216                .process_snapshot
217                .as_ref()
218                .map(ArtifactVariant::ProcessSnapshot),
219            Self::RunMarker => source.run_marker.as_ref().map(ArtifactVariant::RunMarker),
220        }
221    }
222}
223
224// `impl fmt::Display for ArtifactKind` + `impl FromStr for
225// ArtifactKind` + `impl tatara_lisp::ClosedSet for ArtifactKind` +
226// `pub struct UnknownArtifactKind(pub String)` are generated by
227// `#[derive(tatara_closed_set::DeriveClosedSet)]` + `#[closed_set(via =
228// "as_str", display, generate_unknown)]` on the enum declaration above.
229// The auto-derived label `"artifact kind"` matches the prior hand-
230// rolled `#[error("unknown artifact kind: {0}")]` verbatim. The
231// inherent `as_str` projection stays load-bearing — the camelCase
232// wire-format that matches the serde rename + the `ArtifactSource`
233// struct-field name + the `ArtifactError::Empty` diagnostic verbatim
234// — while the trait method `label` gives generic consumers a STABLE
235// name across the workspace-wide closed-set implementors.
236
237crate::declare_tagged_union_error! {
238    pub ArtifactError,
239    empty = "artifact source has no variant set (one of {0} required)",
240    ambiguous = "artifact source has multiple variants set; exactly one required",
241}
242
243/// Slash-joined list of every `ArtifactKind::as_str()` — composed once
244/// at compile time so `ArtifactError::Empty`'s diagnostic carries the
245/// closed-set summary without per-variant string drift. Mirrors
246/// [`crate::intent::INTENT_KIND_LIST`] in shape.
247pub(crate) const ARTIFACT_KIND_LIST: &str = "receipts/testReport/processSnapshot/runMarker";
248
249crate::declare_tagged_union_impls! {
250    parent = ArtifactSource,
251    kind = ArtifactKind,
252    variant = ArtifactVariant,
253    error = ArtifactError,
254    kind_list = ARTIFACT_KIND_LIST,
255}
256
257/// Receipts source — no fields. The worker reads every
258/// `ReceiptEnvelope` annotated with this Process's PID.
259#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]
260#[serde(rename_all = "camelCase")]
261pub struct ReceiptsSource {}
262
263/// Test report source — a ConfigMap key with optional format hint.
264#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
265#[serde(rename_all = "camelCase")]
266pub struct TestReportSource {
267    /// ConfigMap name (in the Process's namespace) the runner wrote to.
268    pub configmap: String,
269    /// Key inside the ConfigMap holding the report bytes.
270    pub key: String,
271    /// Report shape hint — downstream parsers in shinryu key off this.
272    #[serde(default)]
273    pub format: ReportFormat,
274    /// Optional ConfigMap namespace override.
275    #[serde(default, skip_serializing_if = "Option::is_none")]
276    pub namespace: Option<String>,
277}
278
279/// Process snapshot source — bundles spec + status as JSON.
280#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]
281#[serde(rename_all = "camelCase")]
282pub struct ProcessSnapshotSource {
283    /// When true, also bundle Process attestation history.
284    #[serde(default)]
285    pub include_attestation_chain: bool,
286}
287
288/// Run marker source — small synthetic event with labels.
289#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
290#[serde(rename_all = "camelCase")]
291pub struct RunMarkerSource {
292    /// Labels emitted on the marker event. Free-form key/value;
293    /// downstream consumers (shinryu cohort math, vector transforms)
294    /// read by key name.
295    #[serde(default)]
296    pub labels: BTreeMap<String, String>,
297}
298
299/// Bytes-shape hint for `TestReportSource`. Tatara emits the bytes
300/// untransformed and tags the Vector event with this so shinryu
301/// can route to the right parser tier.
302#[derive(
303    Clone,
304    Copy,
305    Debug,
306    PartialEq,
307    Eq,
308    Hash,
309    Serialize,
310    Deserialize,
311    JsonSchema,
312    Default,
313    tatara_closed_set::DeriveClosedSet,
314)]
315#[serde(rename_all = "PascalCase")]
316#[closed_set(via = "as_str", generate_unknown)]
317pub enum ReportFormat {
318    /// xUnit / JUnit XML — the closed-loop probe + test harnesses emit this.
319    Junit,
320    /// TAP v13 — Bash/Bats test suites.
321    TapV13,
322    /// Newline-delimited JSON — one event per line, native shinryu shape.
323    NdJson,
324    /// Opaque bytes — no parser hint, downstream stores as-is.
325    #[default]
326    Raw,
327}
328
329/// How the export worker should embed a `TestReportSource`'s bytes into
330/// the shipped `ExportEvent.payload`. Lifted as a closed-set typed
331/// projection from [`ReportFormat::payload_shape`] so the worker's
332/// dispatch is exhaustive on `ReportPayloadShape`, not on `ReportFormat`
333/// with a silent `_` arm. Adding a future `ReportFormat` variant forces
334/// the author to pick its shape here (single edit site); adding a
335/// future shape (e.g. compressed) forces every consumer to handle it.
336///
337/// The (shape, JSON-embed-field) pairing — `NdJsonLines` → `"ndjson"`,
338/// `OpaqueBytes` → `"raw_b64"` — binds at ONE typed projection
339/// ([`Self::payload_field`]) rather than at the future worker's
340/// embed-site string literals; pre-lift the field names lived in this
341/// enum's per-variant docstring prose AND would have lived at the
342/// worker's `payload.insert("ndjson", …)` / `payload.insert("raw_b64",
343/// …)` call sites, where a rename of `"raw_b64"` → `"raw"` at one site
344/// drifts silently from the docstring and the operator-facing shinryu
345/// schema.
346///
347/// Sibling typed-projection lift over a closed enum (rather than
348/// `matches!` / `_` arm dispatch):
349/// [`crate::lifetime::TeardownPolicy::should_teardown_on`],
350/// [`ExportTrigger::fires_on`], [`crate::phase::ProcessPhase::as_str`].
351///
352/// Sibling closed-set [`Self::ALL`] lift in lockstep with every other
353/// `ALL`-keyed enum on the same `ExportSpec` axis ([`ReportFormat::ALL`],
354/// [`ExportTrigger::ALL`]) and across the crate
355/// ([`crate::phase::ProcessPhase::ALL`],
356/// [`crate::signal::ProcessSignal::ALL`],
357/// [`crate::boundary::ConditionKind::ALL`],
358/// [`crate::lifetime::TeardownPolicy::ALL`]).
359#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, tatara_closed_set::DeriveClosedSet)]
360#[closed_set(via = "as_str", display, generate_unknown = "report payload shape")]
361pub enum ReportPayloadShape {
362    /// Newline-delimited JSON — split on `\n`, parse each non-empty
363    /// line as JSON, embed under `payload.<payload_field>` (=
364    /// `payload.ndjson`) as an array. Field name is the typed
365    /// projection [`Self::payload_field`] so no embed-site literal
366    /// can drift from this docstring.
367    NdJsonLines,
368    /// Opaque bytes — base64-encode the artifact verbatim, embed
369    /// under `payload.<payload_field>` (= `payload.raw_b64`) as a
370    /// string. The default shape for anything Vector / shinryu
371    /// shouldn't pre-parse. Field name is the typed projection
372    /// [`Self::payload_field`].
373    OpaqueBytes,
374}
375
376impl ReportPayloadShape {
377    /// The closed set of payload shapes — single source of truth
378    /// that drives the [`Self::as_str`] / [`fmt::Display`] pair and
379    /// the [`Self::payload_field`] projection. Adding a third shape
380    /// (e.g. `Compressed` → `"gzip"`) lands at one `ALL` entry + one
381    /// `as_str` arm + one `payload_field` arm + at least one new
382    /// `ReportFormat::payload_shape` mapping — exhaustively checked
383    /// by the compiler (the `[Self; 2]` array literal forces the
384    /// arity).
385    ///
386    /// Sibling closed-set lifts on the same `ExportSpec` axis:
387    /// [`ReportFormat::ALL`], [`ExportTrigger::ALL`],
388    /// [`crate::lifetime::TeardownPolicy::ALL`],
389    /// [`crate::boundary::ConditionKind::ALL`],
390    /// [`crate::phase::ProcessPhase::ALL`],
391    /// [`crate::signal::ProcessSignal::ALL`],
392    /// [`crate::encapsulates::EncapsulationMode::ALL`].
393    pub const ALL: [Self; 2] = [Self::NdJsonLines, Self::OpaqueBytes];
394
395    /// Canonical PascalCase identifier — used by [`fmt::Display`] (so
396    /// `format!("{shape}")` never reaches for `{:?}` Debug) and as
397    /// the operator-facing reason-string projection. No serde wire
398    /// shape today (the enum is worker-internal), but the identifier
399    /// matches the sibling-aligned `as_str` shape that every other
400    /// closed-set enum in this crate exposes
401    /// ([`ReportFormat::as_str`], [`ExportTrigger::as_str`],
402    /// [`crate::phase::ProcessPhase::as_str`]). Pinned by
403    /// `report_payload_shape_as_str_unique_per_variant`.
404    pub const fn as_str(self) -> &'static str {
405        match self {
406            Self::NdJsonLines => "NdJsonLines",
407            Self::OpaqueBytes => "OpaqueBytes",
408        }
409    }
410
411    /// The JSON field name within `ExportEvent.payload` where the
412    /// worker embeds this shape's encoded value — `"ndjson"` for the
413    /// newline-split array, `"raw_b64"` for the base64-encoded
414    /// opaque string. Pre-lift these names lived ONLY in the
415    /// per-variant docstring prose; once the export worker lands, a
416    /// rename at the embed site (`payload.insert("ndjson", …)` →
417    /// `payload.insert("events", …)`) silently drifts from the
418    /// docstring and from the operator-facing shinryu schema with no
419    /// compile or runtime signal. Post-lift the worker's embed site
420    /// is `payload.insert(shape.payload_field().into(), …)` and a
421    /// rename lands at ONE arm here. The per-variant uniqueness
422    /// invariant (no two shapes alias to the same field name) is
423    /// pinned by `report_payload_shape_payload_field_unique_per_
424    /// variant` so the worker's embed site cannot have two shapes
425    /// collide on the same destination key. Truth table pinned by
426    /// `report_payload_shape_payload_field_truth_table` so a future
427    /// rename lands here at one site, not in the worker.
428    pub const fn payload_field(self) -> &'static str {
429        match self {
430            Self::NdJsonLines => "ndjson",
431            Self::OpaqueBytes => "raw_b64",
432        }
433    }
434}
435
436// `impl fmt::Display for ReportPayloadShape` +
437// `impl std::str::FromStr for ReportPayloadShape` +
438// `impl tatara_lisp::ClosedSet for ReportPayloadShape` +
439// `pub struct UnknownReportPayloadShape(pub String)` are all generated
440// by `#[derive(tatara_closed_set::DeriveClosedSet)]` +
441// `#[closed_set(via = "as_str", display, generate_unknown = "report
442// payload shape")]` on the enum declaration above. The explicit label
443// preserves the natural spelling "report payload shape" against the
444// auto-projection `pascal_to_spaced_lowercase("ReportPayloadShape")`.
445// The inherent `as_str` projection stays load-bearing — the canonical
446// `"NdJsonLines" | "OpaqueBytes"` string every worker-facing reason
447// projection reads; `via = "as_str"` binds `ClosedSet::label` to the
448// same projection so the substrate-wide
449// `assert_display_matches_label` / `assert_closed_set_well_formed`
450// primitives dispatch through the same byte-identical shape every other
451// closed-set implementor across the crate publishes. Aligns
452// `ReportPayloadShape` with the substrate-wide
453// `#[derive(DeriveClosedSet)]` idiom that every sibling closed-set enum
454// on this `ExportSpec` axis (`ReportFormat`, `ArtifactKind`,
455// `ChannelKind`, `ExportTrigger`) already carries — the last hand-rolled
456// `impl fmt::Display` on the axis is closed at ONE substrate site.
457
458impl ReportFormat {
459    /// The closed set of report formats — single source of truth that
460    /// drives the `as_str` / Display / `FromStr` triad and the typed
461    /// `payload_shape` dispatch. Adding a fifth variant lands at one
462    /// `ALL` entry + one `as_str` arm + one `payload_shape` arm —
463    /// exhaustively checked by the compiler (the `[Self; 4]` array
464    /// literal forces the arity).
465    ///
466    /// Sibling closed-set lifts on the same `ExportSpec` axis:
467    /// [`ExportTrigger::ALL`], [`crate::lifetime::TeardownPolicy::ALL`],
468    /// [`crate::boundary::ConditionKind::ALL`],
469    /// [`crate::phase::ProcessPhase::ALL`],
470    /// [`crate::signal::ProcessSignal::ALL`],
471    /// [`crate::encapsulates::EncapsulationMode::ALL`].
472    pub const ALL: [Self; 4] = [Self::Junit, Self::TapV13, Self::NdJson, Self::Raw];
473
474    /// Canonical PascalCase wire-format projection — matches the serde
475    /// `rename_all = "PascalCase"` output verbatim. Used by Display
476    /// (single source of truth) and by `FromStr`'s sweep of `ALL` so
477    /// the typed surface and the YAML wire format cannot drift. Pinned
478    /// by `report_format_as_str_matches_serde`.
479    pub const fn as_str(self) -> &'static str {
480        match self {
481            Self::Junit => "Junit",
482            Self::TapV13 => "TapV13",
483            Self::NdJson => "NdJson",
484            Self::Raw => "Raw",
485        }
486    }
487
488    /// Typed projection: which payload-embedding strategy the export
489    /// worker should pick for this format. ONE typed dispatch that
490    /// replaces the worker's `match tr.format { NdJson => …, _ => … }`
491    /// silent-default arm. Adding a new `ReportFormat` variant forces
492    /// the author to decide its shape here (the compiler exhaustively
493    /// checks this match); the worker's dispatch on the returned
494    /// `ReportPayloadShape` then remains a closed 2-arm match. Pinned
495    /// by `report_format_payload_shape_truth_table`.
496    pub const fn payload_shape(self) -> ReportPayloadShape {
497        match self {
498            Self::NdJson => ReportPayloadShape::NdJsonLines,
499            Self::Junit | Self::TapV13 | Self::Raw => ReportPayloadShape::OpaqueBytes,
500        }
501    }
502}
503
504impl fmt::Display for ReportFormat {
505    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
506        f.write_str(self.as_str())
507    }
508}
509
510// `impl FromStr for ReportFormat` + `impl tatara_lisp::ClosedSet for
511// ReportFormat` are generated by `#[derive(tatara_closed_set::DeriveClosedSet)]`
512// on the enum declaration above — the trait-impl plumbing collapses onto
513// ONE derive line + the `#[closed_set(via = "as_str")]` attribute that
514// names the inherent projection method. Per-implementor content stays
515// at `pub const ALL` + `pub const fn as_str` + the `UnknownReportFormat`
516// carrier below.
517
518// `pub struct UnknownReportFormat(pub String)` is generated by
519// `#[derive(tatara_closed_set::DeriveClosedSet)]` + `#[closed_set(generate_unknown)]`
520// on the enum declaration above. The auto-derived label `"report format"`
521// matches the prior hand-rolled `#[error("unknown report format: {0}")]`
522// verbatim. Symmetric to [`UnknownChannelKind`],
523// [`UnknownExportTrigger`], [`crate::lifetime::UnknownTeardownPolicy`],
524// [`crate::boundary::UnknownConditionKind`],
525// [`crate::phase::UnknownPhase`].
526
527// ─── VectorChannel ─────────────────────────────────────────────────
528
529/// Where the export bytes flow.
530///
531/// All variants land in the pleme-io Vector + NATS layer — there is
532/// no escape hatch for ad-hoc sinks. Vector's downstream sink graph
533/// (file / Loki / VictoriaLogs / VictoriaMetrics) handles durability
534/// + analytics. This primitive only names the *ingestion* shape.
535#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
536#[serde(rename_all = "camelCase")]
537pub struct VectorChannel {
538    /// HTTP POST to Vector's `http_server` source.
539    #[serde(default, skip_serializing_if = "Option::is_none")]
540    pub http_event: Option<HttpEventChannel>,
541
542    /// Publish to a NATS JetStream subject. Use for tier-1
543    /// guaranteed-delivery events (receipts that MUST survive).
544    #[serde(default, skip_serializing_if = "Option::is_none")]
545    pub nats_subject: Option<NatsSubjectChannel>,
546
547    /// Print to the export worker's stdout. Vector's
548    /// `kubernetes_logs` source picks it up. Lowest-effort channel;
549    /// fine for debug / one-off exports.
550    #[serde(default, skip_serializing_if = "Option::is_none")]
551    pub stdout: Option<StdoutChannel>,
552}
553
554#[derive(Clone, Debug)]
555pub enum ChannelVariant<'a> {
556    HttpEvent(&'a HttpEventChannel),
557    NatsSubject(&'a NatsSubjectChannel),
558    Stdout(&'a StdoutChannel),
559}
560
561impl ChannelVariant<'_> {
562    /// Reverse projection — every borrowed channel variant knows its
563    /// `ChannelKind` discriminator. Pairs with [`ChannelKind::select`]
564    /// so `ChannelKind::select(channel).map(|v| v.kind())` round-trips
565    /// the closed set on the populated side; pinned by
566    /// `channel_kind_round_trips_through_variant_kind`. Future
567    /// kind-keyed consumers (metric labels like
568    /// `tatara_exports_total{channel="natsSubject"}`, status-condition
569    /// reason strings, audit-trail classifiers, LSP completion) reach
570    /// through this projection instead of pattern-matching the
571    /// payload-carrying view. Mirrors
572    /// [`ArtifactVariant::kind`] and [`crate::intent::IntentVariant::kind`].
573    pub fn kind(&self) -> ChannelKind {
574        match self {
575            Self::HttpEvent(_) => ChannelKind::HttpEvent,
576            Self::NatsSubject(_) => ChannelKind::NatsSubject,
577            Self::Stdout(_) => ChannelKind::Stdout,
578        }
579    }
580}
581
582impl crate::tagged_union::VariantKind<ChannelKind> for ChannelVariant<'_> {
583    fn variant_kind(&self) -> ChannelKind {
584        self.kind()
585    }
586}
587
588/// Closed-set discriminator over `VectorChannel`'s three tagged-union
589/// slots. Single source of truth that drives `VectorChannel::variant`'s
590/// ambiguity + emptiness resolver, the `ChannelError::Empty` message,
591/// and the reverse `ChannelVariant::kind` projection. Adding a fourth
592/// channel variant lands at one `ALL` entry + one `as_str` arm + one
593/// `select` arm + one `ChannelVariant::kind` arm — exhaustively
594/// checked by the compiler.
595///
596/// Sibling closed-set lifts on the same `ExportSpec` axis:
597/// [`ArtifactKind::ALL`], [`ExportTrigger::ALL`], [`ReportFormat::ALL`],
598/// [`ReportPayloadShape::ALL`].
599#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, tatara_closed_set::DeriveClosedSet)]
600#[closed_set(via = "as_str", generate_unknown)]
601pub enum ChannelKind {
602    HttpEvent,
603    NatsSubject,
604    Stdout,
605}
606
607impl ChannelKind {
608    /// The closed set of channel kinds — single source of truth that
609    /// drives `VectorChannel::variant`'s sweep so a variant added
610    /// without an `ALL` entry never reaches the resolver. The
611    /// `[Self; 3]` array literal forces the arity at compile time.
612    pub const ALL: [Self; 3] = [Self::HttpEvent, Self::NatsSubject, Self::Stdout];
613
614    /// Canonical camelCase wire-format key — matches the serde
615    /// `rename_all = "camelCase"` field name on `VectorChannel`. The
616    /// `ChannelError::Empty` message composes the human-readable list
617    /// from this projection so a new variant lands in the
618    /// operator-facing diagnostic automatically via the `ALL` sweep,
619    /// not via hand-maintained error-string drift. Pinned by
620    /// `channel_kind_as_str_matches_field_name`.
621    pub const fn as_str(self) -> &'static str {
622        match self {
623            Self::HttpEvent => "httpEvent",
624            Self::NatsSubject => "natsSubject",
625            Self::Stdout => "stdout",
626        }
627    }
628
629    /// Project a `VectorChannel` borrow into the optional typed variant
630    /// view for this kind. Returns `None` iff the matching slot is
631    /// `None`. Composes the closed-set sweep `VectorChannel::variant`
632    /// loops over. Mirrors [`ArtifactKind::select`] +
633    /// [`crate::intent::IntentKind::select`] +
634    /// [`crate::lifetime::LifetimeKind::select`].
635    pub fn select<'a>(self, channel: &'a VectorChannel) -> Option<ChannelVariant<'a>> {
636        match self {
637            Self::HttpEvent => channel.http_event.as_ref().map(ChannelVariant::HttpEvent),
638            Self::NatsSubject => channel
639                .nats_subject
640                .as_ref()
641                .map(ChannelVariant::NatsSubject),
642            Self::Stdout => channel.stdout.as_ref().map(ChannelVariant::Stdout),
643        }
644    }
645}
646
647impl fmt::Display for ChannelKind {
648    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
649        f.write_str(self.as_str())
650    }
651}
652
653// `impl FromStr for ChannelKind` + `impl tatara_lisp::ClosedSet for
654// ChannelKind` are generated by `#[derive(tatara_closed_set::DeriveClosedSet)]`
655// on the enum declaration above.
656
657// `pub struct UnknownChannelKind(pub String)` is generated by
658// `#[derive(tatara_closed_set::DeriveClosedSet)]` + `#[closed_set(generate_unknown)]`
659// on the enum declaration above. The auto-derived label `"channel kind"`
660// matches the prior hand-rolled `#[error("unknown channel kind: {0}")]`
661// verbatim. Symmetric to [`UnknownArtifactKind`],
662// [`UnknownReportFormat`], [`UnknownExportTrigger`],
663// [`crate::lifetime::UnknownTeardownPolicy`],
664// [`crate::boundary::UnknownConditionKind`],
665// [`crate::phase::UnknownPhase`].
666
667crate::declare_tagged_union_error! {
668    pub ChannelError,
669    empty = "vector channel has no variant set (one of {0} required)",
670    ambiguous = "vector channel has multiple variants set; exactly one required",
671}
672
673/// Slash-joined list of every `ChannelKind::as_str()` — composed once
674/// at compile time so `ChannelError::Empty`'s diagnostic carries the
675/// closed-set summary without per-variant string drift. Mirrors
676/// [`ARTIFACT_KIND_LIST`] in shape.
677pub(crate) const CHANNEL_KIND_LIST: &str = "httpEvent/natsSubject/stdout";
678
679crate::declare_tagged_union_impls! {
680    parent = VectorChannel,
681    kind = ChannelKind,
682    variant = ChannelVariant,
683    error = ChannelError,
684    kind_list = CHANNEL_KIND_LIST,
685}
686
687/// HTTP POST channel — Vector `http_server` source.
688#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
689#[serde(rename_all = "camelCase")]
690pub struct HttpEventChannel {
691    /// Vector ingest endpoint. Defaults to the in-cluster Service
692    /// `http://vector.observability.svc.cluster.local:8080` when
693    /// unset.
694    #[serde(default, skip_serializing_if = "Option::is_none")]
695    pub endpoint: Option<String>,
696
697    /// `signal_type` tag added to every emitted event. Vector
698    /// transforms + shinryu's analytical schema route by this tag
699    /// (`receipt`, `test-report`, `ephemeral-marker`, …).
700    pub signal_type: String,
701}
702
703/// Default Vector ingest endpoint when `HttpEventChannel.endpoint`
704/// is unset. Single source of truth for downstream tooling.
705pub const DEFAULT_VECTOR_INGEST: &str = "http://vector.observability.svc.cluster.local:8080";
706
707impl HttpEventChannel {
708    /// Resolve the endpoint URL, falling back to the in-cluster default.
709    pub fn resolved_endpoint(&self) -> &str {
710        self.endpoint.as_deref().unwrap_or(DEFAULT_VECTOR_INGEST)
711    }
712
713    /// Compose an [`HttpEventChannel`] pinned to the in-cluster
714    /// default endpoint ([`DEFAULT_VECTOR_INGEST`], stamped as
715    /// `endpoint: None` so [`Self::resolved_endpoint`] resolves the
716    /// same URL on read) and the given `signal_type` — the ONE
717    /// substrate primitive owning the 4-token `HttpEventChannel {
718    /// endpoint: None, signal_type: <s>.into() }` fixture literal every
719    /// consumer restated by hand pre-lift.
720    ///
721    /// Pre-lift the same 3-slot chain (`endpoint: None`,
722    /// `signal_type: <label>.into()`, `..` for the two-slot struct's
723    /// non-existent tail) was hand-authored at TEN workspace-wide
724    /// sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold,
725    /// EVERY one of them the "default-endpoint, name-only" shape (no
726    /// site in the workspace stamps a non-None endpoint on an
727    /// `HttpEventChannel` literal):
728    ///
729    /// * `tatara-process::export` — five sites: two closed-set
730    ///   ambiguity probes (`single_slot_channel`, the
731    ///   `kind_equals_variant_kind_across_all_carriers` probe), plus
732    ///   three round-trip tests (`vector_channel_resolves_http_event`,
733    ///   `export_spec_serde_round_trip`, `run_marker_labels_round_trip`).
734    /// * `tatara-process::lifetime` — two round-trip tests
735    ///   (`applicable_exports_filters_by_trigger`,
736    ///   `exports_round_trip_through_lifetime`).
737    /// * `tatara-process::tagged_union` — two closed-set property probes
738    ///   (`single_slot_vector_channel_probe`,
739    ///   `kind_equals_variant_kind_across_all_carriers`).
740    /// * `tatara-reconciler::render` — the `spec_run_marker_always`
741    ///   fixture the ephemeral-export-Job renderer's tests seed.
742    /// * `tatara-export-worker::lib` — the `http_spec` fixture the
743    ///   worker's `resolve_run_id` tests seed.
744    ///
745    /// Post-lift every callsite reads `HttpEventChannel::signal(<label>)`
746    /// and the two-slot struct's `endpoint` slot stays owned by the ONE
747    /// substrate site. The `impl Into<String>` bound accepts every
748    /// pre-lift caller shape verbatim — `&'static str` literals
749    /// (`"receipt"`, `"test-report"`, `"ephemeral-marker"`), owned
750    /// `String` values (worker fixture's `signal_type.to_string()`), and
751    /// `.into()`-terminated chains alike — without a per-site coercion.
752    ///
753    /// A future addition (a default-endpoint override for a
754    /// per-fleet Vector ingress, a `signal_type` normalization step
755    /// clamping the tag to shinryu's allowed set, a per-fleet
756    /// `endpoint` seed pulled from a config surface) lands at THIS
757    /// ONE substrate primitive and every downstream consumer inherits
758    /// the upgrade mechanically — no per-site edit at any of the ten
759    /// listed callers or at future test fixtures.
760    ///
761    /// Theory anchor: THEORY.md §VI.1 (generation over composition —
762    /// the `HttpEventChannel { endpoint: None, signal_type: <s>.into() }`
763    /// fixture literal recurred at ten hand-authored sites past the
764    /// ★★ PRIME-DIRECTIVE ≥ 2 duplication trigger, and is lifted to
765    /// ONE owner here). THEORY.md §II.1 invariant 5 (composition
766    /// preserves proofs — a regression that drifted the default-
767    /// endpoint sentinel from `None` to a hardcoded string, or
768    /// reordered the two struct slots, surfaces at the
769    /// `signal_composes_byte_identical_to_pre_lift_literal_across_every_label`
770    /// pin below rather than as silent skew at every downstream fixture).
771    #[must_use]
772    pub fn signal(signal_type: impl Into<String>) -> Self {
773        Self {
774            endpoint: None,
775            signal_type: signal_type.into(),
776        }
777    }
778}
779
780/// NATS JetStream channel — guaranteed-delivery publish.
781#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
782#[serde(rename_all = "camelCase")]
783pub struct NatsSubjectChannel {
784    /// Subject to publish to. May contain `{{run_id}}` template
785    /// substitution — the worker substitutes the resolved run id at
786    /// publish time.
787    pub subject: String,
788
789    /// JetStream stream the subject belongs to. The stream itself is
790    /// declared by the consumer chart (e.g. tatara-pool-reconciler)
791    /// via the pleme-nats broker-only design.
792    pub stream: String,
793
794    /// Optional NATS URL. Defaults to `nats://nats.observability.svc.cluster.local:4222`.
795    #[serde(default, skip_serializing_if = "Option::is_none")]
796    pub url: Option<String>,
797}
798
799/// Default NATS URL when `NatsSubjectChannel.url` is unset.
800pub const DEFAULT_NATS_URL: &str = "nats://nats.observability.svc.cluster.local:4222";
801
802impl NatsSubjectChannel {
803    /// Resolve the NATS URL, falling back to the in-cluster default.
804    pub fn resolved_url(&self) -> &str {
805        self.url.as_deref().unwrap_or(DEFAULT_NATS_URL)
806    }
807
808    /// Compose a [`NatsSubjectChannel`] pinned to the in-cluster
809    /// default NATS URL ([`DEFAULT_NATS_URL`], stamped as `url: None`
810    /// so [`Self::resolved_url`] resolves the same URL on read) and
811    /// the given `subject` + `stream` — the ONE substrate primitive
812    /// owning the 3-slot `NatsSubjectChannel { subject, stream, url:
813    /// None }` fixture literal every consumer restated by hand
814    /// pre-lift.
815    ///
816    /// Pre-lift the same 3-slot chain (`subject: <s>.into()`,
817    /// `stream: <s>.into()`, `url: None`) was hand-authored at SIX
818    /// workspace-wide sites past the ★★ PRIME-DIRECTIVE ≥ 2
819    /// duplication threshold, EVERY one of them the "default-URL,
820    /// subject-and-stream-only" shape (no site in the workspace
821    /// stamps a non-None `url` on a `NatsSubjectChannel` literal):
822    ///
823    /// * `tatara-process::export` — two sites: the
824    ///   `vector_channel_resolves_nats_subject` round-trip fixture
825    ///   plus the `single_slot_channel` closed-set ambiguity probe.
826    /// * `tatara-process::tagged_union` — one site: the
827    ///   `single_slot_vector_channel_probe` closed-set probe (the
828    ///   sibling of the `single_slot_channel` fixture in
829    ///   `tatara-process::export`).
830    /// * `tatara-reconciler::render` — one site: the
831    ///   `spec_receipts_attested` fixture the ephemeral-export-Job
832    ///   renderer's tests seed.
833    /// * `tatara-export-worker::lib` — two sites: the
834    ///   `subject_substitutes_run_id_template` template-substitution
835    ///   fixture plus the `subject_passthrough_when_no_template`
836    ///   passthrough fixture.
837    ///
838    /// Post-lift every callsite reads
839    /// `NatsSubjectChannel::publish(<subject>, <stream>)` and the
840    /// three-slot struct's `url` slot stays owned by the ONE
841    /// substrate site. The `impl Into<String>` bound on both
842    /// positional args accepts every pre-lift caller shape verbatim
843    /// — `&'static str` literals (`"S"`, `"EPHEMERAL_RECEIPTS"`,
844    /// `"pleme.pleme-dev.ephemeral.{{run_id}}.receipt"`), owned
845    /// `String` values, and `.into()`-terminated chains alike —
846    /// without a per-site coercion.
847    ///
848    /// A future addition (a default-URL override for a per-fleet
849    /// NATS endpoint, a per-fleet `subject` prefix normalization, a
850    /// `stream` clamp against a shinryu-registered stream catalog,
851    /// or an authenticated NATS URL seed pulled from a config
852    /// surface) lands at THIS ONE substrate primitive and every
853    /// downstream consumer inherits the upgrade mechanically — no
854    /// per-site edit at any of the six listed callers or at future
855    /// test fixtures.
856    ///
857    /// Theory anchor: THEORY.md §VI.1 (generation over composition —
858    /// the `NatsSubjectChannel { subject, stream, url: None }`
859    /// fixture literal recurred at six hand-authored sites past the
860    /// ★★ PRIME-DIRECTIVE ≥ 2 duplication trigger, and is lifted to
861    /// ONE owner here). THEORY.md §II.1 invariant 5 (composition
862    /// preserves proofs — a regression that drifted the default-URL
863    /// sentinel from `None` to a hardcoded string, or reordered the
864    /// three struct slots, surfaces at the
865    /// `publish_composes_byte_identical_to_pre_lift_literal_across_every_subject_stream_pair`
866    /// pin below rather than as silent skew at every downstream
867    /// fixture).
868    #[must_use]
869    pub fn publish(subject: impl Into<String>, stream: impl Into<String>) -> Self {
870        Self {
871            subject: subject.into(),
872            stream: stream.into(),
873            url: None,
874        }
875    }
876}
877
878/// Stdout channel — worker prints the event; Vector picks up via
879/// `kubernetes_logs`.
880#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]
881#[serde(rename_all = "camelCase")]
882pub struct StdoutChannel {
883    /// Pretty-print JSON (multi-line) instead of compact NDJSON.
884    /// Defaults to false — compact NDJSON matches Vector's parser.
885    #[serde(default)]
886    pub pretty: bool,
887}
888
889// ─── ExportTrigger ─────────────────────────────────────────────────
890
891/// When the export fires. Aligns with `ProcessPhase` so the
892/// reconciler's `Releasing` phase can match against the terminal
893/// phase reached directly.
894#[derive(
895    Clone,
896    Copy,
897    Debug,
898    PartialEq,
899    Eq,
900    Hash,
901    Serialize,
902    Deserialize,
903    JsonSchema,
904    Default,
905    tatara_closed_set::DeriveClosedSet,
906)]
907#[serde(rename_all = "PascalCase")]
908#[closed_set(via = "as_str", generate_unknown)]
909pub enum ExportTrigger {
910    /// Fire when the Process reaches `Attested`. Default — matches
911    /// the most common case (capture successful-run artifacts).
912    #[default]
913    OnAttested,
914    /// Fire when the Process reaches `Failed`. Use for failure
915    /// post-mortems (process snapshots, last receipts).
916    OnFailed,
917    /// Fire on every terminal phase (`Attested` or `Failed`). Use
918    /// for run markers that need to surface regardless of outcome.
919    Always,
920}
921
922impl ExportTrigger {
923    /// The closed set of export triggers — single source of truth that
924    /// drives the `as_str` / Display / `FromStr` triad and the typed
925    /// `fires_on` dispatch over `ProcessPhase`. Adding a fourth variant
926    /// lands at one `ALL` entry + one `as_str` arm + one `fires_on` arm
927    /// — exhaustively checked by the compiler (the `[Self; 3]` array
928    /// literal forces the arity).
929    ///
930    /// Sibling closed-set lifts on the same `ProcessSpec` axis:
931    /// [`crate::lifetime::TeardownPolicy::ALL`],
932    /// [`crate::intent::IntentKind::ALL`],
933    /// [`crate::lifetime::LifetimeKind::ALL`],
934    /// [`crate::boundary::ConditionKind::ALL`],
935    /// [`crate::phase::ProcessPhase::ALL`],
936    /// [`crate::signal::ProcessSignal::ALL`].
937    pub const ALL: [Self; 3] = [Self::OnAttested, Self::OnFailed, Self::Always];
938
939    /// Canonical PascalCase wire-format projection — matches the serde
940    /// `rename_all = "PascalCase"` output verbatim. Used by Display
941    /// (single source of truth), by `FromStr` to identify the variant
942    /// from its annotation / status-field representation, and by
943    /// operator-facing reason strings without reaching for `{:?}` Debug
944    /// formatting. Pinned by `export_trigger_as_str_matches_serde`.
945    pub const fn as_str(self) -> &'static str {
946        match self {
947            Self::OnAttested => "OnAttested",
948            Self::OnFailed => "OnFailed",
949            Self::Always => "Always",
950        }
951    }
952
953    /// True iff, given a `ProcessPhase`, this trigger says "fire."
954    /// ONE typed dispatch over the typed phase enum that replaces the
955    /// four hand-rolled `match phase { Attested => fires_on_attested(),
956    /// Failed => fires_on_failed(), _ => false }` sites the reconciler
957    /// and `EphemeralLifetime` previously branched on. Every
958    /// non-terminal phase always returns `false` — exports are a
959    /// terminal-phase decision, now enforced by the closed-set match
960    /// over `ProcessPhase`.
961    ///
962    /// The legacy [`Self::fires_on_attested`] / [`Self::fires_on_failed`]
963    /// predicates remain as thin delegates so existing call sites keep
964    /// their narrow signatures; the truth table is pinned by
965    /// `export_trigger_legacy_predicates_delegate_to_phase_dispatch`.
966    pub const fn fires_on(self, phase: ProcessPhase) -> bool {
967        match phase {
968            ProcessPhase::Attested => matches!(self, Self::OnAttested | Self::Always),
969            ProcessPhase::Failed => matches!(self, Self::OnFailed | Self::Always),
970            ProcessPhase::Pending
971            | ProcessPhase::Forking
972            | ProcessPhase::Execing
973            | ProcessPhase::Running
974            | ProcessPhase::Reconverging
975            | ProcessPhase::Releasing
976            | ProcessPhase::Exiting
977            | ProcessPhase::Zombie
978            | ProcessPhase::Reaped => false,
979        }
980    }
981
982    /// Thin delegate to [`Self::fires_on`] for the `Attested` case —
983    /// kept so existing call sites that already know the gate keep
984    /// their narrow signature without reaching for the typed-phase
985    /// variant.
986    pub const fn fires_on_attested(self) -> bool {
987        self.fires_on(ProcessPhase::Attested)
988    }
989
990    /// Symmetric delegate to [`Self::fires_on`] for the `Failed` case.
991    pub const fn fires_on_failed(self) -> bool {
992        self.fires_on(ProcessPhase::Failed)
993    }
994}
995
996impl fmt::Display for ExportTrigger {
997    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
998        f.write_str(self.as_str())
999    }
1000}
1001
1002// `impl FromStr for ExportTrigger` + `impl tatara_lisp::ClosedSet for
1003// ExportTrigger` are generated by `#[derive(tatara_closed_set::DeriveClosedSet)]`
1004// on the enum declaration above.
1005
1006// `pub struct UnknownExportTrigger(pub String)` is generated by
1007// `#[derive(tatara_closed_set::DeriveClosedSet)]` + `#[closed_set(generate_unknown)]`
1008// on the enum declaration above. The auto-derived label `"export trigger"`
1009// matches the prior hand-rolled `#[error("unknown export trigger: {0}")]`
1010// verbatim. Symmetric to [`UnknownChannelKind`],
1011// [`UnknownReportFormat`], [`crate::lifetime::UnknownTeardownPolicy`],
1012// [`crate::boundary::UnknownConditionKind`], and
1013// [`crate::phase::UnknownPhase`].
1014
1015/// Slice-level `(ExportTrigger, presence)` probe on any `&[ExportSpec]`
1016/// — the ONE substrate primitive that owns the
1017/// `.iter().any(|e| e.when == K)` walk shape for the export-spec
1018/// vector. Callers compose the answer they want on top:
1019/// `spec.lifetime.resolved_ephemeral().is_some_and(|e|
1020/// e.exports.has_when(kind))` for the point-domain
1021/// `export-when-<kind>` require-tag family, a coherence check that
1022/// verifies "every `Always` export ships through a JetStream-backed
1023/// channel", an editor completion listing which
1024/// [`ExportTrigger`] gates the operator authored — every future
1025/// consumer reaches this ONE primitive through
1026/// `slice.has_when(k)` instead of restating the `.iter().any`
1027/// closure body.
1028///
1029/// # Fourth instance in the slice-level presence-probe algebra
1030///
1031/// Same axis, same shape, fourth instance in the workspace-wide
1032/// slice-level closed-set-driven presence-probe algebra alongside
1033/// [`crate::boundary::ConditionSliceExt::has_kind`] on `&[Condition]`,
1034/// [`crate::spec::DependsOnSliceExt::has_must_reach`] on
1035/// `&[DependsOn]`, and
1036/// [`crate::compliance::ComplianceBindingSliceExt::has_verification_phase`]
1037/// on `&[ComplianceBinding]`. All four live one composition boundary
1038/// below the tagged-union-parent probes ([`crate::intent::Intent::has`],
1039/// [`crate::lifetime::Lifetime::has`],
1040/// [`crate::boundary::Boundary::has_condition_kind`]) at the
1041/// (`&self`, `K`) → `bool` signature; a future normalization at the
1042/// slice-level probe shape (widening the return to
1043/// `Option<&ExportSpec>` for deeper diagnostics, adding a debug-build
1044/// assertion on redundant duplicate `(source, channel)` pairs at the
1045/// same trigger, switching to a linear scan that also counts matches)
1046/// lands at ONE site here and every downstream
1047/// `slice.has_when(K)` callsite picks it up mechanically.
1048///
1049/// # Compounding
1050///
1051/// The `export-when-<kind>` and `channel-<kind>` require-tag prefix
1052/// families in `tatara-reconciler::bin::tatara-check` compose the
1053/// [`Self::has_when`] and [`Self::has_channel_kind`] primitives with
1054/// the closed-set `FromStr` autoderived on [`ExportTrigger`] +
1055/// [`ChannelKind`] through the `strip_and_classify_prefixed_kind`
1056/// substrate to publish the SEVENTH + EIGHTH closed-set-driven prefix
1057/// families byte-for-byte symmetrical with `intent-<kind>` /
1058/// `lifetime-<kind>` / `condition-<kind>` / `must-reach-<kind>` /
1059/// `sighup-<kind>` / `verification-phase-<kind>`. The
1060/// `resolved_ephemeral` projection on the parent
1061/// [`crate::lifetime::Lifetime`] gates BOTH walks: a permanent
1062/// Process (or an ambiguous one, or one without an `:exports` slot)
1063/// returns `false` for every trigger kind AND every channel kind
1064/// because the exports vector isn't reachable — the same operator-
1065/// facing answer as a resolved-ephemeral spec whose `exports` slot is
1066/// present but empty. A future fourth [`ExportTrigger`] variant added
1067/// to [`ExportTrigger::ALL`] (a hypothetical `OnFirstFailure` retry-
1068/// scoped trigger, an `OnAborted` cancellation-scoped trigger)
1069/// reaches every downstream through the SAME closed-set walk with no
1070/// per-caller edit, as does a future fourth [`ChannelKind`] variant
1071/// added to [`ChannelKind::ALL`] (a hypothetical
1072/// `KubernetesEventSink` slot, a `WebhookPost` slot).
1073///
1074/// Peer to [`crate::lifetime::EphemeralLifetime::has_applicable_exports`]
1075/// on the `(ExportTrigger, ProcessPhase) → bool` axis pair — that
1076/// projection asks "does any export FIRE at this terminal phase" (a
1077/// compound `(when, phase) → fires_on(phase)` walk that projects to
1078/// a boolean the reconciler consumes in the `Releasing` gate); this
1079/// primitive asks "does any export CARRY this trigger literal" (a
1080/// direct `when == kind` equality walk that answers the operator's
1081/// `:requires (export-when-<kind>)` audit tag). The two surfaces
1082/// answer distinct questions and coexist — `has_applicable_exports`
1083/// composes the closed-set dispatch over `ExportTrigger::fires_on`,
1084/// while `has_when` composes the closed-set discriminator equality
1085/// on the raw `when` field.
1086///
1087/// Theory anchor: THEORY.md §II.1 invariant 5 — composition preserves
1088/// proofs; the per-slice `when` walk lives at ONE substrate site so
1089/// every downstream (require-tag classifier, coherence check, editor
1090/// completion) binds through the SAME shape rather than restating the
1091/// `.iter().any(|e| e.when == K)` closure body at each callsite.
1092/// THEORY.md §VI.1 — generation over composition; a future
1093/// [`ExportTrigger`] variant lands at ONE `ALL` entry + ONE `as_str`
1094/// arm on the closed set and the presence probe picks it up
1095/// mechanically without further per-consumer edits.
1096pub trait ExportSpecSliceExt {
1097    /// True iff at least one [`ExportSpec`] in this slice carries the
1098    /// given [`ExportTrigger`] on its [`ExportSpec::when`] slot. The
1099    /// single-slice presence probe every consumer of the
1100    /// `(&[ExportSpec], ExportTrigger) -> bool` shape composes against.
1101    fn has_when(&self, kind: ExportTrigger) -> bool;
1102
1103    /// True iff at least one [`ExportSpec`] in this slice ships through
1104    /// a [`VectorChannel`] whose `ChannelKind::select`-populated slot
1105    /// matches the given [`ChannelKind`]. Composes the closed-set
1106    /// [`ChannelKind::select`] projection with the same
1107    /// `.iter().any(|e| …)` walk shape [`Self::has_when`] publishes on
1108    /// the `when` axis, opening a second closed-set-driven presence
1109    /// probe on the SAME `&[ExportSpec]` slice. The
1110    /// [`ChannelKind::select`] projection is the ONE substrate owner
1111    /// of the "is the slot populated" answer for a tagged-union
1112    /// carrier, so a future fourth [`ChannelKind`] variant reaches this
1113    /// walk through the `ALL` sweep + one `select` arm alone — no
1114    /// per-consumer edit here.
1115    ///
1116    /// Note that an ambiguous [`VectorChannel`] (two or more slots
1117    /// populated — a schema-invalid state that
1118    /// [`VectorChannel::variant`] rejects) STILL answers `true` for
1119    /// every kind whose slot is populated because the walk reads the
1120    /// raw `Option<…>` slot rather than the validated tagged-union
1121    /// resolver. Callers whose invariant is "the spec is valid" reach
1122    /// the same answer through the validation gate rather than this
1123    /// primitive; callers whose invariant is "is this kind's slot
1124    /// present at all" (require-tag classifier, editor completion,
1125    /// coherence check that flags a `stdout` export as
1126    /// non-guaranteed-delivery) reach through THIS primitive.
1127    fn has_channel_kind(&self, kind: ChannelKind) -> bool;
1128
1129    /// True iff at least one [`ExportSpec`] in this slice carries an
1130    /// [`ArtifactSource::test_report`] populated with a
1131    /// [`TestReportSource`] whose [`TestReportSource::format`] equals
1132    /// the given [`ReportFormat`]. Composes the nested-`Option`
1133    /// projection past `source.test_report` with the same
1134    /// `.iter().any(|e| …)` walk shape [`Self::has_when`] and
1135    /// [`Self::has_channel_kind`] publish, opening a THIRD closed-set-
1136    /// driven presence probe on the SAME `&[ExportSpec]` slice — the
1137    /// first probe on this trait that reads a NESTED-Option scalar
1138    /// (`Option<TestReportSource>` past `ArtifactSource`, then
1139    /// `ReportFormat` equality on the populated slot) rather than a
1140    /// tagged-union carrier (`has_channel_kind` on `VectorChannel`) or
1141    /// a direct closed-set field (`has_when` on `ExportTrigger`). A
1142    /// future fifth [`ReportFormat`] variant reaches this walk through
1143    /// the [`ReportFormat::ALL`] sweep alone — the equality body reads
1144    /// the raw `format` field so no per-variant substrate edit lands
1145    /// here.
1146    ///
1147    /// Note that an export whose `source` has NO `test_report` slot
1148    /// populated (a `receipts` / `process_snapshot` / `run_marker`
1149    /// export) contributes `false` for EVERY [`ReportFormat`] kind —
1150    /// the outer nested-Option projection collapses before the
1151    /// equality on `format` fires, so the default [`ReportFormat::Raw`]
1152    /// (which the empty projection would spuriously compare to a
1153    /// hand-authored `Raw`-tagged report) never leaks through.
1154    /// Callers whose invariant is "does any test-report export declare
1155    /// THIS payload format" (require-tag classifier, coherence check
1156    /// like "every JUnit report ships through JetStream", editor
1157    /// completion) reach this ONE primitive rather than restating the
1158    /// `e.source.test_report.as_ref().is_some_and(|tr| tr.format == K)`
1159    /// two-step chain at each callsite.
1160    fn has_report_format(&self, kind: ReportFormat) -> bool;
1161
1162    /// True iff at least one [`ExportSpec`] in this slice carries an
1163    /// [`ArtifactSource`] whose `ArtifactKind::select`-populated slot
1164    /// matches the given [`ArtifactKind`]. Composes the closed-set
1165    /// [`ArtifactKind::select`] projection with the same
1166    /// `.iter().any(|e| …)` walk shape [`Self::has_when`] +
1167    /// [`Self::has_channel_kind`] + [`Self::has_report_format`] publish,
1168    /// opening a FOURTH closed-set-driven presence probe on the SAME
1169    /// `&[ExportSpec]` slice — the second probe on this trait whose
1170    /// closure reads a TAGGED-UNION carrier (peer of
1171    /// [`Self::has_channel_kind`] on `VectorChannel`; the
1172    /// [`ArtifactKind::select`] projection is the ONE substrate owner
1173    /// of the "is the slot populated" answer for the four-slot
1174    /// [`ArtifactSource`] carrier, so a future fifth [`ArtifactKind`]
1175    /// variant reaches this walk through the [`ArtifactKind::ALL`]
1176    /// sweep + one `select` arm alone — no per-consumer edit here.
1177    ///
1178    /// Note that an ambiguous [`ArtifactSource`] (two or more slots
1179    /// populated — a schema-invalid state that
1180    /// [`ArtifactSource::variant`] rejects) STILL answers `true` for
1181    /// every kind whose slot is populated because the walk reads the
1182    /// raw `Option<…>` slot rather than the validated tagged-union
1183    /// resolver. Callers whose invariant is "the spec is valid" reach
1184    /// the same answer through the validation gate rather than this
1185    /// primitive; callers whose invariant is "is this kind's slot
1186    /// present at all" (require-tag classifier, editor completion,
1187    /// coherence check like "every ephemeral export ships a receipts
1188    /// artifact") reach through THIS primitive.
1189    ///
1190    /// Distinct from [`Self::has_report_format`] on ONE dimension:
1191    /// `has_report_format` reads a NESTED-Option scalar
1192    /// (`Option<TestReportSource>` past `ArtifactSource`, then
1193    /// `ReportFormat` equality on the populated slot) while
1194    /// `has_artifact_kind` reads the OUTER tagged-union carrier
1195    /// directly (`Option<T>` past `ArtifactSource`) — a receipts-only
1196    /// export answers `true` for `has_artifact_kind(Receipts)` but
1197    /// `false` for every `has_report_format(k)` kind because the
1198    /// `test_report` slot is empty.
1199    fn has_artifact_kind(&self, kind: ArtifactKind) -> bool;
1200
1201    /// True iff at least one [`ExportSpec`] in this slice carries an
1202    /// [`ArtifactSource`] with a populated [`TestReportSource`] whose
1203    /// [`ReportFormat::payload_shape`] projection lowers to the given
1204    /// [`ReportPayloadShape`]. Composes the closed-set
1205    /// [`ReportFormat::payload_shape`] typed projection with the same
1206    /// `.iter().any(|e| …)` walk shape [`Self::has_when`] +
1207    /// [`Self::has_channel_kind`] + [`Self::has_report_format`] +
1208    /// [`Self::has_artifact_kind`] publish, opening a FIFTH closed-set-
1209    /// driven presence probe on the SAME `&[ExportSpec]` slice — the
1210    /// SECOND probe on this trait whose closure reads a NESTED-Option
1211    /// scalar past `ArtifactSource::test_report` (peer of
1212    /// [`Self::has_report_format`] on the SAME carrier), distinct on
1213    /// ONE dimension: [`Self::has_report_format`] compares the raw
1214    /// [`ReportFormat`] discriminator (a 4-arm closed set) directly,
1215    /// while this probe compares the DERIVED [`ReportPayloadShape`]
1216    /// (a 2-arm closed set) reached through the typed projection
1217    /// [`ReportFormat::payload_shape`] — the projection is many-to-one
1218    /// ([`ReportFormat::NdJson`] alone lowers to
1219    /// [`ReportPayloadShape::NdJsonLines`]; [`ReportFormat::Junit`] +
1220    /// [`ReportFormat::TapV13`] + [`ReportFormat::Raw`] all lower to
1221    /// [`ReportPayloadShape::OpaqueBytes`]), so
1222    /// `has_report_payload_shape(OpaqueBytes)` answers `true` on THREE
1223    /// distinct [`ReportFormat`] variants and
1224    /// `has_report_payload_shape(NdJsonLines)` answers `true` on ONE.
1225    ///
1226    /// Callers whose invariant is "does any test-report export lower
1227    /// to THIS payload shape at the export-worker embed site"
1228    /// (require-tag classifier `report-payload-shape-<kind>`, a
1229    /// coherence check like "every ephemeral test-report export
1230    /// lowers to `NdJsonLines` so shinryu can splice the events",
1231    /// editor completion listing which payload shapes the operator's
1232    /// exports resolve to) reach this ONE primitive rather than
1233    /// restating the
1234    /// `e.source.test_report.as_ref().is_some_and(|tr|
1235    /// tr.format.payload_shape() == K)` three-step chain at each
1236    /// callsite.
1237    ///
1238    /// A future `ReportFormat` variant that lowers to a new
1239    /// `ReportPayloadShape` reaches THIS primitive through the ONE
1240    /// [`ReportFormat::payload_shape`] arm alone — no per-consumer
1241    /// edit at this walk or at any downstream `report-payload-shape-
1242    /// <kind>` callsite.
1243    fn has_report_payload_shape(&self, kind: ReportPayloadShape) -> bool;
1244
1245    /// True iff at least one [`ExportSpec`] in this slice would fire
1246    /// on the given terminal-reached [`ProcessPhase`] — the compound
1247    /// `(ExportTrigger, ProcessPhase) → fires_on(phase)` walk lifted
1248    /// to ONE slice-level substrate primitive past the ★★
1249    /// PRIME-DIRECTIVE ≥ 2 duplication threshold. Composes the
1250    /// closed-set [`ExportTrigger::fires_on`] dispatch with the same
1251    /// `.iter().any(|e| …)` walk shape [`Self::has_when`] +
1252    /// [`Self::has_channel_kind`] + [`Self::has_report_format`] +
1253    /// [`Self::has_artifact_kind`] + [`Self::has_report_payload_shape`]
1254    /// publish, opening a SIXTH closed-set-driven presence probe on
1255    /// the SAME `&[ExportSpec]` slice — the FIRST probe on this trait
1256    /// whose closed-set discriminator is [`ProcessPhase`] (the peer
1257    /// five probes discriminate over [`ExportTrigger`],
1258    /// [`ChannelKind`], [`ReportFormat`], [`ArtifactKind`], and
1259    /// [`ReportPayloadShape`] respectively) and the FIRST probe whose
1260    /// closure body composes a DERIVED phase-dispatch projection
1261    /// rather than a raw equality on a stored field.
1262    ///
1263    /// Semantics — DERIVED fires_on, not raw when equality:
1264    /// `has_applicable_at(Attested)` returns `true` for a spec whose
1265    /// `when` is `OnAttested` OR `Always` (both fire on `Attested`);
1266    /// `has_when(OnAttested)` returns `true` only for the exact
1267    /// `OnAttested` variant. The two probes coexist because they
1268    /// answer distinct operator questions: `has_when` asks "does any
1269    /// export CARRY this trigger literal" (raw discriminator equality),
1270    /// while `has_applicable_at` asks "would any export FIRE at this
1271    /// phase" (compound fires_on projection). An `Always`-triggered
1272    /// export answers `false` for `has_when(OnAttested)` but `true`
1273    /// for `has_applicable_at(Attested)`.
1274    ///
1275    /// A future fourth [`ExportTrigger`] variant reaches this walk
1276    /// through the ONE [`ExportTrigger::fires_on`] arm alone — the
1277    /// equality body reads the `fires_on(phase)` projection so no
1278    /// per-variant substrate edit lands here. A future twelfth
1279    /// [`ProcessPhase`] variant is handled by the exhaustive match
1280    /// inside [`ExportTrigger::fires_on`] — either the new phase gets
1281    /// a per-trigger fire rule (an `OnRetryExhausted` reaching a
1282    /// hypothetical `RetryExhausted` phase) or the closed-set match
1283    /// tail collapses it to `false` (every non-terminal phase).
1284    ///
1285    /// Callers whose invariant is "does any declared export fire
1286    /// when we reach THIS phase" — the reconciler's `Attested → Releasing`
1287    /// gate, the operator's `:requires (exports-fire-on-<phase>)`
1288    /// audit tag, a future coherence check that flags an ephemeral
1289    /// env whose exports never fire on `Attested` (a common
1290    /// author-error where every export is `OnFailed` and successful
1291    /// runs leave no trace) — reach this ONE primitive rather than
1292    /// restating the `.iter().any(|e| e.when.fires_on(phase))` two-step
1293    /// chain at each callsite.
1294    fn has_applicable_at(&self, phase: ProcessPhase) -> bool;
1295}
1296
1297impl ExportSpecSliceExt for [ExportSpec] {
1298    fn has_when(&self, kind: ExportTrigger) -> bool {
1299        self.iter().any(|e| e.when == kind)
1300    }
1301
1302    fn has_channel_kind(&self, kind: ChannelKind) -> bool {
1303        self.iter().any(|e| kind.select(&e.channel).is_some())
1304    }
1305
1306    fn has_report_format(&self, kind: ReportFormat) -> bool {
1307        self.iter().any(|e| {
1308            e.source
1309                .test_report
1310                .as_ref()
1311                .is_some_and(|tr| tr.format == kind)
1312        })
1313    }
1314
1315    fn has_artifact_kind(&self, kind: ArtifactKind) -> bool {
1316        self.iter().any(|e| kind.select(&e.source).is_some())
1317    }
1318
1319    fn has_report_payload_shape(&self, kind: ReportPayloadShape) -> bool {
1320        self.iter().any(|e| {
1321            e.source
1322                .test_report
1323                .as_ref()
1324                .is_some_and(|tr| tr.format.payload_shape() == kind)
1325        })
1326    }
1327
1328    fn has_applicable_at(&self, phase: ProcessPhase) -> bool {
1329        self.iter().any(|e| e.when.fires_on(phase))
1330    }
1331}
1332
1333// ─── Tests ─────────────────────────────────────────────────────────
1334
1335#[cfg(test)]
1336mod tests {
1337    use super::*;
1338
1339    #[test]
1340    fn artifact_source_empty_errors() {
1341        let s = ArtifactSource::default();
1342        match s.variant().unwrap_err() {
1343            ArtifactError::Empty(list) => assert_eq!(list, ARTIFACT_KIND_LIST),
1344            other => panic!("expected Empty, got {other:?}"),
1345        }
1346    }
1347
1348    #[test]
1349    fn artifact_source_receipts_resolves() {
1350        let s = ArtifactSource {
1351            receipts: Some(ReceiptsSource::default()),
1352            ..ArtifactSource::default()
1353        };
1354        assert!(matches!(s.variant().unwrap(), ArtifactVariant::Receipts(_)));
1355    }
1356
1357    #[test]
1358    fn artifact_source_two_variants_ambiguous() {
1359        let s = ArtifactSource {
1360            receipts: Some(ReceiptsSource::default()),
1361            test_report: Some(TestReportSource {
1362                configmap: "x".into(),
1363                key: "y".into(),
1364                format: ReportFormat::Junit,
1365                namespace: None,
1366            }),
1367            ..ArtifactSource::default()
1368        };
1369        assert_eq!(s.variant().unwrap_err(), ArtifactError::Ambiguous);
1370    }
1371
1372    #[test]
1373    fn vector_channel_empty_errors() {
1374        let c = VectorChannel::default();
1375        match c.variant().unwrap_err() {
1376            ChannelError::Empty(list) => assert_eq!(list, CHANNEL_KIND_LIST),
1377            other => panic!("expected Empty, got {other:?}"),
1378        }
1379    }
1380
1381    #[test]
1382    fn vector_channel_resolves_http_event() {
1383        let c = VectorChannel {
1384            http_event: Some(HttpEventChannel::signal("test-report")),
1385            ..VectorChannel::default()
1386        };
1387        match c.variant().unwrap() {
1388            ChannelVariant::HttpEvent(h) => {
1389                assert_eq!(h.signal_type, "test-report");
1390                assert_eq!(h.resolved_endpoint(), DEFAULT_VECTOR_INGEST);
1391            }
1392            other => panic!("expected HttpEvent, got {other:?}"),
1393        }
1394    }
1395
1396    #[test]
1397    fn vector_channel_resolves_nats_subject() {
1398        let c = VectorChannel {
1399            nats_subject: Some(NatsSubjectChannel::publish(
1400                "pleme.pleme-dev.ephemeral.{{run_id}}.receipt",
1401                "EPHEMERAL_RECEIPTS",
1402            )),
1403            ..VectorChannel::default()
1404        };
1405        match c.variant().unwrap() {
1406            ChannelVariant::NatsSubject(n) => {
1407                assert_eq!(n.stream, "EPHEMERAL_RECEIPTS");
1408                assert_eq!(n.resolved_url(), DEFAULT_NATS_URL);
1409            }
1410            other => panic!("expected NatsSubject, got {other:?}"),
1411        }
1412    }
1413
1414    #[test]
1415    fn export_trigger_fire_logic() {
1416        assert!(ExportTrigger::OnAttested.fires_on_attested());
1417        assert!(!ExportTrigger::OnAttested.fires_on_failed());
1418        assert!(ExportTrigger::OnFailed.fires_on_failed());
1419        assert!(!ExportTrigger::OnFailed.fires_on_attested());
1420        assert!(ExportTrigger::Always.fires_on_attested());
1421        assert!(ExportTrigger::Always.fires_on_failed());
1422    }
1423
1424    // ── closed-set algebra for ExportTrigger (ALL × as_str × FromStr ×
1425    //    fires_on(phase)) ─
1426
1427    /// `ALL` is the source of truth for the resolver / `FromStr` sweep
1428    /// Structural well-formedness of [`ExportTrigger`] as a
1429    /// [`tatara_lisp::ClosedSet`] implementor — the workspace-wide
1430    /// testkit lift that pins all three structural invariants (`ALL`
1431    /// is non-empty, every variant round-trips through
1432    /// `label ↔ parse_label`, labels are pairwise distinct, `""` is
1433    /// outside the closed set) at ONE call site. Replaces the hand-
1434    /// derived `export_trigger_all_is_unique_and_complete` +
1435    /// `export_trigger_roundtrip_via_as_str` + the empty-input arm of
1436    /// `unknown_export_trigger_errors`. `FromStr` delegates to
1437    /// `<Self as tatara_closed_set::ClosedSet>::parse_label`, so this helper
1438    /// exercises the same code path the reconciler hits when parsing
1439    /// a CRD `enum:`-validated value back to the typed trigger.
1440    #[test]
1441    fn export_trigger_is_well_formed_closed_set() {
1442        tatara_closed_set::assert_closed_set_well_formed::<ExportTrigger>();
1443    }
1444
1445    /// CANONICAL-KEY CONTRACT: `as_str` matches serde's PascalCase
1446    /// output verbatim for every variant. A future variant rename
1447    /// (or an `as_str` arm typo) lands here at one site, instead of
1448    /// drifting between the typed surface and the YAML wire format
1449    /// the reconciler / operator both read.
1450    #[test]
1451    fn export_trigger_as_str_matches_serde() {
1452        crate::tagged_union::assert_label_matches_serde_serialization::<ExportTrigger>();
1453    }
1454
1455    /// The Display impl IS `as_str` — pinning this lets future callers
1456    /// reach for either projection without drift. If a reviewer
1457    /// accidentally re-introduces an inline match in Display, this
1458    /// test would fail the moment a variant rename touches one site
1459    /// but not the other.
1460    #[test]
1461    fn export_trigger_display_matches_as_str() {
1462        crate::tagged_union::assert_display_matches_label::<ExportTrigger>();
1463    }
1464
1465    /// `FromStr` rejects strings that aren't in the canonical
1466    /// projection — lowercased / typo / unrelated — and the error
1467    /// echoes the input verbatim so the operator-facing diagnostic
1468    /// carries the offending value, not a normalized form. The
1469    /// empty-input arm is pinned by
1470    /// [`export_trigger_is_well_formed_closed_set`] via the
1471    /// `tatara_lisp::ClosedSet` testkit; the cases here pin the
1472    /// verbatim-echo contract on the [`UnknownExportTrigger`] newtype,
1473    /// which the trait's `make_unknown` can't see.
1474    #[test]
1475    fn unknown_export_trigger_errors() {
1476        use std::str::FromStr;
1477        for bad in ["onAttested", "ALWAYS", "Never", "OnSuccess"] {
1478            let err = ExportTrigger::from_str(bad).unwrap_err();
1479            assert_eq!(err.0, bad, "error payload should echo input verbatim");
1480        }
1481    }
1482
1483    // `unknown_export_trigger_message_matches_substrate_convention`
1484    // removed — clause (5) of
1485    // `tatara_closed_set::assert_closed_set_well_formed::<ExportTrigger>()`
1486    // verifies the substrate-wide `"unknown {SET_LABEL}: {input}"`
1487    // shape generically (called from `trigger_is_well_formed_closed_set`
1488    // above); the `SET_LABEL` projection is pinned by
1489    // `tatara_lisp_derive::pascal_to_spaced_lowercase_tests`.
1490
1491    /// TRUTH-TABLE CONTRACT: `fires_on(phase)` agrees with the
1492    /// documented (trigger, phase) -> bool table for every (3 × 11)
1493    /// combination. A new variant in either `ExportTrigger` or
1494    /// `ProcessPhase` reaches this test by iteration — adding a phase
1495    /// without extending `fires_on`'s match would be caught by the
1496    /// compiler (the closed-set match over `ProcessPhase` enforces it);
1497    /// adding a trigger without extending its truth row is caught
1498    /// here.
1499    #[test]
1500    fn export_trigger_fires_on_truth_table() {
1501        // ProcessPhase imports are local to the test to keep the
1502        // module's top-level surface minimal.
1503        use crate::phase::ProcessPhase::{
1504            Attested, Execing, Exiting, Failed, Forking, Pending, Reaped, Reconverging, Releasing,
1505            Running, Zombie,
1506        };
1507        let table: &[(ExportTrigger, &[(crate::phase::ProcessPhase, bool)])] = &[
1508            (
1509                ExportTrigger::OnAttested,
1510                &[
1511                    (Attested, true),
1512                    (Failed, false),
1513                    (Pending, false),
1514                    (Forking, false),
1515                    (Execing, false),
1516                    (Running, false),
1517                    (Reconverging, false),
1518                    (Releasing, false),
1519                    (Exiting, false),
1520                    (Zombie, false),
1521                    (Reaped, false),
1522                ],
1523            ),
1524            (
1525                ExportTrigger::OnFailed,
1526                &[
1527                    (Attested, false),
1528                    (Failed, true),
1529                    (Pending, false),
1530                    (Forking, false),
1531                    (Execing, false),
1532                    (Running, false),
1533                    (Reconverging, false),
1534                    (Releasing, false),
1535                    (Exiting, false),
1536                    (Zombie, false),
1537                    (Reaped, false),
1538                ],
1539            ),
1540            (
1541                ExportTrigger::Always,
1542                &[
1543                    (Attested, true),
1544                    (Failed, true),
1545                    (Pending, false),
1546                    (Forking, false),
1547                    (Execing, false),
1548                    (Running, false),
1549                    (Reconverging, false),
1550                    (Releasing, false),
1551                    (Exiting, false),
1552                    (Zombie, false),
1553                    (Reaped, false),
1554                ],
1555            ),
1556        ];
1557        // The truth table must cover every (trigger, phase) pair.
1558        assert_eq!(table.len(), ExportTrigger::ALL.len());
1559        for (_, row) in table {
1560            assert_eq!(row.len(), crate::phase::ProcessPhase::ALL.len());
1561        }
1562        for (trigger, row) in table {
1563            for (phase, expected) in *row {
1564                assert_eq!(
1565                    trigger.fires_on(*phase),
1566                    *expected,
1567                    "fires_on({trigger:?}, {phase:?}) drift"
1568                );
1569            }
1570        }
1571    }
1572
1573    /// DELEGATION CONTRACT: the legacy `fires_on_attested` /
1574    /// `fires_on_failed` predicates agree with the typed
1575    /// `fires_on(phase)` dispatch they delegate to, for every variant
1576    /// in `ALL`. A regression that re-introduces an inline `matches!`
1577    /// in either legacy predicate fails here. `fires_on` is the
1578    /// source of truth.
1579    #[test]
1580    fn export_trigger_legacy_predicates_delegate_to_phase_dispatch() {
1581        for trigger in ExportTrigger::ALL {
1582            assert_eq!(
1583                trigger.fires_on_attested(),
1584                trigger.fires_on(crate::phase::ProcessPhase::Attested),
1585                "legacy fires_on_attested drift for {trigger:?}"
1586            );
1587            assert_eq!(
1588                trigger.fires_on_failed(),
1589                trigger.fires_on(crate::phase::ProcessPhase::Failed),
1590                "legacy fires_on_failed drift for {trigger:?}"
1591            );
1592        }
1593    }
1594
1595    // ── closed-set algebra for ReportFormat (ALL × as_str × FromStr ×
1596    //    payload_shape) ─
1597
1598    /// Structural well-formedness of [`ReportFormat`] as a
1599    /// [`tatara_lisp::ClosedSet`] implementor — the workspace-wide
1600    /// testkit lift that pins all three structural invariants (`ALL`
1601    /// is non-empty, every variant round-trips through
1602    /// `label ↔ parse_label`, labels are pairwise distinct, `""` is
1603    /// outside the closed set) at ONE call site. Replaces the hand-
1604    /// derived `report_format_all_is_unique_and_complete` +
1605    /// `report_format_roundtrip_via_as_str` + the empty-input arm of
1606    /// `unknown_report_format_errors`. `FromStr` delegates to
1607    /// `<Self as tatara_closed_set::ClosedSet>::parse_label`, so this helper
1608    /// exercises the same code path the export worker hits when
1609    /// parsing a CRD `enum:`-validated value back to the typed format.
1610    #[test]
1611    fn report_format_is_well_formed_closed_set() {
1612        tatara_closed_set::assert_closed_set_well_formed::<ReportFormat>();
1613    }
1614
1615    /// CANONICAL-KEY CONTRACT: `as_str` matches serde's PascalCase
1616    /// output verbatim for every variant. A future variant rename
1617    /// (or an `as_str` arm typo) lands here at one site, instead of
1618    /// drifting between the typed surface and the YAML wire format
1619    /// the reconciler / operator both read.
1620    #[test]
1621    fn report_format_as_str_matches_serde() {
1622        crate::tagged_union::assert_label_matches_serde_serialization::<ReportFormat>();
1623    }
1624
1625    /// The Display impl IS `as_str` — pinning this lets future callers
1626    /// reach for either projection without drift.
1627    #[test]
1628    fn report_format_display_matches_as_str() {
1629        crate::tagged_union::assert_display_matches_label::<ReportFormat>();
1630    }
1631
1632    /// `FromStr` rejects strings that aren't in the canonical
1633    /// projection — lowercased / typo / unrelated — and the error
1634    /// echoes the input verbatim so the operator-facing diagnostic
1635    /// carries the offending value, not a normalized form. The
1636    /// empty-input arm is pinned by
1637    /// [`report_format_is_well_formed_closed_set`] via the
1638    /// `tatara_lisp::ClosedSet` testkit; the cases here pin the
1639    /// verbatim-echo contract on the [`UnknownReportFormat`] newtype,
1640    /// which the trait's `make_unknown` can't see.
1641    #[test]
1642    fn unknown_report_format_errors() {
1643        use std::str::FromStr;
1644        for bad in ["junit", "JUNIT", "tap", "Yaml", "TomlV1"] {
1645            let err = ReportFormat::from_str(bad).unwrap_err();
1646            assert_eq!(err.0, bad, "error payload should echo input verbatim");
1647        }
1648    }
1649
1650    // `unknown_report_format_message_matches_substrate_convention`
1651    // removed — clause (5) of
1652    // `tatara_closed_set::assert_closed_set_well_formed::<ReportFormat>()`
1653    // verifies the substrate-wide `"unknown {SET_LABEL}: {input}"`
1654    // shape generically (called from `report_format_is_well_formed_closed_set`
1655    // above); the `SET_LABEL` projection is pinned by
1656    // `tatara_lisp_derive::pascal_to_spaced_lowercase_tests`.
1657
1658    /// TRUTH-TABLE CONTRACT: `payload_shape` agrees with the documented
1659    /// shape table for every variant in `ALL`. A new variant whose
1660    /// shape the author forgets to add to `payload_shape`'s match is
1661    /// caught by the compiler at the match site; a regression that
1662    /// reshuffles existing variants (e.g. routing `NdJson` to opaque
1663    /// bytes) is caught here. `payload_shape` is the worker's only
1664    /// dispatch — once this passes, the worker's `match shape { … }`
1665    /// is exhaustive on the 2-variant `ReportPayloadShape` instead of
1666    /// the 4-variant `ReportFormat`, so adding a future format never
1667    /// touches the worker.
1668    #[test]
1669    fn report_format_payload_shape_truth_table() {
1670        let table: &[(ReportFormat, ReportPayloadShape)] = &[
1671            (ReportFormat::Junit, ReportPayloadShape::OpaqueBytes),
1672            (ReportFormat::TapV13, ReportPayloadShape::OpaqueBytes),
1673            (ReportFormat::NdJson, ReportPayloadShape::NdJsonLines),
1674            (ReportFormat::Raw, ReportPayloadShape::OpaqueBytes),
1675        ];
1676        assert_eq!(table.len(), ReportFormat::ALL.len());
1677        for (format, expected) in table {
1678            assert_eq!(
1679                format.payload_shape(),
1680                *expected,
1681                "payload_shape({format:?}) drift"
1682            );
1683        }
1684    }
1685
1686    /// CLOSURE-OF-PROJECTION CONTRACT: every `ReportPayloadShape`
1687    /// variant is the image of at least one `ReportFormat` variant —
1688    /// no shape is stranded. A `Compressed` shape added to
1689    /// `ReportPayloadShape::ALL` without an `ALL → payload_shape`
1690    /// mapping at any `ReportFormat` arm makes the worker's
1691    /// 3-variant dispatch reachable from no input, which would
1692    /// silently dead-code one arm. Caught here.
1693    #[test]
1694    fn report_payload_shape_reachable_from_some_report_format() {
1695        for shape in ReportPayloadShape::ALL {
1696            let reachable = ReportFormat::ALL.iter().any(|f| f.payload_shape() == shape);
1697            assert!(
1698                reachable,
1699                "{shape:?} is in ReportPayloadShape::ALL but no ReportFormat projects to it"
1700            );
1701        }
1702    }
1703
1704    /// CLOSED-SET CONTRACT: `ReportPayloadShape::ALL` enumerates each
1705    /// variant exactly once. The `[Self; 2]` array literal forces
1706    /// the arity at compile time; this test pins per-variant
1707    /// reachability so adding a third shape (`Compressed`) without
1708    /// extending `ALL` fails here rather than silently dropping the
1709    /// new variant from every sweep through `Self::ALL`.
1710    #[test]
1711    fn report_payload_shape_all_enumerates_each_variant_exactly_once() {
1712        let mut seen = std::collections::HashSet::new();
1713        for shape in ReportPayloadShape::ALL {
1714            assert!(seen.insert(shape), "duplicate variant in ALL: {shape:?}");
1715        }
1716        assert_eq!(seen.len(), ReportPayloadShape::ALL.len());
1717        for shape in [
1718            ReportPayloadShape::NdJsonLines,
1719            ReportPayloadShape::OpaqueBytes,
1720        ] {
1721            assert!(
1722                ReportPayloadShape::ALL.contains(&shape),
1723                "{shape:?} declared but not in ALL"
1724            );
1725        }
1726    }
1727
1728    /// CANONICAL-KEY UNIQUENESS: no two shapes alias the same
1729    /// `as_str` identifier. A future rename of one variant to a name
1730    /// that collides with another (e.g. both → `"Lines"`) breaks the
1731    /// shape's identity in operator-facing reason strings and would
1732    /// silently make Display non-injective. Caught here.
1733    #[test]
1734    fn report_payload_shape_as_str_unique_per_variant() {
1735        let mut seen = std::collections::HashSet::new();
1736        for shape in ReportPayloadShape::ALL {
1737            assert!(
1738                seen.insert(shape.as_str()),
1739                "as_str collision: {shape:?} → {:?}",
1740                shape.as_str()
1741            );
1742        }
1743        assert_eq!(seen.len(), ReportPayloadShape::ALL.len());
1744    }
1745
1746    /// DISPLAY-IS-AS_STR: the Display impl IS `as_str` — pinning
1747    /// this lets callers reach for either projection without drift.
1748    /// Sibling to `report_format_display_matches_as_str` and
1749    /// `export_trigger_display_matches_as_str`. Routed through the
1750    /// substrate-wide [`crate::tagged_union::assert_display_matches_label`]
1751    /// primitive so the sweep body lives at ONE substrate site rather
1752    /// than restated per-implementor. Also exercised through the
1753    /// substrate-wide `every_production_display_impl_binds_through_the_testkit_primitive`
1754    /// sweep so a per-crate test-site drop cannot silently disable the
1755    /// check.
1756    #[test]
1757    fn report_payload_shape_display_matches_as_str() {
1758        crate::tagged_union::assert_display_matches_label::<ReportPayloadShape>();
1759    }
1760
1761    /// EMBED-FIELD UNIQUENESS: no two shapes write into the same
1762    /// `payload.<field>` key. The worker's embed site is
1763    /// `payload.insert(shape.payload_field().into(), …)`; if two
1764    /// shapes aliased to the same field name, two different report
1765    /// sources arriving in the same export envelope would silently
1766    /// overwrite each other's bytes. Caught here.
1767    #[test]
1768    fn report_payload_shape_payload_field_unique_per_variant() {
1769        let mut seen = std::collections::HashSet::new();
1770        for shape in ReportPayloadShape::ALL {
1771            assert!(
1772                seen.insert(shape.payload_field()),
1773                "payload_field collision: {shape:?} → {:?}",
1774                shape.payload_field()
1775            );
1776        }
1777        assert_eq!(seen.len(), ReportPayloadShape::ALL.len());
1778    }
1779
1780    /// TRUTH-TABLE: `payload_field` matches the documented
1781    /// `payload.ndjson` / `payload.raw_b64` shinryu schema. A future
1782    /// rename (e.g. `"raw_b64"` → `"raw"`) lands here at one arm
1783    /// rather than drifting between the docstring prose and the
1784    /// worker's embed-site literal. Adding a third shape forces the
1785    /// author to add a row here (driven by `ALL`), so the table
1786    /// width tracks the closed set.
1787    #[test]
1788    fn report_payload_shape_payload_field_truth_table() {
1789        let table: &[(ReportPayloadShape, &str)] = &[
1790            (ReportPayloadShape::NdJsonLines, "ndjson"),
1791            (ReportPayloadShape::OpaqueBytes, "raw_b64"),
1792        ];
1793        assert_eq!(table.len(), ReportPayloadShape::ALL.len());
1794        for (shape, expected) in table {
1795            assert_eq!(
1796                shape.payload_field(),
1797                *expected,
1798                "payload_field({shape:?}) drift"
1799            );
1800        }
1801    }
1802
1803    /// Every variant's `payload_field` is non-empty and contains no
1804    /// JSON-path-separator (`.`) — the worker concatenates
1805    /// `payload.<payload_field>` so an embedded `.` would alias into
1806    /// the parent map and silently flatten the embed. Structural
1807    /// guard for the field-name shape.
1808    #[test]
1809    fn report_payload_shape_payload_field_is_a_single_segment() {
1810        for shape in ReportPayloadShape::ALL {
1811            let field = shape.payload_field();
1812            assert!(
1813                !field.is_empty(),
1814                "payload_field({shape:?}) is empty — embed site has no destination"
1815            );
1816            assert!(
1817                !field.contains('.'),
1818                "payload_field({shape:?}) contains '.' ({field:?}) — would flatten the embed into payload's parent map"
1819            );
1820        }
1821    }
1822
1823    #[test]
1824    fn export_spec_serde_round_trip() {
1825        let spec = ExportSpec {
1826            source: ArtifactSource {
1827                test_report: Some(TestReportSource {
1828                    configmap: "demo-test-results".into(),
1829                    key: "junit.xml".into(),
1830                    format: ReportFormat::Junit,
1831                    namespace: None,
1832                }),
1833                ..ArtifactSource::default()
1834            },
1835            channel: VectorChannel {
1836                http_event: Some(HttpEventChannel::signal("test-report")),
1837                ..VectorChannel::default()
1838            },
1839            when: ExportTrigger::Always,
1840            experiment_id_override: Some("demo-run-2026-05-20".into()),
1841        };
1842
1843        let yaml = serde_yaml::to_string(&spec).unwrap();
1844        // camelCase wire format — what FluxCD / kubectl users see.
1845        assert!(yaml.contains("source:"));
1846        assert!(yaml.contains("testReport:"));
1847        assert!(yaml.contains("configmap: demo-test-results"));
1848        assert!(yaml.contains("format: Junit"));
1849        assert!(yaml.contains("channel:"));
1850        assert!(yaml.contains("httpEvent:"));
1851        assert!(yaml.contains("signalType: test-report"));
1852        assert!(yaml.contains("when: Always"));
1853        assert!(yaml.contains("experimentIdOverride: demo-run-2026-05-20"));
1854
1855        let back: ExportSpec = serde_yaml::from_str(&yaml).unwrap();
1856        assert!(back.source.test_report.is_some());
1857        assert!(back.channel.http_event.is_some());
1858        assert_eq!(back.when, ExportTrigger::Always);
1859    }
1860
1861    #[test]
1862    fn run_marker_labels_round_trip() {
1863        let mut labels = BTreeMap::new();
1864        labels.insert("run-id".into(), "demo-run-2026-05-20".into());
1865        labels.insert("phase".into(), "end".into());
1866        let spec = ExportSpec {
1867            source: ArtifactSource {
1868                run_marker: Some(RunMarkerSource { labels }),
1869                ..ArtifactSource::default()
1870            },
1871            channel: VectorChannel {
1872                http_event: Some(HttpEventChannel::signal("ephemeral-marker")),
1873                ..VectorChannel::default()
1874            },
1875            when: ExportTrigger::Always,
1876            experiment_id_override: None,
1877        };
1878        let yaml = serde_yaml::to_string(&spec).unwrap();
1879        assert!(yaml.contains("runMarker:"));
1880        assert!(yaml.contains("run-id: demo-run-2026-05-20"));
1881        let back: ExportSpec = serde_yaml::from_str(&yaml).unwrap();
1882        let rm = back.source.run_marker.unwrap();
1883        assert_eq!(rm.labels["phase"], "end");
1884    }
1885
1886    /// Default endpoints resolve to the canonical in-cluster Service
1887    /// DNS — a single source of truth other tatara crates can
1888    /// re-export instead of duplicating literals.
1889    #[test]
1890    fn default_endpoints_are_stable_constants() {
1891        assert_eq!(
1892            DEFAULT_VECTOR_INGEST,
1893            "http://vector.observability.svc.cluster.local:8080"
1894        );
1895        assert_eq!(
1896            DEFAULT_NATS_URL,
1897            "nats://nats.observability.svc.cluster.local:4222"
1898        );
1899    }
1900
1901    // ── HttpEventChannel::signal substrate primitive pins ────────────
1902    //
1903    // Sweeps the wire-shape corners the ten pre-lift `HttpEventChannel {
1904    // endpoint: None, signal_type: <s>.into() }` hand-authored fixture
1905    // literals covered — the default-endpoint sentinel projects to
1906    // `None`, the `signal_type` slot rides through verbatim, and the
1907    // downstream [`HttpEventChannel::resolved_endpoint`] read still
1908    // resolves to [`DEFAULT_VECTOR_INGEST`]. A regression that seeded
1909    // a hardcoded endpoint on the composer, dropped the `Into<String>`
1910    // bound so a `&str` caller has to `.to_string()` per-site, or
1911    // reordered the two struct slots surfaces here rather than as
1912    // silent skew at any of the ten downstream fixtures (five in
1913    // `tatara-process::export`, two in `tatara-process::lifetime`, two
1914    // in `tatara-process::tagged_union`, one in
1915    // `tatara-reconciler::render`, one in `tatara-export-worker::lib`).
1916
1917    /// PRIMARY SHAPE: byte-identical parity with the pre-lift 4-token
1918    /// hand-authored literal every fixture spelled. Sweeps every label
1919    /// the ten collapsed sites carry so a regression that dropped or
1920    /// mutated any observable slot's value surfaces HERE rather than
1921    /// downstream. The `endpoint: None` sentinel is the load-bearing
1922    /// slot ([`HttpEventChannel::resolved_endpoint`] gates on `.is_none()`
1923    /// to reach [`DEFAULT_VECTOR_INGEST`]); the pin binds it before the
1924    /// primitive can drift.
1925    #[test]
1926    fn signal_composes_byte_identical_to_pre_lift_literal_across_every_label() {
1927        for label in [
1928            "receipt",
1929            "test-report",
1930            "ephemeral-marker",
1931            "x",
1932            "s",
1933            "demo-run-2026-05-20",
1934        ] {
1935            let via_primitive = HttpEventChannel::signal(label);
1936            let hand_authored = HttpEventChannel {
1937                endpoint: None,
1938                signal_type: label.to_string(),
1939            };
1940            assert_eq!(
1941                via_primitive.endpoint, hand_authored.endpoint,
1942                "signal must project the endpoint slot byte-identically \
1943                 to the pre-lift literal on label={label:?}",
1944            );
1945            assert_eq!(
1946                via_primitive.signal_type, hand_authored.signal_type,
1947                "signal must project the signal_type slot byte-identically \
1948                 to the pre-lift literal on label={label:?}",
1949            );
1950            assert!(
1951                via_primitive.endpoint.is_none(),
1952                "signal must stamp endpoint: None so resolved_endpoint \
1953                 reaches DEFAULT_VECTOR_INGEST on label={label:?}",
1954            );
1955            assert_eq!(
1956                via_primitive.resolved_endpoint(),
1957                DEFAULT_VECTOR_INGEST,
1958                "signal must compose with resolved_endpoint's \
1959                 default-fallback gate on label={label:?}",
1960            );
1961        }
1962    }
1963
1964    /// COERCION AXIS PIN: the `impl Into<String>` bound accepts every
1965    /// pre-lift caller shape without a per-site coercion. Pre-lift the
1966    /// ten sites carried three distinct source shapes for the
1967    /// `signal_type` slot: `&'static str` literals with `.into()`
1968    /// (`"receipt".into()`), the export-worker fixture's owned
1969    /// `String` via `.to_string()` (`signal_type.to_string()`), and
1970    /// the property-probe fixtures' short single-char labels. Post-lift
1971    /// EVERY shape reaches the composer through the same `Into<String>`
1972    /// gate; the pin binds that so a future narrowing to `&str` (which
1973    /// would break the export-worker's `signal_type: &str` parameter
1974    /// shape) surfaces here.
1975    #[test]
1976    fn signal_accepts_every_pre_lift_caller_source_shape() {
1977        // Shape 1: `&'static str` literal — every test-fixture site.
1978        let a = HttpEventChannel::signal("receipt");
1979        assert_eq!(a.signal_type, "receipt");
1980        // Shape 2: owned `String` — the export-worker `http_spec`
1981        // fixture pre-lift spelled `signal_type: signal_type.to_string()`
1982        // to project its `&str` parameter into the slot.
1983        let owned: String = "test-report".to_string();
1984        let b = HttpEventChannel::signal(owned);
1985        assert_eq!(b.signal_type, "test-report");
1986        // Shape 3: `&String` — verifies the `Into<String>` bound
1987        // accepts a borrowed owned string without an explicit clone
1988        // (matches the reference shape a caller might reach for after
1989        // an intermediate `let label = String::from("...");` binding).
1990        let borrowed = String::from("ephemeral-marker");
1991        let c = HttpEventChannel::signal(&borrowed[..]);
1992        assert_eq!(c.signal_type, "ephemeral-marker");
1993    }
1994
1995    /// COMPOSITION PIN: `HttpEventChannel::signal` composes byte-
1996    /// identically with [`ChannelKind::select`] on the resolver axis —
1997    /// wrapping the primitive's output in the `VectorChannel` tagged-
1998    /// union slot yields the same `ChannelVariant::HttpEvent(...)`
1999    /// projection as the pre-lift literal did. Guards the primary
2000    /// downstream consumer (the tagged-union `.variant()` resolver
2001    /// every fixture round-trips through) against a regression that
2002    /// projected the primitive onto a non-http-event slot or dropped
2003    /// its `endpoint`/`signal_type` slots between composition sites.
2004    #[test]
2005    fn signal_composes_with_channel_variant_resolver() {
2006        let c = VectorChannel {
2007            http_event: Some(HttpEventChannel::signal("receipt")),
2008            ..VectorChannel::default()
2009        };
2010        match c.variant().unwrap() {
2011            ChannelVariant::HttpEvent(h) => {
2012                assert_eq!(h.signal_type, "receipt");
2013                assert_eq!(h.resolved_endpoint(), DEFAULT_VECTOR_INGEST);
2014                assert!(h.endpoint.is_none());
2015            }
2016            other => panic!("expected HttpEvent, got {other:?}"),
2017        }
2018        // Kind projection through the closed-set discriminator stays
2019        // coherent with `ChannelKind::HttpEvent` — a regression that
2020        // wired the primitive to a non-http-event slot would surface
2021        // here as a wrong-kind panic before any downstream test firing.
2022        let via_kind = ChannelKind::HttpEvent.select(&c).unwrap();
2023        assert_eq!(via_kind.kind(), ChannelKind::HttpEvent);
2024    }
2025
2026    // ── NatsSubjectChannel::publish substrate primitive pins ──────────
2027    //
2028    // Sibling to the HttpEventChannel::signal block above. Sweeps the
2029    // wire-shape corners the six pre-lift `NatsSubjectChannel {
2030    // subject, stream, url: None }` hand-authored fixture literals
2031    // covered — the default-URL sentinel projects to `None`, the
2032    // `subject` and `stream` slots ride through verbatim, and the
2033    // downstream [`NatsSubjectChannel::resolved_url`] read still
2034    // resolves to [`DEFAULT_NATS_URL`]. A regression that seeded a
2035    // hardcoded URL on the composer, dropped the `Into<String>` bound
2036    // on either positional arg so a `&str` caller has to `.to_string()`
2037    // per-site, or reordered the three struct slots surfaces here
2038    // rather than as silent skew at any of the six downstream fixtures
2039    // (two in `tatara-process::export`, one in
2040    // `tatara-process::tagged_union`, one in
2041    // `tatara-reconciler::render`, two in `tatara-export-worker::lib`).
2042
2043    /// PRIMARY SHAPE: byte-identical parity with the pre-lift 3-slot
2044    /// hand-authored literal every fixture spelled. Sweeps every
2045    /// (subject, stream) pair the six collapsed sites carry so a
2046    /// regression that dropped or mutated any observable slot's value
2047    /// surfaces HERE rather than downstream. The `url: None` sentinel
2048    /// is the load-bearing slot ([`NatsSubjectChannel::resolved_url`]
2049    /// gates on `.is_none()` to reach [`DEFAULT_NATS_URL`]); the pin
2050    /// binds it before the primitive can drift.
2051    #[test]
2052    fn publish_composes_byte_identical_to_pre_lift_literal_across_every_subject_stream_pair() {
2053        for (subject, stream) in [
2054            (
2055                "pleme.pleme-dev.ephemeral.{{run_id}}.receipt",
2056                "EPHEMERAL_RECEIPTS",
2057            ),
2058            ("pleme.fixed.subject", "S"),
2059            ("s", "S"),
2060            ("pleme.demo.subject.2026-05-20", "DEMO_STREAM"),
2061        ] {
2062            let via_primitive = NatsSubjectChannel::publish(subject, stream);
2063            let hand_authored = NatsSubjectChannel {
2064                subject: subject.to_string(),
2065                stream: stream.to_string(),
2066                url: None,
2067            };
2068            assert_eq!(
2069                via_primitive.subject, hand_authored.subject,
2070                "publish must project the subject slot byte-identically \
2071                 to the pre-lift literal on (subject={subject:?}, stream={stream:?})",
2072            );
2073            assert_eq!(
2074                via_primitive.stream, hand_authored.stream,
2075                "publish must project the stream slot byte-identically \
2076                 to the pre-lift literal on (subject={subject:?}, stream={stream:?})",
2077            );
2078            assert_eq!(
2079                via_primitive.url, hand_authored.url,
2080                "publish must project the url slot byte-identically \
2081                 to the pre-lift literal on (subject={subject:?}, stream={stream:?})",
2082            );
2083            assert!(
2084                via_primitive.url.is_none(),
2085                "publish must stamp url: None so resolved_url reaches \
2086                 DEFAULT_NATS_URL on (subject={subject:?}, stream={stream:?})",
2087            );
2088            assert_eq!(
2089                via_primitive.resolved_url(),
2090                DEFAULT_NATS_URL,
2091                "publish must compose with resolved_url's default-fallback \
2092                 gate on (subject={subject:?}, stream={stream:?})",
2093            );
2094        }
2095    }
2096
2097    /// COERCION AXIS PIN: the `impl Into<String>` bound on both
2098    /// positional args accepts every pre-lift caller shape without a
2099    /// per-site coercion. Pre-lift the six sites carried three
2100    /// distinct source shapes: `&'static str` literals with `.into()`
2101    /// (`"S".into()`), owned `String` values, and short single-char
2102    /// property-probe labels. Post-lift EVERY shape reaches the
2103    /// composer through the same `Into<String>` gate; the pin binds
2104    /// that so a future narrowing to `&str` (which would break every
2105    /// worker-crate fixture that pre-lift wrote `subject: <s>.into()`)
2106    /// surfaces here.
2107    #[test]
2108    fn publish_accepts_every_pre_lift_caller_source_shape() {
2109        // Shape 1: `&'static str` literals on both positional args —
2110        // the closed-set property probes' shape.
2111        let a = NatsSubjectChannel::publish("s", "S");
2112        assert_eq!(a.subject, "s");
2113        assert_eq!(a.stream, "S");
2114        // Shape 2: owned `String` values on both positional args — the
2115        // shape a caller reaches for after `let subj = format!(...);`
2116        // or `let stream = String::from(...);` bindings.
2117        let owned_subject: String = "pleme.demo.subject".to_string();
2118        let owned_stream: String = "DEMO_STREAM".to_string();
2119        let b = NatsSubjectChannel::publish(owned_subject, owned_stream);
2120        assert_eq!(b.subject, "pleme.demo.subject");
2121        assert_eq!(b.stream, "DEMO_STREAM");
2122        // Shape 3: mixed — `&str` slice on one arg, owned `String` on
2123        // the other — verifies the two positional bounds are
2124        // independent (a regression that unified them under a single
2125        // generic type parameter `T: Into<String>` would break this).
2126        let subj_slice = String::from("pleme.mixed.subject");
2127        let stream_owned: String = "MIXED_STREAM".to_string();
2128        let c = NatsSubjectChannel::publish(&subj_slice[..], stream_owned);
2129        assert_eq!(c.subject, "pleme.mixed.subject");
2130        assert_eq!(c.stream, "MIXED_STREAM");
2131    }
2132
2133    /// COMPOSITION PIN: `NatsSubjectChannel::publish` composes byte-
2134    /// identically with [`ChannelKind::select`] on the resolver axis —
2135    /// wrapping the primitive's output in the `VectorChannel` tagged-
2136    /// union slot yields the same `ChannelVariant::NatsSubject(...)`
2137    /// projection as the pre-lift literal did. Guards the primary
2138    /// downstream consumer (the tagged-union `.variant()` resolver
2139    /// every fixture round-trips through) against a regression that
2140    /// projected the primitive onto a non-nats-subject slot or dropped
2141    /// its `subject`/`stream`/`url` slots between composition sites.
2142    #[test]
2143    fn publish_composes_with_channel_variant_resolver() {
2144        let c = VectorChannel {
2145            nats_subject: Some(NatsSubjectChannel::publish(
2146                "pleme.pleme-dev.ephemeral.{{run_id}}.receipt",
2147                "EPHEMERAL_RECEIPTS",
2148            )),
2149            ..VectorChannel::default()
2150        };
2151        match c.variant().unwrap() {
2152            ChannelVariant::NatsSubject(n) => {
2153                assert_eq!(n.subject, "pleme.pleme-dev.ephemeral.{{run_id}}.receipt");
2154                assert_eq!(n.stream, "EPHEMERAL_RECEIPTS");
2155                assert_eq!(n.resolved_url(), DEFAULT_NATS_URL);
2156                assert!(n.url.is_none());
2157            }
2158            other => panic!("expected NatsSubject, got {other:?}"),
2159        }
2160        // Kind projection through the closed-set discriminator stays
2161        // coherent with `ChannelKind::NatsSubject` — a regression that
2162        // wired the primitive to a non-nats-subject slot would surface
2163        // here as a wrong-kind panic before any downstream test firing.
2164        let via_kind = ChannelKind::NatsSubject.select(&c).unwrap();
2165        assert_eq!(via_kind.kind(), ChannelKind::NatsSubject);
2166    }
2167
2168    // ── closed-set algebra for ArtifactKind (ALL × as_str × Display ×
2169    //    FromStr × select × ArtifactVariant::kind) ─────────────────────
2170
2171    /// Structural well-formedness of [`ArtifactKind`] as a
2172    /// [`tatara_lisp::ClosedSet`] implementor — the workspace-wide
2173    /// testkit lift that pins all three structural invariants (`ALL`
2174    /// is non-empty, every variant round-trips through
2175    /// `label ↔ parse_label`, labels are pairwise distinct, `""` is
2176    /// outside the closed set) at ONE call site. Replaces the hand-
2177    /// derived `artifact_kind_all_is_unique_and_complete` +
2178    /// `artifact_kind_roundtrip_via_as_str` + the empty-input arm of
2179    /// `unknown_artifact_kind_errors`. `FromStr` delegates to
2180    /// `<Self as tatara_closed_set::ClosedSet>::parse_label`, so this helper
2181    /// exercises the same code path the export worker hits when
2182    /// parsing a CRD `enum:`-validated value back to the typed kind.
2183    #[test]
2184    fn artifact_kind_is_well_formed_closed_set() {
2185        tatara_closed_set::assert_closed_set_well_formed::<ArtifactKind>();
2186    }
2187
2188    /// CANONICAL-KEY CONTRACT: every `ArtifactKind::as_str()` matches
2189    /// the serde `rename_all = "camelCase"` field name on the
2190    /// corresponding `Option<…>` slot of `ArtifactSource`. A future
2191    /// rename of either the struct field OR the `as_str` arm lands
2192    /// here at one site, instead of drifting between the typed
2193    /// surface, the YAML wire format, and the `ArtifactError::Empty`
2194    /// diagnostic. The mapping is the table the serde derive produces
2195    /// against the struct field declarations above; reading the YAML
2196    /// output pins it without re-deriving by hand.
2197    ///
2198    /// Routes through the substrate primitive
2199    /// [`crate::tagged_union::assert_single_slot_key_matches_label`],
2200    /// which pins the exactly-one-key + name-equality projection
2201    /// byte-identically for every `<T: TaggedUnion + Serialize>`
2202    /// implementor — the wire-alignment testkit shared with the sibling
2203    /// `intent_kind_as_str_matches_intent_field_name` /
2204    /// `encapsulation_target_as_str_matches_field_name` /
2205    /// `channel_kind_as_str_matches_field_name` sites. Pre-lift this
2206    /// site restated a weaker YAML-substring check (`yaml.contains(&format!("{key}:"))`)
2207    /// which would silently pass on drift where a non-tagged-union
2208    /// field was added to `ArtifactSource`; post-lift the primitive's
2209    /// JSON exactly-one form catches that drift too — at ONE substrate
2210    /// site.
2211    #[test]
2212    fn artifact_kind_as_str_matches_field_name() {
2213        crate::tagged_union::assert_single_slot_key_matches_label::<ArtifactSource, _>(
2214            single_slot_source,
2215        );
2216    }
2217
2218    /// CANONICAL-NAMES PIN: byte-exact camelCase wire-format pin —
2219    /// renaming any of these strings IS a wire-format break that fails
2220    /// this test FIRST so the rename stays a deliberate decision, not
2221    /// a typo. Locks the (variant → operator-facing key) table.
2222    #[test]
2223    fn artifact_kind_canonical_names_pinned() {
2224        assert_eq!(ArtifactKind::Receipts.as_str(), "receipts");
2225        assert_eq!(ArtifactKind::TestReport.as_str(), "testReport");
2226        assert_eq!(ArtifactKind::ProcessSnapshot.as_str(), "processSnapshot");
2227        assert_eq!(ArtifactKind::RunMarker.as_str(), "runMarker");
2228    }
2229
2230    /// The Display impl IS `as_str` — pinning this lets future callers
2231    /// reach for either projection without drift. If a reviewer
2232    /// accidentally re-introduces an inline match in Display, this
2233    /// test would fail the moment a variant rename touches one site
2234    /// but not the other.
2235    #[test]
2236    fn artifact_kind_display_matches_as_str() {
2237        crate::tagged_union::assert_display_matches_label::<ArtifactKind>();
2238    }
2239
2240    /// `FromStr` rejects strings that aren't in the canonical
2241    /// projection — PascalCased / typo / cross-axis-leaked inputs
2242    /// from sibling closed-set enums on the same `ExportSpec` axis
2243    /// (`Junit`, `OnAttested`, …) — and the error echoes the input
2244    /// verbatim so the operator-facing diagnostic carries the
2245    /// offending value, not a normalized form. `ArtifactKind` is its
2246    /// own axis, NOT a transparent reflection of any sibling. The
2247    /// empty-input arm is pinned by
2248    /// [`artifact_kind_is_well_formed_closed_set`] via the
2249    /// `tatara_lisp::ClosedSet` testkit; the cases here pin the
2250    /// verbatim-echo contract on the [`UnknownArtifactKind`] newtype,
2251    /// which the trait's `make_unknown` can't see.
2252    #[test]
2253    fn unknown_artifact_kind_errors() {
2254        use std::str::FromStr;
2255        for bad in [
2256            "Receipts",
2257            "test_report",
2258            "RECEIPTS",
2259            "snapshot",
2260            "marker",
2261            "Junit",
2262            "OnAttested",
2263            "NdJsonLines",
2264        ] {
2265            let err = ArtifactKind::from_str(bad).unwrap_err();
2266            assert_eq!(err.0, bad, "error payload should echo input verbatim");
2267        }
2268    }
2269
2270    /// ROUND-TRIP CONTRACT: every kind reaches its borrowed-variant
2271    /// view via `select`, and that variant projects back to the same
2272    /// kind via `ArtifactVariant::kind`. A regression that misroutes a
2273    /// select arm (e.g. `Self::Receipts => source.test_report.as_ref()
2274    /// ...`) fails loudly here.
2275    ///
2276    /// Routes through the substrate primitive
2277    /// [`crate::tagged_union::assert_variant_round_trip`] shared with
2278    /// the sibling `intent_kind_round_trips_through_variant_kind` /
2279    /// `channel_kind_round_trips_through_variant_kind` /
2280    /// `encapsulation_target_round_trips_through_variant_target`
2281    /// sites — the projection lives at ONE substrate primitive and
2282    /// every site binds through a single call.
2283    #[test]
2284    fn artifact_kind_round_trips_through_variant_kind() {
2285        crate::tagged_union::assert_variant_round_trip::<ArtifactSource, _>(single_slot_source);
2286    }
2287
2288    /// SELECT-EMPTY CONTRACT: an unpopulated slot returns `None` from
2289    /// `select`, for every kind. Pairs with the resolver's `Empty`
2290    /// path so a future kind's slot defaulting wrong (e.g. accidentally
2291    /// `Some(Default::default())` instead of `None`) is caught here.
2292    #[test]
2293    fn artifact_kind_select_returns_none_for_unset_slot() {
2294        let empty = ArtifactSource::default();
2295        for kind in ArtifactKind::ALL {
2296            assert!(
2297                kind.select(&empty).is_none(),
2298                "{kind:?} reported populated on a default ArtifactSource"
2299            );
2300        }
2301    }
2302
2303    /// EMPTY-DIAGNOSTIC CONTRACT: the closed-set kind list embedded
2304    /// in `ArtifactError::Empty` echoes the canonical join of every
2305    /// `ArtifactKind::as_str()` projection. A variant added without
2306    /// updating `ARTIFACT_KIND_LIST` (or a renamed variant) shows up
2307    /// here as a mismatch. Routes through the substrate primitive
2308    /// [`crate::tagged_union::assert_kind_list_matches_closed_set`]
2309    /// shared with the sibling
2310    /// `intent_error_empty_lists_every_kind_in_canonical_order`
2311    /// / `channel_error_empty_lists_every_kind_in_canonical_order`
2312    /// / `encapsulation_kind_error_empty_lists_every_target_in_canonical_order`
2313    /// sites — the projection lives at ONE substrate primitive and
2314    /// every site binds through a single call.
2315    #[test]
2316    fn artifact_error_empty_lists_every_kind_in_canonical_order() {
2317        crate::tagged_union::assert_kind_list_matches_closed_set::<ArtifactSource>();
2318    }
2319
2320    /// AMBIGUOUS-PATH CONTRACT: when two slots are populated the
2321    /// resolver yields `Ambiguous`, exhaustively across every pair in
2322    /// `ALL × ALL` (excluding the diagonal). A future asymmetry where
2323    /// one slot would silently shadow another (e.g. an `if-let` chain
2324    /// re-introducing first-wins ordering) is caught here. Routes
2325    /// through the substrate primitive
2326    /// [`crate::tagged_union::assert_two_slots_ambiguous`] shared with
2327    /// the sibling
2328    /// `encapsulation_kind_two_slots_is_ambiguous_across_every_pair`
2329    /// / `vector_channel_two_slots_is_ambiguous_across_every_pair`
2330    /// / `intent_two_slots_is_ambiguous_across_every_pair` sites — the
2331    /// nested-`for a in K::ALL { for b in K::ALL { … } }` sweep lives
2332    /// at ONE substrate site.
2333    #[test]
2334    fn artifact_source_two_slots_is_ambiguous_across_every_pair() {
2335        crate::tagged_union::assert_two_slots_ambiguous::<ArtifactSource, _>(two_slot_source);
2336    }
2337
2338    // ── closed-set algebra for ChannelKind (ALL × as_str × Display ×
2339    //    FromStr × select × ChannelVariant::kind) ─────────────────────
2340
2341    /// Structural well-formedness of [`ChannelKind`] as a
2342    /// [`tatara_lisp::ClosedSet`] implementor — the workspace-wide
2343    /// testkit lift that pins all three structural invariants (`ALL`
2344    /// is non-empty, every variant round-trips through
2345    /// `label ↔ parse_label`, labels are pairwise distinct, `""` is
2346    /// outside the closed set) at ONE call site. Replaces the hand-
2347    /// derived `channel_kind_all_is_unique_and_complete` +
2348    /// `channel_kind_roundtrip_via_as_str` + the empty-input arm of
2349    /// `unknown_channel_kind_errors`. `FromStr` delegates to
2350    /// `<Self as tatara_closed_set::ClosedSet>::parse_label`, so this helper
2351    /// exercises the same code path the export worker hits when
2352    /// parsing a CRD `enum:`-validated value back to the typed kind.
2353    #[test]
2354    fn channel_kind_is_well_formed_closed_set() {
2355        tatara_closed_set::assert_closed_set_well_formed::<ChannelKind>();
2356    }
2357
2358    /// CANONICAL-KEY CONTRACT: every `ChannelKind::as_str()` matches
2359    /// the serde `rename_all = "camelCase"` field name on the
2360    /// corresponding `Option<…>` slot of `VectorChannel`. A future
2361    /// rename of either the struct field OR the `as_str` arm lands
2362    /// here at one site, instead of drifting between the typed
2363    /// surface, the YAML wire format, and the `ChannelError::Empty`
2364    /// diagnostic.
2365    ///
2366    /// Routes through the substrate primitive
2367    /// [`crate::tagged_union::assert_single_slot_key_matches_label`],
2368    /// which pins the exactly-one-key + name-equality projection
2369    /// byte-identically for every `<T: TaggedUnion + Serialize>`
2370    /// implementor — the wire-alignment testkit shared with the sibling
2371    /// `intent_kind_as_str_matches_intent_field_name` /
2372    /// `encapsulation_target_as_str_matches_field_name` /
2373    /// `artifact_kind_as_str_matches_field_name` sites. Pre-lift this
2374    /// site restated a weaker YAML-substring check (`yaml.contains(&format!("{key}:"))`)
2375    /// which would silently pass on drift where a non-tagged-union
2376    /// field was added to `VectorChannel`; post-lift the primitive's
2377    /// JSON exactly-one form catches that drift too — at ONE substrate
2378    /// site.
2379    #[test]
2380    fn channel_kind_as_str_matches_field_name() {
2381        crate::tagged_union::assert_single_slot_key_matches_label::<VectorChannel, _>(
2382            single_slot_channel,
2383        );
2384    }
2385
2386    /// CANONICAL-NAMES PIN: byte-exact camelCase wire-format pin —
2387    /// renaming any of these strings IS a wire-format break that fails
2388    /// this test FIRST so the rename stays a deliberate decision, not
2389    /// a typo. Locks the (variant → operator-facing key) table.
2390    #[test]
2391    fn channel_kind_canonical_names_pinned() {
2392        assert_eq!(ChannelKind::HttpEvent.as_str(), "httpEvent");
2393        assert_eq!(ChannelKind::NatsSubject.as_str(), "natsSubject");
2394        assert_eq!(ChannelKind::Stdout.as_str(), "stdout");
2395    }
2396
2397    /// The Display impl IS `as_str` — pinning this lets future callers
2398    /// reach for either projection without drift.
2399    #[test]
2400    fn channel_kind_display_matches_as_str() {
2401        crate::tagged_union::assert_display_matches_label::<ChannelKind>();
2402    }
2403
2404    /// `FromStr` rejects strings that aren't in the canonical
2405    /// projection — PascalCased / typo / cross-axis-leaked inputs
2406    /// from sibling closed-set enums on the same `ExportSpec` axis
2407    /// (`Receipts`, `OnAttested`, `Junit`, …) — and the error echoes
2408    /// the input verbatim so the operator-facing diagnostic carries
2409    /// the offending value, not a normalized form. `ChannelKind` is
2410    /// its own axis, NOT a transparent reflection of any sibling. The
2411    /// empty-input arm is pinned by
2412    /// [`channel_kind_is_well_formed_closed_set`] via the
2413    /// `tatara_lisp::ClosedSet` testkit; the cases here pin the
2414    /// verbatim-echo contract on the [`UnknownChannelKind`] newtype,
2415    /// which the trait's `make_unknown` can't see.
2416    #[test]
2417    fn unknown_channel_kind_errors() {
2418        use std::str::FromStr;
2419        for bad in [
2420            "HttpEvent",
2421            "http_event",
2422            "HTTPEVENT",
2423            "nats",
2424            "STDOUT",
2425            "Receipts",
2426            "OnAttested",
2427            "Junit",
2428            "NdJsonLines",
2429        ] {
2430            let err = ChannelKind::from_str(bad).unwrap_err();
2431            assert_eq!(err.0, bad, "error payload should echo input verbatim");
2432        }
2433    }
2434
2435    // `unknown_channel_kind_message_matches_substrate_convention`
2436    // removed — clause (5) of
2437    // `tatara_closed_set::assert_closed_set_well_formed::<ChannelKind>()`
2438    // verifies the substrate-wide `"unknown {SET_LABEL}: {input}"`
2439    // shape generically (called from `channel_kind_is_well_formed_closed_set`
2440    // above); the `SET_LABEL` projection is pinned by
2441    // `tatara_lisp_derive::pascal_to_spaced_lowercase_tests`.
2442
2443    /// ROUND-TRIP CONTRACT: every kind reaches its borrowed-variant
2444    /// view via `select`, and that variant projects back to the same
2445    /// kind via `ChannelVariant::kind`. A regression that misroutes a
2446    /// select arm (e.g. `Self::HttpEvent => channel.nats_subject ...`)
2447    /// fails loudly here.
2448    ///
2449    /// Routes through the substrate primitive
2450    /// [`crate::tagged_union::assert_variant_round_trip`] shared with
2451    /// the sibling `intent_kind_round_trips_through_variant_kind` /
2452    /// `artifact_kind_round_trips_through_variant_kind` /
2453    /// `encapsulation_target_round_trips_through_variant_target`
2454    /// sites — the projection lives at ONE substrate primitive and
2455    /// every site binds through a single call.
2456    #[test]
2457    fn channel_kind_round_trips_through_variant_kind() {
2458        crate::tagged_union::assert_variant_round_trip::<VectorChannel, _>(single_slot_channel);
2459    }
2460
2461    /// SELECT-EMPTY CONTRACT: an unpopulated slot returns `None` from
2462    /// `select`, for every kind. Pairs with the resolver's `Empty`
2463    /// path so a future kind's slot defaulting wrong (e.g. accidentally
2464    /// `Some(Default::default())` instead of `None`) is caught here.
2465    #[test]
2466    fn channel_kind_select_returns_none_for_unset_slot() {
2467        let empty = VectorChannel::default();
2468        for kind in ChannelKind::ALL {
2469            assert!(
2470                kind.select(&empty).is_none(),
2471                "{kind:?} reported populated on a default VectorChannel"
2472            );
2473        }
2474    }
2475
2476    /// EMPTY-DIAGNOSTIC CONTRACT: the closed-set kind list embedded
2477    /// in `ChannelError::Empty` echoes the canonical join of every
2478    /// `ChannelKind::as_str()` projection. A variant added without
2479    /// updating `CHANNEL_KIND_LIST` (or a renamed variant) shows up
2480    /// here as a mismatch. Routes through the substrate primitive
2481    /// [`crate::tagged_union::assert_kind_list_matches_closed_set`]
2482    /// shared with the sibling `intent_error_empty_lists_every_kind_in_canonical_order`
2483    /// / `artifact_error_empty_lists_every_kind_in_canonical_order`
2484    /// / `encapsulation_kind_error_empty_lists_every_target_in_canonical_order`
2485    /// sites — the projection lives at ONE substrate primitive and
2486    /// every site binds through a single call.
2487    #[test]
2488    fn channel_error_empty_lists_every_kind_in_canonical_order() {
2489        crate::tagged_union::assert_kind_list_matches_closed_set::<VectorChannel>();
2490    }
2491
2492    /// AMBIGUOUS-PATH CONTRACT: when two slots are populated the
2493    /// resolver yields `Ambiguous`, exhaustively across every pair in
2494    /// `ALL × ALL` (excluding the diagonal). A future asymmetry where
2495    /// one slot would silently shadow another (e.g. an `if-let` chain
2496    /// re-introducing first-wins ordering) is caught here. Routes
2497    /// through the substrate primitive
2498    /// [`crate::tagged_union::assert_two_slots_ambiguous`] shared with
2499    /// the sibling
2500    /// `encapsulation_kind_two_slots_is_ambiguous_across_every_pair`
2501    /// / `artifact_source_two_slots_is_ambiguous_across_every_pair`
2502    /// / `intent_two_slots_is_ambiguous_across_every_pair` sites — the
2503    /// nested-`for a in K::ALL { for b in K::ALL { … } }` sweep lives
2504    /// at ONE substrate site.
2505    #[test]
2506    fn vector_channel_two_slots_is_ambiguous_across_every_pair() {
2507        crate::tagged_union::assert_two_slots_ambiguous::<VectorChannel, _>(two_slot_channel);
2508    }
2509
2510    /// Construct a `VectorChannel` with exactly the given kind's slot
2511    /// populated by a minimal valid inner channel. Shared across the
2512    /// closed-set property tests so they each cover every variant
2513    /// without restating the construction table. Mirrors
2514    /// `single_slot_source` in shape.
2515    fn single_slot_channel(kind: ChannelKind) -> VectorChannel {
2516        match kind {
2517            ChannelKind::HttpEvent => VectorChannel {
2518                http_event: Some(HttpEventChannel::signal("x")),
2519                ..VectorChannel::default()
2520            },
2521            ChannelKind::NatsSubject => VectorChannel {
2522                nats_subject: Some(NatsSubjectChannel::publish("s", "S")),
2523                ..VectorChannel::default()
2524            },
2525            ChannelKind::Stdout => VectorChannel {
2526                stdout: Some(StdoutChannel::default()),
2527                ..VectorChannel::default()
2528            },
2529        }
2530    }
2531
2532    /// Construct a `VectorChannel` with two slots populated — drives
2533    /// the pairwise `Ambiguous` sweep. Composes the single-slot
2534    /// constructor on top of itself to keep one source of truth for
2535    /// per-variant inner payloads.
2536    fn two_slot_channel(a: ChannelKind, b: ChannelKind) -> VectorChannel {
2537        let ca = single_slot_channel(a);
2538        let cb = single_slot_channel(b);
2539        VectorChannel {
2540            http_event: ca.http_event.or(cb.http_event),
2541            nats_subject: ca.nats_subject.or(cb.nats_subject),
2542            stdout: ca.stdout.or(cb.stdout),
2543        }
2544    }
2545
2546    /// Construct an `ArtifactSource` with exactly the given kind's
2547    /// slot populated by a minimal valid inner source. Shared across
2548    /// the closed-set property tests so they each cover every variant
2549    /// without restating the construction table. Mirrors
2550    /// `single_slot_intent` in shape.
2551    fn single_slot_source(kind: ArtifactKind) -> ArtifactSource {
2552        match kind {
2553            ArtifactKind::Receipts => ArtifactSource {
2554                receipts: Some(ReceiptsSource::default()),
2555                ..ArtifactSource::default()
2556            },
2557            ArtifactKind::TestReport => ArtifactSource {
2558                test_report: Some(TestReportSource {
2559                    configmap: "cm".into(),
2560                    key: "k".into(),
2561                    format: ReportFormat::Junit,
2562                    namespace: None,
2563                }),
2564                ..ArtifactSource::default()
2565            },
2566            ArtifactKind::ProcessSnapshot => ArtifactSource {
2567                process_snapshot: Some(ProcessSnapshotSource::default()),
2568                ..ArtifactSource::default()
2569            },
2570            ArtifactKind::RunMarker => ArtifactSource {
2571                run_marker: Some(RunMarkerSource::default()),
2572                ..ArtifactSource::default()
2573            },
2574        }
2575    }
2576
2577    /// Construct an `ArtifactSource` with two slots populated — drives
2578    /// the pairwise `Ambiguous` sweep. Composes the single-slot
2579    /// constructor on top of itself to keep one source of truth for
2580    /// per-variant inner payloads.
2581    fn two_slot_source(a: ArtifactKind, b: ArtifactKind) -> ArtifactSource {
2582        // Merge by populating each kind's slot from its single-slot view.
2583        let sa = single_slot_source(a);
2584        let sb = single_slot_source(b);
2585        ArtifactSource {
2586            receipts: sa.receipts.or(sb.receipts),
2587            test_report: sa.test_report.or(sb.test_report),
2588            process_snapshot: sa.process_snapshot.or(sb.process_snapshot),
2589            run_marker: sa.run_marker.or(sb.run_marker),
2590        }
2591    }
2592
2593    // ── ExportSpecSliceExt::has_when substrate pins ───────────────────
2594    //
2595    // Fail-before-pass-after granularity: `ExportSpecSliceExt` did not
2596    // exist before this commit — the `(&[ExportSpec], ExportTrigger)
2597    // -> bool` walk shape was not spelled anywhere in the workspace on
2598    // the raw `when` field. The lift opens the FOURTH instance in the
2599    // slice-level closed-set-driven presence-probe algebra (peer of
2600    // `ConditionSliceExt::has_kind` on `&[Condition]`,
2601    // `DependsOnSliceExt::has_must_reach` on `&[DependsOn]`, and
2602    // `ComplianceBindingSliceExt::has_verification_phase` on
2603    // `&[ComplianceBinding]`), enabling the seventh
2604    // `export-when-<kind>` require-tag prefix family in
2605    // `tatara-reconciler::bin::tatara-check` to compose against ONE
2606    // substrate site rather than restating the `.iter().any(|e| e.when
2607    // == K)` closure body inline at the classifier.
2608
2609    /// Fixture: a minimal `ExportSpec` with a chosen `when` trigger
2610    /// and a single-slot receipts source + stdout channel. The trigger
2611    /// is the only axis this test module discriminates on; the source
2612    /// + channel are fixed at valid single-slot pairs so the primitive
2613    /// under test reads the `when` field in isolation.
2614    fn export_at(when: ExportTrigger) -> ExportSpec {
2615        ExportSpec {
2616            source: ArtifactSource {
2617                receipts: Some(ReceiptsSource::default()),
2618                ..ArtifactSource::default()
2619            },
2620            channel: VectorChannel {
2621                stdout: Some(StdoutChannel::default()),
2622                ..VectorChannel::default()
2623            },
2624            when,
2625            experiment_id_override: None,
2626        }
2627    }
2628
2629    /// EMPTY-SLICE pin — an empty `&[ExportSpec]` returns `false` for
2630    /// EVERY [`ExportTrigger`]. Sweep [`ExportTrigger::ALL`] so a new
2631    /// variant added without a matching arm in the primitive surfaces
2632    /// at rustc's exhaustiveness gate on the `ALL` literal (arity
2633    /// forced by `[Self; 3]`) rather than as a silent false-positive
2634    /// at every downstream callsite composing this primitive.
2635    #[test]
2636    fn export_spec_slice_has_when_returns_false_on_empty_slice_for_every_kind() {
2637        let empty: &[ExportSpec] = &[];
2638        for kind in ExportTrigger::ALL {
2639            assert!(
2640                !empty.has_when(kind),
2641                "empty slice must return false for {kind:?}",
2642            );
2643        }
2644    }
2645
2646    /// PER-VARIANT pin — a single-element slice returns `true` for
2647    /// exactly the trigger it carries, `false` for every other
2648    /// variant. Sweep the [`ExportTrigger::ALL`] × ALL cross so a
2649    /// regression that (a) hard-coded the arm to a single kind
2650    /// (silently returning true for every populated slice regardless
2651    /// of query kind), or (b) matched on a different field (a stray
2652    /// `experiment_id_override.is_some()`, a `source`-side variant
2653    /// discriminator) fails HERE at the substrate primitive rather
2654    /// than at each downstream `export-when-<kind>` callsite.
2655    #[test]
2656    fn export_spec_slice_has_when_reads_when_field_per_variant() {
2657        for populated in ExportTrigger::ALL {
2658            let slice = [export_at(populated)];
2659            for query in ExportTrigger::ALL {
2660                let expected = query == populated;
2661                assert_eq!(
2662                    slice.has_when(query),
2663                    expected,
2664                    "populated={populated:?}: query {query:?} drifted",
2665                );
2666            }
2667        }
2668    }
2669
2670    /// MULTI-ENTRY pin — a slice with multiple entries returns `true`
2671    /// for every trigger that appears at any position (existential
2672    /// quantifier over the slice), `false` for triggers that appear
2673    /// at no position. Locks the `any` semantics so a regression that
2674    /// collapsed to a `first`-only probe (`slice.first().is_some_and(
2675    /// |e| e.when == kind)`) fails here even though the single-element
2676    /// per-variant pin above passes.
2677    #[test]
2678    fn export_spec_slice_has_when_scans_beyond_the_first_position() {
2679        let slice = [
2680            export_at(ExportTrigger::OnAttested),
2681            export_at(ExportTrigger::Always),
2682        ];
2683        for present in [ExportTrigger::OnAttested, ExportTrigger::Always] {
2684            assert!(
2685                slice.has_when(present),
2686                "trigger at any position must resolve true: {present:?}",
2687            );
2688        }
2689        assert!(
2690            !slice.has_when(ExportTrigger::OnFailed),
2691            "trigger absent from the slice must resolve false: OnFailed",
2692        );
2693    }
2694
2695    // ── ExportSpecSliceExt::has_channel_kind substrate pins ───────────
2696    //
2697    // Fail-before-pass-after granularity: `has_channel_kind` did not
2698    // exist before this commit — the `(&[ExportSpec], ChannelKind) ->
2699    // bool` walk shape was not spelled anywhere in the workspace on the
2700    // tagged-union `channel` field. The lift opens the SECOND method
2701    // on the slice-level `ExportSpecSliceExt` (peer of `has_when` on
2702    // the same slice, and fifth instance across the workspace slice-
2703    // level closed-set-driven presence-probe algebra), composing
2704    // `ChannelKind::select` — the ONE substrate owner of the "is the
2705    // slot populated" projection for a tagged-union carrier — with the
2706    // same `.iter().any(|e| …)` walk shape `has_when` publishes.
2707
2708    /// Fixture: a minimal `ExportSpec` with a chosen [`ChannelKind`]
2709    /// populated on its `channel` slot and a fixed single-slot
2710    /// receipts source + default `when` trigger. The channel kind is
2711    /// the only axis this test module discriminates on; the source +
2712    /// trigger are fixed at valid pairs so the primitive under test
2713    /// reads the `channel` slot in isolation.
2714    ///
2715    /// Sweeps [`ChannelKind::ALL`] via `match` on the closed set so a
2716    /// future fourth variant added to `ALL` reaches this fixture at
2717    /// rustc's exhaustiveness gate on the `match` arm — the same
2718    /// exhaustive-match contract [`ChannelKind::select`] publishes.
2719    fn export_with_channel(kind: ChannelKind) -> ExportSpec {
2720        let channel = match kind {
2721            ChannelKind::HttpEvent => VectorChannel {
2722                http_event: Some(HttpEventChannel::signal("test-report")),
2723                ..VectorChannel::default()
2724            },
2725            ChannelKind::NatsSubject => VectorChannel {
2726                nats_subject: Some(NatsSubjectChannel::publish("s", "STREAM")),
2727                ..VectorChannel::default()
2728            },
2729            ChannelKind::Stdout => VectorChannel {
2730                stdout: Some(StdoutChannel::default()),
2731                ..VectorChannel::default()
2732            },
2733        };
2734        ExportSpec {
2735            source: ArtifactSource {
2736                receipts: Some(ReceiptsSource::default()),
2737                ..ArtifactSource::default()
2738            },
2739            channel,
2740            when: ExportTrigger::default(),
2741            experiment_id_override: None,
2742        }
2743    }
2744
2745    /// EMPTY-SLICE pin — an empty `&[ExportSpec]` returns `false` for
2746    /// EVERY [`ChannelKind`]. Sweep [`ChannelKind::ALL`] so a new
2747    /// variant added without a matching arm in `ChannelKind::select`
2748    /// surfaces at rustc's exhaustiveness gate on the `ALL` literal
2749    /// (arity forced by `[Self; 3]`) rather than as a silent false-
2750    /// positive at every downstream callsite composing this primitive.
2751    #[test]
2752    fn export_spec_slice_has_channel_kind_returns_false_on_empty_slice_for_every_kind() {
2753        let empty: &[ExportSpec] = &[];
2754        for kind in ChannelKind::ALL {
2755            assert!(
2756                !empty.has_channel_kind(kind),
2757                "empty slice must return false for {kind:?}",
2758            );
2759        }
2760    }
2761
2762    /// PER-VARIANT pin — a single-element slice returns `true` for
2763    /// exactly the channel kind whose slot is populated, `false` for
2764    /// every other variant. Sweep the [`ChannelKind::ALL`] × ALL cross
2765    /// so a regression that (a) hard-coded the arm to a single kind
2766    /// (silently returning `true` for every populated slice regardless
2767    /// of query kind), or (b) probed on a different field (a stray
2768    /// `experiment_id_override.is_some()`, a `source`-side variant
2769    /// discriminator, `when`) fails HERE at the substrate primitive
2770    /// rather than at each downstream `channel-<kind>` callsite.
2771    #[test]
2772    fn export_spec_slice_has_channel_kind_reads_channel_slot_per_variant() {
2773        for populated in ChannelKind::ALL {
2774            let slice = [export_with_channel(populated)];
2775            for query in ChannelKind::ALL {
2776                let expected = query == populated;
2777                assert_eq!(
2778                    slice.has_channel_kind(query),
2779                    expected,
2780                    "populated={populated:?}: query {query:?} drifted",
2781                );
2782            }
2783        }
2784    }
2785
2786    /// MULTI-ENTRY pin — a slice with multiple entries returns `true`
2787    /// for every channel kind that appears at any position (existential
2788    /// quantifier over the slice), `false` for kinds that appear at no
2789    /// position. Locks the `any` semantics so a regression that
2790    /// collapsed to a `first`-only probe (`slice.first().is_some_and(
2791    /// |e| kind.select(&e.channel).is_some())`) fails here even though
2792    /// the single-element per-variant pin above passes.
2793    #[test]
2794    fn export_spec_slice_has_channel_kind_scans_beyond_the_first_position() {
2795        let slice = [
2796            export_with_channel(ChannelKind::Stdout),
2797            export_with_channel(ChannelKind::NatsSubject),
2798        ];
2799        for present in [ChannelKind::Stdout, ChannelKind::NatsSubject] {
2800            assert!(
2801                slice.has_channel_kind(present),
2802                "channel kind at any position must resolve true: {present:?}",
2803            );
2804        }
2805        assert!(
2806            !slice.has_channel_kind(ChannelKind::HttpEvent),
2807            "channel kind absent from the slice must resolve false: HttpEvent",
2808        );
2809    }
2810
2811    // ── ExportSpecSliceExt::has_report_format substrate pins ──────────
2812    //
2813    // Fail-before-pass-after granularity: `has_report_format` did not
2814    // exist before this commit — the `(&[ExportSpec], ReportFormat) ->
2815    // bool` walk shape was not spelled anywhere in the workspace on the
2816    // nested-Option `source.test_report.format` field. The lift opens
2817    // the THIRD method on the slice-level `ExportSpecSliceExt` (peer of
2818    // `has_when` + `has_channel_kind` on the SAME slice, and sixth
2819    // instance across the workspace slice-level closed-set-driven
2820    // presence-probe algebra), composing an inner `.as_ref().is_some_and`
2821    // gate on the `Option<TestReportSource>` field with a raw
2822    // `ReportFormat` equality on the populated slot — the FIRST probe
2823    // on this trait whose closure reads a NESTED-Option scalar rather
2824    // than a tagged-union `select` or a direct closed-set field.
2825
2826    /// Fixture: a minimal `ExportSpec` whose `source` carries a
2827    /// [`TestReportSource`] tagged with the chosen [`ReportFormat`] and
2828    /// a fixed single-slot stdout channel + default `when` trigger. The
2829    /// report format is the only axis this test module discriminates
2830    /// on; the channel + trigger + configmap/key strings are fixed at
2831    /// valid pairs so the primitive under test reads the
2832    /// `source.test_report.format` slot in isolation.
2833    ///
2834    /// Sweeps [`ReportFormat::ALL`] via the [`ReportFormat`] closed set
2835    /// so a future fifth variant reaches this fixture by hitting the
2836    /// `ALL` array literal's arity gate at every call site.
2837    fn export_with_report_format(kind: ReportFormat) -> ExportSpec {
2838        ExportSpec {
2839            source: ArtifactSource {
2840                test_report: Some(TestReportSource {
2841                    configmap: "junit-results".into(),
2842                    key: "junit.xml".into(),
2843                    format: kind,
2844                    namespace: None,
2845                }),
2846                ..ArtifactSource::default()
2847            },
2848            channel: VectorChannel {
2849                stdout: Some(StdoutChannel::default()),
2850                ..VectorChannel::default()
2851            },
2852            when: ExportTrigger::default(),
2853            experiment_id_override: None,
2854        }
2855    }
2856
2857    /// EMPTY-SLICE pin — an empty `&[ExportSpec]` returns `false` for
2858    /// EVERY [`ReportFormat`]. Sweep [`ReportFormat::ALL`] so a new
2859    /// variant added without matching visits in the surrounding
2860    /// substrate reaches rustc's exhaustiveness gate on the `ALL`
2861    /// literal (arity forced by `[Self; 4]`) rather than as a silent
2862    /// false-positive at every downstream callsite composing this
2863    /// primitive.
2864    #[test]
2865    fn export_spec_slice_has_report_format_returns_false_on_empty_slice_for_every_kind() {
2866        let empty: &[ExportSpec] = &[];
2867        for kind in ReportFormat::ALL {
2868            assert!(
2869                !empty.has_report_format(kind),
2870                "empty slice must return false for {kind:?}",
2871            );
2872        }
2873    }
2874
2875    /// PER-VARIANT pin — a single-element slice returns `true` for
2876    /// exactly the [`ReportFormat`] its `TestReportSource` carries,
2877    /// `false` for every other variant. Sweep the [`ReportFormat::ALL`]
2878    /// × ALL cross so a regression that (a) hard-coded the arm to a
2879    /// single kind (silently returning `true` for every populated slice
2880    /// regardless of query kind), (b) probed a different field (a stray
2881    /// `experiment_id_override.is_some()`, a `channel`-side variant
2882    /// discriminator, `when`), or (c) collapsed the outer
2883    /// nested-Option projection (probing `test_report.is_some()` and
2884    /// treating the empty case as `ReportFormat::default() == Raw`)
2885    /// fails HERE at the substrate primitive rather than at each
2886    /// downstream `report-format-<kind>` callsite.
2887    #[test]
2888    fn export_spec_slice_has_report_format_reads_test_report_slot_per_variant() {
2889        for populated in ReportFormat::ALL {
2890            let slice = [export_with_report_format(populated)];
2891            for query in ReportFormat::ALL {
2892                let expected = query == populated;
2893                assert_eq!(
2894                    slice.has_report_format(query),
2895                    expected,
2896                    "populated={populated:?}: query {query:?} drifted",
2897                );
2898            }
2899        }
2900    }
2901
2902    /// MULTI-ENTRY pin — a slice with multiple entries returns `true`
2903    /// for every [`ReportFormat`] that appears at any position
2904    /// (existential quantifier over the slice), `false` for formats
2905    /// that appear at no position. Locks the `any` semantics so a
2906    /// regression that collapsed to a `first`-only probe
2907    /// (`slice.first().is_some_and(|e|
2908    /// e.source.test_report.as_ref().is_some_and(|tr| tr.format ==
2909    /// kind))`) fails here even though the single-element per-variant
2910    /// pin above passes.
2911    #[test]
2912    fn export_spec_slice_has_report_format_scans_beyond_the_first_position() {
2913        let slice = [
2914            export_with_report_format(ReportFormat::Junit),
2915            export_with_report_format(ReportFormat::TapV13),
2916        ];
2917        for present in [ReportFormat::Junit, ReportFormat::TapV13] {
2918            assert!(
2919                slice.has_report_format(present),
2920                "report format at any position must resolve true: {present:?}",
2921            );
2922        }
2923        assert!(
2924            !slice.has_report_format(ReportFormat::NdJson),
2925            "report format absent from the slice must resolve false: NdJson",
2926        );
2927        assert!(
2928            !slice.has_report_format(ReportFormat::Raw),
2929            "report format absent from the slice must resolve false: Raw",
2930        );
2931    }
2932
2933    /// NESTED-OPTION-COLLAPSE pin — an export whose `source` carries
2934    /// NO `test_report` slot (a `receipts`-only source) contributes
2935    /// `false` for EVERY [`ReportFormat`] kind, INCLUDING the default
2936    /// [`ReportFormat::Raw`] that a naive `unwrap_or_default()`
2937    /// projection would spuriously match. Locks the outer
2938    /// nested-`Option` short-circuit contract so a regression that
2939    /// dropped the `.as_ref().is_some_and(…)` gate (e.g. rewriting to
2940    /// `e.source.test_report.map_or(ReportFormat::default(), |tr|
2941    /// tr.format) == kind`, which returns `true` for every non-
2942    /// test-report export when `kind == Raw`) fails HERE at ONE
2943    /// narrow substrate site rather than at every downstream `report-
2944    /// format-Raw` classifier callsite. Sweeps
2945    /// [`ReportFormat::ALL`] so the contract is pinned symmetrically
2946    /// across every format the closed set names.
2947    #[test]
2948    fn export_spec_slice_has_report_format_returns_false_on_non_test_report_source() {
2949        let receipts_only = ExportSpec {
2950            source: ArtifactSource {
2951                receipts: Some(ReceiptsSource::default()),
2952                ..ArtifactSource::default()
2953            },
2954            channel: VectorChannel {
2955                stdout: Some(StdoutChannel::default()),
2956                ..VectorChannel::default()
2957            },
2958            when: ExportTrigger::default(),
2959            experiment_id_override: None,
2960        };
2961        let slice = [receipts_only];
2962        for kind in ReportFormat::ALL {
2963            assert!(
2964                !slice.has_report_format(kind),
2965                "receipts-only export must return false for every report format: {kind:?}",
2966            );
2967        }
2968    }
2969
2970    // ── ExportSpecSliceExt::has_artifact_kind substrate pins ──────────
2971    //
2972    // Fail-before-pass-after granularity: `has_artifact_kind` did not
2973    // exist before this commit — the `(&[ExportSpec], ArtifactKind) ->
2974    // bool` walk shape was not spelled anywhere in the workspace on the
2975    // tagged-union `source` field. The lift opens the FOURTH method on
2976    // the slice-level `ExportSpecSliceExt` (peer of `has_when` +
2977    // `has_channel_kind` + `has_report_format` on the SAME slice, and
2978    // seventh instance across the workspace slice-level closed-set-
2979    // driven presence-probe algebra), composing `ArtifactKind::select`
2980    // — the ONE substrate owner of the "is the slot populated"
2981    // projection for a tagged-union carrier — with the same
2982    // `.iter().any(|e| …)` walk shape the three prior methods publish.
2983    // Sibling to `has_channel_kind` in shape (tagged-union outer
2984    // carrier), distinct from `has_report_format` (nested-Option scalar
2985    // past the outer carrier).
2986
2987    /// Fixture: a minimal `ExportSpec` with a chosen [`ArtifactKind`]
2988    /// populated on its `source` slot and a fixed single-slot stdout
2989    /// channel + default `when` trigger. The artifact kind is the only
2990    /// axis this test module discriminates on; the channel + trigger
2991    /// are fixed at valid pairs so the primitive under test reads the
2992    /// `source` slot in isolation.
2993    ///
2994    /// Sweeps [`ArtifactKind::ALL`] via `match` on the closed set so a
2995    /// future fifth variant added to `ALL` reaches this fixture at
2996    /// rustc's exhaustiveness gate on the `match` arm — the same
2997    /// exhaustive-match contract [`ArtifactKind::select`] publishes.
2998    fn export_with_artifact(kind: ArtifactKind) -> ExportSpec {
2999        let source = match kind {
3000            ArtifactKind::Receipts => ArtifactSource {
3001                receipts: Some(ReceiptsSource::default()),
3002                ..ArtifactSource::default()
3003            },
3004            ArtifactKind::TestReport => ArtifactSource {
3005                test_report: Some(TestReportSource {
3006                    configmap: "junit-results".into(),
3007                    key: "junit.xml".into(),
3008                    format: ReportFormat::Junit,
3009                    namespace: None,
3010                }),
3011                ..ArtifactSource::default()
3012            },
3013            ArtifactKind::ProcessSnapshot => ArtifactSource {
3014                process_snapshot: Some(ProcessSnapshotSource::default()),
3015                ..ArtifactSource::default()
3016            },
3017            ArtifactKind::RunMarker => ArtifactSource {
3018                run_marker: Some(RunMarkerSource::default()),
3019                ..ArtifactSource::default()
3020            },
3021        };
3022        ExportSpec {
3023            source,
3024            channel: VectorChannel {
3025                stdout: Some(StdoutChannel::default()),
3026                ..VectorChannel::default()
3027            },
3028            when: ExportTrigger::default(),
3029            experiment_id_override: None,
3030        }
3031    }
3032
3033    /// EMPTY-SLICE pin — an empty `&[ExportSpec]` returns `false` for
3034    /// EVERY [`ArtifactKind`]. Sweep [`ArtifactKind::ALL`] so a new
3035    /// variant added without a matching arm in `ArtifactKind::select`
3036    /// surfaces at rustc's exhaustiveness gate on the `ALL` literal
3037    /// (arity forced by `[Self; 4]`) rather than as a silent false-
3038    /// positive at every downstream callsite composing this primitive.
3039    #[test]
3040    fn export_spec_slice_has_artifact_kind_returns_false_on_empty_slice_for_every_kind() {
3041        let empty: &[ExportSpec] = &[];
3042        for kind in ArtifactKind::ALL {
3043            assert!(
3044                !empty.has_artifact_kind(kind),
3045                "empty slice must return false for {kind:?}",
3046            );
3047        }
3048    }
3049
3050    /// PER-VARIANT pin — a single-element slice returns `true` for
3051    /// exactly the artifact kind whose slot is populated, `false` for
3052    /// every other variant. Sweep the [`ArtifactKind::ALL`] × ALL cross
3053    /// so a regression that (a) hard-coded the arm to a single kind
3054    /// (silently returning `true` for every populated slice regardless
3055    /// of query kind), or (b) probed on a different field (a stray
3056    /// `experiment_id_override.is_some()`, a `channel`-side variant
3057    /// discriminator, `when`) fails HERE at the substrate primitive
3058    /// rather than at each downstream `artifact-<kind>` callsite.
3059    #[test]
3060    fn export_spec_slice_has_artifact_kind_reads_source_slot_per_variant() {
3061        for populated in ArtifactKind::ALL {
3062            let slice = [export_with_artifact(populated)];
3063            for query in ArtifactKind::ALL {
3064                let expected = query == populated;
3065                assert_eq!(
3066                    slice.has_artifact_kind(query),
3067                    expected,
3068                    "populated={populated:?}: query {query:?} drifted",
3069                );
3070            }
3071        }
3072    }
3073
3074    /// MULTI-ENTRY pin — a slice with multiple entries returns `true`
3075    /// for every artifact kind that appears at any position (existential
3076    /// quantifier over the slice), `false` for kinds that appear at no
3077    /// position. Locks the `any` semantics so a regression that
3078    /// collapsed to a `first`-only probe (`slice.first().is_some_and(
3079    /// |e| kind.select(&e.source).is_some())`) fails here even though
3080    /// the single-element per-variant pin above passes.
3081    #[test]
3082    fn export_spec_slice_has_artifact_kind_scans_beyond_the_first_position() {
3083        let slice = [
3084            export_with_artifact(ArtifactKind::RunMarker),
3085            export_with_artifact(ArtifactKind::Receipts),
3086        ];
3087        for present in [ArtifactKind::RunMarker, ArtifactKind::Receipts] {
3088            assert!(
3089                slice.has_artifact_kind(present),
3090                "artifact kind at any position must resolve true: {present:?}",
3091            );
3092        }
3093        for absent in [ArtifactKind::TestReport, ArtifactKind::ProcessSnapshot] {
3094            assert!(
3095                !slice.has_artifact_kind(absent),
3096                "artifact kind absent from the slice must resolve false: {absent:?}",
3097            );
3098        }
3099    }
3100
3101    // ── ExportSpecSliceExt::has_report_payload_shape substrate pins ────
3102    //
3103    // Fail-before-pass-after granularity: `has_report_payload_shape`
3104    // did not exist before this commit — the `(&[ExportSpec],
3105    // ReportPayloadShape) -> bool` walk shape was not spelled anywhere
3106    // in the workspace on the derived `source.test_report.format
3107    // .payload_shape()` projection. The lift opens the FIFTH method on
3108    // the slice-level `ExportSpecSliceExt` (peer of `has_when` +
3109    // `has_channel_kind` + `has_report_format` + `has_artifact_kind`
3110    // on the SAME slice), composing the SAME inner `.as_ref()
3111    // .is_some_and` gate on the `Option<TestReportSource>` field that
3112    // `has_report_format` uses with a DERIVED closed-set equality on
3113    // the [`ReportFormat::payload_shape`] projection — the SECOND
3114    // probe on this trait whose closure reads a NESTED-Option scalar
3115    // past `test_report`, distinct on ONE dimension from
3116    // `has_report_format`: it compares the many-to-one DERIVED
3117    // [`ReportPayloadShape`] discriminator reached through the typed
3118    // projection rather than the raw [`ReportFormat`] discriminator.
3119    // The projection compresses the 4-arm [`ReportFormat`] closed set
3120    // to the 2-arm [`ReportPayloadShape`] closed set:
3121    // [`ReportFormat::NdJson`] alone → [`ReportPayloadShape
3122    // ::NdJsonLines`]; every other format → [`ReportPayloadShape
3123    // ::OpaqueBytes`].
3124
3125    /// EMPTY-SLICE pin — an empty `&[ExportSpec]` returns `false` for
3126    /// EVERY [`ReportPayloadShape`]. Sweep [`ReportPayloadShape::ALL`]
3127    /// so a new variant added without matching visits in the
3128    /// surrounding substrate reaches rustc's exhaustiveness gate on
3129    /// the `ALL` literal (arity forced by `[Self; 2]`) rather than as
3130    /// a silent false-positive at every downstream callsite composing
3131    /// this primitive.
3132    #[test]
3133    fn export_spec_slice_has_report_payload_shape_returns_false_on_empty_slice_for_every_kind() {
3134        let empty: &[ExportSpec] = &[];
3135        for kind in ReportPayloadShape::ALL {
3136            assert!(
3137                !empty.has_report_payload_shape(kind),
3138                "empty slice must return false for {kind:?}",
3139            );
3140        }
3141    }
3142
3143    /// PROJECTION-TRUTH-TABLE pin — sweep [`ReportFormat::ALL`] as the
3144    /// populated slot and [`ReportPayloadShape::ALL`] as the query,
3145    /// asserting the walk answers `true` iff `populated.payload_shape()
3146    /// == query`. Locks the derived-projection contract byte-for-byte:
3147    /// a regression that (a) probed [`ReportFormat`] directly (dropping
3148    /// the `.payload_shape()` call, silently answering `true` on the
3149    /// populated slot only when the query happens to match its raw
3150    /// format), (b) inverted the projection (`OpaqueBytes ↔ NdJsonLines`),
3151    /// or (c) collapsed the many-to-one projection to identity fails
3152    /// HERE at the substrate primitive rather than at each downstream
3153    /// `report-payload-shape-<kind>` callsite. The `Junit`,
3154    /// `TapV13`, and `Raw` populated arms MUST answer `true` only for
3155    /// [`ReportPayloadShape::OpaqueBytes`]; the `NdJson` arm MUST
3156    /// answer `true` only for [`ReportPayloadShape::NdJsonLines`] —
3157    /// the projection's many-to-one shape is pinned SYMMETRICALLY on
3158    /// both sides of the cross.
3159    #[test]
3160    fn export_spec_slice_has_report_payload_shape_reads_projection_per_format() {
3161        for populated in ReportFormat::ALL {
3162            let slice = [export_with_report_format(populated)];
3163            let expected_shape = populated.payload_shape();
3164            for query in ReportPayloadShape::ALL {
3165                let expected = query == expected_shape;
3166                assert_eq!(
3167                    slice.has_report_payload_shape(query),
3168                    expected,
3169                    "populated={populated:?} → shape={expected_shape:?}: query {query:?} drifted",
3170                );
3171            }
3172        }
3173    }
3174
3175    /// MULTI-ENTRY pin — a slice with multiple entries returns `true`
3176    /// for every [`ReportPayloadShape`] whose projected format appears
3177    /// at any position (existential quantifier over the slice), `false`
3178    /// for shapes that appear at no position. Two `OpaqueBytes`-
3179    /// lowering formats at different positions ([`ReportFormat::Junit`]
3180    /// and [`ReportFormat::TapV13`]) both projecting to the SAME
3181    /// [`ReportPayloadShape::OpaqueBytes`] MUST answer `true` for
3182    /// `OpaqueBytes` and `false` for `NdJsonLines` because neither
3183    /// entry's projection lowers to `NdJsonLines`. Locks the `any`
3184    /// semantics AND the many-to-one projection semantics together
3185    /// — a regression that collapsed to `first`-only probing OR
3186    /// dropped the projection (comparing the raw format `Junit ==
3187    /// NdJsonLines`) fails HERE even though the per-format
3188    /// PROJECTION-TRUTH-TABLE pin above passes.
3189    #[test]
3190    fn export_spec_slice_has_report_payload_shape_scans_beyond_the_first_position() {
3191        let slice = [
3192            export_with_report_format(ReportFormat::Junit),
3193            export_with_report_format(ReportFormat::TapV13),
3194        ];
3195        assert!(
3196            slice.has_report_payload_shape(ReportPayloadShape::OpaqueBytes),
3197            "two OpaqueBytes-lowering formats must both project to OpaqueBytes",
3198        );
3199        assert!(
3200            !slice.has_report_payload_shape(ReportPayloadShape::NdJsonLines),
3201            "no NdJson entries must yield NdJsonLines=false even at second position",
3202        );
3203        let mixed = [
3204            export_with_report_format(ReportFormat::Raw),
3205            export_with_report_format(ReportFormat::NdJson),
3206        ];
3207        for kind in ReportPayloadShape::ALL {
3208            assert!(
3209                mixed.has_report_payload_shape(kind),
3210                "mixed slice with both shapes must resolve true for every shape: {kind:?}",
3211            );
3212        }
3213    }
3214
3215    /// NESTED-OPTION-COLLAPSE pin — an export whose `source` carries
3216    /// NO `test_report` slot (a `receipts`-only source) contributes
3217    /// `false` for EVERY [`ReportPayloadShape`] kind, INCLUDING the
3218    /// projection of the default [`ReportFormat::Raw`] (which lowers
3219    /// to [`ReportPayloadShape::OpaqueBytes`]) that a naive
3220    /// `unwrap_or_default().payload_shape()` chain would spuriously
3221    /// match. Locks the outer nested-`Option` short-circuit contract
3222    /// so a regression that dropped the `.as_ref().is_some_and(…)`
3223    /// gate (e.g. rewriting to
3224    /// `e.source.test_report.map_or(ReportFormat::default(), |tr|
3225    /// tr.format).payload_shape() == kind`, which returns `true` for
3226    /// every non-test-report export when `kind == OpaqueBytes`) fails
3227    /// HERE at ONE narrow substrate site rather than at every
3228    /// downstream `report-payload-shape-OpaqueBytes` classifier
3229    /// callsite. Sweeps [`ReportPayloadShape::ALL`] so the contract
3230    /// is pinned symmetrically across every shape the closed set names.
3231    #[test]
3232    fn export_spec_slice_has_report_payload_shape_returns_false_on_non_test_report_source() {
3233        let receipts_only = ExportSpec {
3234            source: ArtifactSource {
3235                receipts: Some(ReceiptsSource::default()),
3236                ..ArtifactSource::default()
3237            },
3238            channel: VectorChannel {
3239                stdout: Some(StdoutChannel::default()),
3240                ..VectorChannel::default()
3241            },
3242            when: ExportTrigger::default(),
3243            experiment_id_override: None,
3244        };
3245        let slice = [receipts_only];
3246        for kind in ReportPayloadShape::ALL {
3247            assert!(
3248                !slice.has_report_payload_shape(kind),
3249                "receipts-only export must return false for every payload shape: {kind:?}",
3250            );
3251        }
3252    }
3253
3254    // ── ExportSpecSliceExt::has_applicable_at substrate pins ──────────
3255    //
3256    // Fail-before-pass-after granularity: `has_applicable_at` did not
3257    // exist before this commit — the `(&[ExportSpec], ProcessPhase) ->
3258    // bool` compound walk was previously spelled inline at
3259    // `EphemeralLifetime::has_applicable_exports`
3260    // (`self.exports.iter().any(|e| e.when.fires_on(phase))`). The lift
3261    // opens the SIXTH method on the slice-level `ExportSpecSliceExt`
3262    // (peer of `has_when`, `has_channel_kind`, `has_report_format`,
3263    // `has_artifact_kind`, `has_report_payload_shape`), composing
3264    // `ExportTrigger::fires_on` — the ONE substrate owner of the
3265    // "(trigger, phase) → bool" projection — with the same
3266    // `.iter().any(|e| …)` walk shape the five peers publish, and
3267    // enabling the `exports-fire-on-<phase>` require-tag prefix family
3268    // on both point and ephemeral surfaces in tatara-check to compose
3269    // against ONE substrate site rather than restating the compound
3270    // chain inline at each classifier arm.
3271
3272    /// EMPTY-SLICE pin — an empty `&[ExportSpec]` returns `false` for
3273    /// EVERY [`ProcessPhase`]. Sweep [`ProcessPhase::ALL`] so a new
3274    /// variant added without a matching arm in
3275    /// [`ExportTrigger::fires_on`] surfaces at rustc's exhaustiveness
3276    /// gate on the `ALL` literal (arity forced by `[Self; 11]`) rather
3277    /// than as a silent false-positive at every downstream callsite.
3278    #[test]
3279    fn export_spec_slice_has_applicable_at_returns_false_on_empty_slice_for_every_phase() {
3280        let empty: &[ExportSpec] = &[];
3281        for phase in ProcessPhase::ALL {
3282            assert!(
3283                !empty.has_applicable_at(phase),
3284                "empty slice must return false for {phase:?}",
3285            );
3286        }
3287    }
3288
3289    /// PER-TRIGGER × PER-PHASE pin — a single-element slice matches the
3290    /// `ExportTrigger::fires_on(phase)` truth table exactly, for every
3291    /// (trigger, phase) pair. Sweep the [`ExportTrigger::ALL`] ×
3292    /// [`ProcessPhase::ALL`] cross so a regression that (a) short-
3293    /// circuited to a bare `e.when == kind` equality on the wrong
3294    /// carrier, (b) matched only the terminal phase pair
3295    /// (Attested/Failed) while dropping `Always`'s dual-phase coverage,
3296    /// or (c) inverted a non-terminal phase to return `true` fails HERE
3297    /// at the substrate primitive rather than at each downstream
3298    /// `exports-fire-on-<phase>` classifier callsite.
3299    #[test]
3300    fn export_spec_slice_has_applicable_at_matches_fires_on_truth_table_per_pair() {
3301        for trigger in ExportTrigger::ALL {
3302            let slice = [export_at(trigger)];
3303            for phase in ProcessPhase::ALL {
3304                let expected = trigger.fires_on(phase);
3305                assert_eq!(
3306                    slice.has_applicable_at(phase),
3307                    expected,
3308                    "trigger={trigger:?} phase={phase:?} drifted from fires_on",
3309                );
3310            }
3311        }
3312    }
3313
3314    /// MULTI-ENTRY pin — a slice with multiple entries returns `true`
3315    /// for every phase where any entry's trigger fires (existential
3316    /// quantifier over the slice), `false` for phases where no entry's
3317    /// trigger fires. Locks the `any` semantics AND the compound
3318    /// projection semantics together — a regression that collapsed to
3319    /// `first`-only probing (`slice.first().is_some_and(|e|
3320    /// e.when.fires_on(phase))`) OR dropped the projection (comparing
3321    /// the raw trigger `Always == OnAttested`) fails HERE even though
3322    /// the single-element per-pair pin above passes.
3323    #[test]
3324    fn export_spec_slice_has_applicable_at_scans_beyond_the_first_position() {
3325        let attested_only = [
3326            export_at(ExportTrigger::OnFailed),
3327            export_at(ExportTrigger::OnAttested),
3328        ];
3329        assert!(
3330            attested_only.has_applicable_at(ProcessPhase::Attested),
3331            "OnAttested at second position must fire on Attested",
3332        );
3333        assert!(
3334            attested_only.has_applicable_at(ProcessPhase::Failed),
3335            "OnFailed at first position must fire on Failed",
3336        );
3337        let always_and_failed = [
3338            export_at(ExportTrigger::OnFailed),
3339            export_at(ExportTrigger::Always),
3340        ];
3341        for phase in [ProcessPhase::Attested, ProcessPhase::Failed] {
3342            assert!(
3343                always_and_failed.has_applicable_at(phase),
3344                "Always at second position must fire on {phase:?}",
3345            );
3346        }
3347        for phase in [
3348            ProcessPhase::Pending,
3349            ProcessPhase::Forking,
3350            ProcessPhase::Execing,
3351            ProcessPhase::Running,
3352            ProcessPhase::Reconverging,
3353            ProcessPhase::Releasing,
3354            ProcessPhase::Exiting,
3355            ProcessPhase::Zombie,
3356            ProcessPhase::Reaped,
3357        ] {
3358            assert!(
3359                !always_and_failed.has_applicable_at(phase),
3360                "no trigger fires on non-terminal {phase:?}",
3361            );
3362        }
3363    }
3364}