"""RUN-017 - Round 14: semantic rebase, claim dominance and hot/cold compaction.

Round 14 lets a claim survive a dependency change when a registered implication covers
it: a lower target, a stricter scalar error budget within the claim's recorded slack
(capacity Cap_e'(c) = T_c + s_c + e_c - e'), a larger witness catalog, an ancestor
domain. It keeps only a minimal active proof core hot, moves the rest into a
content-addressed cold archive, and proves that compaction does not change readiness
(Thm 22.1). The reference compacts Round 13's final checkpoint and tests four policy
scenarios. Here the migrations and the four scenarios are rebuilt by this tree's own
semantic validator, the capacity aggregate recomputed, the hot core and the archive tied
to Round 13, and the hot core read against the round's own rules.

Checks
  A0 anchors      the formulas and numbers below occur in Round 14 as written
  W1 migration    sec. 3-9, 29: capacity, the scalar-margin rule, and the four scenarios by
                  this tree's validator
  W2 capacity     sec. 7, 39: the capacity of the frontier, min over seeds of the best
                  covering claim, against the reference's reported figure
  W3 binding      sec. 20-27: the archive snapshot is Round 13's final checkpoint; digests,
                  Merkle root, rehydration; the hot core's claims are that state's accepted ones
  W4 semantics    sec. 17, 19, 22, 31-36: what the hot core keeps and what it credits
  W5 compaction   Thm 22.1: readiness from the hot core equals readiness from the full state
  P1 package      rule identifiers across the text, the protocol and the hot core
  P2 package      the package's self-description of its files matches the archive
  R1 registry     Round 14's judgments reviewed, first-used statuses defined

Usage: python code/run017_round14_semantic_rebase.py [--drill] [--blind CID]
"""
import argparse
import copy
import hashlib
import json
import re

import gate
import registry
from corpus import SOURCES, TREE, source_bytes, walk_sources
from run014_round11_global_shards import merkle
from run015_round12_checkpoint_crystal import validate

PKG = "main.zip!/AMRAL_LUC_FC_Round_14_Package_v0.1.zip!/"
REF = PKG + "AMRAL_LUC_FC_Round_14_reference_compaction/"
DOC = PKG + "AMRAL_Lebesgue_Finite_Closure_Round_14_Semantic_Rebase_Compaction_v0.1.md"
PROTOCOL = PKG + "AMRAL_LUC_FC_Round_14_SEMANTIC_REBASE_COMPACTION_PROTOCOL_v0.1.md"
CRYSTAL = PKG + "AMRAL_LUC_FC_State_Crystal_Round_14_v0.1.md"
VALIDATION = PKG + "AMRAL_LUC_FC_Round_14_VALIDATION.json"
HOT = REF + "hot_proof_core.json"
SUMMARY = REF + "semantic_compaction_summary.json"
SEMVAL = REF + "semantic_validator.py"
DEMO = REF + "semantic_compaction_demo.py"
ARCH = REF + "cold_archive/"
R13_FINAL = "main.zip!/AMRAL_LUC_FC_Round_13_Package_v0.1.zip!/AMRAL_LUC_FC_Round_13_reference_scheduler/" \
            "r13_checkpoint_after_obsolete_expand.json"
LOG = TREE / "data" / "gate-logs" / "run017-round14-semantic-rebase.json"
MANIFEST = TREE / "data" / "SOURCE_MANIFEST.v1.json"

