802 lines
22 KiB
Markdown
802 lines
22 KiB
Markdown
# User Service
|
|
|
|
## Context and Purpose
|
|
|
|
`User Service` is the internal source of truth for regular Galaxy Plus platform
|
|
users.
|
|
|
|
The service exists to solve six closely related problems:
|
|
|
|
- Own the durable platform user account identified by `user_id`.
|
|
- Store the current editable self-service profile and settings of a user.
|
|
- Materialize the current effective entitlement state used by the rest of the
|
|
platform.
|
|
- Store user-specific sanctions and limit overrides that affect access
|
|
decisions.
|
|
- Expose synchronous trusted APIs needed by `Auth / Session Service`,
|
|
`Game Lobby`, `Geo Profile Service`, and future administrative tooling.
|
|
- Publish auxiliary user-domain change events without turning events into the
|
|
source of truth.
|
|
|
|
The service is intentionally the owner of regular user identity only.
|
|
System-administrator identity is outside this service and belongs to the later
|
|
`Admin Service` architecture.
|
|
|
|
## Explicit Non-Goals
|
|
|
|
The following are intentionally out of scope for this service:
|
|
|
|
- Authentication challenges, device sessions, or request-signing state.
|
|
- System-administrator identity or administrator role management.
|
|
- Ownership of game membership, invites, roster, or per-game moderation.
|
|
- Automatic billing computation or payment-provider integration in v1.
|
|
- History of `declared_country` changes.
|
|
- Geo-IP lookup or country-review workflow logic.
|
|
- Direct public unauthenticated exposure.
|
|
- Using async events as the authoritative representation of user state.
|
|
|
|
## Place in the Existing Microservice System
|
|
|
|
`User Service` operates inside the trusted internal platform and integrates
|
|
with:
|
|
|
|
- `Edge Gateway`
|
|
- `Auth / Session Service`
|
|
- `Game Lobby`
|
|
- `Geo Profile Service`
|
|
- `Admin Service` later
|
|
- `Billing Service` later
|
|
- internal event bus
|
|
|
|
`Edge Gateway` routes authenticated user-facing account operations to this
|
|
service.
|
|
|
|
`Auth / Session Service` uses this service to resolve, create, and block users
|
|
during the public e-mail-code login flow.
|
|
|
|
`Game Lobby` uses this service for synchronous eligibility checks that depend
|
|
on current entitlement, sanctions, and limit state.
|
|
|
|
`Geo Profile Service` remains the owner of country-change workflow and history,
|
|
but synchronizes the latest effective `declared_country` into this service.
|
|
|
|
`Admin Service` will later use the trusted internal read and mutation APIs
|
|
defined here. Administrator accounts themselves still do not belong to
|
|
`User Service`.
|
|
|
|
`Billing Service` is future-only in v1 and will later feed entitlement
|
|
outcomes through the trusted entitlement mutation path defined here.
|
|
|
|
The event bus is an auxiliary propagation channel and not the source of truth.
|
|
|
|
## Responsibility Boundaries
|
|
|
|
`User Service` owns:
|
|
|
|
- regular platform `user_id`
|
|
- login/contact e-mail stored on the user account
|
|
- `race_name`
|
|
- `preferred_language`
|
|
- `time_zone`
|
|
- current effective `declared_country`
|
|
- current effective entitlement snapshot
|
|
- entitlement history records
|
|
- active and historical user sanctions
|
|
- active and historical user-specific limit overrides
|
|
- blocked e-mail subjects that may exist before any user record exists
|
|
- synchronous trusted reads used for auth, lobby, geo, and admin workflows
|
|
- auxiliary user-domain events
|
|
|
|
`User Service` does not own:
|
|
|
|
- system-admin accounts
|
|
- `device_session` or revoke state
|
|
- full payment history
|
|
- game ownership, game membership, or game-level bans
|
|
- `declared_country` history or approval workflow
|
|
- per-request country observations
|
|
|
|
## High-Level Architecture
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
Gateway[Edge Gateway]
|
|
Auth[Auth / Session Service]
|
|
Lobby[Game Lobby]
|
|
Geo[Geo Profile Service]
|
|
Admin[Admin Service]
|
|
Billing[Billing Service]
|
|
User[User Service]
|
|
Redis[Redis]
|
|
Bus[Event Bus]
|
|
|
|
Gateway --> User
|
|
Auth --> User
|
|
Lobby --> User
|
|
Geo --> User
|
|
Admin --> User
|
|
Billing --> User
|
|
User --> Redis
|
|
User --> Bus
|
|
```
|
|
|
|
## Semantic Model
|
|
|
|
The service works with several core concepts.
|
|
|
|
### User Account
|
|
|
|
The user account is the canonical regular-user aggregate.
|
|
|
|
Required logical fields:
|
|
|
|
- `user_id`
|
|
- normalized `email`
|
|
- `race_name`
|
|
- `preferred_language`
|
|
- `time_zone`
|
|
- current effective `declared_country`
|
|
- creation timestamp
|
|
- last update timestamp
|
|
|
|
Important rules:
|
|
|
|
- `email` is the primary login/contact identifier for end users.
|
|
- `email` is not directly editable through self-service profile updates.
|
|
- future e-mail change is a separate confirm-based workflow and is not part of
|
|
v1 `User Service` mutations.
|
|
- `declared_country` is readable in account views but writable only through the
|
|
trusted geo sync path.
|
|
|
|
### race_name
|
|
|
|
`race_name` is the user-facing account name.
|
|
|
|
Properties:
|
|
|
|
- It is globally unique.
|
|
- It is not an identity key. Internal identity remains `user_id`, and end-user
|
|
login identity remains `email`.
|
|
- It is stored and returned in the original user-provided casing.
|
|
- Uniqueness is enforced through a dedicated policy boundary rather than by
|
|
naive string equality.
|
|
|
|
The uniqueness policy must at minimum:
|
|
|
|
- compare case-insensitively
|
|
- reject common confusable substitutions used for impersonation, such as
|
|
`I` versus `1`, `O` versus `0`, and `B` versus `8`
|
|
- remain replaceable behind a dedicated interface because a future shared name
|
|
catalog service is expected
|
|
|
|
### preferred_language and time_zone
|
|
|
|
`preferred_language` and `time_zone` are explicit user settings, not inferred
|
|
runtime facts.
|
|
|
|
Properties:
|
|
|
|
- `preferred_language` uses BCP 47 language tags.
|
|
- `time_zone` uses IANA time zone names.
|
|
- both values exist on every created user in v1
|
|
- both values are editable later through self-service settings mutation
|
|
|
|
Initial creation rules:
|
|
|
|
- `preferred_language` is supplied to `User Service` through auth create-only
|
|
registration context
|
|
- the value is derived by `Edge Gateway` from local geoip country plus local
|
|
country-to-language mapping
|
|
- when that lookup cannot determine a language, gateway falls back to `en`
|
|
- `time_zone` is supplied by the client in public `confirm-email-code`
|
|
|
|
`User Service` does not perform its own geo lookup for this purpose.
|
|
|
|
### declared_country
|
|
|
|
`declared_country` is the latest effective user-declared country.
|
|
|
|
Properties:
|
|
|
|
- It uses ISO 3166-1 alpha-2.
|
|
- It is the read-optimized current value only.
|
|
- It is owned for storage by `User Service`.
|
|
- It is owned for mutation workflow and history by `Geo Profile Service`.
|
|
|
|
This split is intentional:
|
|
|
|
- reads of current account state go to `User Service`
|
|
- reads of review workflow and country history go to `Geo Profile Service`
|
|
|
|
### Entitlement
|
|
|
|
Entitlement describes the paid/free access state of a user account.
|
|
|
|
The plan catalog fixed for v1 is:
|
|
|
|
- `free`
|
|
- `paid_monthly`
|
|
- `paid_yearly`
|
|
- `paid_lifetime`
|
|
|
|
The service stores both:
|
|
|
|
- immutable or append-only entitlement period history records
|
|
- a materialized current effective entitlement snapshot for synchronous reads
|
|
|
|
The current effective snapshot is not computed on every request. It is updated
|
|
when trusted entitlement mutations succeed.
|
|
|
|
Period history records store:
|
|
|
|
- `user_id`
|
|
- `plan_code`
|
|
- `source`
|
|
- actor identity or actor type
|
|
- `reason_code`
|
|
- `starts_at`
|
|
- optional `ends_at`
|
|
- creation timestamp
|
|
|
|
Current effective snapshot stores at minimum:
|
|
|
|
- `user_id`
|
|
- current `plan_code`
|
|
- effective paid/free state
|
|
- effective period bounds when applicable
|
|
- source metadata needed by operations and admin reads
|
|
- last recomputation timestamp
|
|
|
|
In v1, entitlement mutations come from explicit trusted admin/internal
|
|
commands. Later, `Billing Service` uses the same mutation path.
|
|
|
|
### Sanctions
|
|
|
|
Sanctions are negative policy records that may deny or restrict access
|
|
regardless of entitlement state.
|
|
|
|
The initial sanction set for v1 is:
|
|
|
|
- `login_block`
|
|
- `private_game_create_block`
|
|
- `private_game_manage_block`
|
|
- `game_join_block`
|
|
- `profile_update_block`
|
|
|
|
Each sanction record stores:
|
|
|
|
- `user_id`
|
|
- `sanction_code`
|
|
- scope
|
|
- `reason_code`
|
|
- actor identity or actor type
|
|
- `applied_at`
|
|
- optional `expires_at`
|
|
- optional removal metadata if later removed
|
|
|
|
Sanctions are typed records rather than inline booleans so the service can keep
|
|
auditability and deterministic active-state evaluation.
|
|
|
|
### User-Specific Limits
|
|
|
|
User-specific limits are count-based override records that shape access and
|
|
eligibility decisions.
|
|
|
|
The initial limit set for v1 is:
|
|
|
|
- `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`
|
|
|
|
Each limit record stores:
|
|
|
|
- `user_id`
|
|
- `limit_code`
|
|
- numeric value
|
|
- `reason_code`
|
|
- actor identity or actor type
|
|
- `applied_at`
|
|
- optional `expires_at`
|
|
- optional removal metadata if later removed
|
|
|
|
Limit rules:
|
|
|
|
- limits are count-based only in v1
|
|
- limits are user-specific overrides, not the global default catalog itself
|
|
- effective eligibility combines entitlement-derived defaults with active
|
|
user-specific overrides
|
|
|
|
### Blocked E-Mail Subject
|
|
|
|
`User Service` must support blocking an e-mail subject before any user account
|
|
exists.
|
|
|
|
This requires a separate blocked-email-subject model.
|
|
|
|
Required logical fields:
|
|
|
|
- normalized `email`
|
|
- `reason_code`
|
|
- block timestamp
|
|
- optional actor metadata when available
|
|
- optional expiry or removal metadata if policy later requires it
|
|
- optional resolved `user_id` when the e-mail already belongs to an existing
|
|
user
|
|
|
|
This model exists to support `Auth / Session Service` flows such as
|
|
`BlockByEmail` and `ResolveByEmail` before user creation.
|
|
|
|
## Data Ownership Rules
|
|
|
|
The ownership split is intentional and must remain stable.
|
|
|
|
- `User Service` owns regular user identity and current effective account
|
|
state.
|
|
- `Auth / Session Service` owns login challenge and session lifecycle state.
|
|
- `Game Lobby` owns game membership and game-specific moderation.
|
|
- `Geo Profile Service` owns geo workflow and `declared_country` history.
|
|
- `Admin Service` later owns administrator identity and UI orchestration.
|
|
|
|
In particular:
|
|
|
|
- no service other than `Geo Profile Service` should mutate
|
|
`declared_country`
|
|
- no service other than `User Service` should create or edit regular user
|
|
profile/settings records
|
|
- no other service should maintain its own source of truth for current
|
|
entitlements
|
|
|
|
## User-Facing Interface Model
|
|
|
|
User-facing traffic reaches `User Service` only through authenticated gateway
|
|
routing.
|
|
|
|
The v1 aggregate query is:
|
|
|
|
- `GetMyAccount`
|
|
|
|
The v1 self-service mutations are:
|
|
|
|
- `UpdateMyProfile`
|
|
- `UpdateMySettings`
|
|
|
|
### GetMyAccount
|
|
|
|
`GetMyAccount` returns one read-optimized account aggregate for the currently
|
|
authenticated regular user.
|
|
|
|
The aggregate should include at minimum:
|
|
|
|
- `user_id`
|
|
- `email`
|
|
- `race_name`
|
|
- `preferred_language`
|
|
- `time_zone`
|
|
- current `declared_country`
|
|
- current entitlement snapshot
|
|
- active sanctions
|
|
- active effective limits
|
|
- account timestamps needed by clients
|
|
|
|
`declared_country` is read-only in this aggregate.
|
|
|
|
### UpdateMyProfile
|
|
|
|
`UpdateMyProfile` updates self-service profile fields only.
|
|
|
|
Editable fields in v1:
|
|
|
|
- `race_name`
|
|
|
|
Rules:
|
|
|
|
- e-mail cannot be changed here
|
|
- `declared_country` cannot be changed here
|
|
- `race_name` must pass global uniqueness policy before commit
|
|
- active `profile_update_block` sanction rejects the mutation
|
|
|
|
### UpdateMySettings
|
|
|
|
`UpdateMySettings` updates self-service settings only.
|
|
|
|
Editable fields in v1:
|
|
|
|
- `preferred_language`
|
|
- `time_zone`
|
|
|
|
Rules:
|
|
|
|
- values are validated as BCP 47 and IANA formats
|
|
- active `profile_update_block` sanction rejects the mutation
|
|
|
|
## Trusted Internal API Model
|
|
|
|
All service-to-service integration in v1 is documented as trusted JSON REST.
|
|
|
|
### Auth-Facing Contract
|
|
|
|
The auth-facing contract is already reserved by `Auth / Session Service` and
|
|
must remain stable.
|
|
|
|
Frozen endpoints:
|
|
|
|
- `POST /api/v1/internal/user-resolutions/by-email`
|
|
- `GET /api/v1/internal/users/{user_id}/exists`
|
|
- `POST /api/v1/internal/users/ensure-by-email`
|
|
- `POST /api/v1/internal/users/{user_id}/block`
|
|
- `POST /api/v1/internal/user-blocks/by-email`
|
|
|
|
Auth-facing behavior:
|
|
|
|
- resolve by e-mail returns `existing`, `creatable`, or `blocked`
|
|
- ensure by e-mail returns `existing`, `created`, or `blocked`
|
|
- block by user id and block by e-mail are idempotent
|
|
- blocked e-mail subjects are respected even when no user exists yet
|
|
|
|
`EnsureUserByEmail` is extended for v1 user creation context.
|
|
|
|
Recommended request shape:
|
|
|
|
```json
|
|
{
|
|
"email": "pilot@example.com",
|
|
"registration_context": {
|
|
"preferred_language": "en",
|
|
"time_zone": "Europe/Berlin"
|
|
}
|
|
}
|
|
```
|
|
|
|
Rules for `registration_context`:
|
|
|
|
- it is used only when the user is created
|
|
- it is ignored for an existing user
|
|
- it must not overwrite settings of an existing user
|
|
- it is required by the future auth contract because first successful confirm
|
|
may create the user
|
|
|
|
### Lobby-Facing Eligibility Snapshot
|
|
|
|
`Game Lobby` needs one synchronous query by `user_id`.
|
|
|
|
Purpose:
|
|
|
|
- determine whether the user currently may create or join game flows
|
|
- obtain effective quotas relevant to lobby decisions
|
|
|
|
The response should include at minimum:
|
|
|
|
- whether the user exists
|
|
- current entitlement snapshot
|
|
- active sanctions relevant to lobby actions
|
|
- effective limit values relevant to lobby actions
|
|
- derived booleans such as whether private-game creation is currently allowed
|
|
|
|
This query is intentionally one read-optimized snapshot rather than multiple
|
|
smaller cross-service round trips.
|
|
|
|
### Geo-Facing Declared Country Sync
|
|
|
|
`Geo Profile Service` needs one explicit trusted command to synchronize the
|
|
current effective `declared_country`.
|
|
|
|
Required behavior:
|
|
|
|
- update only the current `declared_country` value in `User Service`
|
|
- not create or manage country history here
|
|
- fail explicitly on unknown `user_id`
|
|
- remain synchronous so geo workflow can decide whether its own version record
|
|
becomes effective
|
|
|
|
### Admin/Internal Reads
|
|
|
|
The trusted admin/internal read surface must support:
|
|
|
|
- exact lookup by `user_id`
|
|
- exact lookup by normalized `email`
|
|
- exact lookup by exact `race_name`
|
|
- paginated listing with filters
|
|
|
|
The v1 listing filters must support at minimum:
|
|
|
|
- paid/free state
|
|
- paid expiry window
|
|
- current `declared_country`
|
|
- active sanction code
|
|
- active limit code
|
|
- relevant eligibility markers
|
|
|
|
Listing must use deterministic pagination and stable ordering.
|
|
Recommended default ordering is newest first by `created_at`, with `user_id`
|
|
used as the deterministic tiebreaker.
|
|
|
|
### Admin/Internal Mutations
|
|
|
|
Trusted mutations remain explicit-command based.
|
|
|
|
The minimum command vocabulary in v1 is:
|
|
|
|
- grant paid access
|
|
- extend paid access
|
|
- revoke paid access
|
|
- apply sanction
|
|
- remove sanction
|
|
- set limit
|
|
- remove limit
|
|
- sync declared country
|
|
|
|
These are intentionally explicit commands rather than one generic patch API.
|
|
The service should preserve reason and actor metadata on every trusted
|
|
administrative mutation.
|
|
|
|
## New User Creation Flow
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant Client
|
|
participant Gateway
|
|
participant Auth as Auth / Session Service
|
|
participant User as User Service
|
|
|
|
Client->>Gateway: confirm-email-code(code, client_public_key, time_zone)
|
|
Gateway->>Gateway: local geoip lookup and country-to-language mapping
|
|
Gateway->>Auth: confirm-email-code(..., time_zone)
|
|
Auth->>User: ensure-by-email(email, registration_context)
|
|
alt user already exists
|
|
User-->>Auth: existing user_id
|
|
else new user
|
|
User->>User: create user with generated race_name
|
|
User->>User: initialize language, time zone, free entitlement
|
|
User-->>Auth: created user_id
|
|
end
|
|
Auth-->>Gateway: device_session_id
|
|
Gateway-->>Client: device_session_id
|
|
```
|
|
|
|
New-user defaults:
|
|
|
|
- generated `race_name` in `player-<shortid>` form
|
|
- `preferred_language` from gateway-derived registration context
|
|
- `time_zone` from client-provided registration context
|
|
- `free` entitlement
|
|
- no active sanctions
|
|
- no custom limit overrides
|
|
|
|
## Interface Between Entitlement, Sanction, and Limit Evaluation
|
|
|
|
The service must keep these three layers separate.
|
|
|
|
- entitlement provides the base paid/free access state
|
|
- sanctions can deny actions regardless of entitlement
|
|
- user-specific limits can narrow or override numeric quotas
|
|
|
|
This means:
|
|
|
|
- a paid user may still be denied private-game creation by sanction
|
|
- a non-blocked user may still be quota-limited by effective count limits
|
|
- lobby checks should consume one effective snapshot rather than reimplementing
|
|
this evaluation itself
|
|
|
|
## Events
|
|
|
|
Events are auxiliary notifications only. They are not the source of truth.
|
|
|
|
The service should emit per-domain-area events for:
|
|
|
|
- profile changes
|
|
- settings changes
|
|
- entitlement changes
|
|
- sanction changes
|
|
- limit changes
|
|
- declared-country changes
|
|
|
|
Recommended event classes:
|
|
|
|
- `user.profile.changed`
|
|
- `user.settings.changed`
|
|
- `user.entitlement.changed`
|
|
- `user.sanction.changed`
|
|
- `user.limit.changed`
|
|
- `user.declared_country.changed`
|
|
|
|
Each event should include at minimum:
|
|
|
|
- `user_id`
|
|
- event timestamp
|
|
- mutation source
|
|
- correlation or request metadata when available
|
|
- enough event-specific detail to identify the changed domain area
|
|
|
|
Loss of an event must not lose the authoritative business state.
|
|
|
|
## Data Entities
|
|
|
|
This section defines the core logical entities. These are domain entities, not
|
|
mandatory final physical Redis key names.
|
|
|
|
### User Account Record
|
|
|
|
Required logical fields:
|
|
|
|
- `user_id`
|
|
- normalized `email`
|
|
- `race_name`
|
|
- `preferred_language`
|
|
- `time_zone`
|
|
- current `declared_country`
|
|
- `created_at`
|
|
- `updated_at`
|
|
|
|
### race_name Reservation
|
|
|
|
Required logical fields:
|
|
|
|
- canonical uniqueness key produced by the race-name policy
|
|
- referenced `user_id`
|
|
- original stored `race_name`
|
|
- reservation timestamp
|
|
|
|
This entity exists so uniqueness policy stays replaceable and explicit.
|
|
|
|
### Blocked E-Mail Subject Entity
|
|
|
|
Required logical fields:
|
|
|
|
- normalized `email`
|
|
- `reason_code`
|
|
- block status
|
|
- `blocked_at`
|
|
- optional resolved `user_id`
|
|
|
|
### Entitlement Period Record
|
|
|
|
Required logical fields:
|
|
|
|
- `user_id`
|
|
- `plan_code`
|
|
- `source`
|
|
- actor metadata
|
|
- `reason_code`
|
|
- `starts_at`
|
|
- optional `ends_at`
|
|
- record creation timestamp
|
|
|
|
### Current Entitlement Snapshot
|
|
|
|
Required logical fields:
|
|
|
|
- `user_id`
|
|
- effective `plan_code`
|
|
- paid/free state
|
|
- effective period bounds
|
|
- source metadata
|
|
- snapshot update timestamp
|
|
|
|
### Sanction Record
|
|
|
|
Required logical fields:
|
|
|
|
- `user_id`
|
|
- `sanction_code`
|
|
- scope
|
|
- `reason_code`
|
|
- actor metadata
|
|
- `applied_at`
|
|
- optional `expires_at`
|
|
- current status metadata
|
|
|
|
### Limit Record
|
|
|
|
Required logical fields:
|
|
|
|
- `user_id`
|
|
- `limit_code`
|
|
- numeric value
|
|
- `reason_code`
|
|
- actor metadata
|
|
- `applied_at`
|
|
- optional `expires_at`
|
|
- current status metadata
|
|
|
|
## Failure and Degradation Model
|
|
|
|
The service is synchronous for critical reads and mutations.
|
|
|
|
### Auth Dependency Failure
|
|
|
|
If `User Service` is unavailable during auth flows:
|
|
|
|
- `Auth / Session Service` must fail the affected operation explicitly
|
|
- no user should be created partially without source-of-truth persistence
|
|
|
|
### Event Publication Failure
|
|
|
|
If event publication fails:
|
|
|
|
- the source-of-truth mutation still remains committed
|
|
- failure is logged and metered
|
|
- downstream consumers recover from direct reads if needed
|
|
|
|
### race_name Uniqueness Backend Failure
|
|
|
|
If the dedicated race-name uniqueness policy backend fails:
|
|
|
|
- self-service profile update must fail closed
|
|
- new-user creation must fail explicitly rather than create ambiguous names
|
|
|
|
### Geo Sync Failure
|
|
|
|
If `Geo Profile Service` cannot synchronize `declared_country` into
|
|
`User Service`:
|
|
|
|
- geo must treat the change as not yet effective
|
|
- `User Service` must not create hidden partial country history
|
|
|
|
## Minimal Initial API Surface
|
|
|
|
The minimum useful v1 API surface is:
|
|
|
|
- gateway-routed authenticated:
|
|
- `GetMyAccount`
|
|
- `UpdateMyProfile`
|
|
- `UpdateMySettings`
|
|
- trusted internal auth:
|
|
- resolve by e-mail
|
|
- ensure by e-mail
|
|
- exists by user id
|
|
- block by user id
|
|
- block by e-mail
|
|
- trusted internal lobby:
|
|
- get user eligibility snapshot
|
|
- trusted internal geo:
|
|
- sync current `declared_country`
|
|
- trusted internal admin:
|
|
- exact user reads
|
|
- filtered user listing
|
|
- entitlement mutations
|
|
- sanction mutations
|
|
- limit mutations
|
|
|
|
## Cross-Service Follow-Up Dependencies
|
|
|
|
The service design here depends on later follow-up work in other modules.
|
|
|
|
Required follow-up items:
|
|
|
|
- `Edge Gateway` public `confirm-email-code` contract must add required
|
|
`time_zone`.
|
|
- `Auth / Session Service` public OpenAPI must mirror the same `time_zone`
|
|
addition.
|
|
- `Auth / Session Service -> User Service` ensure-by-email contract must add
|
|
create-only registration context with `preferred_language` and `time_zone`.
|
|
- a shared `pkg/geoip` package must be introduced for `Edge Gateway` and
|
|
`Geo Profile Service`
|
|
- `gateway/README.md` should later document the local geoip dependency used for
|
|
initial language derivation
|
|
- `geoprofile/README.md` should later document the shared `pkg/geoip`
|
|
dependency explicitly alongside its own local geo lookup
|
|
|
|
## Design Trade-Offs Accepted by This Architecture
|
|
|
|
- Current entitlement is materialized for fast reads instead of computed from
|
|
history on each request.
|
|
- User-specific limits are count-based only in v1 to keep evaluation simple.
|
|
- `race_name` uniqueness is stricter than plain case-insensitive comparison to
|
|
reduce impersonation risk.
|
|
- `User Service` stores only the latest effective `declared_country` while geo
|
|
owns the workflow and version history.
|
|
- Explicit trusted commands are preferred over generic patch semantics so
|
|
administrative changes remain auditable and predictable.
|
|
|
|
## Implementation Readiness Statement
|
|
|
|
This service specification is intended to be implementation-ready for a first
|
|
production-capable internal version.
|
|
|
|
The main remaining work is not product ambiguity inside `User Service`, but
|
|
follow-up cross-service contract changes in `gateway`, `authsession`, and the
|
|
future shared `pkg/geoip` package.
|