Files
troupe-standup/wizard-work/forge/MUTANT_GUARD_REFACTOR_VERIFICATION.md
T

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_honoured called importlib.import_module(MOD_NAME) unguarded (no try/except). Importing the mutant module ran main(), which leaked the entire probe output to real stdout (the seed file's contents -- 7932 bytes, "SOURCE: 'Muse web artifact..." onward), then sys.exit(0) raised SystemExit(0).
  • SystemExit(0) propagated out of check_path_override_honoured, out of the verifier's main(), and terminated the verifier process with rc 0 -- before the verifier reached its final sys.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_NAME from sys.modules
  • ensures WORK_DIR is on sys.path
  • imports inside redirect_stdout/redirect_stderr (captures, never leaks)
  • catches every BaseException (including SystemExit)
  • returns (module, exc, out, err); exc set iff module is None

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 catches exc and FAILs instead of letting SystemExit kill the process. A defensive assert mod is not None makes 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_import guards report OK on a healthy import.
  • The mutant scan still kills every other mutant identically to before (the fix changes only the no_main_guard case).
  • 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.