fix(ops-log): record the host's NAME, not the ssh target

elway passes its `host` argument straight through, and that argument is an ssh
target. Five records of a real jobs.cfg change on esh-pve were written under
host `infra-ops@esh-pve`.

The consequence was not cosmetic. infra-hermes triaged the ESH backup job
errors, queried `--host esh-pve`, got nothing back, and correctly reported the
05:29 exclusion of VM 102 as an unattributed change. The record had existed the
whole time, five lines of it, under a name nobody would think to ask for. A log
you cannot query under the obvious name is not a log.

`normalize_host` strips any `user@` prefix on write AND on query, so records
written before this fix are findable too, and `audit` -- which matches records
by host name -- stops missing them as well.

Verified in both directions: a record written with `infra-ops@esh-pve` and one
written with `esh-pve` now both return for `--host esh-pve`, and the five
historical elway entries surface under that query.
This commit is contained in:
2026-09-19 08:07:10 -07:00
parent 2e823538f7
commit f3b68e23a4
+17 -2
View File
@@ -141,12 +141,27 @@ def append(record: dict) -> None:
fcntl.flock(fh.fileno(), fcntl.LOCK_UN)
def normalize_host(host: str) -> str:
"""`infra-ops@esh-pve` -> `esh-pve`.
The ssh TARGET is not the host's name, and recording it as one makes the
entry unfindable. Measured 2026-09-19: five elway records of a real
jobs.cfg change on esh-pve were written under host `infra-ops@esh-pve`, so
infra-hermes's `--host esh-pve` query returned nothing and he correctly
reported the change as unattributed. The record existed the whole time. A
log you cannot query under the obvious name is not a log.
Also fixes `audit`, which matches records by host name.
"""
return host.rsplit("@", 1)[-1].strip() or host
def record(host: str, action: str, target: str = "", outcome: str = "changed",
detail: str = "", extra: dict | None = None) -> dict:
rec = {
"ts": now_iso(),
"agent": agent_id(),
"host": host,
"host": normalize_host(host),
"action": action,
"target": target,
"outcome": outcome,
@@ -181,7 +196,7 @@ def read_records(limit: int | None = None, since_s: int | None = None,
out.append({"ts": "?", "agent": "?", "host": "?", "action": "UNPARSEABLE",
"target": "", "outcome": "failed", "detail": line[:200]})
continue
if host and rec.get("host") != host:
if host and normalize_host(rec.get("host", "")) != normalize_host(host):
continue
if target and rec.get("target") != target:
continue