ANCHORS = {
    "W1": [r"\operatorname{Cap}_{e'}(c)=T_c+s_c+e_c-e'.", r"T'+e'\leT_c+e_c+s_c,", r"s_A=3.8698779558643537\times10^{-7}.",
           r"s_B=9.574949350055206\times10^{-7}.", r"s_C=3.545377508240577\times10^{-7}.",
           r"\operatorname{Cap}(A)\approx0.8350003769877956,", r"\operatorname{Cap}(B)\approx0.8350009474949350,",
           r"\operatorname{Cap}(C)\approx0.8350003445377508.", r"T'=0.8350001,\qquade'=2\times10^{-8}.", r"T''=0.8350004"],
    "W2": [r"T_{\rmcap}^{\rmref}\approx0.8350003445377508.",
           r"T_{\rmcapacity}=\min_{p\in\mathcalP}\max_{c\text{covers}p}\operatorname{Cap}(c)."],
    "P1": [r"`TARGET-MONOTONICITY-V0.1`", r"`SCALAR-ERROR-MARGIN-CREDIT-V0.1`", r"`WITNESS-CATALOG-SUPERSET-V0.1`",
           r"`ANCESTOR-DOMAIN-COVERAGE-V0.1`", r"unresolvedFAILtombstones"],
}
SCENARIOS = {                            # target, error budget, catalog (the text's sec. 29)
    "baseline": (0.835, 1e-8, ["Reuleaux7"]),
    "semantic_rebase_success": (0.8350001, 2e-8, ["Reuleaux7", "Reuleaux9"]),
    "semantic_rebase_failure": (0.8350004, 2e-8, ["Reuleaux7", "Reuleaux9"]),
    "catalog_extension_only": (0.835, 1e-8, ["Reuleaux7", "Reuleaux9", "Round02-Fourier"]),
}
TEXT_RULES = ["TARGET-MONOTONICITY-V0.1", "SCALAR-ERROR-MARGIN-CREDIT-V0.1", "WITNESS-CATALOG-SUPERSET-V0.1",
              "ANCESTOR-DOMAIN-COVERAGE-V0.1"]
SEC19_CLAIM = ["certificate_payload_sha256", "proved_threshold", "verified_error_budget", "worst_verified_slack",
               "required_witness_ids", "a1_evidence"]


# ---------------------------------------------------------------- this tree's semantic validator

def capacity(claim, e_new):
    """sec. 5: Cap_e'(c) = T_c + s_c + e_c - e'"""
    return claim["proved_threshold"] + claim["worst_verified_slack"] + claim["verified_error_budget"] - e_new


def semantic(core, target, e_new, catalog, cap=capacity):
    reports, by_seed = [], {}
    for c in core["active_claims"]:
        k = cap(c, e_new)
        ok_w = set(c["required_witness_ids"]) <= set(catalog)
        ok = ok_w and target <= k
        reports.append({"claim_id": c["claim_id"], "compatible": ok, "capacity": k})
        if ok:
            for s in c["seed_paths"]:
                by_seed[s] = c["claim_id"]
    pending = sorted(s for s in core["frontier"]["seed_paths"] if s not in by_seed)
    return {"theorem_ready": not pending, "accepted_claim_by_seed": dict(sorted(by_seed.items())),
            "pending_seed_paths": pending, "claim_reports": reports}


def frontier_capacity(core, e_new, cap=capacity):
    """sec. 39: min over frontier seeds of the best capacity among the claims covering it"""
    best = {}
    for c in core["active_claims"]:
        for s in c["seed_paths"]:
            best[s] = max(best.get(s, -1.0), cap(c, e_new))
    return min(best.get(s, -1.0) for s in core["frontier"]["seed_paths"])


# ---------------------------------------------------------------- checks

