tools (volt): reply_check learns the reported-interview form, so the gate's reply-headings leg is green again (41 -> 49 cells)

Timmy's c46d7dd filed replies/2026-10-19-to-zephyrus-interview.md, whose name
carries no number by construction. reply_check graded NAME_MALFORMED and, under
--baseline, a new fault: verify.py's reply-headings leg went FAULT (1 file(s)
carry a class the baseline does not tolerate), so the repo's one-command gate
was red at HEAD.

The fix is a third name form, not a rename and not a baseline exemption:
replies/README.md's numbered contract is about replies, and an interview is not
one. INTERVIEW_RE accepts YYYY-MM-DD-to-<recipient>-interview.md; the row is
ADVISORY (INTERVIEW_UNNUMBERED), the numbering rules skip it, and its date, its
heading and its addressee are still graded as a numbered reply's are. A
misspelled slug stays NAME_MALFORMED.

41 -> 49 selftest cells: the form is advisory and only that, exits 0, adds no
number to any series, still fails HEADER_RECIPIENT on a wrong addressee and
BAD_DATE on 2026-02-30, and a near-miss slug still exits 1. Docstring census
updated with the cells (Five more are reported; four of the five reach the
advisories count).

--repo . --baseline tools/reply_baseline.tsv: 196 file(s), 0 new fault(s), 1
known-faulting, rc 0. Bare run rc 1 with faults: 1, advisories: 20 (194 + 6).
This commit is contained in:
volt committed 2026-10-19 23:16:42 +00:00
1 parent c46d7dde33
commit 2f32c23976
2 files changed
+75 -16

No files matched your search

