Files
hq/00-META/checks/records.py
T
jschoubben 418261213c
mesh/merge-gate pass: the change touches no module of the mesh's graph
mesh/repo-check pass: THE CHANGE ALTERS ITS OWN CHECK (merge-check.sh): main's version judged it; the change's judges the pull requests after it merges; it…
mesh/delivery superseded: a newer head of the same pull request
Remove the decision records and research efforts, which the knowledge base now holds whole (issue 426)
ADR 0290 rule 9: once the import is checked record by record, hq keeps no copy.
The records and efforts are removed; every link or path to them reads "ADR NNNN" or
"research NNN"; a design's decisions: lists "ADR NNNN". 02-DECISIONS keeps a pointer
README with the reading order's six topics, which the knowledge base reads through the
records module; 01-RESEARCH keeps a pointer README. The checks drop the rules that
judged records (the knowledge base holds each at propose; checks/README.md says where)
and refuse a record written here again. Playbooks 01 and 02 and the skills write and
read records with the knowledge base's verbs.
2026-10-11 12:56:09 +02:00

228 lines
9.0 KiB
Python

#!/usr/bin/env python3
"""Structural checks over HQ's own documents.
Every check here exists because the thing it checks for actually happened. See README.md
for which incident is behind which check. Run from the repository root:
python3 00-META/checks/records.py
Exits non-zero if anything fails, so it can be a gate rather than a report.
The decision records and research efforts live in the knowledge base (ADR 0290, issue 426), and the
rules that judged them here are the knowledge base's now: `propose` refuses a record that breaks one.
README.md beside this file says where each rule went. What stays here judges what this repository
still holds, and keeps the records from coming back.
"""
import os
import re
import sys
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
LINK = re.compile(r"\[[^\]]*\]\((?!https?:|mailto:)([^)]+)\)")
# The two folders the records left. Each keeps a README that points at the knowledge base, and
# 02-DECISIONS/README.md also writes the reading order's topics, which the knowledge base reads.
RECORD_FOLDERS = ("02-DECISIONS", "01-RESEARCH")
POINTERS = {"02-DECISIONS/README.md", "01-RESEARCH/README.md"}
# A decision record is named "ADR NNNN", four digits, and a research effort "research NNN", three.
DECISION_ENTRY = re.compile(r"^ADR \d{4}$")
ADR_MENTION = re.compile(r"\bADRs?\s+(\d+)\b")
RESEARCH_MENTION = re.compile(r"\b[Rr]esearch\s+(\d+)\b")
def markdown_files():
for base, dirs, files in os.walk(ROOT):
dirs[:] = [d for d in dirs if d not in (".git", ".claude")]
for name in sorted(files):
if name.endswith(".md"):
yield os.path.join(base, name)
def rel(path):
return os.path.relpath(path, ROOT)
def read(path):
with open(path, encoding="utf-8") as handle:
return handle.read()
def frontmatter(text):
"""Minimal frontmatter reader — enough for the fields these checks use.
Not a YAML parser on purpose: a dependency in a repository that has none, to read a few
scalar fields and one list, would cost more than it returns.
"""
if not text.startswith("---\n"):
return {}
end = text.find("\n---", 4)
if end == -1:
return {}
fields, key = {}, None
for line in text[4:end].split("\n"):
item = re.match(r"^\s+-\s+(.*)$", line)
if item and key:
fields.setdefault(key, []).append(item.group(1).strip())
continue
pair = re.match(r"^([A-Za-z_-]+):\s*(.*)$", line)
if pair:
key = pair.group(1)
value = pair.group(2).strip()
if value.startswith("[") and value.endswith("]"):
# inline list: `decisions: [a, b]`, `code: []`
inner = value[1:-1].strip()
fields[key] = [v.strip() for v in inner.split(",") if v.strip()]
elif value:
fields[key] = value
else:
fields[key] = []
return fields
def unquote(value):
value = value.strip()
if len(value) >= 2 and value[0] == value[-1] and value[0] in "\"'":
return value[1:-1]
return value
class Failures:
def __init__(self):
self.items = []
def add(self, check, location, message):
self.items.append((check, location, message))
def report(self):
if not self.items:
print("records: all checks passed")
return 0
by_check = {}
for check, location, message in self.items:
by_check.setdefault(check, []).append((location, message))
for check in sorted(by_check):
print(f"\n{check} — {len(by_check[check])} problem(s)")
for location, message in by_check[check]:
print(f" {location}\n {message}")
print(f"\nrecords: {len(self.items)} problem(s)")
return 1
def check_links(failures):
"""Every relative link resolves to something that exists."""
for path in markdown_files():
folder = os.path.dirname(path)
for number, line in enumerate(read(path).split("\n"), 1):
for target in LINK.findall(line):
target = target.split("#")[0].strip()
if not target:
continue
if not os.path.exists(os.path.normpath(os.path.join(folder, target))):
failures.add("links", f"{rel(path)}:{number}", f"link does not resolve: {target}")
def check_no_records(failures):
"""No decision record or research effort is written into this repository again (ADR 0290 rule 9).
The knowledge base is the only place a record is read; a file here would be a second copy that
drifts from it, and nothing would read it. The folders keep their pointer READMEs and nothing else.
"""
for folder in RECORD_FOLDERS:
base = os.path.join(ROOT, folder)
if not os.path.isdir(base):
failures.add("no-records", folder, "the folder is gone; it keeps a README that points at the "
"knowledge base (and, for 02-DECISIONS, the reading order the knowledge base reads)")
continue
for current, dirs, files in os.walk(base):
for name in sorted(files + [d + "/" for d in dirs]):
path = rel(os.path.join(current, name))
if path.rstrip("/") in POINTERS:
continue
failures.add(
"no-records",
path,
"a decision record or research effort lives in the knowledge base, never here: "
"write it with knowledge-base.reserve and knowledge-base.propose (playbooks 01 and 02)",
)
def check_decisions_entries(failures):
"""A design's `decisions:` names each record as "ADR NNNN".
A to-be design rests on the records it names, and the knowledge base answers for a record by its
number; an entry in another form is a citation nothing can follow.
"""
for path in markdown_files():
if not rel(path).startswith("03-DESIGN/"):
continue
for entry in frontmatter(read(path)).get("decisions", []) or []:
if not DECISION_ENTRY.match(unquote(entry)):
failures.add("decisions-form", rel(path),
f"decisions: entry {entry!r} is not 'ADR NNNN' (four digits)")
def check_mentions(failures):
"""In running text, a decision record is "ADR NNNN" (four digits) and a research effort "research NNN"
(three): the form the knowledge base and the issue tracker read a citation in. A code span is an example,
and is not read."""
for path in markdown_files():
text = read(path)
for number, line in enumerate(text.split("\n"), 1):
line = re.sub(r"`[^`]*`", "", line)
for m in ADR_MENTION.finditer(line):
if len(m.group(1)) != 4:
failures.add("mention-form", f"{rel(path)}:{number}",
f"{m.group(0)!r}: a decision record is named 'ADR NNNN', four digits")
for m in RESEARCH_MENTION.finditer(line):
if len(m.group(1)) != 3:
failures.add("mention-form", f"{rel(path)}:{number}",
f"{m.group(0)!r}: a research effort is named 'research NNN', three digits")
def check_status_against_code(failures):
"""A design document naming specific code may not still call itself `designed`.
**Naming a file is a claim that the file implements this**, so the two fields have to agree.
They drifted: ten to-be documents named working, lab-proven code — several with a *What was
built* or *Raised, and observed* section — while still saying nothing had been built.
Deliberately weak, and that is the point of it being mechanical. It cannot tell whether the
prose is true, only that a document has stopped claiming to be unbuilt once it points at
something. `code: [mesh-controller]` — a repository with no path — is a plan and stays
`designed`.
"""
for path in markdown_files():
if not rel(path).startswith("03-DESIGN/01-to-be/") or path.endswith("README.md"):
continue
front = frontmatter(read(path))
if front.get("status") != "designed":
continue
for entry in front.get("code") or []:
named = re.sub(r"\s*\(.*\)$", "", entry).strip().split(None, 1)
if len(named) > 1:
failures.add(
"status-vs-code",
rel(path),
f"`designed`, but names {named[1]!r} in {named[0]}. Naming a file claims "
f"it implements this — use `in-progress`, or `implemented` once it is "
f"defensible from that repository's main branch.",
)
break
def main():
failures = Failures()
check_links(failures)
check_no_records(failures)
check_decisions_entries(failures)
check_mentions(failures)
check_status_against_code(failures)
return failures.report()
if __name__ == "__main__":
sys.exit(main())