def w1_migration(w):
    out, rows = [], {}
    core, summary = w["hot"], w["summary"]
    caps = {c["claim_id"]: w["capacity"](c, 2e-8) for c in core["active_claims"]}
    for cid, text in w["text_capacities"].items():
        if abs(caps[cid] - text) > 5e-16:
            out.append(f"{cid}: capacity at e' = 2e-8 is {caps[cid]!r}, text {text}")
    # the scalar rule itself: raw witnesses with L >= T + e + s serve any T' + e' <= T + e + s
    for c in core["active_claims"]:
        L = c["proved_threshold"] + c["verified_error_budget"] + c["worst_verified_slack"]
        for tp, ep in ((0.835, 1e-8), (0.8350001, 2e-8), (0.8350004, 2e-8)):
            if (tp + ep <= L + 1e-18) != (L - ep >= tp - 1e-18):
                out.append(f"{c['claim_id']}: the margin rule and the raw inequality disagree at {tp}, {ep}")
    for name, (t, e, cat) in SCENARIOS.items():
        mine = semantic(core, t, e, cat, w["capacity"])
        rep = summary[name]
        theirs = {r["claim_id"]: (r["compatible"], r["capacity"]) for r in rep["claim_reports"]}
        for r in mine["claim_reports"]:
            ok, k = theirs[r["claim_id"]]
            if ok != r["compatible"] or abs(k - r["capacity"]) > 5e-16:
                out.append(f"{name} {r['claim_id']}: rebuilt ({r['compatible']}, {r['capacity']!r}), reported ({ok}, {k!r})")
        if mine["theorem_ready"] != rep["theorem_ready"] or mine["accepted_claim_by_seed"] != rep["accepted_claim_by_seed"] \
                or mine["pending_seed_paths"] != rep["pending_seed_paths"] or mine["theorem_ready"] != w["text_ready"][name]:
            out.append(f"{name}: rebuilt ready {mine['theorem_ready']}, reported {rep['theorem_ready']}, text {w['text_ready'][name]}")
        rows[name] = {"theorem_ready": mine["theorem_ready"], "pending": mine["pending_seed_paths"]}
    w["w1"] = {"capacities_at_2e-8": caps, "scenarios": rows}
    return out


def w2_capacity(w):
    out, rows, wrong = [], {}, []
    core, summary = w["hot"], w["summary"]
    for name, (t, e, _) in SCENARIOS.items():
        mine = frontier_capacity(core, e, w["capacity"])
        reported = summary[name]["reference_global_capacity_if_same_error_policy"]
        rows[name] = {"frontier_capacity": mine, "reported": reported, "target": t}
        if abs(mine - reported) > 5e-16:
            wrong.append(name)
    if abs(frontier_capacity(core, 2e-8, w["capacity"]) - w["text_cap_ref"]) > 5e-16:
        out.append("the frontier capacity at e' = 2e-8 is not the text's T_cap^ref")
    if sorted(wrong) != sorted(w["capacity_field_wrong"]):
        out.append(f"the reported capacity differs from the frontier's in {wrong}, recorded {w['capacity_field_wrong']}")
    w["w2"] = rows
    return out


def w3_binding(w):
    out = []
    snap = w["archive"]["round13_source_checkpoint_snapshot.json"]
    if snap != w["r13_final"]:
        out.append("the archived snapshot is not Round 13's final checkpoint")
    man = json.loads(w["archive"]["archive_manifest.json"])
    for o in man["objects"]:
        if hashlib.sha256(w["archive"][o["path"]]).hexdigest() != o["sha256"] or len(w["archive"][o["path"]]) != o["bytes"]:
            out.append(f"{o['path']}: its digest or size is not the manifest's")
    if merkle([o["sha256"] for o in man["objects"]]) != man["merkle_root"]:
        out.append("the archive's Merkle root does not re-derive")
    hot = w["hot"]["cold_archive"]
    if hot["archive_merkle_root"] != man["merkle_root"] \
            or hot["archive_manifest_sha256"] != hashlib.sha256(w["archive"]["archive_manifest.json"]).hexdigest():
        out.append("the hot core does not bind the archive")
    full = json.loads(snap)["payload"]
    accepted = validate(full)["accepted_claim_ids"]
    if sorted(c["claim_id"] for c in w["hot"]["active_claims"]) != accepted:
        out.append("the hot core's claims are not the full state's accepted claims")
    b1 = next(c for c in full["claim_ledger"] if c["claim_id"] == "claim-shard-B-v1")
    rh = w["summary"]["archive_rehydration"]
    if (rh["rehydrated_status"], rh["rehydrated_certificate_sha256"]) != (b1["status"], b1["certificate_payload_sha256"]):
        out.append("the rehydration record is not the archived claim")
    return out


