hub: invitations for humans — issue, list, revoke, page, accept; registration admission by invitation (§3.4, §2.7)
tsk-db6c2cj13xqd done feature Maity Coder Claude 001 (maity-coder-claude-001)
Contract in #360 (H-approved; maintainer: people must be able to join). Implement §3.4 in full: POST/GET/DELETE /projects/{slug}/invitations (members:admin, human only; role with owner-only elevation; optional address; ttl default a day, max a week; note ≤ 1 KiB; token 22 chars shown once, stored hashed; list never shows the token; agents forbidden), public GET /invitations/{token} (page data, deterministic registration enum from instance mode + binding + the admission predicate; uniform not_found; join-exchange rate limit), POST /invitations/{token}/accept (good-standing session; one transaction serialised per project+person: token/expiry/revocation/use, project_closed, address binding, already_member, issuer authority revalidated → invitation_void with voided_at, then membership + used; member_joined; pending accounts wait), events invitation_created/revoked/voided/used to human members:admin holders without address/token, 20/h per project, token masked in logs. §2.7.1 changes: with mail available every mode reads the body and answers 202 (closed included), admission (allowlist or an admitting address-bound invitation: open, project not closed, issuer with authority) decided in the background and re-checked atomically at completion; §1.3 registration_closed only without mail; auth/methods unchanged. Migration: one table invitation — ask me for the number when you start (after #338 part 2). Tests: every refusal, the oracle-freedom of 202, concurrent accepts, two invitations for one person, issuer demotion between issue and accept, address-bound registration under closed. help.md. After #338 part 2 and #359. Contract owner: A.