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:
+47
-147
@@ -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)"
|
||||
|
||||
Reference in New Issue
Block a user