#!/usr/bin/env bash
# Quorum gate: a branch may not be pushed until its claims have survived refutation.
#
# WHY THIS EXISTS
#
# The methodology was already written down in two places and enforced in
# neither: `docs/specifications/provable-iac.md` requires "quorum-validated
# design (>=3 world-class systems)" per phase, and the fleet's own notes record
# that on the 1.21.0 sweep EVERY high-value finding came from an agent told to
# refute another agent's claim -- not from the implementations. Two regressions
# were caught that a fully green suite had passed.
#
# A methodology that lives only in a spec is a suggestion. `main` here is NOT
# branch-protected (`gh api .../branches/main/protection` -> 404) and CODEOWNERS
# routes everything to one person, so nothing downstream of the push will catch
# an unrefuted claim either. The push is the last chokepoint before a PR exists,
# so the gate goes here.
#
# WHAT THIS GATE DOES AND DOES NOT PROVE
#
# It does NOT re-run the quorum -- that costs millions of tokens and minutes of
# wall clock, and re-running it here would only re-derive what the receipt
# already records. It verifies the RECEIPT: that a quorum ran against THIS EXACT
# DIFF, that it met its thresholds, and that the falsification it claims is real
# enough to check.
#
# The split is deliberate and worth stating plainly, because a gate that
# overclaims is worse than no gate:
#
#   VERIFIED here  - the receipt exists and parses
#                  - it is bound to this diff (hash), so it cannot be recycled
#                  - lane/refuter/judge counts meet the declared floors
#                  - the falsification test EXISTS in the tree
#                  - or, for a receipt that declares `kind: triage` (forjar#491),
#                    that the diff touches nothing outside the triage rail and the
#                    receipt SAYS no test was reverted -- printed, never implied
#                  - that test PASSES right now, with the fix in place
#   ATTESTED only  - that the same test went RED when the fix was reverted
#
# The red half is attested because verifying it means reverting production code
# and rebuilding, which a pre-push hook must not do to a developer's tree. The
# green half is checked, and a receipt naming a test that does not exist or does
# not pass is rejected -- which kills the cheapest way to fake a falsification.
#
# UNMEASURED IS A FAILURE, NOT A PASS. `scripts/cb200-ratchet.sh` learned this
# the hard way: CB-200 reported Skip ("no .pmat/context.db") and that green was
# the index's absence rather than the tree's quality. Every early exit below is
# therefore a non-zero exit with a reason, never a silent 0.
set -euo pipefail

RECEIPT_DIR=".quorum"

# THE BASE IS NOT OVERRIDABLE BY THE ENVIRONMENT.
#
# It was, via $QUORUM_BASE, and that was a one-word bypass of the entire gate:
# `QUORUM_BASE=HEAD git push` makes `git diff HEAD...` mathematically empty, the
# empty-diff branch below then reports "nothing to refute" and exits 0. Verified
# against the first draft of this script. An escape hatch on the one input that
# defines "what is being reviewed" is not a convenience, it is the hole.
#
# A branch based on something other than main is handled by merge-base below,
# which is what the override was actually for.
BASE_REF="origin/main"

# Floors. Deliberately low: this gate enforces that a quorum HAPPENED, not that
# it was large. A 3-lane/3-refuter quorum that actually killed a claim beats a
# 9-lane one that rubber-stamped. Raise them by editing here, in the open.
MIN_LANES=3
MIN_REFUTERS=3
MIN_JUDGES=3

# ENFORCED OR ADVISORY -- resolved before any check runs.
#
# The methodology is the maintainers' practice, not a tax on anyone who sends a
# patch. A contributor without a 7-lane agent stack -- or who is simply out of
# model credits -- must not be blocked for that. They still see every finding;
# the gate just does not refuse their push.
#
# NOT a bare env var for the enforced set. `QUORUM_SKIP=1` available to exactly
# the people it is meant to bind would leave no record and be invisible in
# review; within a month it is how everyone pushes. Identity scoping means an
# enforced author opts out only by editing a TRACKED file (visible in the PR
# diff), by a committed waiver, or by --no-verify (local, and in the reflog).
ENFORCE_CFG="$RECEIPT_DIR/enforce.json"
actor="${QUORUM_ACTOR:-$(git config user.email 2>/dev/null || true)}"
MODE="advisory"
if [ -f "$ENFORCE_CFG" ] && python3 - "$ENFORCE_CFG" "$actor" <<'ENFORCE_PY'
import json, sys
cfg = json.load(open(sys.argv[1]))
a = sys.argv[2].strip().lower()
names = {a, a.split("@")[0]} if a else set()
hits = {str(x).strip().lower() for x in cfg.get("enforced_for", [])}
sys.exit(0 if (names & hits) else 1)
ENFORCE_PY
then
    MODE="blocking"
