Admin & probes

HTTP endpoints share the WS port by default (single-port mode). Set adminPort on createServer to split them onto a private listener.

Routes

PathPurpose
GET /livezprocess alive — always 200
GET /readyz200 if the platform adapter is connected, else 503
GET /healthzJSON diagnostics: core / game version, math identity per mode, platform diagnostics. 503 if unhealthy
GET /admin/logsring buffer of recent log lines. Query ?level=&limit=
GET /admin/metricsPrometheus exposition (text format)
GET /admin/sessionsactive session list
GET /admin/manifestserialised GameManifest
GET /admin/modesclient-visible mode catalog (excludes internal: true)
POST /admin/autocloseexternal autoclose trigger: { sessionId, roundId?, reason }

Suffix matching

Every route is suffix-matched. That means /healthz, /api/healthz, and /api/<gameId>/healthz all hit the same handler. Works behind any ingress rewrite without configuration.

/healthz shape

{
  "status":        "ok" | "degraded",
  "core_version":  "0.3.0",
  "game_version":  "1.4.2",
  "game_id":       "hello-spin",
  "declared_rtp":  0.95,
  "modes": {
    "default": { "name": "hello", "version": "0.1.0", "kind": "simple",
                 "rtp": 0.95, "hash": "<sha256>" }
  },
  "platform": { "healthy": true, "diagnostics": { ... } },
  "uptime_s":   12345
}

POST /admin/autoclose

curl -X POST https://game.example.com/admin/autoclose \
  -H 'content-type: application/json' \
  -d '{ "sessionId": "abc-123", "reason": "ops-close" }'

# 200 OK
{ "closed": true, "roundId": "r_842" }

# 200 OK (no round to close)
{ "closed": false, "reason": "no_round_open" }

Autoclose is never timer-driven inside the RGS — every autoclose is initiated by an external signal (this endpoint, or a wallet autocloseRequested event). Keeps policy decisions where they belong: upstream.