"""Tests for the cross-layer audit.

The properties under test are the ones that make this different from a pile of
checks — and both encode a specific incident:

* **A check that could not run is a FAILURE, not a pass.** A dead webhook looked
  exactly like a healthy repo for three weeks because "unverified" and "verified"
  shared an exit code.
* **A skipped check still names its layer boundary.** A skip that cannot say what
  it would have compared is as opaque as no check at all.

Plus the precision the port had to gain: promoting a *census* to a *gate* raises
the bar, because a noisy row in a report is tolerable and a noisy row in a gate is
how the real findings get ignored.
"""

from __future__ import annotations

import ast
import json
import shutil
import subprocess
from pathlib import Path
from unittest import mock

import pytest

from mgf.fed import _audit
from mgf.fed._audit import (
    CHECKS,
    COMMITTED,
    DECLARED,
    CheckResult,
    Finding,
    check_feedback_status,
    check_fleet,
    run_audit,
    run_checks,
)
from mgf.fed.cli import _init_project as _IP

# --------------------------------------------------------------------------- #
# The two structural properties.
# --------------------------------------------------------------------------- #


def test_every_check_declares_the_layer_boundary_it_crosses() -> None:
    valid = {_audit.DECLARED, _audit.COMMITTED, _audit.PUBLISHED, _audit.INSTALLED, _audit.LIVE}
    for name, (_fn, _net, layers) in CHECKS.items():
        assert len(layers) == 2, name
        assert set(layers) <= valid, f"{name} names a layer that does not exist: {layers}"


def test_an_unrequested_skip_is_a_failure_not_a_pass(tmp_path: Path, monkeypatch) -> None:
    """THE property. A check that cannot run must not share an exit code with a
    check that ran clean — that equivalence is what let a dead webhook pass."""
    monkeypatch.setattr(
        _audit,
        "check_fleet",
        lambda root, **kw: CheckResult("fleet", (DECLARED, COMMITTED), skipped="no token"),
    )
    monkeypatch.setitem(CHECKS, "fleet", (_audit.check_fleet, False, (DECLARED, COMMITTED)))
    assert run_audit(["fleet"], root=tmp_path) == 1


def test_a_skip_requested_by_offline_does_not_fail_the_run(tmp_path: Path) -> None:
    # --offline is an explicit opt-in to a subset: the operator asked for it, so it
    # is not a failure. It is still NAMED in the output (asserted below).
    assert run_audit(["pypi-namespace"], offline=True, root=tmp_path) == 0


def test_offline_skips_are_named_never_silent(tmp_path: Path, capsys) -> None:
    run_audit(["pypi-namespace", "governance"], offline=True, root=tmp_path)
    out = capsys.readouterr().out
    assert "did NOT run" in out
    assert "pypi-namespace" in out and "governance" in out


def test_a_skipped_check_still_reports_its_layers(tmp_path: Path) -> None:
    # A skip that cannot say what it WOULD have compared is as opaque as no check.
    [res] = run_checks(["ci-freshness"], offline=True, root=tmp_path)
    assert res.skipped == "--offline"
    assert res.layers == (_audit.LIVE, _audit.COMMITTED)


def test_clean_run_exits_zero(tmp_path: Path, monkeypatch) -> None:
    monkeypatch.setitem(
        CHECKS,
        "fleet",
        (
            lambda root, **kw: CheckResult("fleet", (DECLARED, COMMITTED), examined=3),
            False,
            (DECLARED, COMMITTED),
        ),
    )
    assert run_audit(["fleet"], root=tmp_path) == 0


def test_findings_exit_one(tmp_path: Path, monkeypatch) -> None:
    monkeypatch.setitem(
        CHECKS,
        "fleet",
        (
            lambda root, **kw: CheckResult(
                "fleet", (DECLARED, COMMITTED), examined=1, findings=[Finding("x", "gap")]
            ),
            False,
            (DECLARED, COMMITTED),
        ),
    )
    assert run_audit(["fleet"], root=tmp_path) == 1


def test_json_output_carries_layers_and_skips(tmp_path: Path, capsys) -> None:
    run_audit(["pypi-namespace"], offline=True, json_only=True, root=tmp_path)
    data = json.loads(capsys.readouterr().out)
    assert data["offline"] is True
    assert data["skipped"] == ["pypi-namespace"]
    assert data["checks"][0]["layers"] == [_audit.PUBLISHED, _audit.DECLARED]


# --------------------------------------------------------------------------- #
# check: fleet — declared ↔ committed/disk
# --------------------------------------------------------------------------- #


def _fleet_env(tmp_path: Path, *, registry: list[str], fleet_md: list[str]) -> Path:
    (tmp_path / "mgf-standard").mkdir(parents=True)
    (tmp_path / "mgf-standard" / "MGF_LIBRARIES.json").write_text(
        json.dumps({"libraries": [{"repo": r} for r in registry]}), encoding="utf-8"
    )
    (tmp_path / "mgf").mkdir(parents=True)
    (tmp_path / "mgf" / "FLEET.md").write_text("\n".join(f"- {n}" for n in fleet_md), "utf-8")
    for r in registry:
        (tmp_path / r).mkdir(exist_ok=True)
    return tmp_path


def test_fleet_flags_a_registry_member_absent_from_fleet_md(tmp_path: Path) -> None:
    root = _fleet_env(tmp_path, registry=["mgf-a", "mgf-b"], fleet_md=["mgf-a"])
    res = check_fleet(root)
    assert [f.target for f in res.findings] == ["mgf-b"]
    assert "not named in FLEET.md" in res.findings[0].detail


def test_fleet_flags_a_registry_member_missing_from_disk(tmp_path: Path) -> None:
    root = _fleet_env(tmp_path, registry=["mgf-a"], fleet_md=["mgf-a"])
    (root / "mgf-a").rmdir()
    assert any("not on disk" in f.detail for f in check_fleet(root).findings)


def test_fleet_is_clean_when_registry_disk_and_fleet_md_agree(tmp_path: Path) -> None:
    root = _fleet_env(tmp_path, registry=["mgf-a", "mgf-b"], fleet_md=["mgf-a", "mgf-b"])
    assert check_fleet(root).ok


def test_fleet_skips_rather_than_fails_without_the_court_workspace(tmp_path: Path) -> None:
    # FLEET.md lives in the private workspace, which is not a federation member. Its
    # absence is not a defect OF THE FLEET, so it must skip — and the skip then fails
    # the run, which is the honest outcome: nothing was verified.
    (tmp_path / "mgf-standard").mkdir(parents=True)
    res = check_fleet(tmp_path)
    assert res.skipped and not res.findings


# --------------------------------------------------------------------------- #
# check: feedback-status — the precision a gate needs that a census did not
# --------------------------------------------------------------------------- #


def _feedback(tmp_path: Path, body: str) -> Path:
    (tmp_path / "mgf-x").mkdir(parents=True)
    (tmp_path / "mgf-x" / "FEEDBACK.md").write_text(body, encoding="utf-8")
    return tmp_path


def test_a_paper_without_a_status_is_flagged(tmp_path: Path) -> None:
    root = _feedback(tmp_path, "## PAPER-12 — a thing\n\nsome prose, no status\n")
    assert [f.target for f in check_feedback_status(root).findings] == ["mgf-x:PAPER-12"]


def test_a_paper_with_a_status_bullet_is_clean(tmp_path: Path) -> None:
    root = _feedback(tmp_path, "## PAPER-12 — a thing\n\n**Status:** SHIPPED\n")
    assert check_feedback_status(root).ok


def test_an_emoji_prefixed_status_still_counts(tmp_path: Path) -> None:
    # Anchoring straight to a letter reported every `✅ SHIPPED` as unstated — i.e. it
    # called resolved papers unstated, the same over-reporting class as the release gate.
    root = _feedback(tmp_path, "## PAPER-12 — a thing\n\n**Status:** ✅ SHIPPED\n")
    assert check_feedback_status(root).ok


def test_a_cohort_heading_naming_a_RANGE_is_not_a_paper(tmp_path: Path) -> None:
    """The false positive this check produced on its very first run against the real
    fleet: `(PAPER-100..106)` groups seven papers that each carry their own heading."""
    root = _feedback(
        tmp_path,
        "## Plasma cohort — filed 2026-06-11 (PAPER-100..106)\n\nAll seven resolved.\n"
        "### PAPER-100 — a real one\n\n**Status:** SHIPPED\n",
    )
    assert check_feedback_status(root).ok


def test_a_real_paper_that_merely_MENTIONS_a_range_is_still_judged(tmp_path: Path) -> None:
    """The control for the fix above. `### PAPER-100 — … + REL-10..13` is a real paper;
    a looser "contains a range" rule would silently drop it — trading a false positive
    for a false negative, which in a gate is the worse of the two."""
    root = _feedback(tmp_path, "### PAPER-100 — Index of MUSTs omits REL-10..13\n\nno status\n")
    assert [f.target for f in check_feedback_status(root).findings] == ["mgf-x:PAPER-100"]


def test_template_headings_are_not_papers(tmp_path: Path) -> None:
    root = _feedback(tmp_path, "## PAPER-NN — <one-line title>\n\nthe submission template\n")
    assert check_feedback_status(root).ok


# --------------------------------------------------------------------------- #
# Ported behaviour that must not regress.
# --------------------------------------------------------------------------- #


def test_release_ignored_covers_the_unshipped_paths(tmp_path: Path) -> None:
    """The tuned list (PAPER-130): without it the gate called 28 distributions stale
    and 26 were noise. A gate that cries wolf 26 times is how the two real findings
    get ignored — over-reporting is a correctness bug in a gate, not a safe default."""
    for unshipped in (
        "docs/x.md",
        "tests/test_x.py",
        "uv.lock",
        "PUBLIC_API.json",
        "fed-resources/a.toml",
        "CHANGELOG.md",
        ".woodpecker.yml",
        "ops/deploy.sh",
    ):
        assert _audit.RELEASE_IGNORED.match(unshipped), unshipped
    for shipped in ("src/mgf/x/__init__.py", "src/mgf/x/core.py"):
        assert not _audit.RELEASE_IGNORED.match(shipped), shipped


def test_least_privilege_secrets_are_never_widened() -> None:
    # If a repo asks for a publish credential outside its allowed events, the fix is
    # the REPO. This set exists so the gate can never recommend loosening the secret.
    assert "devpi_password" in _audit.LEAST_PRIVILEGE
    assert "codeberg_package_token" in _audit.LEAST_PRIVILEGE


def test_held_deploy_path_arming_is_a_finding() -> None:
    """The inversion: while a path is HELD, the secrets being unavailable is the
    intended state and ARMING them is the failure. Registering these publishes the
    website, which is under the founder's explicit hold."""
    doc = {
        "when": {"event": "deployment"},
        "steps": [{"name": "deploy", "environment": {"TOK": {"from_secret": "resend_api_key"}}}],
    }
    findings = _audit._scope_findings("mgf-website", doc, {"resend_api_key": {"deployment"}})
    assert any("ARMED" in f.detail for f in findings)


def test_held_deploy_path_unregistered_is_the_intended_state() -> None:
    doc = {
        "when": {"event": "deployment"},
        "steps": [{"name": "deploy", "environment": {"TOK": {"from_secret": "resend_api_key"}}}],
    }
    assert _audit._scope_findings("mgf-website", doc, {}) == []


def test_an_unregistered_secret_outside_a_held_path_is_a_finding() -> None:
    doc = {"steps": [{"name": "s", "environment": {"T": {"from_secret": "nope"}}}]}
    findings = _audit._scope_findings("mgf-other", doc, {})
    assert findings and "dies at COMPILE" in findings[0].detail


def test_clone_and_services_demand_every_pipeline_event() -> None:
    # They are never pruned by a step-level `when:` — a scope gap there kills the run.
    doc = {"when": {"event": ["push", "tag"]}, "clone": {"x": {"from_secret": "k"}}}
    assert _audit._secret_demands(doc)["k"] == {"push", "tag"}


def test_a_step_when_narrows_the_events_it_demands() -> None:
    doc = {
        "when": {"event": ["push", "tag"]},
        "steps": [
            {
                "name": "publish",
                "when": {"event": "tag"},
                "environment": {"P": {"from_secret": "devpi_password"}},
            }
        ],
    }
    assert _audit._secret_demands(doc)["devpi_password"] == {"tag"}


def test_unknown_check_names_are_rejected_not_silently_ignored() -> None:
    assert "not-a-check" not in CHECKS


@pytest.mark.parametrize("name", sorted(CHECKS))
def test_every_check_is_runnable_offline_without_raising(name: str, tmp_path: Path) -> None:
    # An empty root is the hostile case: no registry, no repos, no workspace. A check
    # must skip or report, never crash the sweep for the checks behind it.
    [res] = run_checks([name], offline=True, root=tmp_path)
    assert res.name == name


# --------------------------------------------------------------------------- #
# The committed artifact + its CI-side gate.
#
# The expensive half needs the whole federation on disk plus credentials, so it
# cannot run in a single-repo CI clone. The cheap half asks the artifact four
# questions about ITSELF — and each is a way a checker has actually died here.
# --------------------------------------------------------------------------- #


def _artifact(tmp_path: Path, **over) -> Path:
    body = {
        "generated": _audit._today(),
        # The INPUT fingerprint (board item 75). Computed for this tmp_path so the
        # default fixture reads "complete AND describing this fleet"; in a bare tmp
        # dir every input is legitimately "absent", and an artifact recording that
        # matches. Tests that care override it.
        "inputs": _audit._input_digests(tmp_path / "AUDIT.json"),
        # A quiet bench by default; the moved-bench tests override it.
        "bench_moved": [],
        "checks": [],
        "findings": 0,
        "skipped": [],
        # Every REGISTERED check, because the default is meant to read "a complete
        # artifact" — naming a fixed pair meant the fixture silently stopped being
        # complete the moment a check was added.
        "ran": list(CHECKS),
    }
    body.update(over)
    p = tmp_path / "AUDIT.json"
    p.write_text(json.dumps(body), encoding="utf-8")
    return p


def test_a_missing_artifact_fails(tmp_path: Path) -> None:
    assert _audit.check_artifact(tmp_path / "nope.json") == 1


def test_a_fresh_complete_clean_artifact_passes(tmp_path: Path) -> None:
    assert _audit.check_artifact(_artifact(tmp_path)) == 0


def test_an_unstamped_artifact_fails(tmp_path: Path, capsys) -> None:
    # An artifact with no date can never be called stale — so it would read as
    # current forever.
    assert _audit.check_artifact(_artifact(tmp_path, generated="")) == 1
    assert "UNSTAMPED" in capsys.readouterr().out


def test_a_stale_artifact_fails(tmp_path: Path, capsys) -> None:
    # The 2026-07-29 conformance tracker asserted a result from 2026-07-04 for
    # twenty-five days, because nothing was ever going to look.
    assert _audit.check_artifact(_artifact(tmp_path, generated="2020-01-01")) == 1
    assert "STALE" in capsys.readouterr().out


def test_an_INCOMPLETE_artifact_fails_even_with_zero_findings(tmp_path: Path, capsys) -> None:
    """THE property. An artifact from an --offline run carries real numbers and covers
    half the boundaries. Zero findings across the checks that RAN is not a clean audit,
    and must never be allowed to read as one."""
    art = _artifact(tmp_path, findings=0, skipped=["ci-freshness", "governance"])
    assert _audit.check_artifact(art) == 1
    out = capsys.readouterr().out
    assert "INCOMPLETE" in out and "ci-freshness" in out


