tatara-closed-set 0.3.26

ClosedSet — the typed witness for the closed-set-enum idiom (ALL + label + parse_label + typed parse-rejection carrier)
Documentation

[ClosedSet] — the typed witness for the closed-set-enum idiom.

The substrate carries 36+ closed-set enums (AtomKind, QuoteForm, SexpShape, MacroDefHead, UnquoteForm, KwargPathKind, ExpectedKwargShape, CompilerSpecIoStage in this crate; ProcessPhase, ConditionKind, IntentKind, LifetimeKind, TeardownPolicy, ProcessSignal, ArtifactKind, ReportFormat, ChannelKind, ExportTrigger, ReceiptKind, RequestorKind, SelectStrategyKind, EncapsulationMode, EncapsulationTarget, DataClassification, AllocationPhase, CalmClassification, OptimizationDirection, HorizonKind, SubstrateType, ConvergencePointType, MemberState, PoolPhase, ReplacementPolicy, ReturnPolicy, AutoTerminateKind, TerminateReasonKind, VerificationPhase, WorkloadKind, MustReachPhase, SighupStrategy, BreatheDimensionKind, MatrixTarget, ReportPayloadShape, … in tatara-process). Each one independently re-derives the same four-piece shape:

  1. pub const ALL: [Self; N] = [...] — the forced-arity array literal that fails compilation if a new variant lands without being added to the set.
  2. fn label(self) -> &'static str (or its domain-canonical sibling — prefix, marker, keyword, as_str) — the typed projection from variant to the canonical &'static str literal the diagnostic / wire format uses.
  3. impl FromStr whose body is a linear sweep over Self::ALL keyed on the projection — exactly the same 6-line for-loop / Err(Unknown<TypeName>(s.to_owned())) shape every implementor re-derives byte-for-byte.
  4. pub struct Unknown<TypeName>(pub String) with #[error("unknown <thing>: {0}")] — the typed parse-rejection carrier that hands the offending input back unchanged.

Pieces 1, 2, 4 carry per-variant content (the variants themselves, their canonical labels, the rejection-class wording) and stay per-implementor. Piece 3 — the for-loop sweep — is mechanically identical across every implementor and is the duplication this trait lifts.

Trait surface

pub trait ClosedSet: Sized + Copy {
    const ALL: &'static [Self];
    type Unknown;
    fn label(self) -> &'static str;
    fn make_unknown(s: &str) -> Self::Unknown;

    // Default — the lifted for-loop body.
    fn parse_label(s: &str) -> Result<Self, Self::Unknown> { ... }
}

A typical implementor wires in three lines beyond its existing inherent surface, and its hand-rolled FromStr body collapses from six lines to one:

impl ClosedSet for AtomKind {
    const ALL: &'static [Self] = &Self::ALL;
    type Unknown = UnknownAtomKind;
    fn label(self) -> &'static str { AtomKind::label(self) }
    fn make_unknown(s: &str) -> Self::Unknown { UnknownAtomKind(s.to_owned()) }
}

impl FromStr for AtomKind {
    type Err = UnknownAtomKind;
    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
        <Self as ClosedSet>::parse_label(s)
    }
}

Implementors with a non-label inherent projection name (QuoteForm::prefix, UnquoteForm::marker, MacroDefHead::keyword, tatara_process's *::as_str) delegate ClosedSet::label to their domain-canonical method — the trait method gives every implementor a STABLE name (label), with the inherent name kept as the load-bearing domain-vocabulary projection (prefix for homoiconic reader-form, marker for template-substitution punctuation, keyword for macro-head reserved word, as_str for tatara-process's PascalCase wire format).

Theory grounding

THEORY.md §V.1 — knowable platform; the for-loop / Unknown- emission pattern was a known idiom carried by convention across 36+ implementors. This trait makes the idiom a TYPED WITNESS — any new closed-set enum that implements ClosedSet plugs into the trait's default parse_label and a future generic consumer (a metrics tagger, a Lisp keyword completer, an iac-forge canonical-form renderer over closed-set kinds) can take a T: ClosedSet parameter and walk the set without knowing which crate it lives in.

THEORY.md §VI.1 — generation over composition; the trait IS the generative shape. New closed-set enums add the trait impl + the one-line FromStr delegation instead of re-deriving the for-loop body, and the parse-rejection diagnostic surface narrows from "36+ independent sweeps that must each be kept symmetric" to "one default body, 36+ impls of a four-method contract."

Provenance

This crate is pleme-io/tatara's tatara-lisp/src/closed_set.rs moved verbatim, per theory/TATARA-LISP-CONSOLIDATION.md phase 2 step 1. tatara-lisp neither carries nor re-exports ClosedSet, which keeps the published tatara-lisp small enough for the phase-3 facade.

That property is a CONVENTION, not a structural guarantee — read this before assuming otherwise. Step 1 landed with the edge pointing tatara-closed-set → tatara-lisp (for domain::suggest) and celebrated the resulting cycle as the enforcement mechanism: the reverse edge "cannot be written". That framing was load-bearing and is now false. Step 2 needs tatara-lisp to carry the ~45 LispError variants whose payloads are ClosedSet implementors, which requires exactly that reverse edge, so phase 2 was structurally blocked by its own step 1.

The edge was therefore INVERTED (patamar's cheapest verb, chosen on an edge census of one): suggest moved here, tatara-closed-set dropped its tatara-lisp dependency and became a true leaf, and tatara-lisp now depends on this crate. The metric was never a tatara-lisp primitive to begin with — step 1 pulled it forward from A because closed-set needed it, and it had zero other callers in this workspace, so inverting put it back with its only consumer rather than moving it away from callers.

Consequence to respect: nothing now makes tatara-lisp re-exporting ClosedSet a compile error. Keeping it out is a review-time rule.

The trait body is byte-identical to its pre-split form apart from crate::domain::suggesttatara_lisp::domain::suggest and the doc-links that pointed at sibling modules which stayed in tatara-lisp. Nothing about the wire surface (label / parse_label / the "unknown {SET_LABEL}: {input}" carrier rendering) was reshaped — the implementors serialize Kubernetes CRD state through it.