Warp Ops — Admin Console TODO
Status: foundations shipped (bans + warp CLI + Warp Ops SPA @ ops.iwgf.org)
Goal: Ops console (web + Tauri) that can administer everything Warp writes online, with real moderation and TEI maintenance — without the Firebase console.
Reuse Firebase Auth custom claims (admin). Privileged mutations via Cloud Functions / Admin SDK + opsAudit. Clients never get raw Admin SDK.
CLI: yarn warp · UI: apps/WarpOps · Host: hosting:ops → warp-12-ops → ops.iwgf.org
Shipped
- Warp Ops SPA + Tauri shell + hosting target
- Google sign-in +
adminclaim gate (UI) - Ban model + callables + CLI + Auth disable
- IP bans: one record per subject with IPv4 and/or IPv6 (+ optional uid)
- Ban enforcement (uid + client IP on callables; rules for uid)
opsAuditappend-only log- Official match PDF certificates (required on approve) + HMAC +
verifyMatchCertificate - Ops dash Sectors tab: active sectors + historical search (id / host / phase / rated / date)
- Ops dash Captains tab: name/uid/email search + dossier + admin notes (+/−)
Priority backlog (complaint-driven moderation)
A. Captain search & identity
- Search display names (substring on recent
playerStats) + uid + email - Lookup by uid / email (Google) / Auth provider metadata on dossier
- Profile dossier: TEI/history slice, ban record, admin notes
- Admin notes collection (
adminNotes/{uid}) — add / edit / delete - Ops-edit display name (
opsSetDisplayName+ Captains dossier rename; audited) - Related-account hints (same email, successive anon→Google link when known) — deferred (Auth enumeration)
B. Chat search, browse, moderate
- Cross-game message search by substring, sender uid/name, date range (
searchMessages)- Collection-group indexes on
messages.at+from/at; text/name filters are in-memory over the bounded scan (not full-text)
- Collection-group indexes on
- Jump to messages from a complaint date (
from/todate inputs) → list hits → open sector thread - Browse full thread for a game (
listSectorMessages) — table + DM + squad (ops Admin path) - Delete message(s) (Admin SDK;
opsAuditmessage_delete) - Redact message(s) (keep doc, blank body;
redactSectorMessage/message_redact) - Global + in-sector mute (server-enforced via
mutes/{uid}+games/{id}/mutes/{uid}+ rules on message create) - Banned-word list (ops-editable) + auto-flag on send (review only; no auto-ban/delete)
- Player report / flag queue (in-game report message) → WarpOps Reports inbox
C. Live sectors
- Dashboard: active games (lobby / active / round-end) + historical game search (date range, id, host, phase, rated)
- Live monitor start: sector detail + supervise watch link; Inspect hands via
getOpsHands(Admin SDK only) - Kick mid-mission (
opsKickCaptain) — strips seat/turn; unrates; soft-terminates if fleet < 2 [x] Terminate / abandon sector ( opsTerminateSectorsofthard) + stale list/cleanup - Inspect hands via Admin path only (
getOpsHands)
D. TEI / ratings (hard)
- Match ledger (append-only
ratingEvents) — writers on official / online / squad / practice apply; ops list + soft-void marks ledger- Still not a full cascade: historical matches before deploy have no events; backfill deferred
- Inspect
playerStatstracks +ratedMatches(getOpsRatedMatch+ dossier TEI tables) - Void or correct a rated match with reason + audit (
opsVoidRatedMatch— soft void; μ/σ not rewound; ledgervoidedflag) - Cascade replay from ledger event N onward — Scope A personal timelines (
opsCascadeFromRatingEvent, dry-run + apply); full multiplayer closure deferred - Manual rating override (set μ/σ) with mandatory reason (
opsSetCaptainRating) - Season / charter soft-reset ops UI (
resetGlobalOfficialSeasonwired in Crews panel)
F. Admin notes (captain dossier)
adminNotes/{uid}— Admin SDK only (deny client)- Notes array with add / edit / delete (+/−) on Captains dossier
- Surface on captain dossier; never visible to the captain
- Soft-delete flag deferred (hard-remove from array is fine)
G. Spectate (players) + Supervision (ops)
- Spectate — watch a sector without taking a seat; lobby host option to disable spectators
- Spectator count visible (watch header / ops detail); spectators do not fill fleet seats
- Supervision — admin silent watch via
/watch?ops=1: not listed, not in count; bypasses allowSpectate - Ops can force allow/close spectate mid-match and drop all spectators
- Spectators: read-only table + public table messages (
audience: table); no hands, no DMs/squad send
H. Match certificates (player-facing — PDF required)
- Server-signed payload (HMAC) + required PDF on official rated-match approval (
pdf-lib, Storage pathcertificates/{code}.pdf) verifyMatchCertificatecallable (signature check + signed PDF URL)- Verify page UI at iwgf.org/verify (
Warp12-leaderboard/verify) - Online sector certificates on
reportOnlineMatch/ squad (ON-{gameId}inratedMatches)
Also: banned-word list (careful)
- Word-boundary / token matching — not naive substring (
assmust not hitCassandra) - Separate lists: chat vs display names (+ allowlist)
- Ops allowlist for false positives (real names / callsigns)
- Prefer flag-for-review over auto-ban on name matches (
moderationReports+ content-review config)
E. Crews, logs, ship
- Charter list / force-remove member / close / clear join requests (WarpOps Crews tab + callables; audited)
- Published logs — confirmed unused (rules + index only; no Bridge/Functions write path). Leave shell; no ops list until a publish product exists.
- Document grant/revoke
admin(yarn warp roles+setUserRoles/bootstrapAdmin); Tauri signed build / further hardening still optional ops polish
Also worth doing
Moderation depth
- Temp ban vs permanent (
expiresAt; blank days = permanent) - Ban appeal note field (
appealNoteon ban doc + Bans panel) - Ban by device / install fingerprint (ops signals + installSightings when Bridge sends
clientInstallId; full graph UI deferred) - Shadow-mute (messages accepted but hidden from other captains; ops-set only — never auto)
- Banned-word hit → ops queue (review, not auto-ban); player reports share the same inbox
- Auto-escalate: N open reports on same target → system integrity queue item (review-only)
- Evidence pack export (JSON): game, messages window, uids, TEI/ledger/certificate slice
Integrity / abuse of rating
- Flag same-cohort rematches + related-IP sightings →
moderationReportssource=system(no auto-ban) - Surface advisor presence in sector detail (coach presence); Class I* / unverified still via eligibility notes
- “Unrate this online sector” without full cascade (
opsUnrateOnlineSector— strip claims + void ON-/ledger)
Platform / UX
- Stale game sweeper; Auth user browser
- Ops role split:
moderatorvs fulladmin - Deep links + audit log viewer + CLI search parity
Suggested build order
Ops dash: active games + historical game searchCaptain name search + dossier + admin notesMessage search by word + date + sender(+ delete)Server mute + ops kickGame terminate / stale cleanupSpectate (players) + Supervision (ops)TEI inspect + manual override + audit(soft void; cascade deferred)Match ledger(append-onlyratingEvents) 8b.Cascade replay(Scope A personal timelines; multiplayer closure deferred)Player certificates(verify page + onlineON-PDFs)Report queue + careful banned-word flagging(Bridge report + WarpOps Reports tab + triggers)Crews/ logs confirmed unused /admin roles docs +warp rolesDeep links + Subspace slash commands (/spectate) + iwgf.org active-game counterBrightDate preference (Bridge)Ops rename / redact / hands inspect / deep-link auth UXOps role split (moderator) + audit viewer + coach presence in sector detail
Done when
Without Firebase console, ops can:
- Find captains by name/uid and chats by word/date/sender
- Kick, mute, ban, delete evidence, terminate sectors
- Correct or void TEI with audit — and eventually cascade subsequent ratings
Notes
- Prefer extending
functions/over new backends. - Keep The Bridge free of ops chrome (except optional “report” entry points).
- Ban enforcement must cover anonymous Auth uids.
- Chat/name search will almost certainly need denormalized search docs or an external index; don’t pretend raw Firestore substring queries are enough.
Resume here — TEI callables (paused mid-deploy)
Code is in-tree and builds. WarpOps Captains tab has TEI table + override + rated-match lookup/soft-void + ledger inspect.
| Callable | Purpose |
|---|---|
getOpsRatedMatch | Load ratedMatches/{MT-…} for ops |
opsSetCaptainRating | Manual μ/σ override (pools: human / squad / localAi / group); audit tei_override |
opsVoidRatedMatch | Soft-void approved match: voided + strip claim ids + mark ratingEvents; does not rewind μ/σ; audit tei_void_match |
listCaptainRatingEvents / listMatchRatingEvents | Inspect append-only ledger |
opsCascadeFromRatingEvent | Scope A cascade (dryRun supported); marks event voided; rewrites personal timelines |
getCaptainDossier | Enriched with claim arrays + fuller rating payload (redeploy with TEI) |
Match ledger (ratingEvents) — writers live beside TEI apply in: rated-matches.ts, report-online-match.ts, report-practice-ai.ts. Rules: Admin SDK only. Indexes: memberUids+playedAt, matchId+playedAt.
Deploy gotcha (expected): org policy blocks Firebase setting allUsers invoker → deploy reports failure even when Cloud Run services exist. After deploy:
bash scripts/ensure-functions-public-invoker.sh
firebase deploy --only firestore:rules,firestore:indexes --project warp-12
yarn deploy:hosting:ops
Next: crews / logs polish (or further TEI cascade / season reset).
Files: functions/src/tei/rating-ledger.ts, functions/src/ops/tei.ts, apps/WarpOps/src/firebase/tei-service.ts, apps/WarpOps/src/app/captains-panel.tsx, rewrites in scripts/firebase-callable-rewrites.json + firebase.json.
Resume here — integrity queue (review-only)
In-tree. Detectors never ban/mute/delete — they only open moderationReports with source: 'system'.
| Piece | Purpose |
|---|---|
openSystemIntegrityReport | Idempotent system integrity queue writer |
| Escalate-lite (threshold 3) | N open player/auto reports → integrity item |
onRatingEventAbuseReview | Same-cohort rematch heuristic → queue |
| Related-IP / install signals | captainSignals + ipSightings (+ optional clientInstallId) → queue |
| Shadow-mute | Ops-set mode: 'shadow'; hide via shadowHidden + rules |
getModerationEvidencePack | JSON pack for human review |
opsUnrateOnlineSector | Soft-unrate online sector without cascade |
Deploy: functions (callables + triggers) + firestore:rules,firestore:indexes + invoker script + yarn deploy:hosting:ops.
Resume here — moderation reports + review terms
In-tree. Review-first only (never auto-ban/delete on term hits).
| Piece | Purpose |
|---|---|
submitModerationReport | Bridge captain reports a sector message (rate-limited; evidence copied server-side) |
listModerationReports / updateModerationReport | WarpOps Reports inbox |
getContentReviewConfig / updateContentReviewConfig | Ops-editable chat / display-name / allowlist |
onMessageContentReview / onDisplayNameContentReview | Firestore triggers → open moderationReports |
Bridge CommsPanel Report | Player entry point |
| WarpOps Reports tab | Inbox + list editor + mute/delete actions |
Deploy: functions (callables + triggers) + firestore:rules,firestore:indexes + invoker script + yarn deploy:hosting:ops (+ Bridge hosting if report UI not live yet).
warp CLI (today)
yarn warp ban <uid> --reason "…" [--ipv4 …] [--ipv6 …] [--days N] [--notes "…"] [--keep-auth]
yarn warp unban <uid|banId> [--keep-disabled]
yarn warp ban-status <uid|banId> | --ipv4 … | --ipv6 …
yarn warp ban-list [--all] [--limit N]
yarn warp roles <uid> # show claims
yarn warp roles <uid> --set admin[,moderator][,match_official]
yarn warp roles <uid> --clear
Grant / revoke admin
- Preferred (ops already admin):
yarn warp roles <uid> --set admin(or callablesetUserRolesfrom any signed-in admin). Moderators:--set moderator. - First admin / recovery:
bootstrapAdmincallable withBOOTSTRAP_ADMIN_SECRET(seefunctions/.env/scripts/bootstrap-admin-claims.mjs). - Captains must refresh claims (sign out/in or Warp Ops “Refresh claim”) after role changes.
- Roles live in Auth custom claims
{ roles: ['admin' | 'moderator' | 'match_official'] }— never grant via Firestore client rules. - Moderator may mute/kick/reports/audit/soft-terminate/spectate; admin keeps bans (Auth disable), hard delete, hands peek, TEI mutate, season reset, review-term lists, charter mutate.
Bridge + leaderboard product (before BrightDate)
Player-facing / public site — not Warp Ops chrome.
Deep links & invitations
- Shareable deep links for sector invitations — join as player or open as spectator (web + Tauri/mobile scheme where applicable)
- Stable URL shapes (e.g.
/online/:gameIdplay/join vs/online/:gameId/watch); copy/share from lobby and in-mission - Handle invite open when not signed in / wrong seat / spectate closed gracefully (anon auth wait; failed-auth panel; spectate-closed copy)
In-game slash commands (Subspace)
- Extensible slash-command path in comms (parse
/command …before free-text send) — designed so more commands can land later without one-offs - First command:
/spectate//spectator//watchreplies with (and copies) the sector’s public watch link - Help / unknown-command UX; rated-play keeps free text blocked but allows local
/commands
Leaderboard home (iwgf.org)
- Active game counter on the leaderboard home page (
countActiveSectorspublic callable)
Related Bridge product (not ops)
These are The Bridge / player-facing — track separately from Warp Ops.
BrightDate display preference
- Sitewide profile option: show all date/times as BrightDates (brightdate.org; npm
@brightchain/brightdate) - Homage to Stardates / immersive federation feel — copy clear they are not Stardates (trademark / legal) and that BrightDates have real astronomical meaning (J2000.0 / TAI)
- Recommend the option to users (profile first-time tip)
- Wire TS lib in Bridge; keep ISO/locale as toggle fallback; use decimal BrightDate spans in the game log