The gate refuses a landing on the public host while the marker stands, binds every remote rule to the URL, and its manifest check survives GNU sed

While tools/ci/github-suspended stands the public host git.igneum.network holds a rewritten copy whose master is replaced at cut-over by the rewrite of the box mirror's final tip, so a landing there is lost: pre-push.sh (forgejo_master_frozen, hook mode) and merge-to-master.sh (forgejo_master_refusal) refuse one and name the box route; a branch pushed there for safekeeping passes.

The installed hook hands the gate "<remote name> <url>", and the remote rules read $2, the name: "origin" matched no *github.com*, so the GitHub refusal and the CI rule never bound a named push. Hook mode now resolves the URL ($3, else git remote get-url) and the self-test drives the hook by name through a fixture repository for the GitHub remote, the public host and a safekeeping branch. GATE_ROOT is the script's own repository, so the helpers are read from it wherever the hook is driven from.

gate-manifest-check.sh under pipefail piped names_in into grep -q; GNU sed took SIGPIPE when grep matched early and the check read it as a missing run line (a research lane's gate on build-3, 13:4x UK: forty names "missing", the Mac never saw it because BSD sed had finished writing). The names are matched through a here-string now, proved green five times on build-3; the same shape is removed from playbook-quit, pc1-step-budget and second-engine. mirror_master fast-forwards every box with a build-server file (build-3 and build-4 sat at 7 October 15:27).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-08 12:57:40 +00:00
parent 17cc15dc5e
commit a5eea5e31c
7 changed files with 77 additions and 15 deletions

View file

@ -10,6 +10,7 @@
| the red watcher fires on cancelled and timed-out runs too (`ci-red.yml`, `red-watch.mjs`) | The watcher's `if` missing any of failure, cancelled, timed_out, or the conclusion not handed to the record step (the self-test reads the workflow file); the line names the kind: CI red, CI cancelled, CI timed out. | 7 October 2026 |
| gh's active account is the stored Igneum entry (`gh-account-check.sh`, in Igneum's own gh directory `~/.config/gh-igneum` through `gh-env.sh`, never the founder's) | A push or a landing from this Mac while Igneum's gh directory names any other account as active, or none (the refusal names the one step: the founder or main stores the Igneum token there with `GH_CONFIG_DIR=~/.config/gh-igneum gh auth login --with-token`; no lane does); skipped with a line while `github-suspended` stands. RULE: no lane switches gh accounts on this Mac, ever; the second owner's login belongs to other projects and must never touch Igneum; the stored entry's name is in ~/.config/igneum/gh-user, never in the repository. | 7 October 2026, 21:41 UK: a lane switched gh to the other login during the suspension; nobody could say which |
| no landing on the public host while the marker stands (`pre-push.sh` `forgejo_master_frozen`, `merge-to-master.sh` `forgejo_master_refusal`); the hook binds every remote rule to the remote's URL | A push of master to git.igneum.network, or a `--remote` naming it, while `tools/ci/github-suspended` stands: its master is a rewritten copy replaced at cut-over, so the landing would be lost (a branch pushed there for safekeeping passes). Before the fix the hook matched the remote NAME, so `git push origin master` bound neither the GitHub refusal nor the CI rule; the self-test now drives the hook by name through a fixture repo. Also: `gate-manifest-check.sh` and four other pipefail checks no longer pipe a file-sized producer into `grep -q` (GNU sed took SIGPIPE on an early match and the check read it as a missing run line on the Linux runners and boxes); `mirror_master` fast-forwards every box with a build-server file | 8 Oct 2026 |
| kill by exact command or pid file (owed as a check) | 6 October 2026, 21:09Z: a Mac-side `pkill -f <log file name>` matched nothing (the log name was a redirect, not part of the command line), the roll-everything script lived on and wiped a box it had been told to hold. Rule: a job is stopped by its pid file (`tools/fleet/fleet-bg.sh start|stop <name>`) or by a pattern anchored on its exact command line (`^python3 -u /root/fleet/in/box-prover.py`), never by a word that may or may not appear in it. The check that flags a `pkill -f`/`pgrep -f` whose literal is a path or a name that never starts a command line is owed to the CI lane |

View file

@ -14,9 +14,13 @@ set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
names_in() { grep -E '^[[:space:]]+run "' "$1" | sed -E 's/^[[:space:]]+run "([^"]+)".*/\1/'; }
compare() { # <gate script> <manifest> -> exit 1 with the lines
local gate="$1" manifest="$2" rc=0 n
while IFS= read -r n; do [ -n "$n" ] || continue; names_in "$gate" | grep -qxF -- "$n" || { echo "gate-manifest: listed check has no run line in $(basename "$gate"): $n" >&2; rc=1; }; done < <(grep -vE '^\s*(#|$)' "$manifest")
while IFS= read -r n; do [ -n "$n" ] || continue; grep -qxF -- "$n" "$manifest" || { echo "gate-manifest: run line not in $(basename "$manifest"): $n" >&2; rc=1; }; done < <(names_in "$gate")
local gate="$1" manifest="$2" rc=0 n names
# the names once, matched through a here-string: `names_in | grep -q` under pipefail reads a SIGPIPE on sed (grep -q closing
# the pipe on an early match before sed has written the rest) as "no run line"; GNU sed on the runners and the boxes hit it on
# the first forty names, BSD sed on the Mac never did (8 October 2026, 13:5x UK, a research lane's gate on build-3)
names=$(names_in "$gate")
while IFS= read -r n; do [ -n "$n" ] || continue; grep -qxF -- "$n" <<<"$names" || { echo "gate-manifest: listed check has no run line in $(basename "$gate"): $n" >&2; rc=1; }; done < <(grep -vE '^\s*(#|$)' "$manifest")
while IFS= read -r n; do [ -n "$n" ] || continue; grep -qxF -- "$n" "$manifest" || { echo "gate-manifest: run line not in $(basename "$manifest"): $n" >&2; rc=1; }; done <<<"$names"
return $rc
}
case "${1:-}" in

View file

@ -42,6 +42,14 @@ github_suspended_refusal() { # <remote>: prints the refusal and returns 0 when
echo "merge-to-master: REFUSED. GitHub is unreachable ($(grep -E '^suspended-since' "$SUSPENDED_FILE" | head -1); tools/ci/github-suspended stands): nothing is pushed, fetched or polled there. Land on the box mirror: tools/ci/merge-to-master.sh --remote box (or MERGE_REMOTE=box). Main removes the marker at the cut-over." >&2
return 0
}
forgejo_master_refusal() { # <remote>: prints the refusal and returns 0 when the remote is the public Forgejo host and the marker stands
# (its master is a rewritten copy replaced at cut-over by the rewrite of the box mirror's final tip; main's word, 8 October 2026, 13:34 UK)
local host="${IGNEUM_PUBLIC_GIT_HOST:-git.igneum.network}"
[ -f "$SUSPENDED_FILE" ] || return 1
case "$(git remote get-url "$1" 2>/dev/null)" in *"$host"*) ;; *) return 1 ;; esac
echo "merge-to-master: REFUSED. $host holds a rewritten copy; its master is replaced at cut-over by the rewrite of the box mirror's final tip, so a landing there is lost. Land on the box mirror: tools/ci/merge-to-master.sh --remote box" >&2
return 0
}
CI_STATE="${CI_STATE_CMD:-node tools/ci/ci-state.mjs}" # the self-test swaps in a fake
clock() { TZ=Europe/London date '+%H:%M %Z'; }
@ -83,7 +91,8 @@ master_gate() {
# that is down prints a line and never fails the landing.
mirror_master() { # <sha>
local sha="$1" m host key="${BS_KEY:-$HOME/.ssh/igneum_ed25519}" list="${IGNEUM_MIRRORS:-}"
if [ -z "$list" ]; then for f in "$HOME/.config/igneum/build-server" "$HOME/.config/igneum/build-server-2"; do [ -s "$f" ] && list="$list $(head -1 "$f" | tr -d '[:space:]'):/srv/igneum.git"; done; fi
# every box with a build-server file (8 October 2026, 13:5x UK: build-3 and build-4's mirrors sat at 7 October 15:27 with only the first two listed)
if [ -z "$list" ]; then for f in "$HOME/.config/igneum/build-server" "$HOME"/.config/igneum/build-server-[0-9]*; do [ -s "$f" ] && list="$list $(head -1 "$f" | tr -d '[:space:]'):/srv/igneum.git"; done; fi
for m in $list; do
if GIT_SSH_COMMAND="ssh -i $key -o BatchMode=yes -o ConnectTimeout=10" git push -q "$m" "$sha:refs/heads/master" 2>/dev/null; then echo "merge-to-master: mirror $m master -> ${sha:0:8}"
else echo "merge-to-master: mirror $m did not take master ${sha:0:8} (down, or not a fast-forward); the next landing tries again"; fi
@ -99,6 +108,11 @@ if [ "$SELF_TEST" = 1 ]; then
case "$out" in *"REFUSED. GitHub is unreachable"*"--remote box"*) ;; *) echo "self-test failed: the refusal did not name the switch: $out"; fails=1 ;; esac
( SUSPENDED_FILE="$d/marker"; github_suspended_refusal box >/dev/null 2>&1 ) && { echo "self-test failed: the box mirror remote was refused under the marker"; fails=1; }
( SUSPENDED_FILE="$d/no-marker"; github_suspended_refusal origin >/dev/null 2>&1 ) && { echo "self-test failed: a GitHub remote was refused without the marker"; fails=1; }
# the public Forgejo host takes no landing while the marker stands; the box mirror does (a fixture repo with both remotes)
mkdir -p "$d/pub" && git -C "$d/pub" init -q && git -C "$d/pub" remote add pub ssh://git@git.igneum.network:2222/igneum-network/igneum.git && git -C "$d/pub" remote add box ssh://build@188.40.146.49/srv/igneum.git
( cd "$d/pub" && SUSPENDED_FILE="$d/marker" forgejo_master_refusal pub >/dev/null 2>&1 ) || { echo "self-test failed: the public host was not refused as a landing target under the marker"; fails=1; }
( cd "$d/pub" && SUSPENDED_FILE="$d/no-marker" forgejo_master_refusal pub >/dev/null 2>&1 ) && { echo "self-test failed: the public host was refused without the marker"; fails=1; }
( cd "$d/pub" && SUSPENDED_FILE="$d/marker" forgejo_master_refusal box >/dev/null 2>&1 ) && { echo "self-test failed: the box mirror was refused as the public host"; fails=1; }
# the fake answers from $d/answer-<sha> (one line per call, consumed top to bottom; the last line repeats) and $d/answer-master
cat > "$fake" <<'FAKE'
#!/usr/bin/env bash
@ -161,7 +175,8 @@ success 4 u push run
[ "$fails" = 0 ] && echo "self-test passed: a GitHub remote is refused with exit 2 while the suspension marker stands and a mirror remote is not; the CI rule binds a GitHub remote only; a landed master is fast-forwarded to every mirror and a refused mirror push is a line, not a failure; a green branch run merges; a red one is refused; an unknown read is waited through and refused only at the deadline; a queued run is waited for with the clock; an unrun branch is pushed once and then waited for; a red master refuses every merge but the declared fix"
exit $fails
fi
if [ "$SELF_TEST" != 1 ] && github_suspended_refusal "$REMOTE"; then exit 2; fi # before any gh or git call (the self-test exercises the function itself)
if [ "$SELF_TEST" != 1 ] && github_suspended_refusal "$REMOTE"; then exit 2; fi
if [ "$SELF_TEST" != 1 ] && forgejo_master_refusal "$REMOTE"; then exit 2; fi # the public host takes no landing while the marker stands # before any gh or git call (the self-test exercises the function itself)
[ -z "$(git status --porcelain --untracked-files=no)" ] || { echo "merge-to-master: the tree has uncommitted tracked changes; commit first" >&2; exit 1; }
bash tools/ci/gh-account-check.sh || exit 1 # gh's active account on this Mac is the stored Igneum entry (main's rule, 7 October 2026, 21:5x UK)
SHA=$(git rev-parse "$BRANCH"); G=$(cd "$(git rev-parse --git-common-dir)" && pwd -P)

