Follow-ups from live testing:
- Game-account creation is now an admin Settings control (disabled / website /
hybrid / game) instead of a hidden on/off flag. The site offers creation for
website+hybrid; help text notes the shard's SignupMode (Bridge.cfg) has the final
say. game_account_signup setting widened to a 4-value enum + validated on save.
- Invites: the accept link is ALWAYS returned and shown with a Copy button, and a
"Email the invitation" toggle lets an admin create a link-only invite (no email)
or email it. Backend takes sendEmail (default true) and always returns acceptUrl.
- Staff can create a game account from their own /admin/characters page too
(POST /admin/shard/account → the shared createGameAccount controller), so the
form is reachable in both the player and admin portals.
Note: the admin Houses view (/admin/houses) already worked; the earlier failure
was a stale Vite HMR state for the new route (needs a hard refresh).
Client build clean; server routes load; swagger regenerated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 5: the account-provisioning backend — link-only stays, plus hybrid
self-signup, an admin email-invite tool, and site-side unlink.
- uoLinkClient.createAccount / unlinkAccount (v2). Password is forwarded to the
shard (hashed there) and never stored/logged; the end-user browser IP is passed
for the shard's per-IP cap; actor is stamped server-side.
- Hybrid signup: POST /player/shard/account provisions a game account (its own
username + password) for the signed-in user and mirrors the link locally. Gated
by the new game_account_signup setting AND the shard's own mode (mapped 403/409/
429/400/503). Serves both self-serve signup and the invite-accept game step.
- Email invites: user_invites table (sha256 token hash, single-use, expiring);
invites model + admin CRUD (POST/GET/DELETE /admin/invites, admin-only) +
mailer.sendInvite (falls back to returning the accept link if email is off);
public token-gated accept (GET /auth/invite/:token, POST .../accept) creates the
user at the invite's preset role and logs them in, bypassing the registration
gate. Accept is race-safe (atomic single-use; rolls back the user if it loses).
- Admin unlink: DELETE /admin/users/:id/shard/link/:account (admin-only) + local
mirror drop; account.unlinked ingest reconciles the mirror when a player runs
[unlink in game. account.audit / account.unlinked are logged (admin channel
only — never on the public SSE allowlist).
Tests: invites model (hashing, single-use, expiry, revoke) + account.* ingest
reconcile/visibility. Full suite 193/193; swagger regenerated.
Refs .plans/protocol2-integration.md (Phase 5).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>