def test_an_artifact_with_findings_fails(tmp_path: Path) -> None:
    assert _audit.check_artifact(_artifact(tmp_path, findings=3)) == 1


def test_an_unreadable_artifact_fails(tmp_path: Path) -> None:
    p = tmp_path / "AUDIT.json"
    p.write_text("{ not json", encoding="utf-8")
    assert _audit.check_artifact(p) == 1


def test_the_artifact_records_what_did_NOT_run(tmp_path: Path) -> None:
    run_audit(
        ["pypi-namespace", "fleet"],
        offline=True,
        root=tmp_path,
        write=tmp_path / "AUDIT.json",
        when="2026-07-30",
    )
    data = json.loads((tmp_path / "AUDIT.json").read_text())
    assert "pypi-namespace" in data["skipped"]
    assert data["generated"] == "2026-07-30"


def test_an_offline_artifact_does_not_satisfy_the_gate(tmp_path: Path) -> None:
    # End-to-end of the property above: writing an offline artifact and then gating
    # it must FAIL, however clean the checks that ran happened to be.
    run_audit(
        ["pypi-namespace", "fleet"],
        offline=True,
        root=tmp_path,
        write=tmp_path / "AUDIT.json",
        when=_audit._today(),
    )
    assert _audit.check_artifact(tmp_path / "AUDIT.json") == 1


def test_the_audits_own_artifact_is_not_shipped_source(tmp_path: Path) -> None:
    """`AUDIT.json` is this module's committed artifact — a repo-root governance file
    that no wheel ships. Leaving it out made the audit report the very repo that stores
    its results, one hour after the same gate taught us the same lesson about
    `AGENTS.md`. Two entries in one session, from opposite directions: an exclusion
    list decays, and it decays fastest right after you add something to the repo."""
    assert _audit.RELEASE_IGNORED.match("AUDIT.json")


# --------------------------------------------------------------------------- #
# Shipped-ness is read from the build config, not guessed.
#
# RELEASE_IGNORED existed because we were GUESSING what a wheel contains. It
# decayed twice in one session (AGENTS.md, AUDIT.json) — noise — and, worse, it
# UNDER-reported: mgf-standard force-includes docs/standards into its wheel while
# the list excludes docs/, so a change to the standards corpus (the repo's entire
# product) would have shipped unnoticed. Over-reporting is noise; under-reporting
# is a gate that lies.
# --------------------------------------------------------------------------- #

_HATCH = """
[project]
name = "mgf-x"
version = "1.0.0"
[tool.hatch.build.targets.wheel]
packages = ["src/mgf"]
force-include = {"docs/standards" = "mgf/x/_standards", "REGISTRY.json" = "mgf/x/_reg.json"}
"""


def test_force_included_paths_ship(tmp_path: Path) -> None:
    """THE under-report. A force-included directory is wheel content."""
    pkgs, forced = _audit._wheel_sources(_HATCH)
    assert _audit._ships("docs/standards/RELEASING.md", pkgs, forced)
    assert _audit._ships("REGISTRY.json", pkgs, forced)


def test_the_old_exclusion_list_would_have_missed_it() -> None:
    # The regression stated as a comparison, so the fix can't silently regress.
    assert _audit.RELEASE_IGNORED.match("docs/standards/RELEASING.md"), (
        "the list excludes docs/ — which is exactly why reading the build config matters"
    )


def test_package_sources_ship(tmp_path: Path) -> None:
    pkgs, forced = _audit._wheel_sources(_HATCH)
    assert _audit._ships("src/mgf/x/core.py", pkgs, forced)


def test_unshipped_paths_do_not(tmp_path: Path) -> None:
    pkgs, forced = _audit._wheel_sources(_HATCH)
    for path in ("tests/test_x.py", "AGENTS.md", "AUDIT.json", "uv.lock", "docs/OTHER.md"):
        assert not _audit._ships(path, pkgs, forced), path


def test_a_sibling_prefix_is_not_a_match() -> None:
    # `docs/standards-old/x` must not match the `docs/standards` force-include.
    pkgs, forced = _audit._wheel_sources(_HATCH)
    assert not _audit._ships("docs/standards-old/x.md", pkgs, forced)
    assert not _audit._ships("src/mgf-other/y.py", pkgs, forced)


def test_an_unreadable_config_degrades_to_the_list_and_says_so() -> None:
    # Losing the config must not silently change the verdict's basis.
    assert _audit._wheel_sources("this is not toml {{{") is None
    assert _audit._wheel_sources('[project]\nname = "x"\n') is None


# --------------------------------------------------------------------------- #
# agent-context — declared ↔ committed.
#
# Phase 2 of ONE-ESTATE installed the root pointer on 54 repos and named
# `audit --check agent-context` as its acceptance criterion. The check did not
# exist, so the mechanism shipped with nothing in any exit code able to notice it
# leaving again. These are its negative controls: every one is a defect the wave
# actually found on a real repo, and the check is only worth its exit code if each
# of them turns it red.
#
# They build REAL git repos with a real `origin`, because the check reads the
# committed tree and a fake `_git` would only prove the parser agrees with the
# fixture — the "writer and checker confirm each other" trap this check exists to
# break. That costs coverage in CI: `python:*-slim` has no `git`, so these skip
# there, as ~76 tests in this suite already do. Filed against the pipeline
# template rather than papered over — the image, not the tests, is the defect.
# --------------------------------------------------------------------------- #

#: The house convention (see ``test_feedback.py``) for tests that shell out to git.
requires_git = pytest.mark.skipif(
    shutil.which("git") is None, reason="git not on PATH (e.g. the slim CI image)"
)

_HEALTHY_DEEP = (
    f"{_IP.CLAUDE_MD_BEGIN_MARKER}\nmanaged deep context -->\n{_IP.CLAUDE_MD_END_TEXT}\n"
    "\nAuthored deep context that must stay readable.\n"
)
#: The eighteen-repo defect: the END comment is gone, so the BEGIN's `<!--` never
#: closes and every authored line below is swallowed into a comment.
_TRUNCATED_DEEP = f"{_IP.CLAUDE_MD_BEGIN_MARKER}\nmanaged deep context -->\n\nAuthored.\n"


def _root_pointer(deep: str) -> str:
    return (
        f"{_IP.CLAUDE_MD_BEGIN_MARKER}\n{_IP.root_claude_md_block(deep)}{_IP.CLAUDE_MD_END_TEXT}\n"
    )


def _git_ok(cwd: Path, *args: str) -> None:
    subprocess.run(  # noqa: S603 — fixed git argv over a tmp_path fixture
        ["git", *args],  # noqa: S607 — `git` from PATH is the house convention
        cwd=cwd,
        check=True,
        capture_output=True,
        text=True,
    )


def _git_ok_out(cwd: Path, *args: str) -> str:
    """`_git_ok`, but returning stdout — for asserting a premise really holds."""
    return subprocess.run(  # noqa: S603
        ["git", *args],  # noqa: S607
        cwd=cwd,
        check=True,
        capture_output=True,
        text=True,
    ).stdout.strip()


def _member(root: Path, name: str, files: dict[str, str]) -> Path:
    """A declared member with a real `origin` the check can interrogate.

    The remote is a bare repo on disk reached over `file://`, so `ls-remote` — the
    freshness question — is exercised for real without touching the network.
    """
    bare = root / ".remotes" / f"{name}.git"
    bare.parent.mkdir(parents=True, exist_ok=True)
    _git_ok(root, "init", "--bare", "-b", "main", str(bare))
    repo = root / name
    repo.mkdir(parents=True)
    _git_ok(repo, "init", "-b", "main")
    _git_ok(repo, "config", "user.email", "t@example.invalid")
    _git_ok(repo, "config", "user.name", "T")
    for rel, body in files.items():
        path = repo / rel
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(body, encoding="utf-8")
    _git_ok(repo, "add", "-A")
    _git_ok(repo, "-c", "commit.gpgsign=false", "commit", "-m", "seed")
    _git_ok(repo, "remote", "add", "origin", f"file://{bare}")
    _git_ok(repo, "push", "-u", "origin", "main")
    return repo


def _fed(tmp_path: Path, members: dict[str, dict[str, str]]) -> Path:
    """A federation root declaring exactly `members` in MGF_LIBRARIES.json."""
    std = tmp_path / "mgf-standard"
    std.mkdir(parents=True)
    (std / "MGF_LIBRARIES.json").write_text(
        json.dumps({"libraries": [{"repo": n} for n in members]}), encoding="utf-8"
    )
    for name, files in members.items():
        _member(tmp_path, name, files)
    return tmp_path


def _healthy(deep: str = _IP.CANONICAL_DEEP_CONTEXT) -> dict[str, str]:
    """A repo carrying the WHOLE agent-context surface.

    `AGENTS.md` is part of it since the Phase 2 court's P2-F4: PLAN §2.2.3 named
    it a managed surface, nothing checked it, and deleting it left every gate
    green. A fixture that omitted it was quietly asserting the old, weaker shape.
    """
    return {"CLAUDE.md": _root_pointer(deep), deep: _HEALTHY_DEEP, "AGENTS.md": "# Agents\n"}


def _findings(root: Path) -> list[str]:
    res = _audit.check_agent_context(root)
    return [f.detail for f in res.findings]


@requires_git
def test_agent_context_is_clean_when_the_pointer_is_present_and_resolves(tmp_path: Path) -> None:
    """The positive control. Without it, every red below could be red for free."""
    root = _fed(tmp_path, {"mgf-thing": _healthy()})
    res = _audit.check_agent_context(root)
    assert res.findings == []
    assert res.examined == 1
    assert res.ok


@requires_git
def test_a_deleted_root_claude_md_turns_the_check_red(tmp_path: Path) -> None:
    """Phase 2's acceptance names this exact experiment."""
    files = _healthy()
    del files["CLAUDE.md"]
    root = _fed(tmp_path, {"mgf-thing": files})
    assert any("no root CLAUDE.md" in d for d in _findings(root))


@requires_git
def test_a_root_file_without_the_managed_block_is_a_finding(tmp_path: Path) -> None:
    files = _healthy() | {"CLAUDE.md": "# Hand-written, no markers\n"}
    root = _fed(tmp_path, {"mgf-thing": files})
    assert any("carries no managed block" in d for d in _findings(root))


@requires_git
def test_a_pointer_naming_a_file_that_does_not_exist_is_a_finding(tmp_path: Path) -> None:
    """The sharpest defect the wave found: green by every check, useless to the agent.

    Writer and checker compared the block against the same constant, so neither
    ever consulted the repo. This one asks the tree.
    """
    root = _fed(tmp_path, {"mgf-thing": {"CLAUDE.md": _root_pointer(_IP.CANONICAL_DEEP_CONTEXT)}})
    assert any("does NOT exist" in d for d in _findings(root))


@requires_git
def test_a_legacy_only_repo_with_a_legacy_pointer_is_CLEAN(tmp_path: Path) -> None:
    """PAPER-54 left `docs/CLAUDE.md` valid and catch-up deliberately does not move
    those repos. A checker that demanded the canonical path would flag every one of
    them — a false positive is how a real finding gets ignored."""
    root = _fed(tmp_path, {"mgf-thing": _healthy(_audit.LEGACY_DEEP_CONTEXT)})
    assert _findings(root) == []


@requires_git
def test_a_legacy_repo_carrying_the_CANONICAL_pointer_is_a_finding(tmp_path: Path) -> None:
    """The five-repo bug, exactly: legacy tree, canonical pointer, dangling."""
    files = {
        "CLAUDE.md": _root_pointer(_IP.CANONICAL_DEEP_CONTEXT),
        _audit.LEGACY_DEEP_CONTEXT: _HEALTHY_DEEP,
    }
    root = _fed(tmp_path, {"mgf-thing": files})
    assert any("has drifted" in d for d in _findings(root))


@requires_git
def test_an_unterminated_managed_block_in_the_deep_file_is_a_finding(tmp_path: Path) -> None:
    """`mgf-common` shipped 696 lines of authored deep context inside a comment."""
    files = _healthy() | {_IP.CANONICAL_DEEP_CONTEXT: _TRUNCATED_DEEP}
    root = _fed(tmp_path, {"mgf-thing": files})
    assert any("END marker(s)" in d for d in _findings(root))


@requires_git
def test_unbalanced_html_comments_in_the_deep_file_are_a_finding(tmp_path: Path) -> None:
    files = _healthy() | {_IP.CANONICAL_DEEP_CONTEXT: _HEALTHY_DEEP + "\n<!-- stray\n"}
    root = _fed(tmp_path, {"mgf-thing": files})
    assert any("unbalanced HTML comments" in d for d in _findings(root))


@requires_git
def test_a_declared_member_absent_from_disk_is_a_finding_not_a_silent_pass(tmp_path: Path) -> None:
    root = _fed(tmp_path, {"mgf-thing": _healthy()})
    (root / "mgf-standard" / "MGF_LIBRARIES.json").write_text(
        json.dumps({"libraries": [{"repo": "mgf-thing"}, {"repo": "mgf-ghost"}]}), encoding="utf-8"
    )
    assert any("not on disk" in d for d in _findings(root))


@requires_git
def test_a_STALE_view_of_origin_main_is_a_finding(tmp_path: Path) -> None:
    """The working tree is never consulted, so the remote-tracking ref IS the
    evidence — and an unfetched ref answers a question nobody asked. Auditing it
    and printing PASS is the same "unverified reads as verified" equivalence the
    module exists to break."""
    root = _fed(tmp_path, {"mgf-thing": _healthy()})
    repo = root / "mgf-thing"
    # Move the forge on without fetching: a second clone pushes, we do not pull.
    other = tmp_path / "elsewhere"
    _git_ok(tmp_path, "clone", f"file://{root / '.remotes' / 'mgf-thing.git'}", str(other))
    _git_ok(other, "config", "user.email", "t@example.invalid")
    _git_ok(other, "config", "user.name", "T")
    (other / "NEW.md").write_text("moved on\n", encoding="utf-8")
    _git_ok(other, "add", "-A")
    _git_ok(other, "-c", "commit.gpgsign=false", "commit", "-m", "forge moves on")
    _git_ok(other, "push", "origin", "main")
    assert repo.is_dir()
    assert any("STALE" in d for d in _findings(root))


@requires_git
def test_the_check_reads_the_COMMITTED_tree_not_the_working_one(tmp_path: Path) -> None:
    """Trees across this estate are routinely parked on other branches. A filesystem
    read scored a merged repo as missing and undercounted by three."""
    root = _fed(tmp_path, {"mgf-thing": _healthy()})
    (root / "mgf-thing" / "CLAUDE.md").unlink()  # merged on main, absent on disk
    assert _findings(root) == []


def test_agent_context_skips_rather_than_guesses_without_the_registry(tmp_path: Path) -> None:
    res = _audit.check_agent_context(tmp_path)
    assert res.skipped
    assert not res.ok  # a skip is not a pass


# --------------------------------------------------------------------------- #
# The remote is resolved, not assumed — `_default_ref` AND the probe below it.
#
# Every committed-layer check reads its evidence through this one pair, so a repo
# they cannot answer for is a repo no check can audit. `soothrede`'s only remote
# on the bench is named `codeberg`; the hardcoded `origin` returned "" and
# `ci-workspace-truth` reported "nothing to audit" about a repo with an entirely
# ordinary default branch — this module's own equivalence ("I could not look" ==
# "there is nothing there") committed in its shared helper. Waived at the
# consumer in mgf-standard's AUDIT_ACCEPTED.toml, whose recorded remedy is this
# fix: "have `_default_ref` resolve the configured remote instead of hardcoding
# the name".
#
# The remedy is taken as a CLASS, which is why `_origin_ref_unverified` is here
# too: fixing only the resolver hands a good `codeberg` ref to a probe that still
# asks `origin`, and trades "unresolvable" for "unverifiable" — a repo that was
# waived-green going red on the first online audit. The same defect one function
# down is still the defect.
#
# Real git repos, as above: the question here IS what git answers, and the
# freshness probe in particular is only worth testing against a forge it can
# actually reach (the neighbouring STALE test makes the same call).
# --------------------------------------------------------------------------- #


