# K1 groups facade
This library is the synchronous consumer facade for the logical KTO subsystem `k1-groups-subsystem`. It privately composes the groups driver and durable projection while exposing only group values and complete facade operations.
## Public API
```rust
use std::{path::Path, sync::Arc};
use kcode_k1_groups::{
ALL_MODELS, ALL_USERS, Group, GroupId, GroupMemberships, GroupName, GroupRevision, GroupRole,
GroupUser, K1Groups, LOCAL_MODELS, ModelId, SentinelGroup, TxId, UserId,
};
use kcode_k1_peering::K1Peering;
use kcode_k1_txn_ordering::K1TxnOrdering;
pub const ALL_USERS: GroupId;
pub const ALL_MODELS: GroupId;
pub const LOCAL_MODELS: GroupId;
pub enum SentinelGroup {
AllUsers,
AllModels,
LocalModels,
}
pub struct GroupId { ... }
pub struct ModelId { ... }
pub struct GroupName { ... }
pub enum GroupRole {
User,
Admin,
Owner,
}
impl GroupId {
pub const fn new(txid: TxId) -> Self;
pub const fn txid(self) -> TxId;
pub const fn sentinel(self) -> Option<SentinelGroup>;
}
impl ModelId {
pub const fn from_bytes(bytes: [u8; 32]) -> Self;
pub const fn as_bytes(&self) -> &[u8; 32];
pub const fn into_bytes(self) -> [u8; 32];
}
impl GroupName {
pub fn new(value: String) -> Result<Self, String>;
pub fn as_str(&self) -> &str;
pub fn into_string(self) -> String;
}
pub struct GroupUser { ... }
pub struct GroupRevision { ... }
pub struct Group { ... }
pub struct GroupMemberships { ... }
impl GroupUser {
pub fn user_id(&self) -> UserId;
pub fn role(&self) -> GroupRole;
}
impl GroupRevision {
pub fn group_id(&self) -> GroupId;
pub fn txid(&self) -> TxId;
}
impl Group {
pub fn id(&self) -> GroupId;
pub fn name(&self) -> &GroupName;
pub fn revision(&self) -> &GroupRevision;
pub fn users(&self) -> &[GroupUser];
pub fn models(&self) -> &[ModelId];
}
impl GroupMemberships {
pub fn revision(&self) -> Option<TxId>;
pub fn user_groups(&self) -> &[GroupId];
pub fn model_groups(&self) -> &[GroupId];
pub fn shared_groups(&self) -> &[GroupId];
}
pub struct K1Groups { ... }
impl K1Groups {
pub fn open(root: &Path, ordering: Arc<K1TxnOrdering>, peering: Arc<K1Peering>) -> Result<Self, String>;
pub fn create(&self, owner: UserId, name: GroupName) -> Result<GroupRevision, String>;
pub fn rename(&self, actor: UserId, group: GroupId, name: GroupName) -> Result<GroupRevision, String>;
pub fn set_user_role(&self, actor: UserId, group: GroupId, user: UserId, role: Option<GroupRole>) -> Result<GroupRevision, String>;
pub fn set_model_membership(&self, actor: UserId, group: GroupId, model: ModelId, present: bool) -> Result<GroupRevision, String>;
pub fn get(&self, group: GroupId) -> Result<Option<Group>, String>;
pub fn groups_for_user(&self, user: UserId) -> Result<Vec<GroupId>, String>;
pub fn groups_for_model(&self, model: ModelId) -> Result<Vec<GroupId>, String>;
pub fn memberships(&self, user: UserId, model: ModelId) -> Result<GroupMemberships, String>;
}
```
All displayed value fields and the facade field are private. The constants and value types are exact projection reexports. `GroupAction`, `ApplyOutcome`, the driver, wire types, and all internals are not reexported. `UserId::from_tx_id(TxId)`, `UserId::as_tx_id()`, `TxId::from_bytes([u8; 12])`, and `TxId::as_bytes()` retain their dependency contracts.
`SentinelGroup`, `GroupId`, `ModelId`, and `GroupRole` implement `Copy`, `Clone`, `Debug`, `Eq`, `Hash`, `Ord`, `PartialEq`, and `PartialOrd`. `GroupName` implements the same traits except `Copy`. `GroupUser`, `GroupRevision`, `Group`, and `GroupMemberships` implement `Clone`, `Debug`, `Eq`, and `PartialEq`. `K1Groups` is `Send + Sync`.
## Names, roles, and results
`GroupName::new` accepts exact UTF-8 values of 1 through 128 bytes, rejects control characters, and requires a non-whitespace character, in that order. It does not trim, normalize, case-fold, or require uniqueness. Its exact failures are `group name must be 1 through 128 UTF-8 bytes`, `group name must not contain control characters`, and `group name must contain a non-whitespace character`.
Create makes its callback transaction the new ordinary `GroupId`, stores the supplied name, and gives `owner` the Owner role. Rename changes only the name and revision. A group always has an Owner. Admins may add or remove Users but cannot affect Admins or Owners or assign elevated roles. Owners may perform every human transition except removing or demoting the final Owner; only Owners may rename or change model membership. Users and absent actors cannot mutate. Authority is checked before an equal desired state is treated as unchanged. Authentication is caller-owned; typed IDs, revisions, and membership are not authority evidence.
Applied mutations return the new addressed-group revision. Authorized unchanged mutations return its existing revision. Rejections and every driver, projection, registration, storage, wire, and availability failure return the exact dependency-owned `String` unchanged. The projection owns deterministic rejection precedence and text.
The three constants are exact reserved synthetic groups. They are immutable and absent from storage. `GroupId::sentinel` recognizes them; `get` returns `None` for them. `groups_for_user` returns ordinary membership plus `ALL_USERS`; `groups_for_model` returns ordinary membership plus `ALL_MODELS`. `memberships` adds those sentinels only to their matching sets and intersects only ordinary membership for `shared_groups`. `LOCAL_MODELS` has no members. Queries return complete owned results in unspecified order without truncation.
## Opening and lifecycle
`open` opens the projection at `root` in its current format, wraps it in `Arc`, and registers the driver strictly after the returned checkpoint. An absent checkpoint requests genesis replay. Registration and dependency-owned replay complete before return. The Peering handle must target the supplied Ordering instance. The projection uses `root/groups.sqlite3`; there is no migration or compatibility reader.
The driver exclusively owns strict wire v2 encoding, decoding, callback correlation, and the canonical subsystem ID. Wire v1 is neither read nor written. Each mutation performs one synchronous Peering submission with no retry. Applied and unchanged callbacks return revisions, rejected callbacks return projection reasons, and exact callback evidence wins over a Peering error. Missing, duplicate, contradictory, malformed, or mismatched callback evidence makes the driver unavailable.
Reorganization and projection failures make the affected driver or projection unavailable as defined by those dependencies. Reorganization clears only projection-owned derived state. Recovery is a fresh `open`; there is no live repair, worker, polling, timeout, retry, background reconciliation, deployment, or network policy in this facade.
## Concurrency and performance
The facade owns no lock or queue and performs one direct driver or projection-opening delegation per public operation. The driver owns only brief availability and pending-correlation locking; the projection owns its apply lane, SQLite serialization, and coherent query locks; Ordering owns callback order. No shared lock spans Peering, callbacks, sleeps, or caller code. Concurrent operations enter independently, while operations for the same dependency-owned resource may wait in that resource's lane. A slow operation does not add facade-level blocking for unrelated work.
For `G` groups, `U` human rows, `M` model rows, and `H` canonical history items examined by replay, `open` inherits `O(G + U + M + H)` work, projection memory proportional to materialized state, one `Arc` allocation, and dependency storage I/O. Create and Rename use `O(name bytes)` work over the at-most-128-byte name; the other mutations use bounded driver work. A noncolliding mutation performs one 16-byte entropy fill, expected-`O(1)` pending transitions, one bounded wire encoding, one callback decode, and exactly one Peering attempt.
For an addressed group with `u` users and `m` models and result size `R`, `get` is expected `O(1) + O(u + m)`, reverse queries are expected `O(1) + O(R)`, and `memberships` is expected `O(1) + O(smaller ordinary set + R)`. Query allocations hold only complete dependency-owned results and possible errors. The facade adds no disk or network I/O to delegated operations. Dependency storage, entropy, scheduling, callback, and lane waits have no finite wall-clock bound.
The managed-check `tests/facade.rs` fixture in the hardened rootless Podman environment verifies Send and Sync, Create, Rename, exact queried name and revision, and persistence through complete handle teardown and reopen. Driver and projection managed-check fixtures own wire, conformance, concurrency-isolation, scale, and latency canaries.
## Compatibility
Version 0.3.0 uses the corrected strict-v2 current format with kinds Create=1, SetUserRole=2, SetModelMembership=3, and Rename=4. Because no live state exists, there is no migration or legacy decoder.