feat(host_agent): a host says what updates it has waiting and whether it needs a reboot (#5765)
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 1m22s
CI / unit (push) Successful in 1m26s
CI / publish (push) Successful in 1m20s

Step 1 of milestone 514 "Updates and reboots on a cadence, carried out by
each host's agent…": #5765 "The agent reports pending updates and whether
a reboot is required".

- Agent 1.16.0: an hourly background check (apt-check, apt list, or dnf -C
  with updateinfo and needs-restarting), each command bounded at 120 s, no
  root and no network. Debian's reboot-required marker and its packages
  are read every sample.
- Ingest records updates_pending, updates_security and reboot_required for
  rules, and keeps the newest on the registration (host_agent_005_updates).
- The Agent card leads with "Reboot required since …", then the updates
  waiting or "Up to date", with when the check ran. list_agents carries
  them too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 18:23:44 -04:00
co-authored by Claude Opus 5.5
parent b1db1e8b0d
commit 210a35c956
12 changed files with 472 additions and 4 deletions
+19
View File
@@ -33,6 +33,19 @@ async def _agents(db):
)).all()
def _updates(reg) -> dict | None:
"""A registration's pending updates and reboot, as list_agents says them (#5765)."""
if reg.updates_checked_at is None and reg.reboot_required is None:
return None
return {
"manager": reg.updates_manager, "pending": reg.updates_pending,
"security": reg.updates_security, "checked_at": iso(reg.updates_checked_at),
"error": reg.updates_error, "reboot_required": reg.reboot_required,
"reboot_required_since": iso(reg.reboot_required_since),
"reboot_packages": reg.reboot_packages.split(", ") if reg.reboot_packages else [],
}
async def list_agents() -> dict:
"""Every host agent Steward has registered, with its version and whether it is reporting.
@@ -41,6 +54,11 @@ async def list_agents() -> dict:
(registered, never checked in, usually an install that is still running
or that failed). Each row carries the agent version, kernel, distro, arch
and the IP the agent reports. get_host shows an agent's latest metrics.
`updates` is what the host's package manager has waiting, from the
agent's hourly check (agent ≥ 1.16.0): manager, pending, security (null
when the manager can't say), checked_at, error, and reboot_required with
since when and the packages that asked for it. null before the first check.
"""
now = now_utc()
stale_after = host_svc.agent_stale_after_seconds(mcp_app().config)
@@ -54,6 +72,7 @@ async def list_agents() -> dict:
"agent_version": reg.agent_version, "kernel": reg.kernel, "distro": reg.distro,
"arch": reg.arch, "reported_ip": reg.host_ip,
"registered_at": iso(reg.created_at),
"updates": _updates(reg),
}
for reg, h in rows
]