def _remote_named(root: Path, name: str, remote: str) -> Path:
    """A member whose sole remote is `remote` — at `codeberg`, the soothrede shape."""
    repo = _member(root, name, {"README.md": "seed\n"})
    if remote != "origin":
        _git_ok(repo, "remote", "rename", "origin", remote)
    return repo


def _also_push(repo: Path, remote: str, branch: str) -> None:
    """A second default-branch candidate on the forge, so resolution ORDER is
    observable: `main` and `master` both exist and only one of them is HEAD."""
    _git_ok(repo, "push", remote, f"HEAD:refs/heads/{branch}")
    _git_ok(repo, "fetch", remote)


@requires_git
def test_default_ref_uses_origin_wherever_origin_exists(tmp_path: Path) -> None:
    """The unchanged path — a convention that IS followed is still the answer."""
    repo = _remote_named(tmp_path, "mgf-thing", "origin")
    assert _audit._default_ref(repo) == "refs/remotes/origin/main"
    # The forge's own answer outranks the named fallbacks: with both branches on
    # the remote, HEAD -> master must beat the `main` guess or the order is fiction.
    _also_push(repo, "origin", "master")
    _git_ok(repo, "remote", "set-head", "origin", "master")
    assert _audit._default_ref(repo) == "refs/remotes/origin/master"
    # And a second remote does not make an existing `origin` ambiguous.
    _git_ok(repo, "remote", "add", "codeberg", f"file://{tmp_path / '.remotes' / 'mgf-thing.git'}")
    _git_ok(repo, "fetch", "codeberg")
    assert _audit._default_ref(repo) == "refs/remotes/origin/master"


@requires_git
def test_default_ref_resolves_a_sole_remote_that_is_not_named_origin(tmp_path: Path) -> None:
    """The soothrede shape. One remote, named `codeberg`: the whole resolution runs
    against it — HEAD included, which is why every ref is built from the name and
    none of them spells `origin`. Sooth-family repos default to `master`, so the
    fallback that matters here is the second one."""
    repo = _remote_named(tmp_path, "soothrede", "codeberg")
    assert _audit._default_ref(repo) == "refs/remotes/codeberg/main"
    _also_push(repo, "codeberg", "master")
    _git_ok(repo, "remote", "set-head", "codeberg", "master")
    assert _audit._default_ref(repo) == "refs/remotes/codeberg/master"


@requires_git
def test_default_ref_is_empty_with_no_remote_at_all(tmp_path: Path) -> None:
    """Nothing to resolve against is still nothing to audit — the return contract
    is unchanged, and the caller's finding is true for a remote-less repo."""
    repo = _remote_named(tmp_path, "mgf-thing", "origin")
    _git_ok(repo, "remote", "remove", "origin")
    assert _audit._default_ref(repo) == ""


@requires_git
def test_default_ref_refuses_to_guess_among_several_non_origin_remotes(tmp_path: Path) -> None:
    """Ambiguity stays UNAUDITED rather than guessed. Two remotes can disagree about
    what the default branch is, and picking one by position would put a coin toss
    behind an exit code — the failure mode this module exists to end, not a smaller
    version of it. Both remotes are fetched, so the "" is the choice being refused,
    not a missing ref."""
    repo = _remote_named(tmp_path, "soothrede", "codeberg")
    _git_ok(repo, "remote", "add", "fork", f"file://{tmp_path / '.remotes' / 'soothrede.git'}")
    _git_ok(repo, "fetch", "fork")
    assert _audit._git(repo, "rev-parse", "--verify", "--quiet", "refs/remotes/fork/main")
    assert _audit._default_ref(repo) == ""


@requires_git
def test_the_freshness_probe_asks_the_remote_the_ref_NAMES(tmp_path: Path) -> None:
    """The other half of the class. The probe is handed `refs/remotes/codeberg/main`
    and must ask `codeberg` — asking `origin` reaches nothing, and this module turns
    "reached nothing" into a finding, so a resolver that had just learned the repo's
    real remote would be overruled by a probe that had not. The forge here is a real
    bare repo over `file://`, so a "" verdict is proof the probe arrived."""
    repo = _remote_named(tmp_path, "soothrede", "codeberg")
    ref = _audit._default_ref(repo)
    assert ref == "refs/remotes/codeberg/main"
    assert _audit._origin_ref_unverified(repo, ref) == ""


@requires_git
def test_a_STALE_view_is_reported_against_the_remote_that_actually_holds_it(
    tmp_path: Path,
) -> None:
    """Staleness must survive the rename, or the fix above would have bought its
    green by going blind. The message names `codeberg`, not a remote this repo
    does not have — an operator cannot fetch from a name that is not there."""
    repo = _remote_named(tmp_path, "soothrede", "codeberg")
    # Move the forge on without fetching: a second clone pushes, we do not pull.
    other = tmp_path / "elsewhere"
    _git_ok(tmp_path, "clone", f"file://{tmp_path / '.remotes' / 'soothrede.git'}", str(other))
    _git_ok(other, "config", "user.email", "t@example.invalid")
    _git_ok(other, "config", "user.name", "T")
    (other / "NEW.md").write_text("moved on\n", encoding="utf-8")
    _git_ok(other, "add", "-A")
    _git_ok(other, "-c", "commit.gpgsign=false", "commit", "-m", "forge moves on")
    _git_ok(other, "push", "origin", "main")
    why = _audit._origin_ref_unverified(repo, _audit._default_ref(repo))
    assert "STALE" in why
    assert "codeberg/main" in why, why


@requires_git
def test_a_member_whose_only_remote_is_codeberg_audits_CLEAN_end_to_end(tmp_path: Path) -> None:
    """The whole point, through a real check rather than its parts. This is the
    soothrede shape as `ci-workspace-truth` met it: resolver AND probe, online (the
    offline path never reached the probe, which is why the residual could hide).
    Against the unfixed resolver this repo is "nothing to audit"; against a fixed
    resolver with an unfixed probe it is "could not reach the forge". Only both
    fixes make it what it is — an ordinary conformant member."""
    root = _fed(tmp_path, {"soothrede": _healthy()})
    _git_ok(root / "soothrede", "remote", "rename", "origin", "codeberg")
    res = _audit.check_agent_context(root)
    assert res.findings == []
    assert res.examined == 1
    assert res.ok


def test_a_check_REGISTERED_SINCE_the_artifact_was_written_makes_it_incomplete(
    tmp_path: Path, capsys
) -> None:
    """The artifact asked only "what was skipped?", and a check added later is in
    neither list — so a whole boundary could go unexamined while the gate stayed
    green. That is this module's own failure class, one layer up."""
    path = _artifact(tmp_path, ran=[n for n in CHECKS if n != "agent-context"], skipped=[])
    assert _audit.check_artifact(path) == 1
    assert "registered since" in capsys.readouterr().out


def test_an_artifact_that_accounts_for_every_registered_check_passes(tmp_path: Path) -> None:
    assert _audit.check_artifact(_artifact(tmp_path, ran=list(CHECKS), skipped=[])) == 0


# --------------------------------------------------------------------------- #
# conformance-ledgers — declared ↔ committed.
#
# On 2026-08-03 an mgf-standard release published five releases' worth of corpus
# in one act: 33 new MUSTs went live, no repo's ledger had a row for any of them,
# and `app-doctor` went red fleet-wide minutes later. The corpus footer had
# recorded the required follow-up wave each time and each time it was deferred.
# The debt was written down; nothing was watching it. This is the watcher.
# --------------------------------------------------------------------------- #

_INDEX = """\
# Standards

## Index of MUSTs

| Rule | Family | Text |
|---|---|---|
| AG-01 | AGENTS | a thing |
| SEAT-01 | SEATS | another thing |

## Something else

| NOT-99 | X | outside the index — must not count |
"""


def _corpus(root: Path, readme: str, members: list[str]) -> None:
    """An mgf-standard checkout whose COMMITTED main carries the corpus."""
    _member(
        root,
        "mgf-standard",
        {
            "docs/standards/README.md": readme,
            "MGF_LIBRARIES.json": json.dumps({"libraries": [{"repo": m} for m in members]}),
            **_healthy(),
        },
    )


def _ledger(*ids: str) -> dict[str, str]:
    rows = "\n".join(f"| {i} | ✅ | evidence | notes |" for i in ids)
    return {"docs/inprogress/MGF_STANDARDS_CONFORMANCE.md": f"# Ledger\n\n{rows}\n"}


@requires_git
def test_only_the_index_of_musts_table_counts(tmp_path: Path) -> None:
    """A rule id elsewhere in the README is not a declared MUST — over-counting
    would make every ledger permanently short, and a gate that can never go green
    is a gate everyone learns to ignore."""
    assert _audit.index_of_musts(_INDEX) == {"AG-01", "SEAT-01"}


@requires_git
def test_a_ledger_accounting_for_every_declared_must_is_clean(tmp_path: Path) -> None:
    _corpus(tmp_path, _INDEX, ["mgf-thing"])
    _member(tmp_path, "mgf-thing", _ledger("AG-01", "SEAT-01"))
    res = _audit.check_conformance_ledgers(tmp_path)
    assert res.findings == []
    assert res.examined == 1


@requires_git
def test_an_unaccounted_must_is_a_finding_that_NAMES_it(tmp_path: Path) -> None:
    _corpus(tmp_path, _INDEX, ["mgf-thing"])
    _member(tmp_path, "mgf-thing", _ledger("AG-01"))
    [f] = _audit.check_conformance_ledgers(tmp_path).findings
    assert "SEAT-01" in f.detail
    assert "1 of 2" in f.detail or "1 unaccounted" in f.detail


@requires_git
def test_a_member_with_no_ledger_at_all_is_a_finding(tmp_path: Path) -> None:
    _corpus(tmp_path, _INDEX, ["mgf-thing"])
    _member(tmp_path, "mgf-thing", {"README.md": "no ledger here\n"})
    [f] = _audit.check_conformance_ledgers(tmp_path).findings
    assert "MGF_STANDARDS_CONFORMANCE" in f.detail


@requires_git
def test_the_corpus_is_read_from_mgf_standards_COMMITTED_main(tmp_path: Path) -> None:
    """THE regression test. The first draft took the MUST index from the INSTALLED
    corpus, the way app-doctor legitimately does — and on the bench that wrote it
    the installed corpus was 0.1.66, nine releases stale. It reported 268 declared
    MUSTs while mgf-standard's main declared 306. A check answering "would
    publishing redden the fleet?" cannot ask this laptop's wheel.

    Here the working tree is edited to DROP a MUST; the committed corpus still
    declares it, so the finding must survive.
    """
    _corpus(tmp_path, _INDEX, ["mgf-thing"])
    _member(tmp_path, "mgf-thing", _ledger("AG-01"))
    (tmp_path / "mgf-standard" / "docs" / "standards" / "README.md").write_text(
        _INDEX.replace("| SEAT-01 | SEATS | another thing |\n", ""), encoding="utf-8"
    )
    [f] = _audit.check_conformance_ledgers(tmp_path).findings
    assert "SEAT-01" in f.detail, "the check read the working tree, not the committed corpus"


@requires_git
def test_the_ledger_is_read_from_the_members_COMMITTED_main(tmp_path: Path) -> None:
    _corpus(tmp_path, _INDEX, ["mgf-thing"])
    _member(tmp_path, "mgf-thing", _ledger("AG-01", "SEAT-01"))
    (tmp_path / "mgf-thing" / "docs" / "inprogress" / "MGF_STANDARDS_CONFORMANCE.md").unlink()
    assert _audit.check_conformance_ledgers(tmp_path).findings == []


def test_conformance_ledgers_skips_rather_than_guesses_without_the_corpus(tmp_path: Path) -> None:
    res = _audit.check_conformance_ledgers(tmp_path)
    assert res.skipped and not res.ok  # a skip is not a pass


# --------------------------------------------------------------------------- #
# Phase 2 court findings — P2-F2, P2-F4, P2-F7.
#
# Every one of these is a FALSE PASS the court found in the check above, which
# the check's own author did not. They are kept as a group because they share a
# lesson: a detector is only as wide as its roster and only as deep as the
# properties it names.
# --------------------------------------------------------------------------- #


@requires_git
def test_the_roster_reaches_beyond_the_declared_members(tmp_path: Path) -> None:
    """P2-F2. Scoping to MGF_LIBRARIES' members meant `exit 0 fleet-wide` was a
    statement about SCOPE, not state: the wave's universe was 57, the registry
    declared 44, and BOTH repos known to lack the mechanism sat outside it."""
    _corpus(tmp_path, _INDEX, ["mgf-declared"])  # registry lists only this one
    _member(tmp_path, "mgf-declared", _healthy())
    _member(tmp_path, "outsider-app", {"README.md": "no agent context here\n"})
    res = _audit.check_agent_context(tmp_path, offline=True)
    assert "outsider-app" in [f.target for f in res.findings], (
        "an on-disk bench outside the registry was invisible to the check"
    )


@requires_git
def test_sooth_family_repos_stay_out_of_scope(tmp_path: Path) -> None:
    """They keep hand-written AGENTS.md and were never in this programme's scope.

    Family is READ from the sibling family's own registry, never inferred from the
    name (F-44) — so the fixture must declare it, exactly as a real bench does.
    The repo here is deliberately named `soothwatch`: under the old prefix filter
    this test passed on the NAME and would have kept passing with the registry
    absent, which is precisely the coincidence F-44 was.
    """
    _corpus(tmp_path, _INDEX, [])
    (tmp_path / "sooth-standard").mkdir(parents=True, exist_ok=True)
    (tmp_path / "sooth-standard" / "SOOTH_LIBRARIES.json").write_text(
        json.dumps({"repos": ["soothwatch"]}), encoding="utf-8"
    )
    _member(tmp_path, "soothwatch", {"README.md": "sibling family\n"})
    assert _audit.check_agent_context(tmp_path, offline=True).findings == []


@requires_git
def test_a_sooth_repo_carrying_the_MANAGED_BLOCK_stays_in_scope(tmp_path: Path) -> None:
    """Opting into the mechanism is what puts a repo in the roster, not its name.

    `first-light` is the live case: a Sooth-family repo carrying MGF's managed
    root-CLAUDE.md block. The prefix filter kept it in by accident; the artifact
    keeps it in for the reason that is actually true.
    """
    from mgf.fed.cli._init_project import CLAUDE_MD_BEGIN_MARKER

    _corpus(tmp_path, _INDEX, [])
    (tmp_path / "sooth-standard").mkdir(parents=True, exist_ok=True)
    (tmp_path / "sooth-standard" / "SOOTH_LIBRARIES.json").write_text(
        json.dumps({"repos": ["soothwatch"]}), encoding="utf-8"
    )
    _member(tmp_path, "soothwatch", {"README.md": "sibling family\n"})
    (tmp_path / "soothwatch" / "CLAUDE.md").write_text(
        CLAUDE_MD_BEGIN_MARKER + "\nopted in\n", encoding="utf-8"
    )
    assert "soothwatch" in [
        f.target for f in _audit.check_agent_context(tmp_path, offline=True).findings
    ]


