kcode-k1-groups 0.1.1

K1 groups transaction facade and projection integration
Documentation
# K1 groups

`kcode-k1-groups` is the synchronous transaction facade for the logical KTO subsystem `k1-groups-subsystem`. It submits group mutations only through Peering and materializes canonical callbacks through `kcode-k1-groups-projection`.

## Public API

```rust
use std::{path::Path, sync::Arc};
use kcode_k1_groups::{
    ALL_MODELS, ALL_USERS, Group, GroupId, GroupMemberships, 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,
}

impl GroupId {
    pub const fn new(txid: TxId) -> Self;
    pub const fn txid(self) -> TxId;
    pub const fn sentinel(self) -> Option<SentinelGroup>;
}

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) -> 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>;
}
```

The package reexports `ALL_USERS`, `ALL_MODELS`, `LOCAL_MODELS`, `SentinelGroup`, `Group`, `GroupId`, `GroupMemberships`, `GroupRevision`, `GroupRole`, `GroupUser`, `ModelId`, `TxId`, and `UserId` from the projection contract. Projection mutation and integration types remain private. `SentinelGroup`, `GroupId`, `ModelId`, and `GroupRole` are `Copy`, `Clone`, `Debug`, `Eq`, `Hash`, `Ord`, `PartialEq`, and `PartialOrd`.

`ALL_USERS`, `ALL_MODELS`, and `LOCAL_MODELS` are exact reserved synthetic `GroupId` values. `GroupId::sentinel` discriminates those identities and returns `None` for ordinary group IDs.

Every `UserId`, `GroupId`, `ModelId`, and `TxId` value is in the domain documented by the projection. Arbitrary typed user and model identities qualify for their applicable synthetic membership even when they have no ordinary group membership. Caller authentication is outside this package. The actor supplied to a mutation is trusted authenticated identity evidence from the caller. IDs, revisions, transaction receipts, operation IDs, and group topology are not authentication or permission evidence by themselves.

## Roles, sentinels, and results

Create makes its canonical callback transaction the new `GroupId` and gives the requested user the Owner role. An Admin may add or remove Users but cannot affect Admins or Owners or assign elevated roles. An Owner may perform human membership transitions except removing or demoting the final Owner. Users cannot mutate. Only Owners may change model membership. Authority is checked before an equal desired state is classified as unchanged.

The sentinel groups are synthetic and immutable. They are not KTO-created and have no stored SQLite group rows:

- `groups_for_user` returns every ordinary membership plus `ALL_USERS`.
- `groups_for_model` returns every ordinary membership plus `ALL_MODELS`.
- `memberships` adds `ALL_USERS` only to `user_groups` and `ALL_MODELS` only to `model_groups`.
- `shared_groups` is the intersection of ordinary stored memberships and never contains a sentinel.
- `LOCAL_MODELS` has no members and no mutation mechanism in this revision.
- `get` returns `None` for all three sentinels.
- User-role or model-membership mutations addressed to a sentinel return `Err("sentinel groups are immutable")` after their canonical callback advances the Groups cursor.

A reserved sentinel transaction ID cannot be produced through the facade's ordinary Create path without the corresponding transaction bytes. The projection contract separately rejects such a callback as `Rejected("reserved sentinel group ID")`.

Applied and Unchanged local callbacks return the projection's exact `GroupRevision`. Rejected callbacks return the projection's exact semantic reason as `Err`. Queries return complete owned results in unspecified order. No result is truncated.

## Wire contract

The subsystem ID is the canonical zero-padded `SubsystemId` for the exact logical text `k1-groups-subsystem`. Only strict version 1 payloads are accepted, with no trailing bytes:

| Action | Exact bytes | Length |
| --- | --- | --- |
| Create | `[1][1][operation_id:16][owner_user_txid:12]` | 30 |
| SetUserRole | `[1][2][operation_id:16][group_txid:12][actor_user_txid:12][target_user_txid:12][role:1]` | 55 |
| SetModelMembership | `[1][3][operation_id:16][group_txid:12][actor_user_txid:12][model_id:32][present:1]` | 75 |

