risk-nexus/tools/register_index.py
tegwick d5147bfaea Typed, dated, defaulted waits — and cut the four-hop chain
The register had nine waits in four days, one four hops deep: F-0003's
embargo waited on F-0009, which waited on railiance-platform, which
waited on live OpenBao verification, which waited on a credential nobody
has. No single link was wrong, which is why it needed a rule.

docs/method/dependencies.md: the register never waits to decide, it
decides and revises. Every wait carries who, what, since, what it would
change, what happens if nobody answers, and the date that default
applies. Depth one — a record never waits on a record that is itself
waiting. Defaults are dates and are pessimistic: silence costs the grade
the evidence supports rather than buying a softer one, and owners are
told the default in advance because a default nobody was warned about is
an ambush.

Applied: F-0009's embargo now lifts on railiance-platform reporting
coverage, with live verification as a refinement rather than a condition,
cutting the F-0003 chain from four hops to two. All eight open waits are
typed with defaults. make check reports them with age, owner and default
date, flags defaults come due, and catches depth-two violations.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 22:34:56 +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 [])]
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)}")