@requires_git
def test_an_exempt_bench_is_skipped_and_its_reason_is_recorded(tmp_path: Path) -> None:
    """An exemption must be explicit and reviewable — never silent absence."""
    _corpus(tmp_path, _INDEX, [])
    _member(tmp_path, "mgf-secrets-store", {"README.md": "ciphertext, not a bench\n"})
    assert _audit.check_agent_context(tmp_path, offline=True).findings == []
    assert _audit.AGENT_CONTEXT_EXEMPT["mgf-secrets-store"].strip()


@requires_git
def test_a_deleted_AGENTS_md_is_a_finding(tmp_path: Path) -> None:
    """P2-F4. PLAN §2.2.3 named AGENTS.md a managed surface and nothing checked
    it, so deleting it left every gate green — including in `mgf`, the subject of
    acceptance criterion 4."""
    _corpus(tmp_path, _INDEX, ["mgf-thing"])
    files = _healthy()
    del files["AGENTS.md"]
    _member(tmp_path, "mgf-thing", files)
    assert any(
        "AGENTS.md" in f.detail for f in _audit.check_agent_context(tmp_path, offline=True).findings
    )


@requires_git
def test_a_DUPLICATED_managed_block_in_the_ROOT_file_is_a_finding(tmp_path: Path) -> None:
    """P2-F7. Block integrity was applied to the deep-context file only, so a root
    CLAUDE.md with two managed blocks passed clean — the mgf-hrb duplicate-block
    defect, one file up, in the file Claude Code actually auto-loads."""
    _corpus(tmp_path, _INDEX, ["mgf-thing"])
    files = _healthy()
    files["CLAUDE.md"] = files["CLAUDE.md"] * 2  # two full BEGIN/END blocks
    _member(tmp_path, "mgf-thing", files)
    assert any(
        "END marker(s)" in f.detail
        for f in _audit.check_agent_context(tmp_path, offline=True).findings
    )


@requires_git
def test_a_single_well_formed_root_block_stays_clean(tmp_path: Path) -> None:
    # The false-positive control for the two properties added above.
    _corpus(tmp_path, _INDEX, ["mgf-thing"])
    _member(tmp_path, "mgf-thing", _healthy())
    assert _audit.check_agent_context(tmp_path, offline=True).findings == []


# --------------------------------------------------------------------------- #
# ci-workspace-truth
#
# The gate that keeps the counterfeit default branch dead. Every test below is
# a defect that was live on the fleet on 2026-08-03, not a hypothetical.
# --------------------------------------------------------------------------- #

_CANON_SCRIPT = '''#!/usr/bin/env python3
"""The hardened AG-03 checker."""


class _NoBaseError(RuntimeError):
    """No base branch could be resolved, so there is nothing to check."""


def main() -> int:
    return 0
'''

_LEGACY_FAIL_OPEN = '''#!/usr/bin/env python3
"""The pre-0.1.75 checker: prints "skipping" and returns 0."""


def main() -> int:
    print("cannot compute main..HEAD ... skipping")
    return 0
'''

#: The canonical fetch step, guards included (SC-23). Shared by the template
#: fixture and the conformant-member fixture so "conformant" means *identical to
#: canon* in the tests exactly as it does in the check.
_FETCH_STEP_YAML = """\
  - name: fetch-default-branch
    image: woodpeckerci/plugin-git:2.9.2
    environment:
      CLONE_SSH_KEY:
        from_secret: codeberg_ssh_clone_key
    commands:
      - '[ -n "$CLONE_SSH_KEY" ] || { echo "FATAL: clone key empty"; exit 1; }'
      - git fetch --no-tags origin "+refs/heads/$B:refs/remotes/origin/$B"
"""

#: The same step with the SC-23 guard stripped — P3-F2's exact shape: a step with
#: the right NAME that pulls a secret into its shell and guards nothing.
_FETCH_STEP_UNGUARDED = (
    "\n".join(ln for ln in _FETCH_STEP_YAML.splitlines() if "FATAL:" not in ln) + "\n"
)

#: …and with the branch hardcoded — figrecovery's live shape on 2026-08-04. It
#: works only while that repo's default happens to be the name in the literal.
_FETCH_STEP_HARDCODED = _FETCH_STEP_YAML.replace(
    '"+refs/heads/$B:refs/remotes/origin/$B"', '"+refs/heads/master:refs/remotes/origin/master"'
)

_CLONE_HEAD = """\
clone:
  - name: clone
    image: woodpeckerci/plugin-git:2.9.2
"""

_STEPS_TAIL = """\
steps:
  - name: commit-trailers
    image: python:3.12-slim
    commands:
      - python scripts/check_commit_trailers.py
"""

#: mgf-standard's WF-13 template. Deliberately carries a ``{{PLACEHOLDER}}`` in its
#: steps section: the template is NOT valid YAML end-to-end, so this fixture proves
#: the check slices the ``clone:`` block before parsing rather than safe_load-ing
#: the file (which raises, and would have made canon silently unavailable).
_CANON_TEMPLATE = (
    _CLONE_HEAD
    + _FETCH_STEP_YAML
    + "steps:\n  - name: lint\n    commands:\n      - {{EXTRAS_INSTALL_CMD}}\n"
)

_PIPELINE_WITH_FETCH = _CLONE_HEAD + _FETCH_STEP_YAML + _STEPS_TAIL

_PIPELINE_FETCH_UNGUARDED = _CLONE_HEAD + _FETCH_STEP_UNGUARDED + _STEPS_TAIL

_PIPELINE_FETCH_HARDCODED = _CLONE_HEAD + _FETCH_STEP_HARDCODED + _STEPS_TAIL

_PIPELINE_NO_FETCH = """\
clone:
  - name: clone
    image: woodpeckerci/plugin-git:2.9.2
steps:
  - name: commit-trailers
    image: python:3.12-slim
    commands:
      - python scripts/check_commit_trailers.py
"""


def _member_on(root: Path, name: str, files: dict[str, str], branch: str) -> Path:
    """`_member`, but on an arbitrary default branch.

    Parameterised because hardcoding `main` is the exact defect this check
    exists to catch: figrecovery and the four sooth* repos default to `master`,
    and a checker that only ever sees `main` would score them as broken (or, in
    the mirror-image failure, never look at them at all).
    """
    bare = root / ".remotes" / f"{name}.git"
    bare.parent.mkdir(parents=True, exist_ok=True)
    _git_ok(root, "init", "--bare", "-b", branch, str(bare))
    repo = root / name
    repo.mkdir(parents=True)
    _git_ok(repo, "init", "-b", branch)
    _git_ok(repo, "config", "user.email", "t@example.invalid")
    _git_ok(repo, "config", "user.name", "T")
    for rel, body in files.items():
        path = repo / rel
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(body, encoding="utf-8")
    _git_ok(repo, "add", "-A")
    _git_ok(repo, "-c", "commit.gpgsign=false", "commit", "-m", "seed")
    _git_ok(repo, "remote", "add", "origin", f"file://{bare}")
    _git_ok(repo, "push", "-u", "origin", branch)
    return repo


def _ci_fed(tmp_path: Path, members: dict[str, dict[str, str]], branch: str = "main") -> Path:
    """A federation root whose mgf-standard carries the canonical checker."""
    _member_on(
        tmp_path,
        "mgf-standard",
        {
            "scripts/check_commit_trailers.py": _CANON_SCRIPT,
            ".woodpecker.yml": _PIPELINE_WITH_FETCH,
            "templates/woodpecker/base.yml": _CANON_TEMPLATE,
        },
        branch,
    )
    for name, files in members.items():
        _member_on(tmp_path, name, files, branch)
    return tmp_path


def _conformant() -> dict[str, str]:
    return {
        ".woodpecker.yml": _PIPELINE_WITH_FETCH,
        "scripts/check_commit_trailers.py": _CANON_SCRIPT,
    }


def _ci_findings(root: Path) -> list[str]:
    return [f.detail for f in _audit.check_ci_workspace_truth(root, offline=True).findings]


@requires_git
def test_ci_workspace_truth_is_clean_on_a_conformant_fleet(tmp_path: Path) -> None:
    """The positive control. Without it every red below could be red for free."""
    root = _ci_fed(tmp_path, {"mgf-thing": _conformant()})
    res = _audit.check_ci_workspace_truth(root, offline=True)
    assert res.findings == []
    assert res.examined == 2  # mgf-thing AND mgf-standard itself
    assert res.ok


@requires_git
def test_a_pipeline_without_the_fetch_step_is_a_finding(tmp_path: Path) -> None:
    """The whole point: with no fetch step, CI's `main` IS the PR head, so the
    AG-03 gate silently checks HEAD's tip alone and reports green."""
    files = _conformant() | {".woodpecker.yml": _PIPELINE_NO_FETCH}
    root = _ci_fed(tmp_path, {"mgf-thing": files})
    assert any("no `fetch-default-branch` clone step" in d for d in _ci_findings(root))


@requires_git
def test_a_fetch_step_without_its_SC23_guard_is_a_finding(tmp_path: Path) -> None:
    """P3-F2, the regression that names this test.

    The step's NAME was the whole of property 1, so a step that pulled both clone
    secrets into its shell and guarded neither shipped to 43 repos and passed the
    gate written in the same session to police this file. Only ``first-light``'s
    bespoke ``verify_ci`` — a check nobody had wired to the fleet — objected.
    """
    files = _conformant() | {".woodpecker.yml": _PIPELINE_FETCH_UNGUARDED}
    root = _ci_fed(tmp_path, {"mgf-thing": files})
    details = _ci_findings(root)
    assert any("differs from canon" in d for d in details), details
    # The finding must say WHAT differs — an unactionable finding gets waived.
    assert any("canonical command(s) MISSING" in d and "FATAL:" in d for d in details), details


@requires_git
def test_a_fetch_step_with_a_hardcoded_branch_is_a_finding(tmp_path: Path) -> None:
    """figrecovery's live shape on 2026-08-04, and the reason a NAME test is not
    enough: the step is present, runs, and goes green — while fetching a branch
    literal instead of ``$CI_REPO_DEFAULT_BRANCH``. It is correct only by
    coincidence of that repo's current default, and it is the exact silent no-op
    the canonical step's own comment says the step exists to end."""
    files = _conformant() | {".woodpecker.yml": _PIPELINE_FETCH_HARDCODED}
    root = _ci_fed(tmp_path, {"mgf-thing": files})
    details = _ci_findings(root)
    assert any("differs from canon" in d for d in details), details
    assert any("refs/heads/master" in d for d in details), details


@requires_git
def test_unavailable_canon_skips_rather_than_passing_everything(tmp_path: Path) -> None:
    """ "I could not verify" must never share an exit code with "I verified".

    With no parseable canonical step, every member would compare equal to nothing
    and the check would report a clean fleet — the failure mode this whole
    programme exists to end. It must refuse to run instead."""
    root = _ci_fed(tmp_path, {"mgf-thing": _conformant()})
    std = root / "mgf-standard"
    (std / "templates" / "woodpecker" / "base.yml").write_text("clone: []\n", encoding="utf-8")
    _git_ok(std, "add", "-A")
    _git_ok(std, "-c", "commit.gpgsign=false", "commit", "-m", "drop the canonical step")
    _git_ok(std, "push", "origin", "HEAD")
    res = _audit.check_ci_workspace_truth(root, offline=True)
    assert res.skipped, "a check with no canon must SKIP, not pass"
    assert "nothing to compare" in res.skipped
    assert not res.ok, "a skipped check must not report ok"


@requires_git
def test_a_legacy_FAIL_OPEN_checker_is_a_finding(tmp_path: Path) -> None:
    """mgf-website and mgf-website-template were still carrying this on
    2026-08-03 — found by this check on its first real run."""
    files = _conformant() | {"scripts/check_commit_trailers.py": _LEGACY_FAIL_OPEN}
    root = _ci_fed(tmp_path, {"mgf-thing": files})
    assert any("FAIL-OPEN copy" in d for d in _ci_findings(root))


@requires_git
def test_the_fail_open_probe_does_not_key_on_the_word_skipping(tmp_path: Path) -> None:
    """PLAN §3.6(c) proposed grepping for "skipping" + return 0. That test is
    unusable: the CANONICAL script contains the word four times ("This check does
    not pass by skipping"), so it would flag every conformant repo. The probe
    asks for a symbol only the fail-closed design defines instead."""
    canon_plus = _CANON_SCRIPT + "\n# This check does not pass by skipping.\n"
    files = _conformant() | {"scripts/check_commit_trailers.py": canon_plus}
    root = _ci_fed(tmp_path, {"mgf-thing": files})
    details = _ci_findings(root)
    assert not any("FAIL-OPEN" in d for d in details), "the word 'skipping' must not condemn"
    assert any("differs from the canonical" in d for d in details)


@requires_git
def test_a_drifted_checker_copy_is_a_finding(tmp_path: Path) -> None:
    files = _conformant() | {"scripts/check_commit_trailers.py": _CANON_SCRIPT + "\n# local edit\n"}
    root = _ci_fed(tmp_path, {"mgf-thing": files})
    assert any("differs from the canonical scaffold copy" in d for d in _ci_findings(root))


@requires_git
def test_a_missing_checker_is_a_finding(tmp_path: Path) -> None:
    root = _ci_fed(tmp_path, {"mgf-thing": {".woodpecker.yml": _PIPELINE_WITH_FETCH}})
    assert any("AG-03 is ungated here" in d for d in _ci_findings(root))


# --- property 4: the checker is actually WIRED (F-41) ---------------------- #

#: Byte-canonical checker, conformant clone section — and no step that runs it.
#: This is what six Zig members look like: `base-zig.yml` has no
#: `<<INSERT:commit-trailers>>` marker, so the renderer cannot emit the gate.
_PIPELINE_FETCH_BUT_NO_TRAILERS_STEP = (
    _CLONE_HEAD
    + _FETCH_STEP_YAML
    + "steps:\n  - name: zig-build-and-test\n    image: alpine\n"
    + "    commands:\n      - zig build test\n"
)

#: mgf-media's actual line. `test -f X && python X` alone is fail-CLOSED (exit 1
#: when X is missing); the `|| echo` is what converts the gate into a message.
_PIPELINE_TRAILERS_FAIL_OPEN = (
    _CLONE_HEAD
    + _FETCH_STEP_YAML
    + "steps:\n  - name: commit-trailers\n    image: python:3.12-slim\n    commands:\n"
    "      - test -f scripts/check_commit_trailers.py && python scripts/check_commit_trailers.py"
    ' || echo "trailer check not yet vendored"\n'
)

#: Woodpecker also accepts `steps:` as a MAP. Reading only the list form would be
#: the `.woodpecker/`-layout blind spot one level down.
_PIPELINE_TRAILERS_MAP_FORM = (
    _CLONE_HEAD
    + _FETCH_STEP_YAML
    + "steps:\n  commit-trailers:\n    image: python:3.12-slim\n    commands:\n"
    "      - python scripts/check_commit_trailers.py\n"
)


