# kcode-speaker-store
Append-only SQLite persistence for speaker-classification lineage, parsed attempts, labels, artifacts, and active-model pointers.
## Operations
```rust
pub fn open(path: impl AsRef<std::path::Path>) -> Result<Store, StoreError>;
impl Store {
pub fn execute(&self, request: Request) -> Result<Response, StoreError>;
}
```
`Request` is `Commit(Vec<EventEnvelope>)` or `Read(Query)`. Each envelope contains a validated `event_id: Key` and one `Event`:
- `RegisterSegment(SegmentRegistration { segment, group_id, recording_kind })`
- `RecordAttempt(AttemptRecord { attempt_id, clip_object, extraction_key, provider_result_object, outcome })`
- `SelectAttempt(AttemptSelection { clip_object, extraction_key, attempt_id, reason })`
- `SetSampleState(SampleStateChange { sample_id, state, reason })`
- `PutArtifact(ArtifactRecord { artifact_id, cohort_id, kind, sha256, bytes })`
- `SetActiveModel(ActiveModelChange { cohort_id, model_artifact_id, reason })`
Attempt outcomes are scored, unscorable, invalid response, or provider failure. Scored outcomes contain recording quality and contiguous `StoredSpeaker` rows. Sample states are unlabeled, confirmed with a speaker ID, or retracted. Artifact kinds are Model, Evaluation, and ReplayManifest.
Queries read one attempt, filtered attempts, training rows, repeatability rows, one artifact, an active model, or inventory. `Response { revision, result }` returns a matching typed `ResponseKind`: `Commit { applied }`, `Attempt`, `Attempts`, `TrainingRows`, `RepeatabilityRows`, `Artifact`, `ActiveModel`, or `Inventory`. Attempt snapshots expose generated sample IDs. Inventory reports registrations, attempts, selections, current sample states, artifact metadata, active-model states, missing source ordinals, and outcome counts.
## Contract
A commit is validated and appended in one SQLite transaction. Repeating an event ID with identical serialized event content is an idempotent no-op; different content conflicts. `revision` is the number of unique committed events, including events that supersede earlier projection state.
Register every segment before its attempts. Registrations validate shared `SegmentRef` rules, duration below 240,000 ms, and source plan/count/ordinal consistency. Response-bearing attempts require a canonical result-object ID; provider failures do not. Scored attempts require quality at most 100, one or more speakers with contiguous ordinals, bounded nonempty text, and usable speech within segment duration. Selection requires a matching scored attempt. A replacement changes only the current selection.
A sample ID is `sample:sha256:` followed by lowercase SHA-256 of the complete attempt-ID bytes, one zero byte, and the speaker ordinal as two-byte big-endian. Obtain IDs through an attempt query before changing sample state. Later state events supersede earlier states.
`TrainingRows` returns only confirmed samples from the currently selected attempt whose extraction key equals the requested cohort; retracted and unlabeled samples are absent. `RepeatabilityRows` also permits confirmed samples from unselected attempts. Returned rows use the extraction key as `LabeledSample.cohort_id`.
Artifact insertion verifies that `sha256` matches `bytes`. An artifact ID cannot be changed. Activating a model requires an existing Model artifact in the same cohort; `None` deactivates the cohort pointer. Activation does not modify artifact bytes or prove host adoption or deployment.
`open` creates schema version 1 only for a fresh database. Existing unversioned databases and unknown future versions are rejected rather than migrated or downgraded. A `Store` persists across restart and serializes calls through its local SQLite connection. It performs no callbacks or network operations.
Store only canonical object references and parsed provider output. Artifact bytes are the sole opaque payload facility; do not pass audio, provider response bodies, temporary locators, or hidden statistical state as events.