Files
mos-dt-0andMos a4280b9c98
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
fix(wake): #984 fatal source guard + #985 absorb re-scan — #973 follow-up batch (#1001)
Co-authored-by: mos-dt-0 <[email protected]>
2026-07-31 10:16:22 +00:00

319 lines
13 KiB
Python
Executable File

#!/usr/bin/env python3
"""check-973.py — computation backend for the #973 validation harness.
Everything here derives from exactly two inputs: the frozen denominator
artifact (denominator-089615f.json) and the SOURCE TEXT of the ten converted
suites at the current tree. It never reads the ledger — set arithmetic against
the runtime trace belongs to validate-973.sh, so the two legs of the
comparison come from independent code paths.
Subcommands (all print sorted, stable output; non-zero exit on any failure):
expected The expected coordinate set from the ARTIFACT: one
"<helper> <file>:<line+7>" row per denominator row (+7 = 3
converter header lines + 4 lines from the #984 source guard,
uniform across all ten suites).
Multi-grep lines stay ONE coordinate.
static The converted-site inventory from the SOURCE TEXT at the current
tree: every non-comment line bearing a has_match/count_lines
token, as "<helper> <file>:<line>". Independent of the artifact
row list, so `expected == static` is a real check on the
conversion, not a tautology. (Amendment ONE, leg 1: the ledger is
an execution trace, not an inventory — the inventory must come
from the text.)
arms The forced-error arm list: the 19 denominator canaries plus one
E-form arm (store-ack:733→740, a $(count_lines) capture compared
afterward — the A6 shape) plus one F-form arm (quarantine:560→567,
the multi-grep pipeline capture), as "<helper> <file>:<line+7>
<form>". Both extras are asserted to exist in the artifact with
the expected form — a renumber that moved them fails here, not
silently downstream.
sweep Residual sweep: the denominator's own classifier (ported from the
frozen derivation) over the ten suites at the current tree must
find ZERO unconverted verdict-form grep sites; and, IN THE SAME
RUN, eight specimens (six per-form + two absorb-branch probes, #985)
planted into a temp copy of a real
suite must ALL be found with their correct forms — an instrument
that reports zero must first be seen finding what it claims to
find (A5).
"""
import json
import re
import shutil
import sys
import tempfile
from pathlib import Path
HERE = Path(__file__).resolve().parent
WAKE = HERE.parent
ART = HERE / "denominator-089615f.json"
HEADER_SHIFT = 7 # 3 converter header lines after SCRIPT_DIR + 4 lines from the
# #984 source guard (1-line `. _wake-common.sh && wake_assert_init` became a 5-line
# guarded block) — both uniform across all ten suites, both above every site.
# The two hand-picked extra arms (base coordinates; forms asserted at load).
EXTRA_ARMS = [
("test-wake-store-ack.sh", 733, "E-count-capture"),
("test-wake-digest-quarantine.sh", 560, "F-extract-capture"),
]
RX_HELPER = re.compile(r"(^|[^A-Za-z0-9_.-])(has_match|count_lines)([^A-Za-z0-9_.-]|$)")
# ---- classifier, ported verbatim in logic from the frozen denominator
# ---- derivation (docs/journal/fleet/drift-derive-089615f__pepper.py)
RX_FAIL_SAME = re.compile(r"(\|\||&&)\s*fail")
RX_COUNT_SUB = re.compile(r"\$\(.*grep\s+[^)]*-c|\$\(\s*grep\s+-c")
RX_ASSIGN_SUB = re.compile(r'=\s*"?\$\(.*grep')
RX_IF = re.compile(r"^\s*(el)?if\s+.*grep")
RX_GREP = re.compile(r"(^|[^A-Za-z0-9_.-])grep([^A-Za-z0-9_.-]|$)")
# grep in COMMAND position: at line start or after a command separator / subshell
# opener / shell keyword / `!`. Quote-unaware by design — a quoted "grep" after a
# separator reads as a command and lands the line in residual, which fails LOUD;
# the absorb direction (note) is the one that must never fire on a real verdict.
RX_GREP_CMD = re.compile(
r"(?:^|[;|&(`]|\$\(|\bif\b|\belif\b|\bthen\b|\belse\b|\bdo\b|\bwhile\b|\buntil\b|!)"
r"\s*grep(?:\s|$)"
)
def classify(lines):
"""Return (sites, dispo). Every line containing the word grep gets a row."""
sites, dispo = [], []
n = len(lines)
for i, raw in enumerate(lines):
line = raw
ln = i + 1
if not RX_GREP.search(line):
continue
stripped = line.strip()
if stripped.startswith("#"):
dispo.append((ln, "comment", stripped))
continue
nxt = ""
for j in range(i + 1, min(i + 3, n)):
if lines[j].strip():
nxt = lines[j].strip()
break
if "$(" in line and RX_COUNT_SUB.search(line):
sites.append((ln, "E-count-capture", stripped))
continue
if RX_ASSIGN_SUB.search(line) and "grep -c" not in line:
sites.append((ln, "F-extract-capture", stripped))
continue
if RX_FAIL_SAME.search(line):
sites.append((ln, "A-same-line", stripped))
continue
if stripped.endswith("\\"):
k = i + 1
joined = stripped[:-1]
while k < n:
cont = lines[k].strip()
joined += " " + (cont[:-1] if cont.endswith("\\") else cont)
if not cont.endswith("\\"):
break
k += 1
if re.search(r"(\|\||&&)\s*fail", joined) or (
joined.rstrip().endswith(("||", "&&"))
and k + 1 < n
and lines[k + 1].strip().startswith("fail")
):
sites.append((ln, "C-cont-backslash", stripped))
continue
dispo.append((ln, "backslash-no-fail-continuation", stripped))
continue
if stripped.endswith(("||", "&&")) and nxt.startswith("fail"):
sites.append((ln, "B-cont-operator", stripped))
continue
if RX_IF.search(line):
window = " ".join(lines[j] for j in range(i, min(i + 5, n)))
if "fail" in window:
sites.append((ln, "D-if-form", stripped))
continue
dispo.append((ln, "if-grep-no-fail-window", stripped))
continue
win = " ".join(lines[j] for j in range(max(0, i - 2), min(i + 3, n)))
if re.search(r"fail", win, re.I):
dispo.append((ln, "BACKSTOP-HAND-REVIEW", stripped))
else:
dispo.append((ln, "no-verdict-context", stripped))
return sites, dispo
def residual_sites(lines):
"""classify() plus the absorb decision — the ONE path both sweep legs share.
A classified site is absorbed as a note only when its line carries a wake
helper token AND the line shows no grep in command position: a converted
line whose PATTERN argument merely contains the word grep. A helper line
that also runs a real grep verdict (has_match ... && grep -q SECRET ... &&
fail) stays residual (#985). Multi-line forms anchor the site at the line
containing grep, so a command-position grep on a continuation line never
shares its line with the helper token and stays residual by construction.
"""
sites, dispo = classify(lines)
residual, notes = [], []
for ln, form, text in sites:
line = lines[ln - 1]
if RX_HELPER.search(line) and not RX_GREP_CMD.search(line):
notes.append((ln, form, text))
else:
residual.append((ln, form, text))
return residual, notes, dispo
def load_art():
art = json.loads(ART.read_text())
assert art["total"] == 261 == len(art["rows"]), "artifact self-consistency"
return art
def helper_for(row):
return "count_lines" if row["form"].startswith("E") else "has_match"
def suite_files(art):
return sorted({r["file"] for r in art["rows"]})
def cmd_expected():
art = load_art()
out = sorted(
f"{helper_for(r)} {r['file']}:{r['line'] + HEADER_SHIFT}" for r in art["rows"]
)
assert len(out) == len(set(out)) == 261, "expected set must be 261 distinct rows"
print("\n".join(out))
return 0
def cmd_static():
art = load_art()
rows = []
for f in suite_files(art):
for i, line in enumerate((WAKE / f).read_text().split("\n"), start=1):
if line.strip().startswith("#"):
continue
m = RX_HELPER.search(line)
if not m:
continue
helper = (
"count_lines"
if RX_HELPER.search(line).group(2) == "count_lines"
else "has_match"
)
rows.append(f"{helper} {f}:{i}")
print("\n".join(sorted(rows)))
return 0
def cmd_arms():
art = load_art()
by_key = {(r["file"], r["line"]): r for r in art["rows"]}
rows = []
canaries = [r for r in art["rows"] if r.get("canary")]
assert len(canaries) == 19, f"expected 19 canaries, artifact has {len(canaries)}"
for r in canaries:
rows.append(f"{helper_for(r)} {r['file']}:{r['line'] + HEADER_SHIFT} {r['form']}")
for f, ln, want_form in EXTRA_ARMS:
r = by_key.get((f, ln))
assert r is not None, f"extra arm {f}:{ln} not in artifact — renumbered?"
assert r["form"] == want_form, f"extra arm {f}:{ln} form {r['form']} != {want_form}"
rows.append(f"{helper_for(r)} {f}:{ln + HEADER_SHIFT} {r['form']}")
assert len(rows) == 21
print("\n".join(rows))
return 0
# (expected classify form, expected disposition through residual_sites, snippet)
PLANTS = [
("A-same-line", "residual", ['grep -q needle haystack || fail "plant-A"']),
("B-cont-operator", "residual", ["grep -q needle haystack ||", ' fail "plant-B"']),
("C-cont-backslash", "residual", ["grep -q needle \\", ' haystack || fail "plant-C"']),
("D-if-form", "residual", ["if ! grep -q needle haystack; then", ' fail "plant-D"', "fi"]),
("E-count-capture", "residual", ['[ "$(grep -c needle haystack)" = "1" ] || fail "plant-E"']),
("F-extract-capture", "residual", ['val="$(grep needle haystack)"']),
# G: a converted line that ALSO runs a raw grep verdict — the helper token
# must not absorb it (#985)
("A-same-line", "residual", ['has_match -q needle "$F" && grep -q SECRET "$F" && fail "plant-G"']),
# H: negative control — helper whose PATTERN argument is the word grep;
# must be absorbed as a note, never residual
("A-same-line", "note", ['has_match -q "grep" haystack || fail "plant-H"']),
]
def cmd_sweep():
art = load_art()
bad = 0
# leg 1: real suites at the current tree must be residual-free
for f in suite_files(art):
residual, notes, _dispo = residual_sites((WAKE / f).read_text().split("\n"))
for ln, form, text in notes:
# converted line whose PATTERN argument contains the word grep:
# not an unconverted site, but never silently absorbed either
print(f"SWEEP-NOTE {f}:{ln} converted line matches grep-token ({form}): {text[:80]}")
for ln, form, text in residual:
print(f"SWEEP-RESIDUAL {f}:{ln} {form}: {text[:100]}")
bad += 1
print(f"SWEEP {f}: {len(residual)} residual verdict site(s)")
# leg 2, SAME RUN, SAME PATH as leg 1: the instrument must find every plant
# with the right form AND the right absorb disposition — plants G/H exercise
# the absorb branch itself, so this leg must go through residual_sites(),
# not raw classify()
donor = suite_files(art)[0]
with tempfile.TemporaryDirectory() as td:
planted = Path(td) / donor
shutil.copy(WAKE / donor, planted)
base_lines = planted.read_text().split("\n")
offset = len(base_lines)
expect = {}
for form, dispo, snippet in PLANTS:
expect[offset + 1] = (form, dispo) # first physical line of each plant
base_lines.extend(snippet)
offset = len(base_lines)
planted.write_text("\n".join(base_lines))
residual, notes, _ = residual_sites(planted.read_text().split("\n"))
found = {ln: (form, "residual") for ln, form, _t in residual}
found.update({ln: (form, "note") for ln, form, _t in notes})
unexpected = [(ln, form) for ln, form, _t in residual if ln not in expect]
hits = sum(1 for ln, want in expect.items() if found.get(ln) == want)
n_plants = len(PLANTS)
print(f"SWEEP-PLANTS found={hits}/{n_plants} in planted copy of {donor}")
if hits != n_plants:
for ln, want in sorted(expect.items()):
got = found.get(ln, ("<missed>", "<missed>"))
if got != want:
print(f"SWEEP-PLANT-MISS line {ln}: expected {want}, got {got}")
bad += 1
if unexpected:
# the donor is a converted suite: any non-plant RESIDUAL site in the
# copy contradicts the zero leg 1 just reported on the original
# (non-plant notes mirror leg 1's treatment: printed there, not bad)
for ln, form in unexpected:
print(f"SWEEP-PLANT-UNEXPECTED {donor}(copy):{ln} {form}")
bad += 1
return 1 if bad else 0
def main():
cmds = {
"expected": cmd_expected,
"static": cmd_static,
"arms": cmd_arms,
"sweep": cmd_sweep,
}
if len(sys.argv) != 2 or sys.argv[1] not in cmds:
sys.exit(f"usage: check-973.py {{{'|'.join(cmds)}}}")
sys.exit(cmds[sys.argv[1]]())
if __name__ == "__main__":
main()