@requires_git
def test_a_canonical_checker_that_no_step_RUNS_is_a_finding(tmp_path: Path) -> None:
    """F-41. Properties 1-3 all pass here and the gate is worth nothing.

    Measured on the real roster: **10 repos ran the checker from no step at
    all** — 7 carrying the byte-canonical fail-closed copy, 2 a legacy fail-open
    one, 1 no copy. The estate's own standing rule is *a verifier that is not in
    a gate's exit code does not exist*, and the check written to enforce this
    gate had never asked.

    (The first version of this docstring said "eleven repos carried the
    byte-canonical, fail-closed checker", conflating the three categories into
    the most alarming one. A fresh-context court chair re-measured it; so did I.
    The error ran in the direction that flatters the finding.)
    """
    files = _conformant() | {".woodpecker.yml": _PIPELINE_FETCH_BUT_NO_TRAILERS_STEP}
    root = _ci_fed(tmp_path, {"mgf-thing": files})
    details = _ci_findings(root)
    assert any("in ZERO exit codes" in d for d in details), details
    # and it must not ALSO be condemned for the file, which is perfect
    assert not any("differs from the canonical" in d for d in details)
    assert not any("FAIL-OPEN copy" in d for d in details)


@requires_git
def test_an_invocation_behind_a_pipe_pipe_is_a_finding(tmp_path: Path) -> None:
    files = _conformant() | {".woodpecker.yml": _PIPELINE_TRAILERS_FAIL_OPEN}
    root = _ci_fed(tmp_path, {"mgf-thing": files})
    assert any("`||` alternative" in d for d in _ci_findings(root))


@requires_git
def test_the_fail_open_invocation_is_caught_even_when_the_checker_is_ABSENT(
    tmp_path: Path,
) -> None:
    """The ordering that matters.

    mgf-media is this exact repo: no checker file, and a step that invokes it
    behind `||` — so the step is green BECAUSE the file is missing. Properties 2
    and 3 `continue` on a missing script, so evaluating property 4 after them
    would have reported only the cheap half and hidden the expensive one.
    """
    root = _ci_fed(tmp_path, {"mgf-thing": {".woodpecker.yml": _PIPELINE_TRAILERS_FAIL_OPEN}})
    details = _ci_findings(root)
    assert any("`||` alternative" in d for d in details), details
    assert any("AG-03 is ungated here" in d for d in details), details


def _with_cmd(cmd: str) -> str:
    return (
        _CLONE_HEAD
        + _FETCH_STEP_YAML
        + "steps:\n  - name: commit-trailers\n    image: python:3.12-slim\n    commands:\n"
        f"      - {cmd}\n"
    )


@requires_git
def test_an_AND_guarded_invocation_that_IS_the_last_command_is_not_a_finding(
    tmp_path: Path,
) -> None:
    """The over-indictment control, kept — but no longer load-bearing on its own.

    A check that cries wolf earns the blanket waiver that ends it, so a genuinely
    fail-CLOSED shape must stay clean. The point is that THIS shape is fail-closed
    only because the guard is the LAST command; see the test below.
    """
    files = _conformant() | {
        ".woodpecker.yml": _with_cmd(
            "test -f scripts/check_commit_trailers.py && python scripts/check_commit_trailers.py"
        )
    }
    assert _ci_findings(_ci_fed(tmp_path, {"mgf-thing": files})) == []


@requires_git
@pytest.mark.parametrize(
    ("cmd", "needle"),
    [
        # POSIX: `set -e` is ignored for every element of an AND-OR list but the
        # LAST. Measured in dash and bash:
        #   set -e; test -f X && python X              -> 1   fail-CLOSED
        #   set -e; test -f X && python X; echo AFTER  -> 0   FAIL-OPEN
        # The original property asserted `&&` was fail-closed FULL STOP and its
        # control used the guard as the sole command — the one position where the
        # claim holds. A court chair reproduced the open case against the real
        # check and it reported CLEAN.
        ("python scripts/check_commit_trailers.py; echo done", "later `;`-separated"),
        (
            "test -f scripts/check_commit_trailers.py"
            " && python scripts/check_commit_trailers.py; echo done",
            "later `;`-separated",
        ),
        ("python scripts/check_commit_trailers.py && echo ok", "non-final element of an `&&`"),
        ("python scripts/check_commit_trailers.py &", "backgrounded"),
        ("python scripts/check_commit_trailers.py || echo oops", "`||` alternative"),
    ],
)
def test_an_invocation_whose_exit_status_is_discarded_is_a_finding(
    tmp_path: Path, cmd: str, needle: str
) -> None:
    files = _conformant() | {".woodpecker.yml": _with_cmd(cmd)}
    details = _ci_findings(_ci_fed(tmp_path, {"mgf-thing": files}))
    assert any(needle in d for d in details), (cmd, details)


@requires_git
def test_a_FAIL_CLOSED_or_alternative_is_not_a_finding(tmp_path: Path) -> None:
    """`cmd || { echo …; exit 1; }` reports failure. Flagging it is crying wolf.

    The first version of property 4 flagged any `||`, and a court chair caught
    this exact false positive — in the property whose own comment warns that a
    check which indicts a conformant shape earns the blanket waiver that ends it.
    """
    files = _conformant() | {
        ".woodpecker.yml": _with_cmd(
            "python scripts/check_commit_trailers.py || { echo 'AG-03 VIOLATED'; exit 1; }"
        )
    }
    assert _ci_findings(_ci_fed(tmp_path, {"mgf-thing": files})) == []


@requires_git
@pytest.mark.parametrize(
    "cmd",
    [
        "echo 'remember to run scripts/check_commit_trailers.py by hand'",
        "cat scripts/check_commit_trailers.py",
    ],
)
def test_a_MENTION_of_the_checker_is_not_an_invocation(tmp_path: Path, cmd: str) -> None:
    """The property promises "some step actually RUNS it" and measured whether a
    STRING APPEARED. A court chair broke it with one `echo`."""
    files = _conformant() | {".woodpecker.yml": _with_cmd(cmd)}
    assert any(
        "in ZERO exit codes" in d for d in _ci_findings(_ci_fed(tmp_path, {"mgf-thing": files}))
    )


@requires_git
def test_failure_ignore_on_the_gate_step_is_a_finding(tmp_path: Path) -> None:
    """Woodpecker's own way of making a step non-blocking. A property whose
    subject is "the invocation can fail" must read the key named `failure`."""
    yml = (
        _CLONE_HEAD
        + _FETCH_STEP_YAML
        + "steps:\n  - name: commit-trailers\n    image: python:3.12-slim\n"
        "    failure: ignore\n    commands:\n"
        "      - python scripts/check_commit_trailers.py\n"
    )
    files = _conformant() | {".woodpecker.yml": yml}
    assert any(
        "failure: ignore" in d for d in _ci_findings(_ci_fed(tmp_path, {"mgf-thing": files}))
    )


@requires_git
def test_a_gate_step_that_never_runs_on_a_pull_request_is_a_finding(tmp_path: Path) -> None:
    yml = (
        _CLONE_HEAD
        + _FETCH_STEP_YAML
        + "steps:\n  - name: commit-trailers\n    image: python:3.12-slim\n"
        "    when:\n      - event: push\n    commands:\n"
        "      - python scripts/check_commit_trailers.py\n"
    )
    files = _conformant() | {".woodpecker.yml": yml}
    assert any(
        "never runs on the pull_request" in d
        for d in _ci_findings(_ci_fed(tmp_path, {"mgf-thing": files}))
    )


@requires_git
def test_the_MAP_form_of_steps_is_read_too(tmp_path: Path) -> None:
    files = _conformant() | {".woodpecker.yml": _PIPELINE_TRAILERS_MAP_FORM}
    root = _ci_fed(tmp_path, {"mgf-thing": files})
    assert _ci_findings(root) == []


@requires_git
def test_every_workflow_needs_its_OWN_fetch_step(tmp_path: Path) -> None:
    """The `break` that made property 1 a repo-level question.

    Its comment said "evaluated per WORKFLOW ... 'the repo has a fetch step
    somewhere' is not the question" while the loop underneath broke out on the
    first workflow carrying the step. `hrb-app` is a `.woodpecker/` pair, and one
    conformant workflow would have covered a bare one.
    """
    files = {
        "scripts/check_commit_trailers.py": _CANON_SCRIPT,
        ".woodpecker/ci.yml": _PIPELINE_WITH_FETCH,
        ".woodpecker/e2e.yml": _PIPELINE_NO_FETCH,
    }
    root = _ci_fed(tmp_path, {"mgf-thing": files})
    details = _ci_findings(root)
    assert any("no `fetch-default-branch` clone step" in d and "e2e.yml" in d for d in details), (
        details
    )
    assert not any("ci.yml" in d for d in details), "the conformant workflow must not be condemned"


@requires_git
def test_a_repo_with_no_pipeline_at_all_is_a_finding_not_a_silent_skip(tmp_path: Path) -> None:
    """ "No CI" and "CI that checks nothing" fail the same way — quietly."""
    root = _ci_fed(tmp_path, {"mgf-thing": {"README.md": "# nothing\n"}})
    assert any("no .woodpecker.yml" in d for d in _ci_findings(root))


@requires_git
def test_a_master_default_repo_is_judged_on_master_not_main(tmp_path: Path) -> None:
    """The hardcode trap, in the check that exists to end it.

    Three sibling checks in this module read the literal `origin/main`. On a
    master-default repo that is either a crash or a false finding — and the
    session that fixed this class produced exactly that bug in its own
    verification loop, on figrecovery, hours after writing the rule down.
    """
    root = _ci_fed(tmp_path, {"mgf-thing": _conformant()}, branch="master")
    res = _audit.check_ci_workspace_truth(root, offline=True)
    assert res.findings == [], f"master-default fleet must be judged on master: {res.findings}"
    assert res.examined == 2


@requires_git
def test_family_is_READ_from_the_sooth_registry_not_guessed_from_a_prefix(
    tmp_path: Path,
) -> None:
    """F-44. The roster said "out by family" and filtered `startswith("sooth")`.

    Both directions of that error are live on the real bench: `first-light` is a
    SOOTH repo the prefix cannot see, and `soothrede` is a Sooth repo the prefix
    excluded while it carried a DRIFTED copy of MGF's own trailers checker.

    The fixture names the two shapes explicitly rather than relying on the real
    registry, so the assertion still means something on a bench without it.
    """
    root = _ci_fed(tmp_path, {"mgf-thing": _conformant(), "oddly-named": _conformant()})
    (root / "sooth-standard").mkdir(parents=True, exist_ok=True)
    (root / "sooth-standard" / "SOOTH_LIBRARIES.json").write_text(
        json.dumps({"repos": ["oddly-named"]}), encoding="utf-8"
    )
    roster = _audit.ci_workspace_truth_roster(root)
    # a Sooth repo carrying MGF scaffold artifacts stays IN, on the artifact
    assert "oddly-named" in roster
    (root / "oddly-named" / _audit._TRAILER_SCRIPT).unlink()
    assert "oddly-named" not in _audit.ci_workspace_truth_roster(root)


@requires_git
def test_an_unreadable_sooth_registry_WIDENS_the_roster_never_narrows_it(tmp_path: Path) -> None:
    """The direction of the failure mode is the whole point.

    With no registry the roster admits the estate. "I could not tell whose family
    this is" must resolve to looking at too much — never to looking at less and
    reporting clean, which is the direction every defect in this programme took.
    """
    root = _ci_fed(tmp_path, {"soothy": _conformant()})
    assert "soothy" in _audit.ci_workspace_truth_roster(root)


@requires_git
def test_an_exempt_repo_is_not_examined(tmp_path: Path) -> None:
    root = _ci_fed(tmp_path, {"mgf-secrets-store": {"README.md": "# vault\n"}})
    res = _audit.check_ci_workspace_truth(root, offline=True)
    assert res.findings == []
    assert res.examined == 1  # mgf-standard only


@requires_git
def test_ci_workspace_truth_is_registered_and_names_its_layers(tmp_path: Path) -> None:
    """A check absent from CHECKS gates nothing — Phase 2's P2-F1, exactly."""
    assert "ci-workspace-truth" in CHECKS
    fn, needs_network, layers = CHECKS["ci-workspace-truth"]
    assert fn is _audit.check_ci_workspace_truth
    assert needs_network is True
    assert layers == (COMMITTED, DECLARED)


# --------------------------------------------------------------------------- #
# ci-secret-guards — SC-23 fleet-wide
#
# SC-23 was a declared MUST from 2026-07-22, and until this check existed exactly
# ONE implementation of it ran anywhere: first-light's tools/verify_ci.py, wired to
# first-light. It was correct, and it was the only thing that objected when the
# unguarded fetch step reached 43 repos. Pointed at the fleet it found 13 more.
# --------------------------------------------------------------------------- #


def _guard_check(root: Path, exempt: dict | None = None):
    """Run the check with an EXPLICIT tolerance table.

    Defaults to empty rather than the real one on purpose: the real table's entries
    describe the live fleet, and on a synthetic fixture they match nothing — which
    this check correctly reports as a finding. Tests that are not about tolerances
    must not inherit that noise, and tests that ARE about them should say so.
    """
    with mock.patch.object(_audit, "SECRET_GUARD_EXEMPT", exempt if exempt is not None else {}):
        return _audit.check_ci_secret_guards(root, offline=True)


def _guard_findings(root: Path, exempt: dict | None = None) -> list[str]:
    return [f.detail for f in _guard_check(root, exempt).findings]


_STEP_UNGUARDED = """\
steps:
  - name: deploy
    image: alpine
    environment:
      DEPLOY_TOKEN:
        from_secret: deploy_token
    commands:
      - ./deploy.sh
"""

_STEP_GUARDED = """\
steps:
  - name: deploy
    image: alpine
    environment:
      DEPLOY_TOKEN:
        from_secret: deploy_token
    commands:
      - '[ -n "$DEPLOY_TOKEN" ] || { echo "FATAL: deploy_token injected empty"; exit 1; }'
      - ./deploy.sh
"""

#: Tests the variable but never exits — a log line, not a gate.
_STEP_TESTED_NOT_EXITED = """\
steps:
  - name: deploy
    image: alpine
    environment:
      DEPLOY_TOKEN:
        from_secret: deploy_token
    commands:
      - '[ -n "$DEPLOY_TOKEN" ] || echo "warning: deploy_token is empty"'
      - ./deploy.sh
"""

#: A plugin step: `settings:`, no `commands:`. Exempt BY CONSTRUCTION.
_STEP_PLUGIN = """\
steps:
  - name: publish
    image: woodpeckerci/plugin-s3
    settings:
      access_key:
        from_secret: s3_key
"""

#: The shape that actually EXERCISES the no-commands exemption: the secret arrives
#: as an env var (which this check does read) but the step runs no shell.
#:
#: Without this fixture the exemption was untested — `_STEP_PLUGIN` alone passes
#: whether or not the exemption exists, because a `settings:` from_secret is never
#: collected in the first place. Found by sabotage: deleting the exemption left the
#: suite green. A test that cannot fail is not evidence.
_STEP_ENV_INJECTED_NO_COMMANDS = """\
steps:
  - name: plugin-with-env
    image: woodpeckerci/plugin-git
    environment:
      DEPLOY_TOKEN:
        from_secret: deploy_token
"""

#: The legacy `secrets:` list — Woodpecker injects the UPPERCASED secret name.
_STEP_LEGACY_SECRETS = """\
steps:
  - name: deploy
    image: alpine
    secrets:
      - deploy_token
    commands:
      - ./deploy.sh
"""


def _guard_member(pipeline: str) -> dict[str, str]:
    return _conformant() | {".woodpecker.yml": _CLONE_HEAD + _FETCH_STEP_YAML + pipeline}


