{
    "openapi": "3.1.0",
    "info": {
        "title": "Uptime local API",
        "description": "Read-only view of a running Uptime simulation. Localhost only, no authentication, no mutating endpoints.\n\nEntity schemas here are the engine's own wire view types, not a REST-specific restatement of them, so they track the simulation exactly.\n\nEvery response carries a freshness envelope (`meta.age_ms`, plus `X-Sim-Age-Ms` / `X-Sim-Publish-Seq` headers): the data is a mirror, not a live read. The mirror is republished on a fixed cadence while anything is reading it, and publishing pauses once nothing has read it for about a minute, because building it costs the simulation thread real work. So a poll faster than that is always within one cadence, and the first read after an idle period may be older. Read again for a fresh one; no request is ever rejected or throttled.",
        "license": { "name": "" },
        "version": "0.1.47"
    },
    "paths": {
        "/api/v1/azs": {
            "get": {
                "tags": ["sites"],
                "operationId": "azs",
                "parameters": [
                    {
                        "name": "offset",
                        "in": "query",
                        "description": "Index of the first item to return. Clamped to the collection length,\nso an offset past the end is an empty page rather than an error.",
                        "required": false,
                        "schema": { "type": "integer", "minimum": 0 }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "description": "Maximum items to return. CLAMPED to 1000, not obeyed and not refused:\na late-campaign save has thousands of hosts and serializing all of them\nwould occupy the single tokio thread to produce a body nobody wants.",
                        "required": false,
                        "schema": { "type": "integer", "maximum": 1000, "minimum": 1 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "One page of results",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/Page_AzView" }
                            }
                        }
                    },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/azs/{id}": {
            "get": {
                "tags": ["sites"],
                "operationId": "az",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "Entity id",
                        "required": true,
                        "schema": { "type": "integer", "format": "int64", "minimum": 0 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The entity",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/AzView" }
                            }
                        }
                    },
                    "404": { "description": "No entity with that id in the current snapshot" },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/customers": {
            "get": {
                "tags": ["business"],
                "operationId": "customers",
                "parameters": [
                    {
                        "name": "offset",
                        "in": "query",
                        "description": "Index of the first item to return. Clamped to the collection length,\nso an offset past the end is an empty page rather than an error.",
                        "required": false,
                        "schema": { "type": "integer", "minimum": 0 }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "description": "Maximum items to return. CLAMPED to 1000, not obeyed and not refused:\na late-campaign save has thousands of hosts and serializing all of them\nwould occupy the single tokio thread to produce a body nobody wants.",
                        "required": false,
                        "schema": { "type": "integer", "maximum": 1000, "minimum": 1 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "One page of results",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/Page_CustomerView" }
                            }
                        }
                    },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/customers/{id}": {
            "get": {
                "tags": ["business"],
                "operationId": "customer",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "Entity id",
                        "required": true,
                        "schema": { "type": "integer", "format": "int64", "minimum": 0 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The entity",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/CustomerView" }
                            }
                        }
                    },
                    "404": { "description": "No entity with that id in the current snapshot" },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/diagnostics": {
            "get": {
                "tags": ["ops"],
                "summary": "One consistent read of everything needed to explain a sick world.",
                "description": "Fixed shape, and every unbounded list in it is capped with its true total\nreported alongside.",
                "operationId": "diagnostics",
                "responses": {
                    "200": {
                        "description": "Everything explaining the world's current health, in one consistent read",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/Diagnostics" }
                            }
                        }
                    },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/events": {
            "get": {
                "tags": ["ops"],
                "summary": "Recent engine events, newest-window, cursored by `at_ns`.",
                "description": "# TRAP, and why this endpoint does NOT touch `Request::DrainEvents`\n\nThe engine's `DrainEvents` request is DESTRUCTIVE and the Godot client is\nits consumer. An events endpoint built on it would silently steal events\nfrom the running game, and the symptom — a player's event feed\nintermittently missing entries whenever a dashboard was open — would be\nmaddening to trace back to an HTTP route.\n\nThis reads `WorldSnapshot::recent_events`, which `sim-project` fills from\n`engine.recent_events(120)`: a bounded READ of the tail, never a drain. The\nplan called for building a separate mirrored ring for this; that turned out\nto be unnecessary because the non-destructive window already exists and\n`at_ns` is already a monotonic cursor. Fewer moving parts, same guarantee.\n\nThe window is bounded, so a slow poller can miss events. That is reported\nvia `possible_gap` rather than hidden — see `EventsPage`.",
                "operationId": "events",
                "parameters": [
                    {
                        "name": "since_ns",
                        "in": "query",
                        "description": "Return only events strictly newer than this `at_ns`. Omit for the whole\nretained window.",
                        "required": false,
                        "schema": { "type": "integer", "format": "int64", "minimum": 0 }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "description": "Maximum events to return. Clamped to 1000.",
                        "required": false,
                        "schema": { "type": "integer", "minimum": 0 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Events newer than `since_ns`",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/EventsPage" }
                            }
                        }
                    },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/host-pools": {
            "get": {
                "tags": ["compute"],
                "operationId": "host_pools",
                "parameters": [
                    {
                        "name": "offset",
                        "in": "query",
                        "description": "Index of the first item to return. Clamped to the collection length,\nso an offset past the end is an empty page rather than an error.",
                        "required": false,
                        "schema": { "type": "integer", "minimum": 0 }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "description": "Maximum items to return. CLAMPED to 1000, not obeyed and not refused:\na late-campaign save has thousands of hosts and serializing all of them\nwould occupy the single tokio thread to produce a body nobody wants.",
                        "required": false,
                        "schema": { "type": "integer", "maximum": 1000, "minimum": 1 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "One page of results",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/Page_HostPoolView" }
                            }
                        }
                    },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/host-pools/{id}": {
            "get": {
                "tags": ["compute"],
                "operationId": "host_pool",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "Entity id",
                        "required": true,
                        "schema": { "type": "integer", "format": "int64", "minimum": 0 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The entity",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/HostPoolView" }
                            }
                        }
                    },
                    "404": { "description": "No entity with that id in the current snapshot" },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/hosts": {
            "get": {
                "tags": ["compute"],
                "operationId": "hosts",
                "parameters": [
                    {
                        "name": "offset",
                        "in": "query",
                        "description": "Index of the first item to return. Clamped to the collection length,\nso an offset past the end is an empty page rather than an error.",
                        "required": false,
                        "schema": { "type": "integer", "minimum": 0 }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "description": "Maximum items to return. CLAMPED to 1000, not obeyed and not refused:\na late-campaign save has thousands of hosts and serializing all of them\nwould occupy the single tokio thread to produce a body nobody wants.",
                        "required": false,
                        "schema": { "type": "integer", "maximum": 1000, "minimum": 1 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "One page of results",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/Page_HostView" }
                            }
                        }
                    },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/hosts/{id}": {
            "get": {
                "tags": ["compute"],
                "operationId": "host",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "Entity id",
                        "required": true,
                        "schema": { "type": "integer", "format": "int64", "minimum": 0 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The entity",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/HostView" }
                            }
                        }
                    },
                    "404": { "description": "No entity with that id in the current snapshot" },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/incidents": {
            "get": {
                "tags": ["ops"],
                "operationId": "incidents",
                "parameters": [
                    {
                        "name": "offset",
                        "in": "query",
                        "description": "Index of the first item to return. Clamped to the collection length,\nso an offset past the end is an empty page rather than an error.",
                        "required": false,
                        "schema": { "type": "integer", "minimum": 0 }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "description": "Maximum items to return. CLAMPED to 1000, not obeyed and not refused:\na late-campaign save has thousands of hosts and serializing all of them\nwould occupy the single tokio thread to produce a body nobody wants.",
                        "required": false,
                        "schema": { "type": "integer", "maximum": 1000, "minimum": 1 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "One page of results",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/Page_IncidentView" }
                            }
                        }
                    },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/incidents/resolved": {
            "get": {
                "tags": ["ops"],
                "summary": "Recently RESOLVED incidents.",
                "description": "A separate route rather than `?state=` on `/incidents` because the snapshot\ngenuinely carries two distinct collections (`active_incidents` and\n`recent_resolved_incidents`) with different retention rules. Collapsing\nthem behind a query parameter would imply a uniformity that does not exist.",
                "operationId": "resolved_incidents",
                "parameters": [
                    {
                        "name": "offset",
                        "in": "query",
                        "description": "Index of the first item to return. Clamped to the collection length,\nso an offset past the end is an empty page rather than an error.",
                        "required": false,
                        "schema": { "type": "integer", "minimum": 0 }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "description": "Maximum items to return. CLAMPED to 1000, not obeyed and not refused:\na late-campaign save has thousands of hosts and serializing all of them\nwould occupy the single tokio thread to produce a body nobody wants.",
                        "required": false,
                        "schema": { "type": "integer", "maximum": 1000, "minimum": 1 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Recently resolved incidents",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/Page_IncidentView" }
                            }
                        }
                    },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/incidents/{id}": {
            "get": {
                "tags": ["ops"],
                "operationId": "incident",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "Entity id",
                        "required": true,
                        "schema": { "type": "integer", "format": "int64", "minimum": 0 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The entity",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/IncidentView" }
                            }
                        }
                    },
                    "404": { "description": "No entity with that id in the current snapshot" },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/metrics/catalog": {
            "get": {
                "tags": ["ops"],
                "summary": "The metric declaration table as JSON.",
                "description": "Same `METRICS` const that produces the `# HELP` / `# TYPE` lines at scrape\ntime, so a consumer building a dashboard reads names, kinds and help text\nfrom the same source the exporter emits from. That is the whole point: a\nseparately-written metrics reference is the classic thing to leave stale.\n\nAnswers before a world exists, because the catalog describes what WILL be\nemitted and does not depend on any world state.",
                "operationId": "metrics_catalog",
                "responses": {
                    "200": {
                        "description": "Every metric this exporter declares",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "array",
                                    "items": { "$ref": "#/components/schemas/MetricDef" }
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/metrics/paths": {
            "get": {
                "tags": ["ops"],
                "summary": "Every metric path the registry currently knows about.",
                "description": "Self-maintaining: this enumerates the registry rather than naming metrics,\nso a new metric path appears here with no API change at all.",
                "operationId": "metric_paths",
                "responses": {
                    "200": {
                        "description": "Known metric paths",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/MetricPaths" }
                            }
                        }
                    },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/metrics/series": {
            "get": {
                "tags": ["ops"],
                "summary": "One decoded metric series.",
                "description": "The metric path is a QUERY parameter, not a path segment. Registry paths\nare hierarchical and contain `/` (`fleet/egress_gbps`), which a single path\nsegment cannot carry — the first cut of this used `/series/{path}` and\nmatched only the first segment. A wildcard route would work but would put\n`{*path}` in the generated spec, so the query parameter is both simpler and\ntruer to what the spec should say.",
                "operationId": "metric_series",
                "parameters": [
                    {
                        "name": "path",
                        "in": "query",
                        "description": "Metric path, exactly as listed by `/api/v1/metrics/paths`.",
                        "required": true,
                        "schema": { "type": "string" }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Decoded samples plus the scale that produced them",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/MetricSeries" }
                            }
                        }
                    },
                    "404": { "description": "No such metric path in the current capture" },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/network/appliances": {
            "get": {
                "tags": ["network"],
                "operationId": "appliances",
                "parameters": [
                    {
                        "name": "offset",
                        "in": "query",
                        "description": "Index of the first item to return. Clamped to the collection length,\nso an offset past the end is an empty page rather than an error.",
                        "required": false,
                        "schema": { "type": "integer", "minimum": 0 }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "description": "Maximum items to return. CLAMPED to 1000, not obeyed and not refused:\na late-campaign save has thousands of hosts and serializing all of them\nwould occupy the single tokio thread to produce a body nobody wants.",
                        "required": false,
                        "schema": { "type": "integer", "maximum": 1000, "minimum": 1 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "One page of results",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/Page_ApplianceView" }
                            }
                        }
                    },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/network/appliances/{id}": {
            "get": {
                "tags": ["network"],
                "operationId": "appliance",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "Entity id",
                        "required": true,
                        "schema": { "type": "integer", "format": "int64", "minimum": 0 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The entity",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/ApplianceView" }
                            }
                        }
                    },
                    "404": { "description": "No entity with that id in the current snapshot" },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/network/cables": {
            "get": {
                "tags": ["network"],
                "operationId": "cables",
                "parameters": [
                    {
                        "name": "offset",
                        "in": "query",
                        "description": "Index of the first item to return. Clamped to the collection length,\nso an offset past the end is an empty page rather than an error.",
                        "required": false,
                        "schema": { "type": "integer", "minimum": 0 }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "description": "Maximum items to return. CLAMPED to 1000, not obeyed and not refused:\na late-campaign save has thousands of hosts and serializing all of them\nwould occupy the single tokio thread to produce a body nobody wants.",
                        "required": false,
                        "schema": { "type": "integer", "maximum": 1000, "minimum": 1 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "One page of results",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/Page_CableView" }
                            }
                        }
                    },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/network/cables/{id}": {
            "get": {
                "tags": ["network"],
                "operationId": "cable",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "Entity id",
                        "required": true,
                        "schema": { "type": "integer", "format": "int64", "minimum": 0 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The entity",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/CableView" }
                            }
                        }
                    },
                    "404": { "description": "No entity with that id in the current snapshot" },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/network/gateways": {
            "get": {
                "tags": ["network"],
                "operationId": "gateways",
                "parameters": [
                    {
                        "name": "offset",
                        "in": "query",
                        "description": "Index of the first item to return. Clamped to the collection length,\nso an offset past the end is an empty page rather than an error.",
                        "required": false,
                        "schema": { "type": "integer", "minimum": 0 }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "description": "Maximum items to return. CLAMPED to 1000, not obeyed and not refused:\na late-campaign save has thousands of hosts and serializing all of them\nwould occupy the single tokio thread to produce a body nobody wants.",
                        "required": false,
                        "schema": { "type": "integer", "maximum": 1000, "minimum": 1 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "One page of results",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/Page_GatewayView" }
                            }
                        }
                    },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/network/gateways/{id}": {
            "get": {
                "tags": ["network"],
                "operationId": "gateway",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "Entity id",
                        "required": true,
                        "schema": { "type": "integer", "format": "int64", "minimum": 0 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The entity",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/GatewayView" }
                            }
                        }
                    },
                    "404": { "description": "No entity with that id in the current snapshot" },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/network/patch-panels": {
            "get": {
                "tags": ["network"],
                "operationId": "patch_panels",
                "parameters": [
                    {
                        "name": "offset",
                        "in": "query",
                        "description": "Index of the first item to return. Clamped to the collection length,\nso an offset past the end is an empty page rather than an error.",
                        "required": false,
                        "schema": { "type": "integer", "minimum": 0 }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "description": "Maximum items to return. CLAMPED to 1000, not obeyed and not refused:\na late-campaign save has thousands of hosts and serializing all of them\nwould occupy the single tokio thread to produce a body nobody wants.",
                        "required": false,
                        "schema": { "type": "integer", "maximum": 1000, "minimum": 1 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "One page of results",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/Page_PatchPanelView" }
                            }
                        }
                    },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/network/patch-panels/{id}": {
            "get": {
                "tags": ["network"],
                "operationId": "patch_panel",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "Entity id",
                        "required": true,
                        "schema": { "type": "integer", "format": "int64", "minimum": 0 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The entity",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/PatchPanelView" }
                            }
                        }
                    },
                    "404": { "description": "No entity with that id in the current snapshot" },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/network/segments": {
            "get": {
                "tags": ["network"],
                "operationId": "segments",
                "parameters": [
                    {
                        "name": "offset",
                        "in": "query",
                        "description": "Index of the first item to return. Clamped to the collection length,\nso an offset past the end is an empty page rather than an error.",
                        "required": false,
                        "schema": { "type": "integer", "minimum": 0 }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "description": "Maximum items to return. CLAMPED to 1000, not obeyed and not refused:\na late-campaign save has thousands of hosts and serializing all of them\nwould occupy the single tokio thread to produce a body nobody wants.",
                        "required": false,
                        "schema": { "type": "integer", "maximum": 1000, "minimum": 1 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "One page of results",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/Page_NetworkSegmentView" }
                            }
                        }
                    },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/network/segments/{id}": {
            "get": {
                "tags": ["network"],
                "operationId": "segment",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "Entity id",
                        "required": true,
                        "schema": { "type": "integer", "format": "int64", "minimum": 0 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The entity",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/NetworkSegmentView" }
                            }
                        }
                    },
                    "404": { "description": "No entity with that id in the current snapshot" },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/network/subnets": {
            "get": {
                "tags": ["network"],
                "operationId": "subnets",
                "parameters": [
                    {
                        "name": "offset",
                        "in": "query",
                        "description": "Index of the first item to return. Clamped to the collection length,\nso an offset past the end is an empty page rather than an error.",
                        "required": false,
                        "schema": { "type": "integer", "minimum": 0 }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "description": "Maximum items to return. CLAMPED to 1000, not obeyed and not refused:\na late-campaign save has thousands of hosts and serializing all of them\nwould occupy the single tokio thread to produce a body nobody wants.",
                        "required": false,
                        "schema": { "type": "integer", "maximum": 1000, "minimum": 1 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "One page of results",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/Page_SubnetView" }
                            }
                        }
                    },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/network/subnets/{id}": {
            "get": {
                "tags": ["network"],
                "operationId": "subnet",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "Entity id",
                        "required": true,
                        "schema": { "type": "integer", "format": "int64", "minimum": 0 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The entity",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/SubnetView" }
                            }
                        }
                    },
                    "404": { "description": "No entity with that id in the current snapshot" },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/network/switches": {
            "get": {
                "tags": ["network"],
                "operationId": "switches",
                "parameters": [
                    {
                        "name": "offset",
                        "in": "query",
                        "description": "Index of the first item to return. Clamped to the collection length,\nso an offset past the end is an empty page rather than an error.",
                        "required": false,
                        "schema": { "type": "integer", "minimum": 0 }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "description": "Maximum items to return. CLAMPED to 1000, not obeyed and not refused:\na late-campaign save has thousands of hosts and serializing all of them\nwould occupy the single tokio thread to produce a body nobody wants.",
                        "required": false,
                        "schema": { "type": "integer", "maximum": 1000, "minimum": 1 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "One page of results",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/Page_SwitchView" }
                            }
                        }
                    },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/network/switches/{id}": {
            "get": {
                "tags": ["network"],
                "operationId": "switch",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "Entity id",
                        "required": true,
                        "schema": { "type": "integer", "format": "int64", "minimum": 0 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The entity",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/SwitchView" }
                            }
                        }
                    },
                    "404": { "description": "No entity with that id in the current snapshot" },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/prospects": {
            "get": {
                "tags": ["business"],
                "operationId": "prospects",
                "parameters": [
                    {
                        "name": "offset",
                        "in": "query",
                        "description": "Index of the first item to return. Clamped to the collection length,\nso an offset past the end is an empty page rather than an error.",
                        "required": false,
                        "schema": { "type": "integer", "minimum": 0 }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "description": "Maximum items to return. CLAMPED to 1000, not obeyed and not refused:\na late-campaign save has thousands of hosts and serializing all of them\nwould occupy the single tokio thread to produce a body nobody wants.",
                        "required": false,
                        "schema": { "type": "integer", "maximum": 1000, "minimum": 1 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "One page of results",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/Page_ProspectView" }
                            }
                        }
                    },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/prospects/{id}": {
            "get": {
                "tags": ["business"],
                "operationId": "prospect",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "Entity id",
                        "required": true,
                        "schema": { "type": "integer", "format": "int64", "minimum": 0 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The entity",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/ProspectView" }
                            }
                        }
                    },
                    "404": { "description": "No entity with that id in the current snapshot" },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/quests": {
            "get": {
                "tags": ["business"],
                "operationId": "quests",
                "parameters": [
                    {
                        "name": "offset",
                        "in": "query",
                        "description": "Index of the first item to return. Clamped to the collection length,\nso an offset past the end is an empty page rather than an error.",
                        "required": false,
                        "schema": { "type": "integer", "minimum": 0 }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "description": "Maximum items to return. CLAMPED to 1000, not obeyed and not refused:\na late-campaign save has thousands of hosts and serializing all of them\nwould occupy the single tokio thread to produce a body nobody wants.",
                        "required": false,
                        "schema": { "type": "integer", "maximum": 1000, "minimum": 1 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "One page of results",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/Page_QuestView" }
                            }
                        }
                    },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/quests/{id}": {
            "get": {
                "tags": ["business"],
                "operationId": "quest",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "Entity id",
                        "required": true,
                        "schema": { "type": "integer", "format": "int64", "minimum": 0 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The entity",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/QuestView" }
                            }
                        }
                    },
                    "404": { "description": "No entity with that id in the current snapshot" },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/racks": {
            "get": {
                "tags": ["sites"],
                "operationId": "racks",
                "parameters": [
                    {
                        "name": "offset",
                        "in": "query",
                        "description": "Index of the first item to return. Clamped to the collection length,\nso an offset past the end is an empty page rather than an error.",
                        "required": false,
                        "schema": { "type": "integer", "minimum": 0 }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "description": "Maximum items to return. CLAMPED to 1000, not obeyed and not refused:\na late-campaign save has thousands of hosts and serializing all of them\nwould occupy the single tokio thread to produce a body nobody wants.",
                        "required": false,
                        "schema": { "type": "integer", "maximum": 1000, "minimum": 1 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "One page of results",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/Page_RackView" }
                            }
                        }
                    },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/racks/{id}": {
            "get": {
                "tags": ["sites"],
                "operationId": "rack",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "Entity id",
                        "required": true,
                        "schema": { "type": "integer", "format": "int64", "minimum": 0 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The entity",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/RackView" }
                            }
                        }
                    },
                    "404": { "description": "No entity with that id in the current snapshot" },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/regions": {
            "get": {
                "tags": ["sites"],
                "operationId": "regions",
                "parameters": [
                    {
                        "name": "offset",
                        "in": "query",
                        "description": "Index of the first item to return. Clamped to the collection length,\nso an offset past the end is an empty page rather than an error.",
                        "required": false,
                        "schema": { "type": "integer", "minimum": 0 }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "description": "Maximum items to return. CLAMPED to 1000, not obeyed and not refused:\na late-campaign save has thousands of hosts and serializing all of them\nwould occupy the single tokio thread to produce a body nobody wants.",
                        "required": false,
                        "schema": { "type": "integer", "maximum": 1000, "minimum": 1 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "One page of results",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/Page_RegionView" }
                            }
                        }
                    },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/regions/{id}": {
            "get": {
                "tags": ["sites"],
                "operationId": "region",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "Entity id",
                        "required": true,
                        "schema": { "type": "integer", "format": "int64", "minimum": 0 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The entity",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/RegionView" }
                            }
                        }
                    },
                    "404": { "description": "No entity with that id in the current snapshot" },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/services": {
            "get": {
                "tags": ["services"],
                "summary": "Service planes: the per-service-class control/data plane rollup.",
                "description": "List only. `ServicePlaneView` is keyed by `class_tag` rather than a numeric\nid, and inventing a synthetic id here to satisfy route symmetry would be a\nfiction the engine does not have.",
                "operationId": "services",
                "parameters": [
                    {
                        "name": "offset",
                        "in": "query",
                        "description": "Index of the first item to return. Clamped to the collection length,\nso an offset past the end is an empty page rather than an error.",
                        "required": false,
                        "schema": { "type": "integer", "minimum": 0 }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "description": "Maximum items to return. CLAMPED to 1000, not obeyed and not refused:\na late-campaign save has thousands of hosts and serializing all of them\nwould occupy the single tokio thread to produce a body nobody wants.",
                        "required": false,
                        "schema": { "type": "integer", "maximum": 1000, "minimum": 1 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Service planes",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/Page_ServicePlaneView" }
                            }
                        }
                    },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/staff": {
            "get": {
                "tags": ["business"],
                "operationId": "staff",
                "parameters": [
                    {
                        "name": "offset",
                        "in": "query",
                        "description": "Index of the first item to return. Clamped to the collection length,\nso an offset past the end is an empty page rather than an error.",
                        "required": false,
                        "schema": { "type": "integer", "minimum": 0 }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "description": "Maximum items to return. CLAMPED to 1000, not obeyed and not refused:\na late-campaign save has thousands of hosts and serializing all of them\nwould occupy the single tokio thread to produce a body nobody wants.",
                        "required": false,
                        "schema": { "type": "integer", "maximum": 1000, "minimum": 1 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "One page of results",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/Page_StaffView" }
                            }
                        }
                    },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/staff/{id}": {
            "get": {
                "tags": ["business"],
                "operationId": "staff_member",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "Entity id",
                        "required": true,
                        "schema": { "type": "integer", "format": "int64", "minimum": 0 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The entity",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/StaffView" }
                            }
                        }
                    },
                    "404": { "description": "No entity with that id in the current snapshot" },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/work-orders": {
            "get": {
                "tags": ["ops"],
                "operationId": "work_orders",
                "parameters": [
                    {
                        "name": "offset",
                        "in": "query",
                        "description": "Index of the first item to return. Clamped to the collection length,\nso an offset past the end is an empty page rather than an error.",
                        "required": false,
                        "schema": { "type": "integer", "minimum": 0 }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "description": "Maximum items to return. CLAMPED to 1000, not obeyed and not refused:\na late-campaign save has thousands of hosts and serializing all of them\nwould occupy the single tokio thread to produce a body nobody wants.",
                        "required": false,
                        "schema": { "type": "integer", "maximum": 1000, "minimum": 1 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "One page of results",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/Page_WorkOrderView" }
                            }
                        }
                    },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/work-orders/{id}": {
            "get": {
                "tags": ["ops"],
                "operationId": "work_order",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "Entity id",
                        "required": true,
                        "schema": { "type": "integer", "format": "int64", "minimum": 0 }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The entity",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/WorkOrderView" }
                            }
                        }
                    },
                    "404": { "description": "No entity with that id in the current snapshot" },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/api/v1/world": {
            "get": {
                "tags": ["world"],
                "summary": "World header: sim time, run status, and the topbar economy figures.",
                "description": "Note this returns `sim-wire`'s `GameStatusView` and `TopBarMetrics`\nverbatim rather than restating their fields, which is the whole point of\nthe 2026-08-06 schema decision.",
                "operationId": "world",
                "responses": {
                    "200": {
                        "description": "Current world header",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/WorldHeader" }
                            }
                        }
                    },
                    "503": { "description": "No world has been published yet" }
                }
            }
        },
        "/health": {
            "get": {
                "tags": ["ops"],
                "summary": "Liveness, plus how stale the mirror is.",
                "description": "The ONLY endpoint that answers before a world exists. It reports\n`world_ready: false` rather than 503, because the process really is up and\na monitoring system should be able to tell \"booting\" from \"dead\".",
                "operationId": "health",
                "responses": {
                    "200": {
                        "description": "Runner is up. `world_ready` is false until the first mirror publish.",
                        "content": {
                            "application/json": {
                                "schema": { "$ref": "#/components/schemas/Health" }
                            }
                        }
                    }
                }
            }
        },
        "/metrics": {
            "get": {
                "tags": ["ops"],
                "summary": "Prometheus text exposition.",
                "description": "Deliberately OUTSIDE `/api/v1`, alongside `/health`, because that is where\nevery scraper expects to find it and versioning a scrape target would break\nthe convention for no benefit.\n\nReturns 200 with an empty body before the first publish rather than 503: a\nscrape failure raises an alert, and \"the game has not started yet\" is not an\nincident.",
                "operationId": "metrics",
                "responses": {
                    "200": {
                        "description": "Prometheus text exposition (version 0.0.4)",
                        "content": { "text/plain": {} }
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "ActiveAttackView": {
                "type": "object",
                "description": "Fleet-wide live threat board (E5 — the security ops-lens reads this). One\nper snapshot. `active` is false when only the always-on ambient internet\nfloor is present; `per_class` is still populated so the lens shows the\nbaseline. Capnp-friendly: flat struct, `per_class` in fixed\n`AttackClass::all()` order.",
                "required": ["active", "summary", "components", "per_class"],
                "properties": {
                    "active": { "type": "boolean" },
                    "summary": { "type": "string" },
                    "components": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/AttackComponentView" }
                    },
                    "per_class": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/ThreatClassView" }
                    }
                }
            },
            "ApiMeta": {
                "type": "object",
                "description": "Freshness envelope. Present on every response body, and mirrored onto the\n`X-Sim-Age-Ms` / `X-Sim-Publish-Seq` headers so a scraper can read it\nwithout parsing the body.",
                "required": ["sim_time_ns", "sim_seconds", "publish_seq", "age_ms"],
                "properties": {
                    "sim_time_ns": {
                        "type": "integer",
                        "format": "int64",
                        "description": "Simulated time at the moment the mirror was published.",
                        "minimum": 0
                    },
                    "sim_seconds": {
                        "type": "number",
                        "format": "double",
                        "description": "`sim_time_ns` as seconds, for consumers that would otherwise divide."
                    },
                    "publish_seq": {
                        "type": "integer",
                        "format": "int64",
                        "description": "Monotonic publish counter, incremented once per cadence publish.",
                        "minimum": 0
                    },
                    "age_ms": {
                        "type": "integer",
                        "format": "int64",
                        "description": "Milliseconds between the mirror being published and this response\nbeing served. Expect roughly the configured cadence WHILE YOU KEEP\nREADING.\n\nA large value has three causes, not one, and they are worth telling\napart before chasing a fault:\n\n1. This is the first read after an idle period. Publishing stops when\n   nothing has read the mirror for a while, because producing it costs\n   the apply thread a full world projection, so an API nobody is using\n   should not be paid for. Read again and the next response is fresh.\n2. The apply thread is not reaching its publish point, which in\n   single-player means the client has gone quiet (see `server.rs`).\n3. The sim is genuinely slow. `apply_queue_depth` distinguishes this\n   from (2).",
                        "minimum": 0
                    }
                }
            },
            "ApplianceView": {
                "type": "object",
                "required": [
                    "id",
                    "rack_id",
                    "start_u",
                    "u_size",
                    "appliance_kind",
                    "sku_name",
                    "fabric_capacity_gbps",
                    "indicator"
                ],
                "properties": {
                    "id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "display_name": {
                        "type": "string",
                        "description": "Player-facing identity like `fw-01` / `ids-01` / `lb-01` (per-kind\nsequence). Empty on legacy saves whose appliances pre-date the\ntwo-voice naming pass; clients fall back to `kind` + `id`."
                    },
                    "display_name_plain": {
                        "type": "string",
                        "description": "Plain-mode counterpart: `firewall 1` / `intrusion detector 1`.\nSee `HostView::display_name_plain`."
                    },
                    "rack_id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "start_u": { "type": "integer", "format": "int32", "minimum": 0 },
                    "u_size": { "type": "integer", "format": "int32", "minimum": 0 },
                    "az_id": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Availability zone this appliance sits in. Host / Switch / Gateway all\ncarry one; the appliance corner did not, so nothing could scope the\nsecurity estate per AZ without re-deriving it from `rack_id`.\nResolved from the rack for a racked box, and from the HOST GATEWAY for\na `gateway_addon` (which has no rack of its own). 0 when neither\nresolves. Four-corner parity — `docs/SCHEMA.md` § Network entities.",
                        "minimum": 0
                    },
                    "inv_status": {
                        "type": "string",
                        "description": "Lifecycle status — see `HostView::inv_status`. Racked appliances are the\nonly ones in this array (carried / loose surface via `carried_items`),\nso in practice this reads `\"Mounted\"` or `\"SellQueued\"`; it exists so a\nclient can tell a box awaiting collection from a working one without a\nsecond lookup. Four-corner parity with Host / Switch / PatchPanel."
                    },
                    "appliance_kind": {
                        "type": "string",
                        "description": "Spec-aligned kind tag (`\"firewall\"` / `\"ids\"` / `\"waf\"` /\n`\"ddos_scrubber\"` / `\"vpn\"` / `\"loadbalancer\"` / `\"hsm\"` /\n`\"siem\"`). Drives icon + tone. Per `docs/SCHEMA.md` § Appliance."
                    },
                    "sku_name": { "type": "string" },
                    "gateway_addon": {
                        "type": "boolean",
                        "description": "True for gateway ADD-ONS (`ApplianceMount::GatewayAddon`) — license\nmodules living inside a gateway, not racked boxes. They have no\nrack position, no ports, and (since the addon-mitigation fold)\ntheir per-class work is attributed to the HOST GATEWAY's defender\nentry, so their own drop/event meters legitimately read 0.\nStandalone-appliance UI (the IDS performance board, SIEM presence,\nrack rendering) must skip these — the gateway console owns their\npresentation."
                    },
                    "fabric_capacity_gbps": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Nameplate forwarding ceiling — wire-rate throughput when the\ninspection plane is bypassed. Compared against `fabric_load_gbps`.",
                        "minimum": 0
                    },
                    "fabric_load_gbps": {
                        "type": "number",
                        "format": "double",
                        "description": "Smoothed full-duplex fabric load in Gbps (= `egress_gbps +\ningress_gbps`, independently smoothed). The saturation metric;\nwhen this exceeds `fabric_capacity_gbps` the forwarding plane\ndrops packets regardless of inspection state."
                    },
                    "fabric_saturated": {
                        "type": "boolean",
                        "description": "Forwarding-plane saturation edge: `fabric_load_gbps` over the appliance's\ninspection-REDUCED effective forwarding capacity (deep inspection eats\nforwarding headroom). Engine-computed in the appliance fabric pass for\nIN-PATH inspectors that actually forward; an in-path box in this state\ndrops customer traffic transiting it (facility-wide, folded into the same\ndrop path as switch/uplink saturation) and fires an\n`ApplianceFabricSaturated` event + incident on the rising edge — like\nSwitch/Gateway/Uplink. Off-path taps (HSM/SIEM) never forward, so this\nstays false for them."
                    },
                    "forwarding_ceiling_gbps": {
                        "type": "number",
                        "format": "double",
                        "description": "The appliance's effective FORWARDING ceiling with deep inspection's\npenalty applied — `fabric_load_gbps` is the load against it, and it IS\nthe threshold `fabric_saturated` flips on. Full `fabric_capacity_gbps`\nwhen inspection is bypassed/off/ceiling-at-or-above-fabric; collapses\ntoward `inspection_capacity_gbps` as inspection engages. Mirrors\n`GatewayView::forwarding_ceiling_gbps` (same shared curve,\n`sim_core::security::effective_forwarding_capacity_gbps`, same reason:\na client dividing `fabric_load_gbps` by the raw nameplate\n`fabric_capacity_gbps` disagreed with the engine's own saturation\nverdict). Single source of truth:\n`Appliance::effective_forwarding_capacity_gbps()`. Never re-derive it\nrenderer-side."
                    },
                    "egress_gbps": {
                        "type": "number",
                        "format": "double",
                        "description": "Smoothed directional egress in Gbps (traffic leaving the\nappliance toward the LAN side). Pairs with `ingress_gbps`.\nUntil the engine ships a directional split on appliance cables,\nthis is populated with the legacy half-duplex flow figure for\nbackward compat."
                    },
                    "ingress_gbps": {
                        "type": "number",
                        "format": "double",
                        "description": "Smoothed directional ingress in Gbps (traffic arriving from the\nWAN side). Mirror of `egress_gbps`."
                    },
                    "wan_ingress_gbps": { "type": "number", "format": "double" },
                    "wan_egress_gbps": { "type": "number", "format": "double" },
                    "lan_ingress_gbps": { "type": "number", "format": "double" },
                    "lan_egress_gbps": { "type": "number", "format": "double" },
                    "threats": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/ThreatMitigationView" },
                        "description": "Per-device threat handling — ONLY the classes THIS box defends, with\nwhat reached it, what it absorbed, and what it passed on. Replaces the\nconfusing fleet-wide read: a firewall shows Protocol only, an IDS shows\nIntrusion only. Empty for off-path boxes (HSM / SIEM). From the engine\npositional cascade."
                    },
                    "ports": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/PortView" },
                        "description": "Per-port views — one entry per physical port on the appliance.\nMirrors `SwitchView.ports` / `GatewayView.ports`. Closes the\nparity gap noted in `docs/SCHEMA.md` § ApplianceView. Each port\ncarries media, state, link capacity, live throughput, peer\nreference; the diegetic inspector + topology view both consume\nthis."
                    },
                    "inspection_capacity_gbps": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Rated DPI ceiling (Gbps). Typically a fraction of\n`fabric_capacity_gbps` — e.g. a 10 G appliance might inspect\nat 3 G. When `inspection_enabled` is true and live throughput\nexceeds this, the appliance either bypasses (drops to forwarding\nrate) or drops packets, depending on `inspection_state`.",
                        "minimum": 0
                    },
                    "inspection_load_gbps": {
                        "type": "number",
                        "format": "double",
                        "description": "Smoothed live throughput **actually deep-inspected**. Distinct\nfrom `egress_gbps` (total pass-through). When inspection is\nbypassed this is 0 even while pass-through is high."
                    },
                    "inspection_state": {
                        "type": "string",
                        "description": "`\"Active\"` (inspecting), `\"Bypass\"` (line-rate forwarding, no\ninspection), or `\"Failed\"` (engine forced drop — DPI overload).\nDrives the inspection-plane LED color on the appliance pill."
                    },
                    "inspection_saturated": {
                        "type": "boolean",
                        "description": "Edge-triggered: `inspection_load_gbps > inspection_capacity_gbps`.\nEven when fabric still has headroom, this fires when the DPI\nengine itself overloads."
                    },
                    "inspection_enabled": {
                        "type": "boolean",
                        "description": "Whether deep inspection is currently on (the bypass lever, toggled by\n`SetApplianceInspection`)."
                    },
                    "enabled_functions": {
                        "type": "array",
                        "items": { "type": "string" },
                        "description": "Enabled security functions on this box as slugs (\"firewall\"/\"ips\"/\n\"scrub\") — the base plus any licensed-on. Dynamic (changes only on a\nlicense toggle). The console joins these with the catalog's per-SKU\nlicense options (`ApplianceSkuView.licenses`) to render the toggle rows\n+ costs — the static option data is NOT duplicated per tick."
                    },
                    "owned_functions": {
                        "type": "array",
                        "items": { "type": "string" },
                        "description": "Function-license slugs whose one-time capex has already been paid on this\nbox (the ownership ledger — superset of `enabled_functions`). An owned\nlicense that's been unsubscribed (not in `enabled_functions`) can be\nre-enabled for FREE; the console reads this to show `Resubscribe (free)`\nvs `Buy $X`."
                    },
                    "dropped_per_sec": {
                        "type": "number",
                        "format": "double",
                        "description": "Live filtering throughput, latest ring sample. ONE FIELD PER ATTACK\nCLASS, in that class's own unit — never combined, and a dual-licensed\n(Scrub + Firewall) box populates more than one:\n- `dropped_per_sec` — stateful-firewall Protocol drops, **Kpps**\n- `scrubbed_gbps` — scrubber Volumetric absorb, **Gbps**\n- `events_per_sec` — IDS/IPS Intrusion detections, **events/s**\n\nA box without the matching licence enabled reports 0 for that class.\nOff-path kinds (HSM/SIEM) report 0 for all three. Abstract magnitudes\n(spec §9.1)."
                    },
                    "events_per_sec": { "type": "number", "format": "double" },
                    "scrubbed_gbps": { "type": "number", "format": "double" },
                    "indicator": { "$ref": "#/components/schemas/IndicatorLed" },
                    "monthly_opex_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Live projected monthly opex ($/mo) — running fee + chassis power +\nseated-optic draw. Mirrors the host console's read."
                    },
                    "powered": {
                        "type": "boolean",
                        "description": "At least one AC cord seated (or none required). False = dark\nuntil the player plugs it in. Mirrors the engine's `powered`."
                    },
                    "power_feeds": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Connected AC cord count. >= 2 = PSU-failure resilience at\n`redundant_psu_overhead_w` extra draw per extra cord.",
                        "minimum": 0
                    }
                }
            },
            "AttackComponentView": {
                "type": "object",
                "description": "One vector of the current composed attack.",
                "required": ["vector", "magnitude", "unit"],
                "properties": {
                    "vector": {
                        "type": "string",
                        "description": "Eng-voice vector name (e.g. \"SYN flood\")."
                    },
                    "magnitude": { "type": "number", "format": "double" },
                    "unit": {
                        "type": "string",
                        "description": "Display unit for the class (\"Gbps\"/\"Kpps\"/\"rps\"/\"events/s\")."
                    }
                }
            },
            "AzView": {
                "type": "object",
                "required": ["id", "region_id", "name", "status", "rack_count", "host_count"],
                "properties": {
                    "id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "region_id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "name": { "type": "string" },
                    "status": { "$ref": "#/components/schemas/AzVisual" },
                    "rack_count": { "type": "integer", "format": "int32", "minimum": 0 },
                    "host_count": { "type": "integer", "format": "int32", "minimum": 0 },
                    "draped_cable_count": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Cables touching residents of this AZ that have no managed routing\npath. The Engineering View / prospect tour penalty surfaces this.",
                        "minimum": 0
                    }
                }
            },
            "AzVisual": { "type": "string", "enum": ["Healthy", "Outage", "Degraded"] },
            "BondRequestView": {
                "type": "object",
                "required": [
                    "id",
                    "cable_ids",
                    "owner_pair_label",
                    "posted_at_ns",
                    "expires_at_ns",
                    "resolution"
                ],
                "properties": {
                    "id": { "type": "integer", "format": "int64", "minimum": 0 },
                    "cable_ids": {
                        "type": "array",
                        "items": { "type": "integer", "format": "int32", "minimum": 0 },
                        "description": "Cable ids the bond would cover. 2+ at creation; the renderer\ncan highlight these cables in the ask card."
                    },
                    "owner_pair_label": {
                        "type": "string",
                        "description": "Pre-rendered owner pair label, e.g. `\"sw-01 → sw-02\"`."
                    },
                    "posted_at_ns": { "type": "integer", "format": "int64", "minimum": 0 },
                    "expires_at_ns": { "type": "integer", "format": "int64", "minimum": 0 },
                    "resolution": {
                        "type": "string",
                        "description": "\"\" while open; \"Approved\" / \"Refused\" / \"Lapsed\" once resolved."
                    }
                }
            },
            "CableEndpointView": {
                "oneOf": [
                    {
                        "type": "object",
                        "required": ["HostNic"],
                        "properties": {
                            "HostNic": {
                                "type": "object",
                                "required": ["host_id", "port"],
                                "properties": {
                                    "host_id": {
                                        "type": "integer",
                                        "format": "int32",
                                        "minimum": 0
                                    },
                                    "port": { "type": "integer", "format": "int32", "minimum": 0 }
                                }
                            }
                        }
                    },
                    {
                        "type": "object",
                        "required": ["SwitchPort"],
                        "properties": {
                            "SwitchPort": {
                                "type": "object",
                                "required": ["switch_id", "port"],
                                "properties": {
                                    "switch_id": {
                                        "type": "integer",
                                        "format": "int32",
                                        "minimum": 0
                                    },
                                    "port": { "type": "integer", "format": "int32", "minimum": 0 }
                                }
                            }
                        }
                    },
                    {
                        "type": "object",
                        "required": ["EddPort"],
                        "properties": {
                            "EddPort": {
                                "type": "object",
                                "required": ["edd_id", "port"],
                                "properties": {
                                    "edd_id": {
                                        "type": "integer",
                                        "format": "int32",
                                        "minimum": 0
                                    },
                                    "port": { "type": "integer", "format": "int32", "minimum": 0 }
                                }
                            }
                        }
                    },
                    {
                        "type": "object",
                        "required": ["EdgeGatewayPort"],
                        "properties": {
                            "EdgeGatewayPort": {
                                "type": "object",
                                "required": ["gateway_id", "port"],
                                "properties": {
                                    "gateway_id": {
                                        "type": "integer",
                                        "format": "int32",
                                        "minimum": 0
                                    },
                                    "port": { "type": "integer", "format": "int32", "minimum": 0 }
                                }
                            }
                        }
                    },
                    {
                        "type": "object",
                        "required": ["AppliancePort"],
                        "properties": {
                            "AppliancePort": {
                                "type": "object",
                                "required": ["appliance_id", "port"],
                                "properties": {
                                    "appliance_id": {
                                        "type": "integer",
                                        "format": "int32",
                                        "minimum": 0
                                    },
                                    "port": { "type": "integer", "format": "int32", "minimum": 0 }
                                }
                            }
                        }
                    },
                    {
                        "type": "object",
                        "required": ["PatchPanelPort"],
                        "properties": {
                            "PatchPanelPort": {
                                "type": "object",
                                "required": ["panel_id", "port"],
                                "properties": {
                                    "panel_id": {
                                        "type": "integer",
                                        "format": "int32",
                                        "minimum": 0
                                    },
                                    "port": { "type": "integer", "format": "int32", "minimum": 0 }
                                }
                            }
                        }
                    },
                    {
                        "type": "object",
                        "description": "Rack-side end of an IEC power cord — the implied feed the lead\ndisappears into down the left manager strip. `feed` is the\nper-rack cord ordinal.",
                        "required": ["RackPower"],
                        "properties": {
                            "RackPower": {
                                "type": "object",
                                "description": "Rack-side end of an IEC power cord — the implied feed the lead\ndisappears into down the left manager strip. `feed` is the\nper-rack cord ordinal.",
                                "required": ["rack_id", "feed"],
                                "properties": {
                                    "rack_id": {
                                        "type": "integer",
                                        "format": "int32",
                                        "minimum": 0
                                    },
                                    "feed": { "type": "integer", "format": "int32", "minimum": 0 }
                                }
                            }
                        }
                    }
                ],
                "description": "Tagged endpoint reference used by the generalized cable model. Mirrors\n`sim_core::cable::CableEndpoint` but with bare ids (no newtypes) so the\nJSON shape stays simple for the frontend / FFI layer."
            },
            "CableLane": {
                "type": "string",
                "description": "A bundling lane on a rack manager strip. Cables sharing a `(side,\nu_band, lane)` triple stack along the lane in slot order. Power /\ncopper / twinax / fibre each occupy their own lane so the renderer\npaints them as visually distinct bundles, matching real datacenter\ncable-management practice.\n\nEngine-assigned at lay time from `CableKind::lane()` — never picked\nby the player.",
                "enum": ["Power", "Copper", "Twinax", "Fibre"]
            },
            "CableView": {
                "type": "object",
                "required": ["from_host", "to_switch", "kind", "length_m"],
                "properties": {
                    "from_host": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Legacy host↔switch fields. Populated for host-link cables for\nback-compat with the pre-Phase-B renderer. New cable types\n(switch↔edd, future player-laid) leave these as 0.",
                        "minimum": 0
                    },
                    "to_switch": { "type": "integer", "format": "int32", "minimum": 0 },
                    "kind": { "type": "string" },
                    "dac_class": {
                        "type": "string",
                        "description": "Twinax class (\"Passive\" / \"Active\"). Only meaningful for DAC kinds;\nsurfaced so resuming a dangling active-DAC cable client-side sees\nthe right 15m reach (instead of the 3m passive baseline)."
                    },
                    "color": {
                        "type": "string",
                        "description": "Player-chosen jacket colour id (\"Orange\" / \"Blue\" / …). Always a\nconcrete colour — the projection resolves the cable's stored choice\nor its family default — so the renderer paints off it directly\n(`Tokens.cable_color_for_choice`). Empty only on legacy wire frames."
                    },
                    "duplex": {
                        "type": "boolean",
                        "description": "True for LC-duplex fibre patch cords (TX/RX strand pair). The\nrenderer draws two parallel strands; everything else is one tube.\nRead directly off the cable view — no catalog/timing dependency.\n\n**Meaning CHANGED in PROTOCOL 48 (UG-207).** This is now purely\n`CableKind::is_duplex()`, the property of the cable itself. It used to\nbe additionally forced false whenever EITHER end sat in a QSFP cage,\nwhich made an SFP-to-QSFP fibre run render single-tube at BOTH ends —\nincluding the SFP end, which genuinely has two LC ferrules. Cage\ngeometry now travels in [`Self::qsfp_end`] instead."
                    },
                    "qsfp_end": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Which END sits in a QSFP (multi-lane) cage: `0` neither, `1` = a,\n`2` = b, `3` = both. A QSFP cage presents one `qsfp_module.glb`\nconnector with a single boot, so the duplex strand pair converges\nthere — the same 2-into-1 the renderer already does at a fibre patch\npanel. `3` means converge at both ends, i.e. render as one tube, which\nis correct for an MPO trunk between two QSFP cages.\n\nZero is \"neither\" deliberately: `CableView` derives [`Default`] and the\nfield is `#[serde(default)]`, so a legacy frame and a default-constructed\nview both land on the value that reproduces pre-48 rendering. A `-1`\nsentinel would have defaulted to `0` and silently claimed end \"a\".\n\nAdded in PROTOCOL 48 (UG-207).",
                        "minimum": 0
                    },
                    "length_m": { "type": "integer", "format": "int32", "minimum": 0 },
                    "id": {
                        "type": ["integer", "null"],
                        "format": "int32",
                        "description": "Generalized endpoint metadata (Phase B+). Populated for *all* cables\ngoing forward, including host-links. Renderers should prefer reading\n`a` / `b` over the legacy `from_host` / `to_switch` fields.",
                        "minimum": 0
                    },
                    "a": {
                        "oneOf": [
                            { "type": "null" },
                            { "$ref": "#/components/schemas/CableEndpointView" }
                        ]
                    },
                    "b": {
                        "oneOf": [
                            { "type": "null" },
                            { "$ref": "#/components/schemas/CableEndpointView" }
                        ]
                    },
                    "lag_id": {
                        "type": ["integer", "null"],
                        "format": "int32",
                        "description": "LAG (link aggregation) membership. Cables sharing this id form a\nbonded link. `None` = standalone cable.",
                        "minimum": 0
                    },
                    "routed": {
                        "type": "boolean",
                        "description": "`true` when this inter-switch link runs L3/Routed (ECMP) vs L2/\nBridged (STP). Authoritative owner is `Cable.link_mode`. Lets the\nswitch console show + toggle each link's forwarding mode."
                    },
                    "mode_pinned": {
                        "type": "boolean",
                        "description": "`true` when the player pinned this link's mode in the console (so\nit's not auto-managed). `false` = Auto. With `routed` this gives the\nconsole the tri-state Auto / L2 / L3."
                    },
                    "breakout_id": {
                        "type": ["integer", "null"],
                        "format": "int32",
                        "description": "Breakout membership. Legs sharing this id fan out from one QSFP\ntrunk port — the renderer draws a single trunk run that splits to\neach leg's far port. `None` = ordinary point-to-point cable.\n(Tiny `Option<u32>` mirror of `lag_id`; delta/capnp treat it like\nany other scalar — no per-tick allocation.)",
                        "minimum": 0
                    },
                    "routed_via": {
                        "type": "array",
                        "items": { "type": "integer", "format": "int32", "minimum": 0 },
                        "description": "Tray segments / hooks / risers this cable runs through, in order\nfrom `a` to `b`. Empty = draped (no managed path)."
                    },
                    "path": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/RouteAnchor" },
                        "description": "Full ordered anchor sequence the cable runs through, port A →\nport B — `sim_core::cable::RouteAnchor`, THE one anchor\nvocabulary (client-send, engine-store, engine-emit; see the\n2026-07-27 RouteAnchor/WaypointView unification). First and\nlast anchor are always `Port`. Middle anchors describe in-rack\nmanagement (`Strip`), rack-edge transitions (`Edge`),\nside-panel rail holes (`RailHole`), and inter-rack runs\n(`Tray`). This is already the FULL resolved route — the engine\nexpands strip runs into their marker/channel sequence\n(`expand_strip_chains_rack`) before emitting, so the renderer\nonly ever draws between consecutive anchors, never re-derives\nwhich ones to visit. When this is shorter than 3 entries (just\nthe two endpoint ports), the renderer treats the cable as\nfreely draped — same as the legacy `routed_via.is_empty()` case."
                    },
                    "route_slots": {
                        "type": "array",
                        "items": { "type": "integer", "format": "int32", "minimum": 0 },
                        "description": "Per-segment bundle slot — same length as `routed_via`. Each entry\nis this cable's slot index (0-based) inside the corresponding tray\nsegment, computed deterministically by sorting cable ids that share\nthe segment. Renderer combines slot + tray cross-section + layout\npattern into a lateral offset so cables don't visually overlap."
                    },
                    "routed_points": {
                        "type": "array",
                        "items": {
                            "type": "array",
                            "items": { "type": "number", "format": "float" }
                        },
                        "description": "Raw world-space waypoints (from `RouteAnchor::Point`) — scene\nauthored tray runs the cable threads through. Spliced into `path`\nas `RouteAnchor::Point`."
                    },
                    "is_draped": {
                        "type": "boolean",
                        "description": "True iff `routed_via` is empty — convenience for renderers that\nwant to draw draped cables differently without inspecting the list."
                    },
                    "is_failed": {
                        "type": "boolean",
                        "description": "True iff this cable is currently in a transient failed state\n(`Cable::is_failed(now)` — gates traffic engine-side). Renderer\ncolours failed cables red."
                    },
                    "routing_side_a": {
                        "type": "string",
                        "description": "Player-chosen cable management path for each end — \"LeftMgr\" /\n\"RightMgr\" / \"VertRiser\" / \"RackTop\" per `cable::RoutingSide`.\nEach end of the cable runs through its own rack's manager\nstrip, so the two can differ. Defaults to \"RightMgr\"."
                    },
                    "routing_side_b": { "type": "string" },
                    "dangling_end": {
                        "type": ["string", "null"],
                        "description": "`Some(\"Source\")` or `Some(\"Target\")` when one end of this\ncable has been yanked out of its terminating port. The cable\nstays in the world; the renderer paints the loose tip."
                    }
                }
            },
            "CandidateCauseView": {
                "type": "object",
                "description": "One ranked candidate cause on an incident: the specific fault hypothesis\n(the failing hop the forwarding graph names), how confident the engine is,\nwhether it's confirmed, and its own per-cause guidance + codex links.",
                "required": [
                    "root_kind",
                    "entity_id",
                    "rack_id",
                    "confidence",
                    "confirmed",
                    "articles"
                ],
                "properties": {
                    "root_kind": {
                        "type": "string",
                        "description": "Variant tag mirroring `IncidentRoot` — \"CableFailed\", \"SwitchTripped\"."
                    },
                    "entity_id": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Primary entity id from the cause (host/cable/switch id). 0 for global.",
                        "minimum": 0
                    },
                    "rack_id": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Rack the cause's beacon should mark; 0 when nothing is racked.",
                        "minimum": 0
                    },
                    "msg_key": {
                        "type": "string",
                        "description": "Localization key for the cause label — `incident-<root_kind-kebab>`,\nthe same key space as `IncidentView::root_msg_key` (a candidate cause\nIS a root hypothesis). Empty until keyed; render `label*` then."
                    },
                    "msg_args": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/MsgArgView" },
                        "description": "Facts for `msg_key`."
                    },
                    "confidence": {
                        "type": "integer",
                        "format": "int32",
                        "description": "0..=100 confidence this is the real cause.",
                        "minimum": 0
                    },
                    "confirmed": {
                        "type": "boolean",
                        "description": "Player- or engine-confirmed as the real cause."
                    },
                    "cleared": {
                        "type": "boolean",
                        "description": "This cause's fault has since cleared, but the symptom is still open —\na tenant's rolling p99 keeps reading high after the cause is gone. The\ncandidate is retained (not deleted) so a pinned cause is never silently\nretracted; the client renders it muted as \"cleared\", offers no repair,\nand does not fall back to \"cause not yet identified\"."
                    },
                    "articles": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/IncidentArticleView" },
                        "description": "Codex articles for this cause's mechanic."
                    }
                }
            },
            "CapacityView": {
                "type": "object",
                "description": "Capacity + burn-rate projection. Drives the dashboard's Capacity tab.\n`monthly_burn_dollars` is the *current operating rate*, not an average\nover history — it answers \"if nothing changes, what does this month\ncost?\" `months_until_cash_out` uses (burn − revenue) so a profitable\nop shows runway as `f64::INFINITY` (renderer formats it as \"∞\" / \"—\").",
                "required": [
                    "free_rack_u",
                    "used_rack_u",
                    "total_rack_u",
                    "free_power_w",
                    "free_public_ips",
                    "free_uplink_capacity_gbps",
                    "monthly_burn_dollars",
                    "monthly_revenue_dollars",
                    "months_until_cash_out"
                ],
                "properties": {
                    "free_rack_u": { "type": "integer", "format": "int32", "minimum": 0 },
                    "used_rack_u": { "type": "integer", "format": "int32", "minimum": 0 },
                    "total_rack_u": { "type": "integer", "format": "int32", "minimum": 0 },
                    "free_power_w": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Power headroom in watts (cooling capacity − heat load). Clamped\nat 0 if heat exceeds cooling (over-budget facility).",
                        "minimum": 0
                    },
                    "free_public_ips": { "type": "integer", "format": "int32", "minimum": 0 },
                    "free_uplink_capacity_gbps": { "type": "number", "format": "double" },
                    "largest_free_vcpu_milli": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Largest free single-host slot across serviceable hosts (after failover\nheadroom) — the biggest VM that could actually be seated right now.\nThe Capacity lens shows this vs the requested spec so a\n`FleetCapacityExhausted` shortfall reads as \"fragmented, add a host\".",
                        "minimum": 0
                    },
                    "largest_free_mem_mb": { "type": "integer", "format": "int32", "minimum": 0 },
                    "largest_free_ssd_gb": { "type": "integer", "format": "int32", "minimum": 0 },
                    "unplaceable_unit_count": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Total contracted CAPACITY the fleet cannot seat right now, summed\nacross every customer AND every service class they buy — VMs, database\ncopies, cluster workers, CDN POPs, LB proxies, object-store copies,\nfunction capacity.\n\nWas `unplaceable_vm_count`, and counted `first_vms_demand()` and nothing\nelse until 2026-08-10, so a tenant whose load balancer, CDN, bucket or\nKubernetes cluster could not place contributed 0 and the Capacity lens\nread \"all placeable\" while they were getting nothing.\n\nThe MEANING was fixed then and the NAME was deferred, because\n`PROTOCOL_VERSION` 42 had shipped and renaming a `#[serde(default)]`\nfield makes every already-built client silently read 0 — a wrong number\nturned into a missing warning, which is worse. That deferral was paid off\nat `PROTOCOL_VERSION` 47 along with the rest of the `vm_`-named generic\nfields, in one bump rather than field by field.\n\n\"Cannot be seated\" means the probe's reason is a capacity/feasibility\none (`Engine::probe_reason_blocks_placement`). Capacity that is merely\nwaiting for the next ensure pass, and units whose hosts are down, are\ndeliberately NOT counted — this number answers \"must I change the\nfleet\", not \"is anything unhappy\".",
                        "minimum": 0
                    },
                    "total_vcpu_milli": {
                        "type": "integer",
                        "format": "int64",
                        "description": "Fleet compute + storage — what you actually HAVE, so the prospect card\ncan show \"free vs. needed\" in absolute terms (not just an after-%).\n`free_*` already nets out VM usage **and** service-plane overhead.\nu64 because at hyperscale these exceed u32. (P1 — `tasks/service_model.md`.)",
                        "minimum": 0
                    },
                    "free_vcpu_milli": { "type": "integer", "format": "int64", "minimum": 0 },
                    "total_mem_mb": { "type": "integer", "format": "int64", "minimum": 0 },
                    "free_mem_mb": { "type": "integer", "format": "int64", "minimum": 0 },
                    "total_ssd_gb": { "type": "integer", "format": "int64", "minimum": 0 },
                    "free_ssd_gb": { "type": "integer", "format": "int64", "minimum": 0 },
                    "service_reserved_vcpu_milli": {
                        "type": "integer",
                        "format": "int64",
                        "description": "Compute the service plane (control planes + agents) is holding — the\noverhead of running your services, surfaced for transparency.",
                        "minimum": 0
                    },
                    "service_reserved_mem_mb": {
                        "type": "integer",
                        "format": "int64",
                        "minimum": 0
                    },
                    "total_vgpu_slices": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Fleet vGPU partition capacity across every GPU host (`Host::total_vgpu_\nslices` / `free_vgpu_slices`). 0 on a fleet with no GPU hardware. Lets\nthe prospect card show a GPU meter and answer \"can I serve a GPU tenant\"\nin absolute slices, not just an after-%.",
                        "minimum": 0
                    },
                    "free_vgpu_slices": { "type": "integer", "format": "int32", "minimum": 0 },
                    "largest_free_vgpu_slices": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Largest free vGPU partition on any SINGLE serviceable host — the biggest\nslice ask the fleet could seat right now.\n\n`free_vgpu_slices` is a fleet SUM and is therefore blind to\nfragmentation: twelve hosts holding 2 free slices each report \"24 free\"\nwhile a 4-slice VM cannot place anywhere. Every other resource on this\nview already publishes a `largest_free_*` beside its total for exactly\nthis reason; vGPU did not, which is why GPU capacity read as available\nwhen it was not. Show BOTH — the sum is what you own, this is what you\ncan actually hand to one VM.",
                        "minimum": 0
                    },
                    "total_gpu_mem_mb": {
                        "type": "integer",
                        "format": "int64",
                        "description": "Fleet GPU frame-buffer capacity (MB) across serviceable GPU hosts, and\nthe largest free block on any single host.\n\nFrame buffer is the SECOND vGPU bound and frequently the BINDING one: a\n32 GB/VM tenant on a `gpu_2x_2u` (8 slices, 48 GB) seats ONE VM, not the\ntwo its slice count implies. Until 2026-07-29 it was not surfaced at\nall, so the constraint that actually refused the placement was invisible\nto the player.",
                        "minimum": 0
                    },
                    "free_gpu_mem_mb": { "type": "integer", "format": "int64", "minimum": 0 },
                    "largest_free_gpu_mem_mb": {
                        "type": "integer",
                        "format": "int32",
                        "minimum": 0
                    },
                    "monthly_burn_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Projected monthly cost at the current operating rate."
                    },
                    "monthly_revenue_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Projected monthly revenue at the current operating rate (last\nhour × `tuning.hours_per_month` — the SAME month `monthly_burn_dollars`\nis denominated in; it used to be a hard-coded `24 × 30`). Computed from\nrecurring billing only — one-time signup bonuses and prepay credits are\nexcluded so accepting a prospect doesn't briefly extrapolate into a\nphantom monthly."
                    },
                    "months_until_cash_out": {
                        "type": "number",
                        "format": "double",
                        "description": "`cash / max(0, burn − revenue)`, with a 1e9-month sentinel (never\n`f64::INFINITY` — serde encodes that as JSON null) when revenue covers\nburn. A renderer must label that sentinel by the SIGN of the surplus:\nrevenue EXCEEDING cost is profitable, not break-even.\n\nThe `burn` in that denominator is the CASH burn —\n`monthly_burn_dollars` less [`CostRatesView::depreciation_dollars`] —\nNOT `monthly_burn_dollars` itself. Depreciation is a real operating cost\nbut it never moves the balance (`CostKind::settles_in_cash()` is false),\nso charging it against the runway makes a fleet that owns its hardware\nlook closer to broke than it is. The P&L figure and the cash figure are\ndeliberately two numbers; do not re-derive this one from the other."
                    },
                    "cash_minute": {
                        "type": "array",
                        "items": { "type": "integer", "format": "int32", "minimum": 0 },
                        "description": "Cash balance sampled once per simulated minute. 60-deep ring →\n\"last hour\" sparkline on the Money lens. Log-encoded via\n`q_gbps_log` (unit-agnostic — mantissa × 10^exp)."
                    },
                    "revenue_minute": {
                        "type": "array",
                        "items": { "type": "integer", "format": "int32", "minimum": 0 },
                        "description": "Revenue *delta* per simulated minute (charged this minute).\nSame encoding + cap as `cash_minute`."
                    },
                    "cost_minute": {
                        "type": "array",
                        "items": { "type": "integer", "format": "int32", "minimum": 0 },
                        "description": "Cost delta per simulated minute. Pairs with `revenue_minute` for\nthe Money lens net-flow trend."
                    }
                }
            },
            "ClientHealthView": {
                "type": "object",
                "description": "MP Phase 2.6 — per-player connection-health entry.",
                "required": ["player_id", "lag_ticks", "egress_bytes_per_sec"],
                "properties": {
                    "player_id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "rtt_ms": {
                        "type": ["integer", "null"],
                        "format": "int32",
                        "description": "Round-trip time in milliseconds, EWMA-smoothed (alpha 0.2). `None`\nuntil the first ack resolves a stamped push, or for sessions that\nnever ack push frames (pull-only clients — RTT has no meaning there).",
                        "minimum": 0
                    },
                    "lag_ticks": {
                        "type": "integer",
                        "format": "int32",
                        "description": "How many push-clock ticks this session's last ack trails the engine\nhead by. Mirrors the push clock's own lag computation\n(`PushClock::lagging_session` / `MAX_LAG_TICKS`).",
                        "minimum": 0
                    },
                    "egress_bytes_per_sec": {
                        "type": "integer",
                        "format": "int64",
                        "description": "This session's own outbound rate (instantaneous, no smoothing —\nonly the host total is EWMA'd per the design spec).",
                        "minimum": 0
                    },
                    "persona": {
                        "type": "string",
                        "description": "MP identity v2 — sanitized display name this session declared in its\nHello (`PlayerIdentityWire`). Empty = never declared; renderers fall\nback to \"Player N\". Keyframe-only like the rest of this view (serde,\nno capnp), so names self-heal for late joiners on every keyframe.\n(Single-byte cosmetics ride the presence stream instead — live.)"
                    },
                    "pushes_skipped": {
                        "type": "integer",
                        "format": "int64",
                        "description": "How many world frames the server has DECLINED TO BUILD for this session\nbecause it had not yet finished writing the previous one, cumulative for\nthe session.\n\nPROTOCOL 54 — RENAMED FROM `pushes_dropped`, AND THE MEANING CHANGED.\nIt used to count frames that were BUILT and then thrown away when the\nwriter queue was full, and the recovery for that was a forced full\nkeyframe — so a drop was expensive and alarming. Both the drop and the\nforced keyframe are gone: a frame is no longer built unless it can be\ndelivered (see `WorldMailbox` in sim-runner), and a skipped frame costs\nnothing, because this session's ack does not move and its next delta\nsimply spans a wider window.\n\nThe rename is not cosmetic. The old field could no longer be non-zero\nonce its only writer was deleted, which left the renderer's health\nwarning reading a counter frozen at 0. A field whose meaning moves\nsilently is worse than one that disappears, so this one moved loudly.\n\nIt remains the clearest signal that a link cannot carry what the world\nis producing — `rtt_ms` conflates distance with congestion (a healthy\n120 ms player in another country is fine) and `lag_ticks` ticks up for a\nclient that is merely busy for a moment — but READ IT MORE GENTLY THAN\nITS PREDECESSOR. Skips are the mechanism by which a poor link is\nabsorbed rather than a symptom of failure: a steady low rate means a\nclient running at less than full frame rate and perfectly healthy. Only\na HIGH sustained rate means the link cannot keep up.\n\nRenderers use the RATE of change, not the absolute. Keyframe-only serde\nlike the rest of this view — no capnp, no protocol bump.",
                        "minimum": 0
                    },
                    "steam_id": {
                        "type": "string",
                        "description": "The player's Steam id (0 = unknown). Published so every client can\nresolve the AUTHORITATIVE Steam persona for a player rather than trusting\nthe name that player declared about themselves — see\n`remote_players.display_name`. Also what makes a seat legible in\ndiagnostics. Keyframe-only serde, no capnp, no protocol bump.\n\nSafe to share within a session: co-op is friends-only and every member is\nalready visible to every other in the Steam lobby. Kept out of LOGS\nregardless (see `coop_relay.rs`'s PII notes).\n**Carried as a STRING, and it must stay one.** A SteamID64 is ~1.1e17,\nfar past f64's 2^53 exact-integer limit — and `variant_ser` renders\nevery number as a float (JSON number semantics, see its module doc). So\nas a `u64` this field reached GDScript already rounded: 76561198012345678\narrives as ...680. That is a DIFFERENT, existing account, which\n`member_persona` then resolves happily — so a player's nameplate showed a\nCOMPLETE STRANGER's Steam persona rather than failing visibly.\n\nA string is exact, and GDScript's `int()` parses it losslessly (its ints\nare 64-bit signed). Empty = never declared. Same trap the top-level\n`sim_time_ns` / `structure_rev` re-set in `apply_full_snapshot` exists\nfor; this one is nested inside `connection_health`, where that fix-up\ncould not reach it."
                    }
                }
            },
            "ConnectionHealthView": {
                "type": "object",
                "description": "MP Phase 2.6 — host-wide connection-health snapshot. Built by\n`sim-runner` (never by `sim-render`'s own projection — this is\ntransport-layer telemetry, not simulation state) and attached to\nkeyframes only, in `--server` mode only.",
                "required": ["host_egress_bytes_per_sec", "clients"],
                "properties": {
                    "host_egress_bytes_per_sec": {
                        "type": "integer",
                        "format": "int64",
                        "description": "Host's total outbound rate across every live connection, smoothed\nwith an EWMA (alpha 0.2) so the UI doesn't flicker tick to tick.",
                        "minimum": 0
                    },
                    "tick_delivery_rate": {
                        "type": ["number", "null"],
                        "format": "float",
                        "description": "TICK HEALTH: the fraction of its intended speed the shared world is\nactually running at, in `[0.0, 1.0]`. `1.0` is on schedule; `0.5` means\nevery player's world is advancing at half rate. `None` until the first\nadvance, and on any runner that is not driving a push clock.\n\nTHE ONLY FIELD HERE THAT IS ABOUT THE HOST RATHER THAN A LINK, and that\nis why it exists. `rtt_ms` conflates distance with congestion, and\n`lag_ticks` / `pushes_skipped` describe one client's link — none of them\ncan say \"the server itself is behind\", which presents to every player at\nonce as a world running slow. The push clock never holds for a slow\nclient, so a low value here is never a link and always the host."
                    },
                    "clients": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/ClientHealthView" },
                        "description": "One entry per currently-connected player (host included when it has\na loopback/local session tracked the same way as remote clients)."
                    }
                }
            },
            "CostBreakdownView": {
                "type": "object",
                "description": "Lifetime spend ledger projected from `Engine::cost_breakdown`. All\nfields are running totals in dollars.\n\n# A purchase is not a cost, so this view describes TWO quantities\n\nThe asset ledger (cost-authority C3) took capital purchases out of\n`Engine::total_cost`: `acquire_asset` books capex to `Engine::total_capex`\nplus an asset row and never touches `total_cost`, and what a purchase\ncontributes to the P&L is the `CostKind::Depreciation` charged off that\nledger every billing tick.\n\nSo the old invariant — \"a naive sum over these fields equals\n`topbar.total_cost_dollars`\" — is **false and must not be reintroduced.**\nThe sum is `total_cost + total_capex - total_asset_disposal`. Which term a\nbucket lands in is decided once, in `CostBreakdown::split`, and the three\nresults ride this view as `total_recurring_dollars` /\n`total_capex_dollars` / `total_asset_disposal_dollars`. A UI that wants\n\"what have I spent operating the business\" reads `total_recurring_dollars`;\none that wants \"what have I sunk into hardware\" reads\n`total_capex_dollars`. Adding them together is a category error.\n\n**Backward-looking only.** What next month COSTS is `CostRatesView`, a\nseparate struct on purpose: deriving a rate from accumulated spend is\nexactly the bug that made facility rent — a config constant — drift on\nscreen tick to tick.\n\nOne field per `CostBreakdown` bucket, no exceptions. `build_cost_breakdown`\ndestructures the engine struct exhaustively, so a new bucket is a compile\nerror until it lands here too.",
                "required": [
                    "host_capex_dollars",
                    "switch_capex_dollars",
                    "appliance_capex_dollars",
                    "patch_panel_capex_dollars",
                    "rack_capex_dollars",
                    "cable_capex_dollars",
                    "region_capex_dollars",
                    "az_capex_dollars",
                    "peering_capex_dollars",
                    "facility_upgrade_capex_dollars",
                    "staff_signing_dollars",
                    "host_hourly_dollars",
                    "switch_hourly_dollars",
                    "power_bill_dollars",
                    "facility_rent_dollars",
                    "uplink_monthly_dollars",
                    "cable_opex_dollars",
                    "staff_salary_dollars",
                    "egress_cost_dollars",
                    "service_debt_dollars",
                    "sla_credits_dollars"
                ],
                "properties": {
                    "host_capex_dollars": { "type": "number", "format": "double" },
                    "switch_capex_dollars": { "type": "number", "format": "double" },
                    "appliance_capex_dollars": { "type": "number", "format": "double" },
                    "patch_panel_capex_dollars": { "type": "number", "format": "double" },
                    "edd_capex_dollars": { "type": "number", "format": "double" },
                    "gateway_capex_dollars": { "type": "number", "format": "double" },
                    "rack_capex_dollars": { "type": "number", "format": "double" },
                    "cable_capex_dollars": { "type": "number", "format": "double" },
                    "region_capex_dollars": { "type": "number", "format": "double" },
                    "az_capex_dollars": { "type": "number", "format": "double" },
                    "peering_capex_dollars": { "type": "number", "format": "double" },
                    "facility_upgrade_capex_dollars": { "type": "number", "format": "double" },
                    "staff_signing_dollars": { "type": "number", "format": "double" },
                    "component_capex_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Spares / replacement components ordered from the vendor."
                    },
                    "uplink_activation_capex_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Carrier activation NRC on a new or re-provisioned WAN circuit —\none-off, distinct from the circuit's recurring `uplink_monthly`."
                    },
                    "service_unlock_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "One-time cost to light up a locked service class."
                    },
                    "staff_severance_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Severance paid when firing staff. Separate from `staff_signing` — a\nhiring bonus and a redundancy payout are opposite events."
                    },
                    "host_hourly_dollars": { "type": "number", "format": "double" },
                    "switch_hourly_dollars": { "type": "number", "format": "double" },
                    "power_bill_dollars": { "type": "number", "format": "double" },
                    "facility_rent_dollars": { "type": "number", "format": "double" },
                    "uplink_monthly_dollars": { "type": "number", "format": "double" },
                    "cable_opex_dollars": { "type": "number", "format": "double" },
                    "peering_monthly_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Monthly opex on established peering relationships."
                    },
                    "staff_salary_dollars": { "type": "number", "format": "double" },
                    "egress_cost_dollars": { "type": "number", "format": "double" },
                    "service_debt_dollars": { "type": "number", "format": "double" },
                    "churn_penalty_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Penalty for dropping a customer."
                    },
                    "sla_credits_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "SLA availability credits + latency penalties settled back to tenants.\n\nReads the engine's `cost_breakdown.sla_credits` BUCKET. It used to be\nsynthesised by summing live `CustomerState::sla_credits`, which lost\nevery credit a churned tenant took with them when they left."
                    },
                    "eviction_fee_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Break fee the operator pays to evict a tenant."
                    },
                    "asset_disposal_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Proceeds from selling / refunding / liquidating owned hardware, held\nNEGATIVE because it is the reversal of a capital conversion, not a cost.\n`total_asset_disposal_dollars` carries the same figure POSITIVE, which\nis the one to render; do not \"fix\" the sign here — see\n`CostBreakdown::asset_disposal`."
                    },
                    "total_recurring_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "`Engine::total_cost` — lifetime spend on what the business CONSUMED,\nand the exact figure `topbar.total_cost_dollars` carries.\n\n**Excludes hardware purchases.** A player who just bought $25k of hosts\nsees no movement here; they see it in `total_capex_dollars`, and they\nsee its P&L consequence arrive slowly as depreciation."
                    },
                    "total_capex_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "`Engine::total_capex` — lifetime CAPITAL spend, cash converted into\nowned things. Equals the sum of the capex buckets above (`host_capex`\n… `uplink_activation_capex`), and NOT of the whole view: the \"CapEx\n(one-time)\" section also holds `staff_signing` / `staff_severance` /\n`service_unlock`, which are one-time but buy no asset and so stay in\n`total_recurring_dollars`."
                    },
                    "total_asset_disposal_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "`Engine::total_asset_disposal` — lifetime proceeds from selling,\nrefunding or liquidating owned hardware, as a POSITIVE figure. Net\ncapital outlay is `total_capex_dollars - total_asset_disposal_dollars`."
                    },
                    "total_depreciation_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "`Engine::total_depreciation` — lifetime depreciation charged off the\nasset ledger. **A memo figure: it is already inside\n`total_recurring_dollars` and already inside the opex buckets it was\ncharged to** (`depreciation_bucket` routes host/component/module to\n`host_hourly`, network gear to `switch_hourly`, rack/region/AZ/facility\nto `facility_rent`, cable/breakout to `cable_opex`, uplink and peering\nto their monthlies). Adding it to anything double-counts. It is on the\nwire so the Finance tab can answer \"what does owning this fleet cost me\nin a month where I buy nothing\" — the question capex-as-a-cost used to\nhide."
                    }
                }
            },
            "CostRatesView": {
                "type": "object",
                "description": "What the business costs to run for one more month, per category, at TODAY's\nstate. The forward-looking twin of `CostBreakdownView`.\n\nProjected straight from `Engine::monthly_cost_rates`, which prices one\nmonth's metered quantities through the same `rate_cost` the charge pass\nuses — so a rate here equals what the engine will actually bill, scalers\n(opex lever, macro power/egress events) included. The renderer does NO\ncost arithmetic of its own.\n\nThe `avg_*` fields are the exception, and are named to say so: those\ncategories are DISCRETE EVENTS with no rate (a penalty is not a monthly\nbill), so they carry a trailing average over `elapsed_months` instead.\nA UI that shows them must label them as estimates.",
                "required": [
                    "host_hourly_dollars",
                    "switch_hourly_dollars",
                    "power_bill_dollars",
                    "facility_rent_dollars",
                    "uplink_monthly_dollars",
                    "cable_opex_dollars",
                    "peering_monthly_dollars",
                    "staff_salary_dollars",
                    "service_debt_dollars",
                    "total_dollars",
                    "elapsed_months",
                    "avg_egress_cost_dollars",
                    "avg_sla_credits_dollars",
                    "avg_churn_penalty_dollars",
                    "avg_eviction_fee_dollars"
                ],
                "properties": {
                    "host_hourly_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Host SKU running fees PLUS the site's vendor-support contract, matching\nwhat the `host_hourly` bucket accumulates."
                    },
                    "switch_hourly_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Switch + gateway + EDD SKU running fees."
                    },
                    "power_bill_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Electricity on MEASURED draw at the site's price."
                    },
                    "facility_rent_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Site rent. A CONSTANT for a given site — if this moves between ticks\nwhile the site has not changed, something is deriving it again."
                    },
                    "uplink_monthly_dollars": { "type": "number", "format": "double" },
                    "cable_opex_dollars": { "type": "number", "format": "double" },
                    "peering_monthly_dollars": { "type": "number", "format": "double" },
                    "staff_salary_dollars": { "type": "number", "format": "double" },
                    "service_debt_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Amortised loan repayment, clamped to the outstanding balance."
                    },
                    "transit_egress_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Upstream transit bought for tenants' projected egress, per month.\n\nAdded in PROTOCOL 51. Before it, this view had NO transit term while\n`CostAttribution::total` had one, so the Finance tab's burn and the\ncustomer-pane cost total answered \"what does a month cost\" with two\ndifferent numbers, differing by exactly this figure. The backward-looking\ntwin `CostBreakdownView` has carried `egress_cost_dollars` all along;\nonly the forward-looking side was missing it.\n\nRead from the SAME `CostAttribution` walk that produces the per-tenant\negress margin, never re-derived, so the two cannot drift."
                    },
                    "total_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Sum of the real rates above — identical to\n`CapacityView::monthly_burn_dollars`, which reads this same projection.\n\nIncludes `transit_egress_dollars` since PROTOCOL 51, so the headline\nburn rose by that term. It was under-reporting before, not over-."
                    },
                    "depreciation_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "What owning the fleet costs per month in straight-line decline —\n`CostKind::Depreciation` over the ledger's active capex.\n\n**Already inside the rates above and inside `total_dollars`.** The\nengine folds each asset class's depreciation into the running-cost\ncategory that already describes it (`depreciation_bucket`), so this is\nthe \"of which\" figure, not a thirteenth row. Sum it with the others and\nyou count hardware decline twice.\n\nWhy it ships at all: since capex left `total_cost`, this is the ONLY\nnumber that says buying a $25k rack of hosts has an ongoing price. A\nmonth in which the player buys nothing still carries it."
                    },
                    "elapsed_months": {
                        "type": "number",
                        "format": "double",
                        "description": "Sim months elapsed since the run started — the averaging window for\nevery `avg_*` field. Zero on a fresh run, in which case the averages\nare all zero and a UI should show \"—\" rather than a number."
                    },
                    "avg_egress_cost_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Upstream transit bought, averaged. Real per-tenant spend, but it varies\nwith traffic, so there is no forward RATE to quote."
                    },
                    "avg_sla_credits_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "SLA credits settled back to tenants, averaged."
                    },
                    "avg_churn_penalty_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Customer-drop penalties, averaged."
                    },
                    "avg_eviction_fee_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Tenant eviction break fees, averaged."
                    }
                }
            },
            "CustomerFilterCounts": {
                "type": "object",
                "description": "Filter-pill counts paired with `CustomersSummary`. One field per\n`_CUST_FILTER_*` id on the client. Engine semantics owned here — if\nthe filter logic changes (e.g. TOP becomes \"top 10\" instead of\n\"top 5\"), this is the one place to update.",
                "required": ["everyone", "at_risk", "top", "prospects"],
                "properties": {
                    "everyone": {
                        "type": "integer",
                        "format": "int32",
                        "description": "`_CUST_FILTER_EVERYONE` — `customers.len()`.",
                        "minimum": 0
                    },
                    "at_risk": {
                        "type": "integer",
                        "format": "int32",
                        "description": "`_CUST_FILTER_AT_RISK` — same as `CustomersSummary.at_risk`,\nsurfaced again for symmetry so all four pill counts read from\none struct.",
                        "minimum": 0
                    },
                    "top": {
                        "type": "integer",
                        "format": "int32",
                        "description": "`_CUST_FILTER_TOP` — `min(5, customers.len())`. The \"top 5 by\nrecent revenue\" list-cap mirrors the GDScript `_cust_filter_count`.",
                        "minimum": 0
                    },
                    "prospects": {
                        "type": "integer",
                        "format": "int32",
                        "description": "`_CUST_FILTER_PROSPECTS` — `prospects.len()`.",
                        "minimum": 0
                    }
                }
            },
            "CustomerRequestView": {
                "type": "object",
                "description": "A pending customer request (P4 in `tasks/consequences.md`) — the\ngeneralized \"a customer wants something, your answer has consequences\"\nsurface. Resolved via `PlayerCommand::ResolveCustomerRequest`.",
                "required": [
                    "id",
                    "customer_id",
                    "customer_name",
                    "kind",
                    "summary",
                    "accept_label",
                    "decline_label",
                    "opened_at_ns",
                    "expires_at_ns",
                    "resolution"
                ],
                "properties": {
                    "id": { "type": "integer", "format": "int64", "minimum": 0 },
                    "customer_id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "customer_name": { "type": "string" },
                    "kind": {
                        "type": "string",
                        "description": "Request kind tag: \"CapacitySurge\" / \"PoachThreat\" / \"CostConcession\" /\n\"ComplianceRequest\". Drives the card icon/colour."
                    },
                    "summary": {
                        "type": "string",
                        "description": "Pre-rendered, player-facing one-liner (\"we've got an event next week,\nexpecting ~10x traffic — can you stand up 4 more VMs?\")."
                    },
                    "accept_label": {
                        "type": "string",
                        "description": "Plain-words label for the accept action (\"Provision for it\" / \"Match\nthe offer\" / \"Grant discount\" / \"Support it\")."
                    },
                    "decline_label": {
                        "type": "string",
                        "description": "Plain-words label for the decline action."
                    },
                    "opened_at_ns": { "type": "integer", "format": "int64", "minimum": 0 },
                    "expires_at_ns": { "type": "integer", "format": "int64", "minimum": 0 },
                    "resolution": {
                        "type": "string",
                        "description": "\"\" while open; \"Accepted\" / \"Declined\" / \"Lapsed\" once resolved."
                    }
                }
            },
            "CustomerView": {
                "type": "object",
                "required": [
                    "id",
                    "name",
                    "archetype",
                    "tier",
                    "kind_label",
                    "lifecycle",
                    "health",
                    "block_quality",
                    "revenue_total_dollars",
                    "revenue_last_hour_dollars",
                    "availability_pct",
                    "cpu_load_pct",
                    "latency_top_term"
                ],
                "properties": {
                    "id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "name": { "type": "string" },
                    "archetype": { "type": "string" },
                    "tier": { "type": "string" },
                    "kind_label": { "type": "string" },
                    "lifecycle": { "type": "string" },
                    "pending_until_ns": {
                        "type": ["integer", "null"],
                        "format": "int64",
                        "minimum": 0
                    },
                    "health": { "type": "number", "format": "double" },
                    "block_quality": { "type": "number", "format": "double" },
                    "revenue_total_dollars": { "type": "number", "format": "double" },
                    "revenue_last_hour_dollars": { "type": "number", "format": "double" },
                    "availability_pct": { "type": "number", "format": "double" },
                    "p99_latency_ms": {
                        "type": ["number", "null"],
                        "format": "double",
                        "description": "p99 request latency over the LONG window (`CustomerState::latency`,\n`tuning.latency_window_capacity` samples at one per sim second, so\nroughly the worst 10 samples of the last ~16.7 sim minutes). This is the\nfigure the SLA is measured against — `sla_p99_budget_ms` compares to\nTHIS, and the engine's breach/credit path reads the same window — so it\nis deliberately sticky and must never be relabelled \"now\". Pair it with\n`p99_recent_ms` for the responsive figure."
                    },
                    "p99_recent_ms": {
                        "type": ["number", "null"],
                        "format": "double",
                        "description": "p99 request latency over the SHORT window (`CustomerState::latency_recent`,\n`tuning.latency_recent_capacity` samples, ~2 sim minutes) — cached by the\nengine once per metrics tick as `CustomerState::recent_p99_ms`, which is\nalso what the incident anomaly machine opens and recovers on.\n\nThis is the \"right now\" number. The long window above pins one spike for\nup to ~16 sim minutes, which read to players as \"customers returning to\nnormal latency taking ages\" while the incident had already closed. Both\nship so the readout can be honest about which is which: current vs the\nwindow the SLA is actually scored on.\n\n`None` until the first metrics tick after load (`load_reported` clears\nthe cached value because it describes a window that no longer exists).\nWhole milliseconds — the engine caches it as `u64` ms and re-deriving it\nhere would mean a clone+sort of the window per customer per snapshot."
                    },
                    "cpu_load_pct": {
                        "type": "number",
                        "format": "float",
                        "description": "Offered CPU as a percentage of what this tenant CONTRACTED, uncapped, so\nit reads past 100 when they are asking for more than they rent.\n\nThis is the number that explains a latency spike on hosts that look idle.\nA VM queues on its OWN vCPU ceiling, and the queue curve bites from\n`queue_util_knee` (70%) upward: 25 ms at 95%, 168 ms at 99%, ~1990 ms at\nthe clamp. Host utilisation is a DIFFERENT quantity and does not move\nwith it, which is why a player checking the host map after a p99 spike\nfinds nothing wrong (player report, Mateo Bank 2026-07-28). 0 when the\ntenant has no placed VMs."
                    },
                    "latency_top_term": {
                        "type": "string",
                        "description": "Pre-rendered dominant latency term, e.g. \"queue 940.2 ms (94%)\", or empty\nwhen nothing has been sampled yet.\n\nThe engine already attributes latency across serve floor / fabric /\nqueue / CPU contention / attack / backbone / hairpin / cold start and\nranks them, but that breakdown only ever reached the INCIDENT card. The\ncustomer panel is where a player asks \"why is this tenant slow\", so the\nleading term belongs here too. Formatted render-side because the client\nhas no business re-deriving shares."
                    },
                    "p50_latency_ms": {
                        "type": ["number", "null"],
                        "format": "double",
                        "description": "Median (p50) request latency in milliseconds — the \"typical\"\nnumber players read as \"how's this customer doing\", distinct from\nthe p99 tail. Float, so lightly-loaded customers don't all collapse\nto the same whole-ms value. `None` until the window has samples."
                    },
                    "rps": {
                        "type": "number",
                        "format": "double",
                        "description": "Smoothed requests per second across this customer's workloads.\nMirrors `CustomerState.rps`. Drives the inspector's \"Acme Co.\n— 4 req/s\" plain-English read."
                    },
                    "failed_requests_lifetime": {
                        "type": "integer",
                        "format": "int64",
                        "description": "Cumulative failed-request count across the customer's services\n(`AvailabilityStats.requests_failed`).",
                        "minimum": 0
                    },
                    "total_requests_lifetime": {
                        "type": "integer",
                        "format": "int64",
                        "description": "Total request count across the same window. Pairs with\n`failed_requests_lifetime` to compute the recent failure rate.",
                        "minimum": 0
                    },
                    "failed_last_hour": {
                        "type": "integer",
                        "format": "int64",
                        "description": "Failed requests since the last hourly snapshot — the delta the\ninspector reads as \"12 errors this hour.\" Reset by the engine\neach hour boundary (`failed_at_hour_snapshot`).",
                        "minimum": 0
                    },
                    "sla_p99_budget_ms": {
                        "type": ["integer", "null"],
                        "format": "int64",
                        "description": "Latency-budget tier — milliseconds the customer's SLA tolerates\nat p99. `None` for Bronze (no latency budget).",
                        "minimum": 0
                    },
                    "in_latency_spike": {
                        "type": "boolean",
                        "description": "True when the customer is currently in a sustained latency\nspike (engine edge-trigger). Drives an SLA-breach pill in the\ninspector."
                    },
                    "on_probation": {
                        "type": "boolean",
                        "description": "True when the customer is on probation — engine flag for\n\"they're seriously considering churn.\" The renderer can show\nthis with an at-risk pill."
                    },
                    "sla_credits_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "SLA-breach credits accumulated this billing period (issued back\nto the customer; net negative for the provider). Lifetime sum."
                    },
                    "egress_gbps": {
                        "type": "number",
                        "format": "double",
                        "description": "Smoothed bandwidth attributed to this customer across their\nservice mix. Egress = response bodies / GET fan-out / etc.;\ningress = request bodies / PUT uploads / replication writes.\nReal workloads are asymmetric (image hosting = egress-heavy;\nbackup ingest = ingress-heavy) and surfacing both reflects\nthat."
                    },
                    "ingress_gbps": { "type": "number", "format": "double" },
                    "service_kinds": {
                        "type": "array",
                        "items": { "type": "string" },
                        "description": "Short-code service mix: subset of {\"fn\", \"vm\", \"obj\", \"lb\", \"db\",\n\"cdn\", \"k8s\"} for the services this customer runs. Used by the\ndashboard's Services drilldown to count tenants per service kind\nand by the customer wall to render service-kind chips."
                    },
                    "stranded_unit_count": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Stranded VMs this customer owns — pinned to an unreachable\nhost with no auto-failover. Drives the red \"N VMs stranded\"\nbadge on the ops-console customer row and the comms-panel\nincident card's `[Migrate]` quick-action.",
                        "minimum": 0
                    },
                    "unit_count": {
                        "type": "integer",
                        "format": "int32",
                        "description": "VM placement spread — the anti-affinity story made legible.\n`vm_count` total VMs this customer runs; `vm_distinct_hosts` how\nmany separate chassis they're spread across; `vm_max_on_host` the\nmost this customer has on any single host (the worst-case \"one\nmachine dies\" blast radius). Anti-affinity raises distinct_hosts\nand lowers max_on_host; bin-packing does the opposite.",
                        "minimum": 0
                    },
                    "distinct_hosts": { "type": "integer", "format": "int32", "minimum": 0 },
                    "max_units_on_host": { "type": "integer", "format": "int32", "minimum": 0 },
                    "satisfaction": {
                        "type": "number",
                        "format": "double",
                        "description": "Composite customer satisfaction in `0.0..=1.0` — folds health,\nrelationship trust, and long-term sentiment into one player-facing\n\"how's this relationship\" number (see `CustomerState::satisfaction`)."
                    },
                    "satisfaction_label": {
                        "type": "string",
                        "description": "Plain-words band for `satisfaction` (Delighted / Happy / Content /\nUnsettled / At risk)."
                    },
                    "under_attack": {
                        "type": "boolean",
                        "description": "True when a malicious attack is currently landing on THIS tenant — the\nnamed victim of a targeted flood, or anyone the per-tenant impact pass is\nfailing requests for. Drives the \"UNDER ATTACK\" risk pill so the player\nsees which tenant an attack is hurting, not just a fleet-wide board."
                    },
                    "attack_class": {
                        "type": "string",
                        "description": "The attack class hitting this tenant (Flood / Connection flood / App flood\n/ Break-in attempts), empty when not under attack — for the pill caption."
                    },
                    "breached": {
                        "type": "boolean",
                        "description": "True while this tenant is in a BREACHED state — a sustained, unmitigated\nintrusion compromised them. Drives the \"BREACHED\" risk pill; distinct from\n(and more severe than) an availability incident."
                    },
                    "services": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/ServiceDemandView" },
                        "description": "Per-service demand-vs-fulfilment breakdown — one entry per service\nthis customer is contracted for. Item 5 / Phase 5 of\n`tasks/service_health_incidents_rework_2026_07.md`: makes the gap\nbetween what a customer asked for and what's actually live legible\neverywhere, instead of only inferable from a health percentage.\nReads `Engine::service_probe` for state/reason (Phase 1's spine) so\nthis view never re-derives feasibility/placement math.\n\n**Populated only when this session asked for THIS customer's detail**\n(`Request::SubscribeCustomer`), or for any reader that did not ask at\nall — the HTTP API mirror, the TUI and tests all project at\n`CustomerDetail::All`. A game session carries it for the tenant whose\nconsole is open and nobody else. Read `detail_omitted`, never\n`services.is_empty()`, to tell \"not sent\" from \"has no services\".\n\nIt is the expensive half of the section: `services` plus\n`service_margins` is ~28% of the customers bytes, and the customers\nsection is 57.2% of a realistic baseline once the comms work landed."
                    },
                    "detail_omitted": {
                        "type": "boolean",
                        "description": "`true` when `services` and `service_margins` were WITHHELD from this\nsession rather than being genuinely empty.\n\nSame rule, and the same reason, as `ChannelView::is_preview`: a\nreader that infers \"this tenant has no services\" from an empty vector\ndraws an empty console for a tenant with a full estate, and the failure\nis silent. A customer really can have zero priced services, so the\nvector's emptiness cannot carry this."
                    },
                    "provisioned_total": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Units this tenant has PLACED, summed across every service class.\n\nAlways sent, whatever `detail_omitted` says. It is the roster's entire\nneed from `services` (`customers_view.gd` sums `provisioned_count` and\n`requested_count` over the vector for its capacity-shortfall badge), so\nshipping two integers lets the list keep working while the array it\nused to walk stays behind the subscription.",
                        "minimum": 0
                    },
                    "requested_total": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Units this tenant CONTRACTED for, summed across every service class.\nThe other half of the roster's shortfall badge; see\n`provisioned_total`.",
                        "minimum": 0
                    },
                    "compliance_required": {
                        "type": "string",
                        "description": "Compliance regime the customer's contract requires (SOC2/HIPAA/PCI), or\n\"None\". Mirrors `ProspectView.compliance_required`."
                    },
                    "required_arch": {
                        "type": "string",
                        "description": "Raw CPU-arch requirement tag (\"x86_64\"/\"riscv\"/…) or \"\" if agnostic."
                    },
                    "requires_multi_az": { "type": "boolean" },
                    "requires_private_segment": { "type": "boolean" },
                    "demands_audit_logs": { "type": "boolean" },
                    "demands_air_gap": { "type": "boolean" },
                    "required_region": {
                        "type": ["integer", "null"],
                        "format": "int32",
                        "description": "Data-residency region the contract pins to, if any.",
                        "minimum": 0
                    },
                    "requires_gpu": { "type": "boolean" },
                    "gpu_slices_demand": { "type": "integer", "format": "int32", "minimum": 0 },
                    "growth_ceiling_vms": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Plateau — the max VM count this persona compounds to (0 = unknown/none).",
                        "minimum": 0
                    },
                    "requires_dedicated_hosts": {
                        "type": "boolean",
                        "description": "True when the customer's contract demands its private network on\nDEDICATED, homed hosts (single-tenant iron) — the 4× isolation tier vs\n2× for a plain private network. Implies `requires_private_segment`; the\ndetail pane shows one combined chip. Mirrors `ProspectView` post-accept."
                    },
                    "requires_uncontended": {
                        "type": "boolean",
                        "description": "True when the customer's archetype contracts UNCONTENDED (1:1 CPU) — its\nworkloads run on hosts admitted to an `uncontended` segment (1:1, no\novercommit). A CPU guarantee, wholly independent of dedication/tenancy\nand anti-affinity spread. Mirrors `ProspectView.requires_uncontended`;\ndrives the detail \"Uncontended CPU (guaranteed 1:1)\" requirement chip."
                    },
                    "requires_dedicated": {
                        "type": "boolean",
                        "description": "True when the customer's archetype contracts DEDICATED, single-tenant\nhosts (private iron, no co-tenants). Tenancy guarantee, not a CPU one —\ndistinct from uncontended. Mirrors `ProspectView.requires_dedicated`;\ndrives the detail \"Dedicated host (private)\" requirement chip."
                    },
                    "eviction_fee_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Termination fee (dollars) the operator would pay to evict this customer\nright now — the tier's `eviction_penalty_frac` of one month's run-rate\n(decoupled from the SLA-outage refund), plus any unused prepay on a\nstill-active Reserved contract (capped), clamped to current cash. Shown\non the Evict action so the player sees the bill before arming it."
                    },
                    "requirements_unmet_mask": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Bitmask of the customer's ASKED contract requirements that are currently\nUNMET, so the always-on requirement chips render met (green) vs unmet\n(amber) instead of alarm-amber for every ask. Stable wire bit layout:\nbit 0 = multi-AZ, 1 = private-segment/dedicated, 2 = data-residency,\n3 = fault-isolation (spread). Derived from `Engine::unmet_requirements`;\n0 = every asked requirement is satisfied (or none is asked). A chip is\nMET when its requirement is asked (the existing demand bools) and its bit\nis clear.",
                        "minimum": 0
                    },
                    "requires_fault_isolation": {
                        "type": "boolean",
                        "description": "True when the contract ASKS for fault isolation — copies of the tenant's\nworkloads kept off a shared fault domain (redundancy tier > 1 host\nreplica, or an archetype that requires anti-affinity). This is the ASK\nside of `requirements_unmet_mask` bit 3; without it a client can render\nthe spread chip UNMET but never MET, because the mask alone cannot\ndistinguish \"asked and satisfied\" from \"never asked\". Mirrors\n`ProspectView.requires_fault_isolation`."
                    },
                    "sla_period_availability_frac": {
                        "type": "number",
                        "format": "double",
                        "description": "SLA accrual, billing-period model (PROTOCOL 32) — the availability\nfraction accrued THIS billing period (since\n`CustomerState::sla_period_start`), `1 - failed/total` over\n`sla_period_requests_{total,failed}`. Guards divide-by-zero: `1.0`\n(nothing to fault yet) when no requests have accrued this period.\nThis is the EXACT figure `Engine::settle_sla_period` compares against\n`SlaTier::availability_target()` at period close — distinct from\n`availability_pct` above (the recovering rolling EWMA read for the\nhealth headline, not what the bill is judged on). Surfaced so a\ncredit on the bill is explainable instead of appearing from nowhere\n(see `memory/billing_authority_design_2026_07_27.md`)."
                    },
                    "sla_period_seconds_over_latency": {
                        "type": "number",
                        "format": "double",
                        "description": "Seconds accrued THIS billing period where the short-window p99\n(`p99_recent_ms`) sat over the tier's `sla_p99_budget_ms` — mirrors\n`CustomerState::sla_period_seconds_over_latency`. Always 0 for Bronze\n(no latency budget, never accrues). Pair with\n`sla_period_elapsed_seconds` to get the fraction\n`SlaTier::latency_penalty_band` bands (2% / 10% / 30% thresholds)."
                    },
                    "sla_period_elapsed_seconds": {
                        "type": "number",
                        "format": "double",
                        "description": "Sim-seconds elapsed since this billing period started accruing\n(`CustomerState::sla_period_start` to now) — the denominator for\n`sla_period_seconds_over_latency`, so the client can render \"over\nbudget N% of the period\" instead of a bare seconds count."
                    },
                    "margin_revenue_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Steady-state MONTHLY revenue for this tenant's live contract —\n`GrossMargin::revenue`, i.e. `meter_projected` + `rate`, the same pair\nthat quotes a prospect. NOT an accumulator: pairing it with\n`revenue_total_dollars` (lifetime) would compare two different months."
                    },
                    "margin_cost_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Monthly cost honestly attributable to this tenant —\n`GrossMargin::attributable_cost`. Their share of the hosts they occupy\n(including those hosts' rack-U share of the facility) plus the upstream\ntransit bought for their bytes. Excludes overhead BY CONSTRUCTION; see\n`CustomersSummary::overhead_dollars`."
                    },
                    "margin_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "`margin_revenue_dollars - margin_cost_dollars`. GROSS margin. Negative\nmeans the tenant loses money before a single dollar of payroll."
                    },
                    "cost_infrastructure_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "The infrastructure half of `margin_cost_dollars` (`TenantCost::\ninfrastructure`) — host share incl. facility."
                    },
                    "cost_transit_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "The transit half of `margin_cost_dollars` (`TenantCost::transit`) —\nupstream bytes. The one cost that arrives already per-customer."
                    },
                    "service_margins": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/ServiceMarginView" },
                        "description": "Per-service revenue/cost/margin split, `ServiceClass::ALL` order, zero\nrows omitted (`GrossMargin::by_service`). Empty until the tenant has a\npriced service."
                    }
                }
            },
            "CustomersSummary": {
                "type": "object",
                "description": "Customers-lens pre-computed counters + filter pill counts. Folded\nfrom a single pass over the just-built `customers` Vec inside\n`build_snapshot`, so the renderer's customers grid doesn't re-walk\nthe roster every 250 ms paint just to compute \"ACTIVE / AT-RISK /\nPROSPECTS / MRR EST.\" + the per-pill counts. Ships engine-authoritative\nnumbers — no chance of GDScript / engine drift on filter semantics.\n\nMRR is in **cents** (u64) instead of dollars (f64) so the renderer's\nfingerprint-and-skip gate (`_cust_summary_fp`) is integer-exact —\nfloat jitter at the cent boundary won't defeat the gate. Renderer\ndivides by 100.0 once at format time.",
                "required": ["active", "at_risk", "prospects", "mrr_total_cents", "filter_counts"],
                "properties": {
                    "active": {
                        "type": "integer",
                        "format": "int32",
                        "description": "All active customers (Active + Pending lifecycle). Equal to\n`customers.len() as u32`, but pre-counted so the renderer's\nsummary band fingerprint is a single integer read.",
                        "minimum": 0
                    },
                    "at_risk": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Customers on probation OR currently in a sustained latency\nspike — the \"needs attention\" count for the AT-RISK pill.",
                        "minimum": 0
                    },
                    "prospects": { "type": "integer", "format": "int32", "minimum": 0 },
                    "mrr_total_cents": {
                        "type": "integer",
                        "format": "int64",
                        "description": "MRR estimate in cents: `sum(c.revenue_last_hour_dollars) × 24 × 30 × 100`.\nCents instead of dollars for integer fingerprint exactness.",
                        "minimum": 0
                    },
                    "filter_counts": {
                        "$ref": "#/components/schemas/CustomerFilterCounts",
                        "description": "Per-filter counts mirroring the four customer-grid filter pill\nids in `ops_console_panel.gd` (`_CUST_FILTER_*`). Pre-computed so\nthe renderer's filter row fingerprint is the same single-pass\nread as the summary band."
                    },
                    "attributed_cost_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Sum of every tenant's `margin_cost_dollars` (`CostAttribution::\nattributed`), monthly run rate."
                    },
                    "unattributed_cost_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Attributable-in-principle monthly cost that honestly reached NO tenant:\nidle hosts, component/module depreciation, truncation residue\n(`CostAttribution::unattributed_infrastructure`)."
                    },
                    "overhead_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Monthly cost that is not attributable BY CONSTRUCTION — salary plus\ndebt service (`CostKind::is_overhead`). C6.4: this MUST be rendered\noutside every tenant's margin. \"Gross margin\" is only honest if the\nunattributed remainder is visible next to it, and spreading payroll\nacross tenants by headcount or revenue share is an invented number."
                    },
                    "total_cost_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "The fleet's whole monthly cost run rate. Invariant, exactly:\n`attributed + unattributed + overhead == total`."
                    }
                }
            },
            "Diagnostics": {
                "type": "object",
                "description": "The whole diagnostic picture, in one consistent read.",
                "required": [
                    "meta",
                    "runtime",
                    "status",
                    "topbar",
                    "capacity",
                    "customers_summary",
                    "active_incidents",
                    "active_incidents_total",
                    "incidents_truncated",
                    "recent_resolved_incidents",
                    "active_work_orders",
                    "active_attack",
                    "active_macro_events",
                    "degraded_control_planes",
                    "stranded_vms",
                    "service_gates",
                    "service_planes",
                    "pending_growth_requests",
                    "pending_customer_requests",
                    "pending_bond_requests",
                    "proposed_bond_groups",
                    "cost_breakdown",
                    "cost_rates",
                    "revenue_breakdown",
                    "financing_breakdown",
                    "spares",
                    "migration",
                    "relocation",
                    "guidance",
                    "loop_stats",
                    "siem_logs",
                    "recent_events",
                    "log_tail"
                ],
                "properties": {
                    "meta": { "$ref": "#/components/schemas/ApiMeta" },
                    "runtime": { "$ref": "#/components/schemas/RuntimeCounters" },
                    "status": { "$ref": "#/components/schemas/GameStatusView" },
                    "topbar": { "$ref": "#/components/schemas/TopBarMetrics" },
                    "capacity": {
                        "$ref": "#/components/schemas/CapacityView",
                        "description": "Physical and compute headroom. The first thing to check when anything\nis unservable."
                    },
                    "customers_summary": { "$ref": "#/components/schemas/CustomersSummary" },
                    "active_incidents": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/IncidentView" },
                        "description": "Capped at `MAX_INCIDENTS`; `active_incidents_total` is the true count."
                    },
                    "active_incidents_total": {
                        "type": "integer",
                        "description": "How many are actually open, regardless of how many are carried above.",
                        "minimum": 0
                    },
                    "incidents_truncated": {
                        "type": "boolean",
                        "description": "True when `active_incidents` was truncated."
                    },
                    "recent_resolved_incidents": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/IncidentView" },
                        "description": "Recently resolved, because \"it keeps happening\" is a different problem\nfrom \"it is happening\", and only the history distinguishes them."
                    },
                    "active_work_orders": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/WorkOrderView" }
                    },
                    "active_attack": { "$ref": "#/components/schemas/ActiveAttackView" },
                    "active_macro_events": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/MacroEventView" }
                    },
                    "degraded_control_planes": {
                        "type": "array",
                        "items": { "type": "string" },
                        "description": "Named control planes running degraded. A common root cause of complaints\nthat look unrelated to each other."
                    },
                    "stranded_vms": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/StrandedVmView" },
                        "description": "VMs the engine could not place. Direct, specific, and invisible in every\ncollection endpoint."
                    },
                    "service_gates": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/ServiceGateView" }
                    },
                    "service_planes": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/ServicePlaneView" }
                    },
                    "pending_growth_requests": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/GrowthRequestView" }
                    },
                    "pending_customer_requests": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/CustomerRequestView" }
                    },
                    "pending_bond_requests": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/BondRequestView" }
                    },
                    "proposed_bond_groups": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/ProposedBondGroupView" }
                    },
                    "cost_breakdown": { "$ref": "#/components/schemas/CostBreakdownView" },
                    "cost_rates": { "$ref": "#/components/schemas/CostRatesView" },
                    "revenue_breakdown": { "$ref": "#/components/schemas/RevenueBreakdownView" },
                    "financing_breakdown": {
                        "$ref": "#/components/schemas/FinancingBreakdownView"
                    },
                    "spares": { "$ref": "#/components/schemas/SpareInventoryView" },
                    "migration": { "$ref": "#/components/schemas/MigrationStatusView" },
                    "relocation": { "$ref": "#/components/schemas/RelocationOfferView" },
                    "guidance": { "$ref": "#/components/schemas/GuidanceView" },
                    "loop_stats": { "$ref": "#/components/schemas/LoopStatsView" },
                    "siem_logs": { "$ref": "#/components/schemas/SiemLogsView" },
                    "connection_health": {
                        "oneOf": [
                            { "type": "null" },
                            {
                                "$ref": "#/components/schemas/ConnectionHealthView",
                                "description": "Multiplayer link quality. `None` outside a shared world."
                            }
                        ]
                    },
                    "recent_events": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/EventView" },
                        "description": "Recent events, already resolved to English."
                    },
                    "log_tail": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/LogEntryView" },
                        "description": "The engine's own diagnostic log tail. NOT an event stream — a separate,\ndeliberately flat surface, and often the only place an odd failure\nleaves a trace."
                    }
                }
            },
            "EventView": {
                "type": "object",
                "description": "Mirror of `sim_core::engine_event::EngineEvent` flattened for the\nrenderer. Variant is exposed as a string tag plus a localization key +\nargs (`msg_key` / `msg_args`) the client resolves against the player's\nlanguage and voice — the projection never bakes English words in.",
                "required": ["at_ns", "kind", "severity"],
                "properties": {
                    "at_ns": { "type": "integer", "format": "int64", "minimum": 0 },
                    "kind": {
                        "type": "string",
                        "description": "Variant tag — e.g. \"CableLaid\", \"SwitchTripped\". Used for filter chips."
                    },
                    "msg_key": {
                        "type": "string",
                        "description": "Localization key for this event — `event-<variant-kebab>`, resolved\nclient-side against the player's language + voice."
                    },
                    "msg_args": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/MsgArgView" },
                        "description": "Facts to substitute into `msg_key`."
                    },
                    "severity": {
                        "type": "string",
                        "description": "\"Info\" / \"Warn\" / \"Error\" / \"Critical\"."
                    },
                    "entity_kind": {
                        "type": ["string", "null"],
                        "description": "Affected entity kind (\"Rack\" / \"Switch\" / \"Host\" / \"Cable\" / \"Customer\").\nPopulated for events with a single primary target — failure-cascade\nrenderers use this to flash the right chassis. `None` for global\nevents (facility-wide saturation, DDoS, win/lose)."
                    },
                    "entity_id": { "type": ["integer", "null"], "format": "int32", "minimum": 0 }
                }
            },
            "EventsPage": {
                "type": "object",
                "description": "`GET /api/v1/events` body.\n\nNot a plain `Page`: the gap fields are the point. The engine keeps a\nbounded recent-event window, so a consumer polling slower than events\narrive WILL miss some, and a response that quietly returned fewer rows\nwould look identical to a quiet period. `possible_gap` says which it was.",
                "required": ["items", "possible_gap", "count"],
                "properties": {
                    "items": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/EventView" }
                    },
                    "next_since_ns": {
                        "type": ["integer", "null"],
                        "format": "int64",
                        "description": "Highest `at_ns` in this response. Pass it back as `since_ns` to\ncontinue; `null` when nothing matched.",
                        "minimum": 0
                    },
                    "oldest_retained_ns": {
                        "type": ["integer", "null"],
                        "format": "int64",
                        "description": "Oldest `at_ns` the engine still retains, across the whole window.",
                        "minimum": 0
                    },
                    "possible_gap": {
                        "type": "boolean",
                        "description": "True when the caller's `since_ns` was older than `oldest_retained_ns`,\nmeaning events happened that this response cannot show. Poll faster, or\naccept the gap knowingly."
                    },
                    "count": { "type": "integer", "minimum": 0 }
                }
            },
            "EvidenceRowView": {
                "type": "object",
                "description": "One measured row on an incident card: what was measured, the value, and\nthe context that makes the value mean something (baseline, budget, share).\n\nDeliberately pre-formatted engine-side. The numbers come with units,\nprecision and comparison baselines that only the engine knows, and every\nclient that re-derived them would drift; the renderer's job is layout.",
                "required": [
                    "label",
                    "label_plain",
                    "value",
                    "value_plain",
                    "detail",
                    "detail_plain"
                ],
                "properties": {
                    "label": {
                        "type": "string",
                        "description": "Row label, engineering voice — \"p99 latency\", \"Where the time goes\"."
                    },
                    "label_plain": {
                        "type": "string",
                        "description": "Plain-voice counterpart of `label`."
                    },
                    "value": {
                        "type": "string",
                        "description": "The headline measurement, formatted with units — \"412 ms\", \"6.2%\".\nKept SHORT: the client renders it in a fixed-width, clipping column."
                    },
                    "value_plain": {
                        "type": "string",
                        "description": "Plain-voice counterpart of `value`. Usually identical (a number is a\nnumber); differs where the value names a term (\"Queueing 4 ms\" vs\n\"Waiting in line 4 ms\").\n\nThis exists so `detail` never has to double as the plain-voice value.\nIt did briefly, and the result was both voices rendering at once in\nboth modes, with the jargon toggle merely reordering them — and, worse,\nplain mode promoting the BASELINE into the headline slot so the\nnon-engineer saw \"normal 88 ms\" where the measured p99 belonged."
                    },
                    "detail": {
                        "type": "string",
                        "description": "Context that gives `value` meaning — \"normal 88 ms · Gold budget\n150 ms\", or the full ranked breakdown. Rendered as a wrapping sub-line,\nso unlike `value` it may be long. May be empty."
                    },
                    "detail_plain": {
                        "type": "string",
                        "description": "Plain-voice counterpart of `detail`."
                    },
                    "leading": {
                        "type": "boolean",
                        "description": "True when this row is the one that BREACHED (p99 over budget, the\ndominant latency term, the leading failure cause) — lets the client\nweight it without re-deriving the engine's own verdict."
                    }
                }
            },
            "FanSpeed": { "type": "string", "enum": ["Off", "Low", "Medium", "High", "Max"] },
            "FinancingBreakdownView": {
                "type": "object",
                "description": "Cash IN that is neither revenue nor cost, projected from\n`Engine::financing_breakdown`.\n\nExists so the Finance tab can explain a cash balance that revenue minus\ncost does not account for. A quest payout is not business the player won,\nand a bailout is a loan, so neither may read as revenue — but they moved\nthe cash, and a finance screen that cannot say where it came from is the\nreason the reconciliation drift went unnoticed for so long.",
                "required": [
                    "quest_rewards_dollars",
                    "bailout_injection_dollars",
                    "relocation_grant_dollars",
                    "total_financing_dollars"
                ],
                "properties": {
                    "quest_rewards_dollars": { "type": "number", "format": "double" },
                    "bailout_injection_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Emergency-bailout principal. A LOAN — its repayments show up as\n`service_debt` on the cost side."
                    },
                    "relocation_grant_dollars": { "type": "number", "format": "double" },
                    "total_financing_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Sum of the buckets above — `Engine::total_financing`, the third term of\n`Δcash == Δrevenue - Δcost + Δfinancing`."
                    }
                }
            },
            "GameStatusView": { "type": "string", "enum": ["InProgress", "Won", "Lost"] },
            "GatewayAddonView": {
                "type": "object",
                "description": "One fit-able gateway security add-on (archetype B — no box, throttles the\ngateway). Static option data + this gateway's enabled state.",
                "required": [
                    "addon",
                    "name",
                    "class_label",
                    "enabled",
                    "capex_dollars",
                    "monthly_dollars"
                ],
                "properties": {
                    "addon": {
                        "type": "string",
                        "description": "Slug for `SetGatewayAddon`: \"advanced_firewall\"/\"basic_ips\"/\"basic_scrub\"."
                    },
                    "name": { "type": "string" },
                    "class_label": {
                        "type": "string",
                        "description": "Class it covers (slug): protocol/intrusion/volumetric."
                    },
                    "enabled": { "type": "boolean" },
                    "owned": {
                        "type": "boolean",
                        "description": "Capex already paid for this add-on on THIS gateway (the ownership\nledger). `enabled` add-ons are always owned; an owned-but-disabled\nadd-on can be RE-subscribed for free. Lets the console show\n`Buy $X` → `Unsubscribe` → `Resubscribe (free)`."
                    },
                    "capex_dollars": { "type": "number", "format": "double" },
                    "monthly_dollars": { "type": "number", "format": "double" }
                }
            },
            "GatewayFirewallView": {
                "type": "object",
                "description": "The intrinsic always-on firewall baseline on every gateway.",
                "required": ["dropped_per_sec", "class_label"],
                "properties": {
                    "dropped_per_sec": {
                        "type": "number",
                        "format": "double",
                        "description": "Baseline filtering this gateway does on its own — latest ring sample\n(abstract drops/s)."
                    },
                    "class_label": {
                        "type": "string",
                        "description": "Class it covers — always \"Protocol\" (stateful L3/L4)."
                    },
                    "reaching": {
                        "type": "number",
                        "format": "double",
                        "description": "Per-device Protocol cascade (this gateway's built-in FW): what reached\nit, what it absorbed, what passed on, and its capacity — in the Protocol\nunit (Kpps). Lets the gateway console show \"Protocol: handling N of M\"\nfor THIS box rather than the fleet aggregate. Zero when the FW is off."
                    },
                    "mitigated": { "type": "number", "format": "double" },
                    "residual": { "type": "number", "format": "double" },
                    "capacity": { "type": "number", "format": "double" },
                    "unit": { "type": "string" }
                }
            },
            "GatewayView": {
                "type": "object",
                "description": "Live edge-gateway projection. WAN port(s) cable to the edd; LAN ports\nto switches. Forwarding device in the reachability chain.",
                "required": ["id", "az_id"],
                "properties": {
                    "id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "display_name": { "type": "string" },
                    "display_name_plain": {
                        "type": "string",
                        "description": "Plain-mode counterpart: `gateway 1` vs `gw-01`."
                    },
                    "az_id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "sku_name": { "type": "string" },
                    "form": { "type": "string" },
                    "u_size": { "type": "integer", "format": "int32", "minimum": 0 },
                    "rack_id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "start_u": { "type": "integer", "format": "int32", "minimum": 0 },
                    "wan_port_count": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Live count of this gateway's ports currently cabled to the carrier\n(a edd / patch panel) — i.e. acting WAN. Derived from cabling,\nnot a nameplate: gateways are ARM-ASIC and have no fixed WAN ports.",
                        "minimum": 0
                    },
                    "lan_port_count": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Live count of ports currently cabled to a switch — i.e. acting LAN.",
                        "minimum": 0
                    },
                    "used_ports": { "type": "integer", "format": "int32", "minimum": 0 },
                    "fabric_capacity_gbps": {
                        "type": "integer",
                        "format": "int32",
                        "description": "SKU fabric/forwarding ceiling — sustained traffic above this\nsaturates the ARM-ASIC packet engine. Legacy name kept as alias.",
                        "minimum": 0
                    },
                    "fabric_load_gbps": {
                        "type": "number",
                        "format": "double",
                        "description": "Smoothed full-duplex fabric load in Gbps (= egress + ingress).\nCompared against `fabric_capacity_gbps` for the saturation banner."
                    },
                    "fabric_saturated": {
                        "type": "boolean",
                        "description": "True when sustained `fabric_load_gbps` exceeds `fabric_capacity_gbps` —\npackets start dropping. Edge-triggered in the engine; banner\nstate here."
                    },
                    "egress_gbps": {
                        "type": "number",
                        "format": "double",
                        "description": "Smoothed directional egress (LAN-side outbound) in Gbps."
                    },
                    "ingress_gbps": {
                        "type": "number",
                        "format": "double",
                        "description": "Smoothed directional ingress (WAN-side inbound) in Gbps."
                    },
                    "wan_ingress_gbps": { "type": "number", "format": "double" },
                    "wan_egress_gbps": { "type": "number", "format": "double" },
                    "lan_ingress_gbps": { "type": "number", "format": "double" },
                    "lan_egress_gbps": { "type": "number", "format": "double" },
                    "ports": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/PortView" }
                    },
                    "builtin_firewall": {
                        "$ref": "#/components/schemas/GatewayFirewallView",
                        "description": "Always-on built-in firewall baseline every gateway ships (drops\nunsolicited inbound — the intrinsic archetype-B firewall).\n\nSUPERSEDED, and NO CLIENT READER (audited 2026-08-09). `threats`\nbelow carries the same numbers per class, and that is what the\ngateway console renders — the only GDScript reference to anything\nnamed `builtin_firewall*` is to the SIBLING bool\n`builtin_firewall_enabled`. This whole nested 7-field struct\nsurvives only because ~17 assertions in\n`sim-render/tests/views/{threat_per_device,security_e2e}.rs` read it;\nretiring it means porting those to the `threats` row for\n`class == \"protocol\"` first. Do not build new UI against it."
                    },
                    "builtin_firewall_enabled": {
                        "type": "boolean",
                        "description": "Whether the built-in firewall is switched ON. Player-toggleable from the\ngateway console (→ `SetGatewayFirewall`); off removes this gateway's\nProtocol baseline. Defaults true."
                    },
                    "bgp_capable": {
                        "type": "boolean",
                        "description": "Whether this gateway's SKU can run BGP (eBGP to the carrier + HA\npairing). Entry/SOHO routers are single boxes — circuit redundancy only,\nno router HA. Gates the \"form a redundant pair\" affordance."
                    },
                    "multi_az_capable": {
                        "type": "boolean",
                        "description": "Whether the SKU can be a multi-AZ region border (implies `bgp_capable`)."
                    },
                    "bgp_advertising": {
                        "type": "boolean",
                        "description": "Whether this gateway is CURRENTLY advertising the provider prefix — a\nlive, `bgp_capable`, internet-facing edge. Two or more advertising edges\n= an HA border; a lone advertiser (or a non-BGP edge) is a border SPOF."
                    },
                    "wan_policy": {
                        "type": "string",
                        "description": "This router's WAN uplink policy across ITS circuits (\"Failover\" /\n\"ActiveActive\"). The console's WAN tab toggles it. Moved off the passive\nEDD 2026-07-22 — the router owns the routing decision."
                    },
                    "security_addons": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/GatewayAddonView" },
                        "description": "One row per available gateway security add-on (advanced firewall /\nbasic IPS / basic scrub). `enabled` reflects whether THIS gateway has\nit fitted. The console renders a CheckButton per row with capex+monthly\n+ the throttle warning, dispatching `SetGatewayAddon`."
                    },
                    "addon_throttled": {
                        "type": "boolean",
                        "description": "A gateway has NO deep-inspection engine. These three fields describe the\ngateway's FORWARDING ceiling being reduced by the security add-ons fitted\nto it, and nothing more. An add-on riding a router costs the router's ASIC\nheadroom; that penalty is a throttle on forwarding, not an inspection\nthroughput rating. Only a dedicated appliance has a real inspection plane\n(`ApplianceView::inspection_*`), and a gateway must never read as capable\nas one. Renamed 2026-07-25 from `inspection_active` /\n`inspection_throttle` / `inspection_capacity_gbps`, which mislabelled the\nthrottle penalty as a DPI ceiling.\n\nTrue when this gateway has a DPI add-on fitted (Σ deep-inspection\n`inspection_capacity_gbps` > 0), i.e. something on the router is actually\ncosting it forwarding headroom.\n\nNOT \"some fitted add-on carries a `gateway_throttle`\". The stateful\nfirewall add-on carries one but does no per-packet DPI, so the engine\ncharges it nothing; a firewall-only gateway reports `false` and keeps its\nfull rated ceiling below."
                    },
                    "addon_throttle": {
                        "type": "number",
                        "format": "float",
                        "description": "`forwarding_ceiling_gbps / fabric_capacity_gbps` — the fraction of rated\nfabric the effective ceiling leaves, for the console's \"×0.80\" hint. 1.0\nwhen nothing is throttling. A PRESENTATION of the ceiling below, derived\nfrom it, so the two can never disagree; it is not an authored constant."
                    },
                    "forwarding_ceiling_gbps": {
                        "type": "number",
                        "format": "double",
                        "description": "The gateway's effective FORWARDING ceiling with its add-ons fitted —\n`fabric_load_gbps` is the load against it, and it IS the threshold\n`fabric_saturated` flips on.\n\nSingle source of truth: `effective_forwarding_capacity_gbps(fabric_capacity,\nEngine::gateway_inspection_capacity_gbps(id), load)`, the same shared curve\n(and the same inputs) `Engine::metrics_edge_traffic_pass` saturates against\nin `sim-engine/src/traffic/fabric.rs`. Load-dependent by nature: DPI eats\nforwarding headroom as inspection engages, so this falls from full fabric\ntoward the DPI ceiling as `fabric_load_gbps` rises.\n\nPre-v29 this was `fabric_capacity_gbps × Π gateway_throttle`, an unrelated\nauthored multiplier that disagreed with the saturation verdict at every\nload. Never re-derive it renderer-side.\n\nNamed `throttled_capacity_gbps` until 2026-08-09. `*_capacity_gbps` is\nRESERVED for an integer nameplate rating (`docs/SCHEMA.md` § Naming\nconventions), and this is an explicitly load-dependent f64 — the one\nthing the suffix rule exists to keep apart. `schema_naming_conventions.rs`\nnow fails the build if the old spelling comes back."
                    },
                    "monthly_opex_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Live projected monthly opex ($/mo) — running fee + chassis power +\nseated-optic draw. Mirrors the host console's read."
                    },
                    "inv_status": {
                        "type": "string",
                        "description": "Lifecycle status — see `HostView::inv_status`. Four-corner parity with\nHost / Switch / PatchPanel; a gateway could not report a sell-queued\nstate before this."
                    },
                    "threats": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/ThreatMitigationView" },
                        "description": "Per-class threat rows for THIS gateway — one row per class reaching it,\ndefended or not. Defended classes are the built-in L3/L4 firewall's\n(Protocol / Volumetric / Intrusion) plus any enabled add-on's (Intrusion\nvia Basic IPS, Volumetric via Basic Scrub); undefended classes (L7\nApplication, or anything whose defence is switched off) still appear at\n`mitigated: 0, capacity: 0` with `defense_capable` telling them apart.\nSame shape + shared `ThreatMitigationView` as `ApplianceView::threats`,\nso the gateway console renders the identical per-class Threats\nbreakdown the appliance does. `builtin_firewall` above stays the legacy\nsingle-Protocol summary; the console uses this vec. Empty only when the\ngateway is off the internet path entirely."
                    },
                    "powered": {
                        "type": "boolean",
                        "description": "At least one AC cord seated (or none required). False = dark\nuntil the player plugs it in. Mirrors the engine's `powered`."
                    },
                    "power_feeds": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Connected AC cord count. >= 2 = PSU-failure resilience at\n`redundant_psu_overhead_w` extra draw per extra cord.",
                        "minimum": 0
                    }
                }
            },
            "GrowthRequestView": {
                "type": "object",
                "required": [
                    "id",
                    "customer_id",
                    "customer_name",
                    "at_ns",
                    "additional_units",
                    "viral",
                    "resolution"
                ],
                "properties": {
                    "id": { "type": "integer", "format": "int64", "minimum": 0 },
                    "customer_id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "customer_name": {
                        "type": "string",
                        "description": "Resolved customer name — from the customer record."
                    },
                    "at_ns": { "type": "integer", "format": "int64", "minimum": 0 },
                    "class_tag": {
                        "type": "string",
                        "description": "**Which service is being asked for more of** — `ServiceClass::tag()`\n(\"vm\", \"k8s\", \"obj\", \"cdn\", \"db\", \"fn\", \"lb\").\n\nThe client selects the noun off this in Fluent (`-growth-unit`), so a\nKubernetes tenant reads \"+2 workers\" and an object-store tenant reads\n\"+500 GB of storage\" rather than the \"+N VMs\" every ask used to say.\n`#[serde(default)]` yields \"vm\", which is what every pre-2026-08-10 ask\nactually was."
                    },
                    "additional_units": {
                        "type": "integer",
                        "format": "int32",
                        "description": "How much more, in that class's own units. See [`Self::class_tag`] — this\nis NOT a VM count except when `class_tag` is \"vm\", and the name is\nretained only until the next `PROTOCOL_VERSION` bump.",
                        "minimum": 0
                    },
                    "additional_vcpu_milli": {
                        "type": "integer",
                        "format": "int32",
                        "description": "vCPU (milli) the ask's additional VMs demand - per-VM spec × count.",
                        "minimum": 0
                    },
                    "additional_mem_mb": {
                        "type": "integer",
                        "format": "int32",
                        "description": "RAM (MB) the ask's additional VMs demand.",
                        "minimum": 0
                    },
                    "resize_add_vcpu": {
                        "type": "integer",
                        "format": "int32",
                        "description": "**A VERTICAL ask**: vCPU to add to EACH existing VM's spec, `0` when the\ncustomer is only asking for more VMs.\n\nWithout these the client cannot tell a resize ask from a count ask, and\na pure resize (`additional_units == 0`) rendered as \"+0 VMs\" — a request\nto change nothing. `spec_resize` existed on the engine's `GrowthRequest`\nall along and simply stopped at the projection.",
                        "minimum": 0
                    },
                    "resize_add_memory_mb": {
                        "type": "integer",
                        "format": "int32",
                        "description": "RAM (MB) to add to EACH existing VM's spec. See [`Self::resize_add_vcpu`].",
                        "minimum": 0
                    },
                    "viral": { "type": "boolean" },
                    "resolution": {
                        "type": "string",
                        "description": "\"\" while open; \"Approved\" / \"Refused\" / \"Lapsed\" once resolved."
                    },
                    "fit_vcpu_after_pct": {
                        "type": "number",
                        "format": "double",
                        "description": "Projected fleet vCPU utilization after this ask's additional VMs land\n(physical basis, display-only). 0.0 when the customer runs no VM demand."
                    },
                    "fit_can_serve_now": {
                        "type": "boolean",
                        "description": "True when the fleet can absorb the ask at 1:1 right now (fits within\nphysical headroom, low contention). No new hardware needed."
                    },
                    "fit_oversubscribed": {
                        "type": "boolean",
                        "description": "True when approving would push the fleet PAST physical into overcommit:\nit still fits under the host overcommit ceiling, but CPU is shared and\ntenants may slow under load. Never a false \"can't fit\"."
                    },
                    "fit_recommendation": {
                        "type": "string",
                        "description": "Contention-tier one-liner: \"Comfortable\" / \"Tight fit\" / \"Will run\noversubscribed (CPU shared under load)\" / \"Over capacity, add hardware\nfirst\" / \"No VM load\"."
                    },
                    "segment_name": {
                        "type": "string",
                        "description": "Customer's isolation segment name when pinned to an L3VNI backed by\ndedicated hosts (fit is scoped to it). \"\" for shared-fleet tenants."
                    },
                    "segment_vni": {
                        "type": "integer",
                        "format": "int32",
                        "description": "L3VNI of that dedicated segment; 0 when shared-fleet.",
                        "minimum": 0
                    },
                    "segment_dedicated_hosts": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Dedicated host count in that segment; 0 when shared-fleet.",
                        "minimum": 0
                    },
                    "segment_free_vcpu_milli": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Segment dedicated-host free/total capacity (all 0 for shared-fleet).",
                        "minimum": 0
                    },
                    "segment_total_vcpu_milli": {
                        "type": "integer",
                        "format": "int32",
                        "minimum": 0
                    },
                    "segment_free_mem_mb": { "type": "integer", "format": "int32", "minimum": 0 },
                    "segment_total_mem_mb": { "type": "integer", "format": "int32", "minimum": 0 }
                }
            },
            "GuidanceView": {
                "type": "object",
                "description": "Onboarding wayfinding hint — computed per tick from the frontier (\"Next\")\nquest + live deploy state (buy → staged → deployed). `active` false = no\nhint. Godot resolves `target` to a world object and draws a marker + the\n`label`; keyframe-only (rides the full snapshot like `relocation`/`migration`,\nso no capnp / delta / protocol change). See `sim_core::guidance`.",
                "required": [
                    "active",
                    "target",
                    "target_id",
                    "target_kind",
                    "shop_section",
                    "label",
                    "quest_id"
                ],
                "properties": {
                    "active": { "type": "boolean" },
                    "target": {
                        "type": "string",
                        "description": "\"\" | \"ops_console\" | \"wall_rack\" | \"delivery_zone\" | \"rack\" | \"device\"."
                    },
                    "target_id": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Rack/device id for \"rack\"/\"device\" targets (0 = resolver picks).",
                        "minimum": 0
                    },
                    "target_kind": {
                        "type": "string",
                        "description": "Device kind for the \"device\" target."
                    },
                    "shop_section": {
                        "type": "string",
                        "description": "Shop section pill to pulse while the console is open (\"gateways\", …)."
                    },
                    "label": {
                        "type": "string",
                        "description": "Short instruction shown at the marker."
                    },
                    "quest_id": {
                        "type": "integer",
                        "format": "int32",
                        "description": "The frontier quest this hint serves.",
                        "minimum": 0
                    }
                }
            },
            "Health": {
                "type": "object",
                "description": "`GET /health` body. The only endpoint that answers before a world exists,\nwhich is why `sim_time_ns` is optional here and mandatory everywhere else.",
                "required": ["status", "world_ready"],
                "properties": {
                    "status": {
                        "type": "string",
                        "description": "Always `\"ok\"` if the process is serving at all. Present so a scraper\nhas something to match on besides the status code."
                    },
                    "world_ready": {
                        "type": "boolean",
                        "description": "`false` until the apply thread has published its first mirror, which\nis the honest answer during boot and after `Load`."
                    },
                    "age_ms": { "type": ["integer", "null"], "format": "int64", "minimum": 0 },
                    "publish_seq": { "type": ["integer", "null"], "format": "int64", "minimum": 0 }
                }
            },
            "HostPoolView": {
                "type": "object",
                "description": "A player-created host pool. Small top-level list on `WorldSnapshot`\n(msgpack-only, like `lags`); per-host membership rides `HostView.pool_id`.\n`serves` are `ServiceClass` tags (\"vm\"/\"fn\"/\"obj\"/\"db\"/\"cdn\"/\"k8s\").",
                "required": ["id", "name", "serves", "host_count"],
                "properties": {
                    "id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "name": { "type": "string" },
                    "serves": { "type": "array", "items": { "type": "string" } },
                    "host_count": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Count of hosts currently assigned to this pool — saves the client a\nscan when rendering the pool list.",
                        "minimum": 0
                    }
                }
            },
            "HostStatus": {
                "type": "string",
                "enum": ["Idle", "Active", "Hot", "Down", "Failed", "Migrating"]
            },
            "HostView": {
                "type": "object",
                "required": [
                    "id",
                    "rack_id",
                    "az_id",
                    "start_u",
                    "u_size",
                    "sku_name",
                    "arch",
                    "label",
                    "visual",
                    "mem_requested_pct",
                    "cpu_requested_pct",
                    "ssd_used_pct",
                    "iops_actual",
                    "iops_capacity"
                ],
                "properties": {
                    "id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "display_name": {
                        "type": "string",
                        "description": "Player-facing identity like `srv-01`. Unique per host; survives\nSKU upgrades and moves. Prefer this over `sku_name` when labelling\nthe host in UI — `sku_name` is the model designation, shared\nacross many hosts."
                    },
                    "display_name_plain": {
                        "type": "string",
                        "description": "Plain-mode counterpart: `server 1` vs `srv-01`. Two-voice per\n`[[two_voice_labels_2026]]`. Empty on legacy saves; clients should\nfall back to `display_name`."
                    },
                    "rack_id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "az_id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "start_u": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Starting U slot inside the rack.",
                        "minimum": 0
                    },
                    "u_size": { "type": "integer", "format": "int32", "minimum": 0 },
                    "sku_name": { "type": "string" },
                    "arch": { "type": "string" },
                    "accelerator": { "type": ["string", "null"] },
                    "label": { "type": "string" },
                    "visual": { "$ref": "#/components/schemas/HostVisual" },
                    "unit_count": {
                        "type": "integer",
                        "format": "int32",
                        "description": "EVERY placed unit on this host, all seven service classes — the generic\noccupancy, straight off `sim_engine::placement::UnitIndex`. Includes\nSTRANDED units (their last known host is still the only place they\nclaim); [`HostView::stranded_unit_count`] is that subset, so\n`unit_count - stranded_unit_count` is what is actually running.\n\n**This is the only per-host workload count.** It replaced `vm_count`,\nwhich counted VM-substrate units alone — plain VMs plus managed-DB\ncopies — so a host carrying only CDN POPs, Kubernetes workers, LB\nproxies, object shards or function containers read 0, and every consumer\ninherited that hole: the capacity lens' Load column, the netview host\nsubtitle and the host status LED all showed \"empty\" on a fleet running\nCDN / Kubernetes / object storage.\n\n`vm_count` was kept alongside for one release purely because it had\nshipped, and was DELETED at `PROTOCOL_VERSION` 47: no reader wanted it.\nEvery live client site was already spelled `unit_count` with a\n`vm_count` fallback. Do not reintroduce a VM-substrate-only per-host\ncount; if one is ever genuinely needed it belongs behind a name that\nsays substrate, not workload.",
                        "minimum": 0
                    },
                    "stranded_unit_count": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Stranded units still pinned to this host — allocations whose customer is\n`Single` tier and whose host has lost reachability while remaining\npowered up. Drives the diegetic inspector's \"N stranded\" line. 0 when\nnothing is stranded on this host. Defaulted via serde for old saves.\n\nGENERIC since 2026-08-10: a stranded shard, worker or POP counts here\ntoo. Was `stranded_vm_count`; renamed at `PROTOCOL_VERSION` 47 with the\nrest of the `vm_`-named generic fields.",
                        "minimum": 0
                    },
                    "online": {
                        "type": "boolean",
                        "description": "Whether the host is up on the network right now — powered on AND\nreachable through a live uplink chain (engine `host_is_serviceable`).\nThe honest \"online/offline\" signal: a host that's cabled but whose\nuplink chain is down reads `false`, unlike a naive \"any cable attached\"\ncheck. Defaulted via serde for old / partial payloads."
                    },
                    "powered": {
                        "type": "boolean",
                        "description": "At least one AC cord seated (or none required — wall rack,\nunracked, or cord-free scenario). False = the box is dark until\nthe player plugs it in. Mirrors engine `Host.powered`."
                    },
                    "power_feeds": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Connected AC cord count (0..=psu_count). >= 2 = PSU-failure\nresilience at `redundant_psu_overhead_w` extra draw per extra cord.",
                        "minimum": 0
                    },
                    "mem_requested_pct": {
                        "type": "number",
                        "format": "float",
                        "description": "REQUESTED (reserved/committed) share, NOT live usage: numerator is\n`vm_used_* + service_reserved_*`, denominator NAMEPLATE `sku.*`. Live\ndemand rides `used_millivcpu` on this same struct — the two answer\ndifferent questions, hence the explicit `requested` in the name."
                    },
                    "cpu_requested_pct": { "type": "number", "format": "float" },
                    "ssd_used_pct": { "type": "number", "format": "float" },
                    "wear_factor": {
                        "type": "number",
                        "format": "float",
                        "description": "Wear multiplier on nameplate capacity (`eff_millivcpu = sku.vcpu *\nwear_factor`). Shipped because without it a client reading this\nstreamed view CANNOT compute the wear-adjusted EFFECTIVE basis at all\n— it has nameplate and nothing else. That gap is why some surfaces\ndivide by nameplate and others by effective. 1.0 = pristine."
                    },
                    "iops_actual": { "type": "integer", "format": "int32", "minimum": 0 },
                    "iops_capacity": { "type": "integer", "format": "int32", "minimum": 0 },
                    "nic_count": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Physical NIC port count. T1-T2 SKUs are single-NIC; T3+ ship\nmulti-NIC for LACP redundancy. Defaulted via serde.",
                        "minimum": 0
                    },
                    "ports": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/PortView" },
                        "description": "Per-NIC readouts — same content as `RackInspectSnapshot`'s host\noccupant ports. Populated for every host so the workbench\ninspector can show speed / state / activity per NIC."
                    },
                    "egress_gbps": {
                        "type": "number",
                        "format": "double",
                        "description": "Smoothed network egress in Gbps for this host's link cable.\nAccumulated across the customer's full service mix on this\nhost (Functions, VMs, ManagedDb, ObjectStore, LB, CDN, K8s).\nDrives per-port throughput at the cable's host-end + the\nswitch port the cable lands on."
                    },
                    "ingress_gbps": {
                        "type": "number",
                        "format": "double",
                        "description": "Smoothed ingress (data flowing IN — request bodies, PUT\nuploads). Surfaced separately so the inspector can show\nasymmetric flows (\"↑ heavy / ↓ light\") accurately."
                    },
                    "served_customer_ids": {
                        "type": "array",
                        "items": { "type": "integer", "format": "int32", "minimum": 0 },
                        "description": "Customers whose workload currently has presence on this host —\nVM allocations, in-flight Function invocations, ManagedDb\nprimaries, K8s nodes. Sourced from engine allocation maps; the\ninspector filters its tenant list to this set when scoped to a\nsingle host. Order: customer id ascending (deterministic)."
                    },
                    "inv_status": {
                        "type": "string",
                        "description": "Lifecycle status — \"Mounted\" / \"Carried\" / \"Loose\" / \"SellQueued\".\nRenderer dispatches off this for inventory presentation (e.g. a\nLoose host renders at its cubby, not its `start_u`)."
                    },
                    "age_hours": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Wall-clock age in hours since first install.",
                        "minimum": 0
                    },
                    "power_on_hours": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Powered-on hours accumulated.",
                        "minimum": 0
                    },
                    "condition_pct": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Condition 0..=100 against SKU MTBF.",
                        "minimum": 0
                    },
                    "mtbf_hours": {
                        "type": "integer",
                        "format": "int32",
                        "description": "SKU MTBF in hours — surfaced so the inspector can show \"120k\nexpected, 18k used\" without re-resolving the catalog.",
                        "minimum": 0
                    },
                    "resale_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Resale value at current condition + age. Same value the engine\ncredits if the item is sold right now."
                    },
                    "pool_id": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Host-pool membership. `0` ⇒ the General pool (`Host.pool_id == None`);\notherwise the `HostPoolView.id` this host belongs to. Delta-encoded —\nonly changes when the player reassigns the host, so it costs nothing\nper tick at rest. Resolve the name via `WorldSnapshot.host_pools`.",
                        "minimum": 0
                    },
                    "service_classes": {
                        "type": "array",
                        "items": { "type": "string" },
                        "description": "Service-class tags this host serves a data-plane agent for (service\nplane). Lets the Services page list a service's allocated hosts exactly\nrather than approximating via served-customer overlap. Empty when the\nmodel is off. Small (0–3 tags); changes rarely."
                    },
                    "vgpu_slices_total": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Total vGPU slices this host's SKU exposes across all its physical\nGPUs (`HostSku::total_vgpu().0`). 0 for a non-GPU host. Pairs with\n`vgpu_slices_used` for the inspector's GPU partition gauge.",
                        "minimum": 0
                    },
                    "vgpu_slices_used": {
                        "type": "integer",
                        "format": "int32",
                        "description": "vGPU slices currently reserved by placed VMs (`Host.vgpu_slices_used`).",
                        "minimum": 0
                    },
                    "gpu_mem_mb_total": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Total GPU frame-buffer (MB) across all physical GPUs\n(`HostSku::total_vgpu().1`). 0 for a non-GPU host.",
                        "minimum": 0
                    },
                    "gpu_mem_mb_used": {
                        "type": "integer",
                        "format": "int32",
                        "description": "GPU frame-buffer (MB) currently reserved (`Host.gpu_mem_used_mb`).",
                        "minimum": 0
                    },
                    "nic_module_slots": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Rear cages that accept a purchasable SFP NIC module\n(`HostSku.module_slots`). 0 = the chassis takes no modules.",
                        "minimum": 0
                    },
                    "nic_modules_installed": {
                        "type": "integer",
                        "format": "int32",
                        "description": "NIC modules currently installed (`Host.nic_modules.len()`), bounded\nby `nic_module_slots`.",
                        "minimum": 0
                    },
                    "free_vcpu_milli": {
                        "type": "integer",
                        "format": "int64",
                        "description": "Absolute free vCPU in millicores (`Host::free_millivcpu()`), i.e.\neffective capacity minus VM + service-reserved usage. Mirrors the\nexact accessor `migrate_vm` gates on, so the migration picker can\noffer only hosts a specific VM footprint actually fits.",
                        "minimum": 0
                    },
                    "free_mem_mb": {
                        "type": "integer",
                        "format": "int64",
                        "description": "Absolute free memory in MB (`Host::free_mb()`). Same accessor as the\n`migrate_vm` capacity gate.",
                        "minimum": 0
                    },
                    "free_ssd_gb": {
                        "type": "integer",
                        "format": "int64",
                        "description": "Absolute free SSD in GB (`Host::free_ssd_gb()`). Same accessor as the\n`migrate_vm` capacity gate.",
                        "minimum": 0
                    },
                    "free_ssd_iops": {
                        "type": "integer",
                        "format": "int64",
                        "description": "Absolute free SSD IOPS headroom (`Host::free_ssd_iops()`). Same\naccessor as the `migrate_vm` capacity gate; lets the picker match the\nengine's IOPS check for block-backed VMs.",
                        "minimum": 0
                    },
                    "physical_vcpu": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Physical vCPU count exposed by this host's SKU (`HostSku::vcpu`).\nThe denominator for the overcommit ratio; `allocated_millivcpu`\nmay exceed `physical_vcpu * 1000` when the host is oversubscribed.",
                        "minimum": 0
                    },
                    "physical_mem_mb": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Nameplate memory in MB (`HostSku::mem_mb`) — the twin of\n`physical_vcpu`, and the denominator `mem_requested_pct` divides by.\nShipped because this view exposed a nameplate total for CPU and none\nfor memory, so a consumer had to reconstruct total RAM from\n`free_mem_mb` and `mem_requested_pct` (UG-320). Memory never decays,\nso this is constant for the life of the host.",
                        "minimum": 0
                    },
                    "physical_ssd_gb": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Nameplate disk in GB (`HostSku::ssd_gb`), same rationale as\n`physical_mem_mb`. Also constant for life — wear scales IOPS, not\nformatted capacity, so `free_ssd_gb` subtracts from exactly this.\nNote `free_ssd_iops` does NOT: IOPS is the one wear-adjusted axis,\nso its free figure is against a ceiling that falls as the host ages.",
                        "minimum": 0
                    },
                    "allocated_millivcpu": {
                        "type": "integer",
                        "format": "int64",
                        "description": "Sum of vCPU (in millicores) *allocated* to placed VMs on this host\n(`Host::committed_millivcpu`). This is the promised/reserved figure,\nnot the live demand — compare against `physical_vcpu * 1000` to show\nthe oversubscription fill.",
                        "minimum": 0
                    },
                    "used_millivcpu": {
                        "type": "integer",
                        "format": "int64",
                        "description": "Live vCPU *demand* in millicores (`Host::used_millivcpu_demand()`),\ni.e. what the workloads are actually asking for this tick. Compared\nagainst `eff_millivcpu` (physical × overcommit_target) to derive the\ncontention state.",
                        "minimum": 0
                    },
                    "overcommit_target": {
                        "type": "number",
                        "format": "double",
                        "description": "Player-dialed overcommit target for this host (`Host::overcommit_target`).\n`1.0` = strictly 1:1 (dedicated); higher permits oversubscription up to\n`overcommit_max`."
                    },
                    "overcommit_max": {
                        "type": "number",
                        "format": "double",
                        "description": "Hard ceiling on `overcommit_target` (`config.tuning.vcpu_overcommit_max`).\nClamps the player-facing slider."
                    },
                    "contention_state": {
                        "type": "string",
                        "description": "Contention read-out: `\"healthy\"` (demand well under effective capacity),\n`\"warm\"` (demand > ~0.8× effective), `\"contended\"` (demand exceeds\neffective capacity — VMs are being throttled). Drives the host console's\ngreen/amber/red contention indicator."
                    },
                    "monthly_cost_direct_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "C6.3 — monthly cost caused by this box directly: power draw, its own\ndepreciation share, maintenance (`HostCost::direct`)."
                    },
                    "monthly_cost_facility_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "This box's share of the shared facility plant — rent, WAN circuits,\ncable opex, peering, network gear and their depreciation\n(`HostCost::facility_share`). Allocated by RACK UNITS, because floor\nspace is what rent buys and a 4U chassis occupies four times the site a\n1U does. Zero for a host that is not racked: a box on the floor serves\nno tenant and consumes no site capacity the fleet is paying for."
                    },
                    "monthly_cost_total_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "`direct + facility_share` — the host's all-in monthly cost\n(`HostCost::total`)."
                    },
                    "facility_basis": {
                        "type": "number",
                        "format": "double",
                        "description": "The rack-U fraction that produced `monthly_cost_facility_dollars`\n(`HostCost::facility_basis`). `0.0` when not racked. Surfaced so the\nconsole can EXPLAIN the facility line rather than assert it."
                    }
                }
            },
            "HostVisual": {
                "type": "object",
                "required": ["status", "indicator", "fan", "port", "accelerator_glow"],
                "properties": {
                    "status": { "$ref": "#/components/schemas/HostStatus" },
                    "indicator": { "$ref": "#/components/schemas/IndicatorLed" },
                    "fan": { "$ref": "#/components/schemas/FanSpeed" },
                    "port": { "$ref": "#/components/schemas/PortLight" },
                    "accelerator_glow": {
                        "type": "number",
                        "format": "float",
                        "description": "0.0..1.0 — frontend uses for soft-glow on accelerator cards."
                    }
                }
            },
            "IncidentArticleView": {
                "type": "object",
                "description": "One codex deep link on an incident detail pane: article id (for the\n`open_codex:<id>` action tag) plus both voice titles.",
                "required": ["id", "title", "title_plain"],
                "properties": {
                    "id": { "type": "string" },
                    "title": { "type": "string" },
                    "title_plain": { "type": "string" }
                }
            },
            "IncidentTimelineEntryView": {
                "type": "object",
                "required": ["at_ns", "kind", "detail"],
                "properties": {
                    "at_ns": { "type": "integer", "format": "int64", "minimum": 0 },
                    "kind": {
                        "type": "string",
                        "description": "Variant tag mirroring `IncidentEventKind` — \"Detected\",\n\"CustomerImpactStarted\", \"AggravatingFactor\", etc."
                    },
                    "detail": {
                        "type": "string",
                        "description": "Free-form details for variants that carry a payload (customer\nname, work-order id, factor description). Empty for variants\nthat are pure transitions."
                    }
                }
            },
            "IncidentView": {
                "type": "object",
                "description": "Renderer-friendly mirror of `sim_core::incident::Incident`.\nChannel UI, post-mortem, and the \"is this customer impacted?\"\nbadge all read from these. Severity (`EventView.severity`) is a\nper-event tag; `band` here is the incident-level gravity read.",
                "required": [
                    "id",
                    "root_kind",
                    "root_entity_id",
                    "started_at_ns",
                    "last_changed_at_ns",
                    "resolved_at_ns",
                    "affected_customer_ids",
                    "revenue_at_risk_per_hour_dollars",
                    "max_sla_tier",
                    "compliance_at_risk",
                    "redundancy_promise_broken",
                    "band"
                ],
                "properties": {
                    "id": { "type": "integer", "format": "int64", "minimum": 0 },
                    "root_kind": {
                        "type": "string",
                        "description": "Variant tag mirroring `IncidentRoot` — \"HostFailed\", \"CableFailed\", etc."
                    },
                    "root_entity_id": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Primary entity id from the root, when there is one (host id,\ncable id, rack id, ...). 0 for global roots like Ddos.",
                        "minimum": 0
                    },
                    "rack_id": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Rack the incident beacon should mark — resolved engine-side from the\ntyped root (device's rack, or the rack itself, or the edge rack where\nWAN ingress lands for global/fabric roots). 0 when nothing is racked\nyet. Lets the renderer skip a per-tick scan of every device array.",
                        "minimum": 0
                    },
                    "root_msg_key": {
                        "type": "string",
                        "description": "Localization key for the root label — `incident-<root_kind-kebab>`.\n\nGuidance (\"what to check\") deliberately has NO key field: it is static\nper root and the client derives `guidance-<root_kind>-check-<n>` from\n`root_kind`, which is already on this struct."
                    },
                    "root_msg_args": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/MsgArgView" },
                        "description": "Facts for `root_msg_key`, including any already-resolved customer name."
                    },
                    "symptom_kind": {
                        "type": "string",
                        "description": "Symptom layer — the customer/operator-facing HEADLINE the card leads\nwith (many causes → one symptom). Variant tag mirroring `Symptom`:\n\"ServiceUnreachable\", \"InfrastructureFault\", etc."
                    },
                    "symptom_msg_key": {
                        "type": "string",
                        "description": "Localization key for the symptom headline — `symptom-<kind-kebab>`."
                    },
                    "symptom_msg_args": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/MsgArgView" },
                        "description": "Facts for `symptom_msg_key`."
                    },
                    "symptom_customer_id": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Tenant the symptom is rooted on, or 0 for an `InfrastructureFault`.",
                        "minimum": 0
                    },
                    "confirmed": {
                        "type": "boolean",
                        "description": "True if any candidate cause has been confirmed as the real cause."
                    },
                    "causes": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/CandidateCauseView" },
                        "description": "Ranked candidate causes, best-first. In Phase 2 exactly one confirmed\ncandidate; full ranking is Phase 4."
                    },
                    "started_at_ns": { "type": "integer", "format": "int64", "minimum": 0 },
                    "last_changed_at_ns": { "type": "integer", "format": "int64", "minimum": 0 },
                    "resolved_at_ns": {
                        "type": "integer",
                        "format": "int64",
                        "description": "0 while open; set to the resolution sim-time once closed.",
                        "minimum": 0
                    },
                    "affected_customer_ids": {
                        "type": "array",
                        "items": { "type": "integer", "format": "int32", "minimum": 0 },
                        "description": "Sorted ascending by id. Engine guarantees deterministic order."
                    },
                    "affected_host_ids": {
                        "type": "array",
                        "items": { "type": "integer", "format": "int32", "minimum": 0 },
                        "description": "The MACHINES this incident is about — sorted ascending, capped at\n`sim_core::incident::AFFECTED_HOSTS_CAP`. Empty for roots with no host\nscope (DDoS, uplink saturation, customer-observation): those degrade\neveryone, so naming hosts would be a lie. Resolve names against\n`hosts[].display_name`."
                    },
                    "affected_host_total": {
                        "type": "integer",
                        "format": "int32",
                        "description": "True count before the cap, so the UI can say \"showing 64 of 812\"\nrather than silently truncating.",
                        "minimum": 0
                    },
                    "revenue_at_risk_per_hour_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Sum of monthly recurring run-rate across the affected set."
                    },
                    "max_sla_tier": {
                        "type": "string",
                        "description": "Worst SLA tier in the affected set — \"Bronze\" / \"Silver\" / \"Gold\" / \"Platinum\"."
                    },
                    "compliance_at_risk": {
                        "type": "string",
                        "description": "Worst compliance tier in the affected set — \"—\" / \"SOC2\" / \"HIPAA\" / \"PCI\"."
                    },
                    "redundancy_promise_broken": { "type": "boolean" },
                    "band": {
                        "type": "string",
                        "description": "Computed gravity band — \"cosmetic\" / \"annoying\" / \"serious\" / \"existential\"."
                    },
                    "timeline": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/IncidentTimelineEntryView" },
                        "description": "Incident timeline, oldest first. Bounded only by incident\nlifetime — preserved into the resolved-ring."
                    },
                    "work_order_ids": {
                        "type": "array",
                        "items": { "type": "integer", "format": "int64", "minimum": 0 },
                        "description": "Active work order ids targeting this incident. Populated once\nthe work model lands (Pass A2); empty until then."
                    },
                    "channel_thread_id": {
                        "type": "integer",
                        "format": "int64",
                        "description": "Channel thread root in `#incidents`. Populated once the channel\nmodule lands (Pass A3); 0 until then.",
                        "minimum": 0
                    },
                    "articles": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/IncidentArticleView" },
                        "description": "Knowledge-base articles covering this root's mechanic — the\ndetail pane renders them as codex deep links. May be empty."
                    },
                    "hint_tier": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Highest progressive-hint tier fired for this incident (0 = none,\n1 = subsystem nudge, 2 = named suspect, 3 = named fix).",
                        "minimum": 0
                    },
                    "hint": {
                        "type": "string",
                        "description": "The current-tier escalating hint text (empty when `hint_tier == 0`)."
                    },
                    "evidence": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/EvidenceRowView" },
                        "description": "MEASURED evidence behind a customer-facing symptom: the tenant's p99\nagainst its own baseline and tier budget, its failure rate, where the\nlatency actually went, and why requests failed.\n\nThe card used to carry labels, prose and hints but not one number —\nits own guidance said \"check their p99 latency vs tier budget\" about a\nfigure it never displayed. These rows are that figure. Empty for\ninfrastructure-rooted incidents with no tenant to measure."
                    }
                }
            },
            "IndicatorLed": {
                "type": "string",
                "enum": ["Off", "Green", "Amber", "Red", "FlashRed"]
            },
            "LogEntryView": {
                "type": "object",
                "required": ["at_ns", "msg"],
                "properties": {
                    "at_ns": { "type": "integer", "format": "int64", "minimum": 0 },
                    "msg": { "type": "string" }
                }
            },
            "LoopStatsView": {
                "type": "object",
                "description": "Run-scale game-loop measurement (see `docs/TUNING.md`) — counters tallied\nover the whole run plus a few derived \"right now\" readings. The instrument\nfor checking the loop is in balance: arrivals vs accepts vs churn, breach\nrate, request resolution mix, the reputation envelope.",
                "required": [
                    "prospects_arrived",
                    "prospects_accepted",
                    "prospects_rejected",
                    "prospects_expired",
                    "customers_churned",
                    "sla_breaches",
                    "host_failures",
                    "rep_milestones_up",
                    "rep_milestones_down",
                    "customer_praises",
                    "services_invested",
                    "requests_raised",
                    "requests_accepted",
                    "requests_declined",
                    "requests_lapsed",
                    "reputation",
                    "reputation_min",
                    "reputation_max",
                    "avg_satisfaction",
                    "active_customers"
                ],
                "properties": {
                    "prospects_arrived": { "type": "integer", "format": "int64", "minimum": 0 },
                    "prospects_accepted": { "type": "integer", "format": "int64", "minimum": 0 },
                    "prospects_rejected": { "type": "integer", "format": "int64", "minimum": 0 },
                    "prospects_expired": { "type": "integer", "format": "int64", "minimum": 0 },
                    "customers_churned": { "type": "integer", "format": "int64", "minimum": 0 },
                    "sla_breaches": { "type": "integer", "format": "int64", "minimum": 0 },
                    "host_failures": { "type": "integer", "format": "int64", "minimum": 0 },
                    "rep_milestones_up": { "type": "integer", "format": "int64", "minimum": 0 },
                    "rep_milestones_down": { "type": "integer", "format": "int64", "minimum": 0 },
                    "customer_praises": { "type": "integer", "format": "int64", "minimum": 0 },
                    "services_invested": { "type": "integer", "format": "int64", "minimum": 0 },
                    "requests_raised": { "type": "integer", "format": "int64", "minimum": 0 },
                    "requests_accepted": { "type": "integer", "format": "int64", "minimum": 0 },
                    "requests_declined": { "type": "integer", "format": "int64", "minimum": 0 },
                    "requests_lapsed": { "type": "integer", "format": "int64", "minimum": 0 },
                    "reputation": {
                        "type": "number",
                        "format": "double",
                        "description": "Current reputation, plus the min/max the run has visited."
                    },
                    "reputation_min": { "type": "number", "format": "double" },
                    "reputation_max": { "type": "number", "format": "double" },
                    "avg_satisfaction": {
                        "type": "number",
                        "format": "double",
                        "description": "Current mean customer satisfaction across active customers (0 when no\ncustomers). The headline \"how is the relationship base doing\" reading."
                    },
                    "active_customers": { "type": "integer", "format": "int32", "minimum": 0 }
                }
            },
            "MacroEventView": {
                "type": "object",
                "required": [
                    "id",
                    "kind",
                    "display_name",
                    "started_at_ns",
                    "ends_at_ns",
                    "price_multipliers",
                    "lead_time_multiplier",
                    "egress_cost_multiplier",
                    "power_cost_multiplier",
                    "customer_acquisition_multiplier",
                    "forces_multi_region",
                    "broadband_churn_risk"
                ],
                "properties": {
                    "id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "kind": {
                        "type": "string",
                        "description": "Variant tag — \"NandShortage\" / \"GpuAllocationCrisis\" / etc."
                    },
                    "display_name": { "type": "string", "description": "Human display name." },
                    "started_at_ns": { "type": "integer", "format": "int64", "minimum": 0 },
                    "ends_at_ns": { "type": "integer", "format": "int64", "minimum": 0 },
                    "price_multipliers": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/MacroPriceMultiplier" },
                        "description": "Capex multipliers per host-shape category. The buy panel reads\nthis when stamping shop prices."
                    },
                    "lead_time_multiplier": { "type": "number", "format": "float" },
                    "egress_cost_multiplier": { "type": "number", "format": "float" },
                    "power_cost_multiplier": { "type": "number", "format": "float" },
                    "customer_acquisition_multiplier": { "type": "number", "format": "float" },
                    "forces_multi_region": { "type": "boolean" },
                    "broadband_churn_risk": { "type": "boolean" }
                }
            },
            "MacroPriceMultiplier": {
                "type": "object",
                "required": ["category", "multiplier"],
                "properties": {
                    "category": { "type": "string" },
                    "multiplier": { "type": "number", "format": "float" }
                }
            },
            "MetricDef": {
                "type": "object",
                "description": "One declared metric. Name, help text, and kind live together so they cannot\ndrift apart.",
                "required": ["name", "help", "kind", "unit"],
                "properties": {
                    "name": { "type": "string" },
                    "help": { "type": "string" },
                    "kind": { "$ref": "#/components/schemas/MetricKind" },
                    "unit": {
                        "type": "string",
                        "description": "Unit, for humans reading the catalog. Prometheus itself infers nothing\nfrom this; the name suffix is what conventionally carries it."
                    }
                }
            },
            "MetricKind": {
                "type": "string",
                "description": "Metric kind, as Prometheus understands it.",
                "enum": ["gauge", "counter"]
            },
            "MetricPaths": {
                "type": "object",
                "description": "`GET /api/v1/metrics/paths` body.",
                "required": ["paths", "total_paths", "truncated"],
                "properties": {
                    "paths": { "type": "array", "items": { "type": "string" } },
                    "total_paths": {
                        "type": "integer",
                        "description": "Paths the registry reported before the capture cap was applied.",
                        "minimum": 0
                    },
                    "truncated": {
                        "type": "boolean",
                        "description": "True when `paths` is incomplete because the world has more series than\nthe per-publish cap."
                    }
                }
            },
            "MetricSeries": {
                "type": "object",
                "description": "One captured series, already decoded.",
                "required": ["path", "values", "scale", "unit"],
                "properties": {
                    "path": { "type": "string" },
                    "values": {
                        "type": "array",
                        "items": { "type": "number", "format": "float" },
                        "description": "Decoded samples, oldest first."
                    },
                    "scale": {
                        "type": "string",
                        "description": "The quantization rule the raw ring used, as a stable tag. Present even\nthough `values` is already decoded, because a consumer graphing this\nstill wants to know whether it is looking at Gbps, a percentage, or\nmilliseconds."
                    },
                    "unit": {
                        "type": "string",
                        "description": "Human unit implied by `scale`, so a dashboard can label an axis without\nhardcoding a mapping."
                    }
                }
            },
            "MigrationStatusView": {
                "type": "object",
                "description": "Live status of an in-progress relocation migration window — drives the HUD\ncountdown bar while carried tenants re-home on the fresh floor after a move.\n`active` is false outside a migration (client hides the widget); it also\nflips false early once every tenant is back Active.",
                "required": [
                    "active",
                    "seconds_left",
                    "window_seconds",
                    "tenants_remaining",
                    "site_display_name"
                ],
                "properties": {
                    "active": { "type": "boolean" },
                    "seconds_left": {
                        "type": "number",
                        "format": "double",
                        "description": "Seconds left in the window (0 once elapsed)."
                    },
                    "window_seconds": {
                        "type": "number",
                        "format": "double",
                        "description": "Full window length in seconds — the denominator for the bar fraction."
                    },
                    "tenants_remaining": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Tenants still re-homing (parked in the migration grace).",
                        "minimum": 0
                    },
                    "site_display_name": {
                        "type": "string",
                        "description": "Display name of the site being migrated into."
                    }
                }
            },
            "MsgArgView": {
                "type": "object",
                "description": "One argument for a localized message.\n\nThe engine sends the MEANING (a stable key) plus the FACTS (these args);\nthe client picks the words. See `crates/sim-i18n`.\n\nShape is Vec-of-typed-pairs, not a map, deliberately: capnp has no map\nconstruct, schema-gen has no map type token (it would emit `Variant` plus a\nbuild warning), and `Vec<PairView>` is the established house idiom\n(`MacroPriceMultiplier`, `GlossaryTermView`, `EntityKindLabelView`).\n\n`is_number` picks the live slot. Numbers must stay numbers across the wire\nbecause Fluent needs a real number for plural selection and locale-aware\nnumber formatting — pre-stringifying them here would bake in en-US grouping\nand break `{ $n ->  [one] ... }` in every language that inflects.",
                "required": ["key", "text", "number", "is_number"],
                "properties": {
                    "key": {
                        "type": "string",
                        "description": "Placeholder name as it appears in the FTL source, without the `$`."
                    },
                    "text": {
                        "type": "string",
                        "description": "Live when `is_number` is false. Already-resolved proper nouns\n(customer names, site names) travel here — names are data, not\ntranslation."
                    },
                    "number": {
                        "type": "number",
                        "format": "double",
                        "description": "Live when `is_number` is true."
                    },
                    "is_number": {
                        "type": "boolean",
                        "description": "Selects which of `text` / `number` carries the value."
                    }
                }
            },
            "NetworkSegmentView": {
                "type": "object",
                "description": "A player-created network segment (VXLAN isolation overlay). Small\ntop-level list on `WorldSnapshot` (msgpack-only, like `host_pools`).\nCustomer membership is derived engine-side via `resolve_vrf`; the\nclient builds a host→segment view from `carrier_host_ids` /\n`dedicated_host_ids`.",
                "required": [
                    "id",
                    "vni",
                    "name",
                    "explicit_customer_ids",
                    "member_count",
                    "carrier_host_ids",
                    "dedicated_host_ids"
                ],
                "properties": {
                    "id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "vni": {
                        "type": "integer",
                        "format": "int32",
                        "description": "VXLAN network identifier assigned at create time.",
                        "minimum": 0
                    },
                    "name": { "type": "string" },
                    "tier_rule": {
                        "type": ["string", "null"],
                        "description": "SLA-tier auto-membership rule as a tier tag (\"Bronze\"..\"Platinum\"),\nor `None` when the segment only gathers explicit pins."
                    },
                    "explicit_customer_ids": {
                        "type": "array",
                        "items": { "type": "integer", "format": "int32", "minimum": 0 },
                        "description": "Customers explicitly pinned to this segment."
                    },
                    "member_count": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Count of customers currently resolving to this segment (explicit\npins + tier-rule matches).",
                        "minimum": 0
                    },
                    "carrier_host_ids": {
                        "type": "array",
                        "items": { "type": "integer", "format": "int32", "minimum": 0 },
                        "description": "Distinct hosts carrying this segment's members' workloads — the\nderived VTEP set, used for the \"touches N hosts\" blast-radius readout."
                    },
                    "dedicated_host_ids": {
                        "type": "array",
                        "items": { "type": "integer", "format": "int32", "minimum": 0 },
                        "description": "Hosts whose `admit` is this segment (physically dedicated)."
                    },
                    "dedicated_host_count": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Number of hosts dedicated to this segment. `0` = logical-only (the\ncustomer lands anywhere in the fleet, so the prospect card reads the\nfleet meter, not these figures). Same set as `dedicated_host_ids.len()`.",
                        "minimum": 0
                    },
                    "dedicated_free_vcpu_milli": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Free physical vCPU (millivcpu) summed over the dedicated host-set —\n`free_millivcpu` per host, the SAME accessor the per-host `HostView`\ncapacity reports, so a customer confined to this segment reads a meter\ncomparable to the fleet's. `0` when logical-only.",
                        "minimum": 0
                    },
                    "dedicated_total_vcpu_milli": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Total (eff/wear-adjusted nominal) vCPU (millivcpu) of the dedicated\nhost-set. `0` when logical-only.",
                        "minimum": 0
                    },
                    "dedicated_free_mem_mb": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Free memory (MB) summed over the dedicated host-set (`free_mb`). `0`\nwhen logical-only.",
                        "minimum": 0
                    },
                    "dedicated_total_mem_mb": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Total (eff) memory (MB) of the dedicated host-set. `0` when logical-only.",
                        "minimum": 0
                    },
                    "dedicated_free_disk_gb": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Free SSD (GB) summed over the dedicated host-set (`free_ssd_gb`). `0`\nwhen logical-only.",
                        "minimum": 0
                    },
                    "dedicated_total_disk_gb": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Total (eff) SSD (GB) of the dedicated host-set. `0` when logical-only.",
                        "minimum": 0
                    }
                }
            },
            "Page_ApplianceView": {
                "type": "object",
                "description": "Wrapper for a paginated collection.\n\nEvery collection endpoint returns this. Unbounded collection responses are\nnot an option: a late-campaign save has thousands of hosts, and serializing\nall of them would occupy the single tokio thread to produce a body nobody\nwants.",
                "required": ["items", "total", "offset", "count"],
                "properties": {
                    "items": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "required": [
                                "id",
                                "rack_id",
                                "start_u",
                                "u_size",
                                "appliance_kind",
                                "sku_name",
                                "fabric_capacity_gbps",
                                "indicator"
                            ],
                            "properties": {
                                "id": { "type": "integer", "format": "int32", "minimum": 0 },
                                "display_name": {
                                    "type": "string",
                                    "description": "Player-facing identity like `fw-01` / `ids-01` / `lb-01` (per-kind\nsequence). Empty on legacy saves whose appliances pre-date the\ntwo-voice naming pass; clients fall back to `kind` + `id`."
                                },
                                "display_name_plain": {
                                    "type": "string",
                                    "description": "Plain-mode counterpart: `firewall 1` / `intrusion detector 1`.\nSee `HostView::display_name_plain`."
                                },
                                "rack_id": { "type": "integer", "format": "int32", "minimum": 0 },
                                "start_u": { "type": "integer", "format": "int32", "minimum": 0 },
                                "u_size": { "type": "integer", "format": "int32", "minimum": 0 },
                                "az_id": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Availability zone this appliance sits in. Host / Switch / Gateway all\ncarry one; the appliance corner did not, so nothing could scope the\nsecurity estate per AZ without re-deriving it from `rack_id`.\nResolved from the rack for a racked box, and from the HOST GATEWAY for\na `gateway_addon` (which has no rack of its own). 0 when neither\nresolves. Four-corner parity — `docs/SCHEMA.md` § Network entities.",
                                    "minimum": 0
                                },
                                "inv_status": {
                                    "type": "string",
                                    "description": "Lifecycle status — see `HostView::inv_status`. Racked appliances are the\nonly ones in this array (carried / loose surface via `carried_items`),\nso in practice this reads `\"Mounted\"` or `\"SellQueued\"`; it exists so a\nclient can tell a box awaiting collection from a working one without a\nsecond lookup. Four-corner parity with Host / Switch / PatchPanel."
                                },
                                "appliance_kind": {
                                    "type": "string",
                                    "description": "Spec-aligned kind tag (`\"firewall\"` / `\"ids\"` / `\"waf\"` /\n`\"ddos_scrubber\"` / `\"vpn\"` / `\"loadbalancer\"` / `\"hsm\"` /\n`\"siem\"`). Drives icon + tone. Per `docs/SCHEMA.md` § Appliance."
                                },
                                "sku_name": { "type": "string" },
                                "gateway_addon": {
                                    "type": "boolean",
                                    "description": "True for gateway ADD-ONS (`ApplianceMount::GatewayAddon`) — license\nmodules living inside a gateway, not racked boxes. They have no\nrack position, no ports, and (since the addon-mitigation fold)\ntheir per-class work is attributed to the HOST GATEWAY's defender\nentry, so their own drop/event meters legitimately read 0.\nStandalone-appliance UI (the IDS performance board, SIEM presence,\nrack rendering) must skip these — the gateway console owns their\npresentation."
                                },
                                "fabric_capacity_gbps": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Nameplate forwarding ceiling — wire-rate throughput when the\ninspection plane is bypassed. Compared against `fabric_load_gbps`.",
                                    "minimum": 0
                                },
                                "fabric_load_gbps": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Smoothed full-duplex fabric load in Gbps (= `egress_gbps +\ningress_gbps`, independently smoothed). The saturation metric;\nwhen this exceeds `fabric_capacity_gbps` the forwarding plane\ndrops packets regardless of inspection state."
                                },
                                "fabric_saturated": {
                                    "type": "boolean",
                                    "description": "Forwarding-plane saturation edge: `fabric_load_gbps` over the appliance's\ninspection-REDUCED effective forwarding capacity (deep inspection eats\nforwarding headroom). Engine-computed in the appliance fabric pass for\nIN-PATH inspectors that actually forward; an in-path box in this state\ndrops customer traffic transiting it (facility-wide, folded into the same\ndrop path as switch/uplink saturation) and fires an\n`ApplianceFabricSaturated` event + incident on the rising edge — like\nSwitch/Gateway/Uplink. Off-path taps (HSM/SIEM) never forward, so this\nstays false for them."
                                },
                                "forwarding_ceiling_gbps": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "The appliance's effective FORWARDING ceiling with deep inspection's\npenalty applied — `fabric_load_gbps` is the load against it, and it IS\nthe threshold `fabric_saturated` flips on. Full `fabric_capacity_gbps`\nwhen inspection is bypassed/off/ceiling-at-or-above-fabric; collapses\ntoward `inspection_capacity_gbps` as inspection engages. Mirrors\n`GatewayView::forwarding_ceiling_gbps` (same shared curve,\n`sim_core::security::effective_forwarding_capacity_gbps`, same reason:\na client dividing `fabric_load_gbps` by the raw nameplate\n`fabric_capacity_gbps` disagreed with the engine's own saturation\nverdict). Single source of truth:\n`Appliance::effective_forwarding_capacity_gbps()`. Never re-derive it\nrenderer-side."
                                },
                                "egress_gbps": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Smoothed directional egress in Gbps (traffic leaving the\nappliance toward the LAN side). Pairs with `ingress_gbps`.\nUntil the engine ships a directional split on appliance cables,\nthis is populated with the legacy half-duplex flow figure for\nbackward compat."
                                },
                                "ingress_gbps": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Smoothed directional ingress in Gbps (traffic arriving from the\nWAN side). Mirror of `egress_gbps`."
                                },
                                "wan_ingress_gbps": { "type": "number", "format": "double" },
                                "wan_egress_gbps": { "type": "number", "format": "double" },
                                "lan_ingress_gbps": { "type": "number", "format": "double" },
                                "lan_egress_gbps": { "type": "number", "format": "double" },
                                "threats": {
                                    "type": "array",
                                    "items": {
                                        "$ref": "#/components/schemas/ThreatMitigationView"
                                    },
                                    "description": "Per-device threat handling — ONLY the classes THIS box defends, with\nwhat reached it, what it absorbed, and what it passed on. Replaces the\nconfusing fleet-wide read: a firewall shows Protocol only, an IDS shows\nIntrusion only. Empty for off-path boxes (HSM / SIEM). From the engine\npositional cascade."
                                },
                                "ports": {
                                    "type": "array",
                                    "items": { "$ref": "#/components/schemas/PortView" },
                                    "description": "Per-port views — one entry per physical port on the appliance.\nMirrors `SwitchView.ports` / `GatewayView.ports`. Closes the\nparity gap noted in `docs/SCHEMA.md` § ApplianceView. Each port\ncarries media, state, link capacity, live throughput, peer\nreference; the diegetic inspector + topology view both consume\nthis."
                                },
                                "inspection_capacity_gbps": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Rated DPI ceiling (Gbps). Typically a fraction of\n`fabric_capacity_gbps` — e.g. a 10 G appliance might inspect\nat 3 G. When `inspection_enabled` is true and live throughput\nexceeds this, the appliance either bypasses (drops to forwarding\nrate) or drops packets, depending on `inspection_state`.",
                                    "minimum": 0
                                },
                                "inspection_load_gbps": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Smoothed live throughput **actually deep-inspected**. Distinct\nfrom `egress_gbps` (total pass-through). When inspection is\nbypassed this is 0 even while pass-through is high."
                                },
                                "inspection_state": {
                                    "type": "string",
                                    "description": "`\"Active\"` (inspecting), `\"Bypass\"` (line-rate forwarding, no\ninspection), or `\"Failed\"` (engine forced drop — DPI overload).\nDrives the inspection-plane LED color on the appliance pill."
                                },
                                "inspection_saturated": {
                                    "type": "boolean",
                                    "description": "Edge-triggered: `inspection_load_gbps > inspection_capacity_gbps`.\nEven when fabric still has headroom, this fires when the DPI\nengine itself overloads."
                                },
                                "inspection_enabled": {
                                    "type": "boolean",
                                    "description": "Whether deep inspection is currently on (the bypass lever, toggled by\n`SetApplianceInspection`)."
                                },
                                "enabled_functions": {
                                    "type": "array",
                                    "items": { "type": "string" },
                                    "description": "Enabled security functions on this box as slugs (\"firewall\"/\"ips\"/\n\"scrub\") — the base plus any licensed-on. Dynamic (changes only on a\nlicense toggle). The console joins these with the catalog's per-SKU\nlicense options (`ApplianceSkuView.licenses`) to render the toggle rows\n+ costs — the static option data is NOT duplicated per tick."
                                },
                                "owned_functions": {
                                    "type": "array",
                                    "items": { "type": "string" },
                                    "description": "Function-license slugs whose one-time capex has already been paid on this\nbox (the ownership ledger — superset of `enabled_functions`). An owned\nlicense that's been unsubscribed (not in `enabled_functions`) can be\nre-enabled for FREE; the console reads this to show `Resubscribe (free)`\nvs `Buy $X`."
                                },
                                "dropped_per_sec": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Live filtering throughput, latest ring sample. ONE FIELD PER ATTACK\nCLASS, in that class's own unit — never combined, and a dual-licensed\n(Scrub + Firewall) box populates more than one:\n- `dropped_per_sec` — stateful-firewall Protocol drops, **Kpps**\n- `scrubbed_gbps` — scrubber Volumetric absorb, **Gbps**\n- `events_per_sec` — IDS/IPS Intrusion detections, **events/s**\n\nA box without the matching licence enabled reports 0 for that class.\nOff-path kinds (HSM/SIEM) report 0 for all three. Abstract magnitudes\n(spec §9.1)."
                                },
                                "events_per_sec": { "type": "number", "format": "double" },
                                "scrubbed_gbps": { "type": "number", "format": "double" },
                                "indicator": { "$ref": "#/components/schemas/IndicatorLed" },
                                "monthly_opex_dollars": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Live projected monthly opex ($/mo) — running fee + chassis power +\nseated-optic draw. Mirrors the host console's read."
                                },
                                "powered": {
                                    "type": "boolean",
                                    "description": "At least one AC cord seated (or none required). False = dark\nuntil the player plugs it in. Mirrors the engine's `powered`."
                                },
                                "power_feeds": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Connected AC cord count. >= 2 = PSU-failure resilience at\n`redundant_psu_overhead_w` extra draw per extra cord.",
                                    "minimum": 0
                                }
                            }
                        }
                    },
                    "total": {
                        "type": "integer",
                        "description": "Total matching items before `limit`/`offset` were applied.",
                        "minimum": 0
                    },
                    "offset": {
                        "type": "integer",
                        "description": "Offset this page started at.",
                        "minimum": 0
                    },
                    "count": {
                        "type": "integer",
                        "description": "Number of items returned. Always `<= limit` and `<= MAX_LIMIT`.",
                        "minimum": 0
                    }
                }
            },
            "Page_AzView": {
                "type": "object",
                "description": "Wrapper for a paginated collection.\n\nEvery collection endpoint returns this. Unbounded collection responses are\nnot an option: a late-campaign save has thousands of hosts, and serializing\nall of them would occupy the single tokio thread to produce a body nobody\nwants.",
                "required": ["items", "total", "offset", "count"],
                "properties": {
                    "items": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "required": [
                                "id",
                                "region_id",
                                "name",
                                "status",
                                "rack_count",
                                "host_count"
                            ],
                            "properties": {
                                "id": { "type": "integer", "format": "int32", "minimum": 0 },
                                "region_id": { "type": "integer", "format": "int32", "minimum": 0 },
                                "name": { "type": "string" },
                                "status": { "$ref": "#/components/schemas/AzVisual" },
                                "rack_count": {
                                    "type": "integer",
                                    "format": "int32",
                                    "minimum": 0
                                },
                                "host_count": {
                                    "type": "integer",
                                    "format": "int32",
                                    "minimum": 0
                                },
                                "draped_cable_count": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Cables touching residents of this AZ that have no managed routing\npath. The Engineering View / prospect tour penalty surfaces this.",
                                    "minimum": 0
                                }
                            }
                        }
                    },
                    "total": {
                        "type": "integer",
                        "description": "Total matching items before `limit`/`offset` were applied.",
                        "minimum": 0
                    },
                    "offset": {
                        "type": "integer",
                        "description": "Offset this page started at.",
                        "minimum": 0
                    },
                    "count": {
                        "type": "integer",
                        "description": "Number of items returned. Always `<= limit` and `<= MAX_LIMIT`.",
                        "minimum": 0
                    }
                }
            },
            "Page_CableView": {
                "type": "object",
                "description": "Wrapper for a paginated collection.\n\nEvery collection endpoint returns this. Unbounded collection responses are\nnot an option: a late-campaign save has thousands of hosts, and serializing\nall of them would occupy the single tokio thread to produce a body nobody\nwants.",
                "required": ["items", "total", "offset", "count"],
                "properties": {
                    "items": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "required": ["from_host", "to_switch", "kind", "length_m"],
                            "properties": {
                                "from_host": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Legacy host↔switch fields. Populated for host-link cables for\nback-compat with the pre-Phase-B renderer. New cable types\n(switch↔edd, future player-laid) leave these as 0.",
                                    "minimum": 0
                                },
                                "to_switch": { "type": "integer", "format": "int32", "minimum": 0 },
                                "kind": { "type": "string" },
                                "dac_class": {
                                    "type": "string",
                                    "description": "Twinax class (\"Passive\" / \"Active\"). Only meaningful for DAC kinds;\nsurfaced so resuming a dangling active-DAC cable client-side sees\nthe right 15m reach (instead of the 3m passive baseline)."
                                },
                                "color": {
                                    "type": "string",
                                    "description": "Player-chosen jacket colour id (\"Orange\" / \"Blue\" / …). Always a\nconcrete colour — the projection resolves the cable's stored choice\nor its family default — so the renderer paints off it directly\n(`Tokens.cable_color_for_choice`). Empty only on legacy wire frames."
                                },
                                "duplex": {
                                    "type": "boolean",
                                    "description": "True for LC-duplex fibre patch cords (TX/RX strand pair). The\nrenderer draws two parallel strands; everything else is one tube.\nRead directly off the cable view — no catalog/timing dependency.\n\n**Meaning CHANGED in PROTOCOL 48 (UG-207).** This is now purely\n`CableKind::is_duplex()`, the property of the cable itself. It used to\nbe additionally forced false whenever EITHER end sat in a QSFP cage,\nwhich made an SFP-to-QSFP fibre run render single-tube at BOTH ends —\nincluding the SFP end, which genuinely has two LC ferrules. Cage\ngeometry now travels in [`Self::qsfp_end`] instead."
                                },
                                "qsfp_end": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Which END sits in a QSFP (multi-lane) cage: `0` neither, `1` = a,\n`2` = b, `3` = both. A QSFP cage presents one `qsfp_module.glb`\nconnector with a single boot, so the duplex strand pair converges\nthere — the same 2-into-1 the renderer already does at a fibre patch\npanel. `3` means converge at both ends, i.e. render as one tube, which\nis correct for an MPO trunk between two QSFP cages.\n\nZero is \"neither\" deliberately: `CableView` derives [`Default`] and the\nfield is `#[serde(default)]`, so a legacy frame and a default-constructed\nview both land on the value that reproduces pre-48 rendering. A `-1`\nsentinel would have defaulted to `0` and silently claimed end \"a\".\n\nAdded in PROTOCOL 48 (UG-207).",
                                    "minimum": 0
                                },
                                "length_m": { "type": "integer", "format": "int32", "minimum": 0 },
                                "id": {
                                    "type": ["integer", "null"],
                                    "format": "int32",
                                    "description": "Generalized endpoint metadata (Phase B+). Populated for *all* cables\ngoing forward, including host-links. Renderers should prefer reading\n`a` / `b` over the legacy `from_host` / `to_switch` fields.",
                                    "minimum": 0
                                },
                                "a": {
                                    "oneOf": [
                                        { "type": "null" },
                                        { "$ref": "#/components/schemas/CableEndpointView" }
                                    ]
                                },
                                "b": {
                                    "oneOf": [
                                        { "type": "null" },
                                        { "$ref": "#/components/schemas/CableEndpointView" }
                                    ]
                                },
                                "lag_id": {
                                    "type": ["integer", "null"],
                                    "format": "int32",
                                    "description": "LAG (link aggregation) membership. Cables sharing this id form a\nbonded link. `None` = standalone cable.",
                                    "minimum": 0
                                },
                                "routed": {
                                    "type": "boolean",
                                    "description": "`true` when this inter-switch link runs L3/Routed (ECMP) vs L2/\nBridged (STP). Authoritative owner is `Cable.link_mode`. Lets the\nswitch console show + toggle each link's forwarding mode."
                                },
                                "mode_pinned": {
                                    "type": "boolean",
                                    "description": "`true` when the player pinned this link's mode in the console (so\nit's not auto-managed). `false` = Auto. With `routed` this gives the\nconsole the tri-state Auto / L2 / L3."
                                },
                                "breakout_id": {
                                    "type": ["integer", "null"],
                                    "format": "int32",
                                    "description": "Breakout membership. Legs sharing this id fan out from one QSFP\ntrunk port — the renderer draws a single trunk run that splits to\neach leg's far port. `None` = ordinary point-to-point cable.\n(Tiny `Option<u32>` mirror of `lag_id`; delta/capnp treat it like\nany other scalar — no per-tick allocation.)",
                                    "minimum": 0
                                },
                                "routed_via": {
                                    "type": "array",
                                    "items": { "type": "integer", "format": "int32", "minimum": 0 },
                                    "description": "Tray segments / hooks / risers this cable runs through, in order\nfrom `a` to `b`. Empty = draped (no managed path)."
                                },
                                "path": {
                                    "type": "array",
                                    "items": { "$ref": "#/components/schemas/RouteAnchor" },
                                    "description": "Full ordered anchor sequence the cable runs through, port A →\nport B — `sim_core::cable::RouteAnchor`, THE one anchor\nvocabulary (client-send, engine-store, engine-emit; see the\n2026-07-27 RouteAnchor/WaypointView unification). First and\nlast anchor are always `Port`. Middle anchors describe in-rack\nmanagement (`Strip`), rack-edge transitions (`Edge`),\nside-panel rail holes (`RailHole`), and inter-rack runs\n(`Tray`). This is already the FULL resolved route — the engine\nexpands strip runs into their marker/channel sequence\n(`expand_strip_chains_rack`) before emitting, so the renderer\nonly ever draws between consecutive anchors, never re-derives\nwhich ones to visit. When this is shorter than 3 entries (just\nthe two endpoint ports), the renderer treats the cable as\nfreely draped — same as the legacy `routed_via.is_empty()` case."
                                },
                                "route_slots": {
                                    "type": "array",
                                    "items": { "type": "integer", "format": "int32", "minimum": 0 },
                                    "description": "Per-segment bundle slot — same length as `routed_via`. Each entry\nis this cable's slot index (0-based) inside the corresponding tray\nsegment, computed deterministically by sorting cable ids that share\nthe segment. Renderer combines slot + tray cross-section + layout\npattern into a lateral offset so cables don't visually overlap."
                                },
                                "routed_points": {
                                    "type": "array",
                                    "items": {
                                        "type": "array",
                                        "items": { "type": "number", "format": "float" }
                                    },
                                    "description": "Raw world-space waypoints (from `RouteAnchor::Point`) — scene\nauthored tray runs the cable threads through. Spliced into `path`\nas `RouteAnchor::Point`."
                                },
                                "is_draped": {
                                    "type": "boolean",
                                    "description": "True iff `routed_via` is empty — convenience for renderers that\nwant to draw draped cables differently without inspecting the list."
                                },
                                "is_failed": {
                                    "type": "boolean",
                                    "description": "True iff this cable is currently in a transient failed state\n(`Cable::is_failed(now)` — gates traffic engine-side). Renderer\ncolours failed cables red."
                                },
                                "routing_side_a": {
                                    "type": "string",
                                    "description": "Player-chosen cable management path for each end — \"LeftMgr\" /\n\"RightMgr\" / \"VertRiser\" / \"RackTop\" per `cable::RoutingSide`.\nEach end of the cable runs through its own rack's manager\nstrip, so the two can differ. Defaults to \"RightMgr\"."
                                },
                                "routing_side_b": { "type": "string" },
                                "dangling_end": {
                                    "type": ["string", "null"],
                                    "description": "`Some(\"Source\")` or `Some(\"Target\")` when one end of this\ncable has been yanked out of its terminating port. The cable\nstays in the world; the renderer paints the loose tip."
                                }
                            }
                        }
                    },
                    "total": {
                        "type": "integer",
                        "description": "Total matching items before `limit`/`offset` were applied.",
                        "minimum": 0
                    },
                    "offset": {
                        "type": "integer",
                        "description": "Offset this page started at.",
                        "minimum": 0
                    },
                    "count": {
                        "type": "integer",
                        "description": "Number of items returned. Always `<= limit` and `<= MAX_LIMIT`.",
                        "minimum": 0
                    }
                }
            },
            "Page_CustomerView": {
                "type": "object",
                "description": "Wrapper for a paginated collection.\n\nEvery collection endpoint returns this. Unbounded collection responses are\nnot an option: a late-campaign save has thousands of hosts, and serializing\nall of them would occupy the single tokio thread to produce a body nobody\nwants.",
                "required": ["items", "total", "offset", "count"],
                "properties": {
                    "items": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "required": [
                                "id",
                                "name",
                                "archetype",
                                "tier",
                                "kind_label",
                                "lifecycle",
                                "health",
                                "block_quality",
                                "revenue_total_dollars",
                                "revenue_last_hour_dollars",
                                "availability_pct",
                                "cpu_load_pct",
                                "latency_top_term"
                            ],
                            "properties": {
                                "id": { "type": "integer", "format": "int32", "minimum": 0 },
                                "name": { "type": "string" },
                                "archetype": { "type": "string" },
                                "tier": { "type": "string" },
                                "kind_label": { "type": "string" },
                                "lifecycle": { "type": "string" },
                                "pending_until_ns": {
                                    "type": ["integer", "null"],
                                    "format": "int64",
                                    "minimum": 0
                                },
                                "health": { "type": "number", "format": "double" },
                                "block_quality": { "type": "number", "format": "double" },
                                "revenue_total_dollars": { "type": "number", "format": "double" },
                                "revenue_last_hour_dollars": {
                                    "type": "number",
                                    "format": "double"
                                },
                                "availability_pct": { "type": "number", "format": "double" },
                                "p99_latency_ms": {
                                    "type": ["number", "null"],
                                    "format": "double",
                                    "description": "p99 request latency over the LONG window (`CustomerState::latency`,\n`tuning.latency_window_capacity` samples at one per sim second, so\nroughly the worst 10 samples of the last ~16.7 sim minutes). This is the\nfigure the SLA is measured against — `sla_p99_budget_ms` compares to\nTHIS, and the engine's breach/credit path reads the same window — so it\nis deliberately sticky and must never be relabelled \"now\". Pair it with\n`p99_recent_ms` for the responsive figure."
                                },
                                "p99_recent_ms": {
                                    "type": ["number", "null"],
                                    "format": "double",
                                    "description": "p99 request latency over the SHORT window (`CustomerState::latency_recent`,\n`tuning.latency_recent_capacity` samples, ~2 sim minutes) — cached by the\nengine once per metrics tick as `CustomerState::recent_p99_ms`, which is\nalso what the incident anomaly machine opens and recovers on.\n\nThis is the \"right now\" number. The long window above pins one spike for\nup to ~16 sim minutes, which read to players as \"customers returning to\nnormal latency taking ages\" while the incident had already closed. Both\nship so the readout can be honest about which is which: current vs the\nwindow the SLA is actually scored on.\n\n`None` until the first metrics tick after load (`load_reported` clears\nthe cached value because it describes a window that no longer exists).\nWhole milliseconds — the engine caches it as `u64` ms and re-deriving it\nhere would mean a clone+sort of the window per customer per snapshot."
                                },
                                "cpu_load_pct": {
                                    "type": "number",
                                    "format": "float",
                                    "description": "Offered CPU as a percentage of what this tenant CONTRACTED, uncapped, so\nit reads past 100 when they are asking for more than they rent.\n\nThis is the number that explains a latency spike on hosts that look idle.\nA VM queues on its OWN vCPU ceiling, and the queue curve bites from\n`queue_util_knee` (70%) upward: 25 ms at 95%, 168 ms at 99%, ~1990 ms at\nthe clamp. Host utilisation is a DIFFERENT quantity and does not move\nwith it, which is why a player checking the host map after a p99 spike\nfinds nothing wrong (player report, Mateo Bank 2026-07-28). 0 when the\ntenant has no placed VMs."
                                },
                                "latency_top_term": {
                                    "type": "string",
                                    "description": "Pre-rendered dominant latency term, e.g. \"queue 940.2 ms (94%)\", or empty\nwhen nothing has been sampled yet.\n\nThe engine already attributes latency across serve floor / fabric /\nqueue / CPU contention / attack / backbone / hairpin / cold start and\nranks them, but that breakdown only ever reached the INCIDENT card. The\ncustomer panel is where a player asks \"why is this tenant slow\", so the\nleading term belongs here too. Formatted render-side because the client\nhas no business re-deriving shares."
                                },
                                "p50_latency_ms": {
                                    "type": ["number", "null"],
                                    "format": "double",
                                    "description": "Median (p50) request latency in milliseconds — the \"typical\"\nnumber players read as \"how's this customer doing\", distinct from\nthe p99 tail. Float, so lightly-loaded customers don't all collapse\nto the same whole-ms value. `None` until the window has samples."
                                },
                                "rps": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Smoothed requests per second across this customer's workloads.\nMirrors `CustomerState.rps`. Drives the inspector's \"Acme Co.\n— 4 req/s\" plain-English read."
                                },
                                "failed_requests_lifetime": {
                                    "type": "integer",
                                    "format": "int64",
                                    "description": "Cumulative failed-request count across the customer's services\n(`AvailabilityStats.requests_failed`).",
                                    "minimum": 0
                                },
                                "total_requests_lifetime": {
                                    "type": "integer",
                                    "format": "int64",
                                    "description": "Total request count across the same window. Pairs with\n`failed_requests_lifetime` to compute the recent failure rate.",
                                    "minimum": 0
                                },
                                "failed_last_hour": {
                                    "type": "integer",
                                    "format": "int64",
                                    "description": "Failed requests since the last hourly snapshot — the delta the\ninspector reads as \"12 errors this hour.\" Reset by the engine\neach hour boundary (`failed_at_hour_snapshot`).",
                                    "minimum": 0
                                },
                                "sla_p99_budget_ms": {
                                    "type": ["integer", "null"],
                                    "format": "int64",
                                    "description": "Latency-budget tier — milliseconds the customer's SLA tolerates\nat p99. `None` for Bronze (no latency budget).",
                                    "minimum": 0
                                },
                                "in_latency_spike": {
                                    "type": "boolean",
                                    "description": "True when the customer is currently in a sustained latency\nspike (engine edge-trigger). Drives an SLA-breach pill in the\ninspector."
                                },
                                "on_probation": {
                                    "type": "boolean",
                                    "description": "True when the customer is on probation — engine flag for\n\"they're seriously considering churn.\" The renderer can show\nthis with an at-risk pill."
                                },
                                "sla_credits_dollars": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "SLA-breach credits accumulated this billing period (issued back\nto the customer; net negative for the provider). Lifetime sum."
                                },
                                "egress_gbps": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Smoothed bandwidth attributed to this customer across their\nservice mix. Egress = response bodies / GET fan-out / etc.;\ningress = request bodies / PUT uploads / replication writes.\nReal workloads are asymmetric (image hosting = egress-heavy;\nbackup ingest = ingress-heavy) and surfacing both reflects\nthat."
                                },
                                "ingress_gbps": { "type": "number", "format": "double" },
                                "service_kinds": {
                                    "type": "array",
                                    "items": { "type": "string" },
                                    "description": "Short-code service mix: subset of {\"fn\", \"vm\", \"obj\", \"lb\", \"db\",\n\"cdn\", \"k8s\"} for the services this customer runs. Used by the\ndashboard's Services drilldown to count tenants per service kind\nand by the customer wall to render service-kind chips."
                                },
                                "stranded_unit_count": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Stranded VMs this customer owns — pinned to an unreachable\nhost with no auto-failover. Drives the red \"N VMs stranded\"\nbadge on the ops-console customer row and the comms-panel\nincident card's `[Migrate]` quick-action.",
                                    "minimum": 0
                                },
                                "unit_count": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "VM placement spread — the anti-affinity story made legible.\n`vm_count` total VMs this customer runs; `vm_distinct_hosts` how\nmany separate chassis they're spread across; `vm_max_on_host` the\nmost this customer has on any single host (the worst-case \"one\nmachine dies\" blast radius). Anti-affinity raises distinct_hosts\nand lowers max_on_host; bin-packing does the opposite.",
                                    "minimum": 0
                                },
                                "distinct_hosts": {
                                    "type": "integer",
                                    "format": "int32",
                                    "minimum": 0
                                },
                                "max_units_on_host": {
                                    "type": "integer",
                                    "format": "int32",
                                    "minimum": 0
                                },
                                "satisfaction": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Composite customer satisfaction in `0.0..=1.0` — folds health,\nrelationship trust, and long-term sentiment into one player-facing\n\"how's this relationship\" number (see `CustomerState::satisfaction`)."
                                },
                                "satisfaction_label": {
                                    "type": "string",
                                    "description": "Plain-words band for `satisfaction` (Delighted / Happy / Content /\nUnsettled / At risk)."
                                },
                                "under_attack": {
                                    "type": "boolean",
                                    "description": "True when a malicious attack is currently landing on THIS tenant — the\nnamed victim of a targeted flood, or anyone the per-tenant impact pass is\nfailing requests for. Drives the \"UNDER ATTACK\" risk pill so the player\nsees which tenant an attack is hurting, not just a fleet-wide board."
                                },
                                "attack_class": {
                                    "type": "string",
                                    "description": "The attack class hitting this tenant (Flood / Connection flood / App flood\n/ Break-in attempts), empty when not under attack — for the pill caption."
                                },
                                "breached": {
                                    "type": "boolean",
                                    "description": "True while this tenant is in a BREACHED state — a sustained, unmitigated\nintrusion compromised them. Drives the \"BREACHED\" risk pill; distinct from\n(and more severe than) an availability incident."
                                },
                                "services": {
                                    "type": "array",
                                    "items": { "$ref": "#/components/schemas/ServiceDemandView" },
                                    "description": "Per-service demand-vs-fulfilment breakdown — one entry per service\nthis customer is contracted for. Item 5 / Phase 5 of\n`tasks/service_health_incidents_rework_2026_07.md`: makes the gap\nbetween what a customer asked for and what's actually live legible\neverywhere, instead of only inferable from a health percentage.\nReads `Engine::service_probe` for state/reason (Phase 1's spine) so\nthis view never re-derives feasibility/placement math.\n\n**Populated only when this session asked for THIS customer's detail**\n(`Request::SubscribeCustomer`), or for any reader that did not ask at\nall — the HTTP API mirror, the TUI and tests all project at\n`CustomerDetail::All`. A game session carries it for the tenant whose\nconsole is open and nobody else. Read `detail_omitted`, never\n`services.is_empty()`, to tell \"not sent\" from \"has no services\".\n\nIt is the expensive half of the section: `services` plus\n`service_margins` is ~28% of the customers bytes, and the customers\nsection is 57.2% of a realistic baseline once the comms work landed."
                                },
                                "detail_omitted": {
                                    "type": "boolean",
                                    "description": "`true` when `services` and `service_margins` were WITHHELD from this\nsession rather than being genuinely empty.\n\nSame rule, and the same reason, as `ChannelView::is_preview`: a\nreader that infers \"this tenant has no services\" from an empty vector\ndraws an empty console for a tenant with a full estate, and the failure\nis silent. A customer really can have zero priced services, so the\nvector's emptiness cannot carry this."
                                },
                                "provisioned_total": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Units this tenant has PLACED, summed across every service class.\n\nAlways sent, whatever `detail_omitted` says. It is the roster's entire\nneed from `services` (`customers_view.gd` sums `provisioned_count` and\n`requested_count` over the vector for its capacity-shortfall badge), so\nshipping two integers lets the list keep working while the array it\nused to walk stays behind the subscription.",
                                    "minimum": 0
                                },
                                "requested_total": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Units this tenant CONTRACTED for, summed across every service class.\nThe other half of the roster's shortfall badge; see\n`provisioned_total`.",
                                    "minimum": 0
                                },
                                "compliance_required": {
                                    "type": "string",
                                    "description": "Compliance regime the customer's contract requires (SOC2/HIPAA/PCI), or\n\"None\". Mirrors `ProspectView.compliance_required`."
                                },
                                "required_arch": {
                                    "type": "string",
                                    "description": "Raw CPU-arch requirement tag (\"x86_64\"/\"riscv\"/…) or \"\" if agnostic."
                                },
                                "requires_multi_az": { "type": "boolean" },
                                "requires_private_segment": { "type": "boolean" },
                                "demands_audit_logs": { "type": "boolean" },
                                "demands_air_gap": { "type": "boolean" },
                                "required_region": {
                                    "type": ["integer", "null"],
                                    "format": "int32",
                                    "description": "Data-residency region the contract pins to, if any.",
                                    "minimum": 0
                                },
                                "requires_gpu": { "type": "boolean" },
                                "gpu_slices_demand": {
                                    "type": "integer",
                                    "format": "int32",
                                    "minimum": 0
                                },
                                "growth_ceiling_vms": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Plateau — the max VM count this persona compounds to (0 = unknown/none).",
                                    "minimum": 0
                                },
                                "requires_dedicated_hosts": {
                                    "type": "boolean",
                                    "description": "True when the customer's contract demands its private network on\nDEDICATED, homed hosts (single-tenant iron) — the 4× isolation tier vs\n2× for a plain private network. Implies `requires_private_segment`; the\ndetail pane shows one combined chip. Mirrors `ProspectView` post-accept."
                                },
                                "requires_uncontended": {
                                    "type": "boolean",
                                    "description": "True when the customer's archetype contracts UNCONTENDED (1:1 CPU) — its\nworkloads run on hosts admitted to an `uncontended` segment (1:1, no\novercommit). A CPU guarantee, wholly independent of dedication/tenancy\nand anti-affinity spread. Mirrors `ProspectView.requires_uncontended`;\ndrives the detail \"Uncontended CPU (guaranteed 1:1)\" requirement chip."
                                },
                                "requires_dedicated": {
                                    "type": "boolean",
                                    "description": "True when the customer's archetype contracts DEDICATED, single-tenant\nhosts (private iron, no co-tenants). Tenancy guarantee, not a CPU one —\ndistinct from uncontended. Mirrors `ProspectView.requires_dedicated`;\ndrives the detail \"Dedicated host (private)\" requirement chip."
                                },
                                "eviction_fee_dollars": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Termination fee (dollars) the operator would pay to evict this customer\nright now — the tier's `eviction_penalty_frac` of one month's run-rate\n(decoupled from the SLA-outage refund), plus any unused prepay on a\nstill-active Reserved contract (capped), clamped to current cash. Shown\non the Evict action so the player sees the bill before arming it."
                                },
                                "requirements_unmet_mask": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Bitmask of the customer's ASKED contract requirements that are currently\nUNMET, so the always-on requirement chips render met (green) vs unmet\n(amber) instead of alarm-amber for every ask. Stable wire bit layout:\nbit 0 = multi-AZ, 1 = private-segment/dedicated, 2 = data-residency,\n3 = fault-isolation (spread). Derived from `Engine::unmet_requirements`;\n0 = every asked requirement is satisfied (or none is asked). A chip is\nMET when its requirement is asked (the existing demand bools) and its bit\nis clear.",
                                    "minimum": 0
                                },
                                "requires_fault_isolation": {
                                    "type": "boolean",
                                    "description": "True when the contract ASKS for fault isolation — copies of the tenant's\nworkloads kept off a shared fault domain (redundancy tier > 1 host\nreplica, or an archetype that requires anti-affinity). This is the ASK\nside of `requirements_unmet_mask` bit 3; without it a client can render\nthe spread chip UNMET but never MET, because the mask alone cannot\ndistinguish \"asked and satisfied\" from \"never asked\". Mirrors\n`ProspectView.requires_fault_isolation`."
                                },
                                "sla_period_availability_frac": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "SLA accrual, billing-period model (PROTOCOL 32) — the availability\nfraction accrued THIS billing period (since\n`CustomerState::sla_period_start`), `1 - failed/total` over\n`sla_period_requests_{total,failed}`. Guards divide-by-zero: `1.0`\n(nothing to fault yet) when no requests have accrued this period.\nThis is the EXACT figure `Engine::settle_sla_period` compares against\n`SlaTier::availability_target()` at period close — distinct from\n`availability_pct` above (the recovering rolling EWMA read for the\nhealth headline, not what the bill is judged on). Surfaced so a\ncredit on the bill is explainable instead of appearing from nowhere\n(see `memory/billing_authority_design_2026_07_27.md`)."
                                },
                                "sla_period_seconds_over_latency": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Seconds accrued THIS billing period where the short-window p99\n(`p99_recent_ms`) sat over the tier's `sla_p99_budget_ms` — mirrors\n`CustomerState::sla_period_seconds_over_latency`. Always 0 for Bronze\n(no latency budget, never accrues). Pair with\n`sla_period_elapsed_seconds` to get the fraction\n`SlaTier::latency_penalty_band` bands (2% / 10% / 30% thresholds)."
                                },
                                "sla_period_elapsed_seconds": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Sim-seconds elapsed since this billing period started accruing\n(`CustomerState::sla_period_start` to now) — the denominator for\n`sla_period_seconds_over_latency`, so the client can render \"over\nbudget N% of the period\" instead of a bare seconds count."
                                },
                                "margin_revenue_dollars": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Steady-state MONTHLY revenue for this tenant's live contract —\n`GrossMargin::revenue`, i.e. `meter_projected` + `rate`, the same pair\nthat quotes a prospect. NOT an accumulator: pairing it with\n`revenue_total_dollars` (lifetime) would compare two different months."
                                },
                                "margin_cost_dollars": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Monthly cost honestly attributable to this tenant —\n`GrossMargin::attributable_cost`. Their share of the hosts they occupy\n(including those hosts' rack-U share of the facility) plus the upstream\ntransit bought for their bytes. Excludes overhead BY CONSTRUCTION; see\n`CustomersSummary::overhead_dollars`."
                                },
                                "margin_dollars": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "`margin_revenue_dollars - margin_cost_dollars`. GROSS margin. Negative\nmeans the tenant loses money before a single dollar of payroll."
                                },
                                "cost_infrastructure_dollars": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "The infrastructure half of `margin_cost_dollars` (`TenantCost::\ninfrastructure`) — host share incl. facility."
                                },
                                "cost_transit_dollars": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "The transit half of `margin_cost_dollars` (`TenantCost::transit`) —\nupstream bytes. The one cost that arrives already per-customer."
                                },
                                "service_margins": {
                                    "type": "array",
                                    "items": { "$ref": "#/components/schemas/ServiceMarginView" },
                                    "description": "Per-service revenue/cost/margin split, `ServiceClass::ALL` order, zero\nrows omitted (`GrossMargin::by_service`). Empty until the tenant has a\npriced service."
                                }
                            }
                        }
                    },
                    "total": {
                        "type": "integer",
                        "description": "Total matching items before `limit`/`offset` were applied.",
                        "minimum": 0
                    },
                    "offset": {
                        "type": "integer",
                        "description": "Offset this page started at.",
                        "minimum": 0
                    },
                    "count": {
                        "type": "integer",
                        "description": "Number of items returned. Always `<= limit` and `<= MAX_LIMIT`.",
                        "minimum": 0
                    }
                }
            },
            "Page_GatewayView": {
                "type": "object",
                "description": "Wrapper for a paginated collection.\n\nEvery collection endpoint returns this. Unbounded collection responses are\nnot an option: a late-campaign save has thousands of hosts, and serializing\nall of them would occupy the single tokio thread to produce a body nobody\nwants.",
                "required": ["items", "total", "offset", "count"],
                "properties": {
                    "items": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "description": "Live edge-gateway projection. WAN port(s) cable to the edd; LAN ports\nto switches. Forwarding device in the reachability chain.",
                            "required": ["id", "az_id"],
                            "properties": {
                                "id": { "type": "integer", "format": "int32", "minimum": 0 },
                                "display_name": { "type": "string" },
                                "display_name_plain": {
                                    "type": "string",
                                    "description": "Plain-mode counterpart: `gateway 1` vs `gw-01`."
                                },
                                "az_id": { "type": "integer", "format": "int32", "minimum": 0 },
                                "sku_name": { "type": "string" },
                                "form": { "type": "string" },
                                "u_size": { "type": "integer", "format": "int32", "minimum": 0 },
                                "rack_id": { "type": "integer", "format": "int32", "minimum": 0 },
                                "start_u": { "type": "integer", "format": "int32", "minimum": 0 },
                                "wan_port_count": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Live count of this gateway's ports currently cabled to the carrier\n(a edd / patch panel) — i.e. acting WAN. Derived from cabling,\nnot a nameplate: gateways are ARM-ASIC and have no fixed WAN ports.",
                                    "minimum": 0
                                },
                                "lan_port_count": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Live count of ports currently cabled to a switch — i.e. acting LAN.",
                                    "minimum": 0
                                },
                                "used_ports": {
                                    "type": "integer",
                                    "format": "int32",
                                    "minimum": 0
                                },
                                "fabric_capacity_gbps": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "SKU fabric/forwarding ceiling — sustained traffic above this\nsaturates the ARM-ASIC packet engine. Legacy name kept as alias.",
                                    "minimum": 0
                                },
                                "fabric_load_gbps": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Smoothed full-duplex fabric load in Gbps (= egress + ingress).\nCompared against `fabric_capacity_gbps` for the saturation banner."
                                },
                                "fabric_saturated": {
                                    "type": "boolean",
                                    "description": "True when sustained `fabric_load_gbps` exceeds `fabric_capacity_gbps` —\npackets start dropping. Edge-triggered in the engine; banner\nstate here."
                                },
                                "egress_gbps": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Smoothed directional egress (LAN-side outbound) in Gbps."
                                },
                                "ingress_gbps": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Smoothed directional ingress (WAN-side inbound) in Gbps."
                                },
                                "wan_ingress_gbps": { "type": "number", "format": "double" },
                                "wan_egress_gbps": { "type": "number", "format": "double" },
                                "lan_ingress_gbps": { "type": "number", "format": "double" },
                                "lan_egress_gbps": { "type": "number", "format": "double" },
                                "ports": {
                                    "type": "array",
                                    "items": { "$ref": "#/components/schemas/PortView" }
                                },
                                "builtin_firewall": {
                                    "$ref": "#/components/schemas/GatewayFirewallView",
                                    "description": "Always-on built-in firewall baseline every gateway ships (drops\nunsolicited inbound — the intrinsic archetype-B firewall).\n\nSUPERSEDED, and NO CLIENT READER (audited 2026-08-09). `threats`\nbelow carries the same numbers per class, and that is what the\ngateway console renders — the only GDScript reference to anything\nnamed `builtin_firewall*` is to the SIBLING bool\n`builtin_firewall_enabled`. This whole nested 7-field struct\nsurvives only because ~17 assertions in\n`sim-render/tests/views/{threat_per_device,security_e2e}.rs` read it;\nretiring it means porting those to the `threats` row for\n`class == \"protocol\"` first. Do not build new UI against it."
                                },
                                "builtin_firewall_enabled": {
                                    "type": "boolean",
                                    "description": "Whether the built-in firewall is switched ON. Player-toggleable from the\ngateway console (→ `SetGatewayFirewall`); off removes this gateway's\nProtocol baseline. Defaults true."
                                },
                                "bgp_capable": {
                                    "type": "boolean",
                                    "description": "Whether this gateway's SKU can run BGP (eBGP to the carrier + HA\npairing). Entry/SOHO routers are single boxes — circuit redundancy only,\nno router HA. Gates the \"form a redundant pair\" affordance."
                                },
                                "multi_az_capable": {
                                    "type": "boolean",
                                    "description": "Whether the SKU can be a multi-AZ region border (implies `bgp_capable`)."
                                },
                                "bgp_advertising": {
                                    "type": "boolean",
                                    "description": "Whether this gateway is CURRENTLY advertising the provider prefix — a\nlive, `bgp_capable`, internet-facing edge. Two or more advertising edges\n= an HA border; a lone advertiser (or a non-BGP edge) is a border SPOF."
                                },
                                "wan_policy": {
                                    "type": "string",
                                    "description": "This router's WAN uplink policy across ITS circuits (\"Failover\" /\n\"ActiveActive\"). The console's WAN tab toggles it. Moved off the passive\nEDD 2026-07-22 — the router owns the routing decision."
                                },
                                "security_addons": {
                                    "type": "array",
                                    "items": { "$ref": "#/components/schemas/GatewayAddonView" },
                                    "description": "One row per available gateway security add-on (advanced firewall /\nbasic IPS / basic scrub). `enabled` reflects whether THIS gateway has\nit fitted. The console renders a CheckButton per row with capex+monthly\n+ the throttle warning, dispatching `SetGatewayAddon`."
                                },
                                "addon_throttled": {
                                    "type": "boolean",
                                    "description": "A gateway has NO deep-inspection engine. These three fields describe the\ngateway's FORWARDING ceiling being reduced by the security add-ons fitted\nto it, and nothing more. An add-on riding a router costs the router's ASIC\nheadroom; that penalty is a throttle on forwarding, not an inspection\nthroughput rating. Only a dedicated appliance has a real inspection plane\n(`ApplianceView::inspection_*`), and a gateway must never read as capable\nas one. Renamed 2026-07-25 from `inspection_active` /\n`inspection_throttle` / `inspection_capacity_gbps`, which mislabelled the\nthrottle penalty as a DPI ceiling.\n\nTrue when this gateway has a DPI add-on fitted (Σ deep-inspection\n`inspection_capacity_gbps` > 0), i.e. something on the router is actually\ncosting it forwarding headroom.\n\nNOT \"some fitted add-on carries a `gateway_throttle`\". The stateful\nfirewall add-on carries one but does no per-packet DPI, so the engine\ncharges it nothing; a firewall-only gateway reports `false` and keeps its\nfull rated ceiling below."
                                },
                                "addon_throttle": {
                                    "type": "number",
                                    "format": "float",
                                    "description": "`forwarding_ceiling_gbps / fabric_capacity_gbps` — the fraction of rated\nfabric the effective ceiling leaves, for the console's \"×0.80\" hint. 1.0\nwhen nothing is throttling. A PRESENTATION of the ceiling below, derived\nfrom it, so the two can never disagree; it is not an authored constant."
                                },
                                "forwarding_ceiling_gbps": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "The gateway's effective FORWARDING ceiling with its add-ons fitted —\n`fabric_load_gbps` is the load against it, and it IS the threshold\n`fabric_saturated` flips on.\n\nSingle source of truth: `effective_forwarding_capacity_gbps(fabric_capacity,\nEngine::gateway_inspection_capacity_gbps(id), load)`, the same shared curve\n(and the same inputs) `Engine::metrics_edge_traffic_pass` saturates against\nin `sim-engine/src/traffic/fabric.rs`. Load-dependent by nature: DPI eats\nforwarding headroom as inspection engages, so this falls from full fabric\ntoward the DPI ceiling as `fabric_load_gbps` rises.\n\nPre-v29 this was `fabric_capacity_gbps × Π gateway_throttle`, an unrelated\nauthored multiplier that disagreed with the saturation verdict at every\nload. Never re-derive it renderer-side.\n\nNamed `throttled_capacity_gbps` until 2026-08-09. `*_capacity_gbps` is\nRESERVED for an integer nameplate rating (`docs/SCHEMA.md` § Naming\nconventions), and this is an explicitly load-dependent f64 — the one\nthing the suffix rule exists to keep apart. `schema_naming_conventions.rs`\nnow fails the build if the old spelling comes back."
                                },
                                "monthly_opex_dollars": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Live projected monthly opex ($/mo) — running fee + chassis power +\nseated-optic draw. Mirrors the host console's read."
                                },
                                "inv_status": {
                                    "type": "string",
                                    "description": "Lifecycle status — see `HostView::inv_status`. Four-corner parity with\nHost / Switch / PatchPanel; a gateway could not report a sell-queued\nstate before this."
                                },
                                "threats": {
                                    "type": "array",
                                    "items": {
                                        "$ref": "#/components/schemas/ThreatMitigationView"
                                    },
                                    "description": "Per-class threat rows for THIS gateway — one row per class reaching it,\ndefended or not. Defended classes are the built-in L3/L4 firewall's\n(Protocol / Volumetric / Intrusion) plus any enabled add-on's (Intrusion\nvia Basic IPS, Volumetric via Basic Scrub); undefended classes (L7\nApplication, or anything whose defence is switched off) still appear at\n`mitigated: 0, capacity: 0` with `defense_capable` telling them apart.\nSame shape + shared `ThreatMitigationView` as `ApplianceView::threats`,\nso the gateway console renders the identical per-class Threats\nbreakdown the appliance does. `builtin_firewall` above stays the legacy\nsingle-Protocol summary; the console uses this vec. Empty only when the\ngateway is off the internet path entirely."
                                },
                                "powered": {
                                    "type": "boolean",
                                    "description": "At least one AC cord seated (or none required). False = dark\nuntil the player plugs it in. Mirrors the engine's `powered`."
                                },
                                "power_feeds": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Connected AC cord count. >= 2 = PSU-failure resilience at\n`redundant_psu_overhead_w` extra draw per extra cord.",
                                    "minimum": 0
                                }
                            }
                        }
                    },
                    "total": {
                        "type": "integer",
                        "description": "Total matching items before `limit`/`offset` were applied.",
                        "minimum": 0
                    },
                    "offset": {
                        "type": "integer",
                        "description": "Offset this page started at.",
                        "minimum": 0
                    },
                    "count": {
                        "type": "integer",
                        "description": "Number of items returned. Always `<= limit` and `<= MAX_LIMIT`.",
                        "minimum": 0
                    }
                }
            },
            "Page_HostPoolView": {
                "type": "object",
                "description": "Wrapper for a paginated collection.\n\nEvery collection endpoint returns this. Unbounded collection responses are\nnot an option: a late-campaign save has thousands of hosts, and serializing\nall of them would occupy the single tokio thread to produce a body nobody\nwants.",
                "required": ["items", "total", "offset", "count"],
                "properties": {
                    "items": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "description": "A player-created host pool. Small top-level list on `WorldSnapshot`\n(msgpack-only, like `lags`); per-host membership rides `HostView.pool_id`.\n`serves` are `ServiceClass` tags (\"vm\"/\"fn\"/\"obj\"/\"db\"/\"cdn\"/\"k8s\").",
                            "required": ["id", "name", "serves", "host_count"],
                            "properties": {
                                "id": { "type": "integer", "format": "int32", "minimum": 0 },
                                "name": { "type": "string" },
                                "serves": { "type": "array", "items": { "type": "string" } },
                                "host_count": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Count of hosts currently assigned to this pool — saves the client a\nscan when rendering the pool list.",
                                    "minimum": 0
                                }
                            }
                        }
                    },
                    "total": {
                        "type": "integer",
                        "description": "Total matching items before `limit`/`offset` were applied.",
                        "minimum": 0
                    },
                    "offset": {
                        "type": "integer",
                        "description": "Offset this page started at.",
                        "minimum": 0
                    },
                    "count": {
                        "type": "integer",
                        "description": "Number of items returned. Always `<= limit` and `<= MAX_LIMIT`.",
                        "minimum": 0
                    }
                }
            },
            "Page_HostView": {
                "type": "object",
                "description": "Wrapper for a paginated collection.\n\nEvery collection endpoint returns this. Unbounded collection responses are\nnot an option: a late-campaign save has thousands of hosts, and serializing\nall of them would occupy the single tokio thread to produce a body nobody\nwants.",
                "required": ["items", "total", "offset", "count"],
                "properties": {
                    "items": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "required": [
                                "id",
                                "rack_id",
                                "az_id",
                                "start_u",
                                "u_size",
                                "sku_name",
                                "arch",
                                "label",
                                "visual",
                                "mem_requested_pct",
                                "cpu_requested_pct",
                                "ssd_used_pct",
                                "iops_actual",
                                "iops_capacity"
                            ],
                            "properties": {
                                "id": { "type": "integer", "format": "int32", "minimum": 0 },
                                "display_name": {
                                    "type": "string",
                                    "description": "Player-facing identity like `srv-01`. Unique per host; survives\nSKU upgrades and moves. Prefer this over `sku_name` when labelling\nthe host in UI — `sku_name` is the model designation, shared\nacross many hosts."
                                },
                                "display_name_plain": {
                                    "type": "string",
                                    "description": "Plain-mode counterpart: `server 1` vs `srv-01`. Two-voice per\n`[[two_voice_labels_2026]]`. Empty on legacy saves; clients should\nfall back to `display_name`."
                                },
                                "rack_id": { "type": "integer", "format": "int32", "minimum": 0 },
                                "az_id": { "type": "integer", "format": "int32", "minimum": 0 },
                                "start_u": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Starting U slot inside the rack.",
                                    "minimum": 0
                                },
                                "u_size": { "type": "integer", "format": "int32", "minimum": 0 },
                                "sku_name": { "type": "string" },
                                "arch": { "type": "string" },
                                "accelerator": { "type": ["string", "null"] },
                                "label": { "type": "string" },
                                "visual": { "$ref": "#/components/schemas/HostVisual" },
                                "unit_count": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "EVERY placed unit on this host, all seven service classes — the generic\noccupancy, straight off `sim_engine::placement::UnitIndex`. Includes\nSTRANDED units (their last known host is still the only place they\nclaim); [`HostView::stranded_unit_count`] is that subset, so\n`unit_count - stranded_unit_count` is what is actually running.\n\n**This is the only per-host workload count.** It replaced `vm_count`,\nwhich counted VM-substrate units alone — plain VMs plus managed-DB\ncopies — so a host carrying only CDN POPs, Kubernetes workers, LB\nproxies, object shards or function containers read 0, and every consumer\ninherited that hole: the capacity lens' Load column, the netview host\nsubtitle and the host status LED all showed \"empty\" on a fleet running\nCDN / Kubernetes / object storage.\n\n`vm_count` was kept alongside for one release purely because it had\nshipped, and was DELETED at `PROTOCOL_VERSION` 47: no reader wanted it.\nEvery live client site was already spelled `unit_count` with a\n`vm_count` fallback. Do not reintroduce a VM-substrate-only per-host\ncount; if one is ever genuinely needed it belongs behind a name that\nsays substrate, not workload.",
                                    "minimum": 0
                                },
                                "stranded_unit_count": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Stranded units still pinned to this host — allocations whose customer is\n`Single` tier and whose host has lost reachability while remaining\npowered up. Drives the diegetic inspector's \"N stranded\" line. 0 when\nnothing is stranded on this host. Defaulted via serde for old saves.\n\nGENERIC since 2026-08-10: a stranded shard, worker or POP counts here\ntoo. Was `stranded_vm_count`; renamed at `PROTOCOL_VERSION` 47 with the\nrest of the `vm_`-named generic fields.",
                                    "minimum": 0
                                },
                                "online": {
                                    "type": "boolean",
                                    "description": "Whether the host is up on the network right now — powered on AND\nreachable through a live uplink chain (engine `host_is_serviceable`).\nThe honest \"online/offline\" signal: a host that's cabled but whose\nuplink chain is down reads `false`, unlike a naive \"any cable attached\"\ncheck. Defaulted via serde for old / partial payloads."
                                },
                                "powered": {
                                    "type": "boolean",
                                    "description": "At least one AC cord seated (or none required — wall rack,\nunracked, or cord-free scenario). False = the box is dark until\nthe player plugs it in. Mirrors engine `Host.powered`."
                                },
                                "power_feeds": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Connected AC cord count (0..=psu_count). >= 2 = PSU-failure\nresilience at `redundant_psu_overhead_w` extra draw per extra cord.",
                                    "minimum": 0
                                },
                                "mem_requested_pct": {
                                    "type": "number",
                                    "format": "float",
                                    "description": "REQUESTED (reserved/committed) share, NOT live usage: numerator is\n`vm_used_* + service_reserved_*`, denominator NAMEPLATE `sku.*`. Live\ndemand rides `used_millivcpu` on this same struct — the two answer\ndifferent questions, hence the explicit `requested` in the name."
                                },
                                "cpu_requested_pct": { "type": "number", "format": "float" },
                                "ssd_used_pct": { "type": "number", "format": "float" },
                                "wear_factor": {
                                    "type": "number",
                                    "format": "float",
                                    "description": "Wear multiplier on nameplate capacity (`eff_millivcpu = sku.vcpu *\nwear_factor`). Shipped because without it a client reading this\nstreamed view CANNOT compute the wear-adjusted EFFECTIVE basis at all\n— it has nameplate and nothing else. That gap is why some surfaces\ndivide by nameplate and others by effective. 1.0 = pristine."
                                },
                                "iops_actual": {
                                    "type": "integer",
                                    "format": "int32",
                                    "minimum": 0
                                },
                                "iops_capacity": {
                                    "type": "integer",
                                    "format": "int32",
                                    "minimum": 0
                                },
                                "nic_count": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Physical NIC port count. T1-T2 SKUs are single-NIC; T3+ ship\nmulti-NIC for LACP redundancy. Defaulted via serde.",
                                    "minimum": 0
                                },
                                "ports": {
                                    "type": "array",
                                    "items": { "$ref": "#/components/schemas/PortView" },
                                    "description": "Per-NIC readouts — same content as `RackInspectSnapshot`'s host\noccupant ports. Populated for every host so the workbench\ninspector can show speed / state / activity per NIC."
                                },
                                "egress_gbps": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Smoothed network egress in Gbps for this host's link cable.\nAccumulated across the customer's full service mix on this\nhost (Functions, VMs, ManagedDb, ObjectStore, LB, CDN, K8s).\nDrives per-port throughput at the cable's host-end + the\nswitch port the cable lands on."
                                },
                                "ingress_gbps": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Smoothed ingress (data flowing IN — request bodies, PUT\nuploads). Surfaced separately so the inspector can show\nasymmetric flows (\"↑ heavy / ↓ light\") accurately."
                                },
                                "served_customer_ids": {
                                    "type": "array",
                                    "items": { "type": "integer", "format": "int32", "minimum": 0 },
                                    "description": "Customers whose workload currently has presence on this host —\nVM allocations, in-flight Function invocations, ManagedDb\nprimaries, K8s nodes. Sourced from engine allocation maps; the\ninspector filters its tenant list to this set when scoped to a\nsingle host. Order: customer id ascending (deterministic)."
                                },
                                "inv_status": {
                                    "type": "string",
                                    "description": "Lifecycle status — \"Mounted\" / \"Carried\" / \"Loose\" / \"SellQueued\".\nRenderer dispatches off this for inventory presentation (e.g. a\nLoose host renders at its cubby, not its `start_u`)."
                                },
                                "age_hours": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Wall-clock age in hours since first install.",
                                    "minimum": 0
                                },
                                "power_on_hours": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Powered-on hours accumulated.",
                                    "minimum": 0
                                },
                                "condition_pct": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Condition 0..=100 against SKU MTBF.",
                                    "minimum": 0
                                },
                                "mtbf_hours": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "SKU MTBF in hours — surfaced so the inspector can show \"120k\nexpected, 18k used\" without re-resolving the catalog.",
                                    "minimum": 0
                                },
                                "resale_dollars": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Resale value at current condition + age. Same value the engine\ncredits if the item is sold right now."
                                },
                                "pool_id": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Host-pool membership. `0` ⇒ the General pool (`Host.pool_id == None`);\notherwise the `HostPoolView.id` this host belongs to. Delta-encoded —\nonly changes when the player reassigns the host, so it costs nothing\nper tick at rest. Resolve the name via `WorldSnapshot.host_pools`.",
                                    "minimum": 0
                                },
                                "service_classes": {
                                    "type": "array",
                                    "items": { "type": "string" },
                                    "description": "Service-class tags this host serves a data-plane agent for (service\nplane). Lets the Services page list a service's allocated hosts exactly\nrather than approximating via served-customer overlap. Empty when the\nmodel is off. Small (0–3 tags); changes rarely."
                                },
                                "vgpu_slices_total": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Total vGPU slices this host's SKU exposes across all its physical\nGPUs (`HostSku::total_vgpu().0`). 0 for a non-GPU host. Pairs with\n`vgpu_slices_used` for the inspector's GPU partition gauge.",
                                    "minimum": 0
                                },
                                "vgpu_slices_used": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "vGPU slices currently reserved by placed VMs (`Host.vgpu_slices_used`).",
                                    "minimum": 0
                                },
                                "gpu_mem_mb_total": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Total GPU frame-buffer (MB) across all physical GPUs\n(`HostSku::total_vgpu().1`). 0 for a non-GPU host.",
                                    "minimum": 0
                                },
                                "gpu_mem_mb_used": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "GPU frame-buffer (MB) currently reserved (`Host.gpu_mem_used_mb`).",
                                    "minimum": 0
                                },
                                "nic_module_slots": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Rear cages that accept a purchasable SFP NIC module\n(`HostSku.module_slots`). 0 = the chassis takes no modules.",
                                    "minimum": 0
                                },
                                "nic_modules_installed": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "NIC modules currently installed (`Host.nic_modules.len()`), bounded\nby `nic_module_slots`.",
                                    "minimum": 0
                                },
                                "free_vcpu_milli": {
                                    "type": "integer",
                                    "format": "int64",
                                    "description": "Absolute free vCPU in millicores (`Host::free_millivcpu()`), i.e.\neffective capacity minus VM + service-reserved usage. Mirrors the\nexact accessor `migrate_vm` gates on, so the migration picker can\noffer only hosts a specific VM footprint actually fits.",
                                    "minimum": 0
                                },
                                "free_mem_mb": {
                                    "type": "integer",
                                    "format": "int64",
                                    "description": "Absolute free memory in MB (`Host::free_mb()`). Same accessor as the\n`migrate_vm` capacity gate.",
                                    "minimum": 0
                                },
                                "free_ssd_gb": {
                                    "type": "integer",
                                    "format": "int64",
                                    "description": "Absolute free SSD in GB (`Host::free_ssd_gb()`). Same accessor as the\n`migrate_vm` capacity gate.",
                                    "minimum": 0
                                },
                                "free_ssd_iops": {
                                    "type": "integer",
                                    "format": "int64",
                                    "description": "Absolute free SSD IOPS headroom (`Host::free_ssd_iops()`). Same\naccessor as the `migrate_vm` capacity gate; lets the picker match the\nengine's IOPS check for block-backed VMs.",
                                    "minimum": 0
                                },
                                "physical_vcpu": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Physical vCPU count exposed by this host's SKU (`HostSku::vcpu`).\nThe denominator for the overcommit ratio; `allocated_millivcpu`\nmay exceed `physical_vcpu * 1000` when the host is oversubscribed.",
                                    "minimum": 0
                                },
                                "physical_mem_mb": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Nameplate memory in MB (`HostSku::mem_mb`) — the twin of\n`physical_vcpu`, and the denominator `mem_requested_pct` divides by.\nShipped because this view exposed a nameplate total for CPU and none\nfor memory, so a consumer had to reconstruct total RAM from\n`free_mem_mb` and `mem_requested_pct` (UG-320). Memory never decays,\nso this is constant for the life of the host.",
                                    "minimum": 0
                                },
                                "physical_ssd_gb": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Nameplate disk in GB (`HostSku::ssd_gb`), same rationale as\n`physical_mem_mb`. Also constant for life — wear scales IOPS, not\nformatted capacity, so `free_ssd_gb` subtracts from exactly this.\nNote `free_ssd_iops` does NOT: IOPS is the one wear-adjusted axis,\nso its free figure is against a ceiling that falls as the host ages.",
                                    "minimum": 0
                                },
                                "allocated_millivcpu": {
                                    "type": "integer",
                                    "format": "int64",
                                    "description": "Sum of vCPU (in millicores) *allocated* to placed VMs on this host\n(`Host::committed_millivcpu`). This is the promised/reserved figure,\nnot the live demand — compare against `physical_vcpu * 1000` to show\nthe oversubscription fill.",
                                    "minimum": 0
                                },
                                "used_millivcpu": {
                                    "type": "integer",
                                    "format": "int64",
                                    "description": "Live vCPU *demand* in millicores (`Host::used_millivcpu_demand()`),\ni.e. what the workloads are actually asking for this tick. Compared\nagainst `eff_millivcpu` (physical × overcommit_target) to derive the\ncontention state.",
                                    "minimum": 0
                                },
                                "overcommit_target": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Player-dialed overcommit target for this host (`Host::overcommit_target`).\n`1.0` = strictly 1:1 (dedicated); higher permits oversubscription up to\n`overcommit_max`."
                                },
                                "overcommit_max": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Hard ceiling on `overcommit_target` (`config.tuning.vcpu_overcommit_max`).\nClamps the player-facing slider."
                                },
                                "contention_state": {
                                    "type": "string",
                                    "description": "Contention read-out: `\"healthy\"` (demand well under effective capacity),\n`\"warm\"` (demand > ~0.8× effective), `\"contended\"` (demand exceeds\neffective capacity — VMs are being throttled). Drives the host console's\ngreen/amber/red contention indicator."
                                },
                                "monthly_cost_direct_dollars": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "C6.3 — monthly cost caused by this box directly: power draw, its own\ndepreciation share, maintenance (`HostCost::direct`)."
                                },
                                "monthly_cost_facility_dollars": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "This box's share of the shared facility plant — rent, WAN circuits,\ncable opex, peering, network gear and their depreciation\n(`HostCost::facility_share`). Allocated by RACK UNITS, because floor\nspace is what rent buys and a 4U chassis occupies four times the site a\n1U does. Zero for a host that is not racked: a box on the floor serves\nno tenant and consumes no site capacity the fleet is paying for."
                                },
                                "monthly_cost_total_dollars": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "`direct + facility_share` — the host's all-in monthly cost\n(`HostCost::total`)."
                                },
                                "facility_basis": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "The rack-U fraction that produced `monthly_cost_facility_dollars`\n(`HostCost::facility_basis`). `0.0` when not racked. Surfaced so the\nconsole can EXPLAIN the facility line rather than assert it."
                                }
                            }
                        }
                    },
                    "total": {
                        "type": "integer",
                        "description": "Total matching items before `limit`/`offset` were applied.",
                        "minimum": 0
                    },
                    "offset": {
                        "type": "integer",
                        "description": "Offset this page started at.",
                        "minimum": 0
                    },
                    "count": {
                        "type": "integer",
                        "description": "Number of items returned. Always `<= limit` and `<= MAX_LIMIT`.",
                        "minimum": 0
                    }
                }
            },
            "Page_IncidentView": {
                "type": "object",
                "description": "Wrapper for a paginated collection.\n\nEvery collection endpoint returns this. Unbounded collection responses are\nnot an option: a late-campaign save has thousands of hosts, and serializing\nall of them would occupy the single tokio thread to produce a body nobody\nwants.",
                "required": ["items", "total", "offset", "count"],
                "properties": {
                    "items": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "description": "Renderer-friendly mirror of `sim_core::incident::Incident`.\nChannel UI, post-mortem, and the \"is this customer impacted?\"\nbadge all read from these. Severity (`EventView.severity`) is a\nper-event tag; `band` here is the incident-level gravity read.",
                            "required": [
                                "id",
                                "root_kind",
                                "root_entity_id",
                                "started_at_ns",
                                "last_changed_at_ns",
                                "resolved_at_ns",
                                "affected_customer_ids",
                                "revenue_at_risk_per_hour_dollars",
                                "max_sla_tier",
                                "compliance_at_risk",
                                "redundancy_promise_broken",
                                "band"
                            ],
                            "properties": {
                                "id": { "type": "integer", "format": "int64", "minimum": 0 },
                                "root_kind": {
                                    "type": "string",
                                    "description": "Variant tag mirroring `IncidentRoot` — \"HostFailed\", \"CableFailed\", etc."
                                },
                                "root_entity_id": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Primary entity id from the root, when there is one (host id,\ncable id, rack id, ...). 0 for global roots like Ddos.",
                                    "minimum": 0
                                },
                                "rack_id": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Rack the incident beacon should mark — resolved engine-side from the\ntyped root (device's rack, or the rack itself, or the edge rack where\nWAN ingress lands for global/fabric roots). 0 when nothing is racked\nyet. Lets the renderer skip a per-tick scan of every device array.",
                                    "minimum": 0
                                },
                                "root_msg_key": {
                                    "type": "string",
                                    "description": "Localization key for the root label — `incident-<root_kind-kebab>`.\n\nGuidance (\"what to check\") deliberately has NO key field: it is static\nper root and the client derives `guidance-<root_kind>-check-<n>` from\n`root_kind`, which is already on this struct."
                                },
                                "root_msg_args": {
                                    "type": "array",
                                    "items": { "$ref": "#/components/schemas/MsgArgView" },
                                    "description": "Facts for `root_msg_key`, including any already-resolved customer name."
                                },
                                "symptom_kind": {
                                    "type": "string",
                                    "description": "Symptom layer — the customer/operator-facing HEADLINE the card leads\nwith (many causes → one symptom). Variant tag mirroring `Symptom`:\n\"ServiceUnreachable\", \"InfrastructureFault\", etc."
                                },
                                "symptom_msg_key": {
                                    "type": "string",
                                    "description": "Localization key for the symptom headline — `symptom-<kind-kebab>`."
                                },
                                "symptom_msg_args": {
                                    "type": "array",
                                    "items": { "$ref": "#/components/schemas/MsgArgView" },
                                    "description": "Facts for `symptom_msg_key`."
                                },
                                "symptom_customer_id": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Tenant the symptom is rooted on, or 0 for an `InfrastructureFault`.",
                                    "minimum": 0
                                },
                                "confirmed": {
                                    "type": "boolean",
                                    "description": "True if any candidate cause has been confirmed as the real cause."
                                },
                                "causes": {
                                    "type": "array",
                                    "items": { "$ref": "#/components/schemas/CandidateCauseView" },
                                    "description": "Ranked candidate causes, best-first. In Phase 2 exactly one confirmed\ncandidate; full ranking is Phase 4."
                                },
                                "started_at_ns": {
                                    "type": "integer",
                                    "format": "int64",
                                    "minimum": 0
                                },
                                "last_changed_at_ns": {
                                    "type": "integer",
                                    "format": "int64",
                                    "minimum": 0
                                },
                                "resolved_at_ns": {
                                    "type": "integer",
                                    "format": "int64",
                                    "description": "0 while open; set to the resolution sim-time once closed.",
                                    "minimum": 0
                                },
                                "affected_customer_ids": {
                                    "type": "array",
                                    "items": { "type": "integer", "format": "int32", "minimum": 0 },
                                    "description": "Sorted ascending by id. Engine guarantees deterministic order."
                                },
                                "affected_host_ids": {
                                    "type": "array",
                                    "items": { "type": "integer", "format": "int32", "minimum": 0 },
                                    "description": "The MACHINES this incident is about — sorted ascending, capped at\n`sim_core::incident::AFFECTED_HOSTS_CAP`. Empty for roots with no host\nscope (DDoS, uplink saturation, customer-observation): those degrade\neveryone, so naming hosts would be a lie. Resolve names against\n`hosts[].display_name`."
                                },
                                "affected_host_total": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "True count before the cap, so the UI can say \"showing 64 of 812\"\nrather than silently truncating.",
                                    "minimum": 0
                                },
                                "revenue_at_risk_per_hour_dollars": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Sum of monthly recurring run-rate across the affected set."
                                },
                                "max_sla_tier": {
                                    "type": "string",
                                    "description": "Worst SLA tier in the affected set — \"Bronze\" / \"Silver\" / \"Gold\" / \"Platinum\"."
                                },
                                "compliance_at_risk": {
                                    "type": "string",
                                    "description": "Worst compliance tier in the affected set — \"—\" / \"SOC2\" / \"HIPAA\" / \"PCI\"."
                                },
                                "redundancy_promise_broken": { "type": "boolean" },
                                "band": {
                                    "type": "string",
                                    "description": "Computed gravity band — \"cosmetic\" / \"annoying\" / \"serious\" / \"existential\"."
                                },
                                "timeline": {
                                    "type": "array",
                                    "items": {
                                        "$ref": "#/components/schemas/IncidentTimelineEntryView"
                                    },
                                    "description": "Incident timeline, oldest first. Bounded only by incident\nlifetime — preserved into the resolved-ring."
                                },
                                "work_order_ids": {
                                    "type": "array",
                                    "items": { "type": "integer", "format": "int64", "minimum": 0 },
                                    "description": "Active work order ids targeting this incident. Populated once\nthe work model lands (Pass A2); empty until then."
                                },
                                "channel_thread_id": {
                                    "type": "integer",
                                    "format": "int64",
                                    "description": "Channel thread root in `#incidents`. Populated once the channel\nmodule lands (Pass A3); 0 until then.",
                                    "minimum": 0
                                },
                                "articles": {
                                    "type": "array",
                                    "items": { "$ref": "#/components/schemas/IncidentArticleView" },
                                    "description": "Knowledge-base articles covering this root's mechanic — the\ndetail pane renders them as codex deep links. May be empty."
                                },
                                "hint_tier": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Highest progressive-hint tier fired for this incident (0 = none,\n1 = subsystem nudge, 2 = named suspect, 3 = named fix).",
                                    "minimum": 0
                                },
                                "hint": {
                                    "type": "string",
                                    "description": "The current-tier escalating hint text (empty when `hint_tier == 0`)."
                                },
                                "evidence": {
                                    "type": "array",
                                    "items": { "$ref": "#/components/schemas/EvidenceRowView" },
                                    "description": "MEASURED evidence behind a customer-facing symptom: the tenant's p99\nagainst its own baseline and tier budget, its failure rate, where the\nlatency actually went, and why requests failed.\n\nThe card used to carry labels, prose and hints but not one number —\nits own guidance said \"check their p99 latency vs tier budget\" about a\nfigure it never displayed. These rows are that figure. Empty for\ninfrastructure-rooted incidents with no tenant to measure."
                                }
                            }
                        }
                    },
                    "total": {
                        "type": "integer",
                        "description": "Total matching items before `limit`/`offset` were applied.",
                        "minimum": 0
                    },
                    "offset": {
                        "type": "integer",
                        "description": "Offset this page started at.",
                        "minimum": 0
                    },
                    "count": {
                        "type": "integer",
                        "description": "Number of items returned. Always `<= limit` and `<= MAX_LIMIT`.",
                        "minimum": 0
                    }
                }
            },
            "Page_NetworkSegmentView": {
                "type": "object",
                "description": "Wrapper for a paginated collection.\n\nEvery collection endpoint returns this. Unbounded collection responses are\nnot an option: a late-campaign save has thousands of hosts, and serializing\nall of them would occupy the single tokio thread to produce a body nobody\nwants.",
                "required": ["items", "total", "offset", "count"],
                "properties": {
                    "items": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "description": "A player-created network segment (VXLAN isolation overlay). Small\ntop-level list on `WorldSnapshot` (msgpack-only, like `host_pools`).\nCustomer membership is derived engine-side via `resolve_vrf`; the\nclient builds a host→segment view from `carrier_host_ids` /\n`dedicated_host_ids`.",
                            "required": [
                                "id",
                                "vni",
                                "name",
                                "explicit_customer_ids",
                                "member_count",
                                "carrier_host_ids",
                                "dedicated_host_ids"
                            ],
                            "properties": {
                                "id": { "type": "integer", "format": "int32", "minimum": 0 },
                                "vni": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "VXLAN network identifier assigned at create time.",
                                    "minimum": 0
                                },
                                "name": { "type": "string" },
                                "tier_rule": {
                                    "type": ["string", "null"],
                                    "description": "SLA-tier auto-membership rule as a tier tag (\"Bronze\"..\"Platinum\"),\nor `None` when the segment only gathers explicit pins."
                                },
                                "explicit_customer_ids": {
                                    "type": "array",
                                    "items": { "type": "integer", "format": "int32", "minimum": 0 },
                                    "description": "Customers explicitly pinned to this segment."
                                },
                                "member_count": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Count of customers currently resolving to this segment (explicit\npins + tier-rule matches).",
                                    "minimum": 0
                                },
                                "carrier_host_ids": {
                                    "type": "array",
                                    "items": { "type": "integer", "format": "int32", "minimum": 0 },
                                    "description": "Distinct hosts carrying this segment's members' workloads — the\nderived VTEP set, used for the \"touches N hosts\" blast-radius readout."
                                },
                                "dedicated_host_ids": {
                                    "type": "array",
                                    "items": { "type": "integer", "format": "int32", "minimum": 0 },
                                    "description": "Hosts whose `admit` is this segment (physically dedicated)."
                                },
                                "dedicated_host_count": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Number of hosts dedicated to this segment. `0` = logical-only (the\ncustomer lands anywhere in the fleet, so the prospect card reads the\nfleet meter, not these figures). Same set as `dedicated_host_ids.len()`.",
                                    "minimum": 0
                                },
                                "dedicated_free_vcpu_milli": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Free physical vCPU (millivcpu) summed over the dedicated host-set —\n`free_millivcpu` per host, the SAME accessor the per-host `HostView`\ncapacity reports, so a customer confined to this segment reads a meter\ncomparable to the fleet's. `0` when logical-only.",
                                    "minimum": 0
                                },
                                "dedicated_total_vcpu_milli": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Total (eff/wear-adjusted nominal) vCPU (millivcpu) of the dedicated\nhost-set. `0` when logical-only.",
                                    "minimum": 0
                                },
                                "dedicated_free_mem_mb": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Free memory (MB) summed over the dedicated host-set (`free_mb`). `0`\nwhen logical-only.",
                                    "minimum": 0
                                },
                                "dedicated_total_mem_mb": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Total (eff) memory (MB) of the dedicated host-set. `0` when logical-only.",
                                    "minimum": 0
                                },
                                "dedicated_free_disk_gb": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Free SSD (GB) summed over the dedicated host-set (`free_ssd_gb`). `0`\nwhen logical-only.",
                                    "minimum": 0
                                },
                                "dedicated_total_disk_gb": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Total (eff) SSD (GB) of the dedicated host-set. `0` when logical-only.",
                                    "minimum": 0
                                }
                            }
                        }
                    },
                    "total": {
                        "type": "integer",
                        "description": "Total matching items before `limit`/`offset` were applied.",
                        "minimum": 0
                    },
                    "offset": {
                        "type": "integer",
                        "description": "Offset this page started at.",
                        "minimum": 0
                    },
                    "count": {
                        "type": "integer",
                        "description": "Number of items returned. Always `<= limit` and `<= MAX_LIMIT`.",
                        "minimum": 0
                    }
                }
            },
            "Page_PatchPanelView": {
                "type": "object",
                "description": "Wrapper for a paginated collection.\n\nEvery collection endpoint returns this. Unbounded collection responses are\nnot an option: a late-campaign save has thousands of hosts, and serializing\nall of them would occupy the single tokio thread to produce a body nobody\nwants.",
                "required": ["items", "total", "offset", "count"],
                "properties": {
                    "items": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "required": [
                                "id",
                                "rack_id",
                                "start_u",
                                "u_size",
                                "sku_name",
                                "port_count",
                                "used_ports"
                            ],
                            "properties": {
                                "id": { "type": "integer", "format": "int32", "minimum": 0 },
                                "rack_id": { "type": "integer", "format": "int32", "minimum": 0 },
                                "start_u": { "type": "integer", "format": "int32", "minimum": 0 },
                                "u_size": { "type": "integer", "format": "int32", "minimum": 0 },
                                "sku_name": { "type": "string" },
                                "port_count": {
                                    "type": "integer",
                                    "format": "int32",
                                    "minimum": 0
                                },
                                "used_ports": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "How many of the panel's ports currently carry a cable.",
                                    "minimum": 0
                                },
                                "ports": {
                                    "type": "array",
                                    "items": { "$ref": "#/components/schemas/PortView" },
                                    "description": "Per-port views — same shape as `HostView::ports` /\n`SwitchView::ports` / `EddView::ports` so click resolvers,\ninspectors, and cable renderers can treat patch panel ports as\nfirst-class without a kind-specific branch.\n\nPatch panels are passive: `state` is always `Down`, `gbps`\nalways 0, no link/activity LEDs render. The fields populate\nfor parity (port_id, label, peer, cable_id) so callers can\nresolve \"what's plugged in here\" the same way they do for\nswitches and hosts."
                                },
                                "inv_status": {
                                    "type": "string",
                                    "description": "Lifecycle status — see `HostView::inv_status`."
                                },
                                "age_hours": { "type": "integer", "format": "int32", "minimum": 0 },
                                "power_on_hours": {
                                    "type": "integer",
                                    "format": "int32",
                                    "minimum": 0
                                },
                                "condition_pct": {
                                    "type": "integer",
                                    "format": "int32",
                                    "minimum": 0
                                },
                                "resale_dollars": { "type": "number", "format": "double" }
                            }
                        }
                    },
                    "total": {
                        "type": "integer",
                        "description": "Total matching items before `limit`/`offset` were applied.",
                        "minimum": 0
                    },
                    "offset": {
                        "type": "integer",
                        "description": "Offset this page started at.",
                        "minimum": 0
                    },
                    "count": {
                        "type": "integer",
                        "description": "Number of items returned. Always `<= limit` and `<= MAX_LIMIT`.",
                        "minimum": 0
                    }
                }
            },
            "Page_ProspectView": {
                "type": "object",
                "description": "Wrapper for a paginated collection.\n\nEvery collection endpoint returns this. Unbounded collection responses are\nnot an option: a late-campaign save has thousands of hosts, and serializing\nall of them would occupy the single tokio thread to produce a body nobody\nwants.",
                "required": ["items", "total", "offset", "count"],
                "properties": {
                    "items": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "required": [
                                "id",
                                "name",
                                "archetype",
                                "tier",
                                "signup_bonus_dollars",
                                "compliance_required",
                                "required_arch",
                                "requires_multi_az",
                                "services_summary",
                                "expires_at_ns"
                            ],
                            "properties": {
                                "id": { "type": "integer", "format": "int64", "minimum": 0 },
                                "name": { "type": "string" },
                                "archetype": { "type": "string" },
                                "tier": { "type": "string" },
                                "signup_bonus_dollars": { "type": "number", "format": "double" },
                                "compliance_required": { "type": "string" },
                                "required_arch": {
                                    "type": "string",
                                    "description": "Raw CPU-arch requirement tag (\"x86_64\" / \"arm64\" / …) this prospect's\nVM or K8s workload demands, or \"\" if arch-agnostic. A hard placement\ngate: the card voices it (jargon \"x86-64 hosts\" vs plain \"Intel/AMD\nhosts\") beside the compliance/air-gap chips. Godot maps the tag via\nthe `arch_req_<tag>` glossary key."
                                },
                                "requires_multi_az": { "type": "boolean" },
                                "required_region": {
                                    "type": ["integer", "null"],
                                    "format": "int32",
                                    "minimum": 0
                                },
                                "services_summary": { "type": "string" },
                                "service_kinds": {
                                    "type": "array",
                                    "items": { "type": "string" },
                                    "description": "Short-code service mix mirroring `CustomerView.service_kinds`\n(subset of {\"fn\",\"vm\",\"obj\",\"lb\",\"db\",\"cdn\",\"k8s\"}). The ops-console\ncustomer/prospect table renders these as upper-case chips, so a\nprospect row reads the same as an active-customer row."
                                },
                                "estimated_monthly_revenue_dollars": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "List-price monthly run-rate this prospect would bill at signup,\nprojected from its VM workload (count × per-VM hourly × hours/month).\n0 for non-VM-shaped prospects. Lets the customers lens show a\nprospect's potential Revenue/hr before you sign them."
                                },
                                "expires_at_ns": {
                                    "type": "integer",
                                    "format": "int64",
                                    "minimum": 0
                                },
                                "initial_vm_count": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Initial VM count this prospect will request at signup. 0 when\nthe prospect doesn't run a VM workload (Functions / ObjectStore /\netc.) — the renderer surfaces those via `services_summary`.",
                                    "minimum": 0
                                },
                                "vcpu_per_vm": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Per-VM vCPU count. 0 when no VM workload.",
                                    "minimum": 0
                                },
                                "memory_mb_per_vm": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Per-VM memory (MB). 0 when no VM workload.",
                                    "minimum": 0
                                },
                                "block_gb_per_vm": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Per-VM block-volume size (GB). 0 when the ask carries no volume\n(legacy templates / non-VM workloads). Billed via `BlockPricing`\nalongside the VM's compute charge.",
                                    "minimum": 0
                                },
                                "growth_ceiling_vms": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Growth ceiling (max VMs the customer can compound to) read from\nthe archetype's `growth.plateau_at_allocation`. 0 = no archetype\nmatch or uncapped (Anchor / Enterprise). Renderer shows \"may\ngrow to ~N\" when > initial_vm_count.",
                                    "minimum": 0
                                },
                                "fit_vcpu_after_pct": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Capacity-fit verdict (P2 in `tasks/consequences.md`) — projected\nfleet VM utilization *after* accepting this prospect's initial demand,\non each axis. `1.0` = exactly full; `> 1.0` = would overcommit. Lets\nthe prospect card answer \"can I actually serve this?\" before signing.\n0.0 for prospects with no VM workload."
                                },
                                "fit_mem_after_pct": { "type": "number", "format": "double" },
                                "fit_ssd_after_pct": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "The DISK axis, on the same basis as the two above. `contract_footprint`\nhas always computed `Resource::SsdGb` (an object-storage bucket, a CDN\norigin, a DB's backup copies, a VM's block volume); the projection threw\nit away, so a card asking for 821 GB showed no disk meter at all and read\n\"Comfortable\" beside it. 0.0 when the bundle asks for no bytes."
                                },
                                "fit_can_serve_now": {
                                    "type": "boolean",
                                    "description": "True when the current fleet has free vCPU, memory **and** disk headroom\nfor this prospect's initial demand right now (no new hardware needed)."
                                },
                                "fit_oversubscribed": {
                                    "type": "boolean",
                                    "description": "True when accepting would push the fleet PAST physical into overcommit:\nthe initial VMs still fit under the host overcommit ceiling, but CPU is\nshared and tenants may slow under load. Never a false \"can't fit\"."
                                },
                                "fit_over_capacity": {
                                    "type": "boolean",
                                    "description": "True only for a genuine over-capacity fit: a VM-load prospect that won't\nfit even under overcommit (the \"Over capacity, add hardware first\"\nverdict). Boolean twin of that string so the card's Accept-anyway confirm\ngate keys off a field, not exact prose. False for no-VM / oversubscribed."
                                },
                                "fit_recommendation": {
                                    "type": "string",
                                    "description": "Engine-classified one-liner for the card: \"Comfortable\" / \"Tight fit\" /\n\"Will run oversubscribed (CPU shared under load)\" / \"Over capacity, add\nhardware first\" / \"Needs GPU hardware — add a GPU host first\" /\n\"No VM load\"."
                                },
                                "requires_gpu": {
                                    "type": "boolean",
                                    "description": "True when any of this prospect's VM services demands a GPU (vGPU\npartition / `required_accelerator == Gpu`). Drives the card's GPU meter\nand makes the serve verdict GPU-aware — a GPU tenant on a GPU-less fleet\nreads \"Needs GPU hardware\", not a false \"Comfortable\"."
                                },
                                "gpu_slices_demand": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Total vGPU slices this prospect's initial VMs would reserve\n(`initial_vm_count × per-VM slices`). 0 for non-GPU prospects. The\nfleet-fit input the serve verdict weighs against free vGPU slices.",
                                    "minimum": 0
                                },
                                "services": {
                                    "type": "array",
                                    "items": { "$ref": "#/components/schemas/ProspectServiceView" },
                                    "description": "Per-service detail for the WHOLE bundle — one entry per service the\nprospect would sign for. The card renders each as a magnitude line so\nnon-VM prospects (object-store / DB / functions / ...) read as more\nthan a bare chip. Additive to the VM fields above, which still drive\nthe capacity-fit bar."
                                },
                                "demands_audit_logs": {
                                    "type": "boolean",
                                    "description": "True when the prospect's archetype demands audit logging (a live\nSIEM + audit feed). Surfaced as a requirement chip so a PCI /\ngovernment-contract prospect's otherwise-inexplicable rejection is\nlegible (\"Requires: audit logging\")."
                                },
                                "demands_air_gap": {
                                    "type": "boolean",
                                    "description": "True when the prospect's archetype demands an air-gapped facility."
                                },
                                "requires_private_segment": {
                                    "type": "boolean",
                                    "description": "True when the prospect wants a private/isolated network segment\n(VXLAN isolation) dedicated to its SLA tier. SOFT / recommended —\nunlike audit-logging / air-gap it does NOT hard-reject signing; it's\nsatisfied by a private segment whose `tier_rule` matches this\nprospect's `tier`. Surfaced as a \"Recommended\" chip, distinct in tone\nfrom the hard requirement chips."
                                },
                                "requires_dedicated_hosts": {
                                    "type": "boolean",
                                    "description": "True when the prospect wants a private network on DEDICATED, homed hosts\n(single-tenant iron) — the 4× isolation tier (vs 2× for a plain private\nnetwork). Implies `requires_private_segment`; the card shows one combined\nchip. msgpack-only (ProspectView is not carried in capnp)."
                                },
                                "requires_fault_isolation": {
                                    "type": "boolean",
                                    "description": "True when the prospect's contract asks for fault isolation — copies kept\noff a shared fault domain (redundancy tier > 1 host replica, or an\narchetype requiring anti-affinity). Mirrors\n`CustomerView.requires_fault_isolation` so the prospect \"Must clear\" card\nand the accepted-customer Requirements row read the SAME key and cannot\ndrift. msgpack-only (ProspectView is not carried in capnp)."
                                },
                                "covering_segment_id": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Id of the segment whose `tier_rule` already claims this prospect's\ntier — i.e. the segment the prospect would auto-join on signup via\ntier-rule precedence. `0` = none (no tier-rule segment covers this\ntier; the prospect would land on the Default shared segment). Lets\nthe prospect card offer \"isolate into <existing segment>\" without a\nsecond round-trip. Mirrors the accept-path covering check.",
                                    "minimum": 0
                                },
                                "covering_segment_name": {
                                    "type": "string",
                                    "description": "Display name of the covering segment (empty when `covering_segment_id`\nis 0). Paired with the id so the card can label the existing-segment\noption without a client-side lookup."
                                },
                                "requires_uncontended": {
                                    "type": "boolean",
                                    "description": "True when the prospect's archetype contracts UNCONTENDED (1:1 CPU) —\nits workloads must land on hosts admitted to an `uncontended` segment\n(running 1:1, no overcommit). Wholly independent of dedication and\nanti-affinity spread. Drives the card's \"Needs 1:1 CPU\" chip so the\nplayer sees the guarantee (and the pool cost) before signing."
                                },
                                "requires_dedicated": {
                                    "type": "boolean",
                                    "description": "True when the prospect's archetype contracts DEDICATED, single-tenant\nhosts (private iron, no co-tenants). Mirrors `requires_dedicated_hosts`\non the archetype's own flag; surfaced as a \"Needs a dedicated host\"\nchip. Tenancy guarantee, not a CPU one — distinct from uncontended."
                                },
                                "uncontended_hosts_available": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Best-effort count of hosts the engine could convert into the shared\nuncontended (1:1) pool right now — open (unadmitted), serviceable,\nVM-serving hosts with physical room. Fleet-wide, not per-prospect. The\ncard renders \"<N> host(s) available for the 1:1 pool\" for an uncontended\nprospect so the player knows whether the pool can grow before signing.\n0 = none convertible (would need new hardware).",
                                    "minimum": 0
                                }
                            }
                        }
                    },
                    "total": {
                        "type": "integer",
                        "description": "Total matching items before `limit`/`offset` were applied.",
                        "minimum": 0
                    },
                    "offset": {
                        "type": "integer",
                        "description": "Offset this page started at.",
                        "minimum": 0
                    },
                    "count": {
                        "type": "integer",
                        "description": "Number of items returned. Always `<= limit` and `<= MAX_LIMIT`.",
                        "minimum": 0
                    }
                }
            },
            "Page_QuestView": {
                "type": "object",
                "description": "Wrapper for a paginated collection.\n\nEvery collection endpoint returns this. Unbounded collection responses are\nnot an option: a late-campaign save has thousands of hosts, and serializing\nall of them would occupy the single tokio thread to produce a body nobody\nwants.",
                "required": ["items", "total", "offset", "count"],
                "properties": {
                    "items": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "description": "One quest item — what the left-side panel renders. State is one of\n\"Locked\" / \"Open\" / \"Next\" / \"Done\"; the Next item is bolded by the\nrenderer to show the player what to focus on.",
                            "required": [
                                "id",
                                "title",
                                "blurb",
                                "state",
                                "unlock_step",
                                "reward_label"
                            ],
                            "properties": {
                                "id": { "type": "integer", "format": "int32", "minimum": 0 },
                                "title": { "type": "string" },
                                "blurb": { "type": "string" },
                                "title_args": {
                                    "type": "array",
                                    "items": { "$ref": "#/components/schemas/MsgArgView" },
                                    "description": "Facts for `title` when it holds an i18n key. A generated rung reads\n\"Grow to {$count} customers\", so the number has to travel beside the\nkey: resolving engine-side would pick one language for every player\nin a co-op session."
                                },
                                "blurb_args": {
                                    "type": "array",
                                    "items": { "$ref": "#/components/schemas/MsgArgView" },
                                    "description": "Facts for `blurb`, same reasoning."
                                },
                                "state": { "type": "string" },
                                "unlock_step": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Progression step at which this quest unlocks (0-indexed).",
                                    "minimum": 0
                                },
                                "reward_label": {
                                    "type": "string",
                                    "description": "Pre-rendered reward chip label (`+$5,000 · +2% rep`); empty = no reward."
                                }
                            }
                        }
                    },
                    "total": {
                        "type": "integer",
                        "description": "Total matching items before `limit`/`offset` were applied.",
                        "minimum": 0
                    },
                    "offset": {
                        "type": "integer",
                        "description": "Offset this page started at.",
                        "minimum": 0
                    },
                    "count": {
                        "type": "integer",
                        "description": "Number of items returned. Always `<= limit` and `<= MAX_LIMIT`.",
                        "minimum": 0
                    }
                }
            },
            "Page_RackView": {
                "type": "object",
                "description": "Wrapper for a paginated collection.\n\nEvery collection endpoint returns this. Unbounded collection responses are\nnot an option: a late-campaign save has thousands of hosts, and serializing\nall of them would occupy the single tokio thread to produce a body nobody\nwants.",
                "required": ["items", "total", "offset", "count"],
                "properties": {
                    "items": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "required": [
                                "id",
                                "az_id",
                                "name",
                                "capacity_u",
                                "power_budget_w",
                                "power_draw_w",
                                "visual"
                            ],
                            "properties": {
                                "id": { "type": "integer", "format": "int32", "minimum": 0 },
                                "az_id": { "type": "integer", "format": "int32", "minimum": 0 },
                                "name": { "type": "string" },
                                "capacity_u": {
                                    "type": "integer",
                                    "format": "int32",
                                    "minimum": 0
                                },
                                "used_u": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "U slots actually consumed in this rack, counting EVERY rack occupant:\nhosts, switches, patch panels, security appliances, EDDs and gateways.\nSame population as `Engine::occupied_u`, sourced from the one-pass\n`Engine::occupied_u_by_rack`.\n\nAdded in PROTOCOL 49 (UG-294). Clients previously re-summed `u_size`\nover the `hosts` array, which under-reported by everything racked that\nwas not a server, and could only be right at world scope where the\nfleet-wide `capacity` block already answered it. Do not re-derive this\nclient-side; a second copy of the occupant list is what drifted.",
                                    "minimum": 0
                                },
                                "power_budget_w": {
                                    "type": "integer",
                                    "format": "int32",
                                    "minimum": 0
                                },
                                "power_draw_w": {
                                    "type": "integer",
                                    "format": "int32",
                                    "minimum": 0
                                },
                                "visual": { "$ref": "#/components/schemas/RackVisual" },
                                "draped_cable_count": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Cables touching this rack that have no managed routing path.\nRenderer can paint the rack's \"neatness\" badge from this.",
                                    "minimum": 0
                                },
                                "depth_m": {
                                    "type": "number",
                                    "format": "float",
                                    "description": "Internal depth in metres. Drives the chassis-fit check at\nswitch placement (`SwitchSku::chassis_depth_m()`) and lets the\nrack inspector show \"this is a 450 mm wall-mount cabinet, the\n700 mm spine you ordered won't fit.\" Resolved engine-side via\n`Rack::depth_m()`, so legacy saves get the standard-DC fallback."
                                },
                                "floor_slot": {
                                    "type": ["integer", "null"],
                                    "format": "int32",
                                    "description": "Index into the AZ's rack-slot grid where this rack physically\nstands. Renderer looks up `RackSlot{N}` Marker3D anchors under\nthe world scene to place it. `None` for tiers that use authored\naisle layouts instead of the slot grid.",
                                    "minimum": 0
                                },
                                "wall_mount": {
                                    "type": "boolean",
                                    "description": "True for the wall-mounted cabinet that ships as a fixture of the\nGarage / SmallColo rooms. Renderer binds the authored `WallRack`\nnode to this rack's id (no procedural visual) and the inspector\nsurfaces AZ-level edd + floor-tower context above / below the\nU stack."
                                },
                                "pending_placement": {
                                    "type": "boolean",
                                    "description": "True while this rack is a bought-but-not-installed crate in the\nshipping zone. The renderer draws a crate prop (not a rack\nchassis) and the player carries it to a floor slot via\n`InstallRack`."
                                },
                                "held_by": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "MULTIPLAYER — which player is carrying this crate. Meaningless unless\n`held` is true. Only meaningful while `pending_placement` is true.\n\nThe renderer skips a held crate's shipping-zone prop and excludes it\nfrom the pile layout, so both clients agree about which crates are on\nthe floor and where. Before this, each client hid only the crate IT was\ncarrying, and the pile is indexed by position in the remaining list —\nso two players carrying crates saw two different piles.\n\nSession state: `load_reported` clears it, so this is always `0` on the\nfirst snapshot after a load.",
                                    "minimum": 0
                                },
                                "held": {
                                    "type": "boolean",
                                    "description": "Is anybody carrying it? Separate from `held_by` because\n`PlayerId::LOCAL` is `0` (see `DeliveryView::held`)."
                                },
                                "resale_dollars": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Resale value if the rack is uninstalled and sold at the sell\nzone. Surfaced so the carry HUD can show \"Sell for $X\"."
                                },
                                "binding_key": {
                                    "type": ["string", "null"],
                                    "description": "Symbolic key tying this rack to an authored scene node of the same\nname. When set, `world_equipment_sync` binds `node.name ==\nbinding_key` and stamps the node's `entity_id` — the multi-rack\ngeneralization of the single-wall-rack first-by-kind matcher.\n`None` for player-bought / procedurally-spawned racks."
                                }
                            }
                        }
                    },
                    "total": {
                        "type": "integer",
                        "description": "Total matching items before `limit`/`offset` were applied.",
                        "minimum": 0
                    },
                    "offset": {
                        "type": "integer",
                        "description": "Offset this page started at.",
                        "minimum": 0
                    },
                    "count": {
                        "type": "integer",
                        "description": "Number of items returned. Always `<= limit` and `<= MAX_LIMIT`.",
                        "minimum": 0
                    }
                }
            },
            "Page_RegionView": {
                "type": "object",
                "description": "Wrapper for a paginated collection.\n\nEvery collection endpoint returns this. Unbounded collection responses are\nnot an option: a late-campaign save has thousands of hosts, and serializing\nall of them would occupy the single tokio thread to produce a body nobody\nwants.",
                "required": ["items", "total", "offset", "count"],
                "properties": {
                    "items": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "required": ["id", "name", "area", "az_count", "status"],
                            "properties": {
                                "id": { "type": "integer", "format": "int32", "minimum": 0 },
                                "name": { "type": "string" },
                                "area": { "type": "string" },
                                "az_count": { "type": "integer", "format": "int32", "minimum": 0 },
                                "status": { "$ref": "#/components/schemas/AzVisual" },
                                "backbone_load_gbps": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Smoothed inter-AZ backbone throughput (Gbps) — cross-AZ traffic on the\nprovider's regional fibre (multi-AZ DB replication today). Currently a\nfacility-wide meter surfaced on every region; per-region-pair split is a\nlater refinement. `#[serde(default)]` for old saves."
                                },
                                "backbone_capacity_gbps": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Inter-AZ backbone capacity (Gbps). Above it the backbone saturates and\ndegrades multi-AZ tenants (`backbone_saturated`)."
                                },
                                "backbone_saturated": {
                                    "type": "boolean",
                                    "description": "True when cross-AZ load exceeds the backbone capacity."
                                }
                            }
                        }
                    },
                    "total": {
                        "type": "integer",
                        "description": "Total matching items before `limit`/`offset` were applied.",
                        "minimum": 0
                    },
                    "offset": {
                        "type": "integer",
                        "description": "Offset this page started at.",
                        "minimum": 0
                    },
                    "count": {
                        "type": "integer",
                        "description": "Number of items returned. Always `<= limit` and `<= MAX_LIMIT`.",
                        "minimum": 0
                    }
                }
            },
            "Page_ServicePlaneView": {
                "type": "object",
                "description": "Wrapper for a paginated collection.\n\nEvery collection endpoint returns this. Unbounded collection responses are\nnot an option: a late-campaign save has thousands of hosts, and serializing\nall of them would occupy the single tokio thread to produce a body nobody\nwants.",
                "required": ["items", "total", "offset", "count"],
                "properties": {
                    "items": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "description": "Per-service control-plane + data-plane status, for the ops-console Services\npage (`tasks/service_model.md`). One per host-bound `ServiceClass` when the\nservice plane is enabled. The renderer picks `label` vs `label_eng` by\njargon mode.",
                            "required": [
                                "class_tag",
                                "label",
                                "label_eng",
                                "control_plane_host_ids",
                                "replicas_configured",
                                "replicas_floor",
                                "shards",
                                "serving_hosts",
                                "healthy",
                                "degraded",
                                "overloaded",
                                "need_vcpu_milli",
                                "need_mem_mb",
                                "agent_vcpu_milli",
                                "agent_mem_mb"
                            ],
                            "properties": {
                                "class_tag": { "type": "string" },
                                "label": { "type": "string" },
                                "label_eng": { "type": "string" },
                                "control_plane_host_ids": {
                                    "type": "array",
                                    "items": { "type": "integer", "format": "int32", "minimum": 0 },
                                    "description": "Hosts running a control-plane member (one per HA replica)."
                                },
                                "replicas_configured": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Configured HA replica count (1 = single instance / SPOF).",
                                    "minimum": 0
                                },
                                "replicas_floor": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Scenario-imposed minimum replicas — the player can't tune below this\n(≥1). Drives the replica spinner's lower bound.",
                                    "minimum": 0
                                },
                                "shards": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Horizontal shard count (P3) — `1` = fits one host; `>1` = the control\nplane outgrew a single host and split across N to relieve load.",
                                    "minimum": 0
                                },
                                "serving_hosts": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Hosts running a data-plane agent (= hosts serving this class).",
                                    "minimum": 0
                                },
                                "healthy": {
                                    "type": "boolean",
                                    "description": "Control plane reachable (≥1 member up + reachable)."
                                },
                                "degraded": {
                                    "type": "boolean",
                                    "description": "Degraded → scheduling frozen (unreachable or overloaded)."
                                },
                                "overloaded": {
                                    "type": "boolean",
                                    "description": "Outgrew the host it runs on — needs a bigger host or more replicas."
                                },
                                "need_vcpu_milli": {
                                    "type": "integer",
                                    "format": "int64",
                                    "description": "Fleet-scaled control-plane compute need (millivcpu, mem_mb) — the \"your\ncontrol plane has to grow with your fleet\" number.",
                                    "minimum": 0
                                },
                                "need_mem_mb": {
                                    "type": "integer",
                                    "format": "int64",
                                    "minimum": 0
                                },
                                "agent_vcpu_milli": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Per-host data-plane agent cost (config — surfaced so the page can show\nand, later, tune it).",
                                    "minimum": 0
                                },
                                "agent_mem_mb": {
                                    "type": "integer",
                                    "format": "int32",
                                    "minimum": 0
                                }
                            }
                        }
                    },
                    "total": {
                        "type": "integer",
                        "description": "Total matching items before `limit`/`offset` were applied.",
                        "minimum": 0
                    },
                    "offset": {
                        "type": "integer",
                        "description": "Offset this page started at.",
                        "minimum": 0
                    },
                    "count": {
                        "type": "integer",
                        "description": "Number of items returned. Always `<= limit` and `<= MAX_LIMIT`.",
                        "minimum": 0
                    }
                }
            },
            "Page_StaffView": {
                "type": "object",
                "description": "Wrapper for a paginated collection.\n\nEvery collection endpoint returns this. Unbounded collection responses are\nnot an option: a late-campaign save has thousands of hosts, and serializing\nall of them would occupy the single tokio thread to produce a body nobody\nwants.",
                "required": ["items", "total", "offset", "count"],
                "properties": {
                    "items": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "required": ["id", "name", "role", "tier", "monthly_salary_dollars"],
                            "properties": {
                                "id": { "type": "integer", "format": "int32", "minimum": 0 },
                                "name": { "type": "string" },
                                "role": { "type": "string" },
                                "tier": { "type": "string" },
                                "monthly_salary_dollars": { "type": "number", "format": "double" },
                                "traits": {
                                    "type": "array",
                                    "items": { "type": "string" },
                                    "description": "Personality trait names (e.g. \"Gifted\", \"Flaky\"). Empty for none."
                                }
                            }
                        }
                    },
                    "total": {
                        "type": "integer",
                        "description": "Total matching items before `limit`/`offset` were applied.",
                        "minimum": 0
                    },
                    "offset": {
                        "type": "integer",
                        "description": "Offset this page started at.",
                        "minimum": 0
                    },
                    "count": {
                        "type": "integer",
                        "description": "Number of items returned. Always `<= limit` and `<= MAX_LIMIT`.",
                        "minimum": 0
                    }
                }
            },
            "Page_SubnetView": {
                "type": "object",
                "description": "Wrapper for a paginated collection.\n\nEvery collection endpoint returns this. Unbounded collection responses are\nnot an option: a late-campaign save has thousands of hosts, and serializing\nall of them would occupy the single tokio thread to produce a body nobody\nwants.",
                "required": ["items", "total", "offset", "count"],
                "properties": {
                    "items": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "description": "An L2 broadcast domain (subnet) — the finer isolation grain BELOW the VRF: a\nVRF is the routed L3 boundary, a subnet is one L2 segment within it, keyed\none-per-`ServiceClass` (so a tenant running VMs + Functions + K8s + a managed\nDB has four sibling subnets, each independently dedicatable). Small top-level\nlist on `WorldSnapshot` (msgpack keyframe-only, exactly like `segments` /\n`host_pools`; subnets change rarely, so they never ride the per-tick capnp\ndelta). Empty until the network-isolation model mints any subnet.",
                            "required": ["id", "l2vni", "vrf_id", "service"],
                            "properties": {
                                "id": { "type": "integer", "format": "int32", "minimum": 0 },
                                "l2vni": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "L2 VXLAN Network Identifier for this subnet's broadcast domain (minted\nfrom a range disjoint from the VRF L3 VNIs).",
                                    "minimum": 0
                                },
                                "vrf_id": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "The VRF this subnet routes within. `0` is the Default shared VRF.",
                                    "minimum": 0
                                },
                                "service": {
                                    "type": "string",
                                    "description": "The resident service class this subnet carries — the isolation grain, as\na `ServiceClass` tag (\"vm\"/\"fn\"/\"obj\"/\"db\"/\"cdn\"/\"k8s\"/\"lb\")."
                                },
                                "member_count": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Count of resident UNITS currently homed to this subnet — one per placed\nunit whose `Engine::unit_subnet` is this one, whatever its product.\n\nGeneric since 2026-08-10 (PROTOCOL 46). This mixed three grains before:\none per VM allocation, one per database CUSTOMER and one per cluster\nCUSTOMER, so a 5-node cluster read 1, a database's copies were counted\ntwice, and a Functions subnet read 0 while its containers ran in it.",
                                    "minimum": 0
                                },
                                "dedicated_host_ids": {
                                    "type": "array",
                                    "items": { "type": "integer", "format": "int32", "minimum": 0 },
                                    "description": "Hosts physically dedicated to this subnet — those whose `admit` filter is\nexactly this subnet's `(vrf, class)` (`{scope: L3vni(vrf), service:\nService(class)}`). Empty when the subnet is logical-only."
                                },
                                "carrier_host_ids": {
                                    "type": "array",
                                    "items": { "type": "integer", "format": "int32", "minimum": 0 },
                                    "description": "Hosts physically CARRYING this subnet's members — the distinct host ids\nrunning the resident UNITS homed to this subnet, every product included\n(function containers were missing until 2026-08-10). The L2VNI\n(service-subnet) analog of\n`NetworkSegmentView.carrier_host_ids` (the VRF-grain equivalent); lets\nthe netview overlay colour hosts by which L2VNI they carry, not just\nwhich VRF. Sorted ascending, deduped. Empty until members are homed."
                                },
                                "routes_via": {
                                    "type": "string",
                                    "description": "How this subnet's cross-subnet (east-west) traffic is ROUTED out of its\nL2VNI — the first-hop router serving its carrier hosts (`Engine::\nfirst_hop_router` on the lowest-id carrier). One of:\n  * `\"switch\"` — a distributed anycast L3 leaf routes it LOCALLY (resilient:\n    survives an edge-gateway death);\n  * `\"gateway\"` — CENTRALIZED at the edge gateway (a pure-L2 fabric hairpins\n    cross-subnet traffic up to it, so a non-HA gateway is the SPOF for the\n    tenant's tier-to-tier traffic);\n  * `\"\"` — no live L3 router on the path (cross-subnet traffic is\n    PARTITIONED), or a logical-only subnet with no carrier hosts yet.\nLets the segments/isolation lens show a tenant WHERE their tiers route and\nflag the pure-L2 cross-subnet SPOF proactively, not just via the\n`InterSubnetPartition` incident once it breaks."
                                }
                            }
                        }
                    },
                    "total": {
                        "type": "integer",
                        "description": "Total matching items before `limit`/`offset` were applied.",
                        "minimum": 0
                    },
                    "offset": {
                        "type": "integer",
                        "description": "Offset this page started at.",
                        "minimum": 0
                    },
                    "count": {
                        "type": "integer",
                        "description": "Number of items returned. Always `<= limit` and `<= MAX_LIMIT`.",
                        "minimum": 0
                    }
                }
            },
            "Page_SwitchView": {
                "type": "object",
                "description": "Wrapper for a paginated collection.\n\nEvery collection endpoint returns this. Unbounded collection responses are\nnot an option: a late-campaign save has thousands of hosts, and serializing\nall of them would occupy the single tokio thread to produce a body nobody\nwants.",
                "required": ["items", "total", "offset", "count"],
                "properties": {
                    "items": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "required": [
                                "id",
                                "rack_id",
                                "az_id",
                                "start_u",
                                "u_size",
                                "sku_name",
                                "port_count",
                                "uplink_capacity_gbps",
                                "egress_gbps",
                                "saturated",
                                "indicator"
                            ],
                            "properties": {
                                "id": { "type": "integer", "format": "int32", "minimum": 0 },
                                "display_name": {
                                    "type": "string",
                                    "description": "Player-facing identity like `sw-01`. See `HostView::display_name`."
                                },
                                "display_name_plain": {
                                    "type": "string",
                                    "description": "Plain-mode counterpart: `switch 1` vs `sw-01`."
                                },
                                "role": {
                                    "$ref": "#/components/schemas/SwitchTopoRole",
                                    "description": "Topology-derived role: where this switch sits in the fabric.\nComputed each snapshot from cable wiring (NOT the SKU's static\n`sim_core::switch::SwitchRole`, which describes the catalog\nclass — `Desktop`/`Access`/`ToR`/`Leaf`/`Aggregation`/`Spine`).\nNetView uses this to pick \"edge switch\" / \"core switch\" sub-labels."
                                },
                                "rack_id": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Rack id when rack-mounted; 0 for wall-shelf switches.",
                                    "minimum": 0
                                },
                                "az_id": { "type": "integer", "format": "int32", "minimum": 0 },
                                "start_u": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Starting U slot when rack-mounted; 0 when on a wall shelf.",
                                    "minimum": 0
                                },
                                "u_size": { "type": "integer", "format": "int32", "minimum": 0 },
                                "on_wall": {
                                    "type": "boolean",
                                    "description": "True for wall-shelf switches (garage tier — no rack required)."
                                },
                                "on_shelf": {
                                    "type": "boolean",
                                    "description": "True when this is a Desktop-class switch sitting on a 1U cantilever\nshelf inside a rack. The renderer spawns a `shelf_1u_cantilever.glb`\nin the same U slot and centers the switch on the tray; engine-side\nthe implicit shelf occupies the slot via `effective_u_size()`.\nMutually exclusive with `on_wall` (a switch can't be both)."
                                },
                                "sku_name": { "type": "string" },
                                "port_count": {
                                    "type": "integer",
                                    "format": "int32",
                                    "minimum": 0
                                },
                                "port_capacity_gbps": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Per-port line speed (Gbps). Used by the renderer to color-code port LEDs.",
                                    "minimum": 0
                                },
                                "uplink_capacity_gbps": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "NO CLIENT READER (audited 2026-08-09) — every `uplink_capacity_gbps`\nhit in `godot/` resolves to a DIFFERENT field\n(`EddView::total_uplink_capacity_gbps`,\n`CapacityView::free_uplink_capacity_gbps`, the fleet history rings).\nLeft in place because it holds capnp ordinal @13 on `SwitchViewCp`\nand removing a capnp field is an ordinal change, which decodes\nsilently WRONG if it drifts — a few bytes per switch per delta is\nnot worth that. Retire it with the next deliberate capnp revision,\nnot opportunistically.",
                                    "minimum": 0
                                },
                                "fabric_capacity_gbps": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Switching fabric (backplane) capacity. The aggregate cross-port\nthroughput ceiling — independent of trunk uplink. Cheap T1\nswitches oversubscribe heavily; datacenter SKUs are full\nnon-blocking.",
                                    "minimum": 0
                                },
                                "egress_gbps": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Smoothed directional egress in Gbps (data leaving the switch\noutbound). Pairs with `ingress_gbps`. The full-duplex fabric\nload (the saturation metric) is `fabric_load_gbps` below."
                                },
                                "ingress_gbps": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Smoothed directional ingress in Gbps — bytes flowing INTO the\nswitch from upstream/peers. Mirrors `egress_gbps`."
                                },
                                "fabric_load_gbps": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Smoothed full-duplex fabric load (= egress + ingress,\nindependently smoothed). Compared against `fabric_capacity_gbps`\n(capacity) — when this exceeds capacity the fabric ASIC drops\npackets. The saturation metric, not the directional metric.\nSee `docs/SCHEMA.md` § \"Naming conventions — Throughput\"."
                                },
                                "saturated": { "type": "boolean" },
                                "fabric_saturated": {
                                    "type": "boolean",
                                    "description": "`true` when fabric load exceeds `fabric_capacity_gbps`. Drives drops\nindependent of trunk saturation."
                                },
                                "broadcast_storm": {
                                    "type": "boolean",
                                    "description": "`true` when this switch is caught in a broadcast storm — a loop\nthrough an unmanaged switch with no STP to break it. The switch\nforwards nothing until a cable is pulled. Drives a critical console\nstate, distinct from saturation. (Switch CLASS is not duplicated\nhere — resolve it via `sku_name` → `SwitchSkuView.switch_class`.)"
                                },
                                "indicator": {
                                    "$ref": "#/components/schemas/IndicatorLed",
                                    "description": "MAC flapping (2+ unbonded cables switch↔gateway) surfaces via the amber\n`indicator` below + the MacFlapDetected event / MacFlapping incident — no\ndedicated wire bool, so the capnp SwitchView contract is unchanged."
                                },
                                "ports": {
                                    "type": "array",
                                    "items": { "$ref": "#/components/schemas/PortView" },
                                    "description": "Per-physical-port readouts. One entry per `port_count`, sourced\nfrom `engine.ports` filtered by owner. Populated for the\nworkbench inspector (T1 has no rack but still wants per-port\nLEDs / speed badges / activity blink). Rack-resident switches\nare also surfaced here for renderers that don't open a full\n`RackInspectSnapshot`."
                                },
                                "inv_status": {
                                    "type": "string",
                                    "description": "Lifecycle status — see `HostView::inv_status`."
                                },
                                "age_hours": { "type": "integer", "format": "int32", "minimum": 0 },
                                "power_on_hours": {
                                    "type": "integer",
                                    "format": "int32",
                                    "minimum": 0
                                },
                                "condition_pct": {
                                    "type": "integer",
                                    "format": "int32",
                                    "minimum": 0
                                },
                                "mtbf_hours": {
                                    "type": "integer",
                                    "format": "int32",
                                    "minimum": 0
                                },
                                "resale_dollars": { "type": "number", "format": "double" },
                                "monthly_opex_dollars": {
                                    "type": "number",
                                    "format": "double",
                                    "description": "Live projected monthly opex ($/mo) — running fee + chassis power +\nthis switch's seated-optic draw. Mirrors the host console's\n`monthly_opex_dollars` read."
                                },
                                "powered": {
                                    "type": "boolean",
                                    "description": "At least one AC cord seated (or none required). False = dark\nuntil the player plugs it in. Mirrors the engine's `powered`."
                                },
                                "power_feeds": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Connected AC cord count. >= 2 = PSU-failure resilience at\n`redundant_psu_overhead_w` extra draw per extra cord.",
                                    "minimum": 0
                                }
                            }
                        }
                    },
                    "total": {
                        "type": "integer",
                        "description": "Total matching items before `limit`/`offset` were applied.",
                        "minimum": 0
                    },
                    "offset": {
                        "type": "integer",
                        "description": "Offset this page started at.",
                        "minimum": 0
                    },
                    "count": {
                        "type": "integer",
                        "description": "Number of items returned. Always `<= limit` and `<= MAX_LIMIT`.",
                        "minimum": 0
                    }
                }
            },
            "Page_WorkOrderView": {
                "type": "object",
                "description": "Wrapper for a paginated collection.\n\nEvery collection endpoint returns this. Unbounded collection responses are\nnot an option: a late-campaign save has thousands of hosts, and serializing\nall of them would occupy the single tokio thread to produce a body nobody\nwants.",
                "required": ["items", "total", "offset", "count"],
                "properties": {
                    "items": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "description": "Renderer-friendly mirror of `sim_core::work::WorkOrder`. Drives the\nwork-order panel UI + per-actor\"currently doing\"badge.",
                            "required": [
                                "id",
                                "kind",
                                "kind_label",
                                "actor",
                                "actor_staff_id",
                                "started_at_ns",
                                "last_progress_at_ns",
                                "resolved_at_ns",
                                "current_step",
                                "steps",
                                "blocking",
                                "incident_id"
                            ],
                            "properties": {
                                "id": { "type": "integer", "format": "int64", "minimum": 0 },
                                "kind": {
                                    "type": "string",
                                    "description": "Variant tag for `WorkOrderKind` — \"SwapPsu\", \"LayCable\", etc."
                                },
                                "kind_label": {
                                    "type": "string",
                                    "description": "Short human label — `WorkOrderKind::label()`."
                                },
                                "actor": {
                                    "type": "string",
                                    "description": "\"Player\" / \"Staff\" / \"Unassigned\" + optional staff id."
                                },
                                "actor_staff_id": {
                                    "type": "integer",
                                    "format": "int32",
                                    "minimum": 0
                                },
                                "started_at_ns": {
                                    "type": "integer",
                                    "format": "int64",
                                    "minimum": 0
                                },
                                "last_progress_at_ns": {
                                    "type": "integer",
                                    "format": "int64",
                                    "minimum": 0
                                },
                                "resolved_at_ns": {
                                    "type": "integer",
                                    "format": "int64",
                                    "description": "0 while open; set on resolution.",
                                    "minimum": 0
                                },
                                "current_step": {
                                    "type": "integer",
                                    "format": "int32",
                                    "description": "Index of the step currently in flight. Past steps in `steps`\nhave `completed_at_ns != 0`; this and following ones don't.",
                                    "minimum": 0
                                },
                                "steps": {
                                    "type": "array",
                                    "items": { "$ref": "#/components/schemas/WorkOrderStepView" }
                                },
                                "blocking": {
                                    "type": "string",
                                    "description": "Reason the WO is paused mid-flight, if any. Empty otherwise."
                                },
                                "incident_id": {
                                    "type": "integer",
                                    "format": "int64",
                                    "description": "Linked incident, or 0 if the WO is standalone.",
                                    "minimum": 0
                                }
                            }
                        }
                    },
                    "total": {
                        "type": "integer",
                        "description": "Total matching items before `limit`/`offset` were applied.",
                        "minimum": 0
                    },
                    "offset": {
                        "type": "integer",
                        "description": "Offset this page started at.",
                        "minimum": 0
                    },
                    "count": {
                        "type": "integer",
                        "description": "Number of items returned. Always `<= limit` and `<= MAX_LIMIT`.",
                        "minimum": 0
                    }
                }
            },
            "PatchPanelView": {
                "type": "object",
                "required": [
                    "id",
                    "rack_id",
                    "start_u",
                    "u_size",
                    "sku_name",
                    "port_count",
                    "used_ports"
                ],
                "properties": {
                    "id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "rack_id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "start_u": { "type": "integer", "format": "int32", "minimum": 0 },
                    "u_size": { "type": "integer", "format": "int32", "minimum": 0 },
                    "sku_name": { "type": "string" },
                    "port_count": { "type": "integer", "format": "int32", "minimum": 0 },
                    "used_ports": {
                        "type": "integer",
                        "format": "int32",
                        "description": "How many of the panel's ports currently carry a cable.",
                        "minimum": 0
                    },
                    "ports": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/PortView" },
                        "description": "Per-port views — same shape as `HostView::ports` /\n`SwitchView::ports` / `EddView::ports` so click resolvers,\ninspectors, and cable renderers can treat patch panel ports as\nfirst-class without a kind-specific branch.\n\nPatch panels are passive: `state` is always `Down`, `gbps`\nalways 0, no link/activity LEDs render. The fields populate\nfor parity (port_id, label, peer, cable_id) so callers can\nresolve \"what's plugged in here\" the same way they do for\nswitches and hosts."
                    },
                    "inv_status": {
                        "type": "string",
                        "description": "Lifecycle status — see `HostView::inv_status`."
                    },
                    "age_hours": { "type": "integer", "format": "int32", "minimum": 0 },
                    "power_on_hours": { "type": "integer", "format": "int32", "minimum": 0 },
                    "condition_pct": { "type": "integer", "format": "int32", "minimum": 0 },
                    "resale_dollars": { "type": "number", "format": "double" }
                }
            },
            "PendingSpareView": {
                "type": "object",
                "required": [
                    "kind",
                    "sku_name",
                    "spec_label",
                    "ordered_at_ns",
                    "arrives_at_ns",
                    "paid_dollars"
                ],
                "properties": {
                    "kind": { "type": "string" },
                    "sku_name": {
                        "type": "string",
                        "description": "SKU name from the catalog (\"PSU 800W EPS\", etc.). Empty when\nthe order was placed via `BuySpare { kind: ... }` fallback."
                    },
                    "spec_label": {
                        "type": "string",
                        "description": "Pre-rendered spec label — \"PSU 800W EPS\", \"Disk 4TB SAS 3.5\\\"\",\n\"Fan 120mm 2000rpm\". Buy-panel renders directly."
                    },
                    "ordered_at_ns": { "type": "integer", "format": "int64", "minimum": 0 },
                    "arrives_at_ns": { "type": "integer", "format": "int64", "minimum": 0 },
                    "paid_dollars": { "type": "number", "format": "double" }
                }
            },
            "PlacementFailureView": {
                "type": "object",
                "description": "**The gate that actually bound a placement, with its numbers.** The wire\nform of `sim_core::placement::Binding` — the structured answer to \"why is\nmy service short\", which every provisioning path used to compute correctly\nand then throw away into an `engine_log!`.\n\nNUMBERS, NOT A SENTENCE. `needed` / `available` are stated in `unit`s of\nwhatever the gate measures — distinct hosts for a spread contract, MB for\nmemory, GB for disk — and the client owns the wording. That split is the\npoint: triage cards UG-31, UG-28, UG-146 and UG-176 are all one bug, a\nCAPACITY figure reported where the binding constraint was a count of\ndistinct hosts, which sends the player to buy RAM that fixes nothing.\n\nEmpty state is `code: \"\"` (per `docs/SCHEMA.md`'s empty-state policy, not\nan `Option`): the last placement attempt for this service did not fail, so\nthere is no gate to name.",
                "required": [
                    "code",
                    "unit",
                    "subject",
                    "needed",
                    "available",
                    "shortfall",
                    "considered",
                    "candidates"
                ],
                "properties": {
                    "code": {
                        "type": "string",
                        "description": "Stable `Binding::code()` — `placement_spread`, `placement_resource`,\n`placement_no_hosts`, `placement_maintenance`, ... Empty when no gate\nbound. The renderer branches on THIS, never on the prose."
                    },
                    "unit": {
                        "type": "string",
                        "description": "STABLE TOKEN for what `needed` / `available` are counted in: a\nfault-domain slug (`FaultDomain::tag` — `host` / `switch` / `az` /\n`region` / `power` / `router`) for a spread contract, a resource unit\n(`Resource::unit_label` — `MB` / `GB` / `IOPS` / `slices`) for a\ncapacity gate, and `host` for the categorical gates, which all count\nmachines. The client turns this into a localized noun; it is\ndeliberately not one already."
                    },
                    "subject": {
                        "type": "string",
                        "description": "STABLE TOKEN naming the thing the gate is about, when it has one — the\nresource (`Resource::tag`), the product a pool is not set to serve\n(`ServiceClass::tag`), the required architecture / accelerator, the\npinned region / AZ id. Empty otherwise."
                    },
                    "needed": {
                        "type": "integer",
                        "format": "int64",
                        "description": "How many `unit`s the placement needed."
                    },
                    "available": {
                        "type": "integer",
                        "format": "int64",
                        "description": "How many were actually available. For a capacity gate this is the MOST\nany single surviving candidate had, so the difference reads as a real\ngap rather than a fleet-wide total the unit cannot use anyway."
                    },
                    "shortfall": {
                        "type": "integer",
                        "format": "int64",
                        "description": "`Binding::shortfall()` — the one number a remedy is sized from (\"buy 29\nmore servers\"). 0 for categorical gates."
                    },
                    "considered": {
                        "type": "integer",
                        "format": "int32",
                        "description": "How many hosts entered the candidate scan, before any gate.",
                        "minimum": 0
                    },
                    "candidates": {
                        "type": "integer",
                        "format": "int32",
                        "description": "How many hosts were still in the running when a CAPACITY gate bound —\nthe field that survived admission, homing, cordon, arch/accel and the\nresidency pins and actually reached the fit check.\n\n**`considered - candidates` is the confinement signal.** \"The roomiest\nserver has 2 GB\" is true and useless when the tenant may only use 3 of\n40 machines: the player looks at an idle 200 GB box and concludes the\ngame is broken, and \"buy another host\" is the wrong remedy anyway. The\ntwo counts let the client pick between that sentence and \"widen the\nsegment, or add a host to it\" — which is why both ship rather than one.\n\nOnly `placement_resource` defines it (`Binding::Resource.candidates`);\nevery other gate reports the count it narrowed to in `available`, so\nthis is 0 there.",
                        "minimum": 0
                    }
                }
            },
            "PortId": { "type": "integer", "format": "int64", "minimum": 0 },
            "PortLight": {
                "type": "object",
                "required": ["link", "active"],
                "properties": {
                    "link": { "$ref": "#/components/schemas/PortLink" },
                    "active": { "type": "boolean" }
                }
            },
            "PortLink": { "type": "string", "enum": ["Down", "Gbe1", "Gbe10", "Gbe25", "Gbe100"] },
            "PortView": {
                "type": "object",
                "description": "Per-physical-port readout. `peer` is the *other end* of the cable on this\nport — None when the port is dark. The renderer uses `peer` to draw the\nport label tooltip (\"→ Switch S2 port 17\") without needing the cable list.",
                "required": ["index", "label", "link", "active"],
                "properties": {
                    "index": { "type": "integer", "format": "int32", "minimum": 0 },
                    "label": { "type": "string" },
                    "link": { "$ref": "#/components/schemas/PortLink" },
                    "active": { "type": "boolean" },
                    "cable_ids": {
                        "type": "array",
                        "items": { "type": "integer", "format": "int32", "minimum": 0 },
                        "description": "The cable ids attached to this port — occupancy is `!is_empty()`, which\ndrives the seated plug/module in the renderer (NOT `state`, which only\ntints the LED). A normal port has 0 or 1; a breakout trunk has one per\nleg, so it seats its QSFP module for the same reason any port seats its\nplug. The first id is the primary (peer/jacket-colour). Stable across\ndeltas AND keyframes — that's why the trunk no longer flickers."
                    },
                    "peer": {
                        "oneOf": [
                            { "type": "null" },
                            { "$ref": "#/components/schemas/CableEndpointView" }
                        ]
                    },
                    "port_id": {
                        "type": "integer",
                        "format": "int64",
                        "description": "Stable engine port id. Lets the renderer cross-reference a port\nacross snapshots without rebuilding from (entity_id, index)\ntuples. Zero when projection couldn't resolve to a Port (defensive).",
                        "minimum": 0
                    },
                    "state": {
                        "type": "string",
                        "description": "Operational state, from `Port.state` — one of \"Down\", \"Up\",\n\"Errored\", \"Disabled\". Drives LED colour at the renderer."
                    },
                    "media": {
                        "type": "string",
                        "description": "Physical media at the port face, from `Port.media`. One of\n`\"Rj45\"` / `\"SfpPlus\"` / `\"Sfp28\"` / `\"Qsfp28\"` / `\"QsfpDD\"` /\n`\"Carrier\"` / `\"IecPower\"` / `\"PatchKeystone\"`. Drives the\nrenderer's per-port plug geometry + boot offset."
                    },
                    "gbps_tx": {
                        "type": "number",
                        "format": "double",
                        "description": "Smoothed throughput in Gbps. For switch ports this is the\nper-port share of the switch's egress, attributed proportionally\nacross its active ports.\nFor host NIC ports, mirrors the host-link smoothed throughput.\n0.0 when the port is dark or no data is flowing.\n\nSmoothed *outbound* throughput in Gbps (data leaving this port\ntoward the peer). Phase 2 of the NetView engine migration:\nsplits the previously summed `gbps` so the dual-strand cable\nchevron can run on independent saturations per direction.\nEngine derives this from `Port.counters.bytes_egress_total` — the\nrx counterpart sits on `gbps_rx`. 0.0 on legacy snapshots."
                    },
                    "gbps_rx": {
                        "type": "number",
                        "format": "double",
                        "description": "Smoothed *inbound* throughput in Gbps (data arriving at this\nport). See `gbps_tx`."
                    },
                    "link_capacity_gbps": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Port-link capacity in Gbps (e.g. 1, 10, 25, 100). Pulled from\n`Port.link_capacity_gbps` — the negotiated speed; equals the SKU rating\ntoday and may shift later if cable kind reduces it.",
                        "minimum": 0
                    },
                    "error_count": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Aggregate error count from `Port.counters.errors_lifetime`.",
                        "minimum": 0
                    },
                    "bytes_egress_total": {
                        "type": "integer",
                        "format": "int64",
                        "description": "Lifetime tx/rx bytes from `Port.counters`.",
                        "minimum": 0
                    },
                    "bytes_ingress_total": { "type": "integer", "format": "int64", "minimum": 0 },
                    "link_flaps": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Number of times this port has flapped Up→Down→Up.",
                        "minimum": 0
                    },
                    "cable_able": {
                        "type": "boolean",
                        "description": "Whether the *player* may cable this port. False for carrier-facing\nports (the edd WAN side — it's the auto-wired ISP handoff,\nmanaged on the Internet panel, never patched). The diegetic\noverlay refuses to start/finish a cable on a port where this is\nfalse. Defaults true (snapshots are transient, never persisted)."
                    },
                    "connector": {
                        "type": "string",
                        "description": "`ConnectorKind::name()` for the connector seated at this port —\nderived from the plugged cable's kind × the port's cage generation\n(`ConnectorKind::resolve`). `\"None\"` when empty. Drives the renderer's\nexact plug/transceiver GLB + seating so it can never seat (e.g.) an LC\nduplex on a QSFP MPO port. Replaces the old coarse `wired_fibre` bool."
                    },
                    "color": {
                        "type": "string",
                        "description": "Jacket-colour id (\"Orange\" / \"Blue\" / …) of the cable plugged into\nthis port — resolved from that cable's `resolved_color()`, exactly\nas `connector` is resolved from its kind. `\"\"` when the port is\nunwired. Lets the renderer tint the RJ45 plug boot to match the run."
                    },
                    "breakout_used_gbps": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Breakout trunk pool, when this QSFP cage has been subdivided into\nlower-speed legs (see `sim_core::breakout`). `breakout_capacity_gbps`\nis > 0 ONLY while this port is an ACTIVE breakout trunk (it's the\npool size, e.g. 100 for a QSFP28 handing out 4×25 G); `used_gbps` is\nthe Gbps already committed to legs and `leg_count` how many legs are\nseated. All three are 0 for an ordinary port — including a plain\nQSFP cage with no legs yet.\n\nThis is the ONLY signal that lets the client tell a partially-used\nbreakout trunk apart from an empty port: both leave `cable_id ==\nNone` (the trunk's occupancy lives in the pool, not a single cable),\nso without this a trunk with 1 of 4 lanes used reads as \"empty\" and\nthe player gets no cue that it can still take more legs.",
                        "minimum": 0
                    },
                    "breakout_capacity_gbps": {
                        "type": "integer",
                        "format": "int32",
                        "minimum": 0
                    },
                    "breakout_leg_count": { "type": "integer", "format": "int32", "minimum": 0 }
                }
            },
            "ProposedBondGroupView": {
                "type": "object",
                "description": "A proposed bond — 2+ unbonded cables sharing a device pair that\nthe player could group into a LAG. Surfaced on every snapshot;\ndisappears as soon as any cable is bonded or removed.",
                "required": ["cable_ids", "owner_pair_label"],
                "properties": {
                    "cable_ids": {
                        "type": "array",
                        "items": { "type": "integer", "format": "int32", "minimum": 0 }
                    },
                    "owner_pair_label": { "type": "string" }
                }
            },
            "ProspectServiceView": {
                "type": "object",
                "description": "Per-service detail for ONE service in a prospect's bundle — the\nprospect-acceptance card's \"what they actually want\" line, for every\nservice kind (vm/fn/obj/db/lb/cdn/k8s), not just VMs. Mirrors the shape\nof the service's demand contract; `vcpu`/`memory_mb`/`storage_gb` are 0\nand `throughput_label`/`durability` are empty when the concept doesn't\napply to that kind (SCHEMA empty-state policy — 0/empty, not `Option`).",
                "required": [
                    "class_tag",
                    "label",
                    "requested_count",
                    "vcpu",
                    "memory_mb",
                    "storage_gb",
                    "throughput_label",
                    "durability"
                ],
                "properties": {
                    "class_tag": {
                        "type": "string",
                        "description": "Short-code service kind (\"vm\"/\"fn\"/\"obj\"/\"db\"/\"lb\"/\"cdn\"/\"k8s\") —\nmatches `ServiceConfig::kind_label` / `CustomerView.service_kinds`."
                    },
                    "label": {
                        "type": "string",
                        "description": "Plain-voice label (`ServiceClass::label`, falls back to the kind code)."
                    },
                    "requested_count": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Requested unit count — VM `target_count` / k8s `node_count` / 1 for\nsingleton services (Functions, Object Store, CDN, LB, managed DB).",
                        "minimum": 0
                    },
                    "vcpu": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Per-unit vCPU. 0 when the kind has no compute shape of its own.",
                        "minimum": 0
                    },
                    "memory_mb": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Per-unit RAM (MB). 0 when not applicable.",
                        "minimum": 0
                    },
                    "storage_gb": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Storage magnitude (GB) — object-store initial bucket / managed-DB\nblock + backup. 0 when the kind carries no storage ask.",
                        "minimum": 0
                    },
                    "throughput_label": {
                        "type": "string",
                        "description": "Service-specific rate, human & concise (\"50 req/s\",\n\"120 req/s · 8 KB/req\"). Empty when the kind has no request rate."
                    },
                    "durability": {
                        "type": "string",
                        "description": "Replication / durability tier name (\"Standard\"/\"High\"/\"Mission\"/\n\"replicated\"). Empty when the kind carries no durability contract."
                    },
                    "vgpu_slices": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Per-unit vGPU partition ask (`VmSpec.vgpu.slices`). 0 when the service\nneeds no GPU — the common case. Non-zero only for GPU personas (AI Lab /\nVFX / genomics), whose VMs must land on a GPU host. Lets the card spell\nout the GPU requirement on the demand line instead of hiding it.",
                        "minimum": 0
                    },
                    "gpu_mem_mb": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Per-unit GPU frame-buffer ask in MB (`VmSpec.vgpu.gpu_mem_mb`). 0 when\nno GPU. Pairs with `vgpu_slices` on the demand line.",
                        "minimum": 0
                    }
                }
            },
            "ProspectView": {
                "type": "object",
                "required": [
                    "id",
                    "name",
                    "archetype",
                    "tier",
                    "signup_bonus_dollars",
                    "compliance_required",
                    "required_arch",
                    "requires_multi_az",
                    "services_summary",
                    "expires_at_ns"
                ],
                "properties": {
                    "id": { "type": "integer", "format": "int64", "minimum": 0 },
                    "name": { "type": "string" },
                    "archetype": { "type": "string" },
                    "tier": { "type": "string" },
                    "signup_bonus_dollars": { "type": "number", "format": "double" },
                    "compliance_required": { "type": "string" },
                    "required_arch": {
                        "type": "string",
                        "description": "Raw CPU-arch requirement tag (\"x86_64\" / \"arm64\" / …) this prospect's\nVM or K8s workload demands, or \"\" if arch-agnostic. A hard placement\ngate: the card voices it (jargon \"x86-64 hosts\" vs plain \"Intel/AMD\nhosts\") beside the compliance/air-gap chips. Godot maps the tag via\nthe `arch_req_<tag>` glossary key."
                    },
                    "requires_multi_az": { "type": "boolean" },
                    "required_region": {
                        "type": ["integer", "null"],
                        "format": "int32",
                        "minimum": 0
                    },
                    "services_summary": { "type": "string" },
                    "service_kinds": {
                        "type": "array",
                        "items": { "type": "string" },
                        "description": "Short-code service mix mirroring `CustomerView.service_kinds`\n(subset of {\"fn\",\"vm\",\"obj\",\"lb\",\"db\",\"cdn\",\"k8s\"}). The ops-console\ncustomer/prospect table renders these as upper-case chips, so a\nprospect row reads the same as an active-customer row."
                    },
                    "estimated_monthly_revenue_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "List-price monthly run-rate this prospect would bill at signup,\nprojected from its VM workload (count × per-VM hourly × hours/month).\n0 for non-VM-shaped prospects. Lets the customers lens show a\nprospect's potential Revenue/hr before you sign them."
                    },
                    "expires_at_ns": { "type": "integer", "format": "int64", "minimum": 0 },
                    "initial_vm_count": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Initial VM count this prospect will request at signup. 0 when\nthe prospect doesn't run a VM workload (Functions / ObjectStore /\netc.) — the renderer surfaces those via `services_summary`.",
                        "minimum": 0
                    },
                    "vcpu_per_vm": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Per-VM vCPU count. 0 when no VM workload.",
                        "minimum": 0
                    },
                    "memory_mb_per_vm": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Per-VM memory (MB). 0 when no VM workload.",
                        "minimum": 0
                    },
                    "block_gb_per_vm": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Per-VM block-volume size (GB). 0 when the ask carries no volume\n(legacy templates / non-VM workloads). Billed via `BlockPricing`\nalongside the VM's compute charge.",
                        "minimum": 0
                    },
                    "growth_ceiling_vms": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Growth ceiling (max VMs the customer can compound to) read from\nthe archetype's `growth.plateau_at_allocation`. 0 = no archetype\nmatch or uncapped (Anchor / Enterprise). Renderer shows \"may\ngrow to ~N\" when > initial_vm_count.",
                        "minimum": 0
                    },
                    "fit_vcpu_after_pct": {
                        "type": "number",
                        "format": "double",
                        "description": "Capacity-fit verdict (P2 in `tasks/consequences.md`) — projected\nfleet VM utilization *after* accepting this prospect's initial demand,\non each axis. `1.0` = exactly full; `> 1.0` = would overcommit. Lets\nthe prospect card answer \"can I actually serve this?\" before signing.\n0.0 for prospects with no VM workload."
                    },
                    "fit_mem_after_pct": { "type": "number", "format": "double" },
                    "fit_ssd_after_pct": {
                        "type": "number",
                        "format": "double",
                        "description": "The DISK axis, on the same basis as the two above. `contract_footprint`\nhas always computed `Resource::SsdGb` (an object-storage bucket, a CDN\norigin, a DB's backup copies, a VM's block volume); the projection threw\nit away, so a card asking for 821 GB showed no disk meter at all and read\n\"Comfortable\" beside it. 0.0 when the bundle asks for no bytes."
                    },
                    "fit_can_serve_now": {
                        "type": "boolean",
                        "description": "True when the current fleet has free vCPU, memory **and** disk headroom\nfor this prospect's initial demand right now (no new hardware needed)."
                    },
                    "fit_oversubscribed": {
                        "type": "boolean",
                        "description": "True when accepting would push the fleet PAST physical into overcommit:\nthe initial VMs still fit under the host overcommit ceiling, but CPU is\nshared and tenants may slow under load. Never a false \"can't fit\"."
                    },
                    "fit_over_capacity": {
                        "type": "boolean",
                        "description": "True only for a genuine over-capacity fit: a VM-load prospect that won't\nfit even under overcommit (the \"Over capacity, add hardware first\"\nverdict). Boolean twin of that string so the card's Accept-anyway confirm\ngate keys off a field, not exact prose. False for no-VM / oversubscribed."
                    },
                    "fit_recommendation": {
                        "type": "string",
                        "description": "Engine-classified one-liner for the card: \"Comfortable\" / \"Tight fit\" /\n\"Will run oversubscribed (CPU shared under load)\" / \"Over capacity, add\nhardware first\" / \"Needs GPU hardware — add a GPU host first\" /\n\"No VM load\"."
                    },
                    "requires_gpu": {
                        "type": "boolean",
                        "description": "True when any of this prospect's VM services demands a GPU (vGPU\npartition / `required_accelerator == Gpu`). Drives the card's GPU meter\nand makes the serve verdict GPU-aware — a GPU tenant on a GPU-less fleet\nreads \"Needs GPU hardware\", not a false \"Comfortable\"."
                    },
                    "gpu_slices_demand": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Total vGPU slices this prospect's initial VMs would reserve\n(`initial_vm_count × per-VM slices`). 0 for non-GPU prospects. The\nfleet-fit input the serve verdict weighs against free vGPU slices.",
                        "minimum": 0
                    },
                    "services": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/ProspectServiceView" },
                        "description": "Per-service detail for the WHOLE bundle — one entry per service the\nprospect would sign for. The card renders each as a magnitude line so\nnon-VM prospects (object-store / DB / functions / ...) read as more\nthan a bare chip. Additive to the VM fields above, which still drive\nthe capacity-fit bar."
                    },
                    "demands_audit_logs": {
                        "type": "boolean",
                        "description": "True when the prospect's archetype demands audit logging (a live\nSIEM + audit feed). Surfaced as a requirement chip so a PCI /\ngovernment-contract prospect's otherwise-inexplicable rejection is\nlegible (\"Requires: audit logging\")."
                    },
                    "demands_air_gap": {
                        "type": "boolean",
                        "description": "True when the prospect's archetype demands an air-gapped facility."
                    },
                    "requires_private_segment": {
                        "type": "boolean",
                        "description": "True when the prospect wants a private/isolated network segment\n(VXLAN isolation) dedicated to its SLA tier. SOFT / recommended —\nunlike audit-logging / air-gap it does NOT hard-reject signing; it's\nsatisfied by a private segment whose `tier_rule` matches this\nprospect's `tier`. Surfaced as a \"Recommended\" chip, distinct in tone\nfrom the hard requirement chips."
                    },
                    "requires_dedicated_hosts": {
                        "type": "boolean",
                        "description": "True when the prospect wants a private network on DEDICATED, homed hosts\n(single-tenant iron) — the 4× isolation tier (vs 2× for a plain private\nnetwork). Implies `requires_private_segment`; the card shows one combined\nchip. msgpack-only (ProspectView is not carried in capnp)."
                    },
                    "requires_fault_isolation": {
                        "type": "boolean",
                        "description": "True when the prospect's contract asks for fault isolation — copies kept\noff a shared fault domain (redundancy tier > 1 host replica, or an\narchetype requiring anti-affinity). Mirrors\n`CustomerView.requires_fault_isolation` so the prospect \"Must clear\" card\nand the accepted-customer Requirements row read the SAME key and cannot\ndrift. msgpack-only (ProspectView is not carried in capnp)."
                    },
                    "covering_segment_id": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Id of the segment whose `tier_rule` already claims this prospect's\ntier — i.e. the segment the prospect would auto-join on signup via\ntier-rule precedence. `0` = none (no tier-rule segment covers this\ntier; the prospect would land on the Default shared segment). Lets\nthe prospect card offer \"isolate into <existing segment>\" without a\nsecond round-trip. Mirrors the accept-path covering check.",
                        "minimum": 0
                    },
                    "covering_segment_name": {
                        "type": "string",
                        "description": "Display name of the covering segment (empty when `covering_segment_id`\nis 0). Paired with the id so the card can label the existing-segment\noption without a client-side lookup."
                    },
                    "requires_uncontended": {
                        "type": "boolean",
                        "description": "True when the prospect's archetype contracts UNCONTENDED (1:1 CPU) —\nits workloads must land on hosts admitted to an `uncontended` segment\n(running 1:1, no overcommit). Wholly independent of dedication and\nanti-affinity spread. Drives the card's \"Needs 1:1 CPU\" chip so the\nplayer sees the guarantee (and the pool cost) before signing."
                    },
                    "requires_dedicated": {
                        "type": "boolean",
                        "description": "True when the prospect's archetype contracts DEDICATED, single-tenant\nhosts (private iron, no co-tenants). Mirrors `requires_dedicated_hosts`\non the archetype's own flag; surfaced as a \"Needs a dedicated host\"\nchip. Tenancy guarantee, not a CPU one — distinct from uncontended."
                    },
                    "uncontended_hosts_available": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Best-effort count of hosts the engine could convert into the shared\nuncontended (1:1) pool right now — open (unadmitted), serviceable,\nVM-serving hosts with physical room. Fleet-wide, not per-prospect. The\ncard renders \"<N> host(s) available for the 1:1 pool\" for an uncontended\nprospect so the player knows whether the pool can grow before signing.\n0 = none convertible (would need new hardware).",
                        "minimum": 0
                    }
                }
            },
            "QuestView": {
                "type": "object",
                "description": "One quest item — what the left-side panel renders. State is one of\n\"Locked\" / \"Open\" / \"Next\" / \"Done\"; the Next item is bolded by the\nrenderer to show the player what to focus on.",
                "required": ["id", "title", "blurb", "state", "unlock_step", "reward_label"],
                "properties": {
                    "id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "title": { "type": "string" },
                    "blurb": { "type": "string" },
                    "title_args": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/MsgArgView" },
                        "description": "Facts for `title` when it holds an i18n key. A generated rung reads\n\"Grow to {$count} customers\", so the number has to travel beside the\nkey: resolving engine-side would pick one language for every player\nin a co-op session."
                    },
                    "blurb_args": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/MsgArgView" },
                        "description": "Facts for `blurb`, same reasoning."
                    },
                    "state": { "type": "string" },
                    "unlock_step": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Progression step at which this quest unlocks (0-indexed).",
                        "minimum": 0
                    },
                    "reward_label": {
                        "type": "string",
                        "description": "Pre-rendered reward chip label (`+$5,000 · +2% rep`); empty = no reward."
                    }
                }
            },
            "RackFace": {
                "type": "string",
                "description": "Top vs bottom face of a rack — where the cable enters / exits the\nrack envelope. Top is overhead-tray territory; bottom feeds the\nfuture raised-floor / underfloor tray network.",
                "enum": ["Top", "Bottom"]
            },
            "RackId": { "type": "integer", "format": "int32", "minimum": 0 },
            "RackSide": {
                "type": "string",
                "description": "Vertical-strip side of a rack. Used for both cable-manager strips\n(left / right of the U column) and the corresponding corner of a\nrack-edge anchor. Distinct from `RoutingSide` — that enum is a\nper-endpoint single choice; this one is a coordinate the path\nreferences multiple times along a multi-anchor route.",
                "enum": ["Left", "Right"]
            },
            "RackView": {
                "type": "object",
                "required": [
                    "id",
                    "az_id",
                    "name",
                    "capacity_u",
                    "power_budget_w",
                    "power_draw_w",
                    "visual"
                ],
                "properties": {
                    "id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "az_id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "name": { "type": "string" },
                    "capacity_u": { "type": "integer", "format": "int32", "minimum": 0 },
                    "used_u": {
                        "type": "integer",
                        "format": "int32",
                        "description": "U slots actually consumed in this rack, counting EVERY rack occupant:\nhosts, switches, patch panels, security appliances, EDDs and gateways.\nSame population as `Engine::occupied_u`, sourced from the one-pass\n`Engine::occupied_u_by_rack`.\n\nAdded in PROTOCOL 49 (UG-294). Clients previously re-summed `u_size`\nover the `hosts` array, which under-reported by everything racked that\nwas not a server, and could only be right at world scope where the\nfleet-wide `capacity` block already answered it. Do not re-derive this\nclient-side; a second copy of the occupant list is what drifted.",
                        "minimum": 0
                    },
                    "power_budget_w": { "type": "integer", "format": "int32", "minimum": 0 },
                    "power_draw_w": { "type": "integer", "format": "int32", "minimum": 0 },
                    "visual": { "$ref": "#/components/schemas/RackVisual" },
                    "draped_cable_count": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Cables touching this rack that have no managed routing path.\nRenderer can paint the rack's \"neatness\" badge from this.",
                        "minimum": 0
                    },
                    "depth_m": {
                        "type": "number",
                        "format": "float",
                        "description": "Internal depth in metres. Drives the chassis-fit check at\nswitch placement (`SwitchSku::chassis_depth_m()`) and lets the\nrack inspector show \"this is a 450 mm wall-mount cabinet, the\n700 mm spine you ordered won't fit.\" Resolved engine-side via\n`Rack::depth_m()`, so legacy saves get the standard-DC fallback."
                    },
                    "floor_slot": {
                        "type": ["integer", "null"],
                        "format": "int32",
                        "description": "Index into the AZ's rack-slot grid where this rack physically\nstands. Renderer looks up `RackSlot{N}` Marker3D anchors under\nthe world scene to place it. `None` for tiers that use authored\naisle layouts instead of the slot grid.",
                        "minimum": 0
                    },
                    "wall_mount": {
                        "type": "boolean",
                        "description": "True for the wall-mounted cabinet that ships as a fixture of the\nGarage / SmallColo rooms. Renderer binds the authored `WallRack`\nnode to this rack's id (no procedural visual) and the inspector\nsurfaces AZ-level edd + floor-tower context above / below the\nU stack."
                    },
                    "pending_placement": {
                        "type": "boolean",
                        "description": "True while this rack is a bought-but-not-installed crate in the\nshipping zone. The renderer draws a crate prop (not a rack\nchassis) and the player carries it to a floor slot via\n`InstallRack`."
                    },
                    "held_by": {
                        "type": "integer",
                        "format": "int32",
                        "description": "MULTIPLAYER — which player is carrying this crate. Meaningless unless\n`held` is true. Only meaningful while `pending_placement` is true.\n\nThe renderer skips a held crate's shipping-zone prop and excludes it\nfrom the pile layout, so both clients agree about which crates are on\nthe floor and where. Before this, each client hid only the crate IT was\ncarrying, and the pile is indexed by position in the remaining list —\nso two players carrying crates saw two different piles.\n\nSession state: `load_reported` clears it, so this is always `0` on the\nfirst snapshot after a load.",
                        "minimum": 0
                    },
                    "held": {
                        "type": "boolean",
                        "description": "Is anybody carrying it? Separate from `held_by` because\n`PlayerId::LOCAL` is `0` (see `DeliveryView::held`)."
                    },
                    "resale_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Resale value if the rack is uninstalled and sold at the sell\nzone. Surfaced so the carry HUD can show \"Sell for $X\"."
                    },
                    "binding_key": {
                        "type": ["string", "null"],
                        "description": "Symbolic key tying this rack to an authored scene node of the same\nname. When set, `world_equipment_sync` binds `node.name ==\nbinding_key` and stamps the node's `entity_id` — the multi-rack\ngeneralization of the single-wall-rack first-by-kind matcher.\n`None` for player-bought / procedurally-spawned racks."
                    }
                }
            },
            "RackVisual": {
                "type": "object",
                "required": ["power_pct", "ambient_heat"],
                "properties": {
                    "power_pct": {
                        "type": "number",
                        "format": "float",
                        "description": "0.0..1.0 (used / budget). Amber > 0.80, red > 0.95."
                    },
                    "ambient_heat": {
                        "type": "number",
                        "format": "float",
                        "description": "0.0..1.0 hot-aisle haze hint."
                    }
                }
            },
            "RailDepth": {
                "type": "string",
                "description": "Front-vs-rear position of a side-panel RAIL hole (`SidePanelHole_\n<side>_<depth>_<height>` in the authored rack mesh). Structurally\nidentical to `RoofHoleSide` but kept as its own type: the rail-hole\nfamily and the roof-hole family must never cross-pair (a rear rail\nhole is never the same anchor as a rear roof cutout), and reusing\n`RoofHoleSide` here would make that accidental aliasing possible.",
                "enum": ["Front", "Rear"]
            },
            "RailHeight": {
                "type": "string",
                "description": "Top-vs-bottom position of a side-panel rail hole. `Top` holes thread\na cable rack-to-rack along a row (`RouteAnchor::RailHole` /\n`RackAnchor::RailHole`); `Bottom` holes instead drop into the\nunderfloor duct (a plain `RouteAnchor::Point`) — the two heights are\nnever interchangeable, per the side-rail design note.",
                "enum": ["Top", "Bottom"]
            },
            "RegionView": {
                "type": "object",
                "required": ["id", "name", "area", "az_count", "status"],
                "properties": {
                    "id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "name": { "type": "string" },
                    "area": { "type": "string" },
                    "az_count": { "type": "integer", "format": "int32", "minimum": 0 },
                    "status": { "$ref": "#/components/schemas/AzVisual" },
                    "backbone_load_gbps": {
                        "type": "number",
                        "format": "double",
                        "description": "Smoothed inter-AZ backbone throughput (Gbps) — cross-AZ traffic on the\nprovider's regional fibre (multi-AZ DB replication today). Currently a\nfacility-wide meter surfaced on every region; per-region-pair split is a\nlater refinement. `#[serde(default)]` for old saves."
                    },
                    "backbone_capacity_gbps": {
                        "type": "number",
                        "format": "double",
                        "description": "Inter-AZ backbone capacity (Gbps). Above it the backbone saturates and\ndegrades multi-AZ tenants (`backbone_saturated`)."
                    },
                    "backbone_saturated": {
                        "type": "boolean",
                        "description": "True when cross-AZ load exceeds the backbone capacity."
                    }
                }
            },
            "RelocationGateView": {
                "type": "object",
                "description": "One unlock gate for relocating to the next site, with live progress —\ndrives the relocation-screen checklist. `current`/`target` are pre-\nformatted for display (e.g. \"3,200\" vs \"5,000\", \"0.42\" vs \"0.40\").",
                "required": ["label", "current", "target", "met"],
                "properties": {
                    "label": {
                        "type": "string",
                        "description": "\"Cash\" / \"Reputation\" / \"Active customers\" / \"Total revenue\" / \"Quest\"."
                    },
                    "current": { "type": "string" },
                    "target": { "type": "string" },
                    "met": { "type": "boolean" }
                }
            },
            "RelocationOfferView": {
                "type": "object",
                "description": "The offer to relocate to the next site (the funded-relocation model).\n`available` is false when the scenario has no next progression step\n(final tier / no ladder) — the client hides the relocate affordance.\n`ready` = every gate met. The money math (`funding_grant` + estimated\n`liquidation` = `new_capital`) and the per-tenant `tenants` list power the\nswitch screen; the client does the per-window churn preview client-side.",
                "required": [
                    "available",
                    "next_site_id",
                    "next_site_display_name",
                    "ready",
                    "gates",
                    "funding_grant_dollars",
                    "estimated_liquidation_dollars",
                    "projected_new_capital_dollars",
                    "windows",
                    "tenants"
                ],
                "properties": {
                    "available": { "type": "boolean" },
                    "next_site_id": { "type": "string" },
                    "next_site_display_name": { "type": "string" },
                    "ready": { "type": "boolean" },
                    "gates": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/RelocationGateView" }
                    },
                    "funding_grant_dollars": { "type": "number", "format": "double" },
                    "estimated_liquidation_dollars": { "type": "number", "format": "double" },
                    "projected_new_capital_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "`cash + funding_grant + estimated_liquidation` — the starting capital\non the new floor, shown on the switch screen."
                    },
                    "windows": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/RelocationWindowView" }
                    },
                    "tenants": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/RelocationTenantView" }
                    }
                }
            },
            "RelocationTenantView": {
                "type": "object",
                "description": "One tenant's exposure to a relocation's migration window. The client\nrenders the live \"won't wait\" list + MRR-at-risk by filtering these to\n`patience_hours < selected_window_hours`. Sorted least-patient first.",
                "required": ["customer_id", "name", "tier", "monthly_dollars", "patience_hours"],
                "properties": {
                    "customer_id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "name": { "type": "string" },
                    "tier": {
                        "type": "string",
                        "description": "\"Bronze\" / \"Silver\" / \"Gold\" / \"Platinum\"."
                    },
                    "monthly_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Approx monthly recurring revenue at risk if this tenant leaves."
                    },
                    "patience_hours": {
                        "type": "integer",
                        "format": "int32",
                        "description": "This tenant churns on commit if the chosen window exceeds this.",
                        "minimum": 0
                    }
                }
            },
            "RelocationWindowView": {
                "type": "object",
                "description": "One migration-window choice on the relocation screen. `key` is the\n`MigrationWindow` variant name sent back as `UpgradeFacility { window }`.",
                "required": ["key", "label", "hours"],
                "properties": {
                    "key": {
                        "type": "string",
                        "description": "\"Rushed\" / \"Planned\" / \"Phased\"."
                    },
                    "label": {
                        "type": "string",
                        "description": "Display label (\"Rushed cutover\")."
                    },
                    "hours": { "type": "integer", "format": "int32", "minimum": 0 }
                }
            },
            "RevenueBreakdownView": {
                "type": "object",
                "description": "Lifetime revenue ledger split by service kind, projected from\n`Engine::revenue_breakdown`. Sum of fields equals\n`topbar.total_revenue_dollars`. Drives the dashboard's Services\ndrilldown — each service's row reads its bucket here, tier-locked\nservices that haven't been sold yet render at $0.",
                "required": [
                    "functions_dollars",
                    "vms_dollars",
                    "object_store_dollars",
                    "load_balancer_dollars",
                    "managed_db_dollars",
                    "cdn_dollars",
                    "k8s_dollars",
                    "signup_bonuses_dollars",
                    "reservation_prepay_dollars"
                ],
                "properties": {
                    "functions_dollars": { "type": "number", "format": "double" },
                    "vms_dollars": { "type": "number", "format": "double" },
                    "object_store_dollars": { "type": "number", "format": "double" },
                    "load_balancer_dollars": { "type": "number", "format": "double" },
                    "managed_db_dollars": { "type": "number", "format": "double" },
                    "cdn_dollars": { "type": "number", "format": "double" },
                    "k8s_dollars": { "type": "number", "format": "double" },
                    "platform_fee": {
                        "type": "number",
                        "format": "double",
                        "description": "Flat per-tier platform/support fee, itemised out of `vms_dollars`."
                    },
                    "signup_bonuses_dollars": { "type": "number", "format": "double" },
                    "reservation_prepay_dollars": { "type": "number", "format": "double" }
                }
            },
            "RoofHoleSide": {
                "type": "string",
                "description": "Front-rear axis selector for the rack roof / floor cutouts. A 2-post\nrack typically has a Front-of-rack and Rear-of-rack cable cutout in\nits roof; this enum picks which one a `RackFace::Top` (or future\n`Bottom`) anchor uses.\n\n`RackSide::{Left, Right}` already covers the lateral axis but doesn't\ndisambiguate Front from Rear — without this field the renderer\nalways defaults to Front, so a player who clicks the rear hole sees\ntheir cable snap to the front hole on commit.",
                "enum": ["Front", "Rear"]
            },
            "RouteAnchor": {
                "oneOf": [
                    {
                        "type": "object",
                        "required": ["Port"],
                        "properties": { "Port": { "$ref": "#/components/schemas/PortId" } }
                    },
                    {
                        "type": "object",
                        "required": ["Strip"],
                        "properties": {
                            "Strip": {
                                "type": "object",
                                "required": ["rack_id", "side"],
                                "properties": {
                                    "rack_id": { "$ref": "#/components/schemas/RackId" },
                                    "side": { "$ref": "#/components/schemas/RackSide" },
                                    "u_band": {
                                        "type": ["integer", "null"],
                                        "format": "int32",
                                        "minimum": 0
                                    },
                                    "lane": {
                                        "oneOf": [
                                            { "type": "null" },
                                            {
                                                "$ref": "#/components/schemas/CableLane",
                                                "description": "Engine-assigned from `CableKind::lane()` at lay time — never\nset by the player. `None` on every player-submitted command;\npopulated once the engine groups this into a `RackAnchor`."
                                            }
                                        ]
                                    },
                                    "slot": {
                                        "type": ["integer", "null"],
                                        "format": "int32",
                                        "description": "Engine-assigned next-free index in the `(side, u_band, lane)`\npool at lay time — never set by the player.",
                                        "minimum": 0
                                    },
                                    "role": {
                                        "oneOf": [
                                            { "type": "null" },
                                            {
                                                "$ref": "#/components/schemas/StripAnchorRole",
                                                "description": "HISTORICAL marker/channel expansion role — see\n`StripAnchorRole`. `None` from the player and on every anchor\nthe engine stores today; only saves from the withdrawn\nexpansion window carry a value."
                                            }
                                        ]
                                    }
                                }
                            }
                        }
                    },
                    {
                        "type": "object",
                        "required": ["Edge"],
                        "properties": {
                            "Edge": {
                                "type": "object",
                                "required": ["rack_id", "face", "side"],
                                "properties": {
                                    "rack_id": { "$ref": "#/components/schemas/RackId" },
                                    "face": { "$ref": "#/components/schemas/RackFace" },
                                    "side": { "$ref": "#/components/schemas/RackSide" },
                                    "roof_hole_side": {
                                        "oneOf": [
                                            { "type": "null" },
                                            {
                                                "$ref": "#/components/schemas/RoofHoleSide",
                                                "description": "Front vs Rear roof cutout for `face: Top` anchors. `None`\nlets the renderer pick (Front by convention). Player commands\nfrom a clipped roof-hole pin set this to the marker's\nauthored side."
                                            }
                                        ]
                                    }
                                }
                            }
                        }
                    },
                    {
                        "type": "object",
                        "description": "Tray pass-through. `tray_id` is a `cable_tray::TrayId`. Used\nfor the inter-rack segments of a route — the cable leaves a\nrack via an EdgeCorner, runs through one or more Tray\nanchors, then enters the next rack via another EdgeCorner.",
                        "required": ["Tray"],
                        "properties": {
                            "Tray": {
                                "type": "object",
                                "description": "Tray pass-through. `tray_id` is a `cable_tray::TrayId`. Used\nfor the inter-rack segments of a route — the cable leaves a\nrack via an EdgeCorner, runs through one or more Tray\nanchors, then enters the next rack via another EdgeCorner.",
                                "required": ["tray_id"],
                                "properties": {
                                    "tray_id": {
                                        "type": "integer",
                                        "format": "int32",
                                        "minimum": 0
                                    },
                                    "slot": {
                                        "type": ["integer", "null"],
                                        "format": "int32",
                                        "description": "Engine-assigned bundle slot within the tray at emit time\n(mirrors the manager-strip `slot` — dynamic per snapshot,\nnot persisted on the cable itself). `None` from the player.",
                                        "minimum": 0
                                    }
                                }
                            }
                        }
                    },
                    {
                        "type": "object",
                        "description": "A raw world position the cable threads through, decoupled from\nany engine entity. Lets scene-authored routing (e.g. a tray run\nplaced visually in a .tscn) be a cable waypoint without the\nengine owning a tray entity — the engine just persists the point\nso the cable redraws through it. No tray semantics (no\nneatness/heat accounting); it's purely a geometric waypoint.",
                        "required": ["Point"],
                        "properties": {
                            "Point": {
                                "type": "object",
                                "description": "A raw world position the cable threads through, decoupled from\nany engine entity. Lets scene-authored routing (e.g. a tray run\nplaced visually in a .tscn) be a cable waypoint without the\nengine owning a tray entity — the engine just persists the point\nso the cable redraws through it. No tray semantics (no\nneatness/heat accounting); it's purely a geometric waypoint.",
                                "required": ["pos"],
                                "properties": {
                                    "pos": {
                                        "type": "array",
                                        "items": { "type": "number", "format": "float" }
                                    }
                                }
                            }
                        }
                    },
                    {
                        "type": "object",
                        "description": "A side-panel rail hole. See `RackAnchor::RailHole` — this is the\nflat player-command mirror, carrying `rack_id` like `Strip`/`Edge`\nso the engine can group it into the right per-rack thread.",
                        "required": ["RailHole"],
                        "properties": {
                            "RailHole": {
                                "type": "object",
                                "description": "A side-panel rail hole. See `RackAnchor::RailHole` — this is the\nflat player-command mirror, carrying `rack_id` like `Strip`/`Edge`\nso the engine can group it into the right per-rack thread.",
                                "required": ["rack_id", "side", "depth", "height"],
                                "properties": {
                                    "rack_id": { "$ref": "#/components/schemas/RackId" },
                                    "side": { "$ref": "#/components/schemas/RackSide" },
                                    "depth": { "$ref": "#/components/schemas/RailDepth" },
                                    "height": { "$ref": "#/components/schemas/RailHeight" }
                                }
                            }
                        }
                    }
                ],
                "description": "Player-side route anchor — flat list version that the LayCableThreaded\ncommand uses. Differs from `RackAnchor` in that strip / edge anchors\ncarry `rack_id` so the engine can group consecutive same-rack anchors\ninto per-rack threads. Tray and (future) ceiling-mount segments\nrepresent inter-rack continuations between threads."
            },
            "RuntimeCounters": {
                "type": "object",
                "description": "Runner internals that no snapshot view carries. Read from lock-free\natomics on the apply thread at publish time, never from `Engine`.",
                "required": ["apply_queue_depth", "sessions_connected", "server_mode"],
                "properties": {
                    "apply_queue_depth": {
                        "type": "integer",
                        "format": "int64",
                        "description": "Depth of the `ApplyChannel` queue. The single best indicator that the\napply thread is falling behind, and the number the P0 invariant test\nasserts is unaffected by HTTP load.",
                        "minimum": 0
                    },
                    "sessions_connected": {
                        "type": "integer",
                        "format": "int64",
                        "description": "Attached IPC sessions. `1` in normal single-player.",
                        "minimum": 0
                    },
                    "server_mode": {
                        "type": "boolean",
                        "description": "True when the runner was started with `--server`."
                    }
                }
            },
            "ServiceDemandView": {
                "type": "object",
                "description": "Demand vs fulfilment for one (customer, service) pair. Requested shape\nmirrors the service's demand contract (`VmDemand`/`ManagedDbDemand`/...);\n`vcpu`/`memory_mb`/`accelerator`/`durability` are 0/empty when the\nconcept doesn't apply to that service kind (e.g. `durability` is empty\nfor VMs, `vcpu` is 0 for CDN). `region_id` is 0 when the customer has no\ndata-residency requirement (mirrors the `rack_id: 0` \"not applicable\"\nconvention used elsewhere on this view — not an `Option`, per\n`docs/SCHEMA.md`'s empty-state policy: absence here isn't semantically\ndistinct from zero).",
                "required": [
                    "class_tag",
                    "label",
                    "requested_count",
                    "provisioned_count",
                    "gap",
                    "vcpu",
                    "memory_mb",
                    "accelerator",
                    "durability",
                    "region_id",
                    "state",
                    "reason",
                    "placement"
                ],
                "properties": {
                    "class_tag": {
                        "type": "string",
                        "description": "Short-code service kind — matches `CustomerView.service_kinds`\nentries (\"vm\"/\"fn\"/\"obj\"/\"lb\"/\"db\"/\"cdn\"/\"k8s\")."
                    },
                    "label": {
                        "type": "string",
                        "description": "Plain-voice service label (`ServiceClass::label`)."
                    },
                    "requested_count": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Requested unit count (VM target_count / k8s node_count / 1 for\nsingleton services like Object Store, CDN, LB, managed DB).",
                        "minimum": 0
                    },
                    "provisioned_count": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Live-capacity unit count backing the service right now.",
                        "minimum": 0
                    },
                    "gap": {
                        "type": "integer",
                        "format": "int32",
                        "description": "`requested_count.saturating_sub(provisioned_count)` — the\nat-a-glance shortfall the inspector reads instead of subtracting.",
                        "minimum": 0
                    },
                    "vcpu": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Per-unit vCPU requested. 0 when the service kind has no compute\nshape of its own (Object Store, CDN, LB).",
                        "minimum": 0
                    },
                    "memory_mb": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Per-unit RAM requested, MB. 0 when not applicable.",
                        "minimum": 0
                    },
                    "accelerator": {
                        "type": "string",
                        "description": "Required accelerator name (`Accelerator::name`), empty when none\nrequired."
                    },
                    "durability": {
                        "type": "string",
                        "description": "Durability/replication tier name (Object Store bucket, managed DB\nbackup bucket). Empty when the service carries no durability\ncontract."
                    },
                    "region_id": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Data-residency region requirement. 0 = no requirement.",
                        "minimum": 0
                    },
                    "state": {
                        "type": "string",
                        "description": "`ProbeState::label()` — Healthy/Degraded/Down/Unservable.\n\nPROSE. Display fallback only — branch on `state_tag`."
                    },
                    "reason": {
                        "type": "string",
                        "description": "`ProbeReason::label()` — why `state` isn't `Healthy`/`Ok`. A\nPRE-RENDERED English sentence with no magnitudes; kept for\ncompatibility, but `placement` is what a card should size a remedy\nfrom.\n\nPROSE. Display fallback only — branch on `reason_tag`."
                    },
                    "state_tag": {
                        "type": "string",
                        "description": "`ProbeState::tag()` — the STABLE slug behind `state` (`\"Healthy\"` /\n`\"Degraded\"` / `\"Down\"` / `\"Unservable\"`). This is what a client\nbranches on; it resolves its own word through\n`label-probe-state-<state_tag>`. Rewording or localizing `state` must\nnever change what renders."
                    },
                    "reason_tag": {
                        "type": "string",
                        "description": "`ProbeReason::tag()` — the STABLE slug behind `reason` (`\"Ok\"`,\n`\"FleetCapacityExhausted\"`, ...), same shape and intent as\n`FindingView.kind_tag`. The client tests `reason_tag != \"Ok\"` for\n\"has a problem\" and resolves the sentence from\n`label-probe-<reason_tag>`.\n\nThis field exists because the client used to compare against `reason`\nitself — see the note on that field and `ProbeReason::label`. That\ncomparison is untranslatable AND breaks the day `Ok`'s wording moves."
                    },
                    "placement": {
                        "$ref": "#/components/schemas/PlacementFailureView",
                        "description": "The gate that bound the last placement attempt for this service, with\nnumbers. `code` empty = nothing bound. See `PlacementFailureView`."
                    },
                    "replication_tag": {
                        "type": "string",
                        "description": "**What `replicas_held`/`replicas_target` COUNT**, or empty when this\nservice has no redundancy contract of its own. `\"copies\"` for object\nstorage — durable copies of the bucket's bytes.\n\nTHE key a client branches on. It must never test `class_tag == \"obj\"`\n(which hard-codes today's one class into every consumer) nor read the\nprose beside it: engine ships the fact + a stable tag, client writes the\nsentence. Same split as `reason_tag` / `state_tag` above."
                    },
                    "replicas_held": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Redundant copies the fleet ACTUALLY holds. 0 when `replication_tag` is\nempty.\n\nThis is a SEPARATE pair from `provisioned_count`/`requested_count` on\npurpose. Those are the LIVENESS reading, and for object storage they are\nbinary by design (`Engine::data_plane_counts` carves the class out, with\nthe reasoning, so that a serving-but-under-replicated bucket cannot route\ninto `service_shortfall_reason` and raise a second incident telling the\nplayer to add compute). Overloading them for this one class would give\none field two meanings depending on `class_tag` — the exact defect family\n`reason_tag` exists to kill — and would silently change what every\nexisting consumer of the liveness pair reads.\n\nByte-identical to the numerator `Engine::object_store_replication`\ncomputes and `note_object_store_replication` opens the typed\n`ObjectStoreUnderReplicated` incident with, so the customer page and the\nincident cannot quote different counts for one bucket.",
                        "minimum": 0
                    },
                    "replicas_target": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Redundant copies the CONTRACT calls for — the customer's durability tier\n(`Engine::object_store_effective_rf`). 0 when `replication_tag` is empty.\n`replicas_held < replicas_target` is under-replication.",
                        "minimum": 0
                    }
                }
            },
            "ServiceGateView": {
                "type": "object",
                "description": "A service-class investment gate (P5 in `tasks/consequences.md`) — whether\nthe class is sellable and, if not, what it costs to light up via\n`PlayerCommand::InvestInService`.",
                "required": [
                    "class_tag",
                    "label",
                    "label_eng",
                    "enabled",
                    "unlock_cost_dollars",
                    "affordable"
                ],
                "properties": {
                    "class_tag": {
                        "type": "string",
                        "description": "`ServiceClass` tag — \"vm\" / \"fn\" / \"obj\" / \"db\" / \"cdn\" / \"k8s\"."
                    },
                    "label": {
                        "type": "string",
                        "description": "Plain label — \"Object storage\" / \"Managed database\" / … (jargon off)."
                    },
                    "label_eng": {
                        "type": "string",
                        "description": "Engineer-voice label — \"Object storage (S3-style)\" / … (jargon on).\nRenderer picks `label` vs `label_eng` by jargon mode."
                    },
                    "enabled": {
                        "type": "boolean",
                        "description": "Currently sellable (default-enabled or already invested in)."
                    },
                    "unlock_cost_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "One-time cost to enable when locked. 0 when default-enabled."
                    },
                    "affordable": {
                        "type": "boolean",
                        "description": "Cash on hand covers `unlock_cost_dollars` (only meaningful while\n`enabled` is false)."
                    }
                }
            },
            "ServiceMarginView": {
                "type": "object",
                "description": "One service's slice of a tenant's gross margin — `sim_engine::business::\ncost_attribution::ServiceMargin` on the wire.",
                "required": ["service", "revenue_dollars", "cost_dollars", "margin_dollars"],
                "properties": {
                    "service": {
                        "type": "string",
                        "description": "`ServiceClass` label, matching `CustomerView::service_kinds` vocabulary."
                    },
                    "revenue_dollars": { "type": "number", "format": "double" },
                    "cost_dollars": { "type": "number", "format": "double" },
                    "margin_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "`revenue_dollars - cost_dollars`."
                    }
                }
            },
            "ServicePlaneView": {
                "type": "object",
                "description": "Per-service control-plane + data-plane status, for the ops-console Services\npage (`tasks/service_model.md`). One per host-bound `ServiceClass` when the\nservice plane is enabled. The renderer picks `label` vs `label_eng` by\njargon mode.",
                "required": [
                    "class_tag",
                    "label",
                    "label_eng",
                    "control_plane_host_ids",
                    "replicas_configured",
                    "replicas_floor",
                    "shards",
                    "serving_hosts",
                    "healthy",
                    "degraded",
                    "overloaded",
                    "need_vcpu_milli",
                    "need_mem_mb",
                    "agent_vcpu_milli",
                    "agent_mem_mb"
                ],
                "properties": {
                    "class_tag": { "type": "string" },
                    "label": { "type": "string" },
                    "label_eng": { "type": "string" },
                    "control_plane_host_ids": {
                        "type": "array",
                        "items": { "type": "integer", "format": "int32", "minimum": 0 },
                        "description": "Hosts running a control-plane member (one per HA replica)."
                    },
                    "replicas_configured": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Configured HA replica count (1 = single instance / SPOF).",
                        "minimum": 0
                    },
                    "replicas_floor": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Scenario-imposed minimum replicas — the player can't tune below this\n(≥1). Drives the replica spinner's lower bound.",
                        "minimum": 0
                    },
                    "shards": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Horizontal shard count (P3) — `1` = fits one host; `>1` = the control\nplane outgrew a single host and split across N to relieve load.",
                        "minimum": 0
                    },
                    "serving_hosts": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Hosts running a data-plane agent (= hosts serving this class).",
                        "minimum": 0
                    },
                    "healthy": {
                        "type": "boolean",
                        "description": "Control plane reachable (≥1 member up + reachable)."
                    },
                    "degraded": {
                        "type": "boolean",
                        "description": "Degraded → scheduling frozen (unreachable or overloaded)."
                    },
                    "overloaded": {
                        "type": "boolean",
                        "description": "Outgrew the host it runs on — needs a bigger host or more replicas."
                    },
                    "need_vcpu_milli": {
                        "type": "integer",
                        "format": "int64",
                        "description": "Fleet-scaled control-plane compute need (millivcpu, mem_mb) — the \"your\ncontrol plane has to grow with your fleet\" number.",
                        "minimum": 0
                    },
                    "need_mem_mb": { "type": "integer", "format": "int64", "minimum": 0 },
                    "agent_vcpu_milli": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Per-host data-plane agent cost (config — surfaced so the page can show\nand, later, tune it).",
                        "minimum": 0
                    },
                    "agent_mem_mb": { "type": "integer", "format": "int32", "minimum": 0 }
                }
            },
            "SiemEventView": {
                "type": "object",
                "description": "One flagged line in the SIEM feed. `summary` is the eng-voice event blurb\nthe renderer prints verbatim; `at_ns` is the sim-time it fired; `severity`\nis the event's level (\"Info\"/\"Warn\"/\"Error\"/\"Critical\") for row colouring;\n`source` is the friendly device label that emitted it (or \"fleet\").",
                "required": ["summary", "at_ns", "severity", "source"],
                "properties": {
                    "summary": { "type": "string" },
                    "at_ns": { "type": "integer", "format": "int64", "minimum": 0 },
                    "severity": { "type": "string" },
                    "source": { "type": "string" }
                }
            },
            "SiemFacetView": {
                "type": "object",
                "description": "One filter-chip count over the retained window.",
                "required": ["key", "count"],
                "properties": {
                    "key": { "type": "string" },
                    "count": { "type": "integer", "format": "int32", "minimum": 0 }
                }
            },
            "SiemLogsView": {
                "type": "object",
                "description": "SIEM (audit recorder) log feed for the security ops-lens. Populated only\nwhen at least one `ApplianceKind::Siem` is racked and online; otherwise\n`active` is false and the lens shows its \"no SIEM racked\" empty-state.\n`log_volume` is an ingest RATE in events/s (not a cumulative count) — it\ntracks the live security-event rate the fleet is emitting.",
                "required": [
                    "active",
                    "log_volume",
                    "retention_window",
                    "source_count",
                    "storage_capacity_tb",
                    "storage_used_tb",
                    "daily_burn_tb",
                    "retention_days",
                    "events_recent"
                ],
                "properties": {
                    "active": { "type": "boolean" },
                    "log_volume": {
                        "type": "number",
                        "format": "double",
                        "description": "Ingest rate in events/s (the Godot Security view labels it\n\"Ingest: N events/s\")."
                    },
                    "retention_window": {
                        "type": "string",
                        "description": "Human label off the largest racked SIEM SKU, e.g. \"30 days\"."
                    },
                    "source_count": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Number of live log sources the recorder is ingesting from (reachable\nhosts/switches/gateways/appliances) — the \"Sources: N\" coverage stat.",
                        "minimum": 0
                    },
                    "storage_capacity_tb": {
                        "type": "number",
                        "format": "double",
                        "description": "Usable retention storage of the working recorder, TB."
                    },
                    "storage_used_tb": {
                        "type": "number",
                        "format": "double",
                        "description": "Log history currently retained on the drive array, TB (fills at the\nburn rate, holds at capacity) — drives the ops-panel usage meter."
                    },
                    "daily_burn_tb": {
                        "type": "number",
                        "format": "double",
                        "description": "Drive burn at the current ingest rate, TB/day."
                    },
                    "retention_days": {
                        "type": "number",
                        "format": "double",
                        "description": "Retention window in days = capacity / burn (what `retention_window`\nlabels). Very large / unbounded when the estate is near-silent."
                    },
                    "events_recent": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/SiemEventView" },
                        "description": "The most recent security-relevant events the recorder flagged."
                    },
                    "records": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/SiemRecordView" },
                        "description": "The retained SAMPLE of individual log records, newest first, capped at\n`SIEM_RECORDS_ON_WIRE`.\n\nThis is NOT the whole feed and must never be presented as one:\n`log_volume` says the fleet emits hundreds of events a second, and\nshipping that at 4 Hz is not a thing anyone should build. The console\nsays \"retained window\" in as many words. The counters above stay the\nauthority on rate, burn and retention; these rows are what the window\nactually contains.\n\nAdditive and serde-only. `SiemLogsView` is not capnp-carried, so this\nneeds no `PROTOCOL_VERSION` bump — an older client simply never reads\nthe key, and a newer client against an older runner reads an empty\nlist and shows its collecting state."
                    },
                    "facets_severity": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/SiemFacetView" },
                        "description": "`(facet, count)` over exactly the records in `records`, so the console's\nfilter chips can show real counts without shipping every record twice.\n\nCOUNTED OVER WHAT IS SHIPPED, AND THAT IS THE POINT. These feed filter\nchips above the row list, so a count the list cannot account for reads as\na bug: \"Critical: 30\" over twelve visible rows. This doc used to promise\n\"the WHOLE retained window\" while the engine retained twice what it\nshipped, which made the promise false; the ring is now the shipped\nwindow, so the two readings coincide and cannot come apart.\n\nSeverity facets use the same \"Info\"/\"Warn\"/\"Error\"/\"Critical\" tags the\nrows do; facility facets use the slugs on `SiemRecordView::facility`."
                    },
                    "facets_facility": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/SiemFacetView" }
                    },
                    "sources": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/SiemSourceView" },
                        "description": "Devices the recorder is currently ingesting from, with how many of the\nretained records each one wrote. Drives the console's Sources tab: a\nreachable device contributing NOTHING is the first actionable gap a\nplayer can see."
                    }
                }
            },
            "SiemRecordView": {
                "type": "object",
                "description": "One retained log record, resolved for display.\n\nEverything here is already formatted or already a stable slug: the client\nprints, colours, filters and sorts, and looks nothing up. Prose is the one\nexception and is deliberately NOT baked — `msg_key` + `msg_args` resolve\nclient-side against the player's language and voice, exactly as\n`EventView` does. On this screen the ENGINEER voice is the literal log\nline and the plain voice is the same fact in words a non-engineer can act\non, which is why every one of these keys declares both.",
                "required": [
                    "at_ns",
                    "severity",
                    "facility",
                    "template",
                    "rule_id",
                    "action",
                    "source",
                    "source_kind",
                    "source_id",
                    "customer_id",
                    "customer_name",
                    "src",
                    "dst",
                    "src_internal",
                    "dst_internal",
                    "proto",
                    "count",
                    "msg_key",
                    "msg_args"
                ],
                "properties": {
                    "at_ns": {
                        "type": "integer",
                        "format": "int64",
                        "description": "Sim time the record was written, ns.",
                        "minimum": 0
                    },
                    "severity": {
                        "type": "string",
                        "description": "\"Info\" / \"Warn\" / \"Error\" / \"Critical\" — row tint."
                    },
                    "facility": {
                        "type": "string",
                        "description": "Facility slug: \"auth\" / \"firewall\" / \"ids\" / \"network\" / \"system\" /\n\"audit\" / \"web\" / \"recorder\". Filter chips key off this."
                    },
                    "template": {
                        "type": "string",
                        "description": "Template tag (\"SshFailed\", \"PortScan\") — stable across languages, so\ngrouping and filtering never depend on the rendered sentence."
                    },
                    "rule_id": {
                        "type": "string",
                        "description": "Namespaced rule id, e.g. \"IDS-2013028\". Stable per template."
                    },
                    "action": {
                        "type": "string",
                        "description": "What the emitting device did: \"accept\" / \"deny\" / \"drop\" / \"block\" /\n\"alert\" / \"failure\" / \"success\" / \"info\". Deliberately separate from\nseverity: a blocked exploit is a high-severity line with a reassuring\naction, and losing that distinction is what makes a security console\nunreadable."
                    },
                    "source": {
                        "type": "string",
                        "description": "Player-facing label of the device that emitted it, resolved live, so a\nrename shows on records written before it."
                    },
                    "source_kind": {
                        "type": "string",
                        "description": "\"host\" / \"switch\" / \"gateway\" / \"appliance\" / \"recorder\", plus the id.\nTogether these name a device the player can walk to — the hook that\nmakes a row actionable rather than decorative."
                    },
                    "source_id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "customer_id": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Tenant the record is about, `0` when it is infrastructure-only.",
                        "minimum": 0
                    },
                    "customer_name": {
                        "type": "string",
                        "description": "Tenant name when `customer_id` is set, else empty."
                    },
                    "src": {
                        "type": "string",
                        "description": "`addr:port`, or `addr`, or empty when this side carries no address."
                    },
                    "dst": { "type": "string" },
                    "src_internal": {
                        "type": "boolean",
                        "description": "True when the corresponding address is fleet-internal (RFC1918). Lets\nthe console colour inside and outside apart with no parsing."
                    },
                    "dst_internal": { "type": "boolean" },
                    "proto": {
                        "type": "string",
                        "description": "\"TCP\" / \"UDP\" / \"ICMP\", or empty for records with no network tuple."
                    },
                    "count": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Repeats folded into this one line, the way a recorder aggregates.\nAlways at least 1.",
                        "minimum": 0
                    },
                    "msg_key": {
                        "type": "string",
                        "description": "Localization key for the row's sentence."
                    },
                    "msg_args": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/MsgArgView" },
                        "description": "Facts to substitute into `msg_key`."
                    }
                }
            },
            "SiemSourceView": {
                "type": "object",
                "description": "One device the recorder ingests from.",
                "required": ["kind", "id", "label", "records", "last_seen_ns"],
                "properties": {
                    "kind": { "type": "string" },
                    "id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "label": { "type": "string" },
                    "records": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Records this device contributed to the retained window. `0` is a real\nand interesting answer: a device that is reachable and silent.",
                        "minimum": 0
                    },
                    "last_seen_ns": {
                        "type": "integer",
                        "format": "int64",
                        "description": "Sim time of this device's most recent retained record, `0` if none.",
                        "minimum": 0
                    }
                }
            },
            "SpareCountView": {
                "type": "object",
                "required": ["kind", "count"],
                "properties": {
                    "kind": { "type": "string" },
                    "count": { "type": "integer", "format": "int32", "minimum": 0 }
                }
            },
            "SpareInventoryView": {
                "type": "object",
                "required": ["on_hand", "pending_orders", "catalog"],
                "properties": {
                    "on_hand": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/SpareCountView" },
                        "description": "On-hand counts by kind — \"Psu\" / \"Disk\" / \"Fan\"."
                    },
                    "pending_orders": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/PendingSpareView" },
                        "description": "Outstanding orders in transit — buy-panel renders ETAs."
                    },
                    "catalog": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/SpareOptionView" },
                        "description": "Catalog entries the player can order from."
                    }
                }
            },
            "SpareOptionView": {
                "type": "object",
                "required": [
                    "kind",
                    "kind_label",
                    "sku_name",
                    "spec_label",
                    "effective_capex_dollars",
                    "baseline_lead_hours",
                    "effective_lead_hours"
                ],
                "properties": {
                    "kind": { "type": "string" },
                    "kind_label": {
                        "type": "string",
                        "description": "Human-readable kind label — \"PSU\" / \"disk\" / \"fan\"."
                    },
                    "sku_name": {
                        "type": "string",
                        "description": "SKU display name — \"PSU 800W EPS\", etc. Identifier the\nplayer picks via `BuySpare { sku_name }`."
                    },
                    "spec_label": {
                        "type": "string",
                        "description": "Pre-rendered spec label — variant-specific detail."
                    },
                    "effective_capex_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Effective capex after macro-event multipliers — what the\nplayer actually pays right now."
                    },
                    "baseline_lead_hours": {
                        "type": "integer",
                        "format": "int64",
                        "description": "Baseline lead time from the SKU.",
                        "minimum": 0
                    },
                    "effective_lead_hours": {
                        "type": "integer",
                        "format": "int64",
                        "description": "Effective lead time after macro multipliers — what the player\nwill actually wait if they order now.",
                        "minimum": 0
                    }
                }
            },
            "StaffView": {
                "type": "object",
                "required": ["id", "name", "role", "tier", "monthly_salary_dollars"],
                "properties": {
                    "id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "name": { "type": "string" },
                    "role": { "type": "string" },
                    "tier": { "type": "string" },
                    "monthly_salary_dollars": { "type": "number", "format": "double" },
                    "traits": {
                        "type": "array",
                        "items": { "type": "string" },
                        "description": "Personality trait names (e.g. \"Gifted\", \"Flaky\"). Empty for none."
                    }
                }
            },
            "StrandedVmView": {
                "type": "object",
                "description": "One stranded VM — its host lost reachability and the customer's\nredundancy tier is `Single`, so no auto-failover applies. Migration\npicker consumes these directly.",
                "required": [
                    "vm_id",
                    "customer_id",
                    "customer_name",
                    "host_id",
                    "host_display_name",
                    "az_id",
                    "vcpu",
                    "memory_mb",
                    "block_gb",
                    "block_iops"
                ],
                "properties": {
                    "vm_id": {
                        "type": "integer",
                        "format": "int64",
                        "description": "VM identity (`VmId(u64)` flat). Passed back to the engine via\n`PlayerCommand::MigrateVm { vm_id, target_host_id }`.",
                        "minimum": 0
                    },
                    "customer_id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "customer_name": { "type": "string" },
                    "host_id": {
                        "type": "integer",
                        "format": "int32",
                        "description": "The host the VM is currently pinned to — unreachable but still\npowered up. Renderer displays it as the \"from\" side of the\nmigration picker.",
                        "minimum": 0
                    },
                    "host_display_name": { "type": "string" },
                    "az_id": {
                        "type": "integer",
                        "format": "int32",
                        "description": "AZ the source host lives in. Migration targets must share this\nid (Single tier is AZ-pinned). Surfaced so the renderer can\nscope the target list without re-joining tables.",
                        "minimum": 0
                    },
                    "vcpu": {
                        "type": "integer",
                        "format": "int32",
                        "description": "VM workload footprint — same fields the bin-packer checks\nagainst a candidate host's free capacity. The picker renders\n\"needs 4 vCPU · 8 GB · 20 GB\" so the player can read why a\nhalf-empty host won't accept the VM.",
                        "minimum": 0
                    },
                    "memory_mb": { "type": "integer", "format": "int32", "minimum": 0 },
                    "block_gb": { "type": "integer", "format": "int32", "minimum": 0 },
                    "block_iops": { "type": "integer", "format": "int32", "minimum": 0 }
                }
            },
            "StripAnchorRole": {
                "type": "string",
                "description": "Structural role of one `StripBand`/`Strip` anchor: which part of a\nmanager-strip run it represents — `Marker` for a finger-gap entry/exit,\n`Channel` for an interior manager-runway position.\n\n**HISTORICAL. Nothing sets this any more.** Engine-side marker/channel\nexpansion shipped briefly, PERSISTED anchors the player never clicked, and\nwas withdrawn; `Engine::reconcile_route_anchor_expansion` now COLLAPSES any\nsuch run back to the single role-less anchor on load, and the function that\nproduced them (`cable_routing::expand_strip_chains_rack`) is gone. The\nengine stores what the player clicked; deciding how a cable physically\nthreads a manager between two clicks is DRAW-TIME geometry the renderer\nowns (`cable_path.gd`) — see `docs/CABLE_INVARIANTS.md`.\n\nThe variants stay for save-compat: a role-bearing anchor can still arrive\nfrom a save written in that window, and it must round-trip through the wire\ncodec unchanged for the collapse pass to recognise and undo it. `None` is\nthe value every live anchor carries.",
                "enum": ["Marker", "Channel"]
            },
            "SubnetView": {
                "type": "object",
                "description": "An L2 broadcast domain (subnet) — the finer isolation grain BELOW the VRF: a\nVRF is the routed L3 boundary, a subnet is one L2 segment within it, keyed\none-per-`ServiceClass` (so a tenant running VMs + Functions + K8s + a managed\nDB has four sibling subnets, each independently dedicatable). Small top-level\nlist on `WorldSnapshot` (msgpack keyframe-only, exactly like `segments` /\n`host_pools`; subnets change rarely, so they never ride the per-tick capnp\ndelta). Empty until the network-isolation model mints any subnet.",
                "required": ["id", "l2vni", "vrf_id", "service"],
                "properties": {
                    "id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "l2vni": {
                        "type": "integer",
                        "format": "int32",
                        "description": "L2 VXLAN Network Identifier for this subnet's broadcast domain (minted\nfrom a range disjoint from the VRF L3 VNIs).",
                        "minimum": 0
                    },
                    "vrf_id": {
                        "type": "integer",
                        "format": "int32",
                        "description": "The VRF this subnet routes within. `0` is the Default shared VRF.",
                        "minimum": 0
                    },
                    "service": {
                        "type": "string",
                        "description": "The resident service class this subnet carries — the isolation grain, as\na `ServiceClass` tag (\"vm\"/\"fn\"/\"obj\"/\"db\"/\"cdn\"/\"k8s\"/\"lb\")."
                    },
                    "member_count": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Count of resident UNITS currently homed to this subnet — one per placed\nunit whose `Engine::unit_subnet` is this one, whatever its product.\n\nGeneric since 2026-08-10 (PROTOCOL 46). This mixed three grains before:\none per VM allocation, one per database CUSTOMER and one per cluster\nCUSTOMER, so a 5-node cluster read 1, a database's copies were counted\ntwice, and a Functions subnet read 0 while its containers ran in it.",
                        "minimum": 0
                    },
                    "dedicated_host_ids": {
                        "type": "array",
                        "items": { "type": "integer", "format": "int32", "minimum": 0 },
                        "description": "Hosts physically dedicated to this subnet — those whose `admit` filter is\nexactly this subnet's `(vrf, class)` (`{scope: L3vni(vrf), service:\nService(class)}`). Empty when the subnet is logical-only."
                    },
                    "carrier_host_ids": {
                        "type": "array",
                        "items": { "type": "integer", "format": "int32", "minimum": 0 },
                        "description": "Hosts physically CARRYING this subnet's members — the distinct host ids\nrunning the resident UNITS homed to this subnet, every product included\n(function containers were missing until 2026-08-10). The L2VNI\n(service-subnet) analog of\n`NetworkSegmentView.carrier_host_ids` (the VRF-grain equivalent); lets\nthe netview overlay colour hosts by which L2VNI they carry, not just\nwhich VRF. Sorted ascending, deduped. Empty until members are homed."
                    },
                    "routes_via": {
                        "type": "string",
                        "description": "How this subnet's cross-subnet (east-west) traffic is ROUTED out of its\nL2VNI — the first-hop router serving its carrier hosts (`Engine::\nfirst_hop_router` on the lowest-id carrier). One of:\n  * `\"switch\"` — a distributed anycast L3 leaf routes it LOCALLY (resilient:\n    survives an edge-gateway death);\n  * `\"gateway\"` — CENTRALIZED at the edge gateway (a pure-L2 fabric hairpins\n    cross-subnet traffic up to it, so a non-HA gateway is the SPOF for the\n    tenant's tier-to-tier traffic);\n  * `\"\"` — no live L3 router on the path (cross-subnet traffic is\n    PARTITIONED), or a logical-only subnet with no carrier hosts yet.\nLets the segments/isolation lens show a tenant WHERE their tiers route and\nflag the pure-L2 cross-subnet SPOF proactively, not just via the\n`InterSubnetPartition` incident once it breaks."
                    }
                }
            },
            "SwitchTopoRole": {
                "type": "string",
                "description": "Topology-derived classification for a switch's position in the fabric.\nDistinct from the catalog SKU's `sim_core::switch::SwitchRole`, which\ndescribes the product class. This one comes from actual wiring at\nsnapshot time:\n\n- `Edge` — switch with at least one host-NIC cable. The leaf layer.\n- `Core` — no hosts, but cabled to a gateway / patch panel. The\n  border-handoff switch.\n- `Spine` — no hosts, no gateway; cabled only switch-to-switch.\n  Intra-fabric backbone.\n- `Agg` — fallback / intermediate (no hosts, no gateway, no peers).\n\nSerialised as snake_case so the GDScript renderer reads\n`\"edge\"`/`\"core\"`/`\"spine\"`/`\"agg\"` directly.",
                "enum": ["edge", "core", "agg", "spine"]
            },
            "SwitchView": {
                "type": "object",
                "required": [
                    "id",
                    "rack_id",
                    "az_id",
                    "start_u",
                    "u_size",
                    "sku_name",
                    "port_count",
                    "uplink_capacity_gbps",
                    "egress_gbps",
                    "saturated",
                    "indicator"
                ],
                "properties": {
                    "id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "display_name": {
                        "type": "string",
                        "description": "Player-facing identity like `sw-01`. See `HostView::display_name`."
                    },
                    "display_name_plain": {
                        "type": "string",
                        "description": "Plain-mode counterpart: `switch 1` vs `sw-01`."
                    },
                    "role": {
                        "$ref": "#/components/schemas/SwitchTopoRole",
                        "description": "Topology-derived role: where this switch sits in the fabric.\nComputed each snapshot from cable wiring (NOT the SKU's static\n`sim_core::switch::SwitchRole`, which describes the catalog\nclass — `Desktop`/`Access`/`ToR`/`Leaf`/`Aggregation`/`Spine`).\nNetView uses this to pick \"edge switch\" / \"core switch\" sub-labels."
                    },
                    "rack_id": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Rack id when rack-mounted; 0 for wall-shelf switches.",
                        "minimum": 0
                    },
                    "az_id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "start_u": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Starting U slot when rack-mounted; 0 when on a wall shelf.",
                        "minimum": 0
                    },
                    "u_size": { "type": "integer", "format": "int32", "minimum": 0 },
                    "on_wall": {
                        "type": "boolean",
                        "description": "True for wall-shelf switches (garage tier — no rack required)."
                    },
                    "on_shelf": {
                        "type": "boolean",
                        "description": "True when this is a Desktop-class switch sitting on a 1U cantilever\nshelf inside a rack. The renderer spawns a `shelf_1u_cantilever.glb`\nin the same U slot and centers the switch on the tray; engine-side\nthe implicit shelf occupies the slot via `effective_u_size()`.\nMutually exclusive with `on_wall` (a switch can't be both)."
                    },
                    "sku_name": { "type": "string" },
                    "port_count": { "type": "integer", "format": "int32", "minimum": 0 },
                    "port_capacity_gbps": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Per-port line speed (Gbps). Used by the renderer to color-code port LEDs.",
                        "minimum": 0
                    },
                    "uplink_capacity_gbps": {
                        "type": "integer",
                        "format": "int32",
                        "description": "NO CLIENT READER (audited 2026-08-09) — every `uplink_capacity_gbps`\nhit in `godot/` resolves to a DIFFERENT field\n(`EddView::total_uplink_capacity_gbps`,\n`CapacityView::free_uplink_capacity_gbps`, the fleet history rings).\nLeft in place because it holds capnp ordinal @13 on `SwitchViewCp`\nand removing a capnp field is an ordinal change, which decodes\nsilently WRONG if it drifts — a few bytes per switch per delta is\nnot worth that. Retire it with the next deliberate capnp revision,\nnot opportunistically.",
                        "minimum": 0
                    },
                    "fabric_capacity_gbps": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Switching fabric (backplane) capacity. The aggregate cross-port\nthroughput ceiling — independent of trunk uplink. Cheap T1\nswitches oversubscribe heavily; datacenter SKUs are full\nnon-blocking.",
                        "minimum": 0
                    },
                    "egress_gbps": {
                        "type": "number",
                        "format": "double",
                        "description": "Smoothed directional egress in Gbps (data leaving the switch\noutbound). Pairs with `ingress_gbps`. The full-duplex fabric\nload (the saturation metric) is `fabric_load_gbps` below."
                    },
                    "ingress_gbps": {
                        "type": "number",
                        "format": "double",
                        "description": "Smoothed directional ingress in Gbps — bytes flowing INTO the\nswitch from upstream/peers. Mirrors `egress_gbps`."
                    },
                    "fabric_load_gbps": {
                        "type": "number",
                        "format": "double",
                        "description": "Smoothed full-duplex fabric load (= egress + ingress,\nindependently smoothed). Compared against `fabric_capacity_gbps`\n(capacity) — when this exceeds capacity the fabric ASIC drops\npackets. The saturation metric, not the directional metric.\nSee `docs/SCHEMA.md` § \"Naming conventions — Throughput\"."
                    },
                    "saturated": { "type": "boolean" },
                    "fabric_saturated": {
                        "type": "boolean",
                        "description": "`true` when fabric load exceeds `fabric_capacity_gbps`. Drives drops\nindependent of trunk saturation."
                    },
                    "broadcast_storm": {
                        "type": "boolean",
                        "description": "`true` when this switch is caught in a broadcast storm — a loop\nthrough an unmanaged switch with no STP to break it. The switch\nforwards nothing until a cable is pulled. Drives a critical console\nstate, distinct from saturation. (Switch CLASS is not duplicated\nhere — resolve it via `sku_name` → `SwitchSkuView.switch_class`.)"
                    },
                    "indicator": {
                        "$ref": "#/components/schemas/IndicatorLed",
                        "description": "MAC flapping (2+ unbonded cables switch↔gateway) surfaces via the amber\n`indicator` below + the MacFlapDetected event / MacFlapping incident — no\ndedicated wire bool, so the capnp SwitchView contract is unchanged."
                    },
                    "ports": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/PortView" },
                        "description": "Per-physical-port readouts. One entry per `port_count`, sourced\nfrom `engine.ports` filtered by owner. Populated for the\nworkbench inspector (T1 has no rack but still wants per-port\nLEDs / speed badges / activity blink). Rack-resident switches\nare also surfaced here for renderers that don't open a full\n`RackInspectSnapshot`."
                    },
                    "inv_status": {
                        "type": "string",
                        "description": "Lifecycle status — see `HostView::inv_status`."
                    },
                    "age_hours": { "type": "integer", "format": "int32", "minimum": 0 },
                    "power_on_hours": { "type": "integer", "format": "int32", "minimum": 0 },
                    "condition_pct": { "type": "integer", "format": "int32", "minimum": 0 },
                    "mtbf_hours": { "type": "integer", "format": "int32", "minimum": 0 },
                    "resale_dollars": { "type": "number", "format": "double" },
                    "monthly_opex_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Live projected monthly opex ($/mo) — running fee + chassis power +\nthis switch's seated-optic draw. Mirrors the host console's\n`monthly_opex_dollars` read."
                    },
                    "powered": {
                        "type": "boolean",
                        "description": "At least one AC cord seated (or none required). False = dark\nuntil the player plugs it in. Mirrors the engine's `powered`."
                    },
                    "power_feeds": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Connected AC cord count. >= 2 = PSU-failure resilience at\n`redundant_psu_overhead_w` extra draw per extra cord.",
                        "minimum": 0
                    }
                }
            },
            "ThreatClassView": {
                "type": "object",
                "description": "Per-class resolution: how much came in, how much the fleet mitigated, what\nleaked. `class` is the slug (\"volumetric\"/\"protocol\"/\"application\"/\n\"intrusion\").\n\nALL FOUR classes are carried, Application included. L7 used to be filtered\nout here on the grounds that no on-prem box defends it — but its residual\ndrives real tenant impact, so hiding the row left the player with damage\nand no readout. It ships with `edge_defensible: false` instead: visible\npressure, with the explicit \"no edge defender exists\" state that explains\nwhy no purchase on the shop shelf changes the number.",
                "required": [
                    "class",
                    "label",
                    "label_eng",
                    "blurb",
                    "incoming",
                    "mitigated",
                    "residual",
                    "unit"
                ],
                "properties": {
                    "class": { "type": "string" },
                    "label": {
                        "type": "string",
                        "description": "Two-voice display labels (plain | engineer), picked by jargon mode —\nsame pattern as `ServiceGateView`. The renderer no longer hardcodes\n\"Volumetric\"/\"Flood\"/etc.; it reads these."
                    },
                    "label_eng": { "type": "string" },
                    "blurb": {
                        "type": "string",
                        "description": "One-line \"what this class does\" caption."
                    },
                    "incoming": { "type": "number", "format": "double" },
                    "mitigated": { "type": "number", "format": "double" },
                    "residual": { "type": "number", "format": "double" },
                    "mitigated_at_gateway": {
                        "type": "number",
                        "format": "double",
                        "description": "Attribution of `mitigated` by defender type — how much was blocked at a\ngateway's built-in firewall vs at a dedicated security appliance. Lets\nthe ops-lens show \"blocked at gateway X / at appliances Y / leaked Z\"\nwithout per-box drill-down. Sums to `mitigated`."
                    },
                    "mitigated_at_appliance": { "type": "number", "format": "double" },
                    "edge_defensible": {
                        "type": "boolean",
                        "description": "Can ANY on-prem edge box defend this class (`AttackClass::defended_by`\nis non-empty)? True for Volumetric / Protocol / Intrusion — a scrubber,\nfirewall or IPS exists to buy. False for Application: L7 is the\ncustomer-purchased WAF service, and by design nothing at our edge\ntouches it, so `mitigated` is structurally 0 and `residual` is the full\nincoming pressure. The renderer must say so plainly rather than let the\nrow read as an unbought upgrade."
                    },
                    "unit": { "type": "string" }
                }
            },
            "ThreatMitigationView": {
                "type": "object",
                "description": "One class of threat **this specific box** sees — the per-device\n(positional) view, from the engine cascade. Distinct from the fleet-wide\n`ThreatClassView`: `reaching` is what arrived at THIS box after upstream\ndefenders, not the fleet total, and `residual` is what this box passes on.\n\nOne row per class the box is IN THE PATH OF, defended or not. Rows are NOT\nfiltered on `capacity > 0`: a box that forwards a class it cannot (or\ncurrently does not) defend still reports `reaching: N, mitigated: 0,\ncapacity: 0`. That is precisely the readout the player needs when they\nswitch a firewall off or run a class nothing on-prem defends — the old\nfilter blanked the console at the one moment exposure mattered. Use\n`defense_capable` to tell the two zero-capacity cases apart.\n\nShared by `ApplianceView::threats` AND `GatewayView::threats` — both\nsecurity consoles render the SAME per-class mitigation row off this type,\nso the gateway and appliance present threat detection/mitigation\nidentically.",
                "required": [
                    "class",
                    "label",
                    "label_eng",
                    "reaching",
                    "mitigated",
                    "residual",
                    "capacity",
                    "unit"
                ],
                "properties": {
                    "class": { "type": "string" },
                    "label": { "type": "string" },
                    "label_eng": { "type": "string" },
                    "reaching": {
                        "type": "number",
                        "format": "double",
                        "description": "Threat (in the class unit) arriving at this box."
                    },
                    "mitigated": {
                        "type": "number",
                        "format": "double",
                        "description": "Absorbed here (`min(reaching, capacity)`)."
                    },
                    "residual": {
                        "type": "number",
                        "format": "double",
                        "description": "Passed on (what this box couldn't catch)."
                    },
                    "capacity": {
                        "type": "number",
                        "format": "double",
                        "description": "This box's own LIVE capacity for the class. `0` means nothing is being\nstopped here right now, whatever the reason."
                    },
                    "defense_capable": {
                        "type": "boolean",
                        "description": "Is this box BUILT to defend the class at all — a gateway's built-in\nL3/L4 firewall for Protocol/Volumetric/Intrusion (plus any fitted\nadd-on's class), or an appliance SKU's base function / fittable\nlicenses. Independent of whether that defence is currently switched on.\n\nThe renderer needs it to word a `capacity == 0` row honestly:\n- `true` → \"defended, but switched off / unlicensed / this box is down\"\n  (actionable: turn it back on);\n- `false` → \"nothing here defends this class, it transits undefended\"\n  (actionable: buy the box that does, or nothing on-prem can help —\n  the L7/Application case)."
                    },
                    "unit": { "type": "string" }
                }
            },
            "TopBarMetrics": {
                "type": "object",
                "required": [
                    "cash_dollars",
                    "total_revenue_dollars",
                    "total_recurring_revenue_dollars",
                    "total_one_time_revenue_dollars",
                    "total_cost_dollars",
                    "last_hour_revenue_dollars",
                    "reputation",
                    "current_site_id",
                    "current_site_name",
                    "customer_count",
                    "active_customer_count",
                    "staff_count",
                    "heat_load_w",
                    "cooling_capacity_w",
                    "public_ips_used",
                    "public_ips_max",
                    "uplink_egress_gbps",
                    "uplink_capacity_total_gbps",
                    "active_ddos_mbps"
                ],
                "properties": {
                    "cash_dollars": { "type": "number", "format": "double" },
                    "total_revenue_dollars": { "type": "number", "format": "double" },
                    "total_recurring_revenue_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Lifetime recurring (opex) revenue — hourly customer billing\nacross all services. The dashboard surfaces this alongside\n`total_one_time_revenue_dollars` so the player can see what\nshare of lifetime revenue was earned vs. paid up-front."
                    },
                    "total_one_time_revenue_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "Lifetime one-time (capex) revenue — signup bonuses + reservation\nprepay credits. Excluded from the monthly-revenue projection."
                    },
                    "total_cost_dollars": {
                        "type": "number",
                        "format": "double",
                        "description": "`Engine::total_cost` — lifetime spend on what the business CONSUMED.\n\n**This is OPERATING spend and EXCLUDES hardware purchases.** Since the\nasset ledger landed, a purchase converts cash into an asset rather than\nbooking a cost, so buying $25k of hosts moves this figure by nothing;\nit shows up in `cost_breakdown.total_capex_dollars` and reaches the P&L\ngradually as depreciation. A UI labelling this \"total cost\" is lying —\nlabel it operating cost, and put capital beside it."
                    },
                    "last_hour_revenue_dollars": { "type": "number", "format": "double" },
                    "reputation": { "type": "number", "format": "double" },
                    "current_site_id": {
                        "type": "string",
                        "description": "Stable id of the site the player is currently at (e.g. \"garage\",\n\"small_colo\"). Display-name lookup goes via the catalog."
                    },
                    "current_site_name": {
                        "type": "string",
                        "description": "Display name for the current site (e.g. \"Garage\", \"Datacenter\")."
                    },
                    "current_progression_step": {
                        "type": "integer",
                        "format": "int32",
                        "description": "0-indexed step rank of the current site in the scenario's\nprogression list. 0 if scenario has no progression. Drives UI\nchrome (tutorial vs post-tutorial) and progression-step quest\ngating on the renderer side.",
                        "minimum": 0
                    },
                    "customer_count": { "type": "integer", "format": "int32", "minimum": 0 },
                    "active_customer_count": { "type": "integer", "format": "int32", "minimum": 0 },
                    "staff_count": { "type": "integer", "format": "int32", "minimum": 0 },
                    "staff_unlocked": {
                        "type": "boolean",
                        "description": "Whether staff hiring is available here (gated by site tier / scenario).\nThe client shows/hides the ops-console Staff tab off this — garage/\nbasement stay solo. See `Engine::staff_unlocked`."
                    },
                    "heat_load_w": { "type": "integer", "format": "int32", "minimum": 0 },
                    "cooling_capacity_w": { "type": "integer", "format": "int32", "minimum": 0 },
                    "public_ips_used": { "type": "integer", "format": "int32", "minimum": 0 },
                    "public_ips_max": { "type": "integer", "format": "int32", "minimum": 0 },
                    "uplink_egress_gbps": { "type": "number", "format": "double" },
                    "uplink_capacity_total_gbps": { "type": "number", "format": "double" },
                    "active_ddos_mbps": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Live volumetric attack arriving on the wire, in **MEGABITS/s**.\n\nWas `active_ddos_gbps: u32` (whole Gbps) until PROTOCOL 53. Once the\nthreat cascade gained a line-speed ceiling, a garage's entire threat\nsurface became at most its 1 Gbps circuit, and every attack it can\nphysically receive rounded to **0** — the gauge read \"no attack\" during\nan attack. Whole Gbps has no resolution at the tier where most players\nmeet the mechanic. Renamed rather than rescaled in place so no reader\ncan keep working while silently meaning something 1000x different.",
                        "minimum": 0
                    },
                    "facility_egress_drop_p": {
                        "type": "number",
                        "format": "double",
                        "description": "Facility WAN egress drop probability from the LAST uplink pass — the\nfraction of offered egress that can't get out the effective pipe,\nclamped to `saturation_drop_cap`. 0 when not saturated. Mirrors\n`Engine::facility_egress_drop_p` (wire-only for now — no console\nbinds it yet, but a player watching failed requests climb with a\nhealthy-looking topbar deserves the number that explains it)."
                    },
                    "backbone_drop_p": {
                        "type": "number",
                        "format": "double",
                        "description": "Inter-AZ backbone drop probability from the LAST backbone pass — the\nfraction of cross-AZ traffic the regional fibre can't carry, clamped\nto `saturation_drop_cap`. 0 when not saturated. Mirrors\n`Engine::backbone_drop_p`."
                    },
                    "appliance_fabric_drop_p": {
                        "type": "number",
                        "format": "double",
                        "description": "In-path appliance forwarding-drop probability from the LAST appliance\nfabric pass — the worst (max) excess-over-effective-capacity fraction\nacross every saturated IN-PATH inspector that actually forwards,\nclamped to `saturation_drop_cap`. 0 when none saturated. Mirrors\n`Engine::appliance_fabric_drop_p`."
                    },
                    "wan_effective_gbps": {
                        "type": "number",
                        "format": "double",
                        "description": "THE facility WAN ceiling the sim actually enforces (Gbps) — mirrors\n`Engine::effective_wan_gbps`, the per-zone `min(lines, gateway fabric,\ninline edge box)` summed over zones. Sustained egress above this is what\ndrives `facility_egress_drop_p` above.\n\n`uplink_capacity_total_gbps` is the NAMEPLATE sum and is routinely far\nlarger: two 100 G circuits behind two non-BGP routers bill 200 and\ndeliver whatever the single largest router forwards. Showing only the\nnameplate is what made that read as a bug rather than a rule (UG-241)."
                    },
                    "wan_lines_gbps": {
                        "type": "number",
                        "format": "double",
                        "description": "The three terms `wan_effective_gbps` is a min over, so the console can\nshow the comparison rather than an unexplained number.\n\n`wan_lines_gbps` is Σ per-line effective circuit capacity (post\npairing-law — a dark line reads 0). The other two are `0.0` when NOT\nMODELLED and therefore not gating; `wan_binding` disambiguates, since it\nnever names a term that is absent rather than empty."
                    },
                    "wan_gateway_fabric_gbps": {
                        "type": "number",
                        "format": "double",
                        "description": "Σ router forwarding capacity after every fitted security add-on's\nthrottle, under the BGP law: advertising edges aggregate, and without\nBGP a SINGLE router is the lone active edge (two non-BGP routers cannot\nshare a prefix). See `Engine::bgp_edge_fabric_gbps` — the rule is\ndeliberate, and naming it is this field's whole purpose."
                    },
                    "wan_inline_appliance_gbps": {
                        "type": "number",
                        "format": "double",
                        "description": "Tightest racked in-path edge firewall, `min(ASIC, Σ bonded WAN ports)`."
                    },
                    "wan_binding": {
                        "type": "string",
                        "description": "WHICH term binds: `\"Lines\"` / `\"GatewayFabric\"` / `\"InlineAppliance\"`,\nor `\"\"` when nothing is deliverable at all. A bare ceiling explains\nnothing — this is what turns \"8 of 200 Gbps\" into an actionable\nsentence. Where zones disagree it names the term wasting the most line\ncapacity, which is the one worth acting on."
                    }
                }
            },
            "WorkOrderStepView": {
                "type": "object",
                "required": [
                    "kind",
                    "detail",
                    "started_at_ns",
                    "completed_at_ns",
                    "estimated_duration_ns",
                    "actual_duration_ns"
                ],
                "properties": {
                    "kind": {
                        "type": "string",
                        "description": "\"WalkTo\" / \"OpenRack\" / \"PullHost\" / etc."
                    },
                    "detail": {
                        "type": "string",
                        "description": "Free-form details for steps that carry a payload (rack id,\ncomponent kind, location label)."
                    },
                    "started_at_ns": { "type": "integer", "format": "int64", "minimum": 0 },
                    "completed_at_ns": { "type": "integer", "format": "int64", "minimum": 0 },
                    "estimated_duration_ns": { "type": "integer", "format": "int64", "minimum": 0 },
                    "actual_duration_ns": { "type": "integer", "format": "int64", "minimum": 0 }
                }
            },
            "WorkOrderView": {
                "type": "object",
                "description": "Renderer-friendly mirror of `sim_core::work::WorkOrder`. Drives the\nwork-order panel UI + per-actor\"currently doing\"badge.",
                "required": [
                    "id",
                    "kind",
                    "kind_label",
                    "actor",
                    "actor_staff_id",
                    "started_at_ns",
                    "last_progress_at_ns",
                    "resolved_at_ns",
                    "current_step",
                    "steps",
                    "blocking",
                    "incident_id"
                ],
                "properties": {
                    "id": { "type": "integer", "format": "int64", "minimum": 0 },
                    "kind": {
                        "type": "string",
                        "description": "Variant tag for `WorkOrderKind` — \"SwapPsu\", \"LayCable\", etc."
                    },
                    "kind_label": {
                        "type": "string",
                        "description": "Short human label — `WorkOrderKind::label()`."
                    },
                    "actor": {
                        "type": "string",
                        "description": "\"Player\" / \"Staff\" / \"Unassigned\" + optional staff id."
                    },
                    "actor_staff_id": { "type": "integer", "format": "int32", "minimum": 0 },
                    "started_at_ns": { "type": "integer", "format": "int64", "minimum": 0 },
                    "last_progress_at_ns": { "type": "integer", "format": "int64", "minimum": 0 },
                    "resolved_at_ns": {
                        "type": "integer",
                        "format": "int64",
                        "description": "0 while open; set on resolution.",
                        "minimum": 0
                    },
                    "current_step": {
                        "type": "integer",
                        "format": "int32",
                        "description": "Index of the step currently in flight. Past steps in `steps`\nhave `completed_at_ns != 0`; this and following ones don't.",
                        "minimum": 0
                    },
                    "steps": {
                        "type": "array",
                        "items": { "$ref": "#/components/schemas/WorkOrderStepView" }
                    },
                    "blocking": {
                        "type": "string",
                        "description": "Reason the WO is paused mid-flight, if any. Empty otherwise."
                    },
                    "incident_id": {
                        "type": "integer",
                        "format": "int64",
                        "description": "Linked incident, or 0 if the WO is standalone.",
                        "minimum": 0
                    }
                }
            },
            "WorldHeader": {
                "type": "object",
                "description": "`GET /api/v1/world` body. Composed of wire types plus the two runner\nstructs above; deliberately NOT a restatement of the topbar's fields.",
                "required": ["meta", "runtime", "status", "topbar"],
                "properties": {
                    "meta": { "$ref": "#/components/schemas/ApiMeta" },
                    "runtime": { "$ref": "#/components/schemas/RuntimeCounters" },
                    "status": { "$ref": "#/components/schemas/GameStatusView" },
                    "topbar": { "$ref": "#/components/schemas/TopBarMetrics" }
                }
            }
        }
    },
    "tags": [
        { "name": "ops", "description": "Liveness, scraping, incidents and work orders" },
        { "name": "world", "description": "World header: sim time, run status, economy topbar" },
        { "name": "sites", "description": "Regions, availability zones, racks" },
        { "name": "compute", "description": "Hosts and host pools" },
        {
            "name": "network",
            "description": "The four-corner network surface (switch / gateway / appliance / host) plus cables, patch panels, segments and subnets"
        },
        { "name": "business", "description": "Customers, prospects, staff, quests" },
        { "name": "services", "description": "Service planes" }
    ]
}