fi

# In advisory mode a failure is REPORTED IN FULL and then forgiven. Silence would
# be worse than no gate: the contributor learns nothing and the maintainer gets a
# PR that merely looks checked.
die() {
    echo "✗ QUORUM GATE: $*" >&2
    if [ "$MODE" = "advisory" ]; then
        echo "" >&2
        echo "  ADVISORY for '${actor:-unknown}' -- NOT blocking your push." >&2
        echo "  The quorum is the maintainers' practice (docs/specifications/quorum-spec.md)." >&2
        echo "  To skip these checks entirely next time (saves the test run):" >&2
        echo "      QUORUM_SKIP='no credits' git push" >&2
        exit 0
    fi
    exit 1
}

# THE EXPLICIT SKIP -- for time pressure, or for having no model credits left.
#
# Deliberately allowed ONLY in advisory mode. For someone already advisory it
# costs nothing but the wall-clock of the checks, so refusing it would be pure
# ceremony. For an enforced author it would be the silent hole this whole design
# rejects -- they get the committed waiver instead, which a reviewer can see.
if [ -n "${QUORUM_SKIP:-}" ]; then
    if [ "$MODE" = "advisory" ]; then
        echo "⚠ quorum gate SKIPPED for '${actor:-unknown}': $QUORUM_SKIP"
        echo "    (advisory anyway -- nothing was bypassed that would have blocked you)"
        exit 0
    fi
    echo "✗ QUORUM GATE: QUORUM_SKIP is not available to an enforced author." >&2
    echo "     '${actor:-unknown}' is listed in $ENFORCE_CFG." >&2
    echo "     Use a committed waiver so the bypass is reviewable:" >&2
    echo "       .quorum/<branch>.json -> {\"waived\": {\"reason\": \"...\"}}" >&2
    echo "     Or, locally and for an emergency only: git push --no-verify" >&2
    exit 1
fi

command -v python3 >/dev/null 2>&1 || die "python3 is required to read the receipt"

# THE BRANCH IS THE ONE BEING PUSHED TO, NOT THE ONE CHECKED OUT.
#
# Reading the local name was a rename bypass: `git checkout -b main && git push
# origin main:real-feature` hits the main|master exemption below and skips the
# gate entirely, while pushing a feature branch to the remote. git's pre-push
# protocol hands the real target on stdin as
#   <local ref> <local sha> <remote ref> <remote sha>
# so the hook passes it here with --remote-ref. Standalone runs (no hook) fall
# back to the local name, which is fine because a human running this by hand is
# not the adversary it defends against.
#
# AND SO IS THE COMMIT (#400). Taking the NAME from the push while resolving the
# diff, the receipt and the falsification test from the local checkout is not
# half a fix -- it is two different subjects with one verdict, and it broke in
# both directions. `git push origin branch-B` from a branch-A checkout died with
# "no quorum receipt at .quorum/branch-B.json" while `git cat-file -e
# branch-B:.quorum/branch-B.json` had it; and PRINT_HASH printed a different
# hash per checkout, so the binding that stops a receipt being recycled was
# computed against code nobody was pushing. `--local-sha` is the second half of
# git's line, and everything below resolves from it.
remote_ref=""
local_sha=""
while [ $# -gt 0 ]; do
    case "$1" in
        --remote-ref) remote_ref="${2:-}"; shift 2 ;;
        --local-sha)  local_sha="${2:-}"; shift 2 ;;
        *) shift ;;
    esac
done

if [ -n "$remote_ref" ]; then
    branch="${remote_ref#refs/heads/}"