Role values are 0 for absent, 1 for User, 2 for Admin, and 3 for Owner. Present is exactly 0 or 1. Every 12-byte human identity is reconstructed with `UserId::from_tx_id`. Unknown versions or kinds, wrong lengths, trailing bytes, and invalid discriminants fault the registration and instance. Sentinel mutation payloads use the unchanged wire shapes.

Each local request obtains one fresh 16-byte operation ID from the operating-system CSPRNG. A collision with another in-process pending operation is rerolled without an arbitrary attempt limit. The ID is only synchronous internal correlation and is not exposed, persisted separately, accepted from callers, reused as an idempotency key, or treated as permission evidence. Randomness errors return without submission.

## Opening, persistence, and recovery

`open` first opens the durable projection at `root`, obtains its checkpoint, constructs the callback owner, and registers strictly after that checkpoint. An absent checkpoint requests genesis replay. Registration completes before return. The supplied Ordering and Peering handles are retained for the facade lifetime, and the Peering handle must target that same KTO instance.

Canonical callbacks parse once and call projection apply exactly once with the callback transaction ID. Remote canonical actions are applied normally. Rejected and Unchanged callbacks are callback successes because their global cursor is durable. Projection errors, malformed payloads, duplicate or contradictory correlation evidence, missing synchronous callbacks, callback/Peering transaction mismatches, and query errors make the facade unavailable until a fresh open.

A successful Peering submission must have produced one matching synchronous callback with the same transaction ID. On a Peering error, an already-recorded exact callback outcome wins and is returned without retry; otherwise the Peering error is returned unchanged. The facade submits at most once and never guesses whether to resubmit.

Reorganization first makes the old facade unavailable, invalidates pending local resolution, and clears only projection-owned derived state. It performs no live repair, replay, retry, or reopen. Fresh `open` is the recovery boundary.

## Concurrency and performance

Projection owns durable SQLite serialization and KTO owns callback ordering. The facade shares only brief availability and pending-correlation bookkeeping. It holds no facade lock across randomness, Peering, projection apply or clear, callback execution, query result construction, or waiting. There is no worker, background task, polling, timeout, retry, queue, or admission cap. Concurrent calls each submit independently; a slow call may wait in its own dependency lane but does not hold facade coordination needed by unrelated calls.

For a noncolliding mutation, facade-owned work is fixed: one 16-byte entropy fill, one payload construction of 30, 55, or 75 bytes, one pending hash-table entry, and exactly one synchronous Peering attempt. Collision handling repeats only entropy and brief reservation work. Peering, KTO callbacks, projection persistence, entropy, allocation, filesystem work, and lane waits have no finite wall-clock bound.

`open` inherits projection materialization and KTO replay work. `get` is average `O(1) + O(users + models)` for the addressed group. Reverse queries are average `O(1) + O(output)` and add one applicable synthetic sentinel in constant work. `memberships` intersects the smaller ordinary reverse set and is average `O(1) + O(smaller set + output)` while adding one user sentinel and one model sentinel in constant work. Queries perform no facade I/O and allocate only dependency-owned complete output plus possible error strings.

The managed-check `full_stack_mutations_queries_restart_and_cursor`, `sentinels_are_domain_separated_immutable_and_restart_safe`, and `many_to_many_reverse_and_intersection` fixtures in the hardened rootless Podman environment are the reproducible functional work fixtures. `concurrent_submissions_and_immediate_revocation` verifies independent concurrent entry and canonical authority ordering. No facade wall-clock promise includes dependency-owned storage, scheduling, entropy, or callback completion.

## Exclusions

This package does not implement Accounts, Invites, HTTP, authentication, Access Controller, model resolution, Server adoption, deployment, provider calls, names, nesting, deletion, metadata, migration, mutable `LOCAL_MODELS` membership, background reconciliation, or external retries. Projection source remains the authority for group invariants and durable query semantics.