kcode-k1-access-profile-wire 0.1.0

Canonical profile and mutation wire codecs for K1 access profiles
Documentation
# Access profile wire

This leaf owns the canonical byte representation shared by stored authorization profiles and profile mutations. It has no persistence or retained state.

## Public API

```rust
pub use kcode_k1_access_profile_values::{
    AuthorizationProfile, GroupId, ModelId, ProfileId, ProfileOwner, ProfileViewer,
    TxId, UserId,
};

pub type OperationId = [u8; 16];

pub enum ProfileMutation {
    Create { owner: UserId, profile: AuthorizationProfile },
    Replace { profile_id: ProfileId, actor: UserId, profile: AuthorizationProfile },
    Delete { profile_id: ProfileId, actor: UserId },
}

pub struct ProfileOperation;
impl ProfileOperation {
    pub const fn new(operation_id: OperationId, mutation: ProfileMutation) -> Self;
    pub const fn operation_id(&self) -> OperationId;
    pub const fn mutation(&self) -> &ProfileMutation;
    pub fn into_mutation(self) -> ProfileMutation;
    pub fn into_parts(self) -> (OperationId, ProfileMutation);
}

pub fn encode_profile(profile: &AuthorizationProfile) -> Result<Vec<u8>, String>;
pub fn decode_profile(bytes: &[u8]) -> Result<AuthorizationProfile, String>;
pub fn encode_operation(operation: &ProfileOperation) -> Result<Vec<u8>, String>;
pub fn parse_operation(payload: &[u8]) -> Result<ProfileOperation, String>;
```

`ProfileMutation` and `ProfileOperation` implement `Clone`, `Debug`, `Eq`, and `PartialEq`. The value reexports are the exact identities from `kcode-k1-access-profile-values`.

## Canonical profile bytes

Version 1 is `[1][owner_count:u32 big-endian][owners...][viewer_count:u32 big-endian][viewers...]`. Owner tags are RequestUser `0`, User `1` plus its 12-byte transaction ID, and Group `2` plus its 12-byte transaction ID. Viewer tags are RequestUser `0`, RequestModel `1`, User `2` plus 12 bytes, Group `3` plus 12 bytes, and Model `4` plus 32 bytes.

Encoding uses the normalized value order. Decoding rejects unknown versions and tags, truncation, trailing bytes, empty owners, platform count conversion overflow, and every noncanonical ordering or duplication. It constructs the normalized value, re-encodes it, and requires byte equality. The signatures of `encode_profile` and `decode_profile` are retained for compatibility reexports.

## Mutation bytes

Create is exactly `[1][1][operation_id:16][owner:12][canonical_profile...]`. Replace is `[1][2][operation_id:16][profile_id:12][actor:12][canonical_profile...]`. Delete is `[1][3][operation_id:16][profile_id:12][actor:12]`, exactly 42 bytes. Parsing checks the two-byte header before action-specific lengths and delegates profile suffix validation to the canonical decoder. There is no legacy decoder or alternate version.

## Work and exclusions

Inspection of the implementation shows profile encoding visits each subject once and writes one fixed-size representation per subject, so work and output allocation are linear in the emitted bytes. Profile decoding advances monotonically through the input, then performs the value constructor's sorting/deduplication and one canonical re-encoding; its work is linear in bytes plus value normalization and its allocation covers the decoded value and comparison bytes. Mutation encoding and parsing add one pass over fixed prefixes and the profile suffix; Delete is fixed work over 42 bytes. These are asymptotic statements, not latency guarantees: allocation and scheduling have no finite wall-clock bound.

The crate is synchronous and has no I/O, randomness, locks, callbacks, persistence, retained state, retries, timeouts, authentication, authorization decisions, migration path, schema framework, or People-specific naming.