Remove the decision records and research efforts, which the knowledge base now holds whole (issue 426)
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

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.
This commit is contained in:
2026-10-11 12:56:09 +02:00
parent 76b346afcd
commit 418261213c
768 changed files with 2435 additions and 57645 deletions
+6 -181
View File
@@ -22,12 +22,12 @@ What is enforced:
them, and two places holding one fact drift. The keys the reports opened before
the import carried were removed once the import held them; the commit it read
is on every imported issue.
research a known `status:`; a `graduated` overview says what it `became:`, and every
target it names exists.
decisions every accepted record is REACHABLE from the cycle: cited by a design doc's
frontmatter, a research overview, an issue report, a 00-META document, or another
record's extends/supersedes chain. A decision nothing points at is one nobody will
find by following pointers -- which is how records go stale in people's heads.
The decision records and research efforts live in the knowledge base (ADR 0290, issue 426), and the
rules this check held over them are the knowledge base's now: a research effort's status and what it
became, and a decision's duplicate number, are refused by `knowledge-base.propose`; an accepted decision
cited by nothing is the knowledge base's whole-set rule `cited`; a deferral that names no issue is its
rule `deferral`. README.md beside this file says where each went.
Deliberately NOT enforced: as-is docs need no decisions (they describe what exists, not what
was decided).
@@ -43,89 +43,12 @@ import sys
ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", ".."))
DESIGN_STATUSES = {"proposed", "designed", "in-progress", "implemented", "abandoned"}
RESEARCH_STATUSES = {"active", "graduated", "abandoned"}
# The keys an issue report carried while its status lived here. The issue tracker holds them now (ADR 0275,
# slice 3 of to-be 53), and `issue-tracker.move` enforces the cycle's rules on them at the write. A report
# carrying one of them is a status written in two places, whenever it was opened.
TRACKER_KEYS = ("status", "located-in", "fixed-by", "amended-design", "replay", "replay-none", "resolved")
# A decision that defers work names the issue that tracks it (issue 354). ADR 0244 left the code
# renames of the node-engine and the tool runner "for a later rename", with no issue, owner or date;
# two days later the operator believed them done, and nothing in the record said otherwise. A deferral
# nothing tracks is indistinguishable from work that was done. Held to it: an accepted record dated
# from DEFERRALS_FROM, and an older one corrected under the rule (a progressive insight dated from then
# on): an edit made under the rule is held to it. The phrases are the ones records use to put work off;
# "for later" alone is not one, because records say "never queued for later" and "too narrow for later".
# The tracking issue is named as tracking it, beside the deferral or in a dated insight in the same section
# or quoting it (ISSUE_REF, covers).
DEFERRALS_FROM = "2026-10-09"
DEFERRAL = re.compile(r"(?i)until (?:it is |they are |the code (?:is )?)?renamed|until the code rename|"
r"\ba later rename|not renamed here|identifier to rename|"
r"left (?:for|to) (?:a )?later|not decided here|\bpostponed\b|future work|"
r"\bdeferred\b|later work|not built here|\ba later change")
# The issue that tracks a deferral is named as tracking it -- "tracked by issue N", or "issue N tracks" --
# because a record cites issues for many reasons, and an issue cited as context beside a deferral (ADR 0273
# names issue 340 in the very sentence that leaves a channel undecided) tracks nothing.
ISSUE_REF = re.compile(r"(?i)tracked (?:by|in|as)\s+\[?issue (\d{3})|\[?issue (\d{3})\]?(?:\([^)]*\))?\s+tracks\b")
INSIGHT = re.compile(r"\*\*Progressive insights?[ \t]*[—–-][ \t]*(\d{4}-\d{2}-\d{2})")
def paragraphs(path):
"""A record's body as (first line number, section heading, chapter, text) paragraphs: frontmatter and fenced
code left out, a block quote's markers stripped, so a quoted insight reads as the paragraph it is. The
section is the nearest heading above the paragraph; a heading is its own paragraph and its own section.
The chapter is the nearest `#` or `##` heading, which a `###` subheading inside it does not change."""
text = open(path, encoding="utf-8").read()
m = re.match(r"^---\n.*?\n---\n?", text, re.S)
body, first = (text[m.end():], text[: m.end()].count("\n") + 1) if m else (text, 1)
body = re.sub(r"(?ms)^\s*```.*?^\s*```", lambda x: re.sub(r"[^\n]", " ", x.group(0)), body)
out, line, section, chapter = [], first, "", ""
for chunk in body.split("\n\n"):
stripped = "\n".join(re.sub(r"^\s*>\s?", "", l) for l in chunk.split("\n")).strip()
if stripped:
heading = re.match(r"^#{1,6}\s+(.*)", stripped)
if heading:
section = heading.group(1).strip()
if len(stripped) - len(stripped.lstrip("#")) <= 2:
chapter = section
# A list's top-level items are units of their own: a Consequences list is one paragraph, and an
# issue named in one item tracks nothing in the next.
items = re.split(r"\n(?=[-*] |\d+\. )", stripped)
at = line + chunk[: len(chunk) - len(chunk.lstrip())].count("\n")
for item in items:
out.append((at, section, chapter, item))
at += item.count("\n") + 1
line += chunk.count("\n") + 2
return out
QUOTE = re.compile(r"\"([^\"]{12,})\"|\u201c([^\u201d]{12,})\u201d")
def flat(text):
"""Text compared for a quotation: markup and case dropped, whitespace collapsed."""
return re.sub(r"\s+", " ", re.sub(r"[*`_]", "", text)).strip().lower()
def covers(insight_section, insight_text, section, text):
"""An insight speaks about a deferral paragraph when it stands in the same section, or quotes it."""
if insight_section == section:
return True
body = flat(text)
return any(flat(a or b) in body for a, b in QUOTE.findall(insight_text))
def issue_refs(text, issues):
"""The issues a text names as tracking it: one with a folder here, or one numbered above every folder.
Since ADR 0275 an issue from 365 on lives only in the issue tracker, and ADR 0291 removes the folders; a
checkout alone cannot see those, so a number above the highest folder counts as one the tracker holds
(issue 436). The tracker hands out numbers in order, so such a number was issued after the last folder.
"""
top = max((int(k) for k in issues if k.isdigit()), default=0)
return {n for a, b in ISSUE_REF.findall(text) for n in (a, b) if n and (n in issues or int(n) > top)}
def rel(path):
return os.path.relpath(path, ROOT)
@@ -216,22 +139,6 @@ def main():
else:
seen[number] = name
# And decision records, which 155's fix left out: on 2026-10-02 two ADRs numbered 0169 landed
# on main from two sessions within the hour, and every check passed.
seen_records = {}
for path in sorted(glob.glob(os.path.join(ROOT, "02-DECISIONS", "[0-9]*.md"))):
name = os.path.basename(path)
number = name.split("-", 1)[0]
if not number.isdigit():
continue
if number in seen_records:
bad(os.path.join("02-DECISIONS", name),
"is numbered %s, and so is %s -- a record's number is how it is cited. Take the next "
"free number across main AND every open pull request; the branch that lands last "
"renumbers" % (number, seen_records[number]))
else:
seen_records[number] = name
# Since the import of slice 3 (ADR 0275), an issue's status, owner, fix and replay live in the issue
# tracker, and its `move` refuses at the write what this check refused at merge. A report carries none
# of those keys.
@@ -250,91 +157,9 @@ def main():
"(ADR 0275): move it with `issue-tracker.move`, and keep no copy here"
% ", ".join("`%s:`" % key for key in carried))
# ---- research ----------------------------------------------------------------------
for path in sorted(glob.glob(os.path.join(ROOT, "01-RESEARCH", "*", "00-overview.md"))):
front = frontmatter(path)
if front is None:
bad(path, "no frontmatter")
continue
status = front.get("status")
if status not in RESEARCH_STATUSES:
bad(path, "status %r is not one of %s" % (status, sorted(RESEARCH_STATUSES)))
if status == "graduated":
became = listy(front, "became")
if not became:
bad(path, "graduated but became: names nothing")
for target in became:
if not os.path.exists(os.path.join(ROOT, target)):
bad(path, "became names %s, which does not exist" % target)
# ---- decisions -------------------------------------------------------------------
records = {}
for path in sorted(glob.glob(os.path.join(ROOT, "02-DECISIONS", "[0-9]*.md"))):
front = frontmatter(path)
records[os.path.basename(path)] = (path, (front or {}).get("status"))
cited = set()
sources = (glob.glob(os.path.join(ROOT, "03-DESIGN", "*", "*.md"))
+ glob.glob(os.path.join(ROOT, "01-RESEARCH", "*", "00-overview.md"))
+ glob.glob(os.path.join(ROOT, "04-ISSUES", "*", "00-report.md"))
+ glob.glob(os.path.join(ROOT, "00-META", "**", "*.md"), recursive=True))
for path in sources:
text = open(path, encoding="utf-8").read()
if os.sep + "03-DESIGN" + os.sep in path:
# A design doc's governing citations live in frontmatter; a prose mention is
# commentary, not a home.
m = re.match(r"^---\n(.*?)\n---", text, re.S)
text = m.group(1) if m else ""
for m in re.finditer(r"([0-9]{4}-[^\s\)\],#]+\.md)", text):
cited.add(os.path.basename(m.group(1)))
for name in records:
front = frontmatter(records[name][0]) or {}
for key in ("extends", "supersedes", "superseded-by"):
v = front.get(key)
if isinstance(v, str) and v:
cited.add(os.path.basename(v))
for name, (path, status) in records.items():
if status == "accepted" and name not in cited:
bad(path, "an accepted decision nothing in the cycle cites -- give it a home in a "
"design doc's decisions:, a research became:, an issue, or 00-META")
# ---- deferrals -------------------------------------------------------------------
# A paragraph or list item that puts work off names the issue that tracks it, or a dated
# progressive insight that speaks about it (its section, or a quotation of it) does. An issue that has
# since resolved still counts: the deferred work landed, which is what the pointer was for.
for name, (path, status) in records.items():
front = frontmatter(path) or {}
decided = str(front.get("date") or "")
if status != "accepted":
continue
paras = paragraphs(path)
insights = [(d, sec, text) for line, sec, _, text in paras for d in INSIGHT.findall(text)]
held = decided >= DEFERRALS_FROM or any(d >= DEFERRALS_FROM for d, _, _ in insights)
if not held:
continue
# A dated insight naming an issue covers the deferrals it speaks about -- those in its own section,
# or ones it quotes -- and no other: one insight must not excuse every deferral in the record.
tracking = [(sec, text) for d, sec, text in insights if issue_refs(text, seen)]
for line, section, chapter, text in paras:
m = DEFERRAL.search(text)
# A rejected option is a decision not to do it, not work put off -- judged by its chapter, so an
# option under a `###` subheading of "## Considered Options" is skipped as well.
if re.match(r"(?i)(options? rejected|considered options|rejected)", chapter):
continue
if not m or issue_refs(text, seen):
continue
if any(covers(isec, itext, section, text) for isec, itext in tracking):
continue
bad(path, "line %d defers work (%r) and names no issue that tracks it -- a deferral nothing "
"tracks reads as done (issue 354). Name the issue beside it, with its owner, or in a "
"dated progressive insight that says what it tracks"
% (line + text.count("\n", 0, m.start()), m.group(0)))
checked = (
len(glob.glob(os.path.join(ROOT, "03-DESIGN", "0*", "*.md")))
+ len(glob.glob(os.path.join(ROOT, "04-ISSUES", "*", "00-report.md")))
+ len(glob.glob(os.path.join(ROOT, "01-RESEARCH", "*", "00-overview.md")))
+ len(records)
)
if failures:
print("cycle: %d document(s) break the development cycle:" % len(failures))