@requires_git
def test_secret_guards_clean_on_a_conformant_fleet(tmp_path: Path) -> None:
    """The positive control. Without it every red below could be red for free."""
    root = _ci_fed(tmp_path, {"mgf-thing": _guard_member(_STEP_GUARDED)})
    res = _guard_check(root)
    assert res.findings == [], res.findings
    assert res.examined == 2
    assert res.ok


@requires_git
def test_an_unguarded_secret_step_is_a_finding(tmp_path: Path) -> None:
    root = _ci_fed(tmp_path, {"mgf-thing": _guard_member(_STEP_UNGUARDED)})
    details = _guard_findings(root)
    assert any("`deploy`" in d and "deploy_token" in d and "SC-23" in d for d in details), details
    # The finding carries the exact remediation — a finding you must go and
    # research is a finding that gets waived.
    assert any('[ -n "$DEPLOY_TOKEN" ]' in d for d in details), details


@requires_git
def test_testing_without_exiting_is_not_a_guard(tmp_path: Path) -> None:
    """A guard that reports and continues is a log line. SC-23 asks the step to
    STOP, because the whole failure mode is running on with an empty credential."""
    root = _ci_fed(tmp_path, {"mgf-thing": _guard_member(_STEP_TESTED_NOT_EXITED)})
    assert any("deploy_token" in d for d in _guard_findings(root))


@requires_git
def test_a_plugin_settings_step_is_exempt_by_construction(tmp_path: Path) -> None:
    """No `commands:` means no shell and no first command to guard. This is why
    the whole fleet's clone block passes — and mistaking `fetch-default-branch`
    for one of these, when it overrides the entrypoint WITH commands, is P3-F2."""
    root = _ci_fed(tmp_path, {"mgf-thing": _guard_member(_STEP_PLUGIN)})
    assert _guard_findings(root) == []
    # …and the case that actually exercises the rule: an ENV-VAR secret (which this
    # check does collect) in a step with no shell to guard it in.
    root2 = _ci_fed(tmp_path / "b", {"mgf-thing": _guard_member(_STEP_ENV_INJECTED_NO_COMMANDS)})
    assert _guard_findings(root2) == []


@requires_git
def test_the_legacy_secrets_list_form_is_caught(tmp_path: Path) -> None:
    """`secrets: [deploy_token]` injects $DEPLOY_TOKEN just as `environment:` does.
    Checking only the modern spelling would leave the older one unguarded and
    unreported — the roster defect one level down, in the syntax."""
    root = _ci_fed(tmp_path, {"mgf-thing": _guard_member(_STEP_LEGACY_SECRETS)})
    assert any("deploy_token" in d for d in _guard_findings(root))


@requires_git
def test_a_declared_tolerance_suppresses_its_own_finding(tmp_path: Path) -> None:
    """ "Guard the guard": a step that legitimately tolerates an empty secret must
    carry no fatal guard, or every correct leaf repo goes false-red."""
    root = _ci_fed(tmp_path, {"mgf-thing": _guard_member(_STEP_UNGUARDED)})
    assert _guard_findings(root, {("deploy", "DEPLOY_TOKEN"): "test tolerance"}) == []


@requires_git
def test_a_tolerance_matching_nothing_is_itself_a_finding(tmp_path: Path) -> None:
    """D-17's rule, applied to tolerances: a list that outlives its subject stops
    describing the fleet and starts hiding it."""
    root = _ci_fed(tmp_path, {"mgf-thing": _guard_member(_STEP_GUARDED)})
    details = _guard_findings(root, {("gone", "VANISHED"): "no longer on the fleet"})
    assert any("matches NO step" in d and "gone" in d for d in details), details


@requires_git
def test_secret_guards_is_registered_and_names_its_layers(tmp_path: Path) -> None:
    """A check absent from CHECKS gates nothing — Phase 2's P2-F1, exactly."""
    assert "ci-secret-guards" in CHECKS
    fn, needs_network, layers = CHECKS["ci-secret-guards"]
    assert fn is _audit.check_ci_secret_guards
    assert needs_network is True
    assert layers == (COMMITTED, DECLARED)


# --------------------------------------------------------------------------- #
# Accepted findings (AUDIT_ACCEPTED.toml)
#
# The gate failed on ANY finding, and the fleet's findings are about OTHER repos.
# On 2026-08-03 that froze mgf-standard: branch protection requires the pipeline,
# the pipeline runs this gate, and the gate was red over eleven unrelated repos'
# ledgers — a state D-12 deliberately chose to carry. The repo you must change to
# fix the fleet was blocked by the fleet's state.
#
# Every test below exists to keep the remedy from becoming a mute button.
# --------------------------------------------------------------------------- #

_FUTURE = "2099-01-01"
_PAST = "2020-01-01"


def _with_findings(tmp_path: Path, pairs: list[tuple[str, str]]) -> Path:
    """An artifact carrying real per-finding detail for `pairs` of (check, target)."""
    by_check: dict[str, list[dict[str, str]]] = {}
    for check, target in pairs:
        by_check.setdefault(check, []).append({"target": target, "detail": "…"})
    return _artifact(
        tmp_path,
        findings=len(pairs),
        checks=[{"check": c, "findings": f} for c, f in by_check.items()],
    )


def _accept(tmp_path: Path, **over: str) -> None:
    entry = {
        "check": "conformance-ledgers",
        "target": "mgf-brand",
        "reason": "pre-existing ledger gap, deliberately unseeded",
        "decision": "D-12",
        "review_by": _FUTURE,
    }
    entry.update(over)
    body = "[[accepted]]\n" + "".join(f'{k} = "{v}"\n' for k, v in entry.items())
    (tmp_path / _audit.ACCEPTED_FILENAME).write_text(body, encoding="utf-8")


def test_an_unwaived_finding_still_fails(tmp_path: Path) -> None:
    """The control. If this ever passes, the whole mechanism is a mute button."""
    art = _with_findings(tmp_path, [("conformance-ledgers", "mgf-brand")])
    assert _audit.check_artifact(art) == 1


def test_an_accepted_finding_passes_and_is_PRINTED(tmp_path: Path, capsys) -> None:
    art = _with_findings(tmp_path, [("conformance-ledgers", "mgf-brand")])
    _accept(tmp_path)
    assert _audit.check_artifact(art) == 0
    out = capsys.readouterr().out
    assert "ACCEPTED conformance-ledgers/mgf-brand" in out
    assert "D-12" in out, "the deciding reference must be visible on every run"


def test_a_NEW_finding_is_still_red_beside_an_accepted_one(tmp_path: Path) -> None:
    """The property that matters most: accepting a known debt must not buy silence
    about anything else. This is what separates a waiver from a suppression."""
    art = _with_findings(
        tmp_path,
        [("conformance-ledgers", "mgf-brand"), ("agent-context", "TelePlasma")],
    )
    _accept(tmp_path)
    assert _audit.check_artifact(art) == 1


def _with_details(tmp_path: Path, check: str, rows: list[tuple[str, str]]) -> Path:
    """An artifact whose findings carry DISTINCT details, not the `…` placeholder."""
    return _artifact(
        tmp_path,
        findings=len(rows),
        checks=[
            {
                "check": check,
                "findings": [{"target": t, "detail": d} for t, d in rows],
            }
        ],
    )


def test_a_SECOND_finding_about_the_SAME_repo_is_still_red_when_the_waiver_is_scoped(
    tmp_path: Path,
) -> None:
    """The gap a fresh-context chair found, and the reason `finding` exists.

    `test_a_NEW_finding_is_still_red_beside_an_accepted_one` proves a waiver does
    not cover a DIFFERENT repo. Nothing proved it does not cover a different
    FINDING about the SAME repo — and a `(check, target)` waiver did exactly that.

    Measured consequence on 2026-08-05: `ci-workspace-truth` gained a property
    that found `mgf-media` invoking the AG-03 checker behind `||`, and three repos
    running it from no step at all. Every one of those repos already carried a
    waiver written months earlier for an unrelated defect. **The session's
    headline findings were silenced on the day they were found.**
    """
    art = _with_details(
        tmp_path,
        "ci-workspace-truth",
        [
            ("mgf-media", "pipeline has no `fetch-default-branch` clone step on main"),
            ("mgf-media", "step `commit-trailers` invokes it behind an `||` alternative"),
        ],
    )
    _accept(
        tmp_path,
        check="ci-workspace-truth",
        target="mgf-media",
        finding="no `fetch-default-branch` clone step",
    )
    assert _audit.check_artifact(art) == 1, (
        "a waiver naming ONE finding must not cover a second one about the same repo"
    )


def test_an_UNSCOPED_waiver_still_covers_the_whole_pair(tmp_path: Path) -> None:
    """Backward compatibility, stated rather than assumed.

    Every waiver in the fleet predates the `finding` field. Making it required
    would red the gate on every one of them at once, which is how a correct change
    gets reverted. Omitting it keeps the old, coarse behaviour — and the migration
    is to narrow each entry deliberately, not to break them all in one commit.
    """
    art = _with_details(
        tmp_path,
        "ci-workspace-truth",
        [("mgf-media", "no fetch step"), ("mgf-media", "invoked behind `||`")],
    )
    _accept(tmp_path, check="ci-workspace-truth", target="mgf-media")
    assert _audit.check_artifact(art) == 0


def test_a_SCOPED_waiver_matching_nothing_is_a_failure(tmp_path: Path) -> None:
    """A scoped waiver rots exactly like an unscoped one — the property that stops
    the list becoming archaeology. Caught the fleet four times in one day."""
    art = _with_details(tmp_path, "ci-workspace-truth", [("mgf-media", "no fetch step")])
    _accept(
        tmp_path,
        check="ci-workspace-truth",
        target="mgf-media",
        finding="a phrase that appears in no finding",
    )
    assert _audit.check_artifact(art) == 1


def test_an_EXPIRED_waiver_fails(tmp_path: Path) -> None:
    """A waiver without an end date is a permanent one, so the date is enforced."""
    art = _with_findings(tmp_path, [("conformance-ledgers", "mgf-brand")])
    _accept(tmp_path, review_by=_PAST)
    assert _audit.check_artifact(art) == 1


def test_a_waiver_matching_NOTHING_fails(tmp_path: Path) -> None:
    """Self-cleaning: once the finding is fixed the waiver must be deleted, or the
    list rots into a set of claims nobody has re-read."""
    art = _artifact(tmp_path)  # clean
    _accept(tmp_path)
    assert _audit.check_artifact(art) == 1


def test_a_waiver_missing_any_field_is_refused(tmp_path: Path) -> None:
    art = _with_findings(tmp_path, [("conformance-ledgers", "mgf-brand")])
    (tmp_path / _audit.ACCEPTED_FILENAME).write_text(
        '[[accepted]]\ncheck = "conformance-ledgers"\ntarget = "mgf-brand"\n', encoding="utf-8"
    )
    assert _audit.check_artifact(art) == 1


def test_an_unparseable_waiver_file_does_not_silently_waive_nothing(tmp_path: Path) -> None:
    art = _with_findings(tmp_path, [("conformance-ledgers", "mgf-brand")])
    (tmp_path / _audit.ACCEPTED_FILENAME).write_text("[[accepted]\nbroken", encoding="utf-8")
    assert _audit.check_artifact(art) == 1


def test_a_count_the_detail_cannot_account_for_is_unwaivable(tmp_path: Path) -> None:
    """ "I cannot enumerate the findings" must never read as "there are none".

    An older-format artifact carries a findings COUNT and no per-check detail. The
    waiver layer reasons over detail, so without this the count would be waived by
    an empty enumeration — the exact equivalence this module exists to break.
    """
    assert _audit.check_artifact(_artifact(tmp_path, findings=3)) == 1


def test_no_waiver_file_at_all_is_the_old_strict_behaviour(tmp_path: Path) -> None:
    """Absence of the file must change nothing — a fleet with no waivers is gated
    exactly as before."""
    art = _with_findings(tmp_path, [("agent-context", "TelePlasma")])
    assert not (tmp_path / _audit.ACCEPTED_FILENAME).exists()
    assert _audit.check_artifact(art) == 1
    assert _audit.check_artifact(_artifact(tmp_path)) == 0


def test_a_waiver_cannot_excuse_a_STALE_or_INCOMPLETE_artifact(tmp_path: Path, capsys) -> None:
    """Severity split, stated as a test: artifact INTEGRITY is this repo's own
    responsibility and is never waivable. Only cross-layer FINDINGS are."""
    art = _with_findings(tmp_path, [("conformance-ledgers", "mgf-brand")])
    body = json.loads(art.read_text())
    body["generated"] = "2020-01-01"  # ancient
    body["skipped"] = ["governance"]  # and incomplete
    art.write_text(json.dumps(body), encoding="utf-8")
    _accept(tmp_path)
    assert _audit.check_artifact(art) == 1
    out = capsys.readouterr().out
    assert "STALE" in out and "INCOMPLETE" in out


@requires_git
def test_the_roster_sees_repos_that_are_not_named_mgf_anything(tmp_path: Path) -> None:
    """P2-F2, committed a second time — in the check written to end that class.

    The first roster globbed ``mgf-*``. That cannot see ``figrecovery`` (the repo
    Phase 3's live proof was built on, and the whole reason the fetch step is
    branch-agnostic), ``TelePlasma`` (named in PLAN §3.4), ``vm-vmanager-app``,
    ``magogi-proof`` or ``first-light`` — five real pipelines, and the two most
    likely to fail, because a ``master`` default is exactly what a ``main``-shaped
    roster overlooks.

    The check's own docstring argued about master-default repos while its roster
    excluded every one of them. This fixture is that repo: not mgf-prefixed, on
    master, and broken.
    """
    root = _ci_fed(tmp_path, {}, branch="master")
    _member_on(
        root,
        "figrecovery",
        {".woodpecker.yml": _PIPELINE_NO_FETCH, "scripts/check_commit_trailers.py": _CANON_SCRIPT},
        "master",
    )
    details = _ci_findings(root)
    assert any(
        "figrecovery" == f.target
        for f in _audit.check_ci_workspace_truth(root, offline=True).findings
    ), "a non-mgf-prefixed repo must be in the roster"
    assert any("no `fetch-default-branch` clone step" in d for d in details)


@requires_git
def test_an_exempt_non_mgf_repo_is_still_skipped(tmp_path: Path) -> None:
    """Widening the roster must not mean auditing the court workspace or the
    founder's store — but each exclusion stays explicit and reasoned."""
    root = _ci_fed(tmp_path, {}, branch="main")
    _member_on(root, "mgf", {"README.md": "# court workspace\n"}, "main")
    assert all(
        f.target != "mgf" for f in _audit.check_ci_workspace_truth(root, offline=True).findings
    )


def _with_raw_findings(tmp_path: Path, pairs: list[tuple[str, str]]) -> Path:
    """Artifact whose headline count is the REAL number of findings, allowing
    repeats of the same (check, target) — which is what the fleet produces."""
    by_check: dict[str, list[dict[str, str]]] = {}
    for check, target in pairs:
        by_check.setdefault(check, []).append({"target": target, "detail": "…"})
    return _artifact(
        tmp_path,
        findings=len(pairs),
        checks=[{"check": c, "findings": f} for c, f in by_check.items()],
    )


