#!/usr/bin/env python3
"""
Phase 3 Local M5 Air Healing & Transplant Validation Runner (v2 — Multi-Prompt & Real-Trace Validated)
======================================================================================================
Implements and validates Phase 3 (`PLAN.md` §2.2, §2.4, §5, and §7.1) on the 13" M5 MacBook Air (24 GB):
1. Signed-d AWQ/Lloyd-Max Q2_0 Expert Healing (`vendor/strata/metal/src/heal_k256_q2_0.cpp`):
   - Unlocks the unused sign bit of the IEEE fp16 scale `d` in `GGML_TYPE_Q2_0` (type 42) so each 64-weight
     block chooses between `{-|d|, 0, +|d|, +2|d|}` (`d > 0`) and `{-2|d|, -|d|, 0, +|d|}` (`d < 0`) with
     0 extra bits and 0 Metal shader changes.
   - Weights `W_gate` and `W_up` quantization errors by tempered real activation RMS `sqrt(E[mixed_d^2])`
     and `W_down` by per-expert analytical SwiGLU RMS `(sigma_g * sigma_u)^(1/2)` over the 1,022-token
     disjoint multi-domain calibration trace (`data/m5_val_prompts/cal_disjoint_1024tok.txt`, including
     `<tools>` + `<tool_call>`, target-voice `persona`, `ops`, `ja`, and R-Tuning abstention).
2. Real-Trace Per-Layer Scale Calibration + Full-Rank Shared-Expert Ridge Healing (`swift_writers_plus_router`):
   - Calibrates per-layer `W_down` `fp16 d` scales `alpha_l = <y_ref, y_q2> / ||y_q2||_2^2` (`gamma = 0.00`,
     exact real-trace MSE optimum across all 1,022 tokens and all 256 routed experts, eliminating Gaussian
     depth overshoot).
   - Solves a full-rank (`T = 1,022 > D = 640`) ridge-regularized least-squares update on
     `blk.0..47.ffn_down_shexp.weight`, monotonically quantizes `Q8_0` (L0) / `IQ4_NL` (L1..47) with 25 fine
     scale multipliers so no 32-weight block ever increases reconstruction error, and byte-level transplants
     the 48 modified `ffn_down_shexp` tensors into `K256-q2_0-healed-metal.gguf` via
     `scripts/phase3_lora_and_transplant.py` (`transplant_gguf_tensors`), leaving all 1,175 other tensors
     100% bit-identical.
3. Evaluates teacher-forced full-vocabulary (248,320) FP64 KLD, PPL, and top-1 agreement across both the
   3-domain 192-token canary set and the 4-stratum unseen multi-window benchmark (`ops12_java_turn1`,
   `cj20_tool_5turn_t3`, `ja22_pedal`, `persona0_t2`, 512 tokens) + 22-window (2,686-token) unseen audit
   + 9-prompt diverse speed/hit-rate suite, with 0.0 MiB swapouts under `MemoryWatchdog`.
"""

import argparse
import datetime
import json
import os
import shutil
import subprocess
import sys
import time
from pathlib import Path
from typing import Any, Dict, List

sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from scripts.phase3_lora_and_transplant import (
    f32_to_f16_scalar,
    transplant_gguf_tensors,
)
from scripts.run_phase0_phase2_m5_validation import (
    _madvise_file,
    read_vm_stat,
    run_guarded_llama as _raw_run_guarded_llama,
    sha256_file,
    warm_file_cache,
)


def evict_file_cache(path: str) -> bool:
    return _madvise_file(path, 4)


def run_guarded_llama(cmd: List[str], **kwargs: Any) -> Dict[str, Any]:
    """Runs _raw_run_guarded_llama; if MemoryWatchdog yields to a transient external build spike (e.g. rustc),
    waits for available memory to recover and retries up to 4 times."""
    for attempt in range(5):
        while read_vm_stat()["avail_gib"] < 9.0:
            time.sleep(0.5)
        res = _raw_run_guarded_llama(cmd, **kwargs)
        if res["returncode"] == 0 and "perf" in res and (res["perf"].get("eval_tok_per_s") or res["perf"].get("cand_ppl")):
            return res
        print(f"  [yielded to external memory spike on attempt {attempt + 1}: {res.get('oom_fuse_reason')}; waiting for headroom...]", flush=True)
        time.sleep(2.0)
    return res


def quantize_block_signed_vs_unsigned_test() -> None:
    """Verifies that Signed-d AWQ Q2_0 strictly reduces MSE vs unsigned d > 0 on negative-skewed blocks."""
    x = [0.0] * 64
    for i in range(8):
        x[i] = -2.0
    for i in range(8, 24):
        x[i] = -1.0
    for i in range(24, 40):
        x[i] = 0.95
    amax = max(abs(v) for v in x)

    def best_err(allow_neg: bool) -> float:
        seeds = [0.42, 0.52, 0.64, 0.78, 0.94]
        if allow_neg:
            seeds += [-0.42, -0.52, -0.64, -0.78, -0.94]
        best_mse = 1e30
        for s in seeds:
            d = amax * s
            for _ in range(2):
                inv = 1.0 / d
                qs = [max(-1, min(2, int(round(v * inv)))) for v in x]
                qq = sum(q * q for q in qs)
                qw = sum(q * v for q, v in zip(qs, x))
                if qq == 0 or (d > 0 and qw <= 0) or (d < 0 and qw >= 0):
                    break
                d = f32_to_f16_scalar(qw / qq)
                mse = sum((d * q - v) ** 2 for q, v in zip(qs, x))
                if mse < best_mse:
                    best_mse = mse
        return best_mse

    err_pos = best_err(allow_neg=False)
    err_sgn = best_err(allow_neg=True)
    assert err_sgn < 0.25 * err_pos, f"Expected Signed-d MSE << Unsigned-d MSE, got {err_sgn} vs {err_pos}"


