package ports import ( "context" "errors" "time" ) //go:generate go run go.uber.org/mock/mockgen -destination=../adapters/mocks/mock_lobbyclient.go -package=mocks galaxy/gamemaster/internal/ports LobbyClient // LobbyClient executes synchronous calls to Game Lobby. The port // surfaces two operations: // // - GetMemberships — used by the membership cache to authorise player // commands on the hot path. // - GetGameSummary — used by the turn-generation orchestrator to // resolve the human-readable `game_name` consumed by // `notification:intents` payloads (`game.turn.ready`, // `game.finished`, `game.generation_failed`). Failure is fail-soft: // callers fall back to `game_id` rather than block the runtime // mutation. // // Membership data and the game record are owned by Game Lobby; GM // treats them as remote projections. Consequently the Membership and // GameSummary types live on the port file rather than as domain types, // mirroring rtmanager's `LobbyGameRecord` precedent. type LobbyClient interface { // GetMemberships returns every membership of gameID, in any // status. The cache layer filters to `active` for authorisation. // Implementations wrap any non-success outcome (transport error, // timeout, non-2xx response) with ErrLobbyUnavailable so callers // can branch with errors.Is. GetMemberships(ctx context.Context, gameID string) ([]Membership, error) // GetGameSummary returns the narrow projection of Lobby's // `GameRecord` GM needs to populate notification payloads with a // human-readable `game_name`. Implementations wrap any non-success // outcome (transport error, timeout, non-2xx response, malformed // payload) with ErrLobbyUnavailable. GetGameSummary(ctx context.Context, gameID string) (GameSummary, error) } // Membership stores one row of the membership projection returned by // `Lobby /api/v1/internal/games/{game_id}/memberships`. The shape // mirrors `MembershipRecord` in // `galaxy/lobby/api/internal-openapi.yaml`. type Membership struct { // UserID identifies the platform user. UserID string // RaceName stores the in-game race reserved for the user. RaceName string // Status reports `active`, `removed`, or `blocked`. GM authorises // only `active` callers on the hot path. Status string // JoinedAt stores the wall-clock at which the membership entered // active. JoinedAt time.Time // RemovedAt stores the wall-clock at which the membership left // active. Nil while the membership is still active. RemovedAt *time.Time } // GameSummary stores the narrow projection of Lobby's `GameRecord` GM // consumes today: the platform game id, the human-readable // `game_name`, and the platform-level lifecycle status. Additional // fields can be added without breaking consumers because every caller // reads through the typed fields directly. type GameSummary struct { // GameID identifies the platform game. Echoed back from Lobby as a // sanity check. GameID string // GameName stores the human-readable game name maintained by // Lobby. Used by the turn-generation orchestrator to populate // `game_name` on `notification:intents` payloads. GameName string // Status stores Lobby's platform-level lifecycle status (`draft`, // `enrollment_open`, `running`, `finished`, etc.). GM does not act // on the value today; it is captured for future audit/log use. Status string } // ErrLobbyUnavailable signals that a Lobby call could not be completed // because the upstream service was unreachable, returned an error // response, or timed out. GM's hot-path callers treat any non-success // outcome uniformly: the player command is rejected with // `service_unavailable` and the cache TTL eventually retries. var ErrLobbyUnavailable = errors.New("lobby unavailable")