// Package handlers serves the trusted internal REST surface of Game // Master frozen by `gamemaster/api/internal-openapi.yaml`. The package // owns one HandlerFunc per OpenAPI operation; route registration goes // through Register so the listener (`internal/api/internalhttp`) keeps // its lifecycle code separate from the per-operation logic. Handlers // delegate every business decision to the `internal/service/*` // packages and never decode engine-owned hot-path payloads. // // The pattern mirrors `rtmanager/internal/api/internalhttp/handlers` // so a reader familiar with one service can find their way around the // other. package handlers import ( "log/slog" "net/http" ) // Route paths frozen by `gamemaster/api/internal-openapi.yaml`. The // values match the operation IDs asserted in // `gamemaster/contract_openapi_test.go`; renaming any of them is a // contract change. const ( registerRuntimePath = "/api/v1/internal/games/{game_id}/register-runtime" banishRacePath = "/api/v1/internal/games/{game_id}/race/{race_name}/banish" invalidateMembershipsPath = "/api/v1/internal/games/{game_id}/memberships/invalidate" gameLivenessPath = "/api/v1/internal/games/{game_id}/liveness" listRuntimesPath = "/api/v1/internal/runtimes" getRuntimePath = "/api/v1/internal/runtimes/{game_id}" forceNextTurnPath = "/api/v1/internal/runtimes/{game_id}/force-next-turn" stopRuntimePath = "/api/v1/internal/runtimes/{game_id}/stop" patchRuntimePath = "/api/v1/internal/runtimes/{game_id}/patch" listEngineVersionsPath = "/api/v1/internal/engine-versions" createEngineVersionPath = "/api/v1/internal/engine-versions" engineVersionItemPath = "/api/v1/internal/engine-versions/{version}" resolveEngineVersionImageRefPath = "/api/v1/internal/engine-versions/{version}/image-ref" executeCommandsPath = "/api/v1/internal/games/{game_id}/commands" putOrdersPath = "/api/v1/internal/games/{game_id}/orders" getReportPath = "/api/v1/internal/games/{game_id}/reports/{turn}" ) // Dependencies bundles the collaborators required to serve the // gateway-, Lobby-, and Admin-facing internal REST surface. Any port // may be nil; in that case the routes that depend on it return // `500 internal_error` with the message «service is not wired». This // mirrors the rtmanager handlers' guard so partially-wired listener // tests do not crash on routes they do not exercise. type Dependencies struct { // Logger receives structured per-handler logs. nil falls back to // slog.Default. Logger *slog.Logger // RuntimeRecords backs the read-only list/get runtime endpoints. // Reads do not produce operation_log rows, mirroring // `rtmanager/docs/services.md` §18. RuntimeRecords RuntimeRecordsReader // RegisterRuntime is the orchestrator for the // `internalRegisterRuntime` operation. RegisterRuntime RegisterRuntimeService // ForceNextTurn drives the synchronous force-next-turn flow. ForceNextTurn ForceNextTurnService // StopRuntime drives the admin stop flow. StopRuntime StopRuntimeService // PatchRuntime drives the admin patch flow. PatchRuntime PatchRuntimeService // BanishRace drives the engine race-banish flow. BanishRace BanishRaceService // InvalidateMemberships purges the in-process membership cache for a // game id; backed by `service/membership.Cache.Invalidate`. InvalidateMemberships MembershipInvalidator // GameLiveness returns the current runtime status without // contacting the engine. GameLiveness LivenessService // EngineVersions exposes the multi-method engine-version registry // service (List/Get/ResolveImageRef/Create/Update/Deprecate). EngineVersions EngineVersionService // CommandExecute forwards a player command batch to the engine. CommandExecute CommandExecuteService // PutOrders forwards a player order batch to the engine. PutOrders OrderPutService // GetReport reads a per-player turn report from the engine. GetReport ReportGetService } // Register attaches every internal REST route to mux. The function is // idempotent against the listener-level probes (`/healthz`, // `/readyz`); the probe routes are owned by the listener and remain // disjoint from the paths registered here. func Register(mux *http.ServeMux, deps Dependencies) { mux.HandleFunc(http.MethodPost+" "+registerRuntimePath, newRegisterRuntimeHandler(deps)) mux.HandleFunc(http.MethodGet+" "+getRuntimePath, newGetRuntimeHandler(deps)) mux.HandleFunc(http.MethodGet+" "+listRuntimesPath, newListRuntimesHandler(deps)) mux.HandleFunc(http.MethodPost+" "+forceNextTurnPath, newForceNextTurnHandler(deps)) mux.HandleFunc(http.MethodPost+" "+stopRuntimePath, newStopRuntimeHandler(deps)) mux.HandleFunc(http.MethodPost+" "+patchRuntimePath, newPatchRuntimeHandler(deps)) mux.HandleFunc(http.MethodPost+" "+banishRacePath, newBanishRaceHandler(deps)) mux.HandleFunc(http.MethodPost+" "+invalidateMembershipsPath, newInvalidateMembershipsHandler(deps)) mux.HandleFunc(http.MethodGet+" "+gameLivenessPath, newGameLivenessHandler(deps)) mux.HandleFunc(http.MethodGet+" "+listEngineVersionsPath, newListEngineVersionsHandler(deps)) mux.HandleFunc(http.MethodPost+" "+createEngineVersionPath, newCreateEngineVersionHandler(deps)) mux.HandleFunc(http.MethodGet+" "+engineVersionItemPath, newGetEngineVersionHandler(deps)) mux.HandleFunc(http.MethodPatch+" "+engineVersionItemPath, newUpdateEngineVersionHandler(deps)) mux.HandleFunc(http.MethodDelete+" "+engineVersionItemPath, newDeprecateEngineVersionHandler(deps)) mux.HandleFunc(http.MethodGet+" "+resolveEngineVersionImageRefPath, newResolveEngineVersionImageRefHandler(deps)) mux.HandleFunc(http.MethodPost+" "+executeCommandsPath, newExecuteCommandsHandler(deps)) mux.HandleFunc(http.MethodPost+" "+putOrdersPath, newPutOrdersHandler(deps)) mux.HandleFunc(http.MethodGet+" "+getReportPath, newGetReportHandler(deps)) }