risk-nexus/tools/register_index.py
tegwick d3aefefdc0 Waits outlive statuses
RISK-F-0001 is fixed and still owes a publication entry; it fell out of
the waiting list because that list was built from watched findings only.
A closed record with an open obligation is exactly the thing that goes
quiet, since nothing prompts anyone to look at it any more.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 22:45:36 +02:00

152 lines
6 KiB
Python

#!/usr/bin/env python3
"""Generate REGISTER.md from the finding front-matter.
RISK-WP-0001-T05. The index is generated because a hand-maintained one
drifts and then lies. Edit the findings, re-run `make register`.
"""
from __future__ import annotations
import datetime as dt
import sys
import register_lib as lib
TODAY = dt.date.today()
def date(value) -> dt.date | None:
if isinstance(value, dt.date):
return value
if isinstance(value, str) and value not in ("", "unset"):
return dt.date.fromisoformat(value)
return None
def check_cell(f: dict) -> str:
when = lib.moment(f.get("next_check"))
if when is None:
return ""
if lib.now() >= when:
return "**due**"
return when.strftime("%Y-%m-%d %H:%MZ")
def cadence_cell(f: dict) -> str:
rung = f.get("cadence", "instant")
streak = f.get("clean_streak", 0)
return f"{rung} ({streak})"
def sev_cell(f: dict) -> str:
sev = f.get("severity", "unset")
return f"**{sev}**" if sev in ("critical", "high") else sev
def esc_cell(f: dict) -> str:
esc = f.get("escalation", "unset")
if esc in ("none", "unset"):
return esc
trigger = f.get("escalation_trigger")
status = f.get("escalation_status", "")
tail = f" (t{trigger}{', ' + status if status else ''})" if trigger else ""
return f"**{esc}**{tail}"
def render() -> str:
fs = lib.findings()
ns = lib.notes()
open_fs = [f for f in fs if lib.watched(f.get("status"))]
out = [
"# Register",
"",
f"Generated by `tools/register_index.py` from `findings/`. Do not edit by hand. Last built {TODAY}.",
"",
f"{len(open_fs)} live of {len(fs)} findings; {len(ns)} notes below the floor.",
"",
"## Findings",
"",
"| ID | Finding | System | Severity | Disclosure | Escalation | Fix owner | Status | Cadence | Next check |",
"| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |",
]
for f in fs:
out.append(
"| [{id}]({path}) | {title} | {system} | {sev} | {disc} | {esc} | {owner} | {status} | {cadence} | {check} |".format(
id=f["id"],
path=f["_path"].relative_to(lib.REPO),
title=str(f.get("title", "")).strip('"'),
system=f.get("system", ""),
sev=sev_cell(f),
disc=f.get("disclosure", "unset"),
esc=esc_cell(f),
owner=f.get("fix_owner", ""),
status=f.get("status", ""),
cadence=cadence_cell(f),
check=check_cell(f),
)
)
constraints = [f for f in fs if f.get("constraint")]
if constraints:
out += ["", "## Constraints", "",
"Hazards created by acting in the wrong order. Each binds another finding's remediation.",
"", "| From | Binds | Severity | Constraint |", "| --- | --- | --- | --- |"]
for f in constraints:
out.append("| {id} | {on} | **{sev}** | {text} |".format(
id=f["id"], on=f.get("constraint_on", ""),
sev=f.get("constraint_severity", ""), text=f["constraint"]))
waits = [(f, w) for f in fs for w in (f.get("waiting_on") or [])] # fs is every finding, closed included
if waits:
out += ["", "## Waiting on someone", "",
"Every wait resolves on its default date whether or not anyone answers.",
"Silence never buys a softer grade — see `docs/method/dependencies.md`.",
"", "| Finding | Who | What would change | Default if silent | On |",
"| --- | --- | --- | --- | --- |"]
for f, w in waits:
out.append("| {id} | {who} | {chg} | {dflt} | {at} |".format(
id=f["id"], who=w.get("who", ""), chg=w.get("would_change", ""),
dflt=w.get("default", ""), at=w.get("default_at", "")))
embargoed = [f for f in fs if f.get("disclosure") == "embargoed"]
if embargoed:
out += ["", "## Embargoes", "",
"Held from publication with a stated condition. A hold with no moving condition is a stall.",
"", "| Finding | Since | Lifts when | Re-decided |", "| --- | --- | --- | --- |"]
for f in embargoed:
out.append("| {id} | {since} | {cond} | {rev} |".format(
id=f["id"], since=f.get("embargo_since", ""),
cond=f.get("embargo_condition", ""), rev=f.get("embargo_review", "")))
if ns:
out += ["", "## Notes (below the floor)", "",
"Seen, deliberately not findings. Not graded, not reviewed, not published.",
"", "| ID | Note | Why below the floor |", "| --- | --- | --- |"]
for n in ns:
out.append("| [{id}]({path}) | {title} | {why} |".format(
id=n["id"], path=n["_path"].relative_to(lib.REPO),
title=str(n.get("title", "")).strip('"'),
why=str(n.get("floor_reason", "")).strip('"')))
out += ["", "## How to read this", "",
"Severity is `docs/method/severity.md`; disclosure `docs/method/disclosure.md`;",
"escalation `docs/method/escalation.md`; the check cadence `docs/method/review.md`.",
"Cadence is the ladder rung and the count of consecutive clean checks — a finding at `1q (9)`",
"has held still for a long time; one at `instant (0)` moved recently. Anything wrong resets it.",
"A constraint may be graded higher than the finding that carries it — read both.",
""]
return "\n".join(out)
if __name__ == "__main__":
text = render()
target = lib.REPO / "REGISTER.md"
if "--check" in sys.argv:
current = target.read_text(encoding="utf-8") if target.exists() else ""
if current != text:
print("REGISTER.md is stale — run `make register`")
raise SystemExit(1)
print("REGISTER.md is current")
else:
target.write_text(text, encoding="utf-8")
print(f"wrote {target.relative_to(lib.REPO)}")