kcode-k1-audio-classification-projection 0.4.2

Durable SQLite projection for K1 audio classification callbacks
Documentation
# 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.

Version 0.4.2 exact-pins audio-classification format 0.6.1 and projection reducer and state 0.2.1. This dependency-only cutover preserves persisted formats, SQLite schema 2, public APIs, and behavior.

## 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.