kcode-k1-invites 0.1.0

Replayable single-use invite commitments for Kennedy K1
Documentation
# Public API

```rust
use std::sync::Arc;
use kcode_k1_invites::{InviteCode, InviteVerifierKey, K1Invites, UserId};
use kcode_k1_peering::K1Peering;
use kcode_k1_txn_ordering::{K1TxnOrdering, TxId};

let invites = K1Invites::open(
    ordering: Arc<K1TxnOrdering>,
    peering: Arc<K1Peering>,
    InviteVerifierKey::from_bytes(key),
)?;
let (issue_id, code): (TxId, InviteCode) = invites.create()?;
let user_id: UserId = invites.consume(&code)?;
```

`InviteCode` parses with `str::parse` and accepts exactly eight canonical URL-safe, unpadded Base64 characters encoding six bytes. It deliberately implements neither `Clone`, `Debug`, nor `Display`. `expose` is the explicit conversion to an ordinary `String`; the caller then owns that plaintext copy. The code's internal bytes are zeroized on drop.

`InviteVerifierKey` takes one 32-byte operator-provisioned key, retains it in zeroizing memory, and deliberately implements neither `Clone`, `Debug`, nor `Display`. `UserId` is a typed, copyable wrapper around the first valid consume transaction's `TxId`; `as_tx_id` returns that ID.

`create` returns the exact canonical Issue ID and its secret code. `consume` returns the first valid Consume ID as `UserId`. Repeating a successful consume returns the same user ID without another transaction.

# Commitment and wire format

Creation draws six bytes from the operating-system CSPRNG through `getrandom`. The eight-character code is case-sensitive and is never trimmed or normalized. The canonical commitment is:

```text
HMAC-SHA256(verifier_key, "k1-invite-v1" || six_raw_code_bytes)
```

Only the 32-byte commitment enters KTO. Plaintext invite codes do not enter transaction payloads, errors, logs, source examples, or package-owned persistence.

Payload version 1 is strict:

| Record | Exact bytes | Length |
| --- | --- | ---: |
| Issue | version `1`, kind `1`, commitment | 34 |
| Consume | version `1`, kind `2`, exact Issue `TxId`, commitment | 46 |

Truncated, extended, unknown-version, and unknown-kind payloads are malformed. The first Issue for a commitment wins. Duplicate Issues are no-ops. The first Consume whose commitment and embedded Issue ID match that Issue wins and its callback ID becomes the `UserId`. Unknown, mismatched, and later Consume records are deterministic no-ops.

# State and lifecycle

The exact subsystem ID is `k1-invites-subsystem`. `open` registers it from genesis with the supplied live KTO, replays oldest first, and becomes ready only after registration completes. The supplied peering handle must use that same KTO instance. Materialized state changes only in canonical callbacks. The package creates no files, checkpoints, cursors, workers, or background tasks.

A reorganization clears the projection, marks that instance unavailable, and wakes local waiters. KTO also takes the registration out of commission. Recovery requires opening a new instance and replaying the current canonical history from genesis. Structurally malformed canonical payloads fault this registration rather than inventing state.

The verifier key is operational secret state, not transaction state. The same protected key is required after restart for outstanding codes to remain usable. This package does not load, persist, rotate, distribute, or back up the key.

# Concurrency, failures, and performance

Projection locks cover only bounded map and reservation work and are never held across Peering or KTO. Pending commitment reservations make local Issue collisions redraw. Concurrent local consumers of one code share one in-flight Consume and receive the same successful `UserId`; independent commitments do not wait on that submission.

Peering errors are not blindly retried. If the callback already proves the reserved Issue or Consume applied, the canonical result is returned. Otherwise the original error is preserved; an unresolved Consume is retained as failed in that process so waiters and later calls do not silently repeat an ambiguous effect. Committed-ID errors are not precommit retry permission.

Codec and commitment work are fixed-size. Expected lookups are constant time. Replay time is linear in invite records and memory is linear in first-issued commitments. Calls have no finite wall-clock bound beyond Peering and KTO's contracts.

Six bytes provide only 48 bits of bearer entropy. Before any public endpoint exists, the server must enforce transport security, uniform unavailable responses, and strict edge/IP plus global attempt limits for malformed, unknown, consumed, and valid codes. HMAC prevents offline verification without the server key; it does not replace online abuse controls.

# Exclusions and lifecycle boundary

This release does not implement usernames, passwords, public-key proof, account registration, profiles, sessions, login, ACLs, HTTP routes, UI, Terms-of-Service text or acceptance, invite expiry, listing, revocation, rate-limit storage, deployment, Server adoption, or live-service behavior. `UserId` is a durable identity seed, not an authenticated account or authorization grant.

Managed source, successful check, publication, dependency selection, secret provisioning, host adoption, deployment, and live behavior remain separate.