#!/usr/bin/env python3
"""Headline-coherence + version-coherence gate (Court pre-lock sweep, B1/B2).

Every confirmed drift in the 2026-07-03 Court review was a hand-maintained
headline surface shadowing a machine source. This gate makes the headline
surfaces un-driftable by comparing them against the machine truth
(``docs/standards/rules.json``, generated by ``gen_rules_index.py``):

B1 — count coherence (recurrence-proofing F-M4/F-M3):
  - ``docs/standards/README.md`` Index-of-MUSTs footer count — the LAST
    ``**N across F families**`` claim in the running maintenance history —
    must equal ``rules.json`` (MUST count + family count).
  - ``SCOPE.md``'s ``N MUSTs across F families`` literal must equal
    ``rules.json`` (the pointer form is preferred; the literal that remains
    is gated here so it can never rot silently again).
  - ``FEDERATION.md``'s §4 family-table row count must equal the
    ``rules.json`` family count (one row per family, ``XX — Name`` cells).

B2 — version coherence (recurrence-proofing F-M3):
  - ``FEDERATION.md`` must carry NO literal *corpus* version — pointer form
    only. Mechanically: the phrase "at time of writing" is banned outright;
    the §4 intro must point at ``docs/standards/README.md`` for the current
    corpus version; and any ``v2+.x`` token must sit in a rule-amendment
    citation context (``DOC-01 v2.11`` / ``MUST since v2.21``) — history,
    not a current-version claim. (``v0.x``/``v1.x`` are the library and
    contract version spaces, out of scope.)
  - The contract Status line must match the single ``CONTRACT_VERSION``
    constant this script owns — one owner for the number, everywhere else
    compares.

Usage::

    python scripts/check_headline_coherence.py        # exit 0 GREEN / 1 with reasons

Wiring: invoked by ``gen_rules_index.py --check`` (the gate CI already runs
through ``tests/unit/test_rules_index.py`` — the ``.woodpecker.yml`` is a
WF-13 GENERATED artifact, so the check rides the same pytest channel as the
existing rules-index gate rather than a hand-edited pipeline step) and
pinned directly by ``tests/unit/test_check_headline_coherence.py``.
"""

from __future__ import annotations

import json
import re
import sys
from pathlib import Path

#: The federation-contract version (FEDERATION.md Status line). This constant
#: is the single owner of the number; the contract file must match it (B2).
CONTRACT_VERSION = "2.0"

# "**N across F families**" — the Index-of-MUSTs footer's running history;
# the LAST occurrence is the current claim.
_FOOTER_COUNT = re.compile(r"\*\*(\d+) across (\d+) families\*\*")
# SCOPE.md's literal (wraps across lines, so match on the full text).
_SCOPE_COUNT = re.compile(r"(\d+)\s+MUSTs across (\d+)\s+families")
# FEDERATION.md §4 family-table rows: "| AG — Agents | AG-01..AG-20 | ... |"
_FAMILY_ROW = re.compile(r"^\|\s*[A-Z]{2,5}\s+—\s", re.MULTILINE)
# The contract Status line.
_STATUS_LINE = re.compile(r"\*\*Status:\*\*\s*v(\d+\.\d+)\s+of this contract")
# A corpus-space version token (the corpus lives in v2.x today, v3.x post-lock;
# v0.x / v1.x are the library + contract spaces and are not matched).
_CORPUS_VERSION = re.compile(r"\bv[2-9]\d*\.\d+(?:\.\d+)?\b")
# Contexts in which a corpus-space version token is legal HISTORY, not a
# current-version claim: a rule-amendment citation ("DOC-01 v2.11") or a
# promotion marker ("MUST since v2.21").
_CITATION_CONTEXT = re.compile(r"(?:[A-Z]{2,5}-\d+\s*|\bsince\s+)$")
# The §4 pointer form ("... see docs/standards/README.md for the current
# corpus version", tolerant of line wraps).
_CORPUS_POINTER = re.compile(r"for the current\s+corpus version")

_BANNED_PHRASE = "at time of writing"


def _counts_from_rules_json(root: Path) -> tuple[int, int]:
    """(must_count, family_count) from the generated machine index."""
    data = json.loads((root / "docs" / "standards" / "rules.json").read_text(encoding="utf-8"))
    must = int(data["counts"]["must"])
    families = len({r["family"] for r in data["rules"] if r["indexed"]})
    return must, families


