import subprocess, sys, os
ROOT = r'C:/Users/decid/Documents/projects/spt-core/.worktrees/351-w5/'
TW = 'crates/spt-store/src/trustwarn.rs'
NS = 'crates/spt/src/api/nowsignal.rs'
WAN = 'crates/spt-daemon/src/wan.rs'
MUTS = [
    ("H1 message_ref takes the attribute raw", TW,
     b"    if let Some(id) = msg_id_attr.filter(|id| crate::msgid::is_short_id(id)) {",
     b"    if let Some(id) = msg_id_attr {",
     "-p spt-daemon -p spt --lib --bins", "a_forged_msg_id"),
    ("H1-peer the proven sender is named unchecked", WAN,
     b"        Some(id) if spt_proto::id::validate_endpoint_id(id).is_ok() => WarnedPeer::Endpoint(id.to_string()),",
     b"        Some(id) => WarnedPeer::Endpoint(id.to_string()),",
     "-p spt-daemon --lib", "a_malformed_proven_sender_is_warned_about_by_its_node"),
    ("M2 seen key is the bare msg_id", TW,
     b'        format!("trust:{}:{}:{}", self.peer_kind, self.peer, self.claim_ref)',
     b'        self.msg_id.clone()',
     "-p spt --bins", "one_message_id_on_two_peers_renders_both"),
    ("M3 reader reads a 64-record tail", TW,
     b"    crate::jsonltail::read_at::<WarningRecord>(&records_file_at(perch_path), RECORD_KEEP)",
     b"    crate::jsonltail::read_at::<WarningRecord>(&records_file_at(perch_path), 64)",
     "-p spt --bins", "a_pending_warning_older_than_64_records_still_renders"),
    ("M3 writer does not retire rendered records first", TW,
     b"            if excess > 0 && r.rendered() {",
     b"            if false {",
     "-p spt --bins", "a_full_file_retires_rendered_records_before_a_pending_one"),
    ("M1 max_lines binds TRUST_WARNINGS (cap_for)", NS,
     b"        if cat == Category::TrustWarnings {\r\n            None\r\n        } else {\r\n            self.max_lines\r\n        }",
     b"        let _ = cat;\r\n        self.max_lines",
     "-p spt --bins", "a_spec_cannot_suppress_trust_warnings|a_zero_line_budget_still_renders"),
    ("L2 lone CR not folded", NS,
     b"            .replace('\\r', \"\\n\")\r\n",
     b"",
     "-p spt --bins", "a_max_size_override_renders_whole_on_one_line"),
    ("R2-1 no per-peer eviction (oldest line trimmed)", TW,
     b"        while records.len() >= RECORD_KEEP {",
     b"        while records.len() >= usize::MAX {",
     "-p spt --bins", "an_unbound_flood_never_evicts_another_peers_pending_warning"),
    ("R2-2 reader keeps every parsed row", TW,
     b"        .filter(WarningRecord::is_well_formed)\r\n",
     b"",
     "-p spt --bins", "a_malformed_row_on_disk_never_renders"),
    ("R2-3 unbound claim key from the msg-id", TW,
     b'            .unwrap_or_else(|| format!("unbound-{}", self.claim_ref))',
     b'            .unwrap_or_else(|| format!("unbound-{}", self.msg_id))',
     "-p spt --bins", "a_repeated_msg_id_on_unbound_messages_warns_each_time"),
    ("R2-4 wiring caps with raw max_lines", NS,
     b"        let mut seen = SeenSet::load(session, cat).with_cap(spec.cap_for(cat));",
     b"        let mut seen = SeenSet::load(session, cat).with_cap(spec.max_lines);",
     "-p spt --bins", "a_zero_line_budget_still_renders_trust_warnings_through_the_poll"),
    ("R2-4b wiring bounds with raw max_lines", NS,
     b"        let lines = spec.bound_for(cat, lines);",
     b"        let lines = { let mut l = lines; if let Some(m) = spec.max_lines { l.truncate(m); } l };",
     "-p spt --bins", "a_zero_line_budget_still_renders_trust_warnings_through_the_poll"),
]
env = dict(os.environ, CARGO_TARGET_DIR=ROOT + 'target')
def git(*a):
    return subprocess.run(["git", *a], cwd=ROOT, capture_output=True, text=True).stdout.strip()
print("sha", git("rev-parse", "HEAD"), "tree", git("rev-parse", "HEAD^{tree}"),
      "dirty_at_start", len(git("status", "--porcelain").splitlines()))
for name, path, old, new, pkgs, filt in MUTS:
    full = ROOT + path
    orig = open(full, 'rb').read()
    if orig.count(old) != 1:
        print(f"MUT {name}: SKIP anchor count {orig.count(old)}"); continue
    open(full, 'wb').write(orig.replace(old, new))
    try:
        cmd = ["cargo", "nextest", "run", "-j", "8", *pkgs.split(), "--no-fail-fast", "-E", f"test(/{filt}/)"]
        p = subprocess.run(cmd, cwd=ROOT, env=env, capture_output=True, text=True, encoding='utf-8', errors='replace')
        out = p.stdout + p.stderr
        summ = [l.strip() for l in out.splitlines() if 'Summary' in l or l.strip().startswith('FAIL') or 'error[' in l]
        verdict = "KILLED" if p.returncode == 100 and any("failed" in l for l in summ if "Summary" in l) else "NOT-KILLED"
        print(f"MUT {name}: {verdict} rc={p.returncode} " + " | ".join(dict.fromkeys(summ)))
    finally:
        open(full, 'wb').write(orig)
print("dirty_at_end", len(git("status", "--porcelain").splitlines()))