def test_two_findings_about_ONE_repo_are_two_findings_not_one(tmp_path: Path, capsys) -> None:
    """Shipped broken in 0.22.0 and it misfired on the first real artifact.

    A ``(check, target)`` pair is a WAIVER KEY, not a finding identity:
    `ci-workspace-truth` says both "no fetch step" AND "fail-open checker" about
    the same repo. Counting distinct PAIRS undercounted, so a genuine artifact
    with 53 findings across 38 pairs tripped the count reconciliation with
    "53 reported but 38 enumerable" — fail-closed, but simply wrong, and it
    blocked mgf-standard for a bogus reason on the release meant to unblock it.
    """
    art = _with_raw_findings(
        tmp_path,
        [("ci-workspace-truth", "mgf-website"), ("ci-workspace-truth", "mgf-website")],
    )
    assert _audit.check_artifact(art) == 1
    out = capsys.readouterr().out
    assert "does not account for its own count" not in out, (
        "the reconciliation must NOT fire when one check reports two findings for one repo"
    )
    assert "2 unresolved cross-layer finding(s) across 1 target(s)" in out


def test_a_waiver_covers_every_finding_its_pair_names(tmp_path: Path, capsys) -> None:
    """Documented consequence of the pair being a waiver key: waiving a repo for a
    check waives all of that check's findings about it."""
    art = _with_raw_findings(
        tmp_path,
        [("conformance-ledgers", "mgf-brand"), ("conformance-ledgers", "mgf-brand")],
    )
    _accept(tmp_path)
    assert _audit.check_artifact(art) == 0
    assert "does not account for its own count" not in capsys.readouterr().out


# --------------------------------------------------------------------------- #
# Woodpecker's OTHER layout — found by a court chair, not by CI.
# --------------------------------------------------------------------------- #


@requires_git
def test_the_woodpecker_directory_layout_is_read(tmp_path: Path) -> None:
    """`hrb-app` keeps its workflows in `.woodpecker/` — `ci.yml` and `e2e.yml`,
    both live on the server. The checks read only the ROOT `.woodpecker.yml`, so
    the gate announced *"no .woodpecker.yml on main — no CI gate at all. Add one,
    or declare the repo in CI_WORKSPACE_TRUTH_EXEMPT with a reason"* about a repo
    with two running pipelines.

    **The remedy it proposed would have permanently exempted a repo whose CI is
    live and unguarded** — a false negative that recruits you into making it
    permanent. It also made the repo a D-18 "adopt CI" target when what it
    actually lacks is the fetch step.

    Reading both layouts immediately surfaced a second finding that had been
    unreachable: its trailers checker is a legacy FAIL-OPEN copy.
    """
    files = {
        "scripts/check_commit_trailers.py": _CANON_SCRIPT,
        ".woodpecker/ci.yml": _CLONE_HEAD + _FETCH_STEP_YAML + _STEPS_TAIL,
        ".woodpecker/e2e.yml": _CLONE_HEAD + _FETCH_STEP_YAML + _STEPS_TAIL,
    }
    root = _ci_fed(tmp_path, {"mgf-thing": files})
    details = _ci_findings(root)
    assert not any("no CI gate at all" in d for d in details), details
    assert not any("mgf-thing" in d and "no `fetch-default-branch`" in d for d in details), details


@requires_git
def test_a_directory_layout_workflow_missing_the_fetch_step_is_still_a_finding(
    tmp_path: Path,
) -> None:
    """Reading the directory form must not become a way to PASS. A `.woodpecker/`
    repo whose workflows never fetch is exactly as starved as a root-file one."""
    files = {
        "scripts/check_commit_trailers.py": _CANON_SCRIPT,
        ".woodpecker/ci.yml": _CLONE_HEAD + _STEPS_TAIL,
    }
    root = _ci_fed(tmp_path, {"mgf-thing": files})
    details = _ci_findings(root)
    assert any("no `fetch-default-branch`" in d for d in details), details


@requires_git
def test_secret_guards_read_the_directory_layout_too(tmp_path: Path) -> None:
    """A secret consumed in `.woodpecker/ci.yml` is exactly as unguarded as one in
    a root file — SC-23 does not care which filename it lives under."""
    files = _conformant() | {".woodpecker/ci.yml": _CLONE_HEAD + _FETCH_STEP_YAML + _STEP_UNGUARDED}
    del files[".woodpecker.yml"]
    root = _ci_fed(tmp_path, {"mgf-thing": files})
    assert any("deploy_token" in d for d in _guard_findings(root)), _guard_findings(root)


# --------------------------------------------------------------------------- #
# lessons-mechanized — ONE-ESTATE Phase 4's ledger, and the exit code under it
# --------------------------------------------------------------------------- #


def _lessons_ledger(tmp_path: Path, **over: str) -> Path:
    """A federation root with a one-entry LESSONS.toml and a real mechanism."""
    std = tmp_path / "mgf-standard"
    (std / "templates").mkdir(parents=True, exist_ok=True)
    (std / "templates" / "base.yml").write_text("clone:\n", encoding="utf-8")
    entry = {
        "id": "L-01",
        "statement": "a detector pointed at the wrong reality reports green",
        "incident": "measured 2026-08-02: 39 of 40 repos printed 'all 0 commit(s)'",
        "mechanism": "mgf-standard/templates/base.yml",
        "gate": "audit:fleet",
    }
    entry.update(over)
    body = "schema = 1\n\n[[lesson]]\n" + "".join(f'{k} = """{v}"""\n' for k, v in entry.items())
    (std / "LESSONS.toml").write_text(body, encoding="utf-8")
    return tmp_path


def _lesson_findings(root: Path) -> list[str]:
    return [f.detail for f in _audit.check_lessons_mechanized(root).findings]


def test_a_complete_lesson_passes(tmp_path: Path) -> None:
    """The positive control. Without it every red below could be red for free."""
    res = _audit.check_lessons_mechanized(_lessons_ledger(tmp_path))
    assert res.findings == []
    assert res.examined == 1


def test_a_PROSE_gate_is_a_finding(tmp_path: Path) -> None:
    """The property the whole ledger rests on.

    "Documented in X" and "we always do Y" are not gates. Allowing them would make
    the ledger a museum with a review date — which is the exact shape of the five
    hand-written verifiers referenced by zero pipelines that this file exists to
    end.
    """
    root = _lessons_ledger(tmp_path, gate="documented in AGENT_PRACTICES.md §8")
    assert any("UNMECHANIZED lesson wearing the word 'gate'" in d for d in _lesson_findings(root))


def test_a_gate_naming_an_UNREGISTERED_check_is_a_finding(tmp_path: Path) -> None:
    root = _lessons_ledger(tmp_path, gate="audit:no-such-check")
    assert any("not registered" in d for d in _lesson_findings(root))


def test_a_gate_naming_a_MISSING_TEST_FILE_is_a_finding(tmp_path: Path) -> None:
    root = _lessons_ledger(tmp_path, gate="test:mgf-fed/tests/unit/nope.py")
    assert any("does not exist" in d for d in _lesson_findings(root))


def test_a_MISSING_MECHANISM_is_a_finding(tmp_path: Path) -> None:
    root = _lessons_ledger(tmp_path, mechanism="mgf-standard/templates/gone.yml")
    assert any("points at nothing" in d for d in _lesson_findings(root))


def test_a_lesson_missing_its_INCIDENT_is_a_finding(tmp_path: Path) -> None:
    """A lesson with no incident is an opinion. Every entry here was paid for."""
    root = _lessons_ledger(tmp_path, incident="")
    assert any("missing `incident`" in d for d in _lesson_findings(root))


def test_an_EMPTY_ledger_is_a_finding_not_a_pass(tmp_path: Path) -> None:
    """ "No lessons" and "no findings" must not share an outcome — the corollary
    this estate enforces everywhere else, applied to its own ledger."""
    std = tmp_path / "mgf-standard"
    std.mkdir(parents=True)
    (std / "LESSONS.toml").write_text("schema = 1\n", encoding="utf-8")
    assert any("empty ledger is not a clean one" in d for d in _lesson_findings(tmp_path))


def test_a_MISSING_ledger_SKIPS_rather_than_passing(tmp_path: Path) -> None:
    res = _audit.check_lessons_mechanized(tmp_path)
    assert res.skipped, "an absent ledger must be a named skip, never a silent pass"


def test_lessons_mechanized_is_registered_and_names_its_layers() -> None:
    """A check absent from CHECKS gates nothing — Phase 2's P2-F1, exactly."""
    assert "lessons-mechanized" in CHECKS
    fn, needs_network, layers = CHECKS["lessons-mechanized"]
    assert fn is _audit.check_lessons_mechanized
    assert needs_network is False
    assert layers == (DECLARED, COMMITTED)


def test_registry_consistency_is_registered_and_names_its_layers() -> None:
    """Carried finding 2 closed: `mgf-fed registry --check` ran in NO pipeline.

    It could not: it shells to mgf-standard's generator, which needs every member
    repo on disk, so no single-repo CI clone could ever run it. As an audit check
    it rides `--write` where the fleet is, lands in the committed artifact, and is
    gated in CI by `--check-artifact` — the expensive/cheap split this audit
    already implements — and it inherits AUDIT_ACCEPTED.toml, so a blocked pin
    becomes an explicit, scoped, expiring waiver instead of a red nobody can clear.
    """
    assert "registry-consistency" in CHECKS
    fn, needs_network, layers = CHECKS["registry-consistency"]
    assert fn is _audit.check_registry_consistency
    assert needs_network is False
    assert layers == (DECLARED, COMMITTED)


@requires_git
def test_registry_consistency_SKIPS_when_the_generator_is_absent(tmp_path: Path) -> None:
    """Exit 2 from the generator is "I could not run", and a skip is not a pass.

    A federation root with no mgf-standard cannot answer the question, and the
    check must say so rather than report a consistent registry.
    """
    res = _audit.check_registry_consistency(tmp_path)
    assert res.skipped, "an unrunnable registry gate must be a NAMED skip"
    assert res.findings == []


# ---------------------------------------------------------------------------
# PAPER-144 — the audit family was worktree-blind.
#
# `.git` is a DIRECTORY in a normal clone and a FILE (`gitdir: …`) in a LINKED
# WORKTREE. Eight sites in this module (and one in `_court`) tested `.is_dir()`,
# so a member checked out as a worktree was dropped from the roster with NO
# finding — the audit reported "nothing to look at" for a repo it could not look
# at. `_roster` had already been repaired and its comment named this very paper;
# `_audit` and `_court` were never carried across.
#
# These tests plant a REAL linked worktree (`git worktree add`), not a fake
# marker, because a fake marker is the workaround the defect taught people to
# write — `_origin_view._mirror_repo` still plants an empty `.git` DIRECTORY
# precisely to satisfy the predicate this fixes.
# ---------------------------------------------------------------------------


def _linked_worktree(root: Path, origin_name: str, wt_name: str) -> Path:
    """A real `git worktree add` checkout at `root/<wt_name>`, sharing origin's history."""
    src = root / origin_name
    src.mkdir(parents=True)
    _git_ok(src, "init", "-b", "main")
    _git_ok(src, "config", "user.email", "t@example.invalid")
    _git_ok(src, "config", "user.name", "T")
    (src / "README.md").write_text("seed\n", encoding="utf-8")
    _git_ok(src, "add", "-A")
    _git_ok(src, "-c", "commit.gpgsign=false", "commit", "-m", "seed")
    wt = root / wt_name
    _git_ok(src, "worktree", "add", "-b", "wt", str(wt))
    return wt


@requires_git
def test_linked_worktree_is_a_git_checkout_not_a_directory(tmp_path: Path) -> None:
    """The premise, pinned: git calls it a work tree while `.git` is a FILE."""
    wt = _linked_worktree(tmp_path, "mgf-origin", "mgf-worktree")
    assert (wt / ".git").exists()
    assert not (wt / ".git").is_dir(), "premise broken: .git should be a FILE here"
    inside = subprocess.run(
        ["git", "rev-parse", "--is-inside-work-tree"],  # noqa: S607
        cwd=wt,
        capture_output=True,
        text=True,
        check=True,
    )
    assert inside.stdout.strip() == "true"


@requires_git
def test_repos_on_disk_sees_a_linked_worktree(tmp_path: Path) -> None:
    """The roster must not silently drop a worktree — that is a fail-open."""
    wt = _linked_worktree(tmp_path, "mgf-origin", "mgf-worktree")
    found = _audit._repos_on_disk(tmp_path)
    assert wt in found, f"linked worktree dropped from the audit roster: {found}"


@requires_git
def test_court_repos_sees_a_linked_worktree(tmp_path: Path) -> None:
    """`_court._repos` shared the defect and is fixed by the same predicate."""
    from mgf.fed import _court

    wt = _linked_worktree(tmp_path, "mgf-origin", "mgf-worktree")
    found = _court._repos(tmp_path)
    assert wt in found, f"linked worktree dropped from the court roster: {found}"


# ---------------------------------------------------------------------------
# Board item 76 — the PAPER-144 sabotage proof covered 2 of 11 sites.
#
# A fresh-context chair reverted all eleven individually and found NINE stay
# green, byte-identical — including `cli/_ledger_seed.py`, which is the paper's
# own reported symptom. Reproduced here before writing this: reverting both
# ledger-seed sites leaves 1641 passed.
#
# Nine bespoke tests would be the wrong answer. The defect is a CLASS — "this
# module decides whether a path is a repo, and it asks the wrong question" — so
# the enforcement is one test over the whole package. It bites on every one of
# the eleven sites and on any site added later, which is what the original fix
# should have shipped with.
#
# The single allowed exception is listed explicitly, with its reason, so that
# deferring it stays a REVIEWED decision rather than an invisible one.
# ---------------------------------------------------------------------------

#: `.git` predicates that may still use `is_dir()`, and why. Anything not here
#: is a PAPER-144 regression.
_ISDIR_ALLOWED = {
    # AG-19's runtime memory store. It reads `mem/.git/config` directly twenty
    # lines later, and for a linked worktree that path resolves only via
    # `commondir` — so flipping this predicate alone changes which WRONG answer
    # it gives, not whether it is right (board item 79). Needs real gitdir
    # resolution; deferred deliberately, and the store is a plain clone by
    # construction so nothing is failing today.
    "src/mgf/fed/cli/_doctor_standards.py",
}


def _dot_git_isdir_sites(path: Path) -> list[int]:
    """Line numbers where this module CALLS `.is_dir()` on a `… / ".git"` path.

    Parsed with `ast`, not grepped: the first version of this check was a regex
    and it flagged two COMMENTS that describe the defect — including the
    `is_git_repo` docstring written to explain it. A checker that cannot tell
    code from prose about code will be silenced by whoever hits that, which is
    how a gate becomes decoration.
    """
    tree = ast.parse(path.read_text(encoding="utf-8"))
    out: list[int] = []
    for node in ast.walk(tree):
        if not isinstance(node, ast.Call):
            continue
        fn = node.func
        if not (isinstance(fn, ast.Attribute) and fn.attr == "is_dir"):
            continue
        recv = fn.value
        if (
            isinstance(recv, ast.BinOp)
            and isinstance(recv.op, ast.Div)
            and isinstance(recv.right, ast.Constant)
            and recv.right.value == ".git"
        ):
            out.append(node.lineno)
    return out


def test_no_module_asks_is_dir_of_dot_git() -> None:
    """PAPER-144 as a class, not as eleven sites.

    `.git` is a FILE in a linked worktree, so `(p / ".git").is_dir()` answers
    "not a repo" about a real checkout. Every roster predicate must ask whether
    `.git` EXISTS.
    """
    src = Path(__file__).resolve().parents[2] / "src"
    offenders: list[str] = []
    for path in sorted(src.rglob("*.py")):
        rel = path.relative_to(src.parent).as_posix()
        if rel in _ISDIR_ALLOWED:
            continue
        offenders += [f"{rel}:{n}" for n in _dot_git_isdir_sites(path)]
    assert not offenders, (
        "these ask whether `.git` is a DIRECTORY, which is false for every linked "
        f"worktree (PAPER-144): {offenders}. Use `mgf.fed._roster.is_git_repo`, or "
        "add the path to _ISDIR_ALLOWED with the reason it cannot."
    )


