# Audio classification projection
Durable local state projection for callbacks in the exact K1 subsystem `audio-classification`. It owns no KTO registration or replay, provider, worker, runtime, Object, Peering, audio copy, event log, polling, or retry policy.
## Public API
The library re-exports `ExecutedAnalysis`, `FragmentStageV1`, `SpeakerLabelV1`, `FragmentId`, `OverallState`, `StageState`, `LlmJobState`, `StageStatus`, `LlmJobStatus`, and `FragmentStatus` from the projection-state library, and `ProjectionEffect` from the projection-reducer library.
```rust
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>;
}
```
`AppliedEvent` and `InterruptedFragment` implement `Clone`, `Debug`, `PartialEq`, and `Eq`. Re-exported declarations and traits are defined by their owning libraries.
Queue creates a fragment and returns `Start`. Queue from `Failed` durably requeues it and preserves history and result fields; the first subsequent progress job starts a new one-based attempt and resets analysis progress and result fields. Other duplicate Queue transitions are invalid. Discard returns `Abort` once and permanently suppresses every later event, including Queue and repeated Discarded, while the cursor still advances. Confirmation returns `LabelsCommitted`. Other transition, error-retention, typed `Option<PersonId>` label, and transcript semantics are those of the re-exported state and reducer contracts. Invalid transitions change neither status nor cursor.
`status` returns one coherent owned value. `queued` returns queued IDs. `running` returns running fragments with their latest running analysis stage, or Queue when none is running; result order is unspecified. `validate_labels` validates without mutation. `clear` atomically removes all fragments and nulls the cursor. The `testkit` hook appends errors through the state library's 5,000-entry cap without changing lifecycle state or cursor.
## Persistence, recovery, and concurrency
The root contains `audio-classification.sqlite3` and SQLite WAL/SHM sidecars. SQLite uses WAL and synchronous `FULL`. Schema version 2 stores canonical postcard bytes for the shared typed state, including known or unknown person identities. Each callback transaction rewrites only its addressed fragment and advances `metadata.last_applied_txid`; status and cursor never move separately.
Opening validates the exact schema, rows, status invariants, canonical postcard bytes, actionable values, and cursor. Schema 1 and every other incompatible schema are recreated empty at this current-format recovery boundary. Recoverably malformed, corrupt, noncanonical, missing-KTO, or wrong-subsystem state is also 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 replay.
One connection mutex serializes bounded SQLite, postcard, and local value work for one root. No KTO query or caller operation runs while held. Independent roots do not share a lane. Work is linear in the addressed status and bounded state collections; startup is linear in stored fragments. SQLite and filesystem latency has no finite wall-clock bound.