View file

@ -44,6 +44,6 @@ n=$(grep -c 'deadline.AddMinutes(-4)' "$f" || true)
[ "$n" -ge 2 ] || { echo "pc1-step-budget: $f guards fewer than two places with the four-minute reserve (\$deadline.AddMinutes(-4): the step loop and the lock loop)"; bad=1; }
grep -q 'lastMhs\[\$pk\] \* 1e6' "$f" || { echo "pc1-step-budget: $f does not size the batch count from the last rate read (\$lastMhs)"; bad=1; }
grep -q 'script:lastMhs\[\$pk\] = \$b.mhs' "$f" || { echo "pc1-step-budget: $f never records the last rate per pack"; bad=1; }
if grep -vE '^\s*#' "$f" | grep -qE 'if \(\(Get-Date\) -gt \$deadline\)'; then echo "pc1-step-budget: $f still guards a step with the bare deadline (no reserve for the restore)"; bad=1; fi
if grep -qE 'if \(\(Get-Date\) -gt \$deadline\)' <<<"$(grep -vE '^\s*#' "$f")"; then echo "pc1-step-budget: $f still guards a step with the bare deadline (no reserve for the restore)"; bad=1; fi
[ $bad = 0 ] && echo "pc1-step-budget: the efficiency pass keeps four minutes for its restore and sizes each step from the last rate"
exit $bad

