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
+9 -4
View File
@@ -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
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 "
+2 -1
View File
@@ -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
@@ -182,9 +182,11 @@ work:
descriptions call a build request an *ask*;
- the nftables module's tool `firewall_rules`, which serves the packet filter.
> **Progressive insight — 2026-10-09.** This section deferred the code renames and named no issue, owner or
> date for them; two days later the operator believed them done, and nothing in this record said otherwise.
> They are tracked by
> **Progressive insight — 2026-10-09.** This record deferred the code renames in four places and named no
> issue, owner or date for them: this section, §2 ("listed in §6 for a later rename and is not renamed here"),
> §3 ("Code that still carries an old name is named on an *Identifier until renamed* line") and §4's table
> ("`mesh-host` an identifier until the code rename"). Two days later the operator believed them done, and
> nothing in this record said otherwise. They are tracked by
> [issue 354](../04-ISSUES/354-a-decided-rename-was-never-scheduled-and-the-operator-believed-it-live/00-report.md),
> which holds the inventory of what carries the old names, the risk of renaming the paths of a self-updating
> daemon on live nodes, and the staged plan; each stage is owned by the repository it names, and the
@@ -377,6 +377,12 @@ Recording a proposal needs no yes: it writes the register and nothing a session
for an agent the mesh runs rather than the operator's; and a channel of the operator's own that can
tell the operator from an agent (issue 340's "once we build a UI"), which would become a third route.
> **Progressive insight — 2026-10-09.** The four things this section leaves undecided — clearing old copies
> of hand versions, a per-project lesson, lessons for an agent the mesh runs, and a channel of the operator's
> own — were left with no issue, owner or date, which is how ADR 0244's deferred renames went unnoticed
> (issue 354). They are tracked by
> [issue 357](../04-ISSUES/357-what-adr-0273-left-undecided-has-no-owner/00-report.md). The decision stands.
## How it is checked
| Rule | Checked by |
@@ -44,10 +44,11 @@ On 2026-10-09 the operator took the rename as long implemented and live. It is n
from one whose work landed.
- **It is not one record's slip.** The check this issue adds, run over every accepted record from the
date ADR 0244 was accepted, found deferrals without an issue in ADR 0241 ("a later change requires them
once both holders serve them"), ADR 0270 ("left for a later change") and ADR 0244 itself. ADR 0270 is
dated 2026-10-09, is held to the rule, and has
once both holders serve them"), ADR 0270 ("left for a later change"), ADR 0273 ("not decided here", four
items) and ADR 0244 itself. ADR 0270 and 0273 are dated 2026-10-09, are held to the rule, and have
[issue 355](../355-a-held-delivery-of-a-module-that-runs-on-one-machine-only-is-never-covered/00-report.md)
now; ADR 0241 is dated before the rule's start and stands as found.
and [issue 357](../357-what-adr-0273-left-undecided-has-no-owner/00-report.md) now; ADR 0241 is dated
before the rule's start and stands as found.
- **Impact.** The operator plans against a mesh that does not exist: in conversation the mesh says
node-engine, while every file, unit, package and condition on a node says `mesh-host`, so a person at a
machine (`systemctl status nox-mesh-host`, `ls /var/lib/mesh-host`) and a person reading this repository
@@ -64,9 +65,10 @@ On 2026-10-09 the operator took the rename as long implemented and live. It is n
owner, or the deferral is not accepted. Playbooks [02](../../00-META/process/02-graduation.md)
(graduation) and [04](../../00-META/process/04-build-handoff.md) (handoff) say it;
[`cycle.py`](../../00-META/checks/cycle.py) checks it: an accepted record dated from 2026-10-09, or an
older one corrected under the rule, whose text puts work off ("until renamed", "left for later",
"deferred", "a later change") names an issue in that paragraph or in a dated progressive insight that
says what it tracks. ADR 0244 carries the insight that points here.
older one corrected under the rule, whose text puts work off ("until renamed", "left for later", "not
decided here", "postponed", "future work", "deferred", "a later change") names the issue as tracking it
("tracked by issue N"), in that paragraph or list item, or in a dated progressive insight in the same
section or quoting it. ADR 0244 carries the insight that points here.
This resolves when the acceptance sentence holds on every node. It is a core issue
([ADR 0237](../../02-DECISIONS/0237-a-change-is-judged-against-the-mesh-that-runs-before-it-merges-on-the-build-seat.md)):
@@ -0,0 +1,41 @@
---
status: open
opened: 2026-10-09
located-in: []
fixed-by:
amended-design:
---
# 357. What ADR 0273 left undecided has no owner
## Symptom
[ADR 0273](../../02-DECISIONS/0273-the-agents-home-is-the-modules-and-the-operators-personal-rules-and-lessons-are-registered-through-it.md)
(2026-10-09: the agent's home is the module's, and the operator's rules and lessons are registered through it)
says in its consequences that four things are not decided there, and names no issue, owner or date for any of
them. The check added with
[issue 354](../354-a-decided-rename-was-never-scheduled-and-the-operator-believed-it-live/00-report.md) found
them on the day the record was accepted. This issue is the pointer the record lacked; the record carries a
dated insight naming it.
1. **Clearing old copies of hand versions.** The agent module keeps the hand version of every file it undoes,
in its state, and "clearing old copies is not decided here, as ADR 0243 left it". Without it the module's
state only grows.
2. **A per-project lesson** — one bound to a repository rather than the operator.
3. **Lessons for an agent the mesh runs**, rather than the operator's own agent.
4. **A channel of the operator's own that can tell the operator from an agent** — what issue 340 calls
"once we build a UI" — which would become a third route for registering into the operator's home.
## Why it is an issue
A deferral nothing tracks reads as done (issue 354). Each of the four is a gap a reader of ADR 0273 can only
find by reading its consequences to the end; the first is also a store that grows without a bound.
## Open
- Whether each item needs a decision record of its own, or whether item 1 is a setting of the agent module
that its owner (the `claude-code` module in the catalogue) can decide in a change.
- Item 4 waits on the operator's channels (ADR 0259) and the controller's terminal; its owner is not known
until the channel is designed.
- `located-in:` is filled when each item's owner is confirmed. The item resolved first is struck through here
with its pull request, and the issue resolves when all four are decided or declined.