openapi: 3.0.3 info: title: Galaxy Game Master Internal REST API version: v1 description: | This specification documents the internal trusted REST contract of `galaxy/gamemaster` served on `GAMEMASTER_INTERNAL_HTTP_ADDR` (default `:8097`). This port is not reachable from the public internet. Callers are: - `Edge Gateway` for verified player commands, orders, and reports. - `Game Lobby` for runtime registration, image-ref resolution, membership-cache invalidation, race banishment, and liveness probes. - `Admin Service` (future) for runtime control operations and the engine version registry. Transport rules: - request bodies are strict JSON only; unknown fields are rejected - error responses use `{ "error": { "code", "message" } }` matching the envelope used by `galaxy/lobby` and `galaxy/rtmanager` - timestamps are UTC Unix milliseconds (`integer, format: int64`) - the listener is unauthenticated; downstream services rely on network segmentation. The `X-User-ID` header is required only on the three Edge Gateway hot-path operations (`internalExecuteCommands`, `internalPutOrders`, `internalGetReport`) and carries the verified player identity. Schema closure: - every body schema owned by `Game Master` sets `additionalProperties: false` - three operations forward engine-owned payloads verbatim (`internalExecuteCommands`, `internalPutOrders`, `internalGetReport`) and therefore use `additionalProperties: true` on the corresponding request and response bodies. The source of truth for those shapes is `galaxy/game/openapi.yaml`. - `EngineVersion.options` is a free-form `jsonb` document and uses `additionalProperties: true` for the same reason. servers: - url: http://localhost:8097 description: Default local internal listener for Game Master. tags: - name: Probes description: Health and readiness probes. - name: Runtimes description: Runtime control surface used by Admin Service. - name: GMIntegration description: Game Lobby integration paths under /api/v1/internal/games. - name: EngineVersions description: Engine version registry CRUD and image-ref resolve. - name: Gateway description: Edge Gateway hot-path commands, orders, and reports. paths: /healthz: get: tags: - Probes operationId: internalHealthz summary: Internal listener health probe responses: "200": description: Service is alive. content: application/json: schema: $ref: "#/components/schemas/ProbeResponse" examples: ok: value: status: ok "503": $ref: "#/components/responses/ServiceUnavailableError" /readyz: get: tags: - Probes operationId: internalReadyz summary: Internal listener readiness probe responses: "200": description: Service is ready to serve traffic. content: application/json: schema: $ref: "#/components/schemas/ProbeResponse" examples: ready: value: status: ready "503": $ref: "#/components/responses/ServiceUnavailableError" /api/v1/internal/games/{game_id}/register-runtime: post: tags: - GMIntegration operationId: internalRegisterRuntime summary: Register a runtime after a successful container start description: | Called by `Game Lobby` after `Runtime Manager` has reported a successful container start. Game Master persists the runtime record, calls the engine `/api/v1/admin/init`, persists player mappings derived from the engine response, and transitions the runtime to `running`. parameters: - $ref: "#/components/parameters/GameIDPath" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/RegisterRuntimeRequest" responses: "200": description: Runtime registered and transitioned to `running`. content: application/json: schema: $ref: "#/components/schemas/RuntimeRecord" "400": $ref: "#/components/responses/InvalidRequestError" "404": $ref: "#/components/responses/EngineVersionNotFoundError" "409": $ref: "#/components/responses/ConflictError" "502": $ref: "#/components/responses/EngineUnreachableError" "500": $ref: "#/components/responses/InternalError" "503": $ref: "#/components/responses/ServiceUnavailableError" /api/v1/internal/games/{game_id}/race/{race_name}/banish: post: tags: - GMIntegration operationId: internalBanishRace summary: Banish a race from the running engine after a permanent removal description: | Called by `Game Lobby` synchronously after a permanent platform-level membership removal. Game Master forwards the call to the engine `/api/v1/admin/race/banish` and records the outcome in the operation log. parameters: - $ref: "#/components/parameters/GameIDPath" - $ref: "#/components/parameters/RaceNamePath" responses: "204": description: Race banished from the engine. "404": $ref: "#/components/responses/NotFoundError" "502": $ref: "#/components/responses/EngineUnreachableError" "500": $ref: "#/components/responses/InternalError" /api/v1/internal/games/{game_id}/memberships/invalidate: post: tags: - GMIntegration operationId: internalInvalidateMemberships summary: Invalidate the membership cache for a game description: | Called by `Game Lobby` post-commit on every roster mutation (application approval, rejection, invite redeem, member remove, member block, user-lifecycle cascade). Game Master purges the in-process per-game membership cache; the TTL is the safety net for missed calls. parameters: - $ref: "#/components/parameters/GameIDPath" responses: "204": description: Membership cache entry invalidated. "404": $ref: "#/components/responses/NotFoundError" "500": $ref: "#/components/responses/InternalError" /api/v1/internal/games/{game_id}/liveness: get: tags: - GMIntegration operationId: internalGameLiveness summary: Report whether a runtime is ready description: | Called by `Game Lobby` as part of the resume flow for a paused game. Reflects Game Master's own runtime view; the engine is not contacted by this endpoint. parameters: - $ref: "#/components/parameters/GameIDPath" responses: "200": description: Liveness reply. content: application/json: schema: $ref: "#/components/schemas/LivenessResponse" "500": $ref: "#/components/responses/InternalError" /api/v1/internal/runtimes: get: tags: - Runtimes operationId: internalListRuntimes summary: List runtime records description: | Returns runtime records ordered by `created_at` descending. The optional `status` query parameter narrows the result to runtimes in the given runtime status. parameters: - $ref: "#/components/parameters/RuntimeStatusQuery" responses: "200": description: Page of runtime records. content: application/json: schema: $ref: "#/components/schemas/RuntimeListResponse" "400": $ref: "#/components/responses/InvalidRequestError" "500": $ref: "#/components/responses/InternalError" /api/v1/internal/runtimes/{game_id}: get: tags: - Runtimes operationId: internalGetRuntime summary: Read one runtime record parameters: - $ref: "#/components/parameters/GameIDPath" responses: "200": description: Runtime record. content: application/json: schema: $ref: "#/components/schemas/RuntimeRecord" "404": $ref: "#/components/responses/NotFoundError" "500": $ref: "#/components/responses/InternalError" /api/v1/internal/runtimes/{game_id}/force-next-turn: post: tags: - Runtimes operationId: internalForceNextTurn summary: Force immediate generation of the next turn description: | Runs the turn-generation flow synchronously and sets `skip_next_tick` so the next regular cron tick is consumed without producing back-to-back turns. parameters: - $ref: "#/components/parameters/GameIDPath" responses: "200": description: Turn generated; runtime record reflects the new turn number and scheduling state. content: application/json: schema: $ref: "#/components/schemas/RuntimeRecord" "404": $ref: "#/components/responses/NotFoundError" "409": $ref: "#/components/responses/ConflictError" "502": $ref: "#/components/responses/EngineUnreachableError" "500": $ref: "#/components/responses/InternalError" /api/v1/internal/runtimes/{game_id}/stop: post: tags: - Runtimes operationId: internalStopRuntime summary: Stop a runtime through Runtime Manager description: | Game Master forwards the request to `Runtime Manager` and CASes the runtime status to `stopped` on success. parameters: - $ref: "#/components/parameters/GameIDPath" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/StopRuntimeRequest" responses: "200": description: Runtime stopped. content: application/json: schema: $ref: "#/components/schemas/RuntimeRecord" "400": $ref: "#/components/responses/InvalidRequestError" "404": $ref: "#/components/responses/NotFoundError" "500": $ref: "#/components/responses/InternalError" "503": $ref: "#/components/responses/ServiceUnavailableError" /api/v1/internal/runtimes/{game_id}/patch: post: tags: - Runtimes operationId: internalPatchRuntime summary: Patch the engine version of a runtime through Runtime Manager description: | Resolves the new image reference from the engine version registry, validates the target version is a semver-patch of the currently running version, and forwards the patch call to `Runtime Manager`. parameters: - $ref: "#/components/parameters/GameIDPath" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/PatchRuntimeRequest" responses: "200": description: Runtime patched; `current_engine_version` and `current_image_ref` updated. content: application/json: schema: $ref: "#/components/schemas/RuntimeRecord" "400": $ref: "#/components/responses/InvalidRequestError" "404": $ref: "#/components/responses/NotFoundError" "409": $ref: "#/components/responses/ConflictError" "500": $ref: "#/components/responses/InternalError" "503": $ref: "#/components/responses/ServiceUnavailableError" /api/v1/internal/engine-versions: get: tags: - EngineVersions operationId: internalListEngineVersions summary: List engine versions parameters: - $ref: "#/components/parameters/EngineVersionStatusQuery" responses: "200": description: Engine version registry contents. content: application/json: schema: $ref: "#/components/schemas/EngineVersionListResponse" "400": $ref: "#/components/responses/InvalidRequestError" "500": $ref: "#/components/responses/InternalError" post: tags: - EngineVersions operationId: internalCreateEngineVersion summary: Create a new engine version record requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/CreateEngineVersionRequest" responses: "201": description: Engine version created. content: application/json: schema: $ref: "#/components/schemas/EngineVersion" "400": $ref: "#/components/responses/InvalidRequestError" "409": $ref: "#/components/responses/ConflictError" "500": $ref: "#/components/responses/InternalError" /api/v1/internal/engine-versions/{version}: get: tags: - EngineVersions operationId: internalGetEngineVersion summary: Read one engine version record parameters: - $ref: "#/components/parameters/VersionPath" responses: "200": description: Engine version record. content: application/json: schema: $ref: "#/components/schemas/EngineVersion" "404": $ref: "#/components/responses/NotFoundError" "500": $ref: "#/components/responses/InternalError" patch: tags: - EngineVersions operationId: internalUpdateEngineVersion summary: Patch an engine version record parameters: - $ref: "#/components/parameters/VersionPath" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/UpdateEngineVersionRequest" responses: "200": description: Engine version updated. content: application/json: schema: $ref: "#/components/schemas/EngineVersion" "400": $ref: "#/components/responses/InvalidRequestError" "404": $ref: "#/components/responses/NotFoundError" "500": $ref: "#/components/responses/InternalError" delete: tags: - EngineVersions operationId: internalDeprecateEngineVersion summary: Deprecate an engine version description: | Sets the engine version status to `deprecated`. Hard removal of a version that is referenced by a non-finished runtime is rejected with `engine_version_in_use`. parameters: - $ref: "#/components/parameters/VersionPath" responses: "204": description: Engine version deprecated. "404": $ref: "#/components/responses/NotFoundError" "409": $ref: "#/components/responses/EngineVersionInUseError" "500": $ref: "#/components/responses/InternalError" /api/v1/internal/engine-versions/{version}/image-ref: get: tags: - EngineVersions operationId: internalResolveEngineVersionImageRef summary: Resolve the image reference of an engine version description: | Hot path used by `Game Lobby` synchronously before publishing a `runtime:start_jobs` envelope. Returns the `image_ref` only. parameters: - $ref: "#/components/parameters/VersionPath" responses: "200": description: Image reference of the requested version. content: application/json: schema: $ref: "#/components/schemas/ImageRefResponse" "404": $ref: "#/components/responses/EngineVersionNotFoundError" "500": $ref: "#/components/responses/InternalError" /api/v1/internal/games/{game_id}/commands: post: tags: - Gateway operationId: internalExecuteCommands summary: Execute a batch of player commands description: | Edge Gateway hot path for `game.command.execute`. Game Master authorises the user, resolves `actor=race_name` from its own player mappings, and forwards the request to the engine `/api/v1/command`. The request and response bodies are engine-owned and pass through unchanged (`additionalProperties: true`). parameters: - $ref: "#/components/parameters/GameIDPath" - $ref: "#/components/parameters/XUserIDHeader" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/ExecuteCommandsRequest" responses: "200": description: Engine response forwarded verbatim. content: application/json: schema: $ref: "#/components/schemas/ExecuteCommandsResponse" "400": $ref: "#/components/responses/InvalidRequestError" "403": $ref: "#/components/responses/ForbiddenError" "404": $ref: "#/components/responses/NotFoundError" "409": $ref: "#/components/responses/ConflictError" "502": $ref: "#/components/responses/EngineUnreachableError" "500": $ref: "#/components/responses/InternalError" /api/v1/internal/games/{game_id}/orders: post: tags: - Gateway operationId: internalPutOrders summary: Submit a batch of player orders description: | Edge Gateway hot path for `game.order.put`. Same authorisation and forwarding semantics as `internalExecuteCommands`; the engine endpoint is `/api/v1/order`. parameters: - $ref: "#/components/parameters/GameIDPath" - $ref: "#/components/parameters/XUserIDHeader" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/PutOrdersRequest" responses: "200": description: Engine response forwarded verbatim. content: application/json: schema: $ref: "#/components/schemas/PutOrdersResponse" "400": $ref: "#/components/responses/InvalidRequestError" "403": $ref: "#/components/responses/ForbiddenError" "404": $ref: "#/components/responses/NotFoundError" "409": $ref: "#/components/responses/ConflictError" "502": $ref: "#/components/responses/EngineUnreachableError" "500": $ref: "#/components/responses/InternalError" /api/v1/internal/games/{game_id}/reports/{turn}: get: tags: - Gateway operationId: internalGetReport summary: Read a per-player turn report description: | Edge Gateway hot path for `game.report.get`. Game Master authorises the user and forwards `GET /api/v1/report?player={race_name}&turn={turn}` to the engine. The response body is engine-owned and pass-through. parameters: - $ref: "#/components/parameters/GameIDPath" - $ref: "#/components/parameters/TurnPath" - $ref: "#/components/parameters/XUserIDHeader" responses: "200": description: Engine response forwarded verbatim. content: application/json: schema: $ref: "#/components/schemas/ReportResponse" "403": $ref: "#/components/responses/ForbiddenError" "404": $ref: "#/components/responses/NotFoundError" "502": $ref: "#/components/responses/EngineUnreachableError" "500": $ref: "#/components/responses/InternalError" components: parameters: GameIDPath: name: game_id in: path required: true description: Opaque stable game identifier owned by Game Lobby. schema: type: string VersionPath: name: version in: path required: true description: Semver string of an engine version registered with Game Master. schema: type: string RaceNamePath: name: race_name in: path required: true description: Race name registered for a player in the running game. schema: type: string TurnPath: name: turn in: path required: true description: Turn number for which the per-player report is fetched. schema: type: integer minimum: 0 XUserIDHeader: name: X-User-ID in: header required: true description: Verified player identity propagated by Edge Gateway. Trusted as authoritative. schema: type: string RuntimeStatusQuery: name: status in: query required: false description: Optional filter; when set, only runtimes in the given runtime status are returned. schema: $ref: "#/components/schemas/RuntimeStatus" EngineVersionStatusQuery: name: status in: query required: false description: Optional filter; when set, only engine versions in the given status are returned. schema: $ref: "#/components/schemas/EngineVersionStatus" schemas: RuntimeStatus: type: string enum: - starting - running - generation_in_progress - generation_failed - stopped - engine_unreachable - finished description: Current runtime status of a registered game. EngineVersionStatus: type: string enum: - active - deprecated description: Engine version registry status. StopReason: type: string enum: - admin_request - finished - timeout description: Reason argument passed to Runtime Manager when stopping a runtime. ProbeResponse: type: object additionalProperties: false required: - status properties: status: type: string description: Probe outcome string (`ok` or `ready`). LivenessResponse: type: object additionalProperties: false required: - ready - status properties: ready: type: boolean description: True when the runtime is in `running`; false otherwise. status: $ref: "#/components/schemas/RuntimeStatus" ImageRefResponse: type: object additionalProperties: false required: - image_ref properties: image_ref: type: string description: Docker reference of the engine image registered for the requested version. RegisterRuntimeMember: type: object additionalProperties: false required: - user_id - race_name properties: user_id: type: string description: Platform user identifier of an active member. race_name: type: string description: Race name reserved for the member in this game. RegisterRuntimeRequest: type: object additionalProperties: false required: - engine_endpoint - members - target_engine_version - turn_schedule properties: engine_endpoint: type: string description: Engine container DNS endpoint, e.g. http://galaxy-game-{game_id}:8080. members: type: array minItems: 1 items: $ref: "#/components/schemas/RegisterRuntimeMember" description: Members included in the engine init roster. target_engine_version: type: string description: Semver of the engine version under which the container was started. turn_schedule: type: string description: Five-field cron expression copied from the platform game record. RuntimeRecord: type: object additionalProperties: false required: - game_id - runtime_status - engine_endpoint - current_image_ref - current_engine_version - turn_schedule - current_turn - next_generation_at - skip_next_tick - engine_health_summary - created_at - updated_at properties: game_id: type: string description: Opaque stable game identifier; primary key. runtime_status: $ref: "#/components/schemas/RuntimeStatus" engine_endpoint: type: string description: Engine container DNS endpoint observed at register-runtime time. current_image_ref: type: string description: Docker reference of the running image. current_engine_version: type: string description: Semver of the running engine version. turn_schedule: type: string description: Five-field cron expression governing the scheduler ticker. current_turn: type: integer minimum: 0 description: Last completed turn number; zero until the first turn generates. next_generation_at: type: integer format: int64 description: UTC Unix milliseconds of the next scheduled tick. skip_next_tick: type: boolean description: True when force-next-turn has set the skip flag for the next regular tick. engine_health_summary: type: string description: Short text summary derived from runtime:health_events; empty until the first health observation. created_at: type: integer format: int64 description: UTC Unix milliseconds; record creation timestamp. updated_at: type: integer format: int64 description: UTC Unix milliseconds; last mutation timestamp. started_at: type: integer format: int64 description: UTC Unix milliseconds; set when status first becomes running. Optional. stopped_at: type: integer format: int64 description: UTC Unix milliseconds; set when status becomes stopped. Optional. finished_at: type: integer format: int64 description: UTC Unix milliseconds; set when status becomes finished. Optional. RuntimeListResponse: type: object additionalProperties: false required: - runtimes properties: runtimes: type: array items: $ref: "#/components/schemas/RuntimeRecord" StopRuntimeRequest: type: object additionalProperties: false required: - reason properties: reason: $ref: "#/components/schemas/StopReason" PatchRuntimeRequest: type: object additionalProperties: false required: - version properties: version: type: string description: Target engine version; must be a semver-patch of the running version. EngineVersion: type: object additionalProperties: false required: - version - image_ref - options - status - created_at - updated_at properties: version: type: string description: Semver string; primary key in the registry. image_ref: type: string description: Non-empty Docker reference of the engine image. options: type: object additionalProperties: true description: Free-form jsonb document of engine-side options. Pass-through; Game Master does not enforce a schema. status: $ref: "#/components/schemas/EngineVersionStatus" created_at: type: integer format: int64 description: UTC Unix milliseconds; record creation timestamp. updated_at: type: integer format: int64 description: UTC Unix milliseconds; last mutation timestamp. EngineVersionListResponse: type: object additionalProperties: false required: - versions properties: versions: type: array items: $ref: "#/components/schemas/EngineVersion" CreateEngineVersionRequest: type: object additionalProperties: false required: - version - image_ref properties: version: type: string description: Semver string of the new version. image_ref: type: string description: Non-empty Docker reference of the engine image. options: type: object additionalProperties: true description: Optional engine-side options document. Free-form jsonb. UpdateEngineVersionRequest: type: object additionalProperties: false description: PATCH body. Every field is optional; at least one must be present. properties: image_ref: type: string description: New Docker reference for the version. options: type: object additionalProperties: true description: Replacement options document. status: $ref: "#/components/schemas/EngineVersionStatus" ExecuteCommandsRequest: type: object additionalProperties: true required: - commands description: | Player command batch carried inside `commands`. Game Master rewrites the envelope before forwarding to the engine `/api/v1/command`: the `commands` array is renamed to `cmd` and a top-level `actor` field is set to the caller's race name resolved from `player_mappings`. Caller-supplied envelope fields other than `commands` are dropped; Game Master never trusts a caller-supplied `actor` per `gamemaster/README.md` §Hot Path. properties: commands: type: array items: type: object additionalProperties: true ExecuteCommandsResponse: type: object additionalProperties: true description: Engine-owned shape; the response from the engine /api/v1/command endpoint, returned to Edge Gateway unchanged. PutOrdersRequest: type: object additionalProperties: true required: - commands description: | Player order batch carried inside `commands`. Same envelope-rewrite semantics as `ExecuteCommandsRequest`: Game Master renames `commands` to `cmd` and sets `actor` from the caller identity before forwarding to the engine `/api/v1/order`. properties: commands: type: array items: type: object additionalProperties: true PutOrdersResponse: type: object additionalProperties: true description: Engine-owned shape; the response from the engine /api/v1/order endpoint, returned to Edge Gateway unchanged. ReportResponse: type: object additionalProperties: true description: Engine-owned shape; the response from the engine /api/v1/report endpoint, returned to Edge Gateway unchanged. ErrorResponse: type: object additionalProperties: false required: - error properties: error: $ref: "#/components/schemas/ErrorBody" ErrorBody: type: object additionalProperties: false required: - code - message properties: code: type: string description: Stable internal API error code. message: type: string description: Human-readable trusted error message. responses: InvalidRequestError: description: Request validation failed. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: invalidRequest: value: error: code: invalid_request message: request is invalid ForbiddenError: description: Caller is not an active member of the game or is otherwise not authorised. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: forbidden: value: error: code: forbidden message: caller is not authorised for this operation NotFoundError: description: The requested runtime, race, or engine version does not exist. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: runtimeNotFound: value: error: code: runtime_not_found message: runtime not found EngineVersionNotFoundError: description: The requested engine version is missing or has been deprecated. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: engineVersionNotFound: value: error: code: engine_version_not_found message: engine version not found EngineVersionInUseError: description: Hard delete attempt against a version referenced by a non-finished runtime. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: engineVersionInUse: value: error: code: engine_version_in_use message: engine version is referenced by a non-finished runtime ConflictError: description: The requested state transition is not allowed from the current status. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: conflict: value: error: code: conflict message: operation not allowed in current status runtimeNotRunning: value: error: code: runtime_not_running message: operation requires runtime status running semverPatchOnly: value: error: code: semver_patch_only message: patch attempt across major or minor boundary EngineUnreachableError: description: The engine container returned 5xx or could not be reached. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: engineUnreachable: value: error: code: engine_unreachable message: engine container is unreachable engineProtocolViolation: value: error: code: engine_protocol_violation message: engine response missing required fields or malformed engineValidationError: value: error: code: engine_validation_error message: engine rejected one or more commands ServiceUnavailableError: description: An upstream dependency (PostgreSQL, Redis, Lobby, RTM) is unavailable. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: unavailable: value: error: code: service_unavailable message: service is unavailable InternalError: description: Unexpected internal service error. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: internal: value: error: code: internal_error message: internal server error