[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:
pub const ALL: [Self; N] = [...]— the forced-arity array literal that fails compilation if a new variant lands without being added to the set.fn label(self) -> &'static str(or its domain-canonical sibling —prefix,marker,keyword,as_str) — the typed projection from variant to the canonical&'static strliteral the diagnostic / wire format uses.impl FromStrwhose body is a linear sweep overSelf::ALLkeyed on the projection — exactly the same 6-line for-loop /Err(Unknown<TypeName>(s.to_owned()))shape every implementor re-derives byte-for-byte.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::suggest → tatara_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.