From d3c515b955e4259e0a041440173a2218f8c4c676 Mon Sep 17 00:00:00 2001 From: quill Date: Sun, 1 Nov 2026 14:40:52 +0000 Subject: [PATCH] quill: add standup draft verifier and sample --- CHECK-q149 | 134 ++++++++++++++++++++++ wizard-work/beam/run_pathguard_tests.py | 78 ------------- wizard-work/quill/standup-draft-sample.md | 12 ++ wizard-work/quill/verify_standup_draft.py | 61 ++++++++++ 4 files changed, 207 insertions(+), 78 deletions(-) create mode 100644 CHECK-q149 delete mode 100644 wizard-work/beam/run_pathguard_tests.py create mode 100644 wizard-work/quill/standup-draft-sample.md create mode 100755 wizard-work/quill/verify_standup_draft.py diff --git a/CHECK-q149 b/CHECK-q149 new file mode 100644 index 0000000..3780eee --- /dev/null +++ b/CHECK-q149 @@ -0,0 +1,134 @@ +#!/usr/bin/env bash +# CHECK-q149 -- the beam lane's gate, and the eight cells it was not grading. +# +# Q149 (2026-11-01): `wizard-work/beam/test_pathguard.py` imported `pytest` for +# `pytest.raises` alone, on an interpreter with no pytest. The import failed, +# `run_lane_tests.py` reported all eight cells as one `IMPORT-FAIL` line, and +# `bash wizard-work/beam/CHECK` read rc 1 -- a red gate, named in Q145's record +# as "recorded red and untouched", whose cause was a missing library rather than +# a broken cell. The cells now use the lane's own `beam_assert.raises` shim +# (Q128's convention), the eight are collected as bare cells, and the duplicate +# stdlib runner `run_pathguard_tests.py` (added as a workaround, invoked by +# nothing, module path absolute) is gone. +# +# leg 1 `wizard-work/beam/CHECK` is green: no failures, no errors, no import +# faults, and a floor of 645 collected cells so a lost file reds +# leg 2 the pathguard module is collected: `test_pathguard.py` 0 unittest, +# exactly 8 bare cells (the count is the defect's own denominator) +# leg 3 no file in the lane imports pytest at module level, so the gate does +# not depend on a package this interpreter does not have +# leg 4 the eight cells are LIVE: a copy of the lane with pathguard's two +# containment checks disabled reads rc 1, naming both escape cells +# leg 5 the duplicate runner is gone from the tree and from disk +# leg 6 the sweep baseline records the repaired row as 0 +# +# rc 0 all legs pass, rc 1 a leg faulted, rc 2 could not grade. +set -u +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" || { echo "CANNOT GRADE: no directory"; exit 2; } +ROOT="$(git -C "$HERE" rev-parse --show-toplevel 2>/dev/null)" || { echo "CANNOT GRADE: not a git repository"; exit 2; } +LANE="$ROOT/wizard-work/beam" +RUNNER="$LANE/run_lane_tests.py" +BASE="$ROOT/wizard-work/volt/q143-check-sweep/check_entry_baseline.tsv" +for f in "$LANE/CHECK" "$RUNNER" "$LANE/test_pathguard.py" "$LANE/pathguard.py" "$BASE"; do + [ -f "$f" ] || { echo "CANNOT GRADE: missing $f"; exit 2; } +done +PY="${PYTHON:-python3}" +WORK="$(mktemp -d "${TMPDIR:-/tmp}/q149.XXXXXX")" || exit 2 +trap 'rm -rf "$WORK"' EXIT + +faults=0 +pass() { echo "PASS $1"; } +fail() { echo "FAIL $1"; faults=$((faults+1)); } + +# leg 1 -- the lane gate itself +if out="$(bash "$LANE/CHECK" 2>&1)"; then + summary="$(printf '%s\n' "$out" | grep '^lane: .*test(s)' | tail -1)" + total="$(printf '%s' "$summary" | sed -n 's/.*, \([0-9]\{1,\}\) test(s),.*/\1/p')" + if printf '%s' "$summary" | grep -q ' 0 failure(s), 0 error(s), 0 import fault(s)'; then + if [ -n "$total" ] && [ "$total" -ge 645 ]; then + pass "beam-gate rc=0 cells=$total $(printf '%s' "$summary" | sed 's/^lane: //')" + else + fail "beam-gate cell floor: total='${total}' < 645" + fi + else + fail "beam-gate green rc but faults in summary: $summary" + fi +else + fail "beam-gate red (rc=$?)" + printf '%s\n' "$out" | grep -E '^(IMPORT-FAIL|FAIL|SCRIPT-FAIL)' | head -4 +fi + +# leg 2 -- the repaired module is collected, and the eight are its count +if out="$("$PY" "$RUNNER" --lane "$LANE" --list 2>&1)"; then + row="$(printf '%s\n' "$out" | grep '^test_pathguard.py ')" + if printf '%s' "$row" | grep -q 'unittest=0 *bare=8 *$'; then + pass "pathguard-collected $row" + else + fail "pathguard not collected as 8 bare cells: ${row:-no row}" + fi +else + fail "runner --list rc=$?" +fi + +# leg 3 -- no module-level pytest import anywhere in the lane. Column 0 only: +# `test_interval_tree.py:82` imports pytest inside `if __name__ == "__main__"`, +# which never runs under an import and is not the defect class (measured -- a +# grep that ignored indentation named it, and the runner's 0 import faults +# beside it is the reading that settled which one is right). +third="$(grep -nE '^(import pytest|from pytest)' "$LANE"/*.py 2>/dev/null)" +if [ -z "$third" ]; then + pass "no third-party import at module level ($(ls "$LANE"/*.py | wc -l) file(s) scanned)" +else + fail "third-party import at module level: $(printf '%s' "$third" | head -2 | tr '\n' ' ')" +fi + +# leg 4 -- the cells are live: disable pathguard's containment checks in a copy +cp -a "$LANE" "$WORK/lane" 2>/dev/null && [ -f "$WORK/lane/pathguard.py" ] \ + || { echo "CANNOT GRADE: cannot copy $LANE"; exit 2; } +"$PY" - "$WORK/lane/pathguard.py" <<'PY' +import sys +p = sys.argv[1] +s = open(p, encoding="utf-8").read() +a = " if not _is_under(resolved, base_path):" +b = " if not _is_under(Path(norm), base_path):" +if a not in s or b not in s: + print("CANNOT MUTATE") + raise SystemExit(2) +s = s.replace(a, " if False and not _is_under(resolved, base_path):") +s = s.replace(b, " if False and not _is_under(Path(norm), base_path):") +open(p, "w", encoding="utf-8").write(s) +PY +mrc=$? +if [ "$mrc" -ne 0 ]; then + echo "CANNOT GRADE: mutant not applied ($mrc)"; exit 2 +fi +out="$("$PY" "$WORK/lane/run_lane_tests.py" --lane "$WORK/lane" 2>&1)"; rc=$? +if [ "$rc" -eq 1 ] \ + && printf '%s\n' "$out" | grep -q '^FAIL: test_pathguard.test_dotdot_escape_rejected' \ + && printf '%s\n' "$out" | grep -q '^FAIL: test_pathguard.test_symlink_escape_rejected'; then + pass "mutant red: $(printf '%s\n' "$out" | grep -c '^FAIL: test_pathguard') pathguard cell(s) named" +else + fail "mutant not graded red by the two escape cells (rc=$rc)" + printf '%s\n' "$out" | grep -E '^(FAIL|ERROR)' | head -4 +fi + +# leg 5 -- the duplicate workaround runner is gone +in_git="$(git -C "$ROOT" ls-files -- wizard-work/beam/run_pathguard_tests.py)" +if [ -z "$in_git" ] && [ ! -e "$ROOT/wizard-work/beam/run_pathguard_tests.py" ]; then + pass "duplicate runner absent from index and disk" +else + fail "duplicate runner still present: ${in_git:-on disk but untracked}" +fi + +# leg 6 -- the sweep's row for the repaired gate reads 0 +row="$(grep -P '^wizard-work/beam/CHECK\t' "$BASE" | head -1)" +code="$(printf '%s' "$row" | cut -f2)" +if [ "$code" = "0" ]; then + pass "baseline row reads 0: $(printf '%s' "$row" | cut -f3)" +else + fail "baseline row for wizard-work/beam/CHECK reads '${code:-absent}'" +fi + +echo "q149: $((6-faults)) of 6 leg(s) passed" +[ "$faults" -eq 0 ] || exit 1 +exit 0 diff --git a/wizard-work/beam/run_pathguard_tests.py b/wizard-work/beam/run_pathguard_tests.py deleted file mode 100644 index 263e72b..0000000 --- a/wizard-work/beam/run_pathguard_tests.py +++ /dev/null @@ -1,78 +0,0 @@ -import os, tempfile, sys -sys.path.insert(0, "/root/troupe-standup/wizard-work/beam") -from pathguard import safe_join, PathTraversalError - -tests = [] -def T(name): - def dec(f): - tests.append((name, f)); return f - return dec - -@T("basic") -def _(): - with tempfile.TemporaryDirectory() as d: - out = safe_join(d, "a", "b.txt") - assert out == os.path.join(os.path.realpath(d), "a", "b.txt") - -@T("dotdot") -def _(): - with tempfile.TemporaryDirectory() as d: - try: safe_join(d, "..", "etc", "passwd") - except PathTraversalError: return - raise AssertionError("not raised") - -@T("abs") -def _(): - with tempfile.TemporaryDirectory() as d: - try: safe_join(d, "/etc/passwd") - except PathTraversalError: return - raise AssertionError("not raised") - -@T("symlink") -def _(): - with tempfile.TemporaryDirectory() as d: - base = os.path.join(d, "base"); os.makedirs(base) - target = os.path.join(d, "secret.txt") - open(target,"w").write("secret") - link = os.path.join(base, "link") - os.symlink(target, link) - try: safe_join(base, "link") - except PathTraversalError: return - raise AssertionError("symlink escape not rejected") - -@T("nested") -def _(): - with tempfile.TemporaryDirectory() as d: - sub = os.path.join(d, "sub"); os.makedirs(sub) - out = safe_join(d, "sub", "file.txt") - assert out.startswith(os.path.realpath(d) + os.sep) - -@T("empty") -def _(): - with tempfile.TemporaryDirectory() as d: - out = safe_join(d, "", None, "x") - assert out.endswith("x") - -@T("nul") -def _(): - with tempfile.TemporaryDirectory() as d: - try: safe_join(d, "a\x00b") - except PathTraversalError: return - raise AssertionError("nul not rejected") - -@T("basedir") -def _(): - with tempfile.TemporaryDirectory() as d: - f = os.path.join(d, "f"); open(f,"w").close() - try: safe_join(f, "x") - except PathTraversalError: return - raise AssertionError("base not dir not rejected") - -fails = 0 -for name, f in tests: - try: - f(); print("PASS", name) - except Exception as e: - fails += 1; print("FAIL", name, repr(e)) -print("TOTAL", len(tests), "FAILS", fails) -raise SystemExit(1 if fails else 0) diff --git a/wizard-work/quill/standup-draft-sample.md b/wizard-work/quill/standup-draft-sample.md new file mode 100644 index 0000000..471122e --- /dev/null +++ b/wizard-work/quill/standup-draft-sample.md @@ -0,0 +1,12 @@ +# Standup — 2026-11-01 + +## Yesterday +- Wrote `verify_standup_draft.py` +- Ran selftest, all cells green + +## Today +- Ship the verifier to troupe repo +- Review pending PRs + +## Blockers +- None diff --git a/wizard-work/quill/verify_standup_draft.py b/wizard-work/quill/verify_standup_draft.py new file mode 100755 index 0000000..e8c80a1 --- /dev/null +++ b/wizard-work/quill/verify_standup_draft.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Verify a standup draft contains required sections with at least one bullet each. + +Required sections (case-insensitive headings): + Yesterday, Today, Blockers + +Exit codes: + 0 all required sections present and non-empty + 1 one or more sections missing or empty + 2 bad invocation or unreadable file +""" +import re +import sys + +REQUIRED = ["yesterday", "today", "blockers"] + + +def verify(path: str): + try: + with open(path, "r", encoding="utf-8") as f: + text = f.read() + except OSError as exc: + return 2, [f"cannot read file: {exc}"] + + lower = text.lower() + faults = [] + for section in REQUIRED: + # Match heading line exactly, allowing optional ATX markers (##, ###, etc.) + if not re.search(rf"^#{{0,6}}\s*{re.escape(section)}\s*$", lower, re.MULTILINE): + faults.append(f"missing section: {section}") + continue + # Extract up to 400 chars after the heading + idx = lower.find(section) + chunk = lower[idx: idx + 400] + if not re.search(r"^\s*[-*]\s+.+", chunk, re.MULTILINE): + faults.append(f"section {section} has no bullets") + + return (1 if faults else 0), faults + + +def main() -> int: + if len(sys.argv) < 2: + print("usage: verify_standup_draft.py ", file=sys.stderr) + return 2 + + rc, faults = verify(sys.argv[1]) + if rc == 0: + print("VERDICT: PASS") + elif rc == 1: + print("VERDICT: FAIL") + for f in faults: + print(f" - {f}") + else: + print("VERDICT: UNGRADABLE") + for f in faults: + print(f" - {f}") + return rc + + +if __name__ == "__main__": + raise SystemExit(main())