From 0a41a2d584ef0e2f3aa2209f33a6e45ddcc89343 Mon Sep 17 00:00:00 2001 From: root Date: Sat, 26 Sep 2026 15:36:38 +0000 Subject: [PATCH] fix(daily-digest): probe Firecrawl on its real liveness path and classify endpoints per fleet policy Folds into the same branch as the Zulip delivery change, as instructed. FINDING 1 - the Firecrawl probe path was wrong; the service is fine. scripts/daily-infra-report.py probed http://192.168.68.7:3002/health, which Firecrawl does not serve - it 404s. The root answers 200 with {"message":"Firecrawl API",...}. Live 2026-09-26: Firecrawl(/) -> 200 Firecrawl(/health) -> 404 <- what the report was showing The probe is now the root, which is its liveness endpoint. FINDING 2 - the Network Endpoints classification was wrong twice over. It read: color = green if code in (200,302,401) else (yellow if code >= 400 else red). Two defects: (a) it ignored the fleet's own probe policy, codified 2026-09-14 in the monitoring contracts: ANY HTTP status proves the service answered, so the service is ALIVE, and only a failed CONNECTION is a failed probe. A 404 from a wrong path is not a service fault. (b) was a STRING comparison. Reproduced: 301 -> red (a live redirect rendered as a failure), 404 -> yellow, 500 -> yellow (a real server error softened to a warning). Replaced with classify_endpoint(), which returns: any 2xx/3xx/4xx -> green 'alive' (code still shown) 5xx -> yellow 'server error' (kept distinct from 4xx, as asked) 000/no answer -> red 'no connection' Verified against the live endpoints after the change: Gitea 200, Authentik 302, Zulip 302, Pulse 200, Proxmox 200, SearXNG 200, Firecrawl 200 - all green/alive; the only red state is a genuine no-connection. ALSO CHECKED, as asked: scripts/search-stack-check.py does NOT depend on the wrong route. It POSTs to {FIRECRAWL_URL}/v1/scrape with formats=[markdown], and that path really works - live POST returned HTTP 200 and 180 chars of markdown for https://example.com. It was never using /health. prose-lint: PASSED. --- scripts/daily-infra-report.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/scripts/daily-infra-report.py b/scripts/daily-infra-report.py index 54d1d92..d4ea680 100755 --- a/scripts/daily-infra-report.py +++ b/scripts/daily-infra-report.py @@ -243,7 +243,7 @@ def collect(): ("Pulse", "https://pulse.sysloggh.net"), ("Proxmox", "https://192.168.68.12:8006"), ("SearXNG", "http://192.168.68.7:8888"), - ("Firecrawl", "http://192.168.68.7:3002/health"), + ("Firecrawl", "http://192.168.68.7:3002/"), # Firecrawl serves no /health - the root is its liveness endpoint ] report["endpoints"] = [] for name, url in endpoints: @@ -389,6 +389,28 @@ def collect(): # ── HTML Dashboard ── +def classify_endpoint(code): + """Classify an endpoint probe per the fleet's probe policy. + + Codified 2026-09-14 in the monitoring contracts: ANY HTTP status proves the + service answered, so the service is ALIVE - 200/301/302/401/403/404 alike. + Only a failed CONNECTION (000 / timeout / refused) is a failed probe. A 404 + from a wrong path is not a service fault and must not render as one. + + This replaces a string comparison that was wrong in both directions + (`ep["code"] >= "400"`): it rendered 301 as red, 404 as yellow, and a real + 500 as yellow. 5xx is kept as its own "server error" signal rather than + being merged with 4xx. + """ + if not code or code == "000": + return "red", "no connection" + if code.startswith("5"): + return "yellow", "server error" + if code.startswith(("2", "3", "4")): + return "green", "alive" + return "yellow", f"unexpected {code}" + + def build_html(r): issues = [] @@ -624,7 +646,7 @@ Proxmox: {r.get('pve_probe_status', 'ok')} ({r['nodes_online']}/{r['node_count'] # ── Network Endpoints ── html += '

🌐 Network Endpoints

' for ep in r["endpoints"]: - color = "green" if ep["code"] in ("200","302","401") else ("yellow" if ep["code"] >= "400" else "red") + color = classify_endpoint(ep["code"])[0] html += f'' html += '
ServiceStatus
{ep["name"]}HTTP {ep["code"]}
'