Files
galaxy-game/user/PLAN.md
T
2026-04-09 09:00:06 +02:00

20 KiB

User Service Implementation Plan

Planning Principles

This plan is aligned with the current repository architecture and is written for an experienced middle-level Go developer implementing an internal trusted microservice.

Execution priorities:

  • preserve the frozen auth and geo ownership boundaries
  • keep user state authoritative in one service
  • prefer explicit command behavior over generic patch behavior
  • keep synchronous read paths simple for auth and lobby
  • separate current effective state from append-only or historical records where fast reads matter
  • keep the first version storage-agnostic at the domain boundary even if Redis is the initial backend

Stage 01 — Freeze Vocabulary, Contracts, and Cross-Service Ownership

Goal

Remove naming ambiguity and freeze the service boundary before implementation.

Tasks

  • Freeze the regular-user-only scope of User Service.
  • Freeze that system-admin identity is out of scope and belongs to later Admin Service.
  • Freeze the self-service vocabulary:
    • GetMyAccount
    • UpdateMyProfile
    • UpdateMySettings
  • Freeze that race_name replaces display_name.
  • Freeze the current ownership split for declared_country:
    • current value in User Service
    • workflow and history in Geo Profile Service
  • Freeze the auth-facing internal REST endpoints already reserved by Auth / Session Service.
  • Freeze the need for create-only registration context on EnsureUserByEmail.

Deliverables

  • service README with stable terminology
  • short internal ADR or equivalent note for declared_country ownership split
  • short internal ADR or equivalent note for regular-user versus admin identity split

Exit Criteria

  • no unresolved naming conflict remains around race_name, declared_country, entitlement, sanction, or limit semantics
  • no service boundary question remains open for auth, lobby, or geo

Targeted Tests

  • none yet beyond documentation review

Stage 02 — Define Domain Entities and Redis-Backed Logical State

Goal

Describe the persistent state clearly enough that storage adapters can be built without revisiting core semantics.

Tasks

  • Define logical entities for:
    • user account
    • race-name reservation
    • blocked e-mail subject
    • entitlement period record
    • current entitlement snapshot
    • sanction record
    • limit record
  • Freeze required fields, timestamps, and identifiers for each entity.
  • Decide Redis logical key layout and lookup indexes without leaking them into the domain layer.
  • Freeze deterministic pagination keys for admin listing.
  • Define how active/effective evaluation works for sanctions and limits.

Deliverables

  • domain entity definitions
  • storage design notes for Redis keys and secondary indexes
  • active/effective evaluation rules

Exit Criteria

  • every required read and mutation can map to a clear logical entity set
  • Redis adapters can be implemented directly from the frozen logical model

Targeted Tests

  • domain validation tests for required fields
  • tests for effective-state evaluation of active versus expired records

Stage 03 — Implement Auth-Facing Resolution, Ensure, Existence, and E-Mail Blocking

Goal

Provide the minimum trusted API needed by Auth / Session Service.

Tasks

  • Implement:
    • resolve by e-mail
    • ensure by e-mail
    • exists by user id
    • block by user id
    • block by e-mail
  • Preserve exact route shapes already reserved by the auth REST client.
  • Implement the separate blocked-email-subject model.
  • Make BlockByEmail idempotent for both existing-user and no-user cases.
  • Ensure ResolveByEmail and EnsureUserByEmail both respect blocked-email subjects.

Deliverables

  • trusted internal REST handlers for auth-facing endpoints
  • domain services for resolution and block behavior
  • Redis-backed storage for user existence and blocked-email subjects

Exit Criteria

  • auth can distinguish existing, creatable, and blocked
  • blocked e-mail subjects prevent user creation before a user exists
  • BlockByUserID and BlockByEmail are idempotent

Targeted Tests

  • resolve existing/creatable/blocked by e-mail
  • ensure existing/created/blocked outcomes
  • blocked e-mail subject prevents creation before user record exists
  • block by user id on unknown user returns not found
  • repeated block calls stay idempotent

Stage 04 — Add New-User Creation Context from Auth

Goal

Support first-login user creation with initial settings captured at confirm time.

