Player identity and Player Names
The public.players table is Wits World's local player record. Authentication
continues to resolve this record through the exact trusted Auth0
(auth_issuer, auth_subject) pair. The local player UUID remains the stable key
for game data, while role and lifecycle_status remain server-authoritative
for application authorization.
Player Name model
For database accounts, player_name mirrors the Auth0 username exposed by the
validated access-token claim https://witsworld.co.za/player_name. Google does
not supply that claim, so a newly provisioned Google player starts with NULL
and chooses a Player Name once through Wits World. It is useful account metadata,
but it is not an authentication, ownership, authorization, or foreign-key
identity.
The column remains nullable for legitimate Google onboarding and legacy
transition. Database tokens require and synchronize the trusted claim through
the existing (auth_issuer, auth_subject) upsert. Google tokens are resolved by
that same identity pair and never copy email or provider profile fields.
Names use the configured Auth0-compatible policy: 3–20 characters, lowercase,
and only letters, numbers, and Auth0-supported username punctuation. Because the
database connection's Allow usernames in email format and Allow usernames
in phone number format settings are off, those identifier-shaped values are
also rejected. This includes email syntax with a single-label domain, such as
player@example. The API trims and lowercases Google proposals; database
usernames must already use Auth0's canonical form. A partial unique index over
lower(player_name) makes the shared
cross-provider namespace database-authoritative while allowing onboarding NULLs.
The database check independently enforces the canonical syntax.
GET /api/v1/auth/me returns playerName and the derived
playerNameSetupRequired flag. PUT /api/v1/auth/me/player-name exists only to
assign an initial name to the authenticated supported social player while the
stored name is null. Database users and already-named users cannot call it.
Player Name is immutable in Basic Tier; future rename support requires an
approved Auth0/Wits synchronization design.
If a Google-chosen name already belongs to either provider, setup returns
409 PLAYER_NAME_CONFLICT. If a later database username collides with a name
already reserved by Google, database-account resolution returns the same safe
conflict. Neither case changes either (iss, sub) binding or local UUID.
Completed account deletion clears player_name together with the Auth0 mapping
and role. This releases the local unique name only after the external Auth0
account has been deleted successfully. Historical gameplay snapshots remain
unchanged under their existing retention rules.
Migration history
Migration 0019_add_player_display_name.sql was recorded by the shared
development database before the final Auth0-backed decision, so its checksum is
immutable. Migration 0020_replace_display_name_with_player_name.sql removes
the temporary field semantics by renaming the column, clearing untrusted
player-entered values, installing defensive checks, and adding case-insensitive
uniqueness. Migration 0021_enforce_player_name_policy.sql retains nullable
Google onboarding while enforcing the shared canonical 3–20-character and
allowed-character policy. It is immutable after application. Migration
0022_tighten_player_name_policy.sql replaces only that check constraint to
reject email-shaped (including single-label domains) and phone-shaped names.
Apply 0019 through 0022 before deploying the API version that selects
player_name.
The preceding document was generated and edited with the assistance of: Codex-CLI[GPT-5].