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()
|
||||
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env python3
|
||||
"""convert-973.py — mechanical #973 site conversion, driven by the frozen
|
||||
denominator artifact (denominator-089615f.json), never by ad-hoc grepping.
|
||||
|
||||
For every row in the artifact it FIRST verifies the worktree line still says
|
||||
what the artifact froze (exact match after whitespace strip, artifact-side
|
||||
truncation as prefix match, or the normalized suite-summary template), and
|
||||
aborts before touching anything on the first verification failure — a
|
||||
conversion applied to a line the denominator did not measure would be the
|
||||
umbrella defect wearing the converter's clothes.
|
||||
|
||||
Transforms (behaviour-preserving; verdict semantics unchanged on grep rc 0/1):
|
||||
E-count-capture `grep -c ARGS` -> `count_lines ARGS` (helper adds -c)
|
||||
all other forms `grep ARGS` -> `has_match ARGS` (drop-in)
|
||||
env prefixes (`LC_ALL=C grep`) are kept — the prefix reaches the grep child
|
||||
through the function (microtest C8).
|
||||
|
||||
The two multi-grep pipeline sites (digest-quarantine:560, install:420) convert
|
||||
BOTH greps: each is measurement-bearing, and under pipefail an rc=2 in the
|
||||
left element is masked by an rc=1 in the right — the same defect one pipe
|
||||
deeper. They stay ONE denominator site each (one coordinate); the ledger
|
||||
records helper calls, so those coordinates appear twice per execution and the
|
||||
equality check compares SETS of coordinates.
|
||||
|
||||
Finally each suite gains three header lines directly after its SCRIPT_DIR
|
||||
assignment (source + wake_assert_init + comment), shifting every site by +3
|
||||
lines exactly; the validation harness maps base coordinates accordingly.
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
WAKE = HERE.parent
|
||||
ART = HERE / "denominator-089615f.json"
|
||||
|
||||
RX_GREP_TOKEN = re.compile(r"(^|[^A-Za-z0-9_.-])grep([^A-Za-z0-9_.-]|$)")
|
||||
|
||||
HEADER = [
|
||||
"# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.\n",
|
||||
"# shellcheck disable=SC1091\n",
|
||||
'. "$SCRIPT_DIR/_wake-common.sh" && wake_assert_init\n',
|
||||
]
|
||||
|
||||
MULTI_GREP_CONVERT_BOTH = {
|
||||
("test-wake-digest-quarantine.sh", 560),
|
||||
("test-wake-install.sh", 420),
|
||||
}
|
||||
|
||||
SUMMARY_TEMPLATE_MARK = '$(grep -c . "$FAILFILE")'
|
||||
|
||||
|
||||
def verify(row, actual):
|
||||
a = actual.strip()
|
||||
t = row["text"].strip()
|
||||
if a == t:
|
||||
return True
|
||||
if t and a.startswith(t): # artifact-side truncation
|
||||
return True
|
||||
# normalized suite-summary template rows
|
||||
if t.startswith('echo "wake ') and SUMMARY_TEMPLATE_MARK in actual and a.startswith('echo "wake '):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def convert_line(row, line):
|
||||
key = (row["file"], row["line"])
|
||||
n_grep = len(RX_GREP_TOKEN.findall(line))
|
||||
if key in MULTI_GREP_CONVERT_BOTH:
|
||||
assert n_grep == 2, f"{key}: expected 2 grep tokens, found {n_grep}"
|
||||
else:
|
||||
assert n_grep == 1, f"{key}: expected 1 grep token, found {n_grep}: {line!r}"
|
||||
|
||||
if row["form"].startswith("E"):
|
||||
assert line.count("grep -c ") == 1, f"{key}: E row without single 'grep -c ': {line!r}"
|
||||
return line.replace("grep -c ", "count_lines ", 1)
|
||||
|
||||
def repl(m):
|
||||
return m.group(1) + "has_match" + m.group(2)
|
||||
|
||||
count = 2 if key in MULTI_GREP_CONVERT_BOTH else 1
|
||||
return RX_GREP_TOKEN.sub(repl, line, count=count)
|
||||
|
||||
|
||||
def main():
|
||||
art = json.loads(ART.read_text())
|
||||
rows = art["rows"]
|
||||
by_file = {}
|
||||
for r in rows:
|
||||
by_file.setdefault(r["file"], []).append(r)
|
||||
|
||||
# pass 1: verify every row before touching any file
|
||||
bad = 0
|
||||
texts = {}
|
||||
for f, frs in by_file.items():
|
||||
lines = (WAKE / f).read_text().split("\n")
|
||||
texts[f] = lines
|
||||
for r in frs:
|
||||
if not verify(r, lines[r["line"] - 1]):
|
||||
bad += 1
|
||||
print(f"VERIFY-FAIL {f}:{r['line']}\n artifact: {r['text']!r}\n worktree: {lines[r['line'] - 1]!r}")
|
||||
if bad:
|
||||
sys.exit(f"ABORT: {bad} row(s) failed verification; nothing was modified.")
|
||||
|
||||
# pass 2: convert + insert header
|
||||
total = {"has_match": 0, "count_lines": 0}
|
||||
for f, frs in sorted(by_file.items()):
|
||||
lines = texts[f]
|
||||
for r in frs:
|
||||
i = r["line"] - 1
|
||||
new = convert_line(r, lines[i])
|
||||
assert new != lines[i], f"{f}:{r['line']}: no-op conversion"
|
||||
lines[i] = new
|
||||
total["count_lines" if r["form"].startswith("E") else "has_match"] += 1
|
||||
# header insertion after the SCRIPT_DIR= line
|
||||
sd = [i for i, ln in enumerate(lines) if ln.startswith('SCRIPT_DIR="$(')]
|
||||
assert len(sd) == 1, f"{f}: expected exactly one SCRIPT_DIR line, found {len(sd)}"
|
||||
first_site = min(r["line"] for r in frs) - 1
|
||||
assert sd[0] < first_site, f"{f}: SCRIPT_DIR line {sd[0] + 1} not before first site {first_site + 1}"
|
||||
lines[sd[0] + 1 : sd[0] + 1] = [h.rstrip("\n") for h in HEADER]
|
||||
(WAKE / f).write_text("\n".join(lines))
|
||||
print(f"{f}: {len(frs)} sites converted, header at line {sd[0] + 2}")
|
||||
|
||||
print(f"TOTAL: {total['has_match']} has_match + {total['count_lines']} count_lines = {sum(total.values())} sites in {len(by_file)} files")
|
||||
assert sum(total.values()) == art["total"] == 261, "site count mismatch vs artifact"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
File diff suppressed because it is too large
Load Diff
+299
@@ -0,0 +1,299 @@
|
||||
#!/usr/bin/env bash
|
||||
# microtest-wake-assert.sh — #973 instrument self-test. Run BEFORE trusting any
|
||||
# validate-run evidence: it proves the counted ledger and the abort mechanics on
|
||||
# two generated mini-suites, so a defect in the instrument cannot silently wear
|
||||
# the colour of a clean validation.
|
||||
#
|
||||
# What it proves (each check named C1..C8 below):
|
||||
# C1 green run: ledger set EQUALS a text-derived expected set spanning TWO
|
||||
# files (file-field discrimination), row count > 1, both sentinels emitted,
|
||||
# exit 0. Also pins the BASH_LINENO convention for backslash-continuation
|
||||
# call sites against the first-physical-line convention the denominator
|
||||
# artifact uses.
|
||||
# C2 early-exit truncation: a suite that exits before its later site yields a
|
||||
# SHORT ledger, and the expected-set comparison catches it — a counted
|
||||
# ledger must report its own truncation, never a smaller total.
|
||||
# C3 abort from inside a `( ... )` test subshell kills the WHOLE suite: no
|
||||
# sentinel, non-zero exit, loud named reason (file:line + raw rc).
|
||||
# C4 abort stays loud at a call site that appends 2>/dev/null (the preimage
|
||||
# canary shape) — the saved-fd path.
|
||||
# C5 abort escapes a `$( count_lines ... )` substitution (A6 shape): the
|
||||
# count from a failed measurement is never compared and the suite dies.
|
||||
# C6 abort escapes a pipeline tail (`printf | has_match`).
|
||||
# C7 count_lines prints 0 on grep rc 1 (zero matches is a measurement, not an
|
||||
# error) — implicit in C1's green run via the delta-count site.
|
||||
# C8 an env-prefix on the helper (`LC_ALL=C has_match ...`) reaches the grep
|
||||
# child — pins the conversion shape for the digest-hmac LC_ALL site.
|
||||
# C9 an arm that matches NO site is loud about it by omission: green run,
|
||||
# sentinel present, and NO "WAKE-ASSERT ARMED" line — so "did not abort"
|
||||
# is separable into arm-never-matched (no ARMED line) vs error-path-
|
||||
# broken (ARMED line, no abort). C3..C6 require the ARMED line AND the
|
||||
# aborting site's ledger row (append lands BEFORE the grep runs, so an
|
||||
# abort can never shorten the count it is part of).
|
||||
# C10 the BASH_LINENO pin's abort arm fires: under a probe interpreter that
|
||||
# misreports the continuation line, wake_assert_init aborts loudly and
|
||||
# nothing past init executes — a pin whose failure arm was never seen
|
||||
# firing is an undertaking, not a control.
|
||||
# C11 the FAILED-summary template executes on the red path: a mini-suite
|
||||
# driven deterministically red emits the exact converted summary shape
|
||||
# (`FAILED ($(count_lines . "$FAILFILE") assertion(s))`) with the right
|
||||
# count, exits 1, and the summary site's ledger row lands. The nine
|
||||
# real-suite summary sites are structurally unreachable in a green run
|
||||
# (guarded by [ -s "$FAILFILE" ]); their dispositions cite THIS check as
|
||||
# the measured execution of the same template, so "unexecuted in the
|
||||
# green run" never silently means "never executed anywhere".
|
||||
set -uo pipefail
|
||||
|
||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
export WAKE_COMMON="$HERE/../_wake-common.sh"
|
||||
[ -f "$WAKE_COMMON" ] || {
|
||||
echo "microtest: _wake-common.sh not found at $WAKE_COMMON" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
TMP="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
fails=0
|
||||
check() { # check NAME COND-DESCRIPTION (pass/fail already decided by caller: $1=name $2=0|1 $3=detail)
|
||||
if [ "$2" -eq 0 ]; then
|
||||
echo " PASS $1"
|
||||
else
|
||||
echo " FAIL $1 — $3"
|
||||
fails=$((fails + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
# --- fixture data ----------------------------------------------------------
|
||||
printf 'alpha\nbeta\nbeta\ngamma-unused\n' >"$TMP/data.txt"
|
||||
|
||||
# --- mini-suite A: six helper sites across every converted form ------------
|
||||
cat >"$TMP/mini-a.sh" <<'MINI_A'
|
||||
#!/usr/bin/env bash
|
||||
set -uo pipefail
|
||||
. "$WAKE_COMMON"
|
||||
wake_assert_init
|
||||
TMP="$1"
|
||||
FAILFILE="$TMP/failures-a"
|
||||
: >"$FAILFILE"
|
||||
fail_msg() { echo " FAIL: $*" >&2; echo x >>"$FAILFILE"; }
|
||||
ok() { :; }
|
||||
(
|
||||
has_match -q alpha "$TMP/data.txt" || fail_msg "alpha missing" # SITE:or-subshell
|
||||
) && ok
|
||||
(
|
||||
has_match -q FORBIDDEN "$TMP/data.txt" 2>/dev/null && fail_msg "forbidden present" # SITE:and-swallow
|
||||
) && ok
|
||||
(
|
||||
[ "$(count_lines beta "$TMP/data.txt")" = "2" ] || fail_msg "beta count" # SITE:count-capture
|
||||
) && ok
|
||||
(
|
||||
printf 'gamma\n' | has_match -q gamma || fail_msg "gamma pipeline" # SITE:pipeline
|
||||
) && ok
|
||||
(
|
||||
has_match -q \
|
||||
alpha "$TMP/data.txt" || fail_msg "continuation" # SITE:continuation
|
||||
) && ok
|
||||
(
|
||||
[ "$(count_lines delta "$TMP/data.txt")" = "0" ] || fail_msg "delta zero" # SITE:count-zero
|
||||
) && ok
|
||||
if [ -s "$FAILFILE" ]; then
|
||||
echo "mini-a: FAILED" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "mini-a: OK" >&2
|
||||
MINI_A
|
||||
|
||||
# --- mini-suite B: second file, one site behind an early exit --------------
|
||||
cat >"$TMP/mini-b.sh" <<'MINI_B'
|
||||
#!/usr/bin/env bash
|
||||
set -uo pipefail
|
||||
. "$WAKE_COMMON"
|
||||
wake_assert_init
|
||||
TMP="$1"
|
||||
(
|
||||
has_match -q alpha "$TMP/data.txt" || echo "b1 missing" >&2 # SITE:b-first
|
||||
)
|
||||
if [ "${MINI_B_EARLY_EXIT:-}" = "1" ]; then
|
||||
exit 0
|
||||
fi
|
||||
(
|
||||
has_match -q beta "$TMP/data.txt" || echo "b2 missing" >&2 # SITE:b-second
|
||||
)
|
||||
echo "mini-b: OK" >&2
|
||||
MINI_B
|
||||
chmod +x "$TMP/mini-a.sh" "$TMP/mini-b.sh"
|
||||
|
||||
# Text-derived expected set: helper-name + basename:line for every SITE-marked
|
||||
# call, taken from the generated files' TEXT (independent of BASH_LINENO), with
|
||||
# the continuation site expected at its FIRST physical line — the denominator
|
||||
# artifact's convention.
|
||||
expected_set() { # expected_set FILE
|
||||
local f="$1" base
|
||||
base="$(basename "$f")"
|
||||
awk '
|
||||
/# SITE:/ {
|
||||
line = NR
|
||||
if ($0 !~ /has_match|count_lines/) line = NR - 1 # marker on the continuation tail
|
||||
print line
|
||||
}
|
||||
' "$f" | while read -r ln; do
|
||||
txt="$(sed -n "${ln}p" "$f")"
|
||||
case "$txt" in
|
||||
*count_lines*) printf 'count_lines %s:%s\n' "$base" "$ln" ;;
|
||||
*) printf 'has_match %s:%s\n' "$base" "$ln" ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
site_line() { # site_line FILE MARKER -> first physical line of that call
|
||||
local f="$1" marker="$2" ln
|
||||
ln="$(grep -n "# SITE:${marker}\$" "$f" | cut -d: -f1)"
|
||||
# continuation marker sits on the tail line; the call starts one line up
|
||||
if ! sed -n "${ln}p" "$f" | grep -Eq 'has_match|count_lines'; then
|
||||
ln=$((ln - 1))
|
||||
fi
|
||||
printf '%s' "$ln"
|
||||
}
|
||||
|
||||
# --- C1: green run, two files, set equality --------------------------------
|
||||
LEDGER="$TMP/ledger-c1"
|
||||
: >"$LEDGER"
|
||||
outA="$(WAKE_ASSERT_LEDGER="$LEDGER" bash "$TMP/mini-a.sh" "$TMP" 2>&1)"
|
||||
rcA=$?
|
||||
outB="$(WAKE_ASSERT_LEDGER="$LEDGER" bash "$TMP/mini-b.sh" "$TMP" 2>&1)"
|
||||
rcB=$?
|
||||
{ expected_set "$TMP/mini-a.sh"; expected_set "$TMP/mini-b.sh"; } | sort >"$TMP/expected-c1"
|
||||
sort "$LEDGER" >"$TMP/got-c1"
|
||||
n_expected="$(grep -c . "$TMP/expected-c1")"
|
||||
if [ "$rcA" -eq 0 ] && [ "$rcB" -eq 0 ] &&
|
||||
printf '%s' "$outA" | grep -q 'mini-a: OK' &&
|
||||
printf '%s' "$outB" | grep -q 'mini-b: OK' &&
|
||||
[ "$n_expected" -gt 1 ] &&
|
||||
cmp -s "$TMP/expected-c1" "$TMP/got-c1"; then
|
||||
check C1 0 ""
|
||||
else
|
||||
check C1 1 "rcA=$rcA rcB=$rcB expected($n_expected)/got diff: $(diff "$TMP/expected-c1" "$TMP/got-c1" 2>&1 | head -n 10 | tr '\n' ' ')"
|
||||
fi
|
||||
|
||||
# --- C2: early exit -> short ledger, comparison catches it -----------------
|
||||
LEDGER="$TMP/ledger-c2"
|
||||
: >"$LEDGER"
|
||||
WAKE_ASSERT_LEDGER="$LEDGER" MINI_B_EARLY_EXIT=1 bash "$TMP/mini-b.sh" "$TMP" >/dev/null 2>&1
|
||||
expected_set "$TMP/mini-b.sh" | sort >"$TMP/expected-c2"
|
||||
sort "$LEDGER" >"$TMP/got-c2"
|
||||
if ! cmp -s "$TMP/expected-c2" "$TMP/got-c2" &&
|
||||
grep -q "has_match mini-b.sh:$(site_line "$TMP/mini-b.sh" b-first)" "$TMP/got-c2" &&
|
||||
! grep -q "mini-b.sh:$(site_line "$TMP/mini-b.sh" b-second)" "$TMP/got-c2"; then
|
||||
check C2 0 ""
|
||||
else
|
||||
check C2 1 "truncated ledger was not detected as short"
|
||||
fi
|
||||
|
||||
# --- C3..C6: per-shape abort proofs ----------------------------------------
|
||||
abort_case() { # abort_case NAME MARKER HELPER
|
||||
local name="$1" marker="$2" helper="$3" ln site out rc ledger
|
||||
ln="$(site_line "$TMP/mini-a.sh" "$marker")"
|
||||
site="mini-a.sh:${ln}"
|
||||
ledger="$TMP/ledger-${name}"
|
||||
: >"$ledger"
|
||||
out="$(WAKE_ASSERT_LEDGER="$ledger" WAKE_ASSERT_FORCE_GREP_ERROR_AT="$site" \
|
||||
bash "$TMP/mini-a.sh" "$TMP" 2>&1)"
|
||||
rc=$?
|
||||
if [ "$rc" -ne 0 ] &&
|
||||
! printf '%s' "$out" | grep -q 'mini-a: OK' &&
|
||||
! printf '%s' "$out" | grep -q 'mini-a: FAILED' &&
|
||||
printf '%s' "$out" | grep -q "WAKE-ASSERT ARMED: forcing real grep error at $site" &&
|
||||
printf '%s' "$out" | grep -q "WAKE-ASSERT ABORT" &&
|
||||
printf '%s' "$out" | grep -q "$site" &&
|
||||
printf '%s' "$out" | grep -q "grep exit 2" &&
|
||||
grep -q "^${helper} ${site}\$" "$ledger"; then
|
||||
check "$name" 0 ""
|
||||
else
|
||||
check "$name" 1 "rc=$rc site=$site ledger=$(grep -c . "$ledger") out=$(printf '%s' "$out" | tail -n 3 | tr '\n' ' ')"
|
||||
fi
|
||||
}
|
||||
abort_case C3 or-subshell has_match
|
||||
abort_case C4 and-swallow has_match
|
||||
abort_case C5 count-capture count_lines
|
||||
abort_case C6 pipeline has_match
|
||||
|
||||
# --- C7: covered by C1 (delta-count site prints 0 on grep rc 1) ------------
|
||||
check C7 0 ""
|
||||
|
||||
# --- C8: env-prefix on a function reaches the grep child -------------------
|
||||
envprobe() { command env | command grep -c '^LC_ALL=xx_wake_test$'; }
|
||||
got="$(LC_ALL=xx_wake_test envprobe 2>/dev/null)" # bash's setlocale warning about the fake locale is itself proof the prefix landed
|
||||
if [ "$got" = "1" ]; then check C8 0 ""; else check C8 1 "env-prefix did not reach child (got=$got)"; fi
|
||||
|
||||
# --- C9: arm matching NO site -> green run, no ARMED line ------------------
|
||||
out="$(WAKE_ASSERT_FORCE_GREP_ERROR_AT="mini-a.sh:9999" bash "$TMP/mini-a.sh" "$TMP" 2>&1)"
|
||||
rc=$?
|
||||
if [ "$rc" -eq 0 ] &&
|
||||
printf '%s' "$out" | grep -q 'mini-a: OK' &&
|
||||
! printf '%s' "$out" | grep -q 'WAKE-ASSERT ARMED'; then
|
||||
check C9 0 ""
|
||||
else
|
||||
check C9 1 "rc=$rc out=$(printf '%s' "$out" | tail -n 3 | tr '\n' ' ')"
|
||||
fi
|
||||
|
||||
# --- C10: lineno pin aborts under an interpreter that breaks the convention -
|
||||
cat >"$TMP/fake-bash" <<'FAKE'
|
||||
#!/usr/bin/env bash
|
||||
# stand-in for a bash whose BASH_LINENO convention differs: misreports the
|
||||
# continuation call one line low (the exact skew the pin exists to catch)
|
||||
printf '3\n5\n'
|
||||
FAKE
|
||||
chmod +x "$TMP/fake-bash"
|
||||
out="$(WAKE_ASSERT_PIN_BASH="$TMP/fake-bash" bash -c '. "$WAKE_COMMON" && wake_assert_init && echo REACHED-PAST-INIT' 2>&1)"
|
||||
rc=$?
|
||||
if [ "$rc" -ne 0 ] &&
|
||||
! printf '%s' "$out" | grep -q 'REACHED-PAST-INIT' &&
|
||||
printf '%s' "$out" | grep -q 'WAKE-ASSERT INIT ABORT: BASH_LINENO convention violated'; then
|
||||
check C10 0 ""
|
||||
else
|
||||
check C10 1 "rc=$rc out=$(printf '%s' "$out" | tail -n 2 | tr '\n' ' ')"
|
||||
fi
|
||||
|
||||
# --- C11: red path executes the converted FAILED-summary template -----------
|
||||
cat >"$TMP/mini-c.sh" <<'MINI_C'
|
||||
#!/usr/bin/env bash
|
||||
set -uo pipefail
|
||||
. "$WAKE_COMMON"
|
||||
wake_assert_init
|
||||
TMP="$1"
|
||||
FAILFILE="$TMP/failures-c"
|
||||
: >"$FAILFILE"
|
||||
fail_msg() { echo " FAIL: $*" >&2; echo x >>"$FAILFILE"; }
|
||||
ok() { :; }
|
||||
(
|
||||
has_match -q alpha "$TMP/data.txt" && fail_msg "alpha present" # SITE:c-inverted (deterministically red: alpha IS in the fixture)
|
||||
) && ok
|
||||
echo
|
||||
if [ -s "$FAILFILE" ]; then
|
||||
echo "wake mini-c harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2 # SITE:c-summary
|
||||
exit 1
|
||||
fi
|
||||
echo "wake mini-c harness: all invariants passed (1 group)"
|
||||
MINI_C
|
||||
chmod +x "$TMP/mini-c.sh"
|
||||
LEDGER="$TMP/ledger-c11"
|
||||
: >"$LEDGER"
|
||||
out="$(WAKE_ASSERT_LEDGER="$LEDGER" bash "$TMP/mini-c.sh" "$TMP" 2>&1)"
|
||||
rc=$?
|
||||
summary_ln="$(site_line "$TMP/mini-c.sh" c-summary)"
|
||||
if [ "$rc" -eq 1 ] &&
|
||||
printf '%s' "$out" | grep -q 'wake mini-c harness: FAILED (1 assertion(s))' &&
|
||||
! printf '%s' "$out" | grep -q 'all invariants passed' &&
|
||||
grep -q "^count_lines mini-c.sh:${summary_ln}\$" "$LEDGER"; then
|
||||
check C11 0 ""
|
||||
else
|
||||
check C11 1 "rc=$rc summary_ln=$summary_ln ledger=$(tr '\n' ' ' <"$LEDGER") out=$(printf '%s' "$out" | tail -n 2 | tr '\n' ' ')"
|
||||
fi
|
||||
|
||||
echo
|
||||
if [ "$fails" -gt 0 ]; then
|
||||
echo "microtest-wake-assert: FAILED ($fails check(s))" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "microtest-wake-assert: OK (all checks passed)"
|
||||
@@ -0,0 +1,37 @@
|
||||
# unexecuted-sites-dispositions.txt — #973 validation, amendment ONE leg 3.
|
||||
#
|
||||
# The ledger is an execution trace, not an inventory: a green instrumented run
|
||||
# cannot execute a site that only lives on a suite's red path. Every converted
|
||||
# site that did NOT appear in the green-run trace is enumerated here with an
|
||||
# individual disposition; validate-973.sh fails if any unexecuted site lacks a
|
||||
# row here, and ALSO fails if a row here names a site that DID execute (stale
|
||||
# disposition). Key = first two whitespace-separated fields; text after "—" is
|
||||
# the adjudication.
|
||||
#
|
||||
# All nine sites below are the same structural shape, adjudicated one by one
|
||||
# from source text: the suite's FAILED-branch summary line,
|
||||
# echo "wake <name> harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2
|
||||
# guarded by `if [ -s "$FAILFILE" ]` — structurally unreachable while every
|
||||
# assertion passes, which is precisely the state a green validation run is
|
||||
# required to be in. (The tenth suite, test-wake-preimage.sh, uses its own
|
||||
# X/Y summary format with no grep in the red branch, so it has no row here.)
|
||||
#
|
||||
# The disposition is NOT "it would work": the exact template is EXECUTED red
|
||||
# in microtest C11 (deterministically failed mini-suite, same
|
||||
# count_lines-in-substitution summary shape → right count, exit 1, ledger row
|
||||
# at the summary coordinate), and the E-in-substitution abort path is proven
|
||||
# by microtest C5 plus the forced-error arm at test-wake-store-ack.sh:736.
|
||||
# Each site's conversion text is independently verified by the static
|
||||
# inventory (check-973.py static == expected, all 261 rows).
|
||||
#
|
||||
# Verified guard per site (line numbers at branch tip, +3 header shift):
|
||||
|
||||
count_lines test-wake-beacon.sh:350 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 349; template execution measured by microtest C11; text verified by static inventory
|
||||
count_lines test-wake-detector.sh:702 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 701; template execution measured by microtest C11; text verified by static inventory
|
||||
count_lines test-wake-digest-hmac.sh:434 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 433; template execution measured by microtest C11; text verified by static inventory
|
||||
count_lines test-wake-digest-quarantine.sh:584 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 583; template execution measured by microtest C11; text verified by static inventory
|
||||
count_lines test-wake-fn-oracle.sh:132 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 131; template execution measured by microtest C11; text verified by static inventory
|
||||
count_lines test-wake-install.sh:434 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 433; template execution measured by microtest C11; text verified by static inventory
|
||||
count_lines test-wake-reconcile.sh:389 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 388; template execution measured by microtest C11; text verified by static inventory
|
||||
count_lines test-wake-store-ack.sh:741 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 740; template execution measured by microtest C11; text verified by static inventory
|
||||
count_lines test-wake-store-enqueue-race.sh:208 — red-path summary (with "#927 TOCTOU reproduced (RED)" tail); guard `[ -s "$FAILFILE" ]` at line 207; template execution measured by microtest C11; text verified by static inventory
|
||||
@@ -0,0 +1,210 @@
|
||||
#!/usr/bin/env bash
|
||||
# validate-973.sh — #973 validation driver. One run produces the complete
|
||||
# evidence chain for the 261-site conversion:
|
||||
#
|
||||
# 0. instrument self-test (microtest) — no validate evidence is trusted
|
||||
# before the instrument itself has been proven, including its abort arms.
|
||||
# 1. expected set: 261 coordinates from the FROZEN artifact (+3 header
|
||||
# shift), count asserted against the number declared below BEFORE any
|
||||
# suite runs.
|
||||
# 2. static inventory: converted call sites re-derived from SOURCE TEXT,
|
||||
# must equal the expected set exactly (amendment ONE, leg 1 — the
|
||||
# inventory comes from the text, never from the ledger).
|
||||
# 3. green instrumented run: all ten suites with WAKE_ASSERT_LEDGER; each
|
||||
# must exit 0 AND emit its own sentinel (per-suite formats differ and are
|
||||
# pinned here — a suite that died early must never pass on another
|
||||
# suite's output).
|
||||
# 4. trace arithmetic on coordinate SETS (loops re-execute sites and the
|
||||
# multi-grep lines append twice per pass, so counts are meaningless;
|
||||
# sets are not):
|
||||
# trace − expected MUST be empty (a helper ran at a coordinate the
|
||||
# denominator never measured);
|
||||
# expected − trace = converted-but-never-executed: enumerated, and
|
||||
# every entry must carry a disposition in the
|
||||
# committed unexecuted-sites-dispositions.txt, with
|
||||
# no stale dispositions the other way (amendment
|
||||
# ONE, legs 2+3 — the ledger is an execution trace,
|
||||
# not an inventory; the difference is enumerated and
|
||||
# individually dispositioned, never silently absent).
|
||||
# 5. forced-error arms: the 19 denominator canaries plus one E-form and one
|
||||
# F-form site, each run with WAKE_ASSERT_FORCE_GREP_ERROR_AT: the suite
|
||||
# must emit the ARMED line (the arm proved it fired), the ABORT line
|
||||
# naming the site, exit non-zero, emit NO sentinel, and the aborting
|
||||
# site's ledger row must already be present (the append lands before the
|
||||
# grep).
|
||||
# 6. residual sweep: the denominator's own classifier finds zero unconverted
|
||||
# verdict greps in the suites — and six per-form plants in the same run.
|
||||
#
|
||||
# Output discipline (A10): every line that reports on a suite names the file
|
||||
# under test; exit codes are reported before failure counts.
|
||||
set -uo pipefail
|
||||
|
||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
WAKE="$(cd "$HERE/.." && pwd)"
|
||||
CHECK="$HERE/check-973.py"
|
||||
DISPO="$HERE/unexecuted-sites-dispositions.txt"
|
||||
TMP="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
# Declared BEFORE any suite runs (A2): the run must produce THESE numbers,
|
||||
# not be described by whatever numbers it produced.
|
||||
EXPECTED_SUITES=10
|
||||
EXPECTED_SITES=261
|
||||
EXPECTED_ARMS=21
|
||||
|
||||
fails=0
|
||||
flag() {
|
||||
printf 'FAIL %s\n' "$*"
|
||||
fails=$((fails + 1))
|
||||
}
|
||||
|
||||
SUITES=(
|
||||
test-wake-beacon.sh
|
||||
test-wake-detector.sh
|
||||
test-wake-digest-hmac.sh
|
||||
test-wake-digest-quarantine.sh
|
||||
test-wake-fn-oracle.sh
|
||||
test-wake-install.sh
|
||||
test-wake-preimage.sh
|
||||
test-wake-reconcile.sh
|
||||
test-wake-store-ack.sh
|
||||
test-wake-store-enqueue-race.sh
|
||||
)
|
||||
[ "${#SUITES[@]}" -eq "$EXPECTED_SUITES" ] ||
|
||||
flag "suite list has ${#SUITES[@]} entries, declared $EXPECTED_SUITES"
|
||||
|
||||
# Per-suite sentinel patterns, pinned: nine suites share the harness template
|
||||
# (enqueue-race appends a tail after it); preimage uses its own format.
|
||||
sentinel_for() {
|
||||
case "$1" in
|
||||
test-wake-preimage.sh) printf '%s' '^== test-wake-preimage: 17/17 passed ==$' ;;
|
||||
*) printf '%s' 'harness: all invariants passed' ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# --- 0: instrument self-test ------------------------------------------------
|
||||
if bash "$HERE/microtest-wake-assert.sh" >"$TMP/microtest.out" 2>&1; then
|
||||
echo "MICROTEST microtest-wake-assert.sh exit=0 (instrument proven)"
|
||||
else
|
||||
rc=$?
|
||||
echo "MICROTEST microtest-wake-assert.sh exit=$rc"
|
||||
sed 's/^/ /' "$TMP/microtest.out" | tail -n 15
|
||||
flag "instrument self-test failed — no validate evidence below is trustworthy"
|
||||
fi
|
||||
|
||||
# --- 1+2: expected set (artifact) vs static inventory (source text) ---------
|
||||
python3 "$CHECK" expected | sort >"$TMP/expected.txt" ||
|
||||
flag "check-973.py expected failed"
|
||||
n_expected="$(grep -c . "$TMP/expected.txt")"
|
||||
echo "EXPECTED-SET $n_expected coordinates (declared: $EXPECTED_SITES)"
|
||||
[ "$n_expected" -eq "$EXPECTED_SITES" ] ||
|
||||
flag "expected set has $n_expected coordinates, declared $EXPECTED_SITES"
|
||||
|
||||
python3 "$CHECK" static | sort >"$TMP/static.txt" ||
|
||||
flag "check-973.py static failed"
|
||||
if cmp -s "$TMP/expected.txt" "$TMP/static.txt"; then
|
||||
echo "STATIC-INVENTORY equals expected set ($(grep -c . "$TMP/static.txt") rows from source text)"
|
||||
else
|
||||
flag "static inventory (source text) differs from expected set (artifact):"
|
||||
diff "$TMP/expected.txt" "$TMP/static.txt" | head -n 20 | sed 's/^/ /'
|
||||
fi
|
||||
|
||||
# --- 3: green instrumented run ----------------------------------------------
|
||||
LEDGER="$TMP/ledger"
|
||||
: >"$LEDGER"
|
||||
for s in "${SUITES[@]}"; do
|
||||
out="$(WAKE_ASSERT_LEDGER="$LEDGER" bash "$WAKE/$s" 2>&1)"
|
||||
rc=$?
|
||||
if printf '%s\n' "$out" | grep -Eq "$(sentinel_for "$s")"; then
|
||||
sent="present"
|
||||
else
|
||||
sent="ABSENT"
|
||||
fi
|
||||
echo "SUITE $s exit=$rc sentinel=$sent"
|
||||
[ "$rc" -eq 0 ] || flag "$s exited $rc in the green instrumented run"
|
||||
[ "$sent" = "present" ] || flag "$s did not emit its sentinel"
|
||||
done
|
||||
|
||||
# --- 4: trace arithmetic on coordinate sets ---------------------------------
|
||||
sort -u "$LEDGER" >"$TMP/trace.txt"
|
||||
echo "TRACE $(grep -c . "$TMP/trace.txt") distinct coordinates from $(grep -c . "$LEDGER") ledger rows"
|
||||
|
||||
comm -13 "$TMP/expected.txt" "$TMP/trace.txt" >"$TMP/rogue.txt"
|
||||
if [ -s "$TMP/rogue.txt" ]; then
|
||||
flag "trace contains coordinates OUTSIDE the frozen denominator:"
|
||||
sed 's/^/ ROGUE /' "$TMP/rogue.txt"
|
||||
else
|
||||
echo "TRACE-MINUS-EXPECTED empty (no helper ran at an unmeasured coordinate)"
|
||||
fi
|
||||
|
||||
comm -23 "$TMP/expected.txt" "$TMP/trace.txt" >"$TMP/unexec.txt"
|
||||
n_unexec="$(grep -c . "$TMP/unexec.txt" || true)"
|
||||
echo "UNEXECUTED $n_unexec of $EXPECTED_SITES converted sites did not execute in the green run"
|
||||
if [ ! -f "$DISPO" ]; then
|
||||
flag "disposition file missing: $DISPO — every unexecuted site must be individually dispositioned"
|
||||
sed 's/^/ UNDISPOSITIONED /' "$TMP/unexec.txt"
|
||||
else
|
||||
awk '!/^#/ && NF >= 2 {print $1, $2}' "$DISPO" | sort -u >"$TMP/dispo-keys.txt"
|
||||
comm -23 "$TMP/unexec.txt" "$TMP/dispo-keys.txt" >"$TMP/undispo.txt"
|
||||
comm -13 "$TMP/unexec.txt" "$TMP/dispo-keys.txt" >"$TMP/stale-dispo.txt"
|
||||
if [ -s "$TMP/undispo.txt" ]; then
|
||||
flag "unexecuted sites WITHOUT a disposition:"
|
||||
sed 's/^/ UNDISPOSITIONED /' "$TMP/undispo.txt"
|
||||
fi
|
||||
if [ -s "$TMP/stale-dispo.txt" ]; then
|
||||
flag "dispositions for sites that DID execute (stale — the file no longer matches the run):"
|
||||
sed 's/^/ STALE-DISPO /' "$TMP/stale-dispo.txt"
|
||||
fi
|
||||
if [ ! -s "$TMP/undispo.txt" ] && [ ! -s "$TMP/stale-dispo.txt" ]; then
|
||||
echo "DISPOSITIONS all $n_unexec unexecuted sites individually dispositioned, none stale"
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- 5: forced-error arms ---------------------------------------------------
|
||||
python3 "$CHECK" arms >"$TMP/arms.txt" || flag "check-973.py arms failed"
|
||||
n_arms="$(grep -c . "$TMP/arms.txt")"
|
||||
echo "ARMS $n_arms forced-error arms (declared: $EXPECTED_ARMS)"
|
||||
[ "$n_arms" -eq "$EXPECTED_ARMS" ] ||
|
||||
flag "arm list has $n_arms entries, declared $EXPECTED_ARMS"
|
||||
|
||||
while read -r helper site form; do
|
||||
f="${site%%:*}"
|
||||
aled="$TMP/ledger-arm"
|
||||
: >"$aled"
|
||||
out="$(WAKE_ASSERT_LEDGER="$aled" WAKE_ASSERT_FORCE_GREP_ERROR_AT="$site" \
|
||||
bash "$WAKE/$f" 2>&1)"
|
||||
rc=$?
|
||||
bad=""
|
||||
[ "$rc" -ne 0 ] || bad="$bad exit=0"
|
||||
printf '%s\n' "$out" | grep -q "WAKE-ASSERT ARMED: forcing real grep error at $site" ||
|
||||
bad="$bad no-ARMED-line"
|
||||
printf '%s\n' "$out" | grep -q "WAKE-ASSERT ABORT: ${helper} at ${site}: grep exit" ||
|
||||
bad="$bad no-ABORT-line"
|
||||
printf '%s\n' "$out" | grep -Eq "$(sentinel_for "$f")" &&
|
||||
bad="$bad sentinel-emitted"
|
||||
grep -q "^${helper} ${site}\$" "$aled" ||
|
||||
bad="$bad no-ledger-row"
|
||||
if [ -z "$bad" ]; then
|
||||
echo "ARM $site ($form) exit=$rc armed+abort+no-sentinel+ledger-row"
|
||||
else
|
||||
echo "ARM $site ($form) exit=$rc DEFECTS:$bad"
|
||||
flag "arm $site ($form) failed:$bad"
|
||||
fi
|
||||
done <"$TMP/arms.txt"
|
||||
|
||||
# --- 6: residual sweep ------------------------------------------------------
|
||||
if python3 "$CHECK" sweep >"$TMP/sweep.out" 2>&1; then
|
||||
echo "SWEEP exit=0"
|
||||
else
|
||||
echo "SWEEP exit=$?"
|
||||
flag "residual sweep failed"
|
||||
fi
|
||||
sed 's/^/ /' "$TMP/sweep.out"
|
||||
|
||||
# --- summary (exit codes above, failure count last — A10) --------------------
|
||||
echo
|
||||
if [ "$fails" -gt 0 ]; then
|
||||
echo "validate-973: FAILED ($fails failure(s))" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "validate-973: OK — $EXPECTED_SUITES suites, $EXPECTED_SITES sites, $EXPECTED_ARMS arms, sweep clean"
|
||||
Reference in New Issue
Block a user