def w4_semantics(w):
    out = []
    full = json.loads(w["archive"]["round13_source_checkpoint_snapshot.json"])["payload"]
    hot = w["hot"]
    no_a1 = sorted(c["claim_id"] for c in hot["active_claims"] if not any("audit" in k or "a1" in k.lower() for k in c))
    b2 = next(c for c in hot["active_claims"] if c["claim_id"] == "claim-shard-B-v2-corrected")
    own = [a for a in full["audit_ledger"] if a["claim_id"] == b2["claim_id"]]
    rejected = [a for a in full["audit_ledger"] if a["claim_id"] == "claim-shard-B-v1" and "worst_verified_slack" in a]
    slack_from_rejected = (not any("worst_verified_slack" in a for a in own) and bool(rejected)
                           and rejected[0]["worst_verified_slack"] == b2["worst_verified_slack"])
    fail_on_active_bytes = any(
        a["result"] == "FAIL" and next(c for c in full["claim_ledger"] if c["claim_id"] == a["claim_id"])
        ["certificate_payload_sha256"] == b2["certificate_payload_sha256"] for a in full["audit_ledger"])
    tombstones = [k for k in hot if "tombstone" in k.lower()]
    found = {"claims_without_A1_evidence": no_a1, "B_v2_slack_is_B_v1_audit": slack_from_rejected,
             "FAIL_against_active_bytes_in_source": fail_on_active_bytes, "hot_tombstones": tombstones}
    if found != w["hot_core_findings"]:
        out.append(f"the hot core: {found}; recorded {w['hot_core_findings']}")
    w["w4"] = found
    return out


def w5_compaction(w):
    out = []
    full = json.loads(w["archive"]["round13_source_checkpoint_snapshot.json"])["payload"]
    ready_full = validate(full)["theorem_ready"]
    ready_hot = semantic(w["hot"], *SCENARIOS["baseline"], cap=w["capacity"])["theorem_ready"]
    if ready_full != ready_hot:
        out.append(f"readiness from the full state {ready_full}, from the hot core {ready_hot}")
    size = len(w["hot_bytes"]), len(w["archive"]["round13_source_checkpoint_snapshot.json"])
    if (size[0], size[1]) != (w["summary"]["size_stats"]["hot_core_bytes"], w["summary"]["size_stats"]["source_checkpoint_bytes"]):
        out.append(f"sizes {size} differ from the summary's")
    return out


def p1_package(w):
    out = []
    hot_rules = w["hot"]["semantic_policy"]["compatibility_rules"]
    unregistered = sorted(r for r in hot_rules if r not in TEXT_RULES)
    in_protocol = sorted(r for r in TEXT_RULES if r in w["protocol"])
    if unregistered != sorted(w["unregistered_rules"]) or in_protocol != sorted(w["rules_named_in_protocol"]):
        out.append(f"the hot core's rules outside sec. 32: {unregistered}; sec. 32's rules in the protocol: {in_protocol}")
    w["p1"] = {"hot_core_rules_not_in_sec32": unregistered, "sec32_rules_named_in_protocol": in_protocol}
    return out


def p2_package(w):
    out, leaves = [], {row[0]: row for row in w["manifest"]["leaves"]}
    v = w["validation"]
    for rec, leaf_id, text in ((v["round14"], DOC, w["text"]), (v["state_crystal"], CRYSTAL, w["crystal"]),
                               (v["protocol"], PROTOCOL, w["protocol"])):
        _, size, digest = leaves[leaf_id]
        if rec["sha256"] != digest or rec["bytes"] != size:
            out.append(f"{rec['file']}: recorded sha256/bytes differ from the archive")
        n = text.count("$$")
        if rec["double_dollar_count"] != n or rec["double_dollar_even"] != (n % 2 == 0):
            out.append(f"{rec['file']}: recorded {rec['double_dollar_count']} $$, document has {n}")
        if rec["contains_unicode_escape_sequence"] != bool(re.search(r"\\u[0-9a-fA-F]{4}", text)):
            out.append(f"{rec['file']}: unicode-escape flag differs")
        if rec["contains_bracket_math_delimiter"] != bool(re.search(r"\\[\[\]()]", text)):
            out.append(f"{rec['file']}: bracket-delimiter flag differs")
    for key, leaf_id in (("hot_core_sha256", HOT), ("archive_manifest_sha256", ARCH + "archive_manifest.json"),
                         ("semantic_validator_sha256", SEMVAL), ("compaction_demo_sha256", DEMO)):
        if v[key] != leaves[leaf_id][2]:
            out.append(f"{key}: recorded digest differs from the archive")
    if v["demo"] != w["summary"] or v["baseline"] != w["summary"]["baseline"]:
        out.append("VALIDATION's copies differ from the compaction summary")
    return out