+6
View File
@@ -5,3 +5,9 @@ addresses and `<N>` is the next free integer in that recipient's own series. The
inside the file, and the commit carries them. Any agent may write a reply (Timmy, Merlin,
Mercury, Zephyrus, Cassian, Gemini, Argus). Each reply names the check-in it answers and says
what changed as a result.
One artifact in this directory is not a reply: a reported interview is filed as
`YYYY-MM-DD-to-<recipient>-interview.md`, with `-interview` where the number goes. It has no
number by construction, so `tools/reply_check.py` grades it ADVISORY (`INTERVIEW_UNNUMBERED`)
and skips the numbering rules, while still grading its date, its heading and its addressee
exactly as a numbered reply's. Any other non-numeric suffix is still `NAME_MALFORMED`.
+69 -16
View File
@@ -22,9 +22,10 @@ WHAT IT MEASURES (read-only; the only write path is --json)
file says about itself. Seven classes are graded:
NAME_MALFORMED the filename is neither the numbered contract form nor
the one unnumbered legacy form -- the row is skipped
by the header rules, because there is no name to check
it against.
either of the two unnumbered forms (the legacy file,
the reported interview) -- the row is skipped by the
header rules, because there is no name to check it
against.
BAD_DATE the date half is not a real calendar date.
NO_HEADER no `# ` heading in the first --head-lines lines.
UNREADABLE the file is not UTF-8 text, so no heading can be read
@@ -39,14 +40,19 @@ WHAT IT MEASURES (read-only; the only write path is --json)
FAULT: "the next free integer in that recipient's own
series" cannot produce the same integer twice.
Four more are reported and printed, and are ADVISORY by construction -- each
Five more are reported and printed, and are ADVISORY by construction -- each
is a stated consequence of history that predates the naming rule, not a
defect this tool can attribute to a writer. Three of the four reach the
defect this tool can attribute to a writer. Four of the five reach the
`advisories: N` count; HOLE rides the `series:` line instead, because a hole
belongs to a recipient's series and not to any one file:
LEGACY_UNNUMBERED the one legacy form (no `-<N>`). Its number is implicit
in its position, so numbering rules skip it.
INTERVIEW_UNNUMBERED the reported-interview form, `-interview` in the
number's place. It is not a reply and carries no
number by construction, so the numbering rules skip it
-- but its date, its heading and its addressee are
still graded exactly as a numbered reply's are.
NO_RECIPIENT_IN_HEADING a heading that names no addressee at all (the
early "`# Reply 14 -- Q31 ...`" house style). Nothing
contradicts the filename; the heading is silent.
@@ -88,14 +94,16 @@ BASELINE -- the archive's one fault, stated instead of hidden
The ledger names the tolerance, and the row stays visible in every report.
SELFTEST
`--selftest` grades 41 cells, building every fixture in a fresh temporary
`--selftest` grades 49 cells, building every fixture in a fresh temporary
directory: each fault class fires on a fixture that carries it and only the
classes that belong there (a wrong addressee, a wrong number, a duplicate
number, an impossible date, a missing heading, a malformed name, non-UTF-8
bytes), the advisory classes stay advisory and exit 0 (legacy form, hole,
heading that names nobody), the prefix match accepts a hyphenated handle,
README.md is not a row, and both rc 2 paths (missing directory, file where a
directory belongs) answer 2. Four cells grade this docstring's own census --
bytes), the advisory classes stay advisory and exit 0 (legacy form, interview
form, hole, heading that names nobody), the prefix match accepts a hyphenated
handle, the interview form still fails on a wrong addressee or an impossible
date and a misspelled `-interview` is still NAME_MALFORMED, README.md is not
a row, and both rc 2 paths (missing directory, file where a directory
belongs) answer 2. Four cells grade this docstring's own census --
the two spelled class counts against FAULTS/ADVISORIES, every graded class
named here, and the pre-fix words refused on a fixture. A rule that stops
firing and a rule that fires everywhere both fail a cell.
@@ -113,6 +121,7 @@ import tempfile
NAME_RE = re.compile(r"^(\d{4})-(\d{2})-(\d{2})-to-([a-z][a-z0-9]*)-(\d{2,})\.md$")
LEGACY_RE = re.compile(r"^(\d{4})-(\d{2})-(\d{2})-to-([a-z][a-z0-9]*)\.md$")
INTERVIEW_RE = re.compile(r"^(\d{4})-(\d{2})-(\d{2})-to-([a-z][a-z0-9]*)-interview\.md$")
HEADING_TO = re.compile(r"\bto\s+([A-Za-z][A-Za-z0-9-]*)")
HEADING_ARROW = re.compile(r"(?:→|->)\s*([A-Za-z][A-Za-z0-9-]*)")
HEADING_NUM = re.compile(r"\breply\s+(\d{1,4})\b", re.IGNORECASE)
@@ -120,8 +129,8 @@ ROSTER_HANDLE_RE = re.compile(r"^[a-z][a-z0-9]*$")
FAULTS = ("NAME_MALFORMED", "BAD_DATE", "NO_HEADER", "UNREADABLE",
"HEADER_RECIPIENT", "HEADER_NUMBER", "DUPLICATE_NUMBER")
ADVISORIES = ("LEGACY_UNNUMBERED", "NO_RECIPIENT_IN_HEADING",
"NO_NUMBER_IN_HEADING", "HOLE")
ADVISORIES = ("LEGACY_UNNUMBERED", "INTERVIEW_UNNUMBERED",
"NO_RECIPIENT_IN_HEADING", "NO_NUMBER_IN_HEADING", "HOLE")
WORD_NUMBERS = {"one": 1, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6,
"seven": 7, "eight": 8, "nine": 9, "ten": 10, "eleven": 11,
@@ -277,8 +286,8 @@ def scan(dirpath, head_lines=10, roster=()):
"""(rows, faults, advisories, series, holes) for one replies directory.
rows: one dict per file, in sorted filename order, each with `file`,
`date`, `recipient`, `number` (None for the legacy form), `heading`, and
`classes` (a list). Nothing is written.
`date`, `recipient`, `number` (None on both unnumbered forms), `heading`,
and `classes` (a list). Nothing is written.
"""
rows = []
for name in sorted(os.listdir(dirpath)):
@@ -290,23 +299,30 @@ def scan(dirpath, head_lines=10, roster=()):
row = {"file": name, "date": None, "recipient": None, "number": None,
"heading": None, "classes": []}
m = NAME_RE.match(name)
legacy = False
legacy = interview = False
if m:
y, mo, d, row["recipient"], num = m.groups()
row["number"] = int(num)
else:
m = LEGACY_RE.match(name)
if m:
y, mo, d, row["recipient"] = m.groups()
legacy = True
else:
m = INTERVIEW_RE.match(name)
if not m:
row["classes"].append("NAME_MALFORMED")
rows.append(row)
continue
y, mo, d, row["recipient"] = m.groups()
legacy = True
interview = True
row["date"] = "%s-%s-%s" % (y, mo, d)
if not _real_date(y, mo, d):
row["classes"].append("BAD_DATE")
if legacy:
row["classes"].append("LEGACY_UNNUMBERED")
if interview:
row["classes"].append("INTERVIEW_UNNUMBERED")
heading = read_heading(path, head_lines)
unreadable = heading is False
row["heading"] = heading if isinstance(heading, str) else None
@@ -588,6 +604,43 @@ def selftest():
classes(d, "2026-09-24-to-zephyrus.md") == ["LEGACY_UNNUMBERED"])
cell("legacy form exits 0", rc(["--dir", d]) == 0)
# 7b the reported-interview form: no number by construction, so the
# numbering rules skip it -- but everything a name can still contradict
# (its date, its addressee) is graded exactly as on a numbered reply.
d = os.path.join(root, "interview")
os.makedirs(d)
_write(d, "2026-10-19-to-zephyrus-interview.md",
"# Interview to zephyrus -- 2026-10-19\n\nbody\n")
cell("interview form is advisory, and only that",
classes(d, "2026-10-19-to-zephyrus-interview.md") == ["INTERVIEW_UNNUMBERED"])
cell("interview form exits 0", rc(["--dir", d]) == 0)
_, _, _, series_i, holes_i = scan(d)
cell("interview form contributes no number to any series",
series_i == {} and holes_i == {})
d = os.path.join(root, "interview-wrongparty")
os.makedirs(d)
_write(d, "2026-10-19-to-zephyrus-interview.md",
"# Interview to Merlin -- 2026-10-19\n")
cell("interview heading still grades its addressee",
classes(d, "2026-10-19-to-zephyrus-interview.md") ==
["INTERVIEW_UNNUMBERED", "HEADER_RECIPIENT"])
cell("interview with the wrong addressee exits 1", rc(["--dir", d]) == 1)
d = os.path.join(root, "interview-baddate")
os.makedirs(d)
_write(d, "2026-02-30-to-zephyrus-interview.md", "# Interview to zephyrus\n")
cell("interview form still grades its date",
classes(d, "2026-02-30-to-zephyrus-interview.md") ==
["BAD_DATE", "INTERVIEW_UNNUMBERED"])
d = os.path.join(root, "interview-nearmiss")
os.makedirs(d)
_write(d, "2026-10-19-to-zephyrus-inteview.md", "# Interview to zephyrus\n")
cell("a misspelled interview slug is still NAME_MALFORMED",
classes(d, "2026-10-19-to-zephyrus-inteview.md") == ["NAME_MALFORMED"])
cell("a misspelled interview slug exits 1", rc(["--dir", d]) == 1)
# 8 a real-calendar check, not a shape check
d = os.path.join(root, "baddate")
os.makedirs(d)