-- 003_invites.sql — single-use registration invites (issue: user accounts / wallet model). -- -- An admin mints an invite so a brand-new identity can join the bus allowlist -- WITHOUT the admin ever handling its private key. The token is the bearer -- secret that authorizes POST /register: the registering client generates its -- keypair locally and publishes only its public keys, fixing the link between an -- invite and the identity it creates via the audit columns below. The handle and -- role are fixed by the admin at mint time and cannot be changed by the client -- (no privilege escalation). -- -- Additive and idempotent: safe to apply repeatedly. Never modify this file; -- further schema changes go in new numbered migrations (see -- .claude/rules/db_migrations.md). The embedded copy under -- pkg/membership/migrations/003_invites.sql mirrors this file byte-for-byte. CREATE TABLE IF NOT EXISTS invites ( token TEXT PRIMARY KEY, -- 32 random bytes in lowercase hex (the bearer secret) handle TEXT NOT NULL, -- handle the new user will get (fixed by admin) role TEXT NOT NULL DEFAULT 'member', -- 'admin' | 'member' (fixed by admin) expires_at TEXT NOT NULL, -- RFC3339; past this the invite is dead used INTEGER NOT NULL DEFAULT 0, -- 0 pending, 1 consumed (single-use) created_at TEXT NOT NULL, used_at TEXT, -- RFC3339 when consumed (NULL until used) used_sign_pub TEXT, -- Ed25519 key that consumed it (audit; NULL until used) used_kex_pub TEXT -- X25519 key presented at registration (audit; NULL until used) ); CREATE INDEX IF NOT EXISTS idx_invites_used ON invites(used);