kcode-k1-audio-classification-projection 0.2.0

Durable SQLite projection for K1 audio classification callbacks
Documentation
# Audio classification projection

Durable local state projection for callbacks belonging to the exact K1 subsystem `audio-classification`. The library owns no KTO registration, replay, provider, worker, runtime, Object, Peering, audio copy, event log, applied-ID ledger, polling, or retry behavior.

## Public API

The library re-exports `ExecutedAnalysis`, `FragmentStageV1`, and `SpeakerLabelV1` from `kcode-k1-audio-classification-format`. `FragmentId` aliases the single unified `TxId` nominal type used by the audio format, fragment submission, transaction, and KTO crates.

```rust
pub type FragmentId = kcode_k1_audio_fragment_submit::FragmentId;

pub enum OverallState { Queued, Running, Failed, Completed, Confirmed, Discarded }
pub enum StageState { Pending, Running, Succeeded, Failed }
pub enum LlmJobState { Running, Succeeded, Failed }

pub struct StageStatus {
    pub stage: FragmentStageV1,
    pub state: StageState,
}

pub struct LlmJobStatus {
    pub attempt: u32,
    pub sequence: u64,
    pub stage: FragmentStageV1,
    pub name: String,
    pub state: LlmJobState,
}

pub struct FragmentStatus {
    pub state: OverallState,
    pub queue: StageStatus,
    pub transcript: StageStatus,
    pub speaker_labels: StageStatus,
    pub speaker_features: StageStatus,
    pub structuring: StageStatus,
    pub label_confirmation: StageStatus,
    pub attempt_count: u32,
    pub jobs: Vec<LlmJobStatus>,
    pub interim_txid: Option<FragmentId>,
    pub analysis: Option<ExecutedAnalysis>,
    pub confirmed_labels: Vec<SpeakerLabelV1>,
    pub final_transcript: Option<String>,
    pub errors: Vec<String>,
    pub errors_truncated: bool,
}

pub enum ProjectionEffect { None, Start, Abort, LabelsCommitted }

pub struct AppliedEvent {
    pub fragment_id: FragmentId,
    pub effect: ProjectionEffect,
}

pub struct InterruptedFragment {
    pub fragment_id: FragmentId,
    pub stage: FragmentStageV1,
}

pub struct Projection;

impl Projection {
    pub fn open(root: &Path, ordering: &K1TxnOrdering) -> Result<(Self, Option<TxId>), String>;
    pub fn apply(&self, callback_txid: TxId, payload: &[u8]) -> Result<AppliedEvent, String>;
    pub fn status(&self, fragment_id: FragmentId) -> Result<Option<FragmentStatus>, String>;
    pub fn queued(&self) -> Result<Vec<FragmentId>, String>;
    pub fn running(&self) -> Result<Vec<InterruptedFragment>, String>;
    pub fn validate_labels(&self, fragment_id: FragmentId, labels: &[SpeakerLabelV1]) -> Result<TxId, String>;
    pub fn clear(&self) -> Result<(), String>;
    #[cfg(feature = "testkit")]
    pub fn inject_errors(&self, fragment_id: FragmentId, errors: Vec<String>) -> Result<(), String>;
}
```

The state enums are `Copy`, `Clone`, `Debug`, `PartialEq`, and `Eq`. The structs and `ProjectionEffect` are `Clone`, `Debug`, and `PartialEq`, and also `Eq` where all fields permit it. Persistence-oriented Serde implementations and postcard bytes are private storage details, not an interchange contract.

A Queue creates one fragment, succeeds its Queue stage, leaves the other five stages pending, and returns `Start`; a duplicate Queue is invalid. Progress starts one-based attempts, retains jobs ordered by attempt and strictly increasing sequence, updates running jobs, and completes analysis stages. A terminal failure marks its exact stage, retains its error, and does not infer a provider retry. Discard returns `Abort`, permanently wins, and suppresses every later event except cursor advancement. Completion stores the callback transaction ID and complete analysis. Confirmation requires that exact interim ID and exact ordered speaker labels, derives the final transcript, confirms the fragment, and returns `LabelsCommitted`. Invalid references or transitions return an error without changing either status or cursor.

`errors` retains the first, oldest 5,000 terminal errors in canonical order. Later errors only set `errors_truncated`. Final transcript derivation replaces only exact `Speaker N` tokens in lines beginning `[high] `, `[medium] `, or `[low] ` and followed by `:` or ` [overlap]:`; every other byte is preserved. Person IDs must be nonblank and contain no carriage return or line feed.

`status` returns one coherent owned value. `queued` returns queued fragment IDs. `running` returns running fragments with their latest currently running analysis stage, or Queue when no analysis stage is running. Result order is unspecified. `validate_labels` performs confirmation validation for a completed fragment without mutation and returns its interim transaction ID. `clear` atomically removes all fragments and nulls the cursor. The `testkit`-only hook appends supplied errors through the same 5,000-entry cap without changing lifecycle state or cursor.

## Persistence, recovery, and concurrency

The root contains only the projection database `audio-classification.sqlite3` and SQLite WAL/SHM sidecars. SQLite uses WAL and synchronous `FULL`. One callback transaction loads and rewrites only its fragment postcard blob and advances `metadata.last_applied_txid`; status and cursor never move separately. `clear` is also one SQLite transaction.

Opening creates a missing root and database. It validates schema, rows, status identities, actionable-state values, error bounds, and cursor bytes. Recoverably malformed, incompatible, corrupt, noncanonical, or wrong-subsystem projection state is deleted and recreated empty. Ordinary permission, busy, locked, read-only, interrupted, full, cannot-open, locking, and filesystem I/O failures are returned. A valid cursor is returned for caller-owned KTO replay; this library never registers or replays it.

One connection mutex serializes only bounded SQLite, postcard, and local value work. No KTO query or other dependency operation runs while it is held. Independent projection instances and all unrelated external operations require no caller coordination. Work is linear in the addressed status, retained jobs, labels, transcript, and capped errors; startup validation is linear in stored fragments. SQLite and filesystem latency has no finite wall-clock bound.