def check(root: Path) -> list[str]:
    """Return the list of coherence failures (empty == GREEN)."""
    errors: list[str] = []
    must, families = _counts_from_rules_json(root)

    # --- B1: README Index-of-MUSTs footer count -------------------------
    readme = (root / "docs" / "standards" / "README.md").read_text(encoding="utf-8")
    footer_counts = _FOOTER_COUNT.findall(readme)
    if not footer_counts:
        errors.append(
            "docs/standards/README.md: no '**N across F families**' footer count "
            "found in the Index-of-MUSTs maintenance history."
        )
    else:
        got_must, got_fam = (int(x) for x in footer_counts[-1])
        if (got_must, got_fam) != (must, families):
            errors.append(
                f"docs/standards/README.md: Index footer claims {got_must} MUSTs "
                f"across {got_fam} families but rules.json says {must} across "
                f"{families} — append the reconciling history sentence."
            )

    # --- B1: SCOPE.md literal --------------------------------------------
    scope = (root / "SCOPE.md").read_text(encoding="utf-8")
    m = _SCOPE_COUNT.search(scope)
    if m is None:
        errors.append(
            "SCOPE.md: no 'N MUSTs across F families' count found — keep the "
            "rules.json pointer + gated literal shape."
        )
    elif (int(m.group(1)), int(m.group(2))) != (must, families):
        errors.append(
            f"SCOPE.md: claims {m.group(1)} MUSTs across {m.group(2)} families "
            f"but rules.json says {must} across {families}."
        )

    # --- B1: FEDERATION.md §4 family-table row count ----------------------
    federation = (root / "FEDERATION.md").read_text(encoding="utf-8")
    fam_rows = len(_FAMILY_ROW.findall(federation))
    if fam_rows != families:
        errors.append(
            f"FEDERATION.md: §4 family table has {fam_rows} family rows but "
            f"rules.json has {families} families — regenerate the table "
            f"(one 'XX — Name' row per family)."
        )

    # --- B2: no literal corpus version in FEDERATION.md -------------------
    if _BANNED_PHRASE in federation:
        errors.append(
            f"FEDERATION.md: contains '{_BANNED_PHRASE}' — never embed a twin "
            "of a number that lives elsewhere; use the pointer form."
        )
    if not _CORPUS_POINTER.search(federation):
        errors.append(
            "FEDERATION.md: the corpus-version pointer ('… for the current "
            "corpus version', pointing at docs/standards/README.md) is missing."
        )
    # The contract Status line is validated against CONTRACT_VERSION above;
    # mask it here so a v2+ CONTRACT version is not misread as a corpus literal
    # (the two version spaces collided once the contract reached v2.0 at LOCK v1).
    status_masked = federation
    _sm_mask = _STATUS_LINE.search(federation)
    if _sm_mask is not None:
        s, e = _sm_mask.span()
        status_masked = federation[:s] + (" " * (e - s)) + federation[e:]
    for vm in _CORPUS_VERSION.finditer(status_masked):
        context = status_masked[max(0, vm.start() - 24) : vm.start()]
        if not _CITATION_CONTEXT.search(context):
            line_no = federation.count("\n", 0, vm.start()) + 1
            errors.append(
                f"FEDERATION.md:{line_no}: literal corpus-space version "
                f"'{vm.group(0)}' outside a rule-amendment citation — the "
                f"contract must not carry a corpus version (pointer form only)."
            )

    # --- B2: contract Status line matches CONTRACT_VERSION ----------------
    sm = _STATUS_LINE.search(federation)
    if sm is None:
        errors.append("FEDERATION.md: no '**Status:** vX.Y of this contract' line found.")
    elif sm.group(1) != CONTRACT_VERSION:
        errors.append(
            f"FEDERATION.md: contract Status says v{sm.group(1)} but this "
            f"script's CONTRACT_VERSION is {CONTRACT_VERSION} — bump the "
            f"constant and the contract line together (one owner, one number)."
        )

    return errors


def main() -> int:
    root = Path(__file__).resolve().parents[1]
    errors = check(root)
    if errors:
        print("HEADLINE/VERSION COHERENCE: FAILED")
        for e in errors:
            print(f"  ✗ {e}")
        return 1
    must, families = _counts_from_rules_json(root)
    print(
        f"HEADLINE/VERSION COHERENCE: GREEN — README footer, SCOPE.md, and the "
        f"FEDERATION.md family table all agree with rules.json ({must} MUSTs / "
        f"{families} families); contract v{CONTRACT_VERSION}; no literal corpus "
        f"version in the contract."
    )
    return 0


if __name__ == "__main__":
    sys.exit(main())