Tasks

  • Extend EnsureUserByEmail contract with create-only registration context:
    • preferred_language
    • time_zone
  • Validate preferred_language as BCP 47.
  • Validate time_zone as IANA TZ name.
  • Generate initial race_name in player-<shortid> form during creation.
  • Initialize the newly created user with:
    • free entitlement
    • no active sanctions
    • no custom limits
  • Ignore registration context for existing users.
  • Document required follow-up changes in gateway and authsession.

Deliverables

  • extended ensure-by-email request model
  • create-user domain service
  • generated-race-name helper

Exit Criteria

  • first successful ensure-create path can fully initialize a new user
  • existing-user ensure does not overwrite language or time zone

Targeted Tests

  • new user created with generated race_name, derived preferred_language, and required client time_zone
  • existing user ensure ignores create-only registration context
  • invalid BCP 47 or IANA inputs are rejected on create path

Stage 05 — Implement Self-Service Account Read and Split Profile/Settings Mutations

Goal

Expose the minimal authenticated account surface routed by Edge Gateway.

Tasks

  • Implement GetMyAccount.
  • Implement UpdateMyProfile for race_name only.
  • Implement UpdateMySettings for:
    • preferred_language
    • time_zone
  • Ensure GetMyAccount returns:
    • account identity fields
    • current entitlement snapshot
    • active sanctions
    • active effective limits
    • read-only declared_country
  • Reject attempts to mutate email or declared_country through self-service flows.
  • Enforce profile_update_block sanction on both self-service mutations.

Deliverables

  • authenticated application services for account read and updates
  • gateway-facing handler or adapter contracts for future routing
  • DTOs for account aggregate and mutation requests

Exit Criteria

  • authenticated users can read current account state in one aggregate
  • profile and settings changes are clearly separated
  • self-service updates cannot mutate forbidden fields

Targeted Tests

  • GetMyAccount returns current entitlement, active sanctions, active limits, and read-only declared_country
  • UpdateMyProfile cannot change email or declared_country
  • UpdateMySettings validates BCP 47 and IANA values
  • active profile_update_block denies both update flows

Stage 06 — Implement race_name Uniqueness Policy Behind a Dedicated Interface

Goal

Keep race_name uniqueness strict and replaceable.

Tasks

  • Introduce a dedicated race-name policy interface.
  • Implement canonicalization for uniqueness checks:
    • case-insensitive folding
    • confusable anti-fraud normalization
  • Add Redis-backed reservation storage for canonicalized keys.
  • Preserve original casing for stored and returned race_name.
  • Ensure rename flow handles reservation swap safely.
  • Keep the interface narrow so a future shared name-catalog service can replace the local implementation.

Deliverables

  • race-name policy interface
  • local normalization implementation
  • reservation adapter and conflict handling

Exit Criteria

  • no two users can hold conflicting race_name values under the frozen policy
  • self-service rename is atomic with respect to uniqueness reservation

Targeted Tests

  • uniqueness rejects case-insensitive collisions
  • uniqueness rejects common anti-fraud-confusable collisions
  • rename releases the old reservation only after the new one is secured
  • failed reservation backend causes mutation to fail closed

Stage 07 — Implement Entitlement History Plus Materialized Current Snapshot

Goal

Support both auditability and fast synchronous entitlement reads.

Tasks

  • Implement period-based entitlement history records.
  • Implement a materialized current entitlement snapshot.
  • Define the v1 plan catalog:
    • free
    • paid_monthly
    • paid_yearly
    • paid_lifetime
  • Implement explicit trusted entitlement commands:
    • grant paid access
    • extend paid access
    • revoke paid access
  • Update current snapshot transactionally with each successful entitlement mutation.
  • Ensure the default new-user path creates the correct free snapshot.

Deliverables

  • entitlement domain model
  • history store
  • current snapshot store
  • trusted entitlement command handlers

Exit Criteria

  • current effective entitlement is always readable without replaying history
  • history and snapshot stay consistent across supported mutation paths

Targeted Tests

  • entitlement period mutations update the materialized current snapshot correctly
  • free default is created for new users
  • extending or revoking access preserves deterministic current-state behavior

Stage 08 — Implement Sanctions and Limit Records with Active/Effective Evaluation

Goal

Support negative policy and quota overrides without scattering policy logic into consumers.

