#!/usr/bin/env python
"""Put each key defect back, run the tests that guard it, and require them to FAIL — then restore the code.
python scripts/mutation_check.py # every mutation
python scripts/mutation_check.py chain # one
A test suite that still passes with the defect in place is not protecting the rule (AGENTS.md: verify by breaking).
"""
import pathlib
import subprocess
import sys
ROOT = pathlib.Path(__file__).resolve().parents[1]
SRC = ROOT / "src" / "brick_contracts"
PY = sys.executable
D, A = SRC / "domain", SRC / "application"
MUTATIONS = {
"approval_fingerprint": (
"an approval survives an edit (it is not bound to the draft fingerprint)",
D / "contract.py",
" if a.draft_fingerprint != fp and a.state in (\"pending\", \"escalated\", \"approved\"):",
" if False and a.draft_fingerprint != fp:",
["tests/test_rules.py::test_ct_07_an_approval_binds_to_the_fingerprint_and_an_edit_voids_it",
"tests/test_scenarios.py::test_scenario[SC-CT-02]"]),
"own_approval": (
"an owner approves their own deviation",
D / "contract.py",
" if by and by.lower() in {self.owner.lower(), (self.created_by or \"\").lower()}:",
" if False:",
["tests/test_rules.py::test_ct_07_an_owner_cannot_approve_their_own_deviation"]),
"chain": (
"the verifier does not recompute row hashes (an edited IP passes)",
D / "services.py",
" if want != e.row_hash:\n return False, e.seq",
" if False:\n return False, e.seq",
["tests/test_rules.py::test_ct_32_events_are_hash_chained_and_a_changed_row_is_found",
"tests/test_scenarios.py::test_scenario[SC-CT-11]"]),
"single_use": (
"the signing link is never claimed (SIGN is not single use)",
A / "signing.py",
" link.use(shared) # claimed in OUR transaction",
" pass",
["tests/test_doors.py::test_a_signing_link_reaches_only_its_own_page_and_a_read_link_cannot_sign"]),
"frozen_version": (
"a signature on a version other than the frozen one is accepted (a known failure mode)",
D / "contract.py",
" if cur is None or int(version) != cur.version or (sha_shown or \"\") != cur.sha256:",
" if cur is None:",
["tests/test_rules.py::test_ct_09_a_stale_version_is_refused",
"tests/test_scenarios.py::test_scenario[SC-CT-05]"]),
"edit_after_send": (
"a contract out for signature can still be edited (its words are not frozen)",
D / "contract.py",
" if self.state == OUT_FOR_SIGNATURE:\n raise RuleBroken(f\"version {self.current_version} is out",
" if False:\n raise RuleBroken(f\"version {self.current_version} is out",
["tests/test_rules.py::test_editing_out_for_signature_is_refused_recall_it_first"]),
"employment_404": (
"an employment contract's body is shown to anyone who may see the contract",
A / "support.py",
" return sens != EMPLOYMENT or has(self.caller, EMPLOYMENT_READ)",
" return True",
["tests/test_doors.py::test_an_employment_body_is_404_to_its_owner_and_a_manager_through_both_doors",
"tests/test_scenarios.py::test_scenario[SC-CT-20]"]),
"originator_only": (
"any consumer may fetch the executed copy, not only its originator",
A / "capabilities.py",
" c = u.contracts.get(call.input[\"contract_id\"])\n if c is None or not c.originated_by(names):",
" c = u.contracts.get(call.input[\"contract_id\"])\n if c is None:",
["tests/test_scenarios.py::test_scenario[SC-CT-24]",
"tests/test_tier1.py::test_another_consumer_never_gets_hrs_executed_copy"]),
"elsewhere_signed": (
"an upload marks the signers SIGNED, a status no event backs (a known failure mode)",
D / "contract.py",
" for s in self.signers:\n s.status = S_ELSEWHERE",
" for s in self.signers:\n s.status = S_SIGNED",
["tests/test_rules.py::test_ct_12_signed_elsewhere_never_marks_a_signer_signed",
"tests/test_scenarios.py::test_scenario[SC-CT-08]"]),
"placeholder_copy": (
"the executed copy is a placeholder, not a rendered file (a known failure mode)",
A / "workflow.py",
" executed = renderer.append(stored[0], \"\\n\".join(lines), footer=f\"{c.contract_id} — certificate\")",
" executed = f\"signed-online:{c.contract_id}\".encode()",
["tests/test_flow.py::test_the_whole_story"]),
"link_signs_for_us": (
"a public link can sign for our side",
D / "contract.py",
" if via == EXTERNAL and s.kind != EXTERNAL:\n raise NotFound(\"no such signer\")",
" if False:\n raise NotFound(\"no such signer\")",
["tests/test_rules.py::test_ct_17_a_link_never_signs_for_our_side_and_the_app_never_for_theirs"]),
"deed_online": (
"a deed can be sent for online signing",
D / "contract_type.py",
" if execution == DEED:\n esign = False",
" if False:\n esign = False",
["tests/test_rules.py::test_ct_02_a_deed_type_is_never_signed_online",
"tests/test_scenarios.py::test_scenario[SC-CT-08]"]),
}
def run(tests):
r = subprocess.run([PY, "-m", "pytest", "-q", "--tb=no", "-p", "no:cacheprovider", *tests], cwd=ROOT,
capture_output=True, text=True)
return r.returncode, (r.stdout.strip().splitlines() or [""])[-1]
def main(names):
results = []
for name in names or MUTATIONS:
title, path, old, new, tests = MUTATIONS[name]
original = path.read_text()
if old not in original:
results.append((name, "STALE", f"the code to mutate is not in {path.name}"))
continue
try:
path.write_text(original.replace(old, new, 1))
code, last = run(tests)
finally:
path.write_text(original)
results.append((name, "CAUGHT" if code != 0 else "MISSED", f"{title}: {last}"))
for name, verdict, detail in results:
print(f"{verdict:7} {name:22} {detail}")
return 0 if all(v == "CAUGHT" for _, v, _ in results) else 1
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))