# ---------------------------------------------------------------- registry (Round 14)

TOKEN = "a ledger label of sec. 30, defined there; not a judgment"
R14_DEFINITIONS = {
    "NOT AUTHORIZED": ("contextual", "sec. 0, 40: an action the round withholds - here, physically deleting evidence"),
    "R12-STRENGTHENING-001": ("in-line", "sec. 30: Round 12's hash-equality rule kept as the fast path, with registered "
                                         "semantic migrations beside it"),
    "SAFE-CONSERVATIVE": ("in-line", "sec. 30: the label kept for Round 12's rule: sound, and stricter than needed"),
}
R14_REVIEWS = [
    ("PROVED", "Scalar margin migration", "analytic", "PASS",
     "for the leaf witnesses, L >= T + e + s and T' + e' <= T + e + s give L - e' >= T' (W1); a claim whose proof also "
     "uses an a-priori exclusion has that exclusion's capacity as well - for the reference claims the B7 lift root's "
     "0.835 (corrected by RUN-018, W4)"),
    ("PROVED", "Witness catalog extension", "analytic", "PASS", "a certificate uses only its own witnesses (W1)"),
    ("PROVED", "Hot-core compaction theorem", "analytic", "PASS",
     "Thm 22.1 under claim-level semantics (W5); the reference hot core does not carry the A1 evidence hypothesis 5 "
     "names (W4)"),
    ("PASS", "Archive rehydration", "computation", "PASS",
     "W3; the rehydrated rejected claim carries the active claim's certificate digest"),
    ("PASS", "Semantic rebase boundary tests", "computation", "PARTIAL",
     "W1: this tree's validator gives the same four verdicts, but the 'higher target' case (0.8350001, ready) is "
     "unsupported: the claims' B7 lift square was sized at t_7(0.835), and outside it the exclusion supports only 0.835 "
     "(corrected by RUN-018, W4)"),
    ("CLOSED", "SEMANTIC REBASE LAYER", "reading", "PARTIAL",
     "the migration rules hold for leaf witnesses (W1), but a claim's capacity omits its a-priori exclusions "
     "(RUN-018), the reference reports, for the failing scenario, a frontier capacity above the target it has just "
     "failed (W2), and its hot core names rules that are not sec. 32's registered ones (P1)"),
    ("CLOSED", "HOT/COLD PROOF COMPACTION", "reading", "PARTIAL",
     "compaction keeps readiness (W5) and the archive binds (W3), but the hot core carries no A1 evidence, no "
     "tombstone for the FAIL against its active claim's bytes, and credits that claim with the slack audited under "
     "the rejected one (W4)"),
    ("FORMALIZED", "Semantic compatibility", "reading", "PASS", "sec. 2-13: explicit rules only"),
    ("FORMALIZED", "Claim dominance", "reading", "PASS", "sec. 15-17"),
    ("FORMALIZED", "Minimal active proof core", "reading", "PASS",
     "sec. 19 lists an A1 evidence summary per claim; the reference omits it (W4)"),
    ("IMPLEMENTED", "Cold archive binding", "computation", "PASS", "W3: digests and the Merkle root re-derive"),
    ("NOT AUTHORIZED", "DESTRUCTIVE EVIDENCE DELETION", "reading", "PASS",
     "nothing is deleted: the archive holds Round 13's whole final checkpoint (W3)"),
    ("NOT AUTHORIZED", "Destructive evidence deletion", "reading", "PASS", "as above"),
    ("R12-STRENGTHENING-001", "30. Round 12 strengthening ledger", "n/a", "n/a", TOKEN),
    ("SAFE-CONSERVATIVE", "30. Round 12 strengthening ledger", "n/a", "n/a", TOKEN),
]


