Hold an insight to the deferrals it speaks about, and name what ADR 0273 left undecided
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:
@@ -62,13 +62,18 @@ allocate the same one). And a core issue — one opened from 2026-10-07 whose `l
|
||||
repository — resolves only with `replay:` (an id in mesh-lab's replays register) or `replay-none:` saying
|
||||
why none is possible ([ADR 0237](../../02-DECISIONS/0237-a-change-is-judged-against-the-mesh-that-runs-before-it-merges-on-the-build-seat.md));
|
||||
it failed on a resolved core issue carrying neither before it passed. And an accepted record that puts work
|
||||
off — "until renamed", "left for later", "deferred", "a later change" — names the issue that tracks it, in that
|
||||
paragraph or in a dated progressive insight that says what it tracks
|
||||
off — "until renamed", "left for later", "not decided here", "postponed", "future work", "deferred", "a later
|
||||
change" — names the issue that tracks it, as tracking it ("tracked by issue N", or "issue N tracks"), in that
|
||||
paragraph or list item, or in a dated progressive insight that speaks about it: one in the same section, or one
|
||||
that quotes it. An issue cited for another reason tracks nothing, and one insight does not excuse every deferral
|
||||
in a record. A rejected option is not work put off and is not held
|
||||
([issue 354](../../04-ISSUES/354-a-decided-rename-was-never-scheduled-and-the-operator-believed-it-live/00-report.md):
|
||||
ADR 0244 deferred the code renames with no issue, and two days later the operator believed them done). Held to
|
||||
it: a record dated from 2026-10-09, and an older one corrected under the rule. Measured from ADR 0244's date
|
||||
it found deferrals without an issue in ADR 0241, 0244 and 0270; held from 2026-10-09 it failed on 0270, and on
|
||||
0244 once its insight brought it under the rule, before each named its issue. `python3 00-META/checks/cycle.py`
|
||||
it found deferrals without an issue in ADR 0241, 0244 and 0270; held from 2026-10-09 it failed on 0270 and 0273
|
||||
(whose "not decided here" sits beside a citation of issue 340 that tracks none of it), and on 0244's four
|
||||
deferral places once its insight brought it under the rule — on three of them while the insight, in §6, named
|
||||
the issue without quoting them — before each named its issue (355, 357, 354). `python3 00-META/checks/cycle.py`
|
||||
|
||||
## words.py
|
||||
|
||||
|
||||
+51
-16
@@ -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 "
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
number. Format and rules are in [`02-DECISIONS/README.md`](../../02-DECISIONS/README.md). State evidence,
|
||||
not assertion, and record the options that were rejected — that is the half worth keeping.
|
||||
**A record that puts work off** — a rename, a check, a case it does not handle — names the issue that
|
||||
tracks it and the owner who will do it, in the sentence that defers it, or the deferral is not accepted:
|
||||
tracks it and the owner who will do it, in the sentence that defers it ("tracked by issue N"), or the
|
||||
deferral is not accepted:
|
||||
a deferral nothing tracks reads as done
|
||||
([issue 354](../../04-ISSUES/354-a-decided-rename-was-never-scheduled-and-the-operator-believed-it-live/00-report.md),
|
||||
ADR 0244's code renames). [`cycle.py`](../checks/cycle.py) holds every accepted record dated from
|
||||
|
||||
Reference in New Issue
Block a user