kcode-k1-persons-projection 0.2.0

Durable current-name projection for K1 persons
Documentation
# K1 persons projection

This library owns the coherent in-memory current-name projection and direct alias classes. `kcode-k1-persons-store` solely owns SQLite, schema validation, recovery, and persistence at `root/persons.sqlite3`. This library has no authorization, discovery, listing, migration, legacy reader, retry, worker, polling, network I/O, or multiprocess coordination.

## Public API

```rust
use std::path::Path;
use kcode_k1_txn_ordering::K1TxnOrdering;
pub use kcode_k1_person_types::PersonId;
pub use kcode_k1_txn_ordering::TxId;

pub struct PersonView {
    pub person_id: PersonId,
    pub name: String,
}

pub struct PersonAction { /* private representation */ }
impl PersonAction {
    pub fn create(name: String) -> Result<Self, String>;
    pub fn update(person: PersonId, name: String) -> Result<Self, String>;
    pub fn resolve(canonical: PersonId, alias: PersonId) -> Self;
}
pub enum ApplyOutcome { Applied(PersonId), Unchanged(PersonId), Rejected(String) }
pub struct Projection { /* private fields */ }
impl Projection {
    pub fn open(root: &Path, ordering: &K1TxnOrdering) -> Result<(Self, Option<TxId>), String>;
    pub fn apply(&self, callback: TxId, action: PersonAction) -> Result<ApplyOutcome, String>;
    pub fn read(&self, person: PersonId) -> Result<Option<PersonView>, String>;
    pub fn get(&self, person: PersonId) -> Result<Option<String>, String>;
    pub fn clear(&self) -> Result<(), String>;
}
```

`PersonView`, `PersonAction`, and `ApplyOutcome` implement `Clone`, `Debug`, `Eq`, and `PartialEq`; `Projection` is `Send + Sync`. `PersonView.person_id` is the current canonical root and `PersonView.name` is that root's current exact name, read coherently under one projection snapshot. Unknown IDs return `None`. `get` preserves the prior name-only API and delegates to the same coherent read.

Names preserve exact UTF-8 and validate in this order: 1 through 128 bytes, no control character, then at least one non-whitespace character. Errors are exactly `person name must be 1 through 128 UTF-8 bytes`, `person name must not contain control characters`, and `person name must contain a non-whitespace character`.

Create uses the callback ID as identity and rejects duplicates. Update accepts any alias and changes its direct root; an equal name is unchanged. Resolve checks canonical before alias, redirects the alias class to the canonical root, preserves the canonical name, and is unchanged within one class. Rejections are exactly `person already exists`, `person is unknown`, `canonical person is unknown`, and `alias person is unknown`. Every successful apply, including rejected and unchanged results, commits its callback checkpoint; only `Applied` changes state.

Open loads O(P) store state and ordering validation; attempts over 100 ms write one structured warning to stderr. `read` and `get` are expected O(1) plus one name copy and perform no disk I/O. Create and update cost O(name bytes) plus expected O(1) state and indexed store work; resolve costs O(class); clear costs O(P). SQLite contention is dependency-owned and may wait up to five seconds.