fix(daily-digest): probe Firecrawl on its real liveness path and classify endpoints per fleet policy
PR Pipeline — Authorize → Validate → Review → Merge / auth (pull_request) Successful in 6s
PR Pipeline — Authorize → Validate → Review → Merge / validate (pull_request) Successful in 3s
PR Pipeline — Authorize → Validate → Review → Merge / lint (pull_request) Successful in 11s
PR Pipeline — Authorize → Validate → Review → Merge / ai-review (pull_request) Successful in 8s
PR Pipeline — Authorize → Validate → Review → Merge / gate (pull_request) Successful in 1s

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.
This commit is contained in:
root
2026-09-26 15:36:38 +00:00
parent de32f54337
commit 0a41a2d584
+24 -2
View File
@@ -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 += '<div class="card"><h2>🌐 Network Endpoints</h2><table><tr><th>Service</th><th>Status</th></tr>'
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'<tr><td>{ep["name"]}</td><td class="{color}">HTTP {ep["code"]}</td></tr>'
html += '</table></div>'