kcode-k1-audio-classification-format 0.5.0

Binary formats for K1 audio-classification events and fragments
Documentation
# Audio classification formats

Pure Rust structural binary formats for audio-classification transaction payloads and local fragment values. The library performs no filesystem I/O, provider calls, transaction work, retry execution, persistence, classification, training, or network work.

## Public API

The library re-exports `TxId`, `ExecutedAnalysis`, `FeatureVector24`, and `LocalSpeakerLabel`.

```rust
pub enum FragmentStageV1 {
    Queue,
    Transcript,
    SpeakerLabels,
    SpeakerFeatures,
    Structuring,
    LabelConfirmation,
}

pub struct QueueV2 {
    pub audio_object_id: TxId,
}

pub struct ProgressV1 {
    pub fragment_id: TxId,
    pub update: ProgressUpdateV1,
}

pub enum ProgressUpdateV1 {
    LlmJobStarted {
        sequence: u64,
        stage: FragmentStageV1,
        name: String,
    },
    LlmJobSucceeded {
        sequence: u64,
    },
    LlmJobFailed {
        sequence: u64,
        error: String,
    },
    StageCompleted {
        stage: FragmentStageV1,
    },
}

pub struct TranscriptionCompleteV1 {
    pub fragment_id: TxId,
    pub analysis: ExecutedAnalysis,
}

pub struct FailedV2 {
    pub fragment_id: TxId,
    pub stage: FragmentStageV1,
    pub llm_job_sequence: Option<u64>,
    pub error: String,
}

pub struct DiscardedV2 {
    pub fragment_id: TxId,
}

pub struct SpeakerLabelV1 {
    pub speaker: LocalSpeakerLabel,
    pub person_id: String,
}

pub struct LabelConfirmationV1 {
    pub fragment_id: TxId,
    pub interim_txid: TxId,
    pub speakers: Vec<SpeakerLabelV1>,
}

pub enum AudioClassificationEventV3 {
    Queue(QueueV2),
    Progress(ProgressV1),
    TranscriptionComplete(TranscriptionCompleteV1),
    Failed(FailedV2),
    Discarded(DiscardedV2),
    LabelConfirmation(LabelConfirmationV1),
}

pub struct StagedSpeakerV1 {
    pub speaker: LocalSpeakerLabel,
    pub language: String,
    pub features: FeatureVector24,
    pub usable_for_training: bool,
}

pub struct StagedFragmentV1 {
    pub analysis_txid: TxId,
    pub transcript: String,
    pub speakers: Vec<StagedSpeakerV1>,
}

pub struct FinalSpeakerV1 {
    pub speaker: LocalSpeakerLabel,
    pub person_id: String,
    pub language: String,
    pub features: FeatureVector24,
    pub usable_for_training: bool,
}

pub struct FinalFragmentV1 {
    pub analysis_txid: TxId,
    pub confirmation_txid: TxId,
    pub transcript: String,
    pub speakers: Vec<FinalSpeakerV1>,
}

pub enum FormatError {
    Truncated,
    LengthOverflow,
    UnsupportedVersion(u8),
    UnknownEventTag(u8),
    InvalidEventBody,
    InvalidFragmentKind(u8),
    NonZeroReserved,
    NonZeroPadding,
    NonZeroStagedConfirmation,
    InvalidTxIdSlotLength(usize),
    InvalidUtf8,
    InvalidSpeakerLabel,
    InvalidFeatureBody,
    InvalidBoolean(u8),
    TrailingBytes,
    InvalidPath,
}

pub struct TxIdSlot { /* private fields */ }

impl TxIdSlot {
    pub const LEN: usize = 16;
    pub const PADDING_LEN: usize = 4;
    pub const fn new(txid: TxId) -> Self;
    pub const fn txid(self) -> TxId;
    pub fn encode(self) -> [u8; Self::LEN];
    pub fn decode(bytes: &[u8]) -> Result<Self, FormatError>;
}

pub fn encode_event(
    event: &AudioClassificationEventV3,
) -> Result<Vec<u8>, FormatError>;

pub fn decode_event(
    bytes: &[u8],
) -> Result<AudioClassificationEventV3, FormatError>;

pub fn encode_staged_fragment(
    value: &StagedFragmentV1,
) -> Result<Vec<u8>, FormatError>;

pub fn decode_staged_fragment(
    bytes: &[u8],
) -> Result<StagedFragmentV1, FormatError>;

pub fn encode_final_fragment(
    value: &FinalFragmentV1,
) -> Result<Vec<u8>, FormatError>;

pub fn decode_final_fragment(
    bytes: &[u8],
) -> Result<FinalFragmentV1, FormatError>;

pub fn txid_path(txid: TxId) -> PathBuf;

pub fn txid_from_path(
    path: impl AsRef<Path>,
) -> Result<TxId, FormatError>;
```

