4.4 KiB
MUTANT-GUARD REFACTOR VERIFICATION
Target: wizard-work/forge/verify_triage_dump_pending_refactor.py
Instrument: wizard-work/forge/_mutant_scan_triage_dump_pending.py
Lane: Refactor (forge)
Verdict: FIXED -- the no_main_guard mutant escape is closed.
The defect (measured, not argued)
The no_main_guard mutant strips the if __name__ == "__main__": guard from
_triage_dump_pending_refactored.py, leaving sys.exit(main()) at module scope.
Before this fix, that mutant SURVIVED the verifier:
check_path_override_honouredcalledimportlib.import_module(MOD_NAME)unguarded (notry/except). Importing the mutant module ranmain(), which leaked the entire probe output to real stdout (the seed file's contents -- 7932 bytes, "SOURCE: 'Muse web artifact..." onward), thensys.exit(0)raisedSystemExit(0).SystemExit(0)propagated out ofcheck_path_override_honoured, out of the verifier'smain(), and terminated the verifier process with rc 0 -- before the verifier reached its finalsys.exit(1).- The verifier therefore reported green on a tree carrying a guard-less module.
The fix (behavior-preserving on clean input)
Extracted _safe_import() which centralises the safe-import pattern already
used by check_import_side_effect_free:
- pops
MOD_NAMEfromsys.modules - ensures
WORK_DIRis onsys.path - imports inside
redirect_stdout/redirect_stderr(captures, never leaks) - catches every
BaseException(includingSystemExit) - returns
(module, exc, out, err);excset iff module isNone
Both import sites now use it:
check_import_side_effect_free-- unchanged semantics: exc -> FAIL, output -> FAIL, else OK.check_path_override_honoured-- was unguarded; now catchesexcand FAILs instead of lettingSystemExitkill the process. A defensiveassert mod is not Nonemakes the mutual-exclusion invariant explicit.
exc is None implies mod is not None, so the subsequent
mod.main(path=custom) call is unreachable when the import failed.
Evidence (real commands, real outputs)
$ cd /root/troupe-standup/wizard-work/forge
$ python3 verify_triage_dump_pending_refactor.py
OK: py_compile passes
OK: identical output (exit 0, 7932 bytes)
OK: __main__ guard present
OK: no 'global' statements in code
OK: import is side-effect-free (no probe output at import)
OK: _print_summary, _print_decided, _print_pending, load_data, main extracted; main calls all three print helpers
OK: main() delegates via distinct standalone statements (no ';'-compaction)
OK: main(path) accepts a configurable path parameter
OK: original still exhibits the removed smells (refactor is non-vacuous)
OK: main(path=custom) honours the override (prints custom file, not DEFAULT_PATH)
=== Verification: 10/10 checks passed === exit 0
Mutation scan (copies artifacts to a temp dir, never touches the real files):
$ python3 _mutant_scan_triage_dump_pending.py
no_main_guard DIED exit=1 === Verification: 7/10 checks passed ===
path_ignored DIED exit=1 === Verification: 9/10 checks passed ===
skip_pending DIED exit=1 === Verification: 8/10 checks passed ===
stub_data DIED exit=1 === Verification: 8/10 checks passed ===
remove_path_param DIED exit=1 TypeError: main() got an unexpected keyword argument 'path'
hardcode_source DIED exit=1 === Verification: 9/10 checks passed ===
inline_helpers DIED exit=1 === Verification: 9/10 checks passed ===
Survivors: none
exit 0
Before the fix, no_main_guard was SURVIVED with a leaked probe dump on
stdout and exit=0. After the fix it is DIED (exit 1, 7/10 checks):
check_import_side_effect_free catches the SystemExit AND
check_path_override_honoured now catches it too, so the verifier reaches its
final verdict row and exits non-zero.
Non-vacuity
- The clean (unmutated) refactored module still passes 10/10 -- the added
_safe_importguards report OK on a healthy import. - The mutant scan still kills every other mutant identically to before (the
fix changes only the
no_main_guardcase). - Run twice for determinism; both runs report
Survivors: none.
Scope
Only verify_triage_dump_pending_refactor.py changed. The subject module
_triage_dump_pending_refactored.py is untouched (it already had the guard);
the vulnerability was in the verifier's unguarded second import, not the
refactored subject.