def r1_registry(w):
    return registry.completeness(w["registry"], "R14", w["definitions"], w["reviews"])


# ---------------------------------------------------------------- world, checks, drill

CHECKS = {"A0": gate.anchors_check(ANCHORS), "W1": w1_migration, "W2": w2_capacity, "W3": w3_binding,
          "W4": w4_semantics, "W5": w5_compaction, "P1": p1_package, "P2": p2_package, "R1": r1_registry}
DEPENDS = {"A0": {"text"}, "W1": {"hot", "summary", "capacity", "text_capacities", "text_ready"},
           "W2": {"hot", "summary", "capacity", "text_cap_ref", "capacity_field_wrong"},
           "W3": {"archive", "r13_final", "hot", "summary"}, "W4": {"archive", "hot", "hot_core_findings"},
           "W5": {"archive", "hot", "hot_bytes", "summary", "capacity"},
           "P1": {"hot", "protocol", "unregistered_rules", "rules_named_in_protocol"},
           "P2": {"validation", "text", "crystal", "protocol", "manifest", "summary"},
           "R1": {"registry", "definitions", "reviews"}}

CLAIMS = {
    "capacity": capacity,
    "text_capacities": {"claim-shard-A-v1": 0.8350003769877956, "claim-shard-B-v2-corrected": 0.8350009474949350,
                        "claim-shard-C-v1": 0.8350003445377508},
    "text_ready": {"baseline": True, "semantic_rebase_success": True, "semantic_rebase_failure": False,
                   "catalog_extension_only": True},
    "text_cap_ref": 0.8350003445377508,
    "capacity_field_wrong": ["semantic_rebase_failure"],
    "hot_core_findings": {"claims_without_A1_evidence": ["claim-shard-A-v1", "claim-shard-B-v2-corrected",
                                                         "claim-shard-C-v1"],
                          "B_v2_slack_is_B_v1_audit": True, "FAIL_against_active_bytes_in_source": True,
                          "hot_tombstones": []},
    "unregistered_rules": ["DOMAIN_ANCESTOR_COVERAGE_V0.1", "SCALAR_ERROR_BUDGET_MIGRATION_V0.1",
                           "TARGET_MARGIN_CREDIT_V0.1", "WITNESS_CATALOG_EXTENSION_V0.1"],
    "rules_named_in_protocol": [],
}


