Fix three CI failures: a stale black, my scratchpad path, an impossible check

First push of this branch in a while, and CI found three things.

BLACK VERSION DRIFT. The repo pins black ^24.3 (24.10.0 locked) and the local
environment has 26.1.0, whose stable style differs. An earlier commit titled
"satisfy the black gate" reformatted five tui_v3 screens with 26.1.0, which
UNformatted them as far as CI was concerned — the fix made the gate worse.
Everything is now formatted with 24.10.0, the pinned version, reproducing
CI's six files exactly before and zero after. scripts/mutation_test.py is in
there because I wrote it this session and never ran black on it at all.

MY SCRATCHPAD PATH IN requirements.lock. pip-compile records the command that
generated a lockfile in its header, and when I regenerated it during the
dependency-vulnerability fix I compiled to a temp path and copied the result
in. That baked an absolute path from my machine into a committed file, so
CI's regenerate-and-diff could never match. Regenerated with CI's exact
command on Python 3.11; only the two header lines change, so the dependency
set was never wrong.

AN UNSATISFIABLE LOCKFILE CHECK. Separately, and not caused by me: the step
regenerated the lockfile and diffed it on every matrix entry, including
Windows on 3.10 while Linux and macOS use 3.11. pip-compile resolves
version-conditional dependencies per interpreter, so those two produce
genuinely different files — 135 lines apart here, argon2-cffi-bindings among
them. One committed lockfile cannot equal both outputs, so whichever version
it was built on, the other job failed. Verification now runs once on
Linux/3.11, the canonical generator; every platform still installs from the
lockfile with --require-hashes, which is the part that had to stay everywhere.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01Vtx94nXDmNgXEeThUZP4QN
This commit is contained in:
thePR0M3TH3ANandClaude Opus 5 committed 2026-08-20 09:14:19 -04:00
1 parent bc6ee2288e
commit 4204a23cab
8 files changed
+67 -27

No files matched your search

+27 -18
View File
@@ -77,6 +77,7 @@ def acquire_lock() -> None:
def release_lock() -> None:
LOCK.unlink(missing_ok=True)
# Each target names the source file and the tests that should defend it.
# Running only the relevant suites keeps a full pass tractable; a mutant that
# survives its own suite but would be caught by another is still a finding,
@@ -123,8 +124,7 @@ SECURITY_HINTS = re.compile(
# `kinds` list is half of what it may reach -- and leaving it out hid the
# shape check that stops a string `kinds` becoming a substring match.
# `mnemonic`/`seed` guard the material everything else protects.
r"kind|mnemonic|seed"
r")",
r"kind|mnemonic|seed" r")",
re.IGNORECASE,
)
@@ -192,9 +192,7 @@ def generate(source: str) -> list[Mutant]:
mutated = line.replace(old, new, 1)
if mutated == line:
continue
mutants.append(
Mutant(i, line, mutated, f"{old.strip()} -> {new.strip()}")
)
mutants.append(Mutant(i, line, mutated, f"{old.strip()} -> {new.strip()}"))
# Force a guard to always allow. This is the single most informative
# mutation for security code: if no test notices a check being removed
# entirely, that check is unverified.
@@ -221,7 +219,10 @@ def generate(source: str) -> list[Mutant]:
if (
inline
and not line.rstrip().endswith("{")
and (SECURITY_HINTS.search(inline.group(2)) or REFUSAL.search(inline.group(3)))
and (
SECURITY_HINTS.search(inline.group(2))
or REFUSAL.search(inline.group(3))
)
):
mutants.append(
Mutant(
@@ -247,9 +248,7 @@ def run(cmd: list[str], cwd: Path, timeout: int) -> tuple[int, str]:
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--target", default="all", choices=[*sorted(TARGETS), "all"]
)
parser.add_argument("--target", default="all", choices=[*sorted(TARGETS), "all"])
parser.add_argument(
"--limit", type=int, default=0, help="cap mutants per target (0 = all)"
)
@@ -335,12 +334,12 @@ def main() -> int:
lines[mutant.line_no - 1] = mutant.mutated + ending
path.write_text("".join(lines))
code, _ = run(
["npx", "tsc", "--noEmit"], CLI, timeout=args.timeout
)
code, _ = run(["npx", "tsc", "--noEmit"], CLI, timeout=args.timeout)
if code != 0:
overall_counts["invalid"] += 1
print(f" [{idx}/{len(all_mutants)}] line {mutant.line_no}: invalid (does not typecheck)")
print(
f" [{idx}/{len(all_mutants)}] line {mutant.line_no}: invalid (does not typecheck)"
)
continue
code, _ = run(
@@ -348,11 +347,15 @@ def main() -> int:
)
if code != 0:
overall_counts["killed"] += 1
print(f" [{idx}/{len(all_mutants)}] line {mutant.line_no}: killed ({mutant.description})")
print(
f" [{idx}/{len(all_mutants)}] line {mutant.line_no}: killed ({mutant.description})"
)
else:
overall_counts["survived"] += 1
overall_survivors.append((f"{rel}:{mutant.line_no}", mutant))
print(f" [{idx}/{len(all_mutants)}] line {mutant.line_no}: SURVIVED ({mutant.description})")
print(
f" [{idx}/{len(all_mutants)}] line {mutant.line_no}: SURVIVED ({mutant.description})"
)
finally:
restore()
# Prove the restore worked rather than assuming it: a mutant left
@@ -360,7 +363,9 @@ def main() -> int:
# tree waiting to be committed.
if path.read_text() != original:
release_lock()
raise SystemExit(f"FAILED TO RESTORE {rel} -- fix this before committing")
raise SystemExit(
f"FAILED TO RESTORE {rel} -- fix this before committing"
)
release_lock()
@@ -372,7 +377,9 @@ def main() -> int:
f"{overall_counts['invalid']} invalid (of {total})"
)
if viable:
print(f"mutation score: {overall_counts['killed'] / viable:.0%} of viable mutants caught")
print(
f"mutation score: {overall_counts['killed'] / viable:.0%} of viable mutants caught"
)
if unmeasured:
print(
@@ -383,7 +390,9 @@ def main() -> int:
print(f" {where}")
if overall_survivors:
print(f"\nSURVIVORS — each is either a gap or an equivalent mutant, and needs judging:\n")
print(
f"\nSURVIVORS — each is either a gap or an equivalent mutant, and needs judging:\n"
)
for where, mutant in overall_survivors:
print(f" {where} [{mutant.description}]")
print(f" was: {mutant.original.strip()[:110]}")