Tasks

  • Implement sanction records with optional expiry.
  • Implement limit records with numeric values and optional expiry.
  • Freeze v1 sanction catalog:
    • login_block
    • private_game_create_block
    • private_game_manage_block
    • game_join_block
    • profile_update_block
  • Freeze v1 limit catalog:
    • max_owned_private_games
    • max_active_private_games
    • max_pending_public_applications
    • max_pending_private_join_requests
    • max_pending_private_invites_sent
    • max_active_game_memberships
  • Implement active/effective evaluation with current time.
  • Implement trusted explicit commands to apply/remove sanctions and set/remove limits.

Deliverables

  • sanction model and store
  • limit model and store
  • effective-state evaluator
  • trusted mutation handlers

Exit Criteria

  • active sanctions and active limits can be read consistently from one user account view
  • expired or removed records are not treated as active

Targeted Tests

  • active sanctions appear in account reads
  • expired sanctions and limits stop affecting effective state
  • applying and removing sanctions/limits is idempotent where appropriate

Stage 09 — Implement Lobby Eligibility Snapshot API

Goal

Give Game Lobby one synchronous read that contains everything it needs for user-level access decisions.

Tasks

  • Design and implement one trusted query by user_id.
  • Return:
    • existence
    • current entitlement snapshot
    • active lobby-relevant sanctions
    • effective lobby-relevant limits
    • derived booleans for lobby decisions
  • Keep the response read-optimized so lobby does not need multiple dependent calls back into User Service.
  • Define deterministic not-found behavior.

Deliverables

  • lobby eligibility query endpoint
  • response DTO
  • mapping from entitlement/sanction/limit state to derived eligibility fields

Exit Criteria

  • Game Lobby can decide create/join/manage eligibility from one read
  • no extra fan-out to other user sub-queries is required

Targeted Tests

  • lobby eligibility snapshot reflects paid status, sanctions, and limits
  • unknown user returns stable not-found behavior
  • derived booleans remain consistent with raw effective state

Stage 10 — Implement Geo declared_country Sync Command

Goal

Support the current-country denormalization path owned by Geo Profile Service.

Tasks

  • Implement one explicit trusted command to sync current declared_country.
  • Validate ISO alpha-2 input.
  • Ensure the command updates only the current value on the user account.
  • Do not add country history behavior to User Service.
  • Preserve explicit not-found behavior for unknown user_id.
  • Emit the corresponding auxiliary declared-country change event after a successful commit.

Deliverables

  • geo-facing sync endpoint
  • application service for country sync
  • event publication on successful mutation

Exit Criteria

  • geo can synchronize current declared_country without introducing hidden history in User Service
  • unknown users are rejected deterministically

Targeted Tests

  • geo country sync changes only current declared_country
  • invalid country codes are rejected
  • country sync emits the correct auxiliary event after commit

Stage 11 — Implement Admin Lookup, Filtered Listing, and Explicit Trusted Mutations

Goal

Provide the operational surface required by future Admin Service and manual operations.

Tasks

  • Implement exact reads by:
    • user_id
    • normalized email
    • exact race_name
  • Implement paginated listing with richer filters:
    • paid/free state
    • paid expiry
    • current declared_country
    • sanction code
    • limit code
    • eligibility markers
  • Freeze deterministic ordering for the listing.
  • Implement the explicit trusted command surface for:
    • entitlement grant/extend/revoke
    • sanction apply/remove
    • limit set/remove
    • declared-country sync
  • Preserve audit metadata on every trusted mutation.

Deliverables

  • admin/internal read endpoints
  • filtered listing endpoint
  • explicit trusted mutation endpoints

Exit Criteria

  • future Admin Service can operate fully through this trusted API without needing direct storage access
  • list filtering and pagination are deterministic

Targeted Tests

  • admin listing filters behave deterministically
  • exact lookups by user_id, email, and race_name resolve the correct user
  • every trusted mutation preserves actor and reason metadata

Stage 12 — Add Per-Domain-Area Async Events and Observability

Goal

Make production behavior observable without treating events as the source of truth.

Tasks

  • Publish per-domain-area events for:
    • profile changes
    • settings changes
    • entitlement changes
    • sanction changes
    • limit changes
    • declared-country changes
  • Add structured logs for trusted mutations and critical failures.
  • Add metrics for:
    • auth-facing resolution outcomes
    • user creation outcomes
    • race-name reservation conflicts
    • entitlement mutation outcomes
    • sanction and limit mutation outcomes
    • event publication failures
  • Add tracing spans on synchronous internal request paths where useful.

