Add the data layer for WhatsApp-style accounts on the wallet model: the admin
mints a single-use invitation link, the new user redeems it by publishing only
its public keys, and the admin can hard-delete a user.
- Invite type and lifecycle (invites.go): 32-byte crypto/rand hex token, 7-day
default TTL, fail-closed expiry parsing. Methods CreateInvite/GetInvite/
ListInvites/ConsumeInvite/CancelInvite on both backends. ConsumeInvite is
atomic and single-use: SQLite uses a transaction guarded by `used = 0`, the KV
store uses a compare-and-swap on the entry revision (mark-first). Both burn the
token on claim, so an already-registered key surfaces ErrUserExists with the
invite spent — identical semantics across backends.
- DeleteUser (users.go + jetstream_store.go): hard-delete of the allowlist row,
distinct from RevokeUser's status flip. Room memberships of the ex-user are
intentionally left inert (they can no longer authenticate); no partial cleanup.
- Migration 003_invites.sql (root + embedded copy, byte-identical): additive
`invites` table with audit columns, per db_migrations rules.
- Store interface gains DeleteUser, CreateInvite, GetInvite, ListInvites,
ConsumeInvite, CancelInvite. New UNIBUS_invites KV bucket.
- Consistency fix: SQLite GetUser now maps sql.ErrNoRows to ErrNotFound, matching
the KV backend and the storage-agnostic contract documented in store.go.
- ValidateKexPubHex added alongside ValidateSignPubHex for /register key checks.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bus-level user allowlist (issue 0001a): the authoritative directory of
Ed25519 signing identities permitted to use the bus, independent of room
membership. Migration is additive and mirrored byte-for-byte between the
module-root migrations/ and the embedded pkg/membership/migrations/.
Store adds AddUser/GetUser/ListUsers/RevokeUser/IsAuthorized/HasAdmin.
IsAuthorized is the single fail-closed predicate both the control plane and
the NATS data plane will consult, so revocation is a status flip that denies
access on both without a restart.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>