diff --git a/tools/ci/README.md b/tools/ci/README.md index 2e15aace5..75e0f87ee 100644 --- a/tools/ci/README.md +++ b/tools/ci/README.md @@ -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 ` 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 `) 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 | diff --git a/tools/ci/gate-manifest-check.sh b/tools/ci/gate-manifest-check.sh index 1f8d83d32..0ece6b7fc 100755 --- a/tools/ci/gate-manifest-check.sh +++ b/tools/ci/gate-manifest-check.sh @@ -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() { # -> 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 diff --git a/tools/ci/merge-to-master.sh b/tools/ci/merge-to-master.sh index 54d5263ea..204f4157d 100755 --- a/tools/ci/merge-to-master.sh +++ b/tools/ci/merge-to-master.sh @@ -42,6 +42,14 @@ github_suspended_refusal() { # : 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() { # : 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() { # 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- (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) diff --git a/tools/ci/pc1-step-budget-check.sh b/tools/ci/pc1-step-budget-check.sh index e89436f99..055a3f31f 100755 --- a/tools/ci/pc1-step-budget-check.sh +++ b/tools/ci/pc1-step-budget-check.sh @@ -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 diff --git a/tools/ci/playbook-quit-check.sh b/tools/ci/playbook-quit-check.sh index bb7961652..556937ebe 100755 --- a/tools/ci/playbook-quit-check.sh +++ b/tools/ci/playbook-quit-check.sh @@ -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 )"; 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 diff --git a/tools/ci/pre-push.sh b/tools/ci/pre-push.sh index 48e3f533d..695938a82 100755 --- a/tools/ci/pre-push.sh +++ b/tools/ci/pre-push.sh @@ -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:-, 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 )"`), and @@ -227,6 +229,20 @@ github_suspended() { # : 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() { # : 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() { # : 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 " "; 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="" 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" diff --git a/tools/ci/second-engine-check.sh b/tools/ci/second-engine-check.sh index ec8e8a647..376712f9e 100755 --- a/tools/ci/second-engine-check.sh +++ b/tools/ci/second-engine-check.sh @@ -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