feat(docker): overview findings say when the problem began

#5013 "Overview findings for containers and Swarm services carry since=null"

- Container problems are dated from their own evidence: a crash or restart
  loop from its first death in the hour, an OOM kill or error exit from the
  death that ended it, a failing healthcheck from its last turn to unhealthy.
  list_containers and get_container carry problem_since / problem_age_s.
- Swarm services carry trouble_since: a crash loop's first failure in the
  hour, a short service's oldest troubled task in each failing slot, a
  recovered service's first failure in 24h. list_swarm_services shows it.
- get_overview findings from the docker plugin now have since and age_s,
  so they rank by how long they have stood.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-06 10:24:10 -04:00
co-authored by Claude Opus 5.5
parent bac2859455
commit e9341c5639
6 changed files with 209 additions and 15 deletions
+7 -1
View File
@@ -204,6 +204,8 @@ def test_docker_and_agent_tools_over_seeded_rows(app):
assert (s["failures_1h"], s["failures_24h"], s["crash_looping"]) == (1, 1, False)
assert s["last_error"]["error"] == "task: non-zero exit (1)"
assert s["last_error"]["node"] == host_name and s["last_error"]["age_s"] >= 600
# Short of replicas since slot 2's task failed, ten minutes ago.
assert 600 <= s["trouble_age_s"] < 900
old = (await dtools.list_containers(host=host_name, status="exited", limit=500))["containers"]
old = next(c for c in old if c["container_name"] == replaced)
assert old["swarm_replaced"] is True and old["problems"] == []
@@ -213,11 +215,15 @@ def test_docker_and_agent_tools_over_seeded_rows(app):
# ── overview contributions ─────────────────────────────────────────
async with app.db_sessionmaker() as db:
found = {(f["kind"], f["subject"]) for f in await dtools.mcp_overview(db)}
findings = await dtools.mcp_overview(db)
found = {(f["kind"], f["subject"]) for f in findings}
agent_found = {(f["kind"], f["subject"]) for f in await atools.mcp_overview(db)}
assert ("container_unhealthy", db_) in found
assert ("container_exited_nonzero", job) in found
assert ("swarm_service_degraded", svc) in found
# Each says how long it has been wrong: the death, the failed task.
since = {f["subject"]: f["age_s"] for f in findings}
assert 600 <= since[job] < 900 and 600 <= since[svc] < 900
assert not any(subject == replaced for _, subject in found)
assert not any(subject == web for _, subject in found)
assert ("agent_never_reported", quiet_host) in agent_found