View file

@ -19,7 +19,7 @@ cd "$(dirname "$0")/../.."
check_file() {
local f="$1" bad=0
# rule 2: any request to api/cards outside a comment (Invoke-RestMethod, Invoke-WebRequest, curl, fetch: the shape is the URL)
if grep -vE '^\s*#' "$f" | grep -qE "api/cards"; then
if grep -qE "api/cards" <<<"$(grep -vE '^\s*#' "$f")"; then
echo "playbook-quit: $f sends a request to the installed app's api/cards (a script never switches cards; ask the runner: publish-jobs.sh add --kind run --cards-off <key,key>)"; bad=1
fi
# rule 3 (STANDING RULE, the founder through main, 7 October 2026, 18:5x UTC): no PC job raises a UAC prompt or needs a click, ever.
@ -27,11 +27,11 @@ check_file() {
# Power Helper task (Start-ScheduledTask by the owning user, commands through its cmd.txt: app/igneum-app/src/powertask.rs).
# publish-jobs.sh refuses --elevated for the same reason (the 18:27Z job run-ca3-pc1-v4-eff-5090-20261007: exit 251 after
# two minutes waiting for a click).
if grep -vE '^\s*#' "$f" | grep -qiE -e "(-Verb +['\"]?RunAs)|(\brunas(\.exe)? +/user)"; then
if grep -qiE -e "(-Verb +['\"]?RunAs)|(\brunas(\.exe)? +/user)" <<<"$(grep -vE '^\s*#' "$f")"; then
echo "playbook-quit: $f raises an administrator prompt (-Verb RunAs or runas): no PC job prompts; use the Igneum Power Helper task (app/igneum-app/src/powertask.rs)"; bad=1
fi
grep -qE "api/(quit|pause|resume)" "$f" || return $bad
if grep -vE '^\s*#' "$f" | grep -qE "igneum\\\\app\\\\app\.url|igneum/app/app\.url|Application Support/Igneum/app/app\.url|IGNEUM_APP_DIR[^\n]*app\.url|\\\$appDir[^\n]*'app\.url'"; then
if grep -qE "igneum\\\\app\\\\app\.url|igneum/app/app\.url|Application Support/Igneum/app/app\.url|IGNEUM_APP_DIR[^\n]*app\.url|\\\$appDir[^\n]*'app\.url'" <<<"$(grep -vE '^\s*#' "$f")"; then
echo "playbook-quit: $f reads the installed app's URL file and sends quit, pause or resume to it (a job may only quit an engine it started: its own scratch URL file)"; bad=1
fi
return $bad

View file

@ -26,7 +26,9 @@ cd "$(git rev-parse --show-toplevel)" || exit 1
# and fire this hook again inside the fixture: the first master push through the gate died that way (6 October 2026).
unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_PREFIX GIT_COMMON_DIR GIT_OBJECT_DIRECTORY GIT_ALTERNATE_OBJECT_DIRECTORIES GIT_QUARANTINE_PATH GIT_PUSH_OPTION_COUNT
MODE="${1:-local}"; MODE="${MODE#--}"
GATE_ROOT="$(pwd -P)"
GATE_ROOT="$(cd "$(dirname "$0")/../.." && pwd -P)" # the repository this gate belongs to: its helpers (tools/ci/*.sh) are read from here even when the hook
# is driven from another directory (the self-test's fixture repositories, 8 October 2026); the tree under
# test stays the working directory
# Kill-proof by rule (8 October 2026, 12:4x UK: twice in a day a gate died to signal 15 from another lane's kill pattern): every gate run
# carries a unique process title (igneum-gate:<pid>-<start>, set by re-exec through `exec -a`) and writes its pid, start and mode to
# .git/igneum-gate.pid under the worktree (removed at exit); a gate is stopped by that file only (`kill "$(cut -d' ' -f1 <file>)"`), and
@ -227,6 +229,20 @@ github_suspended() { # <remote url>: 0 with a printed refusal when the marker
echo "pre-push gate: REFUSED. GitHub is unreachable ($(grep -E '^suspended-since' "$f" | head -1); tools/ci/github-suspended stands): no push, fetch or poll there. Push to the box mirror (git push box ...; landings: tools/ci/merge-to-master.sh --remote box). Main removes the marker at the cut-over." >&2
return 0
}
forgejo_master_frozen() { # <remote url> <hook ref lines>: 0 with a printed refusal when the marker stands, the remote is the public
# Forgejo host and a ref line lands master there. Forgejo's master is a rewritten copy that the cut-over replaces with the rewrite
# of the box mirror's final tip (main's word through the coordinator, 8 October 2026, 13:34 UK): a branch pushed there for
# safekeeping is fine, a landing on its master is lost at cut-over. The marker goes at cut-over, and the refusal with it.
local f="${IGNEUM_GITHUB_SUSPENDED_FILE:-$GATE_ROOT/tools/ci/github-suspended}" host="${IGNEUM_PUBLIC_GIT_HOST:-git.igneum.network}" lref lsha rref rsha
[ -f "$f" ] || return 1
case "${1:-}" in *"$host"*) ;; *) return 1 ;; esac
while read -r lref lsha rref rsha; do
[ "$rref" = refs/heads/master ] && [ -n "$lsha" ] && [ "$lsha" != 0000000000000000000000000000000000000000 ] || continue
echo "pre-push gate: REFUSED. $host holds a rewritten copy; its master is replaced at cut-over by the rewrite of the box mirror's final tip, so a landing there is lost. Land on the box mirror (tools/ci/merge-to-master.sh --remote box); a branch may go to $host for safekeeping." >&2
return 0
done <<<"${2:-}"
return 1
}
master_rule_binds() { # <remote url>: 0 when the CI rule applies to this push (a GitHub remote, no declared exception), 1 with a printed line otherwise
local url="${1:-}"
if [ -n "${IGNEUM_MASTER_EXCEPTION:-}" ]; then echo " EXCEPTION to the CI rule for this push, declared by main: $IGNEUM_MASTER_EXCEPTION (the local gate is the verdict)"; return 1; fi
@ -323,8 +339,29 @@ FAKEGH
case "$out" in *"REFUSED. GitHub is unreachable"*"--remote box"*) ;; *) echo "self-test failed: the refusal did not name the switch: $out"; fails=1 ;; esac
( IGNEUM_GITHUB_SUSPENDED_FILE="$mk" github_suspended ssh://build@188.40.146.49/srv/igneum.git >/dev/null 2>&1 ) && { echo "self-test failed: a mirror remote was refused under the marker"; fails=1; }
( IGNEUM_GITHUB_SUSPENDED_FILE="$mk.none" github_suspended https://github.com/x/y.git >/dev/null 2>&1 ) && { echo "self-test failed: a GitHub remote was refused without the marker"; fails=1; }
grep -qE 'github_suspended "\$URL" && exit 1' "$0" || { echo "self-test failed: the hook does not refuse a GitHub push under the marker"; fails=1; }
grep -qE 'master_rule_binds "\$URL"' "$0" || { echo "self-test failed: the hook does not bind the CI rule by the remote URL"; fails=1; }
fl=$(printf 'refs/heads/master %s refs/heads/master %s\n' 1111111111111111111111111111111111111111 2222222222222222222222222222222222222222)
bl=$(printf 'refs/heads/x %s refs/heads/x %s\n' 1111111111111111111111111111111111111111 2222222222222222222222222222222222222222)
out=$(IGNEUM_GITHUB_SUSPENDED_FILE="$mk" forgejo_master_frozen ssh://git@git.igneum.network:2222/igneum-network/igneum.git "$fl" 2>&1) || { echo "self-test failed: a landing on the public host's master was not refused under the marker"; fails=1; }
case "$out" in *"REFUSED"*"--remote box"*) ;; *) echo "self-test failed: the public-host refusal did not name the box route: $out"; fails=1 ;; esac
( IGNEUM_GITHUB_SUSPENDED_FILE="$mk" forgejo_master_frozen ssh://git@git.igneum.network:2222/igneum-network/igneum.git "$bl" >/dev/null 2>&1 ) && { echo "self-test failed: a branch pushed to the public host for safekeeping was refused"; fails=1; }
( IGNEUM_GITHUB_SUSPENDED_FILE="$mk.none" forgejo_master_frozen ssh://git@git.igneum.network:2222/igneum-network/igneum.git "$fl" >/dev/null 2>&1 ) && { echo "self-test failed: the public host's master was refused without the marker"; fails=1; }
( IGNEUM_GITHUB_SUSPENDED_FILE="$mk" forgejo_master_frozen ssh://build@188.40.146.49/srv/igneum.git "$fl" >/dev/null 2>&1 ) && { echo "self-test failed: a landing on the box mirror's master was refused as the public host"; fails=1; }
grep -qE 'forgejo_master_frozen "\$URL" "\$REFS" && exit 1' "$0" || { echo "self-test failed: the hook does not refuse a landing on the public host's master under the marker"; fails=1; }
# the hook itself, driven by remote NAME as git drives it, in a fixture repo: a GitHub name and the public host's name are refused
# before any check runs; the box mirror's name is not (it reaches the gate, which is cut short by an empty ref list)
hx=$(mktemp -d); git -C "$hx" init -q && git -C "$hx" -c user.name=t -c user.email=t@t commit -q --allow-empty -m one
git -C "$hx" remote add origin https://github.com/igneum-network/igneum.git; git -C "$hx" remote add pub ssh://git@git.igneum.network:2222/igneum-network/igneum.git; git -C "$hx" remote add box ssh://build@188.40.146.49/srv/igneum.git
hsha=$(git -C "$hx" rev-parse HEAD)
out=$(cd "$hx" && printf 'refs/heads/master %s refs/heads/master %s\n' "$hsha" "$hsha" | IGNEUM_GITHUB_SUSPENDED_FILE="$mk" IGNEUM_GATE_NO_TITLE=1 bash "$GATE_ROOT/tools/ci/pre-push.sh" --hook origin 2>&1); rc=$?
{ [ "$rc" != 0 ] && case "$out" in *"REFUSED. GitHub is unreachable"*) true ;; *) false ;; esac; } || { echo "self-test failed: the hook let a push to the GitHub remote by NAME through under the marker (rc=$rc): $(printf '%s' "$out" | tail -2)"; fails=1; }
out=$(cd "$hx" && printf 'refs/heads/master %s refs/heads/master %s\n' "$hsha" "$hsha" | IGNEUM_GITHUB_SUSPENDED_FILE="$mk" IGNEUM_GATE_NO_TITLE=1 bash "$GATE_ROOT/tools/ci/pre-push.sh" --hook pub ssh://git@git.igneum.network:2222/igneum-network/igneum.git 2>&1); rc=$?
{ [ "$rc" != 0 ] && case "$out" in *"REFUSED. git.igneum.network"*) true ;; *) false ;; esac; } || { echo "self-test failed: the hook let a landing on the public host's master through under the marker (rc=$rc): $(printf '%s' "$out" | tail -2)"; fails=1; }
out=$(cd "$hx" && printf 'refs/heads/x %s refs/heads/x %s\n' "$hsha" "$hsha" | IGNEUM_GITHUB_SUSPENDED_FILE="$mk" IGNEUM_GATE_NO_TITLE=1 bash "$GATE_ROOT/tools/ci/pre-push.sh" --hook pub 2>&1); rc=$?
case "$out" in *"REFUSED"*) echo "self-test failed: a branch pushed to the public host by name for safekeeping was refused: $(printf '%s' "$out" | head -3)"; fails=1 ;; esac
rm -rf "$hx"
rm -f "$mk"
grep -qE 'github_suspended "\$\{2:-\}" && exit 1' "$0" || { echo "self-test failed: the hook does not refuse a GitHub push under the marker"; fails=1; }
master_rule_binds build@188.40.146.49:/srv/igneum.git >/dev/null && { echo "self-test failed: the CI rule bound a box mirror remote"; fails=1; }
( IGNEUM_MASTER_EXCEPTION="main, 7 Oct 2026 19:5x UK: GitHub suspended" master_rule_binds https://github.com/x/y.git >/dev/null ) && { echo "self-test failed: a declared exception did not lift the CI rule"; fails=1; }
out=$(IGNEUM_MASTER_EXCEPTION="ruling text" master_rule_binds https://github.com/x/y.git); case "$out" in *"EXCEPTION"*"ruling text"*) ;; *) echo "self-test failed: the exception was not printed with its ruling: $out"; fails=1 ;; esac
@ -335,8 +372,13 @@ FAKEGH
hook)
# gh's active account on this Mac is the stored Igneum entry, before any push (main's rule, 7 October 2026, 21:5x UK; tools/ci/gh-account-check.sh)
bash "$GATE_ROOT/tools/ci/gh-account-check.sh" || exit 1
github_suspended "${2:-}" && exit 1 # the suspension marker: no push to GitHub at all (8 October 2026, 03:39 UK: 21 polls of a 403 before a kill by pid)
# git hands the hook "<remote name> <url>"; the remote checks below read the URL, never the name (8 October 2026, 13:5x UK: with
# the name, "origin" matched no *github.com* and the GitHub refusal and the CI rule never bound a named push). A push to a bare
# URL arrives with the URL in both places; a name alone resolves through git remote get-url.
URL="${3:-}"; [ -n "$URL" ] || URL="$(git remote get-url "${2:-}" 2>/dev/null || printf '%s' "${2:-}")"
github_suspended "$URL" && exit 1 # the suspension marker: no push to GitHub at all (8 October 2026, 03:39 UK: 21 polls of a 403 before a kill by pid)
REFS="$(cat)"; which="$(printf '%s\n' "$REFS" | gated_refs)"
forgejo_master_frozen "$URL" "$REFS" && exit 1 # the public host's master takes no landing while the marker stands (8 October 2026, 13:34 UK)
if [ "$which" = full ]; then
# a merge of a green-stamped branch onto the exact remote tip goes through on the light gate (CI runs the full one)
verdict=""; while read -r lref lsha rref rsha; do case "$rref" in refs/heads/master|refs/heads/release-*) verdict=$(deferred_merge "$lsha" "$rsha"); break ;; esac; done <<<"$REFS"
@ -345,7 +387,7 @@ FAKEGH
# takes the local gate as before. IGNEUM_MASTER_EXCEPTION="<main's ruling>" lifts the CI rule for one push and is printed with
# the push (7 October 2026, 19:5x UK: the GitHub account suspended, lanes landing on the box mirror's master by main's ruling,
# the box gate stamp as the verdict; GitHub gets the fast-forward when it answers again).
if master_rule_binds "${2:-}"; then
if master_rule_binds "$URL"; then
while read -r lref lsha rref rsha; do
if [ "$rref" = refs/heads/master ] && [ "$lsha" != 0000000000000000000000000000000000000000 ]; then master_ci_ok "$lsha" "$rsha" || exit 1; fi
done <<<"$REFS"

