"""``mgf-fed court`` — the fleet-wide-act cadence.

The cadence exists because this estate's failures cluster by BLAST RADIUS, not by
effort: a count-based trigger fires most during the safest work and can pass over
the one commit that changes canon for 54 repos.

These tests exist because the gate failed its own first run.
"""

from __future__ import annotations

import shutil
import subprocess
from datetime import date as _dt_date
from pathlib import Path

import pytest

from mgf.fed import _court

#: The house convention (see `test_audit.py` / `test_feedback.py`) for tests that
#: shell out to git. `shutil.which` NEVER raises; `subprocess.run(["git", ...])`
#: raises FileNotFoundError when the binary is absent — which is what this file
#: originally did, so it exploded at COLLECTION time in the slim CI image instead
#: of skipping, taking the whole module (and its coverage) with it.
#:
#: Session 5 had already recorded that exact trap — "a PATH with git that CI's
#: image lacks". Inventing a second convention walked straight back into it.
requires_git = pytest.mark.skipif(
    shutil.which("git") is None, reason="git not on PATH (e.g. the slim CI image)"
)


def _run(argv: list[str]) -> None:
    """`git` from PATH over a tmp_path fixture — the house convention."""
    subprocess.run(argv, check=True)  # noqa: S603


def _repo(root: Path, name: str, files: dict[str, str]) -> Path:
    repo = root / name
    repo.mkdir(parents=True, exist_ok=True)
    _run(["git", "init", "-q", "-b", "main", str(repo)])
    for rel, body in files.items():
        p = repo / rel
        p.parent.mkdir(parents=True, exist_ok=True)
        p.write_text(body, encoding="utf-8")
    _run(["git", "-C", str(repo), "add", "-A"])
    _run(
        [
            "git",
            "-C",
            str(repo),
            "-c",
            "commit.gpgsign=false",
            "-c",
            "user.email=t@x.invalid",
            "-c",
            "user.name=T",
            "commit",
            "-q",
            "-m",
            "seed",
        ]
    )
    return repo


# --------------------------------------------------------------------------- #
# The regression that names this file's reason to exist.
# --------------------------------------------------------------------------- #


def test_a_bare_date_is_normalised_to_midnight() -> None:
    """THE defect this gate shipped with, for about ten minutes.

    git's approxidate fills unspecified fields from **now**, so `--since=2026-08-04`
    evaluated at 23:28 means "since 23:28 today" — not midnight. The first real run
    of `court --check` therefore reported *"no fleet-wide act detected"* on a day
    with four of them, which is this programme's own defect class committed by the
    check written to police it.
    """
    assert _court._normalise_since("2026-08-04") == "2026-08-04 00:00:00"
    # Anything already carrying a time, or a ref, is passed through untouched.
    assert _court._normalise_since("2026-08-04 09:30:00") == "2026-08-04 09:30:00"
    assert _court._normalise_since("HEAD~5") == "HEAD~5"
    assert _court._normalise_since("1.day.ago") == "1.day.ago"


@requires_git
def test_a_RELATIVE_window_bound_is_resolved_not_string_compared(tmp_path: Path) -> None:
    """D3 — and it disarmed D1 in the only invocation that actually runs.

    The window's start was `_normalise_since(since)[:10]`, compared to a round's
    ISO date with `>=`. That is chronological only when BOTH sides are ISO dates,
    and a relative expression passes `_normalise_since` through untouched:

        "2019-01-01" >= "1.day.ago"   -> True   (an ancient round licenses the act)
        "2026-08-12" >= "3.days.ago"  -> False  (a round written today does not)

    The pre-commit hook passes `--since 1.day.ago`, so D1 — "a round must not
    license its trigger forever", this module's self-declared worst defect — was
    switched off in production while its own test passed on a bare date.
    """
    # The string comparison the old code performed, pinned so the premise cannot
    # rot silently.
    assert "2019-01-01" >= "1.day.ago", "the false-pass direction"
    assert not ("2026-08-12" >= "3.days.ago"), "the false-fail direction"

    repo = _repo(tmp_path, "mgf-standard", {"templates/woodpecker/base.yml": "clone:\n"})
    start = _court._window_start("1.day.ago", repo)
    assert isinstance(start, _court.datetime), "git's own approxidate must answer"
    age = (_court.datetime.now().astimezone() - start).total_seconds()
    assert 23 * 3600 < age < 25 * 3600, f"1.day.ago resolved to {start}"

    _seed_round(tmp_path, "ancient.toml", date="2019-01-01")
    assert _court.run_court(since="1.day.ago", root=tmp_path) == 1, (
        "a 2019 round must not license a canon act in a 1-day window"
    )


@requires_git
def test_a_TODAYS_round_satisfies_a_relative_window(tmp_path: Path) -> None:
    """The other half of D3: the lexicographic bound also REJECTED every valid
    round for any `--since` of `3.days.ago` or later, because "2026…" < "3…".
    A gate that fails on a correct round is how `--no-verify` becomes habit."""
    _repo(tmp_path, "mgf-standard", {"templates/woodpecker/base.yml": "clone:\n"})
    today = _court.datetime.now().astimezone().strftime("%Y-%m-%d")
    _seed_round(tmp_path, "today.toml", date=today)
    assert _court.run_court(since="3.days.ago", root=tmp_path) == 0


@requires_git
def test_WIDENING_the_window_does_not_buy_licensing_slack(tmp_path: Path) -> None:
    """The chair's L-1, and it was found on the act that introduced it.

    D1's bound used to be the CALLER's `--since`, which made "how old may a round
    be" a function of an argument. Widening the court hook from `1.day.ago` to
    `7.days.ago` — for coverage, a sound goal — therefore bought seven days of
    licensing slack as a side effect, and a live run printed ONE gate act `OK`
    four times over, licensed by four rounds that had judged other things.

    The bound is now the act's OWN date, so `--since` decides only which acts are
    asked about. This fixture is the chair's: an act TODAY, a round from
    yesterday. It must fail at every window width."""
    _repo(tmp_path, "mgf-standard", {"templates/woodpecker/base.yml": "clone:\n"})
    from datetime import timedelta

    yesterday = (_court.datetime.now().astimezone() - timedelta(days=1)).strftime("%Y-%m-%d")
    _seed_round(tmp_path, "yesterday.toml", date=yesterday)
    for window in ("1.day.ago", "7.days.ago", "90.days.ago"):
        assert _court.run_court(since=window, root=tmp_path) == 1, (
            f"a round predating the act must not license it at --since {window}"
        )


