From f3b68e23a4432a4fbdd9353cd4c40393564ae92e Mon Sep 17 00:00:00 2001 From: Vuong Hoang Date: Sat, 19 Sep 2026 08:07:10 -0700 Subject: [PATCH] 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. --- scripts/ops-log | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/scripts/ops-log b/scripts/ops-log index fbbf8c7..afd579c 100755 --- a/scripts/ops-log +++ b/scripts/ops-log @@ -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