View file

@ -20,10 +20,10 @@ while IFS= read -r f; do
if ! grep -qE 'taskkill /T /F' "$f"; then
echo "second-engine: $f starts an engine without ending its process tree (taskkill /T /F) at the end"; fail=1
fi
if grep -qE 'settings\.json|\.json' "$f" && grep -vE '^\s*#' "$f" | grep -qE 'Set-Content[^\n]*-Encoding +utf8'; then
if grep -qE 'settings\.json|\.json' "$f" && grep -qE 'Set-Content[^\n]*-Encoding +utf8' <<<"$(grep -vE '^\s*#' "$f")"; then
echo "second-engine: $f writes JSON with Set-Content -Encoding utf8 (a BOM the engine refuses: the copy read as defaults, no payout address, nothing mined); use [IO.File]::WriteAllText with UTF8Encoding(\$false)"; fail=1
fi
if grep -vE '^\s*#' "$f" | grep -E 'ConvertTo-Json' | grep -qE 'settings\.json|Set-Content|WriteAllText|Out-File'; then
if grep -qE 'settings\.json|Set-Content|WriteAllText|Out-File' <<<"$(grep -vE '^\s*#' "$f" | grep -E 'ConvertTo-Json' || true)"; then
echo "second-engine: $f rewrites settings.json through ConvertTo-Json (a lossy round trip: big integers become doubles and the engine reads the whole file as defaults; run 4, 6 October 2026); copy the file verbatim, the engine applies Settings::for_measurement under --sweep"; fail=1
fi
if ! grep -qE "IGNEUM_APP_NO_OTA *= *'1'" "$f"; then