Deliverables

  • event publisher integration
  • structured logging hooks
  • metrics and tracing instrumentation

Exit Criteria

  • mutation flows are observable in production without ad hoc logging
  • event publication failure does not compromise source-of-truth persistence

Targeted Tests

  • async event publication failure does not lose source-of-truth state
  • event payloads include minimum required metadata
  • observability hooks do not change business behavior

Stage 13 — Add Contract Tests Against Auth, Lobby, and Geo Expectations

Goal

Verify the service not only in isolation, but against the internal contracts it must satisfy for other services.

Tasks

  • Add compatibility tests against the frozen auth-facing REST contract.
  • Add compatibility tests for the future ensure-by-email registration context.
  • Add lobby eligibility snapshot contract tests.
  • Add geo country-sync contract tests.
  • Add account aggregate tests matching gateway-routed user expectations.
  • Add tests for deterministic admin listing filters and ordering.

Deliverables

  • cross-service contract test suite
  • test fixtures for auth/lobby/geo integration expectations

Exit Criteria

  • no ambiguity remains about service behavior expected by auth, lobby, or geo
  • regressions in reserved internal contract shapes are caught automatically

Targeted Tests

  • new user created on first successful confirm with generated race_name, derived preferred_language, and required client time_zone
  • existing user confirm ignores create-only registration context
  • blocked e-mail subject prevents user creation before a user record exists
  • GetMyAccount returns current entitlement, active sanctions, active limits, and read-only declared_country
  • lobby eligibility snapshot reflects paid status, sanctions, and limits
  • geo country sync changes only current declared_country

Stage 14 — Add Rollout Notes for Gateway/Auth/OpenAPI Updates and Shared geoip

Goal

Prepare the surrounding platform changes required for the service to work in its intended end-to-end form.

Tasks

  • Document the required gateway public confirm-email-code addition of time_zone.
  • Document the required authsession public OpenAPI mirror change.
  • Document the required authsession -> user ensure contract extension for create-only registration context.
  • Document the required shared pkg/geoip package for gateway and geo.
  • Document README follow-up updates needed in gateway and geoprofile.
  • Define rollout order so the cross-service contract changes do not land in an unsafe sequence.

Deliverables

  • rollout checklist
  • dependency order notes
  • cross-repo or cross-module follow-up ticket list

Exit Criteria

  • the implementation can be integrated into surrounding services without rediscovering hidden dependencies
  • no required upstream or downstream change is left implicit

Targeted Tests

  • documentation review only

The smallest useful end-to-end slice is:

  1. Stage 01
  2. Stage 02
  3. Stage 03
  4. Stage 04

This slice makes it possible to support auth-driven user creation and blocking before the rest of the service surface exists.

The next highest-value slice is:

  1. Stage 05
  2. Stage 06
  3. Stage 07
  4. Stage 08
  5. Stage 09

This slice gives the platform usable account reads, self-service profile and settings updates, and the lobby eligibility integration.

Final Acceptance Criteria

The first production-capable v1 of User Service should satisfy all of the following:

  • new users can be created through auth with generated race_name, derived preferred_language, and required client time_zone
  • existing-user auth confirm ignores create-only registration context
  • blocked e-mail subjects prevent new-user creation before a user record exists
  • race_name uniqueness rejects case-insensitive and anti-fraud-confusable collisions
  • GetMyAccount returns current entitlement, active sanctions, active limits, and read-only declared_country
  • UpdateMyProfile cannot change email or declared_country
  • UpdateMySettings validates BCP 47 and IANA values
  • entitlement period mutations update the materialized current snapshot correctly
  • lobby eligibility snapshot reflects paid status, sanctions, and limits
  • geo declared_country sync changes only current account state
  • admin listing filters and ordering are deterministic
  • async event publication failure does not lose source-of-truth state

Implementation Order Summary

Recommended implementation order:

  1. freeze vocabulary and ownership
  2. define domain entities and logical storage
  3. build auth-facing resolution and blocking
  4. add new-user creation context
  5. build self-service account read and updates
  6. add race-name uniqueness policy
  7. build entitlement history and current snapshot
  8. build sanctions and limits
  9. add lobby eligibility snapshot
  10. add geo country sync
  11. add admin reads, listing, and mutations
  12. add events and observability
  13. add cross-service contract tests
  14. document and sequence rollout dependencies