dodil/erp-brick-contracts

scripts/mutation_check.py

#!/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:]))