Skip to content

Commit fd27dff

Browse files
health: /healthz reports live state, one payload on every backend
Production verification could not answer "is the geo denylist actually in force?" from outside. The control board and the public policy showcase both showed seven countries denied while every request was served 200, and the only surfaces that could have settled it — the boot log and the token-gated operator panel — need credentials a verification pass does not have. So the probe now carries what it takes to tell. Adds two fields: app — WHICH satellite answered. `build` says which commit; on a fleet where several hosts share a template and a hostname can be repointed between services, those are different questions. geo — {configured, denied}. Counts and flags only, never the country codes: they are already public on /showcase/policy-panel, but a health endpoint is not where anyone should learn policy. FIXES TWO INHERITED DEFECTS found while adding them. 1. THE PAYLOAD WAS A SNAPSHOT. register_health_route computed it ONCE at registration and the route closed over that dict. Harmless while every field was static (ok/backend/dash_version/build never change for a running process) and silently wrong the moment one is not — the route is registered at run.py:566 and configure_geo runs ~150 lines later, so the first version of this diagnostic reported the guardrail UNCONFIGURED on a host where it is configured. The diagnostic lying in exactly the situation it exists for. Now built per request. 2. FASTAPI HAD ITS OWN PAYLOAD. lib/asgi_routes.py constructed HealthResponse independently and never called health_payload, so a FastAPI deployment silently lacked `build` — and cd.yml's build-match wait polls /healthz for precisely that field. It would have fallen into the "predates the build field" warning path forever, verifying whichever release happened to be serving rather than the one it shipped. That is the muicharts defect the wait was written to prevent, reintroduced by backend. Both backends now render from one function; HealthResponse only types it for Swagger. Three tests pin it: the app identity, that a live configure_geo change is reflected (the snapshot regression), and that country codes never appear in the body. 600 passed / 1 skipped (flask); 597 / 4 (fastapi); flake8 clean.
1 parent 519a777 commit fd27dff

3 files changed

Lines changed: 130 additions & 8 deletions

File tree

lib/asgi_routes.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,23 @@ class PageListResponse(BaseModel):
5050

5151

5252
class HealthResponse(BaseModel):
53+
"""The probe contract, identical on every backend.
54+
55+
`lib.health.health_payload` is the single source — this model only types
56+
it for Swagger. It used to be built independently here, which meant a
57+
FastAPI deployment silently lacked `build`: CD's build-match wait polls
58+
/healthz for exactly that field, so it would have fallen into the
59+
"predates the build field" warning path forever, verifying whichever
60+
release happened to be serving.
61+
"""
62+
5363
ok: bool = True
5464
backend: str
5565
dash_version: str
66+
# Optional because they are environment-dependent, not backend-dependent.
67+
build: Optional[str] = None
68+
app: Optional[str] = None
69+
geo: Optional[dict] = None
5670

5771

5872
# ---------------------------------------------------------------------------
@@ -102,11 +116,12 @@ def build_health_router() -> APIRouter:
102116

103117
@router.get("/healthz", response_model=HealthResponse, summary="Liveness probe")
104118
def healthz() -> HealthResponse:
105-
return HealthResponse(
106-
ok=True,
107-
backend="fastapi",
108-
dash_version=dash.__version__,
109-
)
119+
# One payload builder for all three backends — see HealthResponse.
120+
# Built per request: `geo` reports live state, and this route is
121+
# mounted long before configure_geo runs.
122+
from lib.health import health_payload
123+
124+
return HealthResponse(**health_payload("fastapi"))
110125

111126
return router
112127

lib/health.py

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,34 @@ def health_payload(backend: str) -> dict:
3232
build = os.environ.get("RENDER_GIT_COMMIT")
3333
if build:
3434
payload["build"] = build
35+
36+
# WHICH satellite answered. `build` says which commit, this says which
37+
# app — and on a fleet where several hosts share a template and a
38+
# hostname can be repointed, "is this the site I think it is?" is a
39+
# different question from "is this the build I shipped?". Cheap, and the
40+
# hub's sweep gets it for free.
41+
payload["app"] = os.environ.get("SATELLITE_APP_KEY") or "unknown"
42+
43+
# The geo guardrail's LIVE state. Added 2026-08-23 after a production
44+
# verification could not answer "is the denylist actually in force?"
45+
# from outside: the control board and the public policy showcase both
46+
# showed countries denied while every request was served 200, and the
47+
# only surfaces that could have settled it (the boot log, the operator
48+
# panel) need credentials this check does not have.
49+
#
50+
# Counts and flags only — never the country codes. The codes are already
51+
# public on /showcase/policy-panel, but a health endpoint should not be
52+
# the place anyone learns policy.
53+
try:
54+
from dash_improve_my_llms import geo
55+
56+
payload["geo"] = {
57+
"configured": bool(geo.is_configured()),
58+
"denied": len(geo.effective_policy().get("deny_countries") or []),
59+
}
60+
except Exception: # never let a diagnostic break the health probe
61+
payload["geo"] = {"configured": False, "denied": 0, "error": True}
62+
3563
return payload
3664