def test_the_isdir_allowlist_is_not_stale() -> None:
    """An allowlist entry that no longer offends is a claim nobody re-checked."""
    src = Path(__file__).resolve().parents[2] / "src"
    for rel in sorted(_ISDIR_ALLOWED):
        path = src.parent / rel
        assert path.is_file(), f"_ISDIR_ALLOWED names {rel}, which does not exist"
        assert _dot_git_isdir_sites(path), (
            f"{rel} is exempted from the PAPER-144 check but no longer calls "
            "`.is_dir()` on a `.git` path — remove it from _ISDIR_ALLOWED."
        )


# ---------------------------------------------------------------------------
# Board item 72 — a pipeline whose `when.branch` matches NO branch on its own
# origin never fires, and the board shows no run and no red.
#
# `ci-workspace-truth` reads the CLONE section; nothing read the TRIGGER. The
# case that made it real: `figrecovery` is the one member whose default branch is
# `master`, so one word changed to `main` — the fleet's habit, and what a careless
# re-render writes — silently retires its entire pipeline.
#
# THE PREDICATE IS "NONE MATCH", NOT "ANY MISSING", and that is the whole check.
# Seven repos across the estate deliberately declare `[main, master]` so one
# pipeline serves both conventions. Measured before writing this: "any missing"
# flags all seven and finds nothing real; "none match" flags none of them and
# still catches the figrecovery case.
# ---------------------------------------------------------------------------


def _repo_with_pipeline(root: Path, name: str, *, origin_branch: str, when_branch: object) -> Path:
    """A member whose origin has exactly `origin_branch`, declaring `when_branch`."""
    bare = root / ".remotes" / f"{name}.git"
    bare.parent.mkdir(parents=True, exist_ok=True)
    _git_ok(root, "init", "--bare", "-b", origin_branch, str(bare))
    repo = root / name
    repo.mkdir(parents=True)
    _git_ok(repo, "init", "-b", origin_branch)
    _git_ok(repo, "config", "user.email", "t@example.invalid")
    _git_ok(repo, "config", "user.name", "T")
    rendered = json.dumps(when_branch) if isinstance(when_branch, list) else f'"{when_branch}"'
    (repo / ".woodpecker.yml").write_text(
        f"when:\n  - event: push\n    branch: {rendered}\n\nsteps:\n"
        f"  - name: lint\n    image: python:3.13-slim\n    commands:\n      - true\n",
        encoding="utf-8",
    )
    _git_ok(repo, "add", "-A")
    _git_ok(repo, "-c", "commit.gpgsign=false", "commit", "-m", "seed")
    _git_ok(repo, "remote", "add", "origin", f"file://{bare}")
    _git_ok(repo, "push", "-u", "origin", origin_branch)
    return repo


@requires_git
def test_branch_reachable_flags_a_pipeline_that_can_never_fire(tmp_path: Path) -> None:
    """The figrecovery case: a `master` repo whose pipeline runs only on `main`."""
    _repo_with_pipeline(tmp_path, "mgf-onlymain", origin_branch="master", when_branch="main")
    res = _audit.check_ci_branch_reachable(tmp_path)
    assert res.examined >= 1, "examined nothing — a pass here would be vacuous"
    assert not res.skipped
    details = " ".join(f.detail for f in res.findings)
    assert res.findings, f"a never-firing pipeline was not flagged (examined {res.examined})"
    assert "never fire" in details


@requires_git
def test_branch_reachable_tolerates_the_belt_and_braces_pattern(tmp_path: Path) -> None:
    """`[main, master]` on a master-only origin is CORRECT config, not a finding.

    This is the false-positive guard. Seven real repos use this shape; a check
    that reddens them is noise, and a noisy gate is how real findings get ignored.
    """
    _repo_with_pipeline(
        tmp_path, "mgf-bothnames", origin_branch="master", when_branch=["main", "master"]
    )
    res = _audit.check_ci_branch_reachable(tmp_path)
    assert res.examined >= 1
    assert not res.findings, (
        f"belt-and-braces config was flagged: {[f.detail for f in res.findings]}"
    )


@requires_git
def test_branch_reachable_matching_branch_is_clean(tmp_path: Path) -> None:
    """The ordinary case stays quiet."""
    _repo_with_pipeline(tmp_path, "mgf-fine", origin_branch="main", when_branch="main")
    res = _audit.check_ci_branch_reachable(tmp_path)
    assert res.examined >= 1
    assert not res.findings


def test_branch_reachable_offline_is_skipped_not_passed(tmp_path: Path) -> None:
    """Offline it cannot tell "no such branch" from "I have not looked"."""
    res = _audit.check_ci_branch_reachable(tmp_path, offline=True)
    assert res.skipped, "offline produced a silent pass — that is the whole defect class"
    assert not res.ok, "a skip must not read as ok"


# ---------------------------------------------------------------------------
# Board item 75 — the artifact's freshness bound was a CLOCK, and a clock cannot
# tell "old" from "about a different fleet".
#
# Measured 2026-08-14: the committed AUDIT.json was stamped 2026-08-08 — six days
# old, comfortably inside MAX_AUDIT_AGE_DAYS = 14 — and described the registry as
# it was BEFORE figrecovery was enrolled as an app-tier member. It passed. The
# audit was not stale; it was answering a question nobody had asked.
#
# A member's `kind` decides which gates apply to it, so a two-line registry edit
# changes what "clean" MEANS for that repo — and that is board item 73's hand-edit
# walking past CI. The artifact now records a hash of its inputs, and the gate
# refuses when they have moved.
# ---------------------------------------------------------------------------


def _artifact_with_registry(tmp_path: Path, registry: str, **over) -> Path:
    """An artifact next to a real MGF_LIBRARIES.json, fingerprinted correctly."""
    (tmp_path / "MGF_LIBRARIES.json").write_text(registry, encoding="utf-8")
    art = tmp_path / "AUDIT.json"
    body = {
        "generated": _audit._today(),
        "inputs": _audit._input_digests(art),
        "checks": [],
        "findings": 0,
        "skipped": [],
        "ran": list(CHECKS),
    }
    body.update(over)
    art.write_text(json.dumps(body), encoding="utf-8")
    return art


def test_an_artifact_whose_registry_moved_fails(tmp_path: Path, capsys) -> None:
    """The board-75 case: young, complete, clean — and about a different fleet."""
    art = _artifact_with_registry(tmp_path, '{"libraries": [{"repo": "a", "kind": "library"}]}')
    assert _audit.check_artifact(art) == 0, "precondition: it passes while inputs match"

    # the two-line hand-edit of board item 73
    (tmp_path / "MGF_LIBRARIES.json").write_text(
        '{"libraries": [{"repo": "a", "kind": "app"}]}', encoding="utf-8"
    )
    assert _audit.check_artifact(art) == 1, (
        "a registry edit left the artifact passing — the clock said 0 days old and "
        "nothing asked whether it still described this fleet"
    )
    out = capsys.readouterr().out
    assert "INPUTS CHANGED" in out
    assert "MGF_LIBRARIES.json" in out


def test_an_artifact_with_no_input_fingerprint_fails(tmp_path: Path, capsys) -> None:
    """Pre-fingerprint artifacts cannot answer the question, so they are not trusted."""
    art = _artifact(tmp_path)
    body = json.loads(art.read_text(encoding="utf-8"))
    del body["inputs"]
    art.write_text(json.dumps(body), encoding="utf-8")
    assert _audit.check_artifact(art) == 1
    assert "NO INPUT FINGERPRINT" in capsys.readouterr().out


def test_a_missing_registry_is_recorded_not_skipped(tmp_path: Path) -> None:
    """ "The registry was absent" and "the registry was unchanged" must not render alike."""
    digests = _audit._input_digests(tmp_path / "AUDIT.json")
    assert digests["MGF_LIBRARIES.json"] == "absent"
    (tmp_path / "MGF_LIBRARIES.json").write_text("{}", encoding="utf-8")
    assert _audit._input_digests(tmp_path / "AUDIT.json")["MGF_LIBRARIES.json"] != "absent"


def test_write_artifact_records_the_fingerprint(tmp_path: Path) -> None:
    """`--write` must record what it was computed from, or the gate has nothing to ask."""
    (tmp_path / "MGF_LIBRARIES.json").write_text('{"libraries": []}', encoding="utf-8")
    art = tmp_path / "AUDIT.json"
    _audit.write_artifact([], art, when=_audit._today())
    data = json.loads(art.read_text(encoding="utf-8"))
    assert "inputs" in data, "write_artifact dropped the input fingerprint"
    assert data["inputs"]["MGF_LIBRARIES.json"] != "absent"


# ---------------------------------------------------------------------------
# The bench must hold still while it is being measured.
#
# `audit --write` takes tens of minutes and reads working trees. On 2026-08-14 I
# ran one and committed to `mgf-fed` seven minutes before it finished — and the
# artifact came back asserting `MGF_LIBRARIES.md is STALE` about a registry that
# `gen_mgf_libraries.py --check` called CURRENT, exit 0, one minute later.
#
# The artifact was not stale and it was not wrong about an old fleet. It described
# a state that existed at NO SINGLE MOMENT, which is a third thing, and nothing in
# the tool could say so. Session 12's lesson exactly — "the bench went stale
# underneath me and I described the state I had arranged rather than the state
# that existed" — except the arranging was concurrent with the measuring.
#
# The run now records which repos moved, and the gate refuses an artifact that
# names any.
# ---------------------------------------------------------------------------


@requires_git
def test_bench_heads_sees_every_checkout(tmp_path: Path) -> None:
    """Every checkout is watched, linked worktrees included (PAPER-144).

    This test used to assert `heads["mgf-worktree"] == rev-parse HEAD`, which
    encoded the defect: the guard was watching the working checkout while every
    check reads `refs/remotes/*`. The roster property it was really protecting —
    a linked worktree is a checkout too — is unchanged and asserted below. These
    fixtures have no `origin`, so both resolve to the honest "unresolvable"
    rather than to a HEAD that no check would ever read.
    """
    _linked_worktree(tmp_path, "mgf-origin", "mgf-worktree")
    heads = _audit.bench_heads(tmp_path)
    assert "mgf-origin" in heads
    assert "mgf-worktree" in heads, "a linked worktree is a checkout too (PAPER-144)"
    assert heads["mgf-worktree"] == "unresolvable", (
        "a fixture with no origin has no ref the checks would read; recording a "
        "HEAD here is what made the guard watch the wrong thing"
    )


def test_an_artifact_whose_bench_moved_is_refused(tmp_path: Path, capsys) -> None:
    """The 2026-08-14 case: a result describing a moment that never existed."""
    art = _artifact(tmp_path, bench_moved=["mgf-fed"])
    assert _audit.check_artifact(art) == 1
    out = capsys.readouterr().out
    assert "BENCH MOVED DURING THE RUN" in out
    assert "mgf-fed" in out
    assert "never existed" in out


def test_a_quiet_bench_passes(tmp_path: Path) -> None:
    """An empty `bench_moved` is the normal case and must stay silent."""
    assert _audit.check_artifact(_artifact(tmp_path, bench_moved=[])) == 0


def test_write_artifact_records_the_moved_list(tmp_path: Path) -> None:
    art = tmp_path / "AUDIT.json"
    _audit.write_artifact([], art, when=_audit._today(), moved=["mgf-fed", "mgf-standard"])
    data = json.loads(art.read_text(encoding="utf-8"))
    assert data["bench_moved"] == ["mgf-fed", "mgf-standard"]


# ---------------------------------------------------------------------------
# The quiescence guard must watch the ref the CHECKS read, not `HEAD`.
#
# 0.25.6 shipped `bench_heads` running `rev-parse HEAD` — the working checkout —
# while every check reads `refs/remotes/*`. A fresh-context chair measured both
# halves of the mismatch, and both were live on this bench:
#
#   * a commit on a SIDE BRANCH moves HEAD while `ls-tree origin/main` is
#     byte-identical  -> the guard REFUSED a run over a change no check saw
#   * a fetch advancing origin/main changes what every check reads while HEAD
#     sits still      -> the guard SAID NOTHING about the movement that matters
#
# A guard pointed at the wrong ref is loud where it should be quiet and quiet
# where it should be loud. These tests pin both directions.
# ---------------------------------------------------------------------------


@requires_git
def test_bench_heads_ignores_a_side_branch_commit(tmp_path: Path) -> None:
    """HEAD moving on a side branch must NOT read as bench movement.

    The checks read `origin/main`; if that is byte-identical, nothing they saw
    changed, and refusing the artifact would be a false alarm.
    """
    repo = _member(tmp_path, "mgf-side", {"README.md": "seed\n"})
    before = _audit.bench_heads(tmp_path)

    _git_ok(repo, "checkout", "-q", "-b", "sidework")
    (repo / "scratch.txt").write_text("local work\n", encoding="utf-8")
    _git_ok(repo, "add", "-A")
    _git_ok(repo, "-c", "commit.gpgsign=false", "commit", "-m", "side-branch work")

    after = _audit.bench_heads(tmp_path)
    assert _git_ok_out(repo, "rev-parse", "HEAD") != before.get("mgf-side"), (
        "premise: HEAD must actually have moved"
    )
    assert after["mgf-side"] == before["mgf-side"], (
        "a side-branch commit was reported as bench movement — the guard is "
        "watching HEAD, not the ref the checks read"
    )


@requires_git
def test_bench_heads_sees_the_origin_ref_advancing(tmp_path: Path) -> None:
    """The movement that DOES matter: origin/<default> advancing under the run."""
    repo = _member(tmp_path, "mgf-adv", {"README.md": "seed\n"})
    before = _audit.bench_heads(tmp_path)

    # A second clone pushes, then the audited checkout fetches — exactly what a
    # peer session merging mid-run looks like from here.
    other = tmp_path / "other"
    _git_ok(tmp_path, "clone", "-q", f"file://{tmp_path / '.remotes' / 'mgf-adv.git'}", str(other))
    _git_ok(other, "config", "user.email", "t@example.invalid")
    _git_ok(other, "config", "user.name", "T")
    (other / "new.txt").write_text("landed elsewhere\n", encoding="utf-8")
    _git_ok(other, "add", "-A")
    _git_ok(other, "-c", "commit.gpgsign=false", "commit", "-m", "a peer merged")
    _git_ok(other, "push", "-q", "origin", "HEAD:main")
    _git_ok(repo, "fetch", "-q", "origin")

    after = _audit.bench_heads(tmp_path)
    assert after["mgf-adv"] != before["mgf-adv"], (
        "origin/main advanced — changing what every check reads — and the guard did not notice"
    )


@requires_git
def test_an_unresolvable_repo_is_recorded_not_skipped(tmp_path: Path) -> None:
    """ "I could not read this" must not render as "this did not move"."""
    d = tmp_path / "mgf-noorigin"
    d.mkdir()
    _git_ok(d, "init", "-b", "main")
    _git_ok(d, "config", "user.email", "t@example.invalid")
    _git_ok(d, "config", "user.name", "T")
    (d / "f.txt").write_text("x\n", encoding="utf-8")
    _git_ok(d, "add", "-A")
    _git_ok(d, "-c", "commit.gpgsign=false", "commit", "-m", "seed")
    heads = _audit.bench_heads(tmp_path)
    assert heads.get("mgf-noorigin") == "unresolvable"
