kcode-audio-ingress 0.1.1

Durable automatic audio transcription with restart recovery
Documentation
# API

`kcode-audio-ingress` durably accepts complete WAV byte buffers and
automatically runs its integrated transcription pipeline. The caller supplies
an already configured transcriber and one persistence-root path. The library
derives its database and retained-original locations beneath that root.

```rust
pub struct AudioInput {
    pub bytes: Vec<u8>,
    pub recorded_at: chrono::DateTime<chrono::Utc>,
    pub original_filename: Option<String>,
}

pub struct Submission {
    pub recording_id: uuid::Uuid,
    pub deduplicated: bool,
}

pub struct Status {
    pub recordings: Vec<RecordingStatus>,
}

pub struct RecordingStatus {
    pub id: uuid::Uuid,
    pub sha256: String,
    pub original_filename: String,
    pub size_bytes: u64,
    pub recorded_at: chrono::DateTime<chrono::Utc>,
    pub received_at: chrono::DateTime<chrono::Utc>,
    pub transcription_model: String,
    pub reconciliation_model: String,
    pub reconciliation_reasoning: String,
    pub state: RecordingState,
}

pub enum RecordingState {
    Queued,
    Processing {
        attempt: u8,
        progress: TranscriptionStatus,
    },
    Complete {
        transcript: String,
    },
    Failed {
        attempts: u8,
        error: String,
        retryable: bool,
    },
}

pub enum ErrorKind {
    InvalidInput,
    NotFound,
    Conflict,
    Internal,
}

pub struct Error {
    // private fields
}

pub struct AudioIngress {
    // private fields
}

impl Error {
    pub fn kind(&self) -> ErrorKind;
}

impl AudioIngress {
    pub async fn open(
        persistence_root: impl AsRef<std::path::Path>,
        transcriber: AudioTranscriber,
    ) -> Result<AudioIngress, Error>;

    pub async fn submit(&self, input: AudioInput) -> Result<Submission, Error>;

    pub fn status(&self) -> Result<Status, Error>;

    pub fn retry(&self, recording_id: uuid::Uuid) -> Result<(), Error>;
}
```

The merged transcriber API remains available from this crate:

```rust
pub struct AudioTranscriber;

impl AudioTranscriber {
    pub fn new(
        gemini: kcode_gemini_api::Gemini,
        codex: kcode_codex_runtime::Codex,
    ) -> AudioTranscriber;

    pub fn transcribe(&self, audio: Vec<u8>) -> TranscriptionJob;
}

impl TranscriptionJob {
    pub fn status(&self) -> TranscriptionStatus;
}
```

`TranscriptionStatus`, `JobState`, `Step`, `StepState`, `StepStatus`, and
`StepError` retain the former transcriber library's fields, serialization, and
semantics. `AudioTranscriber::transcribe` is still available for callers that
want an in-memory job without ingress persistence.

## Operation

- `AudioIngress::open` creates or opens `<root>/state.sqlite3`, creates
  `<root>/originals/`, applies compatible schema migrations, recovers
  interrupted attempts, and starts the background worker. It requires an
  active Tokio runtime.
- `submit` rejects an empty buffer, computes its SHA-256 identity, retains the
  exact bytes in content-addressed storage, commits the recording row, and
  returns only after the original and metadata are durable. Repeated bytes
  return the existing recording with `deduplicated: true`.
- `status` returns one complete snapshot ordered by recording time, newest
  first. A completed state contains the canonical reconciled Markdown
  transcript. Processing progress never contains a transcript body.
- `retry` applies only to a failed recording. It clears the previous attempt
  budget and schedules a fresh automatic five-attempt budget. Retrying an
  unknown identifier returns `NotFound`; retrying any nonfailed recording
  returns `Conflict`.

The library automatically retries a retryable full transcription job at most
five times. Non-retryable input or provider failures stop immediately.
Exhausted work remains failed until `retry` is called. Active dependency jobs
are in memory; after process restart the library starts a new job from the
durable original.

Each completed audio chunk's validated structured transcript is inserted into
SQLite before its progress step becomes complete. Piece rows are keyed by
recording, full-job attempt, and chronological index. A failed or interrupted
attempt therefore retains every piece it completed, and a later retry cannot
overwrite that history. These rows are intentionally internal so the existing
ingress API and status JSON remain unchanged.

The library has no HTTP, multipart, streaming-input, storage-quota, transcript
delivery, Kmap, Chatend, or application-framework API.