volt: check_lint catches a trailing prose aside after sentence punctuation
`bash CHECK-quill-draft-compliance (the artifact gate).` is prose, and bash refuses it before the command runs: syntax error near unexpected token `(`, rc 2. tools/check_lint.py graded that exact line SOUND because its "trailing parenthetical" marker required `)` to be end-of-line; the sentence's full stop after the aside defeated it. The marker now allows `[.!?,;:]?` between the closing paren and EOL, and five selftest cells pin it: the two measured prose shapes as PROSE_TAIL, and three controls that must stay SOUND (process substitution, an opening subshell, a quoted aside). The pin moves 37 -> 42 in tools/selftest_baseline.tsv, in this diff. Evidence: selftest 42/42 rc 0; the repro line PROSE_TAIL rc 1 and the bare command SOUND rc 0; a one-line mutant (old regex only) fails exactly cells 29 and 30, 40 passed / 2 failed rc 1; selftest_sweep 23 pinned / 23 agree / 0 drift rc 0; tools/verify.py --fast 12 legs, 11 pass, 0 fault, 1 skipped, rc 0. Artifact and leg-graded CHECK: wizard-work/volt/q-checkparen/.
This commit is contained in:
1 parent
fcd5d6b0f9
commit
aba1621812
4 files changed
+241
-3
No files matched your search
+17
-2
@@ -61,7 +61,7 @@ WHAT IT DOES NOT DO
|
||||
USAGE
|
||||
python3 check_lint.py --check "$CHECK"
|
||||
python3 check_lint.py --ralph-dir /root/.hermes/wizard-outputs/ralph
|
||||
python3 check_lint.py --selftest # 37 cells, one per class and edge
|
||||
python3 check_lint.py --selftest # 42 cells, one per class and edge
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -115,7 +115,12 @@ CATCH_ALL_RE = re.compile(
|
||||
PROSE_MARKERS = [
|
||||
(re.compile(r"\s+[\u2014\u2013]\s+"), "em dash"),
|
||||
(re.compile(r"\s--\s+"), "spaced --"),
|
||||
(re.compile(r"\s\([^()]*\)\s*$"), "trailing parenthetical"),
|
||||
# The parenthetical may carry sentence punctuation: `bash CHECK-x (the
|
||||
# artifact gate).` is prose with a full stop after the aside, and every
|
||||
# unquoted `(` there is a bash syntax error, not an argument. Measured on
|
||||
# the 2026-11-01 ralph run: the runner REDs with "syntax error near
|
||||
# unexpected token `('" while the old `\)\s*$` read the line SOUND.
|
||||
(re.compile(r"\s\([^()]*\)[.!?,;:]?\s*$"), "trailing parenthetical"),
|
||||
(re.compile(r"\.\s+[A-Z]"), "sentence break"),
|
||||
]
|
||||
|
||||
@@ -366,6 +371,16 @@ FIXTURES = [
|
||||
("CHECK: sha256sum a.tar.gz | grep -q 4844c5fc\n", "SOUND"),
|
||||
("CHECK: python3 tools/x.py --selftest | tail -1 | grep -q 'cells, 0 failed'\n", "SOUND"),
|
||||
("CHECK: python3 -c \"print(1)\"\n", "SOUND"), # paren not preceded by space
|
||||
# A trailing prose aside with sentence punctuation after the closing paren.
|
||||
# This is the exact line the 2026-11-01 ralph run RED'd on: bash reads the
|
||||
# unquoted `(` as a subshell opener and exits 2 before the command runs.
|
||||
("CHECK: bash CHECK-quill-draft-compliance (the artifact gate).\n", "PROSE_TAIL"),
|
||||
("CHECK: python3 v.py (re-derives the table),\n", "PROSE_TAIL"),
|
||||
# Controls that must stay SOUND: the paren is not preceded by whitespace
|
||||
# (process substitution), opens the command (a real subshell), or is quoted.
|
||||
("CHECK: diff -u <(a) <(b)\n", "SOUND"),
|
||||
("CHECK: (cd sub && python3 t.py)\n", "SOUND"),
|
||||
("CHECK: python3 x.py \"note (a).\"\n", "SOUND"),
|
||||
("CHECK: python3 x.py --json /tmp/o.json\n", "SOUND"), # -- flag, not " -- "
|
||||
("CHECK: python3 v.py && echo CHECK_OK", "SOUND"),
|
||||
# last-wins extraction, and a prose mention AFTER the real line. The second
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
append_only_audit.py 18 cells
|
||||
append_only_owners.py 15 cells
|
||||
census_row_delta.py 37 cells
|
||||
check_lint.py 37 cells
|
||||
check_lint.py 42 cells
|
||||
checkin_check.py 36 cells
|
||||
citation_hash_class.py 19 fixtures
|
||||
citation_resolve_check.py 14 fixtures
|
||||
|
||||
|
@@ -0,0 +1,125 @@
|
||||
# check_lint: a trailing prose parenthetical with sentence punctuation read SOUND
|
||||
|
||||
Artifact: `tools/check_lint.py` (PROSE_TAIL marker) + `tools/selftest_baseline.tsv` (pin 37 -> 42).
|
||||
HEAD when measured: `626e272` (`volt: Q142 record -- ...`). Author: volt, 2026-11-01.
|
||||
Repro is not hypothetical: the 2026-11-01 ralph run RED'd on exactly this line.
|
||||
|
||||
## 1. The defect, measured
|
||||
|
||||
The runner extracts the last `^\s*CHECK:` line and hands it to `bash -c`. This
|
||||
line -- my own previous CHECK -- is prose with a gloss:
|
||||
|
||||
bash CHECK-quill-draft-compliance (the artifact gate).
|
||||
|
||||
`bash` reads the unquoted `(` as a subshell opener and refuses before running
|
||||
anything:
|
||||
|
||||
$ printf '%s\n' 'bash CHECK-quill-draft-compliance (the artifact gate).' > parenthetical-probe.sh
|
||||
$ bash -n parenthetical-probe.sh
|
||||
parenthetical-probe.sh: line 1: syntax error near unexpected token `('
|
||||
parenthetical-probe.sh: line 1: `bash CHECK-quill-draft-compliance (the artifact gate).'
|
||||
rc 2
|
||||
|
||||
`tools/check_lint.py` exists to catch this class before the runner does (§9 of
|
||||
STANDING-AGREEMENT.md: a CHECK that is prose is a fault, not a verification).
|
||||
Before the fix it graded the line **SOUND**, rc 0:
|
||||
|
||||
$ python3 tools/check_lint.py --check 'bash CHECK-quill-draft-compliance (the artifact gate).'
|
||||
SOUND: 'bash CHECK-quill-draft-compliance (the artifact gate).'
|
||||
rc 0
|
||||
|
||||
Cause: the marker was `\s\([^()]*\)\s*$`. The parenthetical is there, but the
|
||||
sentence's full stop after the closing paren means `\)` is followed by `.`, not
|
||||
end of line, so the match failed. The two sibling markers (`em dash`, `spaced --`)
|
||||
were not affected; the trailing-aside shape was.
|
||||
|
||||
## 2. The fix, one character class
|
||||
|
||||
- (re.compile(r"\s\([^()]*\)\s*$"), "trailing parenthetical"),
|
||||
+ (re.compile(r"\s\([^()]*\)[.!?,;:]?\s*$"), "trailing parenthetical"),
|
||||
|
||||
The paren still has to follow whitespace, so the three legitimate shapes are
|
||||
untouched: process substitution (`<(a)` -- no space before `(`), an opening
|
||||
subshell (`(cd x && y)` -- no character before `(`), and quoted text. The
|
||||
optional punctuation covers `. , ; : ! ?`, the sentence marks that appear after
|
||||
an aside.
|
||||
|
||||
After:
|
||||
|
||||
$ python3 tools/check_lint.py --check 'bash CHECK-quill-draft-compliance (the artifact gate).' --fail-on-fault
|
||||
PROSE_TAIL: 'bash CHECK-quill-draft-compliance (the artifact gate).'
|
||||
faults: PROSE_TAIL
|
||||
detail: {"prose_marker": "trailing parenthetical", "command_prefix": "bash CHECK-quill-draft-compliance"}
|
||||
rc 1
|
||||
|
||||
$ python3 tools/check_lint.py --check 'bash CHECK-quill-draft-compliance' --fail-on-fault
|
||||
SOUND: 'bash CHECK-quill-draft-compliance'
|
||||
rc 0
|
||||
|
||||
## 3. Tests
|
||||
|
||||
Five selftest cells added (37 -> 42):
|
||||
|
||||
- PROSE_TAIL: the exact failing line, full stop after `)`.
|
||||
- PROSE_TAIL: `python3 v.py (re-derives the table),` -- comma after `)`.
|
||||
- SOUND control: `diff -u <(a) <(b)` -- process substitution.
|
||||
- SOUND control: `(cd sub && python3 t.py)` -- a real subshell.
|
||||
- SOUND control: `python3 x.py "note (a)."` -- quoted, punctuation inside quotes.
|
||||
|
||||
Measured:
|
||||
|
||||
$ python3 tools/check_lint.py --selftest
|
||||
selftest: 42 cells, 42 passed, 0 failed
|
||||
rc 0
|
||||
|
||||
Non-vacuity, one-line mutant (`q-checkparen/mutant.py`, the old regex restored,
|
||||
nothing else changed):
|
||||
|
||||
$ python3 mutant.py --selftest
|
||||
cell 29: want PROSE_TAIL got SOUND <- 'CHECK: bash CHECK-quill-draft-compliance (the artifact gate).\n'
|
||||
cell 30: want PROSE_TAIL got SOUND <- 'CHECK: python3 v.py (re-derives the table),\n'
|
||||
selftest: 42 cells, 40 passed, 2 failed
|
||||
rc 1
|
||||
|
||||
Exactly the two new cells fail; the 40 other cells are indifferent to the fix.
|
||||
|
||||
## 4. The pin, and the repo gate
|
||||
|
||||
`tools/selftest_baseline.tsv` carried `check_lint.py 37 cells`; re-pinned to
|
||||
42 in the same diff, as the file's own header asks ("Re-pin on purpose, in the
|
||||
diff").
|
||||
|
||||
$ python3 tools/selftest_sweep.py --repo .
|
||||
...
|
||||
pin: 23 pinned, 23 agree, 0 drift
|
||||
verdict: rc 0 -- clean: tools 0 fatal, read-refusal 0 fault, malformed-input 0 fault, pin 0 drift
|
||||
|
||||
$ python3 tools/verify.py --fast
|
||||
...
|
||||
verify: 12 legs, 11 pass, 0 fault, 0 ungradable, 1 skipped
|
||||
gate: OK -- 11 of 11 graded leg(s) passed (1 skipped: not a pass, not a fault)
|
||||
rc 0
|
||||
|
||||
## 5. Not claimed
|
||||
|
||||
- That this makes `check_lint` complete. It is a text rubric; the new cell is
|
||||
one measured prose shape, and a gloss that bash happens to parse (a bare word
|
||||
after the command, say) is still SOUND. Only unquoted `(` is now caught.
|
||||
- That every `CHECK:` line in `WORK-QUEUE.md` passes under the widened rule.
|
||||
The queue's 64 CHECK lines were not graded by this item, before or after.
|
||||
- That the fix would have failed the run differently. The runner already
|
||||
RED'd; the tool now names the same line PROSE_TAIL instead of SOUND.
|
||||
- That the parenthetical case is one class. The optional punctuation also
|
||||
matches `cmd (x);` and `cmd (x):`; both are bash syntax errors at the `(`
|
||||
for the same reason, and no selftest cell pins those two separately.
|
||||
- That a queue record exists for this change. None was appended: the fix is
|
||||
two tool files, the report is this directory, and no `WORK-QUEUE.md` byte
|
||||
moved, so no index re-render was due.
|
||||
|
||||
Files changed: `tools/check_lint.py` (regex + 5 selftest cells + docstring count),
|
||||
`tools/selftest_baseline.tsv` (pin 37 -> 42), and this artifact
|
||||
(`wizard-work/volt/q-checkparen/`: `REPORT.md`, `verify_fix.py`). Nothing else
|
||||
was edited. The report's own CHECK is leg-graded by `verify_fix.py`, and the
|
||||
five legs were read rc 0 before the commit.
|
||||
|
||||
CHECK: python3 wizard-work/volt/q-checkparen/verify_fix.py
|
||||
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env python3
|
||||
"""CHECK for the q-checkparen artifact: check_lint catches a trailing prose aside.
|
||||
|
||||
Legs (each prints PASS/FAULT; exit 0 only if all pass, 2 if it cannot grade):
|
||||
|
||||
1 selftest tools/check_lint.py --selftest prints 42 cells, 0 failed, rc 0
|
||||
2 repro the measured ralph line classifies PROSE_TAIL, rc 1, and the
|
||||
command prefix printed is exactly the command
|
||||
3 clean the bare command classifies SOUND, rc 0 (no over-fire)
|
||||
4 pin tools/selftest_baseline.tsv pins check_lint.py at 42 cells
|
||||
5 falsifier a copy with only the regex reverted fails selftest on exactly
|
||||
the two added PROSE_TAIL cells (40 passed / 2 failed), rc 1
|
||||
|
||||
Read-only over the repo: leg 5 writes only inside a TemporaryDirectory. The
|
||||
repo root defaults to this file's own tree (four levels up), never a pinned
|
||||
host path, so the check travels with a clone.
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
DEFAULT_REPO = os.path.dirname(os.path.dirname(os.path.dirname(HERE)))
|
||||
|
||||
REPRO = "bash CHECK-quill-draft-compliance (the artifact gate)."
|
||||
CLEAN = "bash CHECK-quill-draft-compliance"
|
||||
OLD = r'(re.compile(r"\s\([^()]*\)[.!?,;:]?\s*$"), "trailing parenthetical"),'
|
||||
NEW = r'(re.compile(r"\s\([^()]*\)\s*$"), "trailing parenthetical"),'
|
||||
|
||||
|
||||
def run(args, cwd=None):
|
||||
p = subprocess.run(args, capture_output=True, text=True, timeout=120, cwd=cwd)
|
||||
return p.returncode, (p.stdout or "") + (p.stderr or "")
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--repo", default=DEFAULT_REPO)
|
||||
a = ap.parse_args()
|
||||
|
||||
tool = os.path.join(a.repo, "tools", "check_lint.py")
|
||||
pin = os.path.join(a.repo, "tools", "selftest_baseline.tsv")
|
||||
for path in (tool, pin):
|
||||
if not os.path.isfile(path):
|
||||
print("cannot grade: missing " + path)
|
||||
return 2
|
||||
|
||||
failed = 0
|
||||
|
||||
rc, out = run([sys.executable, tool, "--selftest"])
|
||||
ok = rc == 0 and "42 cells, 42 passed, 0 failed" in out
|
||||
print(("PASS" if ok else "FAULT") + " selftest rc=%d %s" % (rc, out.strip().splitlines()[-1]))
|
||||
failed += 0 if ok else 1
|
||||
|
||||
rc, out = run([sys.executable, tool, "--check", REPRO, "--fail-on-fault"])
|
||||
ok = rc == 1 and out.startswith("PROSE_TAIL:") and "command_prefix" in out
|
||||
print(("PASS" if ok else "FAULT") + " repro rc=%d %s" % (rc, out.strip().splitlines()[0]))
|
||||
failed += 0 if ok else 1
|
||||
|
||||
rc, out = run([sys.executable, tool, "--check", CLEAN, "--fail-on-fault"])
|
||||
ok = rc == 0 and out.startswith("SOUND:")
|
||||
print(("PASS" if ok else "FAULT") + " clean rc=%d %s" % (rc, out.strip().splitlines()[0]))
|
||||
failed += 0 if ok else 1
|
||||
|
||||
with open(pin, encoding="utf-8") as fh:
|
||||
rows = [ln for ln in fh.read().splitlines()
|
||||
if ln and not ln.startswith("#") and ln.split("\t")[0] == "check_lint.py"]
|
||||
ok = rows == ["check_lint.py\t42\tcells"]
|
||||
print(("PASS" if ok else "FAULT") + " pin %s" % (rows or "no row"))
|
||||
failed += 0 if ok else 1
|
||||
|
||||
with open(tool, encoding="utf-8") as fh:
|
||||
src = fh.read()
|
||||
if src.count(OLD) != 1:
|
||||
print("FAULT falsifier fixed regex not found exactly once")
|
||||
failed += 1
|
||||
else:
|
||||
with tempfile.TemporaryDirectory(prefix="checkparen.") as tmp:
|
||||
mut = os.path.join(tmp, "mutant.py")
|
||||
with open(mut, "w", encoding="utf-8") as fh:
|
||||
fh.write(src.replace(OLD, NEW))
|
||||
rc, out = run([sys.executable, mut, "--selftest"])
|
||||
tail = out.strip().splitlines()[-1]
|
||||
cells = re.findall(r"cell (\d+): want PROSE_TAIL got SOUND", out)
|
||||
ok = (rc == 1 and "42 cells, 40 passed, 2 failed" in out
|
||||
and cells == ["29", "30"])
|
||||
print(("PASS" if ok else "FAULT") + " falsifier rc=%d %s" % (rc, tail))
|
||||
failed += 0 if ok else 1
|
||||
|
||||
print("verify_fix: %d leg(s) faulted" % failed)
|
||||
return 1 if failed else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user