# Public API
```rust
use std::{path::Path, str::FromStr, sync::Arc};
use kcode_k1_invites::{
InviteCode, InviteVerifierKey, K1Invites, Registration, RegistrationKey, UserId,
};
use kcode_k1_peering::K1Peering;
use kcode_k1_txn_ordering::{K1TxnOrdering, TxId};
pub struct InviteCode;
impl InviteCode {
pub fn expose(&self) -> String;
}
impl FromStr for InviteCode {
type Err = String;
}
pub struct InviteVerifierKey;
impl InviteVerifierKey {
pub fn from_bytes(bytes: [u8; 32]) -> Self;
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct UserId;
impl UserId {
pub fn as_tx_id(self) -> TxId;
}
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub struct RegistrationKey;
impl RegistrationKey {
pub fn from_bytes(bytes: [u8; 32]) -> Self;
pub fn as_bytes(&self) -> &[u8; 32];
}
#[derive(Clone, Eq, PartialEq)]
pub struct Registration;
impl Registration {
pub fn user_id(&self) -> UserId;
pub fn registration_key(&self) -> RegistrationKey;
pub fn data(&self) -> &[u8];
}
pub struct K1Invites;
impl K1Invites {
pub fn open(
root: &Path,
ordering: Arc<K1TxnOrdering>,
peering: Arc<K1Peering>,
verifier_key: InviteVerifierKey,
) -> Result<Self, String>;
pub fn create(&self) -> Result<(TxId, InviteCode), String>;
pub fn consume_with_data(
&self,
code: &InviteCode,
registration_key: RegistrationKey,
data: &[u8],
) -> Result<UserId, String>;
pub fn registrations(&self) -> Result<Vec<Registration>, String>;
}
```
`InviteCode` implements `FromStr<Err = String>` and `Drop`, and deliberately implements neither `Clone`, `Debug`, nor `Display`. Parsing accepts exactly eight canonical, case-sensitive, URL-safe unpadded Base64 characters encoding six bytes. `expose` creates an ordinary plaintext `String` owned by the caller. Internal code bytes are zeroized on drop.
`InviteVerifierKey` accepts one operator-provisioned 32-byte HMAC key, retains it in zeroizing memory, and deliberately implements neither `Clone`, `Debug`, nor `Display`. The same protected key is required after restart for outstanding codes to remain usable. The library does not load, persist, rotate, distribute, or back up this key.
`UserId` implements `Clone`, `Copy`, `Debug`, `Eq`, `Hash`, and `PartialEq`. `RegistrationKey` implements `Clone`, `Copy`, `Eq`, `Hash`, and `PartialEq`. `Registration` implements `Clone`, `Eq`, and `PartialEq`. All public types are `Send + Sync`.
# Commitment and wire contract
Creation draws six bytes from the operating-system CSPRNG. The commitment is HMAC-SHA256 with the verifier key over the exact concatenation `b"k1-invite-v2" || raw_six_code_bytes`. Only the commitment enters transaction payloads; plaintext invite codes do not enter KTO, projection persistence, or package-produced errors.
Only payload version `2` is accepted. An Issue is exactly `[2, 1, commitment:32]`, totaling 34 bytes. A Consume is exactly `[2, 2, issue_id:12, commitment:32, registration_key:32, opaque_data:remaining]`, with a minimum length of 78 bytes. There is no compatibility decoder. Truncated Issues, truncated Consumes, extended Issues, other versions, and unknown kinds are structurally malformed.
# State and operations
`open` opens the durable invite projection at `root`, reads its snapshot, and registers the exact subsystem `k1-invites-subsystem` with the supplied KTO strictly after the projection checkpoint. No checkpoint means replay from genesis. The Peering handle must submit through that same live KTO. Registration completes before `open` returns.
The first canonical Issue for a commitment wins. `create` reserves a newly drawn commitment only within the instance, submits one Issue, and returns only when that Issue or an Issue accepted by its synchronous callback owns the commitment. A projected or same-process pre-submission collision causes a fresh draw, as does a projected collision when a successful submission returns a losing ID. A submission error is never retried, but if its synchronous callback has already established the requested Issue as accepted, `create` returns that accepted Issue ID and code as success. Otherwise the error is returned and may identify a committed transaction.
The first canonical Consume wins only when its Issue ID and commitment identify the winning unconsumed Issue and its registration key is unused. `consume_with_data` rejects an unknown Invite, a registration key already accepted or pending for another Invite, and a differing key or opaque byte sequence for an already accepted or pending request. An identical accepted request returns its existing `UserId` without another transaction. Concurrent identical calls for one Invite share the submitted call's result. After a successful submission, the returned ID must equal the accepted registration's `UserId`; a semantic loser returns an error. A submission error is never retried, but if its synchronous callback has already established the exact requested registration key and opaque bytes as the accepted registration, `consume_with_data` returns that accepted `UserId` as success. Otherwise the error is returned and may identify a committed transaction.
`registrations` reads a projection snapshot and returns accepted registrations in canonical Consume acceptance order. Opaque data is returned byte-for-byte without interpretation. It and the registration key are plaintext in KTO transaction bytes and the local projection. The library makes no confidentiality claim for either. There is no arbitrary opaque-data limit; representational length overflow or fallible reservation failure is reported before submission.
Only accepted state-changing actions are durably appended and checkpointed. New semantic losers are callback-success no-ops without projection store access or checkpoint advancement. Identical replay of an accepted action is a durable duplicate no-op that repairs a lagging checkpoint. Conflicting reuse of an accepted action ID is an error.
A structurally malformed canonical payload returns a callback error, faults the KTO registration, and makes the instance unavailable without projection or checkpoint advancement. Projection errors also make the instance unavailable. Reorganization makes the instance unavailable and discards its projection-owned state. No live instance retries, repairs, reconstructs, or migrates state; recovery requires a fresh `open`, which replays from genesis after discard.
# Concurrency, failures, and performance
One instance uses only brief facade locks for availability, indexes, and reservations. Brief hash-table reservations may occur under the facade lock, while no facade lock is held across Peering, projection I/O, waits, randomness/HMAC, or opaque-byte comparison. Calls waiting on one identical pending Consume do not block unrelated facade work. The projection owns irreducible serialization of its durable root, and KTO owns same-subsystem callback order. Separate invite roots and unrelated KTO subsystem lanes do not share facade coordination. There are no workers, retries, timeouts, queues, or background tasks.
A collision-free `create` performs fixed-size randomness and HMAC work plus one synchronous Peering submission; pre-submission commitment collisions repeat that fixed work. `consume_with_data` performs work linear in opaque bytes plus one submission and projection persistence. `registrations` performs work linear in accepted registration count and opaque bytes. `open` is linear in projection materialization and KTO replay after its checkpoint. Peering, KTO, projection storage, entropy, allocation, and related-call waits have no finite wall-clock bound.
Invite codes contain 48 bits of bearer entropy. Any public endpoint must add transport security, uniform unavailable responses, and strict edge, source, and global attempt limits. A `UserId` is a durable identity seed, not an account, session, authentication proof, or authorization grant.