feat(game): canonical gameId in POST /api/v1/admin/init
Tests · Go / test (push) Successful in 1m57s
Tests · Integration / integration (pull_request) Successful in 1m48s
Tests · Go / test (pull_request) Successful in 2m0s

Engine no longer mints its own game UUID. The orchestrator (backend)
generates the game UUID at game-create time and passes it in the
admin/init request body as the required `gameId` field, so the value
that names the engine container and host bind-mount directory also
ends up inside the engine's state.json.

The engine rejects the zero UUID with 400 and any init that conflicts
with an existing state.json with 409 (a second init on the same gameId
is also a conflict; full idempotency is not part of the contract).

Updates rest.InitRequest, openapi.yaml (schema + 409 response),
controller.GenerateGame/NewGame/buildGameOnMap signatures, the engine
HTTP handler/executor, the backend runtime worker, and the relevant
unit and contract tests. Documentation in game/README.md,
docs/ARCHITECTURE.md, backend/README.md, and backend/docs/{runtime,flows}.md
is updated in the same patch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ilia Denisov
2026-05-29 13:13:31 +02:00
parent 4a7bf0be61
commit 15d35f6f1f
20 changed files with 270 additions and 37 deletions
+44 -3
View File
@@ -69,8 +69,10 @@ paths:
operationId: adminInitGame
summary: Initialize a new game
description: |
Generates a new game instance with the supplied list of races.
Requires at least 10 race entries. Routed only from the trusted
Generates a new game instance with the supplied canonical
`gameId` and list of races. Requires at least 10 race entries.
The engine refuses to overwrite an already-initialised storage
directory (responds with 409). Routed only from the trusted
network segment that connects `Game Master` to the engine
container.
requestBody:
@@ -88,6 +90,8 @@ paths:
$ref: "#/components/schemas/StateResponse"
"400":
$ref: "#/components/responses/ValidationError"
"409":
$ref: "#/components/responses/ConflictError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/race/banish:
@@ -410,10 +414,23 @@ components:
description: True when the race has been eliminated or voluntarily quit.
InitRequest:
type: object
description: Initialization request specifying the race list for a new game.
description: |
Initialization request specifying the canonical game identity
and the race list for a new game. `gameId` is generated by the
orchestrator (the platform's backend) before the engine
container is launched; the engine rejects the zero UUID and
any value that conflicts with an existing `state.json` on
disk.
required:
- gameId
- races
properties:
gameId:
type: string
format: uuid
description: |
Canonical game identity supplied by the orchestrator. Must
be a non-zero UUID.
races:
type: array
description: List of participating races. Minimum 10 entries required.
@@ -1299,6 +1316,19 @@ components:
error:
type: string
description: Human-readable validation error message from the binding layer.
ConflictErrorResponse:
type: object
description: |
Conflict error returned when the engine refuses an operation
that would clash with persisted state. Today the only producer
is `POST /api/v1/admin/init` when the storage directory
already contains a game state.
required:
- error
properties:
error:
type: string
description: Human-readable conflict description.
InternalErrorResponse:
type: object
description: |
@@ -1326,6 +1356,17 @@ components:
validationError:
value:
error: "Key: 'InitRequest.Races' Error:Field validation for 'Races' failed on the 'gte' tag"
ConflictError:
description: |
The requested operation conflicts with persisted engine state.
content:
application/json:
schema:
$ref: "#/components/schemas/ConflictErrorResponse"
examples:
alreadyInitialized:
value:
error: "game init: game already initialized"
InternalError:
description: Internal Game Service error.
content: