helena 0.1.0

Core types and component interfaces for helena, a latent data-to-waveform generation platform.
Documentation
//! Versioned experiment configuration: raw documents to validated specs.
//!
//! This module is the raw→validated typestate for the experiment config
//! (refoundation A5), the config-side twin of [`manifest`](crate::manifest).
//! Deserialization lands *only* in [`RawExperimentConfig`], whose sole exit is
//! [`RawExperimentConfig::resolve`]; there is no other way to obtain an
//! [`ExperimentSpec`], so a command cannot act on an unvalidated document.
//!
//! The two tiers separate three concerns that used to blur:
//!
//! - **Shape** ([`RawExperimentConfig`] and its `Raw*` sections): a serde
//!   schema with `#[serde(deny_unknown_fields)]` on the leaves — a typo is a
//!   parse error, so the CLI's mirrored strict-config schema is unnecessary.
//!   The three component sections carry a deliberate flattened `extra` map and
//!   so cannot deny unknowns; their discipline is [`reject_unknown_extra`],
//!   applied by the resolver that consumes `extra`.
//! - **Semantics** ([`ExperimentSpec`] and its sections): domain-typed and
//!   proven — [`SampleRate`](crate::time::SampleRate), `NonZero` dims, a
//!   validated [`Segmentation`], [`GenerationDefaults`].
//! - **Provenance** ([`ConfigFingerprint`] / [`RecipeFingerprint`]): the full
//!   config stamp versus the training-determining [`Recipe`] projection, so
//!   "inference knobs never invalidate trained weights" is a typed boundary,
//!   not a comment.
//!
//! # Wire-format break
//!
//! Two formerly-stringly fields are now enums whose parse is total:
//! `source_data.type` is a [`SourceKind`](crate::manifest::SourceKind) and
//! `audio_codec.latent_type` a [`KindTag`](crate::latent::KindTag). The old
//! `audio_codec.latent_type = "rvq"` / `"rvq_tokens"` value no longer parses —
//! tokens-with-codebooks is the single token model. A pre-refoundation config
//! naming `rvq` must be rewritten to `"tokens"` (a sanctioned pre-publish
//! breaking change).

mod extra;
mod raw;
mod spec;

#[cfg(test)]
mod tests;

use serde::{Deserialize, Serialize};

use crate::error::Result;
use crate::provenance::Fingerprint;

pub use extra::reject_unknown_extra;
pub use raw::{
    Alignment, RawAudioCodec, RawDataEncoder, RawExperimentConfig, RawGeneration, RawGenerator,
    RawSegmentation, RawSourceData, RawTargetAudio, RawTraining,
};
pub use spec::{
    AudioCodecSpec, DataEncoderSpec, ExperimentSpec, GenerationDefaults, GeneratorSpec, Recipe,
    Segmentation, SourceDataSpec, TargetAudioSpec, TrainingSpec,
};

/// The full-provenance fingerprint of an experiment config (PRD NFR-010).
///
/// Mintable only from a [`RawExperimentConfig`], so an artifact field of this
/// type provably describes a real config. Covers *every* field including the
/// inference-only `generation` section — the provenance stamp records the
/// knobs that shaped an output. Serializes transparently as the underlying
/// `sha256:…` [`Fingerprint`].
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct ConfigFingerprint(Fingerprint);

impl ConfigFingerprint {
    /// Fingerprint a raw config through the one canonical
    /// [`Fingerprint::of`](crate::provenance::Fingerprint::of) path.
    pub fn of(config: &RawExperimentConfig) -> Result<ConfigFingerprint> {
        Ok(Self(Fingerprint::of(config)?))
    }

    /// The underlying fingerprint.
    pub fn fingerprint(&self) -> Fingerprint {
        self.0
    }
}

impl std::fmt::Display for ConfigFingerprint {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}

/// The recipe fingerprint that gates checkpoint reload (GEN-14, NFR-010).
///
/// Mintable only from a [`Recipe`] — the training-determining projection that
/// excludes the `generation` section — so it is invariant under inference-knob
/// edits by construction and cannot be minted from anything that includes them.
/// Serializes transparently as the underlying `sha256:…` [`Fingerprint`].
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct RecipeFingerprint(Fingerprint);

impl RecipeFingerprint {
    /// Fingerprint a recipe projection.
    pub fn of(recipe: &Recipe<'_>) -> Result<RecipeFingerprint> {
        Ok(Self(Fingerprint::of(recipe)?))
    }

    /// The underlying fingerprint.
    pub fn fingerprint(&self) -> Fingerprint {
        self.0
    }
}

impl std::fmt::Display for RecipeFingerprint {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}