def load_inputs():
    blobs = {n: source_bytes(n) for n in SOURCES}
    arch = ["round13_source_checkpoint_snapshot.json", "archive_index.json", "archive_manifest.json"]
    want = {DOC, PROTOCOL, CRYSTAL, VALIDATION, HOT, SUMMARY, R13_FINAL, *(ARCH + f for f in arch)}
    got = {leaf.id: d for leaf, d in walk_sources(blobs, keep=lambda l: l.id in want) if d is not None}
    return {"text": got[DOC].decode("utf-8"), "protocol": got[PROTOCOL].decode("utf-8"),
            "crystal": got[CRYSTAL].decode("utf-8"), "validation": json.loads(got[VALIDATION]),
            "hot": json.loads(got[HOT]), "hot_bytes": got[HOT], "summary": json.loads(got[SUMMARY]),
            "archive": {f: got[ARCH + f] for f in arch}, "r13_final": got[R13_FINAL],
            "manifest": json.loads(MANIFEST.read_text(encoding="utf-8"))}


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--drill", action="store_true")
    ap.add_argument("--blind", metavar="CID")
    args = ap.parse_args()
    inputs = load_inputs()

    def build():
        return {**{k: (copy.deepcopy(v) if k not in ("archive",) else dict(v)) for k, v in inputs.items()},
                "registry": registry.load(), "definitions": dict(R14_DEFINITIONS), "reviews": list(R14_REVIEWS),
                **copy.deepcopy({k: v for k, v in CLAIMS.items() if k != "capacity"}), "capacity": CLAIMS["capacity"]}

    if args.blind:
        CHECKS[args.blind] = lambda w: []
        args.drill = True
    w = build()
    results = {c: fn(w) for c, fn in CHECKS.items()}
    for c, f in results.items():
        print(f"{c} {'PASS' if not f else 'FAIL'}", *(f"\n     {x}" for x in f[:6]))
    for key in ("w1", "w2", "w4", "p1"):
        print(key, w.get(key))
    if not args.drill:
        return
    hot, summ = inputs["hot"], inputs["summary"]
    cases = [
        ("K0-unmodified", "control", {}, set()),
        ("K1-doc-crlf", "control", {"text": inputs["text"].replace("\n", "\r\n")}, set()),
        ("K2-capacity-rearranged", "control",
         {"capacity": lambda c, e: (c["proved_threshold"] + c["verified_error_budget"] - e) + c["worst_verified_slack"]},
         set()),
        ("K3-claims-reordered", "control", {"hot": {**hot, "active_claims": list(reversed(hot["active_claims"]))}}, set()),
        ("M1-doc-capacity-digit", "mutation",
         {"text": inputs["text"].replace("0.8350003769877956", "0.8350003769877966")}, {"A0"}),
        ("M2-capacity-without-old-budget", "mutation",
         {"capacity": lambda c, e: c["proved_threshold"] + c["worst_verified_slack"] - e}, {"W1", "W2"}),
        ("M3-text-ready-flipped", "mutation", {"text_ready": {**CLAIMS["text_ready"], "semantic_rebase_failure": True}},
         {"W1"}),
        ("M4-capacity-field-assumed-right", "mutation", {"capacity_field_wrong": []}, {"W2"}),
        ("M5-snapshot-edited", "mutation", {"archive": {**inputs["archive"], "round13_source_checkpoint_snapshot.json":
                                                        inputs["archive"]["round13_source_checkpoint_snapshot.json"] + b" "}},
         {"W3", "W5"}),
        ("M6-hot-core-assumed-complete", "mutation", {"hot_core_findings": {
            **CLAIMS["hot_core_findings"], "claims_without_A1_evidence": []}}, {"W4"}),
        ("M7-rules-assumed-registered", "mutation", {"unregistered_rules": []}, {"P1"}),
        ("M8-hot-core-digest", "mutation", {"validation": {**inputs["validation"], "hot_core_sha256": "0" * 64}}, {"P2"}),
        ("M9-review-dropped", "mutation", {"reviews": list(R14_REVIEWS[1:])}, {"R1"}),
    ]
    if args.blind:
        cases = [c for c in cases if args.blind in c[3]]
    d = gate.run_drill(build, CHECKS, DEPENDS, cases)
    summary = gate.summarize(d, CHECKS)
    print(gate.report_drill(summary))
    for x in d:
        if not x["ok"]:
            print("   DRILL FAIL", x)
    if args.blind:
        n = sum(1 for x in d if args.blind in x["expected"] and args.blind not in x["tripped"])
        print(f"blind {args.blind}: drill reported it blind in {n} cases (nothing written)")
        return
    log = {"run": "RUN-017", "subject": "Round 14: semantic rebase, claim dominance and hot/cold compaction",
           "document": DOC, "checks": {c: {"pass": not f, "failures": f} for c, f in results.items()},
           **{k: w.get(k) for k in ("w1", "w2", "w4", "p1")}, "drill": summary}
    LOG.write_text(json.dumps(log, ensure_ascii=False, indent=1, default=float) + "\n", encoding="utf-8")
    if not results["R1"]:
        registry.record("R14", "RUN-017", R14_DEFINITIONS, R14_REVIEWS)
        print(f"registry: {len(R14_DEFINITIONS)} definitions, {len(R14_REVIEWS)} reviews recorded for R14")


if __name__ == "__main__":
    main()
