Rated matches (officiated TEI)

Human-pool and crew TEI for offline / in-person domino is applied through approved rated matches on iwgf.org.

Flow

  1. Match official (role: match_official) creates a match → receives code e.g. MT-7K3Q
  2. Captains sign in with Google at /matches and check in with the code
  3. After play, the official enters standings at /officiate/{code}
  4. Official approves → Cloud Function applies TEI server-side

Practice vs AI TEI still uses the reportPracticeAiMatch callable (reference-AI buckets).

Crew-rated matches

When the official selects a crew charter at /officiate:

  1. Match objective, fleet size, and campaign length are locked to the charter.
  2. Checked-in captains must be members of that crew (join via invite link first).
  3. On approval, TEI updates groupTei[charterId] — not global humanTei, unless the charter is Global Official (updates both).

See Crews & charters for membership, invites, and online sector parity.

Roles

RoleSet viaCapabilities
adminbootstrapAdmin (one-time secret) or setUserRolesGrant roles
match_officialsetUserRolesCreate matches, submit standings, approve

Roles live in Firebase Auth custom claims — never in client-writable Firestore fields.

Bootstrap first admin

This is a one-time chicken-and-egg fix: nobody has admin yet, so the first admin proves they know a server-side secret you configured at deploy time.

What happens under the hood

  1. You pick a long random passphrase (e.g. openssl rand -base64 32) — only you know it.
  2. You store it as Firebase secret BOOTSTRAP_ADMIN_SECRET and redeploy functions.
  3. At iwgf.org/admin, you sign in with Google (not guest).
  4. You paste the passphrase and click Claim admin.
  5. The bootstrapAdmin Cloud Function checks the passphrase, then sets on your Google account’s Firebase uid:

    { "roles": ["admin"] }
    

    That lives in Auth custom claims, not Firestore — clients cannot forge it.

  6. The page refreshes your ID token; the Grant roles panel appears.

  7. Grant yourself match_official (paste your uid — same account you just bootstrapped) so you can create/approve matches at /officiate.

After that, use Grant roles for other officials; bootstrap is only for the first admin.

Commands (in order)

Note: Cloud Functions require the Firebase Blaze plan (pay-as-you-go). Hosting + Firestore rules work on Spark; rated-match Functions do not. Upgrade: Firebase pricing → Blaze (you only pay if usage exceeds free tiers).

We use a .env file for the bootstrap passphrase — not firebase functions:secrets:set (that needs Secret Manager + Blaze secret API).

# 1. Choose a secret
openssl rand -base64 32

# 2. Put it in functions/.env (never commit this file)
cp functions/.env.example functions/.env
# Edit functions/.env → BOOTSTRAP_ADMIN_SECRET=<paste secret>

# 3. Deploy (requires Blaze)
yarn deploy:functions
yarn deploy:firestore
yarn deploy:hosting:leaderboard

UI walkthrough

StepWhereAction
1/adminContinue with Google
2Bootstrap panelEnter secret → Claim admin
3Grant roles panelYour uid + check match_officialSave roles
4/officiateCreate a match code

Finding your uid: Firebase Console → Authentication → Users → click your Google user → copy User UID. Or temporarily log it from browser devtools after sign-in (auth.currentUser.uid).

Common failures

ErrorCause
Invalid bootstrap secretfunctions/.env missing/wrong, or functions not redeployed after editing .env
Rated matches require a signed-in accountStill on guest — use Google on /admin
Grant roles panel never appearsToken stale — sign out/in, or hard refresh after claim
Admin role required on setUserRolesBootstrap didn’t succeed — check secret + redeploy

Security note

Anyone who knows BOOTSTRAP_ADMIN_SECRET can claim admin until you rotate or remove the secret’s usefulness. After you’re admin, consider setting the secret to a new value (or revoking access) so the bootstrap door isn’t left wide open. There is no “disable bootstrap” flag yet — rotating the secret is the practical mitigation.

Firestore

  • ratedMatches/{matchCode} — read-only to clients; writes via Admin SDK (Functions); optional charterId, rulesProfileId, playerCount
  • charters/{charterId} — read-only to clients; writes via Functions
  • charterMembers/{charterId}_{uid} — read own membership only
  • playerStats/{uid} — clients cannot mutate humanTei, humanRatedGameIds, groupTei, groupRatedIds, or localAi

Bridge integration

After any completed campaign, the bridge links to leaderboard check-in. Captains use the official’s match code, not the online sector code.


Warp 12 — a Double-Twelve domino variant for the NX Epoch.