else
    branch="$(git rev-parse --abbrev-ref HEAD)"
    [ "$branch" != "HEAD" ] || die "detached HEAD -- cannot identify the branch under review"
fi

# Callers that pass no sha keep working unchanged: `make quorum` (Makefile) and
# .github/workflows/quorum.yml both invoke this with at most --remote-ref, and
# in CI the checkout IS `pull_request.head.sha`, so HEAD is already the honest
# answer there.
pushed="${local_sha:-HEAD}"

# A TAG IS NOT A BRANCH AND HAS NO DIFF TO REFUTE.
#
# Caught pushing v1.24.0: git hands the hook `refs/tags/v1.24.0`, which matched
# no exemption, so the gate looked for `.quorum/v1.24.0.json` and refused the
# release tag. A tag names a commit whose claims were already adjudicated when
# the branch that produced it was pushed -- re-gating it asks for a receipt about
# a diff that does not exist.
case "$remote_ref" in
    refs/tags/*) echo "✓ quorum gate: '$remote_ref' is a tag, not a PR branch"; exit 0 ;;
esac

# main is exempt ONLY as a push target: there is no PR to gate, and a release
# commit legitimately has no new claims of its own to refute.
case "$branch" in
    main|master) echo "✓ quorum gate: '$branch' is not a PR branch, skipping"; exit 0 ;;
esac

# A DELETION HAS NO DIFF TO REFUTE.
#
# `git push --delete branch-B` hands the hook an all-zero LOCAL sha (measured:
# `local_ref=[(delete)] local_sha=[000…0] remote_ref=[refs/heads/branch-B]`).
# The hook's comment claimed the gate's own empty/branch logic already skipped
# this; it did not, and the guard has to sit HERE rather than next to the
# receipt read, because `git merge-base 0000…0 origin/main` is the first thing
# that breaks -- `fatal: Not a valid commit name`, exit 128.
case "$pushed" in
    "") ;;
    *[!0]*) ;;
    *) echo "✓ quorum gate: '$branch' is being deleted -- no diff to refute"; exit 0 ;;
esac

# THE DIFF THIS RECEIPT MUST MATCH.
#
# Bound to the merge-base, not to HEAD~1: a receipt has to cover everything the
# PR proposes, not the last commit someone happened to make. `git diff <base>...`
# (three dots) is the same set GitHub shows in the PR.
if ! git rev-parse --verify "$BASE_REF" >/dev/null 2>&1; then
    # A missing base is UNMEASURED, not clean. Say which state we are in.
    die "base ref '"$BASE_REF"' does not exist -- cannot compute the diff under review.
     Fetch it (git fetch origin main) or set QUORUM_BASE to the right base."
fi

# `git hash-object --stdin`, not `sha256sum`: the latter is GNU coreutils and does
# not exist on macOS, where `set -euo pipefail` would turn its absence into exit
# 127 and block every push for every macOS contributor. git is by definition
# present in a git hook.
git rev-parse --verify "$pushed^{commit}" >/dev/null 2>&1 \
    || die "'$pushed' is not a commit in this repository -- cannot review it."

merge_base="$(git merge-base "$pushed" "$BASE_REF")" \
    || die "no merge-base between $pushed and $BASE_REF"

# A base equal to the pushed commit means the diff is empty by construction --
# the shape the removed $QUORUM_BASE override exploited. Refuse rather than
# report clean.
if [ "$merge_base" = "$(git rev-parse "$pushed")" ]; then
    die "$pushed is at the merge-base with $BASE_REF -- there is nothing on this branch.
     If you expected changes, you are on the wrong branch or have not committed."
fi

# EXCLUDE GENERATED FILES FROM WHAT THE RECEIPT BINDS.
#
# `.quorum` is excluded so writing the receipt cannot invalidate the receipt.
# `.pmat` is excluded because the post-commit hook REGENERATES
# `.pmat/baseline.json` on every single commit -- watched live: the receipt was
# bound, the baseline was auto-staged behind it, the next commit carried it, and
# the hash moved for a reason no human touched. A binding that a background hook
# can break at any moment is a binding nobody can satisfy, and the pressure is
# then to delete the check rather than fix it.
#
# The rule: the hash covers what a REVIEWER reviews. Generated artefacts are not
# claims, so they are not part of the diff a quorum adjudicated.
diff_text="$(git diff "$merge_base" "$pushed" -- . ':(exclude,glob).quorum/*.json' ':(exclude).pmat')"
diff_hash="$(printf '%s' "$diff_text" | git hash-object --stdin)"
[ -n "$diff_hash" ] || die "could not compute a diff hash"

# An empty diff with commits ahead of the base is not "nothing to review" -- it is
# a doc-only or whitespace-only change, or a pathspec that ate everything. Say so.
if [ -z "$diff_text" ]; then
    die "the diff against $BASE_REF is empty, but HEAD is ahead of the merge-base.
     Nothing can be refuted because nothing is visible to the gate. Investigate
     before pushing rather than treating this as clean."
fi

# ONE IMPLEMENTATION OF THE HASH, EXPOSED.
#
# Writing a receipt means putting this hash in it, and the obvious way to get it
# -- `git diff ... | git hash-object --stdin` at a shell -- silently disagrees
# with the line above: command substitution strips the trailing newline, a bare
# pipeline does not, so the two hashes differ and the receipt looks stale for a
# reason nobody can see. Hit while writing the FIRST receipt this gate ever
# checked. Callers ask the gate rather than reimplementing it.
if [ "${PRINT_HASH:-0}" = "1" ]; then
    printf '%s\n' "$diff_hash"
    exit 0
fi

receipt="$RECEIPT_DIR/${branch//\//-}.json"

# THE RECEIPT IS READ FROM THE COMMIT, NOT FROM THE WORKING TREE.
#
# It used to be `[ -f "$receipt" ]` plus a pair of `git diff --quiet` calls
# meant to catch an edited-but-uncommitted receipt, because the gate hashed the
# COMMITTED diff and a local-green/CI-red gate teaches people the gate is noise.
# That happened on this gate's own first PR: a `git add` in the write-the-receipt
# step errored on an ignored path, `&&` short-circuited, the amend never ran, and
# CI rejected the stale committed receipt 22 seconds later.
#
# The guard was silent for the case that mattered. MEASURED on an UNTRACKED
# `.quorum/<branch>.json`: `git diff --quiet -- <path>` exits 0 and `git diff
# --cached --quiet -- <path>` exits 0, so a receipt that was never `git add`ed
# sailed through -- and with a `waived.reason` in it the gate exited 0 announcing
# "(committed in ... -- visible in the PR diff)" about a file `git status`
# reports as `??`. A complete, silent, unreviewable bypass, available to exactly
# the enforced authors QUORUM_SKIP is refused to, in the script that says
# "Bypass exists; silent bypass does not". (Tracked-then-modified DID trip it:
# the guard worked for the one case it was written for and only that one.)
#
# Reading the blob out of the pushed commit subsumes both: a receipt that is not
# committed is not there, and one that is committed is read exactly as CI will
# read it. No separate uncommitted-changes check is needed, and none can be
# skipped.
receipt_blob="$(mktemp)"
trap 'rm -f "$receipt_blob"' EXIT
git cat-file -p "$pushed:$receipt" > "$receipt_blob" 2>/dev/null \
    || die "no quorum receipt at $receipt in the commit being pushed ($pushed)

  A receipt written but not committed does not exist to this gate, because it
  will not exist to the reviewer or to CI either.
  This branch proposes changes that have not survived refutation.
  Run the quorum, then write the receipt AND COMMIT IT. See docs/quorum.md.
  Emergency bypass (recorded in the reflog): git push --no-verify"

# THE WAIVER -- a bypass that leaves a permanent, reviewable record.
#
# An enforced author cannot use --no-verify in CI, so without an escape the gate
# becomes a hostage the first time it is wrong. It is a reason STRING in the
# COMMITTED receipt: it lands in the PR diff where a reviewer sees it, and it
# cannot be set from the environment. Bypass exists; silent bypass does not.
waiver="$(python3 - "$receipt_blob" <<'WAIVE_PY' 2>/dev/null || true
import json, sys
try:
    print((json.load(open(sys.argv[1])).get("waived") or {}).get("reason", "").strip())
except Exception:
    print("")
WAIVE_PY
)"
if [ -n "$waiver" ]; then
    echo "⚠ quorum gate WAIVED for '$branch'"
    echo "    reason: $waiver"
    # This line used to claim the receipt was committed for a file that need not
    # have been. It is now true by construction -- the reason was read out of
    # $pushed's tree, so there is no other place it could have come from.
    echo "    (from $receipt in $pushed -- visible in the PR diff)"
    exit 0
fi

# Everything below is one python pass so a malformed receipt fails once, loudly,
# instead of eight times through eight greps.
# The files this branch actually touches, so the falsification test can be
# required to be one of them (see the free-rider defence below).
touched="$(git diff --name-only "$merge_base" "$pushed")"

python3 - "$receipt_blob" "$diff_hash" "$MIN_LANES" "$MIN_REFUTERS" "$MIN_JUDGES" "$touched" "$pushed" <<'PY' || exit 1
import json, os, subprocess, sys

path, want_hash, min_lanes, min_refuters, min_judges, touched_raw, pushed = sys.argv[1:8]
min_lanes, min_refuters, min_judges = int(min_lanes), int(min_refuters), int(min_judges)
touched = set(filter(None, touched_raw.splitlines()))

def die(msg):
    print(f"✗ QUORUM GATE: {msg}", file=sys.stderr)
    sys.exit(1)

try:
    r = json.load(open(path))
except Exception as e:
    die(f"{path} is not valid JSON: {e}")

for field in ("issue", "diff_sha256", "quorum", "falsification", "crux", "agy_teamwork", "pmat"):
    if field not in r:
        die(f"receipt is missing required field '{field}' -- all four lanes are mandatory\n"
            "  (crux = competitive survey, quorum = adversarial, agy_teamwork, pmat = mechanical)")

# THE KIND (forjar#491). A receipt may declare `kind: triage`: the branch
# classifies and links -- a ledger under docs/audits/, roadmap rows, and its own
# receipt -- and writes no code. That shape could not pass this gate at all (no
# Rust test to revert, no citable path), so every triage PR was pushed `waived`,
# which is the failure mode the CIT_RE comment in quorum_evidence.py names: a
# gate that cannot be passed honestly teaches a repo to reach for the waiver.
#
# The rail is verified FROM THE DIFF, not trusted from the receipt: a `kind:
# triage` receipt over a diff that touches anything outside docs/audits/**,
# docs/roadmaps/roadmap.yaml, docs/roadmaps/releases.yaml (PMAT-226) and
# .quorum/** is refused BY NAME, because declaring
# the kind would otherwise be the cheapest way to skip the falsification below.
receipt_kind = r.get("kind", "code")
if receipt_kind not in ("code", "triage"):
    die(f"receipt kind={receipt_kind!r} is not one of code | triage")
if receipt_kind == "triage":
    # PMAT-226: the release ledger, docs/roadmaps/releases.yaml, is the roadmap's
    # sibling -- declared goals, one row per tag, edited textually -- and booking
    # a tag's row after the cut is classify + link with no code in it. Named by
    # file, not by directory: docs/roadmaps/** would admit any future file there.
    def on_rail(p):
        return p.startswith("docs/audits/") or p == "docs/roadmaps/roadmap.yaml" \
            or p == "docs/roadmaps/releases.yaml" or p.startswith(".quorum/")
    off_rail = sorted(p for p in touched if not on_rail(p))
    if off_rail:
        die("a kind: triage receipt over a diff that touches "
            + ", ".join(off_rail) + "\n"
            "  A triage branch is classify + link, no diff: docs/audits/**, the roadmap,\n"
            "  the release ledger docs/roadmaps/releases.yaml and its own receipt under\n"
            "  .quorum/. Anything else is code, and a code change is judged as code --\n"
            "  with a falsification test it wrote itself.")

# THE BINDING. Without this the whole gate is theater: one receipt would clear
# every future branch, and an amended commit would keep a verdict about code
# that no longer exists.
got = r["diff_sha256"]
if got != want_hash:
    die(f"""receipt is STALE -- it describes a different diff.
     receipt: {got[:16]}...
     tree:    {want_hash[:16]}...
  The code changed after the quorum ran, so its verdict no longer covers it.
  Re-run the quorum against the current diff and rewrite the receipt.""")

q = r["quorum"]
lanes = q.get("lanes", [])
if len(lanes) < min_lanes:
    die(f"quorum had {len(lanes)} evidence lanes, floor is {min_lanes}")
if len(set(lanes)) != len(lanes):
    die("evidence lanes are not distinct -- duplicate lanes are one lane")

refuters = int(q.get("refuters_per_claim", 0))
if refuters < min_refuters:
    die(f"{refuters} refuters per claim, floor is {min_refuters}")

judges = int(q.get("judges", 0))
if judges < min_judges:
    die(f"{judges} judges, floor is {min_judges}")

confirmed = int(q.get("claims_confirmed", 0))
refuted = int(q.get("claims_refuted", 0))
if confirmed + refuted == 0:
    die("the quorum adjudicated 0 claims -- that is not a quorum, it is a formality")

# A QUORUM THAT NEVER KILLS ANYTHING IS NOT REFUTING.
#
# This is the anti-rubber-stamp check and the one most likely to be argued with.
# The memory this gate encodes says every high-value finding came from refutation;
# a panel that confirmed 100% of what it was handed was not adversarial, it was
# an echo. Set `refutation_waived` with a reason if a run genuinely found nothing
# wrong -- it is then visible in the receipt and in review, which is the point.
# THE CLAIMS MUST BE PRESENT AS TEXT, NOT ONLY AS TALLIES.
#
# `claims_confirmed: 43` is a black box: no human can review it, and it is exactly
# as easy to type as `4300`. An outside review made the Goodhart case against a
# bare kill-count -- an agent needing refuted>0 can manufacture a throwaway claim
# to shoot down -- and the answer is not to drop the count but to make what was
# killed legible. A fabricated "the sky is green" is invisible as a number and
# obvious as a sentence.
refuted_texts = q.get("refuted_claims", [])
if refuted and not refuted_texts:
    die("""the receipt reports refuted claims as a NUMBER but not as text.
  Add quorum.refuted_claims: [ ... the actual sentences that were killed ... ].
  A tally cannot be reviewed; a manufactured kill is only visible as prose.""")
if refuted_texts and len(refuted_texts) != refuted:
    die(f"claims_refuted={refuted} but {len(refuted_texts)} refuted_claims listed -- "
        "the tally and the text disagree")

if refuted == 0 and not q.get("refutation_waived"):
    die("""the quorum refuted NOTHING.
  Every claim survived, which usually means the refuters were not adversarial.
  If that is genuinely the result, set quorum.refutation_waived to a reason
  string so the claim is on the record rather than implied by silence.""")

# LANE 1 -- CRUX (competitive survey).
#
# >=3 NAMED systems, matching the bar `provable-iac.md` already sets. This lane is
# what stops a "fix" that is worse than the industry default: on #390, Ansible has
# returned stdout/stderr/rc as separate fields for a decade, and one survey lane
# would have caught the gap years before a misfiled caching bug did.
crux = r["crux"]
systems = crux.get("systems", [])
if len(systems) < 3:
    die(f"CRUX lane surveyed {len(systems)} systems, floor is 3 named systems")
if len(set(systems)) != len(systems):
    die("CRUX systems are not distinct")
if not crux.get("verdict"):
    die("CRUX lane has no verdict -- a survey with no conclusion is not a survey")

# LANE 3 -- agy /teamwork (independent stack).
agy = r["agy_teamwork"]
if not agy.get("ran"):
    die("agy /teamwork lane did not run -- an independent stack must have reviewed this")
if not agy.get("verdict"):
    die("agy /teamwork ran but recorded no verdict")

# LANE 4 -- pmat mcp (mechanical).
#
# `analyze_vacuous_tests` is required by name, not merely "some pmat tool". The whole
# output of a quorum is a claim backed by a test; a test that cannot fail backs
# nothing. This tree already contained tautologies inside files named
# `falsification_*` -- passing, correctly named, and proving nothing.
pmat = r["pmat"]
tools = pmat.get("tools", [])
if "analyze_vacuous_tests" not in tools:
    die("pmat lane did not run analyze_vacuous_tests -- it is required by name.\n"
        "  A quorum's output is a claim backed by a test; a vacuous test backs nothing.")
vac = pmat.get("vacuous_tests_in_touched_paths")
if vac is None:
    die("pmat lane did not report vacuous_tests_in_touched_paths -- UNMEASURED is not a pass")
if int(vac) > 0 and not pmat.get("accepted"):
    die(f"pmat found {vac} vacuous test(s) in the touched paths and none are accepted.\n"
        "  Fix them, or name each in pmat.accepted with a reason. Silence is not a pass.")

# THE RULE WITH TEETH.
#
# "A passing test suite proves the tests pass, not that the fix works." The only
# check that outranks the whole panel is: revert the production hunk and watch
# the test go red for the right reason.
f = r["falsification"]

# forjar#491: for a kind: triage receipt the revert-the-hunk check has no hunk
# to revert. The gate does not pretend otherwise -- it requires the receipt to
# SAY so (`not_applicable`, a reason), refuses a receipt that also names a test
# (one shape, not both), and prints exactly what it did not verify, so an
# unmeasured check never reads like a passed one. The rail check above is what
# keeps this arm honest: only a diff that touches no code reaches it.
if receipt_kind == "triage":
    na = f.get("not_applicable")
    if not isinstance(na, str) or not na.strip():
        die("a kind: triage receipt must carry falsification.not_applicable: a reason\n"
            "  string saying why no test was reverted. Silence is not a pass.")
    both = [k for k in ("test", "test_file", "cargo_test_target", "reverted", "observed_failure") if f.get(k)]
    if both:
        die("a kind: triage receipt carries falsification." + ", ".join(both) + " beside\n"
            "  not_applicable -- one shape, not both. Either the branch reverted a hunk\n"
            "  and is a code receipt, or it did not and says so.")
    print(f"✓ quorum receipt valid for {r['issue']} (kind: triage)")
    print(f"    lanes={len(lanes)} refuters={refuters} judges={judges} "
          f"confirmed={confirmed} refuted={refuted}")
    print("    falsification: NOT APPLICABLE (kind: triage) -- no test was reverted and none was run;")
    print("      the ledger's read-backs are the branch's evidence, and this gate does not read them")
    print(f"      reason: {na.strip()}")
    sys.exit(0)

for field in ("test", "reverted", "observed_failure"):
    if not f.get(field):
        die(f"falsification is missing '{field}' -- the revert-the-hunk check is not optional")

def in_pushed_tree(rel_path):
    """Is this path in the COMMIT BEING PUSHED?

    `os.path.exists` answered about the working tree, which is a different
    subject entirely when the push is not of the checked-out branch -- and it
    would happily accept a test file that is present locally and absent from
    everything a reviewer will ever see.
    """
    return subprocess.run(
        ["git", "cat-file", "-e", f"{pushed}:{rel_path}"],
        capture_output=True,
    ).returncode == 0

test_file = f.get("test_file", "")
if not test_file or not in_pushed_tree(test_file):
    die(f"falsification names test_file '{test_file}', which is not in the commit "
        "being pushed.\n"
        "  A falsification against a test nobody else can see cannot have happened.")

# THE FREE-RIDER DEFENCE.
#
# Requiring only that the named test EXISTS and PASSES was the gate's worst hole,
# and an outside review found it before this shipped: an agent minimising effort
# points `test_file` at any pre-existing always-green test -- `tests/sanity.rs` --
# and the gate happily verifies it is green, then trusts the attestation that it
# went red. The whole falsification becomes free.
#
# A falsification test must be one this branch WROTE OR CHANGED. That is
# mechanically checkable against the diff, costs nothing, and closes the cheap
# path completely: to fake it now you must actually add a test to the diff, which
# is most of the work you were trying to avoid.
if test_file not in touched:
    die(f"""falsification test '{test_file}' is not touched by this branch.
  Every file this branch changes was checked; that test is not among them.
  A falsification must exercise the change under review, so its test has to be
  written or modified HERE. Pointing at a pre-existing green test proves nothing
  -- it is the cheapest way to fake this check, which is why it is blocked.""")

# Verify the half that is cheap to verify: the test passes WITH the fix.
# A receipt citing a test that does not currently pass is rejected outright.
target = f.get("cargo_test_target")
if not target:
    die("falsification is missing 'cargo_test_target' (e.g. the --test name)")

# THE ONE CHECK THAT CANNOT FOLLOW THE PUSHED SHA, STATED AS A TRADE.
#
# Everything above resolves from $pushed. `cargo test` cannot: it compiles the
# WORKING TREE, so on a cross-branch push it would report on code that is not
# being reviewed -- a green that means nothing, which is worse than an absence.
#
# This IS a reachable local bypass: `git checkout main && git push origin
# my-branch` skips the only check this gate actually executes. It is accepted,
# deliberately, for two reasons and they must both hold:
#   1. .github/workflows/quorum.yml re-runs this entire gate on the PR head,
#      where the checkout IS the pushed commit, so the skip is local-only.
#   2. The honest alternative -- `git worktree add` at $pushed and build there
#      -- is not impossible, it is expensive: a second full target dir, and this
#      fleet already races on a shared CARGO_TARGET_DIR.
# If (1) ever stops being true, this skip becomes a hole and must be replaced by
# the worktree build, not by deleting the message.
head_now = subprocess.run(
    ["git", "rev-parse", "HEAD"], capture_output=True, text=True
).stdout.strip()
pushed_full = subprocess.run(
    ["git", "rev-parse", pushed], capture_output=True, text=True
).stdout.strip()

if head_now != pushed_full:
    print(f"  ⚠ NOT RUNNING the falsification test '{target}': the working tree is at "
          f"{head_now[:12]} but {pushed_full[:12]} is being pushed.")
    print("    cargo compiles the tree, not the commit, so a result here would "
          "describe code")
    print("    that is not under review. CI (.github/workflows/quorum.yml) runs it "
          "on the PR head.")
else:
    print(f"  verifying falsification test passes with the fix: {target}")
    # SCRUB GIT_* BEFORE RUNNING TESTS. This gate runs inside the pre-push hook,
    # where git has exported GIT_DIR for the repository being pushed. A test that
    # spawns `git` in its own tempdir then inherits that GIT_DIR and operates on
    # the developer's repository with the tempdir as work tree -- measured on
    # 2026-09-02: a falsification test's `git add -A && git commit` deleted 2,556
    # tracked files from the branch under review. The tests get a clean git
    # environment; the gate keeps its own.
    test_env = {k: v for k, v in os.environ.items() if not k.startswith("GIT_")}
    proc = subprocess.run(
        ["cargo", "test", "--test", target, "--quiet"],
        capture_output=True, text=True, env=test_env,
    )
    if proc.returncode != 0:
        tail = (proc.stdout + proc.stderr).strip().splitlines()[-15:]
        die("the falsification test does NOT pass on this tree:\n     "
            + "\n     ".join(tail))

print(f"✓ quorum receipt valid for {r['issue']}")
print(f"    lanes={len(lanes)} refuters={refuters} judges={judges} "
      f"confirmed={confirmed} refuted={refuted}")
print(f"    falsification: {f['test']}")
print(f"      reverted: {f['reverted']}")
print(f"      observed: {f['observed_failure']}")
PY

# EVIDENCE -- the owner's rule: intermediate results AND conclusion attached.
#
# quorum_evidence.py resolves every manifest blob at the commit it is handed, so
# it gets $pushed too. Handing it HEAD while the receipt described another branch
# was the same split subject as everything above.
head_commit="$(git rev-parse "$pushed")"
tallies="$(python3 - "$receipt_blob" <<'TALLY_PY'
import json, sys
q = json.load(open(sys.argv[1])).get("quorum", {})
print(int(q.get("claims_confirmed", 0)), int(q.get("claims_refuted", 0)))
TALLY_PY
)"
python3 "$(dirname "$0")/quorum_evidence.py" \
    "$receipt_blob" ${tallies} "$touched" "$merge_base" "$head_commit" \
    || die "evidence checks failed (see above)"

echo "✓ quorum gate passed ($MODE)"