`FragmentStageV1`, `QueueV2`, `ProgressUpdateV1`, `FailedV2`, `DiscardedV2`, `SpeakerLabelV1`, and `LabelConfirmationV1` implement `Debug`, `Clone`, `PartialEq`, `Eq`, `Serialize`, and `Deserialize`. `ProgressV1` and `TranscriptionCompleteV1` implement `Debug`, `Clone`, `PartialEq`, `Serialize`, and `Deserialize`. `AudioClassificationEventV3`, `StagedSpeakerV1`, `StagedFragmentV1`, `FinalSpeakerV1`, and `FinalFragmentV1` implement `Debug`, `Clone`, and `PartialEq`. `FormatError` implements `Debug`, `Clone`, `PartialEq`, `Eq`, `Display`, and `Error`. `TxIdSlot` implements `Debug`, `Clone`, `Copy`, `PartialEq`, and `Eq`.

## Transaction event bytes

`encode_event` and `decode_event` use envelope version byte `4`, followed by one stable tag byte and one postcard body for the selected variant. Tags are `1` Queue, `2` TranscriptionComplete, `3` Failed, `4` Discarded, `5` LabelConfirmation, and `6` Progress. Every event `TxId` serializes as exactly `[u8; 12]`.

Decoding accepts only version 4, a known tag, one structurally valid body, and no trailing bytes; it rejects version 3. It is structural only and does not reject blank strings, duplicate labels, invalid state references, or ordering.

## Local fragment bytes

The staged and final fragment codecs use format version 1. Bytes 0 through 15 are a header: version `1`, kind `1` staged or `2` final, then fourteen zero reserved bytes. Bytes 16 through 31 contain the aligned analysis `TxIdSlot`. Bytes 32 through 47 are zero for staged values and contain the aligned confirmation `TxIdSlot` for final values. The body starts at byte 48.

Variable lengths and speaker counts are little-endian `u64`. The body contains transcript UTF-8, speaker count, and ordered speakers. Speakers contain canonical labels, final-only person IDs, language, length-prefixed postcard `FeatureVector24`, and one usability byte restricted to 0 or 1.

`TxIdSlot` stores 12 ID bytes followed by four zero padding bytes. Fragment decoding rejects truncation, length overflow, unsupported versions or kinds, nonzero reserved bytes or padding, a nonzero staged confirmation slot, malformed UTF-8, labels or features, invalid booleans, and trailing bytes. It does not call semantic validators from the re-exported analysis values.

## Paths, performance, and concurrency

`txid_path` maps a `TxId` to `<first>/<remaining15>.dat` using a 16-character URL-safe unpadded base64 name. `txid_from_path` accepts only that two-component relative form. Neither accesses a filesystem.

Encoding and decoding take O(input plus output) work and O(output) encoding or decoded-value memory. Untrusted speaker counts are not eagerly allocated. Path conversion takes constant time and bounded memory. The library is stateless and owns no locks, queues, waits, callbacks, backpressure, or serialization; independent calls do not block one another.