def verify_disjoint_calibration_prompts() -> None:
    """Verifies that cal_disjoint_1024tok.txt has zero 16-gram overlap with all 22 unseen windows and 3 eval_tf_* files."""
    p_dir = Path(__file__).resolve().parent.parent / "data" / "m5_val_prompts"
    cal_path = p_dir / "cal_disjoint_1024tok.txt"
    assert cal_path.exists(), f"Missing {cal_path}"
    cal_ids = [int(x) for x in cal_path.read_text(encoding="utf-8").split()]
    assert len(cal_ids) == 1024, f"Expected 1024 calibration tokens, got {len(cal_ids)}"
    cal_ngrams = {tuple(cal_ids[i : i + 16]) for i in range(len(cal_ids) - 15)}

    for dom in ("ops_tools", "chat_json", "ja"):
        ev_path = p_dir / f"eval_tf_{dom}.txt"
        assert ev_path.exists(), f"Missing {ev_path}"
        ev_ids = [int(x) for x in ev_path.read_text(encoding="utf-8").split()]
        scored_ids = ev_ids[-64:]
        ev_ngrams = {tuple(scored_ids[i : i + 16]) for i in range(len(scored_ids) - 15)}
        overlap = cal_ngrams & ev_ngrams
        assert not overlap, f"Calibration overlap detected with eval_tf_{dom}.txt: {len(overlap)} 16-grams"

    manifest_path = p_dir / "turns" / "manifest.json"
    assert manifest_path.exists(), f"Missing {manifest_path}"
    manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
    assert len(manifest) == 22, f"Expected 22 windows in manifest.json, got {len(manifest)}"
    expected_ctx_overlap = {"cj03_toolcall", "cj20_t3_answer_tool", "cj20_tool_5turn_t3", "persona3_t4"}
    clean_ctx_count = 0
    for item in manifest:
        w_path = p_dir / "turns" / f"{item['name']}.txt"
        w_ids = [int(x) for x in w_path.read_text(encoding="utf-8").split()]
        scored = w_ids[item["n_prompt"] : item["n_prompt"] + item["n_gen"]]
        w_ngrams = {tuple(scored[i : i + 16]) for i in range(len(scored) - 15)}
        overlap = cal_ngrams & w_ngrams
        assert not overlap, f"Calibration scored-span overlap detected with unseen window {item['name']}: {len(overlap)} 16-grams"
        full_ngrams = {tuple(w_ids[i : i + 16]) for i in range(len(w_ids) - 15)}
        ctx_ov = cal_ngrams & full_ngrams
        if not ctx_ov:
            clean_ctx_count += 1
        else:
            assert item["name"] in expected_ctx_overlap, f"Unexpected context overlap in {item['name']}: {len(ctx_ov)} 16-grams"
    assert clean_ctx_count == 18, f"Expected 18 context-clean windows, got {clean_ctx_count}"


def run_self_test() -> int:
    quantize_block_signed_vs_unsigned_test()
    verify_disjoint_calibration_prompts()
    print("scripts/run_phase3_local_healing.py self-test: PASS")
    return 0


def transplant_writers_subprocess(orig_gguf: Path, target_gguf: Path, writers_bin: Path) -> Dict[str, Any]:
    """Loads the 48 modified ffn_down_shexp replacement tensors from writers_bin and transplants them."""
    sub_code = f"""
import json, os, struct, sys
from pathlib import Path
sys.path.insert(0, {repr(str(Path(__file__).resolve().parent.parent))})
from scripts.phase3_lora_and_transplant import transplant_gguf_tensors

replacements = {{}}
with open({repr(str(writers_bin))}, "rb") as f:
    (n_tensors,) = struct.unpack("<I", f.read(4))
    for _ in range(n_tensors):
        (nlen,) = struct.unpack("<I", f.read(4))
        name = f.read(nlen).decode("utf-8")
        gtype, nbytes = struct.unpack("<II", f.read(8))
        raw = f.read(nbytes)
        replacements[name] = (gtype, raw)

tmp_out = {repr(str(target_gguf) + ".tmp")}
res = transplant_gguf_tensors(
    {repr(str(orig_gguf))},
    tmp_out,
    replacements,
    scope="swift_writers_plus_router",
)
os.replace(tmp_out, {repr(str(target_gguf))})
print(json.dumps(res))
"""
    out = subprocess.check_output([sys.executable, "-B", "-c", sub_code], text=True)
    return json.loads(out.strip().splitlines()[-1])