@requires_git
def test_an_UNRESOLVABLE_since_cannot_evaluate(
    tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
    """Approxidate does not reject garbage — it silently returns *now*, giving a
    zero-width window in which nothing can have happened. Measured: `not-a-date`,
    `''` and `0` all resolve to the current second.

    The MESSAGE is asserted, not just the code. An earlier draft caught this with
    a one-hour floor under the window, which produced the right exit code for the
    wrong reason — and refused three legitimate windows a chair measured,
    including `--since <today>` before 01:00, the form the help text recommends."""
    _repo(tmp_path, "mgf-standard", {"templates/woodpecker/base.yml": "clone:\n"})
    assert _court.run_court(since="not-a-date", root=tmp_path) == 2
    assert "resolves to this instant" in capsys.readouterr().out


@requires_git
def test_a_NARROW_but_deliberate_window_is_honoured(tmp_path: Path) -> None:
    """The casualties of the withdrawn one-hour floor, each measured by a chair:
    `--since <today>` in the first hour of a day, `--since 30.minutes.ago`, and a
    post-merge CI invocation windowed to the merge that just happened — the most
    precise window anyone could ask for, and the one shape it refused."""
    _repo(tmp_path, "mgf-standard", {"templates/woodpecker/base.yml": "clone:\n"})
    today = _court.datetime.now().astimezone().strftime("%Y-%m-%d")
    _seed_round(tmp_path, "today.toml", date=today)
    assert _court.run_court(since="30.minutes.ago", root=tmp_path) == 0


@requires_git
def test_a_round_dated_in_WORDS_is_not_dated(
    tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
    """The chair's L-4 — D1 arriving through the operand D1's fix did not touch.

    `date` is a hand-typed TOML field that was never validated while the code
    compared it as a string, so anything sorting above "2026-…" was permanently
    in-window. The chair measured five: `yesterday`, `TBD`, `n/a`, `soon` and
    `pending` each licensed their trigger and exited 0."""
    _repo(tmp_path, "mgf-standard", {"templates/woodpecker/base.yml": "clone:\n"})
    for bad in ("yesterday", "TBD", "n/a", "soon", "pending"):
        d = tmp_path / _court.COURT_ROUNDS_DIR
        for old in d.glob("*.toml"):
            old.unlink()
        _seed_round(tmp_path, "wordy.toml", date=bad)
        assert _court.run_court(since="1.day.ago", root=tmp_path) == 1, (
            f"date = {bad!r} must not license anything"
        )
        assert "is not a date" in capsys.readouterr().out


@requires_git
def test_an_empty_window_cannot_evaluate_rather_than_pass(
    tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
    """ "I could not evaluate" must never share an exit code with "I evaluated and
    it was clean" — the audit's corollary, applied to this gate.

    A window that cannot CONTAIN an act says nothing about the fleet, and that is
    how the bare-date trap stayed invisible: it produced a zero-width window and
    the gate called it clean. `2099-01-01` is the same shape, inverted — the
    window starts in the future, so emptiness is guaranteed by arithmetic.

    The REASON is asserted, not only the code, because a chair proved the code
    alone is not enough here: with the future branch deleted the run still exits
    2 (via the nothing-moved refusal below it) and the suite stayed entirely
    green — an untested guard hiding behind a neighbour's exit code. A gate whose
    diagnosis is wrong is a gate that sends the next reader somewhere else."""
    _repo(tmp_path, "mgf-thing", {"README.md": "x"})
    rc = _court.run_court(since="2099-01-01", root=tmp_path)
    assert rc == 2, "an unevaluable window must not exit 0"
    assert "FUTURE" in capsys.readouterr().out, "the refusal must name WHY it could not look"


@requires_git
def test_a_root_with_no_repos_cannot_evaluate(tmp_path: Path) -> None:
    """Zero repos examined is zero evidence. It used to fall through to "no
    fleet-wide act in the window" and exit 0 — a clean bill of health issued over
    an empty room."""
    assert _court.run_court(since="2000-01-01", root=tmp_path) == 2


@requires_git
def test_no_since_is_exit_2_not_a_pass(tmp_path: Path) -> None:
    assert _court.run_court(since=None, root=tmp_path) == 2
    assert _court.run_court(since="", root=tmp_path) == 2


# --------------------------------------------------------------------------- #
# Trigger detection
# --------------------------------------------------------------------------- #


@requires_git
def test_three_repos_is_a_wave_and_two_is_not(tmp_path: Path) -> None:
    """Two repos is a pair you can hold in your head; three is where you stop
    editing and start scripting — and every roster/sample defect in this
    programme lived in a script."""
    two = tmp_path / "two"
    two.mkdir()
    for n in ("mgf-a", "mgf-b"):
        _repo(two, n, {"README.md": "x"})
    assert [a.trigger for a in _court.detect_acts(two, "2000-01-01")] == []

    three = tmp_path / "three"
    three.mkdir()
    for n in ("mgf-a", "mgf-b", "mgf-c"):
        _repo(three, n, {"README.md": "x"})
    assert "wave" in [a.trigger for a in _court.detect_acts(three, "2000-01-01")]


@requires_git
def test_a_canon_change_is_an_act_even_alone(tmp_path: Path) -> None:
    """One repo, one commit — and it is still a fleet-wide act, because every
    member renders from canon. This is the case a count-based trigger misses."""
    _repo(tmp_path, "mgf-standard", {"templates/woodpecker/base.yml": "clone:\n"})
    acts = _court.detect_acts(tmp_path, "2000-01-01")
    assert "canon" in [a.trigger for a in acts], acts
    assert "wave" not in [a.trigger for a in acts], "one repo is not a wave"


@requires_git
def test_a_gate_change_is_an_act_even_alone(tmp_path: Path) -> None:
    """A wrong gate does not fail loudly — it PASSES. That is the whole programme."""
    _repo(tmp_path, "mgf-fed", {"src/mgf/fed/_audit.py": "x = 1\n"})
    assert "gate" in [a.trigger for a in _court.detect_acts(tmp_path, "2000-01-01")]


@requires_git
def test_an_unrelated_change_in_canon_repo_is_not_a_canon_act(tmp_path: Path) -> None:
    """Scoped to canon PATHS, not to the repo. Editing mgf-standard's README is
    not a fleet-wide act, and a trigger that fires on everything gets ignored."""
    _repo(tmp_path, "mgf-standard", {"README.md": "x"})
    assert [a.trigger for a in _court.detect_acts(tmp_path, "2000-01-01")] == []


# --------------------------------------------------------------------------- #
# Rounds
# --------------------------------------------------------------------------- #


@requires_git
def test_an_act_without_a_round_fails(tmp_path: Path) -> None:
    _repo(tmp_path, "mgf-standard", {"templates/woodpecker/base.yml": "clone:\n"})
    assert _court.run_court(since="2000-01-01", root=tmp_path) == 1


@requires_git
def test_a_round_with_a_blank_docket_answer_fails(tmp_path: Path) -> None:
    """A round that answers four of five is not a round. Generic "looks fine" is
    what a court round decays into, so every docket question is required."""
    _repo(tmp_path, "mgf-standard", {"templates/woodpecker/base.yml": "clone:\n"})
    d = tmp_path / _court.COURT_ROUNDS_DIR
    d.mkdir(parents=True)
    answers = "\n".join(f'{q} = "answered"' for q in _court.DOCKET[:-1])
    (d / "r.toml").write_text(
        f'[round]\nact = "a"\ntrigger = "canon"\ndate = "2026-08-05"\n'
        f'chair = "self"\n\n[answers]\n{answers}\n'
        f'{_court.DOCKET[-1]} = ""\n',
        encoding="utf-8",
    )
    assert _court.run_court(since="2000-01-01", root=tmp_path) == 1


@requires_git
def test_a_complete_round_passes(tmp_path: Path) -> None:
    """The positive control. Without it every red above could be red for free."""
    _repo(tmp_path, "mgf-standard", {"templates/woodpecker/base.yml": "clone:\n"})
    d = tmp_path / _court.COURT_ROUNDS_DIR
    d.mkdir(parents=True)
    answers = "\n".join(f'{q} = "answered"' for q in _court.DOCKET)
    today = _court.datetime.now().astimezone().strftime("%Y-%m-%d")
    (d / "r.toml").write_text(
        f'[round]\nact = "canon: mgf-standard base.yml"\ntrigger = "canon"\n'
        f'date = "{today}"\nchair = "fresh-context"\n\n[answers]\n{answers}\n',
        encoding="utf-8",
    )
    assert _court.run_court(since="2000-01-01", root=tmp_path) == 0


@requires_git
def test_a_round_with_no_chair_fails(tmp_path: Path) -> None:
    """CRT-01: the implementer is disqualified from judging his own work, so a
    round that names nobody has not been chaired."""
    _repo(tmp_path, "mgf-standard", {"templates/woodpecker/base.yml": "clone:\n"})
    d = tmp_path / _court.COURT_ROUNDS_DIR
    d.mkdir(parents=True)
    answers = "\n".join(f'{q} = "answered"' for q in _court.DOCKET)
    (d / "r.toml").write_text(
        f'[round]\nact = "a"\ntrigger = "canon"\ndate = "2026-08-05"\n'
        f'chair = ""\n\n[answers]\n{answers}\n',
        encoding="utf-8",
    )
    assert _court.run_court(since="2000-01-01", root=tmp_path) == 1


#: A round must NAME the act in front of it (D2), so each fixture's round
#: text names the repos that fixture's own act touches.
_ACT_TEXT = {
    "canon": "canon: mgf-standard base.yml",
    "gate": "gate: mgf-fed _audit.py",
    "wave": "wave: mgf-a, mgf-b, mgf-c",
}


def _chair_fixture(tmp_path: Path, *, trigger: str, chair: str) -> Path:
    """A root whose ONLY detected act is `trigger`, with one round chairing it.

    The act must be produced by the fixture's own repos, not asserted by the
    round's `trigger` field — the first version of these tests set
    `trigger = "gate"` on a root that only ever produced a CANON act, so they
    went red on "NO ROUND for canon" and passed with the code under test DELETED.
    Sabotage caught it; the assertion had never been attached to the guarantee.
    """
    root = tmp_path / trigger
    root.mkdir(parents=True)
    if trigger == "canon":
        _repo(root, "mgf-standard", {"templates/woodpecker/base.yml": "clone:\n"})
    elif trigger == "gate":
        _repo(root, "mgf-fed", {"src/mgf/fed/_audit.py": "x = 1\n"})
    elif trigger == "wave":
        for n in ("mgf-a", "mgf-b", "mgf-c"):
            _repo(root, n, {"README.md": "x"})
    d = root / _court.COURT_ROUNDS_DIR
    d.mkdir(parents=True, exist_ok=True)
    answers = "\n".join(f'{q} = "answered"' for q in _court.DOCKET)
    # Dated TODAY, deliberately: the fixture's commits are made now, and a round
    # must be no older than the act it judges (the chair's L-1). A hardcoded past
    # date here would test the old rule.
    today = _court.datetime.now().astimezone().strftime("%Y-%m-%d")
    (d / "r.toml").write_text(
        f'[round]\nact = "{_ACT_TEXT[trigger]}"\ntrigger = "{trigger}"\n'
        f'date = "{today}"\nchair = "{chair}"\n\n[answers]\n{answers}\n',
        encoding="utf-8",
    )
    return root


@requires_git
def test_the_fixture_produces_exactly_the_act_it_claims(tmp_path: Path) -> None:
    """The control that makes the three below mean anything.

    Without it a CRT-01 test can go red because the fixture produced a DIFFERENT
    unjudged act, which is exactly how the first draft of these tests passed
    against deleted code.
    """
    for trigger in ("canon", "gate", "wave"):
        root = _chair_fixture(tmp_path, trigger=trigger, chair="fresh-context")
        assert [a.trigger for a in _court.detect_acts(root, "2000-01-01")] == [trigger]


@requires_git
def test_a_SELF_chaired_gate_act_fails_CRT01(tmp_path: Path) -> None:
    """The third proven false-pass path in this tool, and the plainest.

    `--check` asked only whether `chair` was NON-EMPTY. So a gate act signed
    `chair = "self"` printed OK and exited 0 — CRT-01 unenforced by the gate whose
    entire subject is CRT-01. The skeleton has always told the writer which chair
    each trigger needs; the checker never read it back.
    """
    root = _chair_fixture(tmp_path, trigger="gate", chair="self")
    assert _court.run_court(since="2000-01-01", root=root) == 1


@requires_git
def test_a_chair_that_DESCRIBES_the_gap_still_fails(tmp_path: Path) -> None:
    """The live instance, and why the comparison is exact rather than substring.

    This session filed a gate round chaired
    `"self — CRT-01 NOT SATISFIED, a fresh-context chair is OWED on this act"`.
    It passed, and the tool echoed that sentence back beside the word OK. A chair
    field that merely CONTAINS "fresh-context" must not count either — writing
    down that you did not satisfy the rule is not satisfying it.
    """
    root = _chair_fixture(
        tmp_path, trigger="canon", chair="self — CRT-01 NOT SATISFIED, fresh-context OWED"
    )
    assert _court.run_court(since="2000-01-01", root=root) == 1


@requires_git
def test_a_SELF_chaired_WAVE_act_still_passes(tmp_path: Path) -> None:
    """The over-reach control. A plain wave may be self-judged — that is the
    split the skeleton prints, and tightening it everywhere would make the
    cadence unusable, which is how a gate earns a blanket waiver."""
    root = _chair_fixture(tmp_path, trigger="wave", chair="self")
    assert _court.run_court(since="2000-01-01", root=root) == 0


@requires_git
def test_an_unparseable_round_fails_loudly(tmp_path: Path) -> None:
    """A malformed round is not silently skipped — a round nobody can read is not
    a round, and skipping it would let a broken file satisfy the gate."""
    _repo(tmp_path, "mgf-standard", {"templates/woodpecker/base.yml": "clone:\n"})
    d = tmp_path / _court.COURT_ROUNDS_DIR
    d.mkdir(parents=True)
    (d / "r.toml").write_text("this is not = valid = toml [[[", encoding="utf-8")
    assert _court.run_court(since="2000-01-01", root=tmp_path) == 1


@requires_git
def test_a_round_predating_the_window_does_not_license_the_trigger(tmp_path: Path) -> None:
    """D1, and it was the worst defect in this module.

    Rounds matched on `r.trigger == act.trigger` ALONE, so one round licensed its
    trigger forever: a 2019-dated round made every future gate change pass, exit
    0. A gate satisfiable once and never again is the drawer of dead verifiers
    this module's own docstring is written against — built into the module."""
    _repo(tmp_path, "mgf-standard", {"templates/woodpecker/base.yml": "clone:\n"})
    d = tmp_path / _court.COURT_ROUNDS_DIR
    d.mkdir(parents=True)
    answers = "\n".join(f'{q} = "answered"' for q in _court.DOCKET)
    (d / "ancient.toml").write_text(
        f'[round]\nact = "something else"\ntrigger = "canon"\ndate = "2019-01-01"\n'
        f'chair = "fresh-context"\n\n[answers]\n{answers}\n',
        encoding="utf-8",
    )
    assert _court.run_court(since="2026-01-01", root=tmp_path) == 1


@requires_git
def test_acts_are_read_from_the_landed_ref_not_a_parked_checkout(tmp_path: Path) -> None:
    """D2. `detect_acts` ran `git log --since` with no ref, so a gate change
    merged to `main` VANISHED the moment the checkout was parked elsewhere — the
    counterfeit-ref discipline CLAUDE.md states outright, inverted, inside the
    tool whose job is refusing a false pass."""
    repo = _repo(tmp_path, "mgf-fed", {"README.md": "x"})
    bare = tmp_path / ".remotes" / "mgf-fed.git"
    bare.parent.mkdir(parents=True, exist_ok=True)
    _run(["git", "init", "-q", "--bare", "-b", "main", str(bare)])
    _run(["git", "-C", str(repo), "remote", "add", "origin", f"file://{bare}"])
    _run(["git", "-C", str(repo), "push", "-q", "-u", "origin", "main"])
    # The gate change lands on main…
    (repo / "src" / "mgf" / "fed").mkdir(parents=True, exist_ok=True)
    (repo / "src" / "mgf" / "fed" / "_audit.py").write_text("x = 1\n", encoding="utf-8")
    _run(["git", "-C", str(repo), "add", "-A"])
    _run(
        [
            "git",
            "-C",
            str(repo),
            "-c",
            "commit.gpgsign=false",
            "-c",
            "user.email=t@x.invalid",
            "-c",
            "user.name=T",
            "commit",
            "-q",
            "-m",
            "gate change",
        ]
    )
    _run(["git", "-C", str(repo), "push", "-q", "origin", "main"])
    # …and the working tree is then parked on an unrelated branch.
    _run(["git", "-C", str(repo), "checkout", "-q", "-b", "parked", "HEAD~1"])
    assert "gate" in [a.trigger for a in _court.detect_acts(tmp_path, "2000-01-01")], (
        "the act LANDED on main; parking the checkout must not hide it"
    )


# --------------------------------------------------------------------------- #
# git-FREE coverage. The suite above needs real repos, so it skips entirely in
# the slim CI image — and a module whose tests all skip contributes nothing to
# the coverage floor, which is what took the total from 85% to 78%. These pin the
# pure logic with no subprocess at all, so the gate is exercised everywhere.
# --------------------------------------------------------------------------- #


def test_round_missing_answers_names_every_blank(tmp_path: Path) -> None:
    r = _court.Round(path=tmp_path / "r.toml", act="a", trigger="canon", chair="self")
    assert r.missing_answers == list(_court.DOCKET), "a round with no answers is all-missing"
    r.answers = dict.fromkeys(_court.DOCKET, "x")
    assert r.missing_answers == []
    r.answers["blind_spots"] = "   "
    assert r.missing_answers == ["blind_spots"], "whitespace is not an answer"


def test_load_rounds_reads_and_reports_unparseable(tmp_path: Path) -> None:
    d = tmp_path / _court.COURT_ROUNDS_DIR
    d.mkdir(parents=True)
    (d / "good.toml").write_text(
        '[round]\nact = "a"\ntrigger = "gate"\ndate = "2026-08-05"\nchair = "fresh-context"\n'
        "\n[answers]\n" + "\n".join(f'{q} = "x"' for q in _court.DOCKET),
        encoding="utf-8",
    )
    (d / "bad.toml").write_text("not [[[ toml", encoding="utf-8")
    rounds = {r.path.name: r for r in _court.load_rounds(tmp_path)}
    assert rounds["good.toml"].missing_answers == []
    assert rounds["good.toml"].chair == "fresh-context"
    # An unreadable round is RETURNED, not skipped — skipping would let a broken
    # file satisfy the gate by being invisible to it.
    assert rounds["bad.toml"].act == "<unparseable>"
    assert rounds["bad.toml"].missing_answers == list(_court.DOCKET)


def test_load_rounds_is_empty_when_the_dir_is_absent(tmp_path: Path) -> None:
    assert _court.load_rounds(tmp_path) == []


def test_write_round_template_emits_every_docket_question_blank(tmp_path: Path) -> None:
    """The skeleton must NOT pre-answer anything — a docket the tool fills in is
    the tool reviewing itself."""
    p = _court.write_round_template(tmp_path, "an-act", trigger="canon", act="an act")
    text = p.read_text(encoding="utf-8")
    for q in _court.DOCKET:
        assert f'{q} = ""' in text, f"{q} must be present and blank"
    assert 'chair = ""' in text
    loaded = _court.load_rounds(tmp_path)
    assert len(loaded) == 1
    assert loaded[0].missing_answers == list(_court.DOCKET), "a skeleton must not pass the gate"


def test_the_docket_is_the_five_recorded_failures() -> None:
    """A canary: silently dropping a question would weaken every future round."""
    assert _court.DOCKET == (
        "gate_reality",
        "gate_list_run",
        "sabotage_bites",
        "blind_spots",
        "blast_radius",
    )
    assert _court.WAVE_THRESHOLD == 3


# --------------------------------------------------------------------------- #
# The DECISION logic, tested without git.
#
# `run_court` decides pass/fail, and both false-pass defects a court chair found
# lived here — not in git plumbing. Exercising it through real repos meant it was
# only covered where git exists, i.e. never in the CI image that runs the gate.
# Feeding synthetic acts tests the decision directly and everywhere.
# --------------------------------------------------------------------------- #


def _seed_round(root: Path, name: str, **over: str) -> None:
    d = root / _court.COURT_ROUNDS_DIR
    d.mkdir(parents=True, exist_ok=True)
    f = {
        "act": "canon: mgf-standard",  # D2 — a round must NAME what it judges
        "trigger": "canon",
        "date": "2026-08-05",
        "chair": "fresh-context",
        **over,
    }
    answers = "\n".join(f'{q} = "answered"' for q in _court.DOCKET)
    (d / name).write_text(
        "[round]\n" + "".join(f'{k} = "{v}"\n' for k, v in f.items()) + f"\n[answers]\n{answers}\n",
        encoding="utf-8",
    )


def _one_act(monkeypatch: pytest.MonkeyPatch, root: Path) -> None:
    """One canon act, one repo that moved — no git involved."""
    monkeypatch.setattr(_court, "_repos", lambda _r: [root / "mgf-standard"])
    monkeypatch.setattr(_court, "_default_ref", lambda _r: "origin/main")
    monkeypatch.setattr(_court, "_git", lambda *_a, **_k: "abc123 a commit\n")
    monkeypatch.setattr(
        _court,
        "detect_acts",
        lambda _root, _since: [
            _court.Act(
                trigger="canon",
                detail="canon moved",
                repos=["mgf-standard"],
                latest=_dt_date(2026, 8, 5),
            )
        ],
    )


def test_decision_passes_only_with_a_complete_in_window_round(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    _one_act(monkeypatch, tmp_path)
    _seed_round(tmp_path, "r.toml")
    assert _court.run_court(since="2026-08-01", root=tmp_path) == 0


def test_decision_fails_with_no_round_at_all(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    _one_act(monkeypatch, tmp_path)
    assert _court.run_court(since="2026-08-01", root=tmp_path) == 1


def test_decision_fails_when_every_round_predates_the_window(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """D1 — one round must not license its trigger forever."""
    _one_act(monkeypatch, tmp_path)
    _seed_round(tmp_path, "ancient.toml", date="2019-01-01")
    assert _court.run_court(since="2026-08-01", root=tmp_path) == 1


def test_decision_fails_on_a_blank_docket_answer(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    _one_act(monkeypatch, tmp_path)
    d = tmp_path / _court.COURT_ROUNDS_DIR
    d.mkdir(parents=True, exist_ok=True)
    answers = "\n".join(f'{q} = "x"' for q in _court.DOCKET[:-1])
    (d / "partial.toml").write_text(
        f'[round]\nact = "a"\ntrigger = "canon"\ndate = "2026-08-05"\n'
        f'chair = "self"\n\n[answers]\n{answers}\n{_court.DOCKET[-1]} = ""\n',
        encoding="utf-8",
    )
    assert _court.run_court(since="2026-08-01", root=tmp_path) == 1


def test_decision_fails_when_no_chair_is_named(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """CRT-01 — the implementer is disqualified from judging his own work, so a
    round naming nobody has not been chaired."""
    _one_act(monkeypatch, tmp_path)
    _seed_round(tmp_path, "nochair.toml", chair="")
    assert _court.run_court(since="2026-08-01", root=tmp_path) == 1


def _quiet_fleet(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
    monkeypatch.setattr(_court, "_repos", lambda _r: [tmp_path / "mgf-standard"])
    monkeypatch.setattr(_court, "_default_ref", lambda _r: "origin/main")
    monkeypatch.setattr(_court, "_git", lambda *_a, **_k: "")
    monkeypatch.setattr(_court, "detect_acts", lambda _root, _since: [])


def test_decision_cannot_evaluate_when_nothing_moved(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
    """A window in which no repo moved is a suspect window, not a clean fleet —
    and 0.25.3's attempt to soften this (D4, a `.git/FETCH_HEAD` freshness probe
    that let a quiet-but-fetched bench pass) was WITHDRAWN before release.

    A chair killed it with one measurement: a FAILED `git fetch` writes
    FETCH_HEAD too — an unreachable remote exits 128 and CREATES the file with a
    current mtime. So in a forge outage every repo would look fetched and the
    gate would have printed "a quiet fleet rather than a blind gate" over exactly
    the blind bench the probe existed to detect. The refusal stands, and the
    message now names WHY it cannot tell rather than merely that it cannot."""
    _quiet_fleet(monkeypatch, tmp_path)
    assert _court.run_court(since="2026-08-01", root=tmp_path) == 2
    out = capsys.readouterr().out
    assert "LOCAL CACHE" in out, "the refusal must say what it could not distinguish"


def test_decision_is_clean_when_repos_moved_but_no_trigger_fired(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Below the wave threshold and touching no canon/gate path is genuinely
    nothing to seat a round for — the gate must not cry wolf."""
    monkeypatch.setattr(_court, "_repos", lambda _r: [tmp_path / "a", tmp_path / "b"])
    monkeypatch.setattr(_court, "_default_ref", lambda _r: "origin/main")
    monkeypatch.setattr(_court, "_git", lambda *_a, **_k: "abc123 x\n")
    monkeypatch.setattr(_court, "detect_acts", lambda _root, _since: [])
    assert _court.run_court(since="2026-08-01", root=tmp_path) == 0


# --------------------------------------------------------------------------- #
# The git PLUMBING, tested with a fake `git` — no real binary required.
#
# CI runs `python:3.12-slim`, which has no git, so every test that shells out
# skips there: 141 of 1306. That left `_git`, `_default_ref` and `detect_acts`
# covered NOWHERE on the image that actually runs the gate — the same shape as
# the decision logic before it, one layer down.
#
# A stub on PATH is the honest seam: what these functions must get right is how
# they INVOKE git and how they read its output, and neither needs the real thing.
# --------------------------------------------------------------------------- #


@pytest.fixture
def fake_git(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
    """A `git` on PATH that answers from files, so the plumbing is exercised."""
    bin_dir = tmp_path / "bin"
    bin_dir.mkdir()
    script = bin_dir / "git"
    script.write_text(
        "#!/bin/sh\n"
        # `git -C <repo> <verb> ...` — echo the canned answer for <verb>.
        'repo="$2"; shift 2; verb="$1"\n'
        'f="$repo/.fakegit/$verb"\n'
        '[ -f "$f" ] && cat "$f" && exit 0\n'
        "exit 1\n",
        encoding="utf-8",
    )
    script.chmod(0o755)
    monkeypatch.setenv("PATH", f"{bin_dir}:{__import__('os').environ['PATH']}")
    return bin_dir


def _fake_repo(root: Path, name: str, **answers: str) -> Path:
    d = root / name / ".fakegit"
    d.mkdir(parents=True)
    (root / name / ".git").mkdir(exist_ok=True)
    for verb, out in answers.items():
        (d / verb).write_text(out, encoding="utf-8")
    return root / name


def test_git_helper_returns_stdout_and_empty_on_failure(tmp_path: Path, fake_git: Path) -> None:
    repo = _fake_repo(tmp_path, "mgf-thing", log="abc123 a commit\n")
    assert _court._git(repo, "log").strip() == "abc123 a commit"
    # A non-zero exit yields "" rather than raising or leaking stderr — callers
    # treat empty as "could not read", never as "nothing there".
    assert _court._git(repo, "unknown-verb") == ""


def test_default_ref_prefers_the_remote_head_then_falls_back(
    tmp_path: Path, fake_git: Path
) -> None:
    a = _fake_repo(tmp_path, "a", **{"symbolic-ref": "origin/master\n"})
    assert _court._default_ref(a) == "origin/master"
    # No symbolic-ref, but origin/main verifies -> use it.
    b = _fake_repo(tmp_path, "b", **{"rev-parse": "deadbeef\n"})
    assert _court._default_ref(b) == "origin/main"
    # Nothing resolves: HEAD is the only truth a remote-less repo has, and it is
    # RETURNED rather than skipped — dropping a repo silently is how a wave goes
    # uncounted.
    c = _fake_repo(tmp_path, "c")
    assert _court._default_ref(c) == "HEAD"


def test_detect_acts_reads_the_landed_ref_and_classifies(tmp_path: Path, fake_git: Path) -> None:
    """Canon and gate triggers are path-scoped; the wave trigger is a count."""
    _fake_repo(
        tmp_path,
        "mgf-standard",
        **{"symbolic-ref": "origin/main\n", "log": "MGF_LIBRARIES.json\n"},
    )
    acts = _court.detect_acts(tmp_path, "2000-01-01")
    assert "canon" in [a.trigger for a in acts], acts
    assert "wave" not in [a.trigger for a in acts], "one repo is not a wave"

    _fake_repo(tmp_path, "mgf-a", **{"symbolic-ref": "origin/main\n", "log": "README.md\n"})
    _fake_repo(tmp_path, "mgf-b", **{"symbolic-ref": "origin/main\n", "log": "README.md\n"})
    triggers = [a.trigger for a in _court.detect_acts(tmp_path, "2000-01-01")]
    assert "wave" in triggers, "three repos moved — that is a wave"


def test_detect_acts_skips_a_repo_whose_ref_cannot_resolve(tmp_path: Path, fake_git: Path) -> None:
    """`_default_ref` falls back to HEAD, so a repo is only skipped when git
    itself answers nothing at all — and then it contributes no phantom act."""
    _fake_repo(tmp_path, "mgf-quiet", **{"symbolic-ref": "origin/main\n"})
    assert _court.detect_acts(tmp_path, "2000-01-01") == []


@requires_git
def test_the_fresh_context_trigger_set_is_DERIVED_from_the_act_producer(tmp_path: Path) -> None:
    """No dead strings. `detect_acts` emits `CANON_PATHS`' keys plus "wave".

    The first version of `_FRESH_CONTEXT_TRIGGERS` was a hand-written literal
    naming "credentials" and "release" — triggers nothing can produce. The tool
    therefore READ as if it gated those acts while gating nothing, and no test
    could have covered them. A magic-string set not read back from its producer,
    committed in the same act as the fix for exactly that class one module over.
    """
    assert _court._FRESH_CONTEXT_TRIGGERS == frozenset(_court.CANON_PATHS)
    producible = set(_court.CANON_PATHS) | {"wave"}
    assert _court._FRESH_CONTEXT_TRIGGERS <= producible, "a trigger nothing can emit"
    assert "wave" not in _court._FRESH_CONTEXT_TRIGGERS, "a plain wave may be self-judged"


def test_a_round_naming_a_DIFFERENT_act_does_not_license_this_one(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """D2 — the fourth false-pass path, found by a court chair on 2026-08-07.

    `court --check` PASSED a five-repo remediation using a round whose `act`
    field named four entirely different PRs. The match was (trigger,
    date-in-window) and nothing asked whether the round DESCRIBED the commits
    it had just counted — zero repo overlap, and it printed OK.

    D1 had already stopped a round licensing its trigger *forever*. It did not
    stop one licensing a *different act in the same window*. This is F-45's
    shape a fourth time: the check asked whether a FIELD WAS PRESENT, not
    whether it SAID SOMETHING ADMISSIBLE.
    """
    monkeypatch.setattr(_court, "_repos", lambda _r: [tmp_path / "inthewords"])
    monkeypatch.setattr(_court, "_default_ref", lambda _r: "origin/main")
    monkeypatch.setattr(_court, "_git", lambda *_a, **_k: "abc123 a commit\n")
    monkeypatch.setattr(
        _court,
        "detect_acts",
        lambda _root, _since: [
            _court.Act(
                trigger="wave",
                detail="5 repos took commits",
                repos=["inthewords", "soothfast", "soothtroth", "mgf-common-zig", "sooth-standard"],
                latest=_dt_date(2026, 8, 7),
            )
        ],
    )
    # An in-window, fully-answered, fresh-context round — for a DIFFERENT act.
    _seed_round(
        tmp_path,
        "other.toml",
        trigger="wave",
        date="2026-08-07",
        act="wave: mgf-standard #310, mgf-fed #165, mgf-common #107, ipcam-app #6",
    )
    assert _court.run_court(since="2026-08-01", root=tmp_path) == 1


def test_a_round_naming_ONE_of_the_moved_repos_is_enough(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """The bar is deliberately low: a 35-repo wave cannot list them all.

    Naming one repo that actually moved is enough — a low bar still makes the
    zero-overlap case above impossible, which is the defect that occurred.
    """
    monkeypatch.setattr(_court, "_repos", lambda _r: [tmp_path / "inthewords"])
    monkeypatch.setattr(_court, "_default_ref", lambda _r: "origin/main")
    monkeypatch.setattr(_court, "_git", lambda *_a, **_k: "abc123 a commit\n")
    monkeypatch.setattr(
        _court,
        "detect_acts",
        lambda _root, _since: [
            _court.Act(
                trigger="wave",
                detail="5 repos",
                repos=["inthewords", "soothfast", "mgf-common-zig"],
                latest=_dt_date(2026, 8, 7),
            )
        ],
    )
    _seed_round(
        tmp_path,
        "w.toml",
        trigger="wave",
        date="2026-08-07",
        act="wave: the .claude/org remediation, headed by inthewords",
    )
    assert _court.run_court(since="2026-08-01", root=tmp_path) == 0


def test_a_sibling_name_does_not_count_as_naming_the_repo(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """`mgf-common` must NOT satisfy an act that only touched `mgf-common-zig`.

    A substring match would let a round about one repo license a sibling whose
    name merely extends it — the same "close enough" failure D2 exists to stop,
    and an easy way to reintroduce the hole while the test above stays green.
    """
    monkeypatch.setattr(_court, "_repos", lambda _r: [tmp_path / "mgf-common-zig"])
    monkeypatch.setattr(_court, "_default_ref", lambda _r: "origin/main")
    monkeypatch.setattr(_court, "_git", lambda *_a, **_k: "abc123 a commit\n")
    monkeypatch.setattr(
        _court,
        "detect_acts",
        lambda _root, _since: [
            _court.Act(
                trigger="wave",
                detail="3 repos",
                repos=["mgf-common-zig", "a", "b"],
                latest=_dt_date(2026, 8, 7),
            )
        ],
    )
    _seed_round(
        tmp_path,
        "w.toml",
        trigger="wave",
        date="2026-08-07",
        act="wave: mgf-common only",
    )
    assert _court.run_court(since="2026-08-01", root=tmp_path) == 1


def test_an_incomplete_round_is_not_masked_by_a_covering_sibling(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """D2 must not WEAKEN the docket check — a chair caught it doing so.

    The first D2 narrowed `matching` to the rounds that name a repo. That
    silently dropped an in-window round with ZERO answered docket questions
    whenever a sibling happened to name one: a NEW false-pass, introduced by
    the fix for a false-pass, and covered by no test. It also falsified
    `load_rounds`' promise that a malformed round "is NOT silently skipped".
    """
    monkeypatch.setattr(_court, "_repos", lambda _r: [tmp_path / "inthewords"])
    monkeypatch.setattr(_court, "_default_ref", lambda _r: "origin/main")
    monkeypatch.setattr(_court, "_git", lambda *_a, **_k: "abc123 a commit\n")
    monkeypatch.setattr(
        _court,
        "detect_acts",
        lambda _root, _since: [
            _court.Act(
                trigger="wave",
                detail="3 repos",
                repos=["inthewords", "a", "b"],
                latest=_dt_date(2026, 8, 7),
            )
        ],
    )
    _seed_round(tmp_path, "covers.toml", trigger="wave", date="2026-08-07", act="wave: inthewords")
    d = tmp_path / _court.COURT_ROUNDS_DIR
    (d / "incomplete.toml").write_text(
        '[round]\nact = "wave: something else"\ntrigger = "wave"\n'
        'date = "2026-08-07"\nchair = "fresh-context"\n\n[answers]\n',
        encoding="utf-8",
    )
    assert _court.run_court(since="2026-08-01", root=tmp_path) == 1


def test_an_act_naming_no_repos_fails_rather_than_passes(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """`if act.repos:` made D2 OPT-IN on a field defaulting to [].

    `detect_acts` populates `repos` for every trigger today, but nothing
    asserted that — so the day it stopped, D2 would silently switch off and the
    zero-overlap hole would reopen with every test still green. A guard
    disabled by an empty field is the shape this module's own
    `_FRESH_CONTEXT_TRIGGERS` comment indicts.
    """
    monkeypatch.setattr(_court, "_repos", lambda _r: [tmp_path / "mgf-standard"])
    monkeypatch.setattr(_court, "_default_ref", lambda _r: "origin/main")
    monkeypatch.setattr(_court, "_git", lambda *_a, **_k: "abc123 a commit\n")
    monkeypatch.setattr(
        _court,
        "detect_acts",
        lambda _root, _since: [
            _court.Act(trigger="canon", detail="canon moved", repos=[], latest=_dt_date(2026, 8, 5))
        ],
    )
    _seed_round(tmp_path, "r.toml", act="canon: totally unrelated")
    assert _court.run_court(since="2026-08-01", root=tmp_path) == 1


@requires_git
def test_detect_acts_always_populates_repos(tmp_path: Path) -> None:
    """The producer-side half of the guard above, read back from the producer.

    Pins that every trigger `detect_acts` can emit carries a non-empty `repos`,
    so the check above can never be vacuously satisfied.
    """
    _repo(tmp_path, "mgf-standard", {"templates/woodpecker/base.yml": "clone:\n"})
    _repo(tmp_path, "mgf-fed", {"src/mgf/fed/_audit.py": "x = 1\n"})
    for n in ("mgf-a", "mgf-b", "mgf-c"):
        _repo(tmp_path, n, {"README.md": "x"})
    acts = _court.detect_acts(tmp_path, "2000-01-01")
    assert acts, "fixture produced no acts — the assertion below would be vacuous"
    for a in acts:
        assert a.repos, f"{a.trigger} act carries no repos: {a.detail}"
        # Same shape, one field over: D1's bound is now the act's OWN date, so an
        # undated act would disable D1 exactly as an unrepo'd act disabled D2.
        assert a.latest is not None, f"{a.trigger} act carries no date: {a.detail}"


@requires_git
def test_an_act_is_dated_by_the_CHANGE_it_is_not_by_its_repo(tmp_path: Path) -> None:
    """Caught live on the first 7-day run, in the same hour the rule shipped.

    `mgf-fed`'s gate paths last moved on 2026-08-05; an UNRELATED commit landed
    on 08-11; the act was dated 08-11 and three rounds that had judged it
    correctly went red. A round must be no older than the act — so the act must
    be dated by the change it IS, not by whatever else touched the repo in the
    same window. Otherwise a busy repo silently invalidates its own rounds."""
    import os
    from datetime import timedelta

    long_ago = (_court.datetime.now().astimezone() - timedelta(days=30)).strftime("%Y-%m-%d")
    repo = tmp_path / "mgf-fed"
    (repo / "src" / "mgf" / "fed").mkdir(parents=True)
    (repo / "src" / "mgf" / "fed" / "_audit.py").write_text("x = 1\n", encoding="utf-8")
    _run(["git", "init", "-q", "-b", "main", str(repo)])
    _run(["git", "-C", str(repo), "add", "-A"])
    env = {
        **os.environ,
        "GIT_AUTHOR_DATE": f"{long_ago}T12:00:00",
        "GIT_COMMITTER_DATE": f"{long_ago}T12:00:00",
    }
    args = ["-c", "commit.gpgsign=false", "-c", "user.email=t@x.invalid", "-c", "user.name=T"]
    subprocess.run(  # noqa: S603
        ["git", "-C", str(repo), *args, "commit", "-q", "-m", "gate: the act"],  # noqa: S607
        check=True,
        env=env,
    )
    # …then something unrelated, today. Under the old rule this dated the act.
    (repo / "README.md").write_text("nothing to do with the gate\n", encoding="utf-8")
    _run(["git", "-C", str(repo), "add", "-A"])
    _run(["git", "-C", str(repo), *args, "commit", "-q", "-m", "docs: unrelated"])

    gate = next(a for a in _court.detect_acts(tmp_path, "2000-01-01") if a.trigger == "gate")
    assert gate.latest is not None
    assert gate.latest.isoformat() == long_ago, (
        f"the gate act must be dated by the gate commit ({long_ago}), not by the repo's tip"
    )


@requires_git
def test_an_act_is_dated_on_THIS_benchs_calendar_not_the_forges(tmp_path: Path) -> None:
    """Measured the hour the act-date rule shipped, on the estate itself.

    `--date=short` renders in the COMMIT's own timezone, and Codeberg stamps
    merge commits `+0200`. Four merges made at 15:44 local (UTC-7) therefore came
    back dated **2026-08-13** — a day ahead of any date a human writing a round on
    this bench could produce, so the gate was unsatisfiable by construction until
    the calendar caught up. It printed `FAIL wave … act dated 2026-08-13`.

    A round is dated by a person reading a local calendar; the act must be read on
    the same calendar or the two are not comparable at all.

    The fixture stamps its commit in **UTC**, exactly as a forge does, at an
    instant chosen from this bench's own offset so that the UTC calendar day and
    the local calendar day are guaranteed to DIFFER. That construction is what
    keeps it from passing for free — a fixed literal would be vacuous on some
    benches and this test's whole subject is benches disagreeing about the day."""
    import os
    from datetime import UTC, timedelta

    local_tz = _court.datetime.now().astimezone().tzinfo
    offset = _court.datetime.now().astimezone().utcoffset() or timedelta(0)
    if not offset:
        pytest.skip("this bench runs at UTC, so no local/UTC day can disagree")
    # West of UTC: late local evening is already tomorrow in UTC. East: early
    # local morning is still yesterday in UTC.
    hour = 23 if offset < timedelta(0) else 1
    local_when = (
        _court.datetime.now()
        .astimezone(local_tz)
        .replace(hour=hour, minute=30, second=0, microsecond=0)
    )
    stamp = local_when.astimezone(UTC).strftime("%Y-%m-%dT%H:%M:%S+00:00")
    assert local_when.astimezone(UTC).date() != local_when.date(), (
        "fixture construction failed — the two calendars must disagree or this proves nothing"
    )

    repo = tmp_path / "mgf-fed"
    (repo / "src" / "mgf" / "fed").mkdir(parents=True)
    (repo / "src" / "mgf" / "fed" / "_audit.py").write_text("x = 1\n", encoding="utf-8")
    _run(["git", "init", "-q", "-b", "main", str(repo)])
    _run(["git", "-C", str(repo), "add", "-A"])
    env = {**os.environ, "GIT_AUTHOR_DATE": stamp, "GIT_COMMITTER_DATE": stamp}
    args = ["-c", "commit.gpgsign=false", "-c", "user.email=t@x.invalid", "-c", "user.name=T"]
    subprocess.run(  # noqa: S603
        ["git", "-C", str(repo), *args, "commit", "-q", "-m", "gate: the act"],  # noqa: S607
        check=True,
        env=env,
    )
    gate = next(a for a in _court.detect_acts(tmp_path, "2000-01-01") if a.trigger == "gate")
    assert gate.latest == local_when.date(), (
        f"act dated {gate.latest} but this bench's calendar says {local_when.date()} — "
        f"a round writer cannot reach a date the forge's timezone invented"
    )


def test_an_act_carrying_NO_date_fails_rather_than_passes(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """The consumer-side half. D1's bound is the act's own date, so a `None` there
    would make "is this round old enough?" unanswerable — and an unanswerable
    guard must refuse, not abstain. Precisely the `if act.repos:` mistake, one
    field over, and it is refused before it can be made."""
    monkeypatch.setattr(_court, "_repos", lambda _r: [tmp_path / "mgf-standard"])
    monkeypatch.setattr(_court, "_default_ref", lambda _r: "origin/main")
    monkeypatch.setattr(_court, "_git", lambda *_a, **_k: "abc123 a commit\n")
    monkeypatch.setattr(
        _court,
        "detect_acts",
        lambda _root, _since: [
            _court.Act(trigger="canon", detail="canon moved", repos=["mgf-standard"], latest=None)
        ],
    )
    _seed_round(tmp_path, "r.toml")
    assert _court.run_court(since="2026-08-01", root=tmp_path) == 1


def test_the_since_help_does_not_invite_a_ref() -> None:
    """The chair's L-8: the runtime error string was corrected and the argparse
    help — the surface a caller reads BEFORE choosing a value — was not. `HEAD~5`
    is not rejected by git; it silently becomes a seven-day window."""
    import io
    from contextlib import redirect_stdout

    from mgf.fed.__main__ import build_parser

    buf = io.StringIO()
    with redirect_stdout(buf), pytest.raises(SystemExit):
        build_parser().parse_args(["court", "--help"])
    help_text = buf.getvalue()
    assert "--since" in help_text, "the fixture must actually reach the court parser"
    line = next(ln for ln in help_text.splitlines() if "window start" in ln)
    assert "not a ref" in line, f"--since help still invites a ref: {line!r}"


# ---------------------------------------------------------------------------
# Board item 77 — the PAPER-144 fix moved ELEVEN roster predicates out of the
# gate paths and into `_roster.is_git_repo()`, which `CANON_PATHS["gate"]` did
# not watch. A fresh-context chair measured it: a commit touching only
# `_roster.py` demanded NO round, while the same change plus one `_audit.py`
# line convened a chair. A review-coverage regression, shipped as a refactor.
#
# This pins the repair. `_roster` decides what counts as a repo, so it decides
# what every gate is able to see; that is a gate path by any honest reading.
# ---------------------------------------------------------------------------


def test_roster_is_a_gate_path() -> None:
    """Whoever defines "is this a repo" defines what every gate can look at."""
    _repo, paths = _court.CANON_PATHS["gate"]
    assert "src/mgf/fed/_roster.py" in paths, (
        "_roster.py holds `is_git_repo()`, the predicate every audit roster now "
        "calls. Dropping it from the gate trigger means eleven predicates can be "
        "changed with no chair convened — the exact regression board item 77 records."
    )


def test_gate_paths_cover_every_module_defining_the_roster_predicate() -> None:
    """A stronger form: if `is_git_repo` moves house again, this test moves with it."""
    src = Path(__file__).resolve().parents[2] / "src"
    definers = sorted(
        p.relative_to(src.parent).as_posix()
        for p in src.rglob("*.py")
        if "def is_git_repo" in p.read_text(encoding="utf-8")
    )
    assert definers, "is_git_repo is not defined anywhere — has it been renamed?"
    _repo, paths = _court.CANON_PATHS["gate"]
    missing = [d for d in definers if d not in paths]
    assert not missing, (
        f"these modules define the roster predicate but are not gate paths: {missing}. "
        "Add them to CANON_PATHS['gate'] — otherwise changing what counts as a repo "
        "convenes no chair."
    )
