Hold an insight to the deferrals it speaks about, and name what ADR 0273 left undecided
mesh/merge-gate pass: the change touches no module of the mesh's graph
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered

A review found the check too lenient: an issue cited as context beside a deferral passed it, and one
insight excused every deferral in a record. The tracking issue is now named as tracking it, an insight
covers only its section or what it quotes, and the phrases include "not decided here", "postponed" and
"future work". ADR 0273's four undecided items get issue 357.
This commit is contained in:
jochen
2026-10-09 19:29:18 +02:00
parent 85da2fb93f
commit 2349f4bb63
7 changed files with 122 additions and 30 deletions
+51 -16
View File
@@ -63,31 +63,62 @@ REPLAY_ID = re.compile(r"^R\d+\b")
# 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(?! record)|"
r"left (?:for|to) (?:a )?later|not decided here|\bpostponed\b|future work|"
r"\bdeferred\b|later work|not built here|\ba later change")
ISSUE_REF = re.compile(r"\bissue (\d{3})\b|04-ISSUES/(\d{3})-")
# 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, text) paragraphs: frontmatter and fenced code left out,
a block quote's markers stripped, so a quoted insight reads as the paragraph it is."""
"""A record's body as (first line number, section heading, 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."""
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 = [], first
out, line, section = [], 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:
out.append((line, stripped))
heading = re.match(r"^#{1,6}\s+(.*)", stripped)
if heading:
section = heading.group(1).strip()
# 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, 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):
return {n for a, b in ISSUE_REF.findall(text) for n in (a, b) if n and n in issues}
@@ -269,8 +300,8 @@ def main():
"design doc's decisions:, a research became:, an issue, or 00-META")
# ---- deferrals -------------------------------------------------------------------
# A paragraph that puts work off names the issue that tracks it, or the record carries a
# dated progressive insight that names the issue and says what it tracks. An issue that has
# 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 {}
@@ -278,17 +309,21 @@ def main():
if status != "accepted":
continue
paras = paragraphs(path)
insights = [(d, text) for line, text in paras for d in INSIGHT.findall(text)]
held = decided >= DEFERRALS_FROM or any(d >= DEFERRALS_FROM for d, _ in insights)
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
covering = set()
for d, text in insights:
if DEFERRAL.search(text):
covering |= issue_refs(text, seen)
for line, text in paras:
# 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, text in paras:
m = DEFERRAL.search(text)
if not m or issue_refs(text, seen) or covering:
# A rejected option is a decision not to do it, not work put off.
if re.match(r"(?i)(options? rejected|considered options|rejected)", section):
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 "