risk-nexus/tools/fix_tracker.py
tegwick 623f7ccb2e Match the record to what the owners were told, and stop the tracker hiding ids
RISK-F-0002's fix_tracking now names ZONE-WP-0001 / WARDEN-WP-0032, with
the superseded pair kept beside it — the register had told ops-warden the
path had changed and then left the old ids in the file.

An unresolvable fix_tracking (TEN-IN-0002 is an intake, not a workplan)
printed nothing at all, which reads identically to a finding with no
tracking. It now says so by name. Both ZONE-WP-0001 and WARDEN-WP-0032
read proposed, which is the true state of the successor.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:34:57 +02:00

107 lines
4.2 KiB
Python

#!/usr/bin/env python3
"""Read the state of the fixes this register claims to be tracking.
RISK-WP-0005-T01. Until now `fix_tracking` held ids like `FLEX-WP-0015-T02`
that this repo wrote down and never read, so an owner who had gone quiet and a
fix that had gone quiet were indistinguishable.
Resolution is against the owning repo's workplan **file**, which is the
ADR-001 source of truth, and the file's last commit date is the honest answer
to "has this moved" — independent of whether this register happened to look.
"""
from __future__ import annotations
import datetime as dt
import pathlib
import re
import subprocess
import register_lib as lib
ESTATE = pathlib.Path("/home/worsch")
WP_ID = re.compile(r"\b([A-Z]+-WP-[0-9]{4})(-T[0-9]{2,3})?\b")
# How long a fix may sit unchanged before the register says so, by severity.
STALL_DAYS = {"critical": 14, "high": 14, "medium": 30, "low": 60}
def find_workplan(wp_id: str) -> pathlib.Path | None:
hits = sorted(ESTATE.glob(f"*/workplans/{wp_id}-*.md"))
if hits:
return hits[0]
# Archived workplans are still the record. A fix that finished and was
# archived must not read as "not found in the estate" — that is the
# register inventing a gap out of a filing convention.
archived = sorted(ESTATE.glob(f"*/workplans/archived/*{wp_id}-*.md"))
return archived[0] if archived else None
def last_commit(path: pathlib.Path) -> dt.datetime | None:
try:
out = subprocess.run(
["git", "-C", str(path.parent.parent), "log", "-1", "--format=%cI", "--", str(path)],
capture_output=True, text=True, timeout=15,
).stdout.strip()
return lib.moment(out) if out else None
except (subprocess.SubprocessError, OSError):
return None
def task_status(path: pathlib.Path, task_id: str) -> str | None:
m = re.search(rf"id: {re.escape(task_id)}\s*\nstatus: (\w+)", path.read_text(encoding="utf-8"))
return m.group(1) if m else None
def resolve(fix_tracking: str) -> list[dict]:
"""One entry per work-record id named in the finding's fix_tracking."""
out = []
for wp, task in WP_ID.findall(str(fix_tracking or "")):
path = find_workplan(wp)
if path is None:
out.append({"id": wp + (task or ""), "state": "not found in the estate", "moved": None})
continue
fm = lib.load(path)
archived = "archived" in path.parts
full = wp + task if task else wp
state = task_status(path, full) if task else fm.get("status")
out.append({
"id": full,
"repo": fm.get("repo", path.parent.parent.name),
"state": (state or "unreadable") + (" [archived]" if archived else ""),
"workplan_status": fm.get("status"),
"moved": last_commit(path),
})
return out
def report() -> list[str]:
lines, now = [], lib.now()
for f in lib.findings():
if not lib.watched(f.get("status")):
continue
tracking = f.get("fix_tracking")
if not tracking or str(tracking).startswith("unset"):
lines.append(f"{f['id']} ({f.get('severity')}) — NO FIX TRACKING; owner {f.get('fix_owner')}")
continue
resolved = resolve(tracking)
if not resolved:
# An id the tracker cannot parse is not the same as no tracking, and
# silently printing neither is how a gap hides.
lines.append(f"{f['id']} — fix_tracking '{tracking}' names no workplan this tool can resolve")
continue
for r in resolved:
if r["moved"]:
age = (now - r["moved"]).days
window = STALL_DAYS.get(f.get("severity"), 30)
done = str(r["state"]).lower() in ("done", "finished", "cancel")
flag = "" if done or age <= window else f" ← UNCHANGED {age}d (window {window}d)"
lines.append(f"{f['id']}{r['id']}: {r['state']}, last changed {r['moved']:%Y-%m-%d}{flag}")
else:
lines.append(f"{f['id']}{r['id']}: {r['state']}")
return lines
if __name__ == "__main__":
print("Fix state, read from the owning repo's workplan file\n")
for line in report():
print(" ", line)