3765

@@ -41,20 +69,26 @@ def register_health_route(app, backend: str) -> None:
4169
return
4270

4371
server = app.server
44-
payload = health_payload(backend)
4572

73+
# Built PER REQUEST, not once at registration. It used to be a snapshot
74+
# closed over by the route — harmless while every field was static
75+
# (ok/backend/dash_version/build never change for a running process), and
76+
# silently wrong the moment one is not. This route is registered at
77+
# run.py:566 and `configure_geo` runs ~150 lines later, so a snapshot
78+
# reported the guardrail as unconfigured on a host where it is configured
79+
# — the diagnostic lying in exactly the situation it exists for.
4680
if backend == "quart":
4781
from quart import jsonify
4882

4983
@server.get("/healthz")
5084
async def _healthz(): # pragma: no cover — quart runtime
51-
return jsonify(payload)
85+
return jsonify(health_payload(backend))
5286
else:
5387
from flask import jsonify
5488

5589
@server.get("/healthz")
5690
def _healthz():
57-
return jsonify(payload)
91+
return jsonify(health_payload(backend))
5892

5993
print(f"[boilerplate] /healthz registered ({backend}) — "
6094
"the 2plot.ai hourly health sweep probes this path.")

tests/test_llms_routes.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,3 +277,76 @@ def test_nav_block_is_absent_from_the_root_index(client):
277277
body = client.get("/llms.txt").text
278278
assert "## Pages" in body, "the root document should be an index"
279279
assert not CHROME.search(body), "viewer chrome leaked into the root index"
280+
281+
282+
# ---------------------------------------------------------------------------
283+
# /healthz is a LIVE report, not a snapshot
284+
# ---------------------------------------------------------------------------
285+
286+
def test_healthz_reports_which_app_answered(client):
287+
"""`build` says which commit; `app` says which satellite.
288+
289+
On a fleet where several hosts share a template and a hostname can be
290+
repointed between services, those are different questions.
291+
"""
292+
import json
293+
294+
payload = json.loads(client.get("/healthz").text)
295+
assert payload["app"], "no app identity on /healthz"
296+
assert payload["ok"] is True
297+
298+
299+
def test_healthz_reports_the_live_geo_state(app_module, client):
300+
"""The regression pin for a snapshot payload.
301+
302+
`register_health_route` used to compute the payload ONCE at registration
303+
and close over it. Harmless while every field was static — and silently
304+
wrong the moment one is not. The route is registered ~150 lines before
305+
`configure_geo` runs, so a snapshot reported the guardrail unconfigured
306+
on a host where it is configured: the diagnostic lying in exactly the
307+
situation it exists for.
308+
"""
309+
import json
310+
311+
from lib import policy_store
312+
313+
before = json.loads(client.get("/healthz").text)["geo"]
314+
assert before["configured"] is True, (
315+
"this app calls configure_geo unconditionally, so /healthz must "
316+
"report it configured"
317+
)
318+
319+
try:
320+
app_module.configure_geo(deny_countries=["RU", "CN"])
321+
after = json.loads(client.get("/healthz").text)["geo"]
322+
assert after["denied"] == 2, (
323+
f"/healthz did not follow a live config change ({after}) — it is "
324+
"a snapshot again"
325+
)
326+
finally:
327+
app_module.configure_geo(
328+
deny_countries=policy_store.geo_deny,
329+
unknown=policy_store.geo_unknown(),
330+
exempt_paths=("/healthz", "/health", "/livez", "/readyz"),
331+
)
332+
333+
334+
def test_healthz_never_publishes_the_country_codes(app_module, client):
335+
"""Counts and flags only. The codes are already public on the policy
336+
showcase, but a health endpoint is not where anyone should learn policy.
337+
"""
338+
import json
339+
340+
from lib import policy_store
341+
342+
try:
343+
app_module.configure_geo(deny_countries=["RU", "CN"])
344+
body = client.get("/healthz").text
345+
assert "RU" not in body and "CN" not in body, body
346+
assert json.loads(body)["geo"]["denied"] == 2
347+
finally:
348+
app_module.configure_geo(
349+
deny_countries=policy_store.geo_deny,
350+
unknown=policy_store.geo_unknown(),
351+
exempt_paths=("/healthz", "/health", "/livez", "/readyz"),
352+
)

0 commit comments

Comments
 (0)