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>
152 lines
6 KiB
Python
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)}")
|