comments: the gate script says what each gate refuses

Each rung keeps one or two lines: what it refuses and the fact that
makes it necessary. The stories of how each gate came to exist are
gone. 148 comment lines become 48; the code is unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sam
2026-09-23 19:52:57 +07:00
co-authored by Claude Opus 5.5
parent f1a415a6ae
commit 6e5538df0d
+47 -147
View File
@@ -1,37 +1,28 @@
#!/usr/bin/env bash
# The commit gates (docs/METHOD.md "Gates"), one vetted runner: fmt-check →
# clippy -D warnings → workspace tests (skips refused where the data is) → doc-links →
# pass-span-lint → the player build and its own tests → the enforcer. Each is introduced where it
# runs, below. Fail-fast, nonzero exit on ANY failure, the failing log's tail printed.
# The land gate chain: every gate in order, stopping at the first failure.
#
# Exists because ad-hoc gate chains keep masking failures: `cargo … | tail` reports the PIPE's
# exit code, and `grep -c` exits nonzero on a zero count — both bit real sessions (phase-1a of
# the social arc, then again at the 6a land). Run this instead of composing pipelines.
# fmt, clippy, test, test-no-install, doc-links, pass-span-lint, player-build, player-tests,
# enforcer, enforcer-no-install. A failure prints the gate's log tail and exits nonzero; a green
# run stamps the tree, which then re-greens without running. GATES_FORCE=1 runs the chain anyway,
# WOW_DATA picks the install (set and empty: none) and TMPDIR holds the scratch files; the script
# sets BENILLA_REQUIRE_DATA, BENILLA_SKIP_LOG and WOW_WORLDVIEW_CHECK for its gates.
#
# scripts/gates.sh
set -uo pipefail
# Gate the tree you are STANDING IN, not the one this file happens to live in. A machine with
# several checkouts reaches for the gates by absolute path — `scripts/gates.sh` resolved from
# `$0`, which is another checkout. That silently gated the wrong one: fmt-red work in one
# worktree reported ALL GATES GREEN, because the other was clean.
#
# So: the git toplevel of $PWD, as long as it is a checkout of THIS repo (it has this script) — a
# stray invocation from a sibling repo like wow-5875-re falls back rather than gating that repo with
# benilla's gates. And print the tree either way: the silence is what made the old bug invisible.
# Gate the checkout $PWD is in, not this file's: a caller in one worktree may run another's copy
# by absolute path. Outside a checkout of this repo, gate this file's own checkout.
here="$(cd "$(dirname "$0")/.." && pwd)"
root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
[ -n "$root" ] && [ -f "$root/scripts/gates.sh" ] || root="$here"
cd "$root" || exit 1
echo "gating: $root"
# ── Green-stamp memoization (decision 1822) ──────────────────────────────────────────────────────
# A green chain stamps target/.gates-green with a key of exactly what determined the verdict: the
# WORKING TREE's true content hash (untracked files included — hashed through a throwaway index, the
# real one untouched, so `target/` and `WoW` stay excluded by the ignore rules), the
# toolchain, this script itself, and where the install resolver points. Re-running on an unchanged
# tree is then instant instead of ~5–8 min — which is what a land after an already-gated sync, or a
# "once more to be sure", actually costs the machine. GATES_FORCE=1 runs the chain regardless.
# The install path is compared lazily (its resolver is a `cargo run`, instant only on a warm
# target): a tree that doesn't match skips that probe entirely.
# ── Green-stamp memoization ──────────────────────────────────────────────────────────────────────
# A green run stamps target/.gates-green with the working tree's hash (untracked files included,
# through a throwaway index that leaves the real one alone), rustc's version, this script's hash
# and the install path, since the data-gated tests and the enforcer run only where one resolves.
# The install is compared last: resolving it is a `cargo run`.
stamp="target/.gates-green"
tree_key() {
local tmpidx t
@@ -44,13 +35,8 @@ tree_key() {
"$(shasum "$root/scripts/gates.sh" 2>/dev/null | cut -d' ' -f1)"
}
resolve_wow() { cargo run -q -p benilla-formats --example where 2>/dev/null || true; }
# **A docs-only delta keeps the verdict** (decision 2049). The stamped tree and the current one are
# both real tree objects (write-tree puts them in the object store), so git can say exactly what
# differs. If every differing path is under `docs/` or a top-level `*.md` — nothing compiles,
# tests, formats or `include_str!`s those, checked — then the chain's outcome on this tree IS the
# stamped one, by construction rather than by optimism. This is what lets a landing that only
# regenerated the map after a rebase go through without paying the chain again.
# A stamped tree that the object store has since pruned simply fails the check, and the chain runs.
# A delta only under docs/ or in top-level *.md files keeps the stamped verdict, since no gate
# reads those paths; a gate that starts to read one must narrow this pattern.
docs_only_delta() { # $1 = stamped tree, $2 = current tree
git cat-file -e "$1^{tree}" 2>/dev/null && git cat-file -e "$2^{tree}" 2>/dev/null || return 1
[ -z "$(git diff-tree -r --name-only "$1" "$2" 2>/dev/null | grep -vE '^(docs/|[^/]*\.md$)' || true)" ]
@@ -78,12 +64,8 @@ log="$(mktemp "${TMPDIR:-/tmp}/benilla-gates.XXXXXX")"
skips="$(mktemp "${TMPDIR:-/tmp}/benilla-gates-skips.XXXXXX")"
trap 'rm -f "$log" "$skips"' EXIT
# **How hollow was that green?** The data-gated tests pass without asserting on a machine that
# lacks the install or the addon corpus, and libtest swallows the skip line — so `install.rs`'s
# `skipped` appends one to `$BENILLA_SKIP_LOG` instead, and this reads the file back after each
# test rung. A clone without the data sees the number rather than a green it cannot weigh
# (docs/CONTRIBUTING.md, "Setting up"). Where the data is, `BENILLA_REQUIRE_DATA` has already
# made every skip a failure and the file stays empty.
# libtest hides a passing test's output, so `install.rs`'s `skipped` also appends each data-gated
# skip to `$BENILLA_SKIP_LOG`; this prints them, grouped, after a test rung and empties the file.
report_skips() { # $1 = rung name
[ -s "$skips" ] || return 0
echo " $1: $(wc -l <"$skips" | tr -d ' ') data-gated tests SKIPPED on this machine —"
@@ -92,14 +74,9 @@ report_skips() { # $1 = rung name
: >"$skips"
}
# ── Timing (decision 2265 §C1) ──────────────────────────────────────────────────────────────────
# Every gate's wall seconds, printed beside its verdict and appended to `target/.gates-timing`
# (gitignored with the rest of `target/`; one tab-separated line per gate per run: UTC time, tree
# short-sha, gate, seconds, verdict). 1822 measured the chain as the machine's single biggest load
# and scoped the per-round verify; its successor question — WHICH gate is the eleven minutes a
# land costs, and how that moves as the tree grows — could not be asked, because the only log was
# `rm -f`'d on exit and `run()` printed no duration. A memo hit and a skipped gate are recorded
# too, at 0 s, so a run's shape reads off the file whole.
# ── Timing ───────────────────────────────────────────────────────────────────────────────────────
# target/.gates-timing gets one tab-separated line per gate: UTC time, HEAD's short sha, gate,
# seconds, verdict. A memo hit and a skipped gate are logged at 0 s, the whole chain as `chain`.
timing="target/.gates-timing"
chain_t0=$SECONDS
tree_sha="$(git rev-parse --short HEAD 2>/dev/null || echo '-')"
@@ -125,14 +102,9 @@ run() {
run fmt cargo fmt --all -- --check
run clippy cargo clippy --workspace --all-targets -- -D warnings
# **Skips are refused where the data is** (decision 2329). `wow_data_or_skip!` and
# `addon_corpus_or_skip!` pass without asserting on a machine that lacks the install or the addon
# corpus, and libtest swallows a passing test's stderr — so on THIS machine a resolver that has
# drifted reads as green, which is how thirty corpus tests skipped at every land for three weeks
# after the pool moved drives. Where both resolve, `BENILLA_REQUIRE_DATA=1` turns a skip into the
# failure it is. Both, not either: with the install alone every corpus test would fail for the
# honest reason. Ask the resolver for the install (the same `where` the player-tests rung and the
# enforcer use); the corpus is the `wow-addons-vanilla` link at the tree's root.
# test: where the install resolves and the `wow-addons-vanilla` corpus link exists,
# BENILLA_REQUIRE_DATA=1 refuses a data-gated test that skips; with the install alone it would
# fail every corpus test. `$wow_data` is resolved once, for player-tests and the stamp too.
wow_data="$(resolve_wow)"
require_data=""
if [ -n "$wow_data" ] && [ -d "$root/wow-addons-vanilla" ]; then
@@ -145,87 +117,34 @@ fi
run test env ${require_data:+BENILLA_REQUIRE_DATA=1} BENILLA_SKIP_LOG="$skips" cargo test --workspace
report_skips test
# test-no-install: the same suite as a clone without client data runs it. A test that reads the
# install without opening with `wow_data_or_skip!` passes where the data is and panics everywhere
# else; `WOW_DATA=` (set, empty) is the resolver's "no install", so this run is that machine. No
# build variable reads it, so the binaries are reused and only the run time is paid.
# test-no-install: refuses a test that reads the install without `wow_data_or_skip!`. `WOW_DATA=`
# (set, empty) is the resolver's "no install"; no build reads it, so the test binaries are reused.
run test-no-install env -u BENILLA_REQUIRE_DATA WOW_DATA= cargo test --workspace
# **doc-links** (decision 1925) — the docs are the knowledge base, so a doc link pointing at a
# DELETED item is rot, and nothing else here runs rustdoc. Deliberately narrow: it fails only on a
# `crate::`/`super::`/`self::`/`Self::` path whose leaf exists nowhere in the workspace. The broad
# `-D rustdoc::broken_intra_doc_links` was measured and rejected — 818 of its warnings are
# conventions this codebase writes on purpose (docs naming private internals by path, one crate
# naming another's module). The script's own header carries that measurement. ~22 s warm.
# doc-links: refuses a doc link to a `crate::`, `super::`, `self::` or `Self::` path whose leaf is
# declared nowhere in the workspace. Narrow on purpose: every other unresolved link is a convention.
run doc-links scripts/doc-links.py
# **pass-span-lint** (decision 2258, bug B390) — one `pass_span` per render pass at a time. bevy's
# pass span is a pipeline-statistics query as well as a timestamp pair, wgpu allows one active at
# a time, and only Vulkan exposes the feature: Metal (this machine) and DX12 (the lab laptop's
# pinned backend) run a nested span as a silent no-op, which is how the 09-15 sync aborted every
# Linux and Vulkan-on-Windows player on their first world frame with every gate green. The runtime
# fact no compile or macOS run can reach, read off the source in a tenth of a second.
# pass-span-lint: refuses a second `pass_span` on a render pass whose span is still open, a fatal
# wgpu validation error on Vulkan and a silent no-op on Metal and DX12, which no macOS run sees.
run pass-span-lint scripts/pass-span-lint.py
# **The player build** (decision 1173's deliverable; built in 1174). `benilla` is the binary a
# player would run, and `--no-default-features` drops the `dev` feature — so the debug panel, the
# perf HUD, the inspector, the capture harness and the probe fleet are not compiled at all. It
# fails the moment anything outside those roots names one of them.
#
# This line IS the mechanism, not the module boundary above it. 0026 adopted "add no new
# gameplay→dev coupling" in June 2026 with nothing to fail; by August there were 24 references
# across 12 files, because a rule with no failing build is a wish (1173). Same lesson as the
# enforcer below: a tripwire nobody trips is not a tripwire.
#
# `build`, not `test`: the seam's claim is that the player binary LINKS without the instruments.
# The tests are dev-side by construction (they drive fixtures and probes) and stay on the default
# feature set, where `cargo test --workspace` above already runs them.
# player-build: `benilla` without `dev`, which compiles out the debug panel, perf HUD, inspector,
# capture harness and probes, so it fails when any other code names one of them.
run player-build cargo build -p benilla --no-default-features
# **The player build's own unit tests** (decision 1175). A build proves the seam LINKS; it cannot
# prove the player configuration BEHAVES. The two facts 1175 rests on are both `#[cfg(not(feature =
# "dev"))]` — the install resolver looks nowhere inside the source tree, and the state folder lands
# beside the binary — so `cargo test --workspace` above, which runs with default features, never
# executes either of them. A falsifier that never runs is 1173's wish with a `#[test]` on it.
#
# `--lib` only: the integration tests and examples are dev-side by construction. Measured at ~7 s
# warm, because these two crates' unit tests are pure.
#
# **`$WOW_DATA` is handed in, and that is not a loophole.** Decision 1751 began sourcing FrameXML
# off the player's own patch chain, so the shipped-UI tests in this same `--lib` set now need an
# install — while the whole point of `--no-default-features` is that the resolver's *source-tree*
# rung is compiled out. The two collided silently: 27 tests here failed for a missing
# `Interface\FrameXML\ContainerFrame.xml` on every machine whose shell did not happen to export
# the variable, and the gate had been red for that reason rather than for anything a commit did.
# `$WOW_DATA` is rung ONE of the ladder, not the dev rung, so both facts 1175 asks this gate to
# falsify still hold: nothing here reads the source tree, and `install.rs`'s own tests take their
# environment as arguments (that is why they do) and cannot see this at all.
#
# Ask the resolver, don't re-derive it — the same rule, and the same `where`, as the enforcer
# below; `$wow_data` was resolved once, ahead of the test gate. No install found: run it bare,
# and let the client-data tests say so themselves (`BENILLA_REQUIRE_DATA` is deliberately NOT
# handed down: rung 2 is compiled out here, and what this rung falsifies is the player ladder,
# not the resolver — a skip here would be the honest one).
# player-tests: the unit tests of the `cfg(not(feature = "dev"))` code (the resolver skips the
# source tree; the state folder sits beside the binary), which no other gate runs. WOW_DATA, which
# a player build reads too, goes in for the shipped-UI tests that read FrameXML off the install; no
# BENILLA_REQUIRE_DATA, since without `dev` only $BENILLA_ADDON_CORPUS finds the addon corpus.
run player-tests env ${wow_data:+WOW_DATA="$wow_data"} BENILLA_SKIP_LOG="$skips" \
cargo test -p benilla-formats -p benilla-app --no-default-features --lib
report_skips player-tests
# **The enforcer** (decision 1160's second binary, made into a gate by 1164). `benilla-worldview`
# boots the engine plugin set with no server, no login, no UI and no player, and any system whose
# parameters need a gameplay resource reports itself. It is the only check for coupling that
# crosses NO SYMBOL — a `Res<player::Player>` read is invisible to the compiler and to the API wall
# test, and shows up only at runtime.
#
# It is here because it rotted: the binary sat broken on main, failing on frame one, for as long as
# nobody happened to run it. A tripwire nobody trips is not a tripwire.
#
# ~15 s, and it opens a small cornered window (`bgwin`'s no-pixel rule) — it reads the error log,
# not the framebuffer. Skipped, loudly, without the 1.12.1 install: the gate cannot demand an asset
# tree the repo is forbidden to contain.
# **Ask the resolver, don't re-derive it** (decision 1175). This used to read
# `[ -n "$WOW_DATA" ] || [ -d WoW/Data ]` — a second, hand-rolled copy of the rule for where the
# install is, which is the exact duplication that record exists to end. A gate that disagrees with
# the client skips on a machine where the client would have run, or runs where it cannot.
# enforcer: `benilla-worldview` boots the engine plugins with no server, login, UI or player, in a
# small window, and refuses any system whose parameters need a gameplay resource, a coupling no
# compiler sees. The client's own resolver decides whether there is an install, so it skips
# exactly where the client could not run.
if cargo run -q -p benilla-formats --example where >/dev/null 2>&1; then
run enforcer env WOW_WORLDVIEW_CHECK=10 cargo run -q -p benilla-worldview
else
@@ -234,39 +153,20 @@ else
note_timing enforcer 0 SKIPPED
fi
# **The enforcer again, with NO install** (decision 1451) — the boot every player who unzips
# benilla into the wrong folder takes, and the one nothing on this machine could take: a dev build
# finds the project folder, so `wow_data()` never returned `None` in any run we make. Nobody had
# to *break* that path; it simply drifted, and by August a `Startup` that inserts a resource only
# when there is client data faced an `Update` that took it as a hard `Res`. Bevy's default handler
# panics on a parameter that cannot validate, so the client died on frame one with a system name it
# could not even print. `WOW_DATA=` (set, empty) is `benilla_formats::install`'s spelling of "there
# is no install", and the check's error handler collects every such fault instead of dying on the
# first — so this run names them all, the day they land.
#
# No `if`: this gate is the one that needs no install. ~5 s (nothing to stream — every system is
# offered to the executor, which is what validates its parameters, within the first frames), and
# the same small cornered window as above. It covers the engine crate, which is where the
# world-shaped resources live; the client's own no-install boot is still eyes-on.
# enforcer-no-install: the engine boot with no client data, which a dev build reaches only under
# `WOW_DATA=`; refuses a system that needs a resource inserted only when there is client data.
# Five seconds suffice: with nothing to stream, every system validates in the first frames.
run enforcer-no-install env WOW_DATA= WOW_WORLDVIEW_CHECK=5 cargo run -q -p benilla-worldview
echo "ALL GATES GREEN ($((SECONDS - chain_t0)) s; per-gate seconds in $timing)"
note_timing chain "$((SECONDS - chain_t0))" ok
# Stamp the verdict — but only if the tree is still the one the chain actually ran on: parallel
# agents share this working tree (docs/METHOD.md, "Who types what"), and a stamp for a half-edited tree
# would memoize a green nothing verified. A changed key just means no stamp; nothing fails.
# Stamp only if the tree still hashes as it did at the start: an edit during the run would
# otherwise memoize a green for a tree the chain never saw.
key_end="$(tree_key || true)"
if [ -n "$key_end" ] && [ "$key_end" = "$key_start" ] && [ -d target ]; then
printf '%s|%s\n' "$key_end" "$wow_data" > "$stamp" 2>/dev/null || true
fi
# **The fourth gate has a runner too, and it is not this script** (2277). docs/METHOD.md asks for
# `fmt` · `clippy` · `test` · *a clean run*, and for a long time only the first three were a
# command — so "a clean run" was a paragraph in docs/METHOD.md, which a session can simply not have
# read. One did, decided it could not reach the server at all, and said so to the director while
# shipping a change whose entire subject was a session boundary.
#
# Not a rung: it wants the local vmangos, opens a window, and costs ~45 s, so paying it per commit
# is the director's call. A pointer costs nothing and makes the verb impossible to not know about.
# The clean-run gate, scripts/smoke.sh, needs a server and a window: named here, not run.
echo " (a clean run is the fourth gate: scripts/smoke.sh — live logout/re-login round trip)"