# 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. A scored outcome contains caller-supplied `recording_quality`, one or more complete `StoredSpeaker` profiles, and zero or more `StoredAdditionalSpeaker` descriptions for substantive speakers without complete profiles. An unscorable outcome contains a reason and any additional-speaker evidence. All feature vectors use the shared 24-field `kcode-speaker-types` 0.2.0 order.
`recording_quality` is independent metadata supplied by the caller or host. It is not a feature, is not produced by the frozen speaker-analysis prompt, and is never invented by this store.
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.
For a scored outcome, `recording_quality` must be at most 100 and there must be at least one complete speaker. Across complete and additional speakers, ordinals must be unique and contiguous from zero. Additional-speaker descriptions and unscorable reasons must be nonblank and at most 240 bytes. Complete-speaker dialect text remains bounded and usable speech must fit within the segment duration. An unscorable outcome has no complete speakers by construction. 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 complete speaker ordinal as two-byte big-endian. Sample IDs, sample-state ownership, labels, training rows, and repeatability rows apply only to complete speakers; additional-speaker evidence remains attached to the stored attempt. Obtain IDs through an attempt query before changing sample state. Later state events supersede earlier states.
`TrainingRows` returns only confirmed complete-speaker samples from the currently selected attempt whose extraction key equals the requested cohort; retracted and unlabeled samples are absent. `RepeatabilityRows` also permits confirmed complete-speaker 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 2 only for a fresh database. Existing version 1 databases are rejected because their immutable event bytes may contain the former 35-feature contract; they are not migrated, replayed, or relabeled. Existing unversioned databases and unknown future versions are also 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 or pending locators, or hidden statistical state as events.