fix: error-pages graded healthy, unwired sensors listed apart, Maintenance failed reads 0 (#5780, #5781, #5782)
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 1m23s
CI / unit (push) Successful in 1m29s
CI / publish (push) Successful in 18s

- #5780 "The error-pages service is graded on other routers' errors". The
  Traefik scrape reads which services an errors middleware sends to, and
  grades them healthy: their 4xx and 5xx are the codes of the errors they
  cover for, already graded on the routers that failed.
- #5781 "The Maintenance failed rules sit at no data until a host has had
  a maintenance job". Every host with an agent reads 0 until a job fails.
- #5782 "VDNT-Util01's host page lists SYSTIN at 115 °C in red". The host
  page lists sensors that counts_as_hottest leaves out (unwired board
  inputs, 0 °C or below) on one muted line, not as heat.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 21:15:44 -04:00
co-authored by Claude Opus 5.5
parent 0f604ecb4a
commit 72e754b580
9 changed files with 120 additions and 13 deletions
+11 -5
View File
@@ -285,12 +285,17 @@ async def tick(session, now: datetime) -> list[str]:
async def failed_hosts(session, now: datetime) -> dict[str, float]:
"""{host name: 1 or 0}: whether each host's latest finished maintenance job
failed or timed out, for the maintenance_failed figure an alert rule
watches. A later job that succeeds puts it back to 0."""
"""{host name: 1 or 0} for every host with an agent: whether its latest
finished maintenance job failed or timed out, for the maintenance_failed
figure an alert rule watches. A host with no job yet reads 0, so a rule
on it reports rather than sitting at "no data". A later job that
succeeds puts it back to 0."""
from sqlalchemy import select
from steward.models.hosts import Host
from .models import HostMaintenanceJob
from .models import HostAgentRegistration, HostMaintenanceJob
agents = (await session.execute(
select(Host.name).join(HostAgentRegistration, HostAgentRegistration.host_id == Host.id))).scalars()
out = {name: 0.0 for name in agents}
rows = (await session.execute(
select(Host.name, HostMaintenanceJob.state, HostMaintenanceJob.finished_at)
.join(Host, Host.id == HostMaintenanceJob.host_id)
@@ -300,4 +305,5 @@ async def failed_hosts(session, now: datetime) -> dict[str, float]:
for name, state, finished in rows:
if name not in latest or finished > latest[name][1]:
latest[name] = (state, finished)
return {name: 0.0 if state == "done" else 1.0 for name, (state, _) in latest.items()}
out.update({name: 0.0 if state == "done" else 1.0 for name, (state, _) in latest.items()})
return out