fix(wake): three-valued grep verdicts — has_match/count_lines across all ten suites (closes #973) (#983)
This commit was merged in pull request #983.
This commit is contained in:
@@ -0,0 +1,280 @@
|
||||
#!/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+3>" row per denominator row (+3 = the
|
||||
uniform header shift the converter applied; converter-verified).
|
||||
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→736, a $(count_lines) capture compared
|
||||
afterward — the A6 shape) plus one F-form arm (quarantine:560→563,
|
||||
the multi-grep pipeline capture), as "<helper> <file>:<line+3>
|
||||
<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, six per-form specimens 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 = 3 # converter inserted 3 header lines after SCRIPT_DIR in every suite
|
||||
|
||||
# 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_.-]|$)")
|
||||
|
||||
|
||||
def polarity(line):
|
||||
m = RX_FAIL_SAME.search(line)
|
||||
return "OR" if m.group(1) == "||" else "AND"
|
||||
|
||||
|
||||
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 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
|
||||
|
||||
|
||||
PLANTS = [
|
||||
("A-same-line", ['grep -q needle haystack || fail "plant-A"']),
|
||||
("B-cont-operator", ["grep -q needle haystack ||", ' fail "plant-B"']),
|
||||
("C-cont-backslash", ["grep -q needle \\", ' haystack || fail "plant-C"']),
|
||||
("D-if-form", ["if ! grep -q needle haystack; then", ' fail "plant-D"', "fi"]),
|
||||
("E-count-capture", ['[ "$(grep -c needle haystack)" = "1" ] || fail "plant-E"']),
|
||||
("F-extract-capture", ['val="$(grep needle haystack)"']),
|
||||
]
|
||||
|
||||
|
||||
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):
|
||||
lines = (WAKE / f).read_text().split("\n")
|
||||
sites, _dispo = classify(lines)
|
||||
residual = []
|
||||
for ln, form, text in sites:
|
||||
if RX_HELPER.search(lines[ln - 1]):
|
||||
# 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]}")
|
||||
continue
|
||||
residual.append((ln, form, text))
|
||||
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: the instrument must find six per-form plants
|
||||
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, snippet in PLANTS:
|
||||
expect[offset + 1] = form # first physical line of each plant
|
||||
base_lines.extend(snippet)
|
||||
offset = len(base_lines)
|
||||
planted.write_text("\n".join(base_lines))
|
||||
sites, _ = classify(planted.read_text().split("\n"))
|
||||
found = {ln: form for ln, form, _t in sites if ln in expect}
|
||||
unexpected = [(ln, form) for ln, form, _t in sites if ln not in expect]
|
||||
hits = sum(1 for ln, form in expect.items() if found.get(ln) == form)
|
||||
print(f"SWEEP-PLANTS found={hits}/6 in planted copy of {donor}")
|
||||
if hits != 6:
|
||||
for ln, form in sorted(expect.items()):
|
||||
got = found.get(ln, "<missed>")
|
||||
if got != form:
|
||||
print(f"SWEEP-PLANT-MISS line {ln}: expected {form}, got {got}")
|
||||
bad += 1
|
||||
if unexpected:
|
||||
# the donor is a converted suite: any non-plant site the sweep finds
|
||||
# in the copy contradicts the zero it just reported on the original
|
||||
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()
|
||||
Reference in New Issue
Block a user