def main() -> int:
    parser = argparse.ArgumentParser(description="Phase 3 Local M5 Air Healing & Transplant Validation Runner")
    parser.add_argument("--self-test", action="store_true", help="Run unit self-tests and exit")
    parser.add_argument(
        "--output",
        default="results/phase3_local_healing_validation.json",
        help="Output JSON report path",
    )
    args = parser.parse_args()
    if args.self_test:
        return run_self_test()

    run_self_test()
    root = Path(__file__).resolve().parent.parent
    build_dir = root / "vendor" / "strata" / "build-metal"
    strata_cli = build_dir / "strata_window_cli"
    heal_bin = build_dir / "heal_k256_q2_0"

    iq3s_gguf = root / "models" / "K256_reap_l2_sum" / "K256-metal.gguf"
    unhealed_q2_gguf = root / "models" / "K256_reap_l2_sum" / "K256-q2_0-metal.gguf"
    healed_q2_gguf = root / "models" / "K256_reap_l2_sum" / "K256-q2_0-healed-metal.gguf"
    ple_gguf = root / "models" / "K256_reap_l2_sum" / "Swift-Qwen3.8-Flash-Next-GSQ-RCO-IQ3_S-K256_reap_l2_sum-00001-of-00002.gguf"
    prof_bin = root / "models" / "K256_reap_l2_sum" / "K256-expert-profile.bin"
    cal_prompt = root / "data" / "m5_val_prompts" / "cal_disjoint_1024tok.txt"

    base_env = {
        "STRATA_METAL_EXPERT_PROFILE": str(prof_bin.resolve()),
        "STRATA_METAL_ARENA_GB": "3.4",
        "STRATA_METAL_WIRE_ARENA": "0",
        "STRATA_METAL_MLOCK_DENSE": "1",
        "STRATA_METAL_SEPARATE_PROMPT_BUF": "1",
        "STRATA_METAL_PROMPT_GEMM": "1",
        "STRATA_METAL_PROMPT_GEMM_MIN": "16",
        "STRATA_METAL_PROMPT_CHUNK": "1024",
        "STRATA_METAL_READ_SPLIT": "2",
        "STRATA_METAL_IO_THREADS": "8",
        "STRATA_METAL_PREFETCH": "8",
        "STRATA_METAL_PREFETCH2": "0",
        "STRATA_METAL_PREFETCH_MRU": "0",
        "STRATA_METAL_MATH": "fast",
        "STRATA_METAL_PROFILE": "1",
    }

    # Ensure 1,022-token multi-domain teacher calibration dump exists
    cal_cache_dir = root / "models" / "K256_reap_l2_sum" / "cal_cache"
    cal_cache_dir.mkdir(parents=True, exist_ok=True)
    cal_pfx = str(cal_cache_dir / "cal_iq3s_1024")
    yr_q2_bin = str(cal_cache_dir / "cal_teacher_yr_q2_1024.bin")
    if not Path(cal_pfx + ".mixed").exists() or not Path(cal_pfx + ".yr").exists():
        print("=== Dumping 1,022-token disjoint teacher MoE activations from IQ3_S ===", flush=True)
        warm_file_cache(str(iq3s_gguf))
        env_d = dict(base_env, STRATA_METAL_MOCE_BONUS="0.0", STRATA_METAL_DUMP_MOE=cal_pfx)
        run_guarded_llama(
            [str(strata_cli), "--metal-gguf", str(iq3s_gguf), "--ple-gguf", str(ple_gguf),
             "--tokens-file", str(cal_prompt), "--n-expert", "256", "--ctx", "4096",
             "--n-prompt", "1023", "--n-gen", "1"],
            min_avail_gib=2.2, max_swapout_delta_mib=64.0, timeout_sec=180, env_overrides=env_d,
        )
        evict_file_cache(str(iq3s_gguf))
        evict_file_cache(str(iq3s_gguf) + ".experts")

    orig_dense_bak = root / "models" / "K256_reap_l2_sum" / "K256-q2_0-healed-metal.gguf.orig_dense_bak"
    if not orig_dense_bak.exists():
        shutil.copy2(unhealed_q2_gguf, orig_dense_bak)

    d_bak = Path(str(healed_q2_gguf) + ".experts.d_bak.bin")
    q_meta = Path(str(healed_q2_gguf) + ".experts.quant.meta")
    import struct as _struct
    _gu_b, = _struct.unpack("<I", _struct.pack("<f", 0.50))
    _gd_b, = _struct.unpack("<I", _struct.pack("<f", 0.30))
    _h = 0xcbf29ce484222325
    for _b in cal_pfx.encode("utf-8"):
        _h = ((_h ^ _b) * 0x100000001b3) & 0xFFFFFFFFFFFFFFFF
    _want_qmeta = _struct.pack("<3Q", (_gu_b << 32) | _gd_b, _h, 1022)
    if (
        not healed_q2_gguf.with_suffix(".gguf.experts").exists()
        or not d_bak.exists()
        or not q_meta.exists()
        or q_meta.read_bytes() != _want_qmeta
    ):
        print("=== Running Stage 1: Signed-d AWQ/Lloyd-Max Q2_0 Expert Quantization ===", flush=True)
        subprocess.run(
            [str(heal_bin), "--quantize", str(iq3s_gguf), str(healed_q2_gguf), "0.50", "0.30", cal_pfx],
            check=True,
        )
        shutil.copy2(healed_q2_gguf, orig_dense_bak)

    print("=== Running Stage 2: Real-Trace Scale Calibration (T=1022, gamma=0.00) + Shared-Expert Ridge Solver ===", flush=True)
    writers_bin = Path("/tmp/healed_writers_1024.bin")
    t_heal0 = time.monotonic()
    heal_proc = subprocess.run(
        [
            str(heal_bin),
            "--calibrate-and-heal",
            str(iq3s_gguf),
            str(healed_q2_gguf),
            cal_pfx,
            yr_q2_bin,
            str(writers_bin),
            "0.00",
            "0.15",
            "6.0",
        ],
        capture_output=True,
        text=True,
        check=True,
    )
    heal_sec = round(time.monotonic() - t_heal0, 3)
    print(heal_proc.stdout.strip(), flush=True)

    print("=== Running Stage 3: Byte-Level GGUF Transplant (48 modified ffn_down_shexp tensors) ===", flush=True)
    tp_res = transplant_writers_subprocess(orig_dense_bak, healed_q2_gguf, writers_bin)
    untouched_cnt = tp_res["total_tensors"] - tp_res["transplanted_tensors_count"]
    print(
        f"Transplanted {tp_res['transplanted_tensors_count']} tensors | "
        f"untouched: {untouched_cnt} / {tp_res['total_tensors']} tensors (bit-identical)",
        flush=True,
    )

    # 1. Speed + Cache Hit Rate + 0.0 MiB Swapout Verification on Healed Q2_0 FIRST (thermally cool)
    warm_file_cache(str(healed_q2_gguf))
    speed_runs: List[Dict[str, Any]] = []
    for step_name, moce_val, p_rel, n_p in [
        ("healed_q2_0_moce085_short_43tok", "0.85", "data/m5_val_prompts/prompt_43tok_wal.txt", 43),
        ("healed_q2_0_moce085_long_1024tok", "0.85", "data/m5_val_prompts/prompt_1024tok_ops.txt", 1024),
        ("healed_q2_0_moce085_long_2048tok", "0.85", "data/m5_val_prompts/prompt_2048tok_ops.txt", 2048),
        ("healed_q2_0_moce0_short_43tok", "0.0", "data/m5_val_prompts/prompt_43tok_wal.txt", 43),
    ]:
        r_sp = run_guarded_llama(
            [str(strata_cli), "--metal-gguf", str(healed_q2_gguf), "--ple-gguf", str(ple_gguf),
             "--tokens-file", str((root / p_rel).resolve()), "--n-expert", "256", "--ctx", "4096",
             "--n-prompt", str(n_p), "--n-gen", "64"],
            min_avail_gib=2.2, max_swapout_delta_mib=64.0, timeout_sec=180,
            env_overrides=dict(base_env, STRATA_METAL_MOCE_BONUS=moce_val),
        )
        r_sp["step"] = step_name
        speed_runs.append(r_sp)
        pf = r_sp["perf"]
        print(
            f"{step_name}: prefill={pf['prefill_tok_per_s']} tok/s | decode={pf['eval_tok_per_s']} tok/s | "
            f"hit={pf['combined_hit_rate']} | misses/tok={pf['misses_per_tok']} | swap={r_sp['max_swapout_delta_mib']} MiB",
            flush=True,
        )

    domains = ["ops_tools", "chat_json", "ja"]
    kld_runs: Dict[str, Dict[str, Any]] = {
        "iq3s_moce0_ref": {},
        "unhealed_q2_0_moce0_vs_iq3s": {},
        "unhealed_q2_0_moce085_vs_iq3s": {},
        "unhealed_q2_0_moce085_vs_unhealed_q2_0_moce0": {},
        "healed_q2_0_moce0_vs_iq3s": {},
        "healed_q2_0_moce085_vs_iq3s": {},
        "healed_q2_0_moce085_vs_healed_q2_0_moce0": {},
    }

    # 2. IQ3_S Reference (3 canary domains)
    evict_file_cache(str(healed_q2_gguf))
    evict_file_cache(str(healed_q2_gguf) + ".experts")
    warm_file_cache(str(iq3s_gguf))
    for dom in domains:
        pfile = root / "data" / "m5_val_prompts" / f"eval_tf_{dom}.txt"
        n_tot = len(pfile.read_text(encoding="utf-8").split())
        n_p = n_tot - 64
        ref_bin = f"/tmp/p3_ref_iq3s_{dom}.bin"
        r = run_guarded_llama(
            [str(strata_cli), "--metal-gguf", str(iq3s_gguf), "--ple-gguf", str(ple_gguf),
             "--tokens-file", str(pfile), "--n-expert", "256", "--ctx", "4096",
             "--n-prompt", str(n_p), "--n-gen", "64", "--logits-out", ref_bin],
            min_avail_gib=2.2, max_swapout_delta_mib=64.0, timeout_sec=180,
            env_overrides=dict(base_env, STRATA_METAL_MOCE_BONUS="0.0"),
        )
        r["step"] = f"tf_{dom}_iq3s_moce0_ref"
        kld_runs["iq3s_moce0_ref"][dom] = r["perf"]
        speed_runs.append(r)
        print(f"IQ3_S ref {dom}: ppl={r['perf']['cand_ppl']}", flush=True)

    # 3. Unhealed Q2_0 (MOCE=0.0 and MOCE=0.85 on 3 canary domains)
    evict_file_cache(str(iq3s_gguf))
    evict_file_cache(str(iq3s_gguf) + ".experts")
    warm_file_cache(str(unhealed_q2_gguf))
    for dom in domains:
        pfile = root / "data" / "m5_val_prompts" / f"eval_tf_{dom}.txt"
        n_tot = len(pfile.read_text(encoding="utf-8").split())
        n_p = n_tot - 64
        ref_bin = f"/tmp/p3_ref_iq3s_{dom}.bin"
        u_m0_bin = f"/tmp/p3_unhealed_q2_m0_{dom}.bin"
        r0 = run_guarded_llama(
            [str(strata_cli), "--metal-gguf", str(unhealed_q2_gguf), "--ple-gguf", str(ple_gguf),
             "--tokens-file", str(pfile), "--n-expert", "256", "--ctx", "4096",
             "--n-prompt", str(n_p), "--n-gen", "64", "--logits-out", u_m0_bin, "--logits-ref", ref_bin],
            min_avail_gib=2.2, max_swapout_delta_mib=64.0, timeout_sec=180,
            env_overrides=dict(base_env, STRATA_METAL_MOCE_BONUS="0.0"),
        )
        r0["step"] = f"tf_{dom}_unhealed_q2_0_moce0_vs_iq3s"
        kld_runs["unhealed_q2_0_moce0_vs_iq3s"][dom] = r0["perf"]
        speed_runs.append(r0)

        r85 = run_guarded_llama(
            [str(strata_cli), "--metal-gguf", str(unhealed_q2_gguf), "--ple-gguf", str(ple_gguf),
             "--tokens-file", str(pfile), "--n-expert", "256", "--ctx", "4096",
             "--n-prompt", str(n_p), "--n-gen", "64", "--logits-ref", ref_bin, "--logits-ref2", u_m0_bin],
            min_avail_gib=2.2, max_swapout_delta_mib=64.0, timeout_sec=180,
            env_overrides=dict(base_env, STRATA_METAL_MOCE_BONUS="0.85"),
        )
        Path(u_m0_bin).unlink(missing_ok=True)
        r85["step"] = f"tf_{dom}_unhealed_q2_0_moce085_vs_iq3s"
        p85 = dict(r85["perf"])
        p85_r2 = p85.pop("ref2")
        kld_runs["unhealed_q2_0_moce085_vs_iq3s"][dom] = p85
        kld_runs["unhealed_q2_0_moce085_vs_unhealed_q2_0_moce0"][dom] = p85_r2
        speed_runs.append(r85)
        print(
            f"Unhealed Q2_0 {dom}: m0_kld={r0['perf']['mean_kld']} ({r0['perf']['top1_agree_pct']}%) | "
            f"m85_kld={p85['mean_kld']} ({p85['top1_agree_pct']}%)",
            flush=True,
        )

    # 4. Healed Q2_0 (MOCE=0.0 and MOCE=0.85 on 3 canary domains + 4 unseen multi-stratum windows)
    evict_file_cache(str(unhealed_q2_gguf))
    evict_file_cache(str(unhealed_q2_gguf) + ".experts")
    warm_file_cache(str(healed_q2_gguf))
    for dom in domains:
        pfile = root / "data" / "m5_val_prompts" / f"eval_tf_{dom}.txt"
        n_tot = len(pfile.read_text(encoding="utf-8").split())
        n_p = n_tot - 64
        ref_bin = f"/tmp/p3_ref_iq3s_{dom}.bin"
        h_m0_bin = f"/tmp/p3_healed_q2_m0_{dom}.bin"
        r0 = run_guarded_llama(
            [str(strata_cli), "--metal-gguf", str(healed_q2_gguf), "--ple-gguf", str(ple_gguf),
             "--tokens-file", str(pfile), "--n-expert", "256", "--ctx", "4096",
             "--n-prompt", str(n_p), "--n-gen", "64", "--logits-out", h_m0_bin, "--logits-ref", ref_bin],
            min_avail_gib=2.2, max_swapout_delta_mib=64.0, timeout_sec=180,
            env_overrides=dict(base_env, STRATA_METAL_MOCE_BONUS="0.0"),
        )
        r0["step"] = f"tf_{dom}_healed_q2_0_moce0_vs_iq3s"
        kld_runs["healed_q2_0_moce0_vs_iq3s"][dom] = r0["perf"]
        speed_runs.append(r0)

        r85 = run_guarded_llama(
            [str(strata_cli), "--metal-gguf", str(healed_q2_gguf), "--ple-gguf", str(ple_gguf),
             "--tokens-file", str(pfile), "--n-expert", "256", "--ctx", "4096",
             "--n-prompt", str(n_p), "--n-gen", "64", "--logits-ref", ref_bin, "--logits-ref2", h_m0_bin],
            min_avail_gib=2.2, max_swapout_delta_mib=64.0, timeout_sec=180,
            env_overrides=dict(base_env, STRATA_METAL_MOCE_BONUS="0.85"),
        )
        Path(h_m0_bin).unlink(missing_ok=True)
        Path(ref_bin).unlink(missing_ok=True)
        r85["step"] = f"tf_{dom}_healed_q2_0_moce085_vs_iq3s"
        p85 = dict(r85["perf"])
        p85_r2 = p85.pop("ref2")
        kld_runs["healed_q2_0_moce085_vs_iq3s"][dom] = p85
        kld_runs["healed_q2_0_moce085_vs_healed_q2_0_moce0"][dom] = p85_r2
        speed_runs.append(r85)
        print(
            f"Healed Q2_0 {dom}: m0_kld={r0['perf']['mean_kld']} ({r0['perf']['top1_agree_pct']}%) | "
            f"m85_kld={p85['mean_kld']} ({p85['top1_agree_pct']}%)",
            flush=True,
        )

    # 5. Evaluate 4-stratum unseen multi-window benchmark (512 tokens: ops12_java_turn1, cj20_tool_5turn_t3, ja22_pedal, persona0_t2)
    manifest = {
        m["name"]: m
        for m in json.loads((root / "data" / "m5_val_prompts" / "turns" / "manifest.json").read_text(encoding="utf-8"))
    }
    kld_22win = json.loads((root / "results" / "phase3_22win_kld_audit_v1.json").read_text(encoding="utf-8"))
    kld_22win_v2 = json.loads((root / "results" / "phase3_22win_kld_audit_v2.json").read_text(encoding="utf-8"))
    speed_9prompt_v2 = json.loads((root / "results" / "phase3_9prompt_speed_audit_v2.json").read_text(encoding="utf-8"))

    unseen_4win_names = ["ops12_java_turn1", "cj20_tool_5turn_t3", "ja22_pedal", "persona0_t2"]
    unseen_4win_results: Dict[str, Any] = {}
    for wname in unseen_4win_names:
        m = manifest[wname]
        pfile = root / "data" / "m5_val_prompts" / "turns" / f"{wname}.txt"
        ref_bin = f"/tmp/ref_4win_{wname}.bin"
        if not Path(ref_bin).exists() or Path(ref_bin).stat().st_size != m["n_gen"] * 248320 * 4:
            evict_file_cache(str(healed_q2_gguf))
            warm_file_cache(str(iq3s_gguf))
            run_guarded_llama(
                [str(strata_cli), "--metal-gguf", str(iq3s_gguf), "--ple-gguf", str(ple_gguf),
                 "--tokens-file", str(pfile), "--n-expert", "256", "--ctx", "4096",
                 "--n-prompt", str(m["n_prompt"]), "--n-gen", str(m["n_gen"]), "--logits-out", ref_bin],
                min_avail_gib=2.2, max_swapout_delta_mib=64.0, timeout_sec=180,
                env_overrides=dict(base_env, STRATA_METAL_MOCE_BONUS="0.0"),
            )
            evict_file_cache(str(iq3s_gguf))
            evict_file_cache(str(iq3s_gguf) + ".experts")
            warm_file_cache(str(healed_q2_gguf))

        h_m0_bin = f"/tmp/p3_4win_m0_{wname}.bin"
        r0_w = run_guarded_llama(
            [str(strata_cli), "--metal-gguf", str(healed_q2_gguf), "--ple-gguf", str(ple_gguf),
             "--tokens-file", str(pfile), "--n-expert", "256", "--ctx", "4096",
             "--n-prompt", str(m["n_prompt"]), "--n-gen", str(m["n_gen"]),
             "--logits-out", h_m0_bin, "--logits-ref", ref_bin],
            min_avail_gib=2.2, max_swapout_delta_mib=64.0, timeout_sec=180,
            env_overrides=dict(base_env, STRATA_METAL_MOCE_BONUS="0.0"),
        )
        r0_w["step"] = f"tf_unseen_{wname}_healed_v2_moce0_vs_iq3s"
        speed_runs.append(r0_w)

        r85_w = run_guarded_llama(
            [str(strata_cli), "--metal-gguf", str(healed_q2_gguf), "--ple-gguf", str(ple_gguf),
             "--tokens-file", str(pfile), "--n-expert", "256", "--ctx", "4096",
             "--n-prompt", str(m["n_prompt"]), "--n-gen", str(m["n_gen"]),
             "--logits-ref", ref_bin, "--logits-ref2", h_m0_bin],
            min_avail_gib=2.2, max_swapout_delta_mib=64.0, timeout_sec=180,
            env_overrides=dict(base_env, STRATA_METAL_MOCE_BONUS="0.85"),
        )
        Path(h_m0_bin).unlink(missing_ok=True)
        r85_w["step"] = f"tf_unseen_{wname}_healed_v2_moce085_vs_iq3s"
        speed_runs.append(r85_w)
        p85_w = dict(r85_w["perf"])
        p85_w_r2 = p85_w.pop("ref2")

        unseen_4win_results[wname] = {
            "unhealed_moce0_kld": kld_22win[f"unhealed_m0.0_{wname}"]["perf"]["mean_kld"],
            "unhealed_moce0_top1_pct": kld_22win[f"unhealed_m0.0_{wname}"]["perf"]["top1_agree_pct"],
            "unhealed_moce085_kld": kld_22win[f"unhealed_m0.85_{wname}"]["perf"]["mean_kld"],
            "unhealed_moce085_top1_pct": kld_22win[f"unhealed_m0.85_{wname}"]["perf"]["top1_agree_pct"],
            "v1_gauss_healed_moce0_kld": kld_22win[f"healed_m0.0_{wname}"]["perf"]["mean_kld"],
            "v1_gauss_healed_moce085_kld": kld_22win[f"healed_m0.85_{wname}"]["perf"]["mean_kld"],
            "v2_real_healed_moce0_kld": r0_w["perf"]["mean_kld"],
            "v2_real_healed_moce0_top1_pct": r0_w["perf"]["top1_agree_pct"],
            "v2_real_healed_moce085_kld": p85_w["mean_kld"],
            "v2_real_healed_moce085_top1_pct": p85_w["top1_agree_pct"],
            "v2_real_isolated_moce_kld": p85_w_r2["mean_kld"],
        }
        print(
            f"Unseen 4-stratum {wname}: unhealed_m85={unseen_4win_results[wname]['unhealed_moce085_kld']:.4f} | "
            f"v1_m85={unseen_4win_results[wname]['v1_gauss_healed_moce085_kld']:.4f} | "
            f"v2_m85={p85_w['mean_kld']:.4f} ({p85_w['top1_agree_pct']}%)",
            flush=True,
        )

    for key in [
        "unhealed_q2_0_moce0_vs_iq3s",
        "unhealed_q2_0_moce085_vs_iq3s",
        "unhealed_q2_0_moce085_vs_unhealed_q2_0_moce0",
        "healed_q2_0_moce0_vs_iq3s",
        "healed_q2_0_moce085_vs_iq3s",
        "healed_q2_0_moce085_vs_healed_q2_0_moce0",
    ]:
        blk = kld_runs[key]
        blk["macro_mean_kld"] = round(sum(blk[d]["mean_kld"] for d in domains) / 3.0, 6)
        blk["macro_top1_agree_pct"] = round(sum(blk[d]["top1_agree_pct"] for d in domains) / 3.0, 2)

    u_m0 = kld_runs["unhealed_q2_0_moce0_vs_iq3s"]
    u_m85 = kld_runs["unhealed_q2_0_moce085_vs_iq3s"]
    u_moce = kld_runs["unhealed_q2_0_moce085_vs_unhealed_q2_0_moce0"]
    h_m0 = kld_runs["healed_q2_0_moce0_vs_iq3s"]
    h_m85 = kld_runs["healed_q2_0_moce085_vs_iq3s"]
    h_moce = kld_runs["healed_q2_0_moce085_vs_healed_q2_0_moce0"]

    sp_by_name = {r["step"]: r["perf"] for r in speed_runs}
    s0_43 = sp_by_name["healed_q2_0_moce0_short_43tok"]
    s85_43 = sp_by_name["healed_q2_0_moce085_short_43tok"]
    s85_1k = sp_by_name["healed_q2_0_moce085_long_1024tok"]
    s85_2k = sp_by_name["healed_q2_0_moce085_long_2048tok"]

    # Compute 4-stratum unseen means
    u4_m0 = round(sum(unseen_4win_results[w]["unhealed_moce0_kld"] for w in unseen_4win_names) / 4.0, 6)
    u4_m85 = round(sum(unseen_4win_results[w]["unhealed_moce085_kld"] for w in unseen_4win_names) / 4.0, 6)
    v1_4_m0 = round(sum(unseen_4win_results[w]["v1_gauss_healed_moce0_kld"] for w in unseen_4win_names) / 4.0, 6)
    v1_4_m85 = round(sum(unseen_4win_results[w]["v1_gauss_healed_moce085_kld"] for w in unseen_4win_names) / 4.0, 6)
    v2_4_m0 = round(sum(unseen_4win_results[w]["v2_real_healed_moce0_kld"] for w in unseen_4win_names) / 4.0, 6)
    v2_4_m85 = round(sum(unseen_4win_results[w]["v2_real_healed_moce085_kld"] for w in unseen_4win_names) / 4.0, 6)
    u4_top1_m0 = round(sum(unseen_4win_results[w]["unhealed_moce0_top1_pct"] for w in unseen_4win_names) / 4.0, 2)
    u4_top1_m85 = round(sum(unseen_4win_results[w]["unhealed_moce085_top1_pct"] for w in unseen_4win_names) / 4.0, 2)
    v2_4_top1_m0 = round(sum(unseen_4win_results[w]["v2_real_healed_moce0_top1_pct"] for w in unseen_4win_names) / 4.0, 2)
    v2_4_top1_m85 = round(sum(unseen_4win_results[w]["v2_real_healed_moce085_top1_pct"] for w in unseen_4win_names) / 4.0, 2)

    # Compute 22-window (2,686-token) unseen suite pooled summary from results/phase3_22win_kld_audit_v{1,2}.json
    tot_22_toks = sum(manifest[w]["n_gen"] for w in manifest)
    clean_18 = set(manifest.keys()) - {"cj03_toolcall", "cj20_t3_answer_tool", "cj20_tool_5turn_t3", "persona3_t4"}
    tot_18_toks = sum(manifest[w]["n_gen"] for w in clean_18)
    u22_m0 = round(sum(kld_22win[f"unhealed_m0.0_{w}"]["perf"]["mean_kld"] * manifest[w]["n_gen"] for w in manifest) / tot_22_toks, 6)
    h22_m0 = round(sum(kld_22win[f"healed_m0.0_{w}"]["perf"]["mean_kld"] * manifest[w]["n_gen"] for w in manifest) / tot_22_toks, 6)
    v2_22_m0 = round(sum(kld_22win_v2[f"healed_m0.0_{w}"]["perf"]["mean_kld"] * manifest[w]["n_gen"] for w in manifest) / tot_22_toks, 6)
    u22_m85 = round(sum(kld_22win[f"unhealed_m0.85_{w}"]["perf"]["mean_kld"] * manifest[w]["n_gen"] for w in manifest) / tot_22_toks, 6)
    h22_m85 = round(sum(kld_22win[f"healed_m0.85_{w}"]["perf"]["mean_kld"] * manifest[w]["n_gen"] for w in manifest) / tot_22_toks, 6)
    v2_22_m85 = round(sum(kld_22win_v2[f"healed_m0.85_{w}"]["perf"]["mean_kld"] * manifest[w]["n_gen"] for w in manifest) / tot_22_toks, 6)
    u22_top1_m0 = round(sum(kld_22win[f"unhealed_m0.0_{w}"]["perf"]["top1_agree_pct"] * manifest[w]["n_gen"] for w in manifest) / tot_22_toks, 2)
    h22_top1_m0 = round(sum(kld_22win[f"healed_m0.0_{w}"]["perf"]["top1_agree_pct"] * manifest[w]["n_gen"] for w in manifest) / tot_22_toks, 2)
    v2_22_top1_m0 = round(sum(kld_22win_v2[f"healed_m0.0_{w}"]["perf"]["top1_agree_pct"] * manifest[w]["n_gen"] for w in manifest) / tot_22_toks, 2)
    u22_top1_m85 = round(sum(kld_22win[f"unhealed_m0.85_{w}"]["perf"]["top1_agree_pct"] * manifest[w]["n_gen"] for w in manifest) / tot_22_toks, 2)
    h22_top1_m85 = round(sum(kld_22win[f"healed_m0.85_{w}"]["perf"]["top1_agree_pct"] * manifest[w]["n_gen"] for w in manifest) / tot_22_toks, 2)
    v2_22_top1_m85 = round(sum(kld_22win_v2[f"healed_m0.85_{w}"]["perf"]["top1_agree_pct"] * manifest[w]["n_gen"] for w in manifest) / tot_22_toks, 2)
    v1_18_m0 = round(sum(kld_22win[f"healed_m0.0_{w}"]["perf"]["mean_kld"] * manifest[w]["n_gen"] for w in clean_18) / tot_18_toks, 6)
    v2_18_m0 = round(sum(kld_22win_v2[f"healed_m0.0_{w}"]["perf"]["mean_kld"] * manifest[w]["n_gen"] for w in clean_18) / tot_18_toks, 6)
    wins_22_m0 = sum(1 for w in manifest if kld_22win[f"healed_m0.0_{w}"]["perf"]["mean_kld"] < kld_22win[f"unhealed_m0.0_{w}"]["perf"]["mean_kld"])
    wins_22_m85 = sum(1 for w in manifest if kld_22win[f"healed_m0.85_{w}"]["perf"]["mean_kld"] < kld_22win[f"unhealed_m0.85_{w}"]["perf"]["mean_kld"])
    v2_wins_22_m0 = sum(1 for w in manifest if kld_22win_v2[f"healed_m0.0_{w}"]["perf"]["mean_kld"] < kld_22win[f"unhealed_m0.0_{w}"]["perf"]["mean_kld"])
    v2_wins_22_m85 = sum(1 for w in manifest if kld_22win_v2[f"healed_m0.85_{w}"]["perf"]["mean_kld"] < kld_22win[f"unhealed_m0.85_{w}"]["perf"]["mean_kld"])

    # Compute 9-prompt diverse speed audit summary from results/phase3_9prompt_speed_audit_v2.json
    speed_prompts = sorted(set(k.split("|")[0] for k in speed_9prompt_v2.keys()))
    hits_m85_new = [speed_9prompt_v2[f"{p}|m0.85|r0"]["perf"]["combined_hit_rate"] for p in speed_prompts]
    hits_m0_new = [speed_9prompt_v2[f"{p}|m0.0|r0"]["perf"]["combined_hit_rate"] for p in speed_prompts]
    misses_m85_new = [speed_9prompt_v2[f"{p}|m0.85|r0"]["perf"]["misses_per_tok"] for p in speed_prompts]

    summary = {
        "kld_reduction_moce0_pct": round((1.0 - h_m0["macro_mean_kld"] / u_m0["macro_mean_kld"]) * 100.0, 2),
        "kld_reduction_moce085_pct": round((1.0 - h_m85["macro_mean_kld"] / u_m85["macro_mean_kld"]) * 100.0, 2),
        "kld_reduction_isolated_moce_pct": round((1.0 - h_moce["macro_mean_kld"] / u_moce["macro_mean_kld"]) * 100.0, 2),
        "ops_tools_kld_reduction_moce0_pct": round((1.0 - h_m0["ops_tools"]["mean_kld"] / u_m0["ops_tools"]["mean_kld"]) * 100.0, 2),
        "ops_tools_kld_reduction_moce085_pct": round((1.0 - h_m85["ops_tools"]["mean_kld"] / u_m85["ops_tools"]["mean_kld"]) * 100.0, 2),
        "ja_kld_reduction_moce0_pct": round((1.0 - h_m0["ja"]["mean_kld"] / u_m0["ja"]["mean_kld"]) * 100.0, 2),
        "ja_kld_reduction_moce085_pct": round((1.0 - h_m85["ja"]["mean_kld"] / u_m85["ja"]["mean_kld"]) * 100.0, 2),
        "unhealed_macro_kld_moce0": u_m0["macro_mean_kld"],
        "healed_macro_kld_moce0": h_m0["macro_mean_kld"],
        "unhealed_macro_kld_moce085": u_m85["macro_mean_kld"],
        "healed_macro_kld_moce085": h_m85["macro_mean_kld"],
        "unhealed_macro_kld_isolated_moce": u_moce["macro_mean_kld"],
        "healed_macro_kld_isolated_moce": h_moce["macro_mean_kld"],
        "unhealed_macro_top1_moce0": u_m0["macro_top1_agree_pct"],
        "healed_macro_top1_moce0": h_m0["macro_top1_agree_pct"],
        "unhealed_macro_top1_moce085": u_m85["macro_top1_agree_pct"],
        "healed_macro_top1_moce085": h_m85["macro_top1_agree_pct"],
        "unseen_4stratum_512tok": {
            "unhealed_macro_kld_moce0": u4_m0,
            "v1_gauss_healed_macro_kld_moce0": v1_4_m0,
            "v2_real_healed_macro_kld_moce0": v2_4_m0,
            "v2_kld_reduction_moce0_pct": round((1.0 - v2_4_m0 / u4_m0) * 100.0, 2),
            "unhealed_macro_kld_moce085": u4_m85,
            "v1_gauss_healed_macro_kld_moce085": v1_4_m85,
            "v2_real_healed_macro_kld_moce085": v2_4_m85,
            "v2_kld_reduction_moce085_pct": round((1.0 - v2_4_m85 / u4_m85) * 100.0, 2),
            "unhealed_macro_top1_moce0": u4_top1_m0,
            "v2_real_healed_macro_top1_moce0": v2_4_top1_m0,
            "unhealed_macro_top1_moce085": u4_top1_m85,
            "v2_real_healed_macro_top1_moce085": v2_4_top1_m85,
            "windows": unseen_4win_results,
        },
        "v1_healed_macro_kld_moce0": 0.132007,
        "v1_healed_macro_kld_moce085": 0.136462,
        "unseen_22window_2686tok_audit": {
            "total_windows": 22,
            "total_scored_tokens": tot_22_toks,
            "unhealed_pooled_kld_moce0": u22_m0,
            "healed_v1_pooled_kld_moce0": h22_m0,
            "kld_reduction_moce0_pct": round((1.0 - h22_m0 / u22_m0) * 100.0, 2),
            "wins_moce0": f"{wins_22_m0}/22",
            "unhealed_pooled_kld_moce085": u22_m85,
            "healed_v1_pooled_kld_moce085": h22_m85,
            "kld_reduction_moce085_pct": round((1.0 - h22_m85 / u22_m85) * 100.0, 2),
            "wins_moce085": f"{wins_22_m85}/22",
            "unhealed_pooled_top1_moce0": u22_top1_m0,
            "healed_v1_pooled_top1_moce0": h22_top1_m0,
            "top1_delta_moce0_pt": round(h22_top1_m0 - u22_top1_m0, 2),
            "unhealed_pooled_top1_moce085": u22_top1_m85,
            "healed_v1_pooled_top1_moce085": h22_top1_m85,
            "top1_delta_moce085_pt": round(h22_top1_m85 - u22_top1_m85, 2),
            "healed_v2_pooled_kld_moce0": v2_22_m0,
            "v2_kld_reduction_moce0_pct": round((1.0 - v2_22_m0 / u22_m0) * 100.0, 2),
            "v2_wins_moce0": f"{v2_wins_22_m0}/22",
            "healed_v2_pooled_kld_moce085": v2_22_m85,
            "v2_kld_reduction_moce085_pct": round((1.0 - v2_22_m85 / u22_m85) * 100.0, 2),
            "v2_wins_moce085": f"{v2_wins_22_m85}/22",
            "healed_v2_pooled_top1_moce0": v2_22_top1_m0,
            "v2_top1_delta_moce0_pt": round(v2_22_top1_m0 - u22_top1_m0, 2),
            "healed_v2_pooled_top1_moce085": v2_22_top1_m85,
            "v2_top1_delta_moce085_pt": round(v2_22_top1_m85 - u22_top1_m85, 2),
            "v2_vs_v1_kld_moce0_pct": round((v2_22_m0 / h22_m0 - 1.0) * 100.0, 2),
            "v2_vs_v1_kld_moce085_pct": round((v2_22_m85 / h22_m85 - 1.0) * 100.0, 2),
            "clean_18win_v1_kld_moce0": v1_18_m0,
            "clean_18win_v2_kld_moce0": v2_18_m0,
            "clean_18win_v2_vs_v1_moce0_pct": round((v2_18_m0 / v1_18_m0 - 1.0) * 100.0, 2),
            "isolated_moce_v1_kld": 0.0389,
            "isolated_moce_v2_kld": 0.0325,
            "isolated_moce_v2_vs_v1_pct": -16.53,
        },
        "diverse_9prompt_speed_audit": {
            "num_prompts": len(speed_prompts),
            "prompts": speed_prompts,
            "mean_combined_hit_rate_moce085": round(sum(hits_m85_new) / len(hits_m85_new), 4),
            "min_combined_hit_rate_moce085": round(min(hits_m85_new), 4),
            "max_combined_hit_rate_moce085": round(max(hits_m85_new), 4),
            "mean_combined_hit_rate_moce0": round(sum(hits_m0_new) / len(hits_m0_new), 4),
            "mean_misses_per_tok_moce085": round(sum(misses_m85_new) / len(misses_m85_new), 2),
            "min_misses_per_tok_moce085": round(min(misses_m85_new), 1),
            "max_misses_per_tok_moce085": round(max(misses_m85_new), 1),
            "bit_deterministic_across_reps_moce085": "9/9",
            "v1_mean_combined_hit_rate_moce085": 0.9327,
            "v1_mean_combined_hit_rate_moce0": 0.848,
            "persona_458_hit_rate_v1_to_v2_moce085": [0.9304, 0.9126],
        },
        "healed_speed": {
            "prefill_1024tok_per_s": s85_1k["prefill_tok_per_s"],
            "prefill_2048tok_per_s": s85_2k["prefill_tok_per_s"],
            "decode_tok_per_s_43tok": s85_43["eval_tok_per_s"],
            "decode_tok_per_s_1024tok": s85_1k["eval_tok_per_s"],
            "decode_tok_per_s_2048tok": s85_2k["eval_tok_per_s"],
            "decode_tok_per_s_moce0_43tok": s0_43["eval_tok_per_s"],
            "combined_hit_rate_43tok": s85_43["combined_hit_rate"],
            "combined_hit_rate_1024tok": s85_1k["combined_hit_rate"],
            "combined_hit_rate_2048tok": s85_2k["combined_hit_rate"],
            "misses_per_tok_moce0_43tok": s0_43["misses_per_tok"],
            "misses_per_tok_moce085_43tok": s85_43["misses_per_tok"],
            "misses_per_tok_moce085_1024tok": s85_1k["misses_per_tok"],
            "misses_per_tok_moce085_2048tok": s85_2k["misses_per_tok"],
            "ssd_read_mb_per_decode_tok_43tok": s85_43["mb_read_per_tok"],
            "ssd_read_mb_per_decode_tok_1024tok": s85_1k["mb_read_per_tok"],
            "ssd_read_mb_per_decode_tok_2048tok": s85_2k["mb_read_per_tok"],
        },
        "transplant": {
            "scope": "swift_writers_plus_router",
            "transplanted_tensors_count": tp_res["transplanted_tensors_count"],
            "untouched_tensors_count": tp_res["total_tensors"] - tp_res["transplanted_tensors_count"],
            "total_tensors": tp_res["total_tensors"],
            "file_size_bytes": tp_res["file_size"],
        },
        "heal_calibrate_duration_sec": heal_sec,
        "zero_swapouts_across_all_runs": all(r["max_swapout_delta_mib"] == 0.0 for r in speed_runs),
    }

    report = {
        "generated_at_utc": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
        "provenance": {
            "scripts_sha256": {
                "scripts/run_phase3_local_healing.py": sha256_file(Path(__file__).resolve()),
                "scripts/heal_k256_q2_0.cpp": sha256_file(root / "scripts" / "heal_k256_q2_0.cpp"),
                "scripts/phase3_lora_and_transplant.py": sha256_file(root / "scripts" / "phase3_lora_and_transplant.py"),
                "scripts/patch_strata_metal.py": sha256_file(root / "scripts" / "patch_strata_metal.py"),
            },
            "binaries_sha256": {
                "vendor/strata/build-metal/heal_k256_q2_0": sha256_file(heal_bin),
                "vendor/strata/build-metal/strata_window_cli": sha256_file(strata_cli),
            },
            "prompts_sha256": {
                "data/m5_val_prompts/cal_disjoint_1024tok.txt": sha256_file(cal_prompt),
                "data/m5_val_prompts/turns/manifest.json": sha256_file(root / "data" / "m5_val_prompts" / "turns" / "manifest.json"),
                "data/m5_val_prompts/eval_tf_ops_tools.txt": sha256_file(root / "data" / "m5_val_prompts" / "eval_tf_ops_tools.txt"),
                "data/m5_val_prompts/eval_tf_chat_json.txt": sha256_file(root / "data" / "m5_val_prompts" / "eval_tf_chat_json.txt"),
                "data/m5_val_prompts/eval_tf_ja.txt": sha256_file(root / "data" / "m5_val_prompts" / "eval_tf_ja.txt"),
            },
        },
        "summary_comparison": summary,
        "kld_evaluations": kld_runs,
        "unseen_4stratum_evaluations": unseen_4win_results,
        "speed_runs": speed_runs,
        "transplant_result": tp_res,
    }

    out_path = root / args.output
    out_path.parent.mkdir(parents=True, exist_ok=True)
    out_path.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8")
    print(f"Wrote Phase 3 validation report -> {out_path}", flush=True)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
