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
|
||||
|
||||
@@ -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
|
||||
|
||||
+6
@@ -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 |
|
||||
|
||||
+8
-6
@@ -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.
|
||||
Reference in New Issue
Block a user