319 lines
13 KiB
Python
Executable File
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()
|