From 2855f37858f1355ac65e22979d99aaa7336177f4 Mon Sep 17 00:00:00 2001 From: igneum-labs <337424239+igneum-labs@users.noreply.github.com> Date: Wed, 7 Oct 2026 21:03:29 +0000 Subject: [PATCH] gh's active account on this Mac is the stored Igneum entry, checked before every push and every landing (main's rule, 7 October 2026, 21:5x UK) tools/ci/gh-account-check.sh reads `gh auth status` for the account marked active and refuses with the line when it is not the stored entry (~/.config/igneum/gh-user) or when none is active; a machine without gh or without the file skips with a line. Known-failed first: a fake gh whose active account is another login is refused and named; the stored one passes; no active account is refused; no gh skips. The pre-push hook runs it live before any push; merge-to-master.sh before any landing; the gate carries its self-test. The rule row in tools/ci/README.md: no lane switches gh accounts on this Mac, ever. At 21:41 UK a lane switched gh to the second owner's login during the suspension; no transcript or history on the Mac shows which; the check closes it. Co-Authored-By: Claude Fable 5.1 --- tools/ci/README.md | 2 ++ tools/ci/gh-account-check.sh | 52 ++++++++++++++++++++++++++++++++++++ tools/ci/merge-to-master.sh | 1 + tools/ci/pre-push.sh | 4 +++ 4 files changed, 59 insertions(+) create mode 100755 tools/ci/gh-account-check.sh diff --git a/tools/ci/README.md b/tools/ci/README.md index e8e368710..eedd894de 100644 --- a/tools/ci/README.md +++ b/tools/ci/README.md @@ -9,4 +9,6 @@ | a box or network check gets one retry (`retry-once.sh`) | Nothing by itself: wraps the box-locks check, the scene parity check and the live public API check so a first failure is printed and retried once; the second is the verdict. The checks keep their own skip line on a runner without the resource. | 7 October 2026 | | 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`) | A push or a landing from this Mac while `gh auth status` names any other account as active (or none). 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 | + | 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/gh-account-check.sh b/tools/ci/gh-account-check.sh new file mode 100755 index 000000000..376d14f21 --- /dev/null +++ b/tools/ci/gh-account-check.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# gh's ACTIVE account on this Mac is the stored Igneum entry and nothing else (main's rule, 7 October 2026, 21:5x UK: at 21:41 a lane +# switched gh to the second owner's login, which belongs to other projects and must never touch Igneum; nobody could say which lane). +# The stored entry's name is in ~/.config/igneum/gh-user (the login's pre-rename spelling until a re-login; never in the repository). +# Runs before a push (tools/ci/pre-push.sh --hook) and before a landing (tools/ci/merge-to-master.sh); refuses with the line otherwise. +# A machine without gh, or without the stored-name file, is not this Mac: skip with a line (CI runners, the boxes). +# +# tools/ci/gh-account-check.sh # exit 0 when gh's active account is the stored entry (or gh / the file is absent, with a skip line); exit 1 with the line otherwise +# tools/ci/gh-account-check.sh --self-test # a fake gh whose active account is another login is refused and named; the stored one passes; +# # a status with no active account is refused; no gh on PATH skips +set -uo pipefail +STORED_FILE="${IGNEUM_GH_USER_FILE:-$HOME/.config/igneum/gh-user}" + +active_account() { # from `gh auth status`: the account whose block carries "Active account: true" + gh auth status 2>&1 | awk ' + /account [^ ]+ \(/ { for (i = 1; i <= NF; i++) if ($i == "account") { acct = $(i + 1) } } + /Active account: true/ { print acct; exit }' +} +check() { + local stored active + command -v gh >/dev/null 2>&1 || { echo "gh-account: skipped, no gh on this machine (the rule binds the Mac that pushes)"; return 0; } + [ -s "$STORED_FILE" ] || { echo "gh-account: skipped, no stored-name file at $STORED_FILE (not the pushing Mac)"; return 0; } + stored="$(tr -d '[:space:]' < "$STORED_FILE")" + active="$(active_account)" + if [ -z "$active" ]; then echo "gh-account: REFUSED. gh has no active account (gh auth status); the Igneum rule: the stored entry, and only it, is active on this Mac: gh auth switch --user $stored" >&2; return 1; fi + if [ "$active" != "$stored" ]; then echo "gh-account: REFUSED. gh's active account is $active, not the stored Igneum entry; that login must never touch Igneum (rule of 7 October 2026). Run: gh auth switch --user $stored" >&2; return 1; fi + echo "gh-account: gh's active account is the stored Igneum entry" +} + +if [ "${1:-}" = --self-test ]; then + d="$(mktemp -d)"; trap 'rm -rf "$d"' EXIT + printf 'stored-login\n' > "$d/gh-user" + fake="$d/bin/gh"; mkdir -p "$d/bin" + cat > "$fake" <<'FAKE' +#!/usr/bin/env bash +# fake gh: the status text, with the active account named by $FAKE_ACTIVE (empty = none active) +printf 'github.com\n' +printf ' X Failed to log in to github.com account stored-login (keyring)\n - Active account: %s\n - Token: gho_x\n\n' "$([ "${FAKE_ACTIVE:-}" = stored-login ] && echo true || echo false)" +printf ' %s Logged in to github.com account other-login (keyring)\n - Active account: %s\n - Token: gho_y\n' "$([ "${FAKE_ACTIVE:-}" = other-login ] && echo '✓' || echo '✓')" "$([ "${FAKE_ACTIVE:-}" = other-login ] && echo true || echo false)" +FAKE + chmod +x "$fake"; fails=0 + out="$(PATH="$d/bin:$PATH" IGNEUM_GH_USER_FILE="$d/gh-user" FAKE_ACTIVE=other-login bash "$0" 2>&1)" && { echo "self-test failed: another active login was not refused"; fails=1; } + case "$out" in *"REFUSED. gh's active account is other-login"*) ;; *) echo "self-test failed: the refusal did not name the active login: $out"; fails=1 ;; esac + PATH="$d/bin:$PATH" IGNEUM_GH_USER_FILE="$d/gh-user" FAKE_ACTIVE=stored-login bash "$0" >/dev/null 2>&1 || { echo "self-test failed: the stored active login was refused"; fails=1; } + PATH="$d/bin:$PATH" IGNEUM_GH_USER_FILE="$d/gh-user" FAKE_ACTIVE= bash "$0" >/dev/null 2>&1 && { echo "self-test failed: no active account was let through"; fails=1; } + # a machine without gh: the system binaries on PATH, no gh + out="$(PATH="/usr/bin:/bin" IGNEUM_GH_USER_FILE="$d/gh-user" bash "$0" 2>&1)" || { echo "self-test failed: a machine without gh did not skip"; fails=1; } + case "$out" in *"skipped, no gh"*) ;; *) echo "self-test failed: no skip line without gh: $out"; fails=1 ;; esac + [ "$fails" = 0 ] && echo "self-test passed: another active login is refused and named, the stored one passes, no active account is refused, a machine without gh skips with its line" + exit $fails +fi +check diff --git a/tools/ci/merge-to-master.sh b/tools/ci/merge-to-master.sh index 9f39efab4..f94c5392b 100755 --- a/tools/ci/merge-to-master.sh +++ b/tools/ci/merge-to-master.sh @@ -138,6 +138,7 @@ success 4 u push run exit $fails fi [ -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) if [ ! -f "$G/igneum-gate-green/$SHA" ]; then echo "merge-to-master: no green stamp for ${SHA:0:8}; running the full gate on the branch first (then CI's own verdict on it is read)" diff --git a/tools/ci/pre-push.sh b/tools/ci/pre-push.sh index d401750cb..3d3176c65 100755 --- a/tools/ci/pre-push.sh +++ b/tools/ci/pre-push.sh @@ -144,6 +144,7 @@ tree_checks() { run "the public ledger (docs/ledger-public.md) is what docs/fud-ledger.md generates: one row per item, no commit ids, times or team names (self-test first)" bash -c 'node tools/ledger/export-public.mjs --self-test && node tools/ledger/export-public.mjs --check' run "every workflow job carries timeout-minutes (site 15, changes 10, pow 60, sims 45; the hung-job class of 7 October 2026)" bash tools/ci/workflow-timeouts-check.sh --self-test run "a box or network check gets one retry before it is red (retry-once self-test)" bash tools/ci/retry-once.sh --self-test + run "gh's active account on the pushing Mac is the stored Igneum entry (self-test: another login refused and named; the hook and the merge tool run the check live)" bash tools/ci/gh-account-check.sh --self-test run "CI state reader: a commit's newest run, master's last compiled run, a branch's last red (fake gh; the merge rule's reader)" node tools/ci/ci-state.mjs --self-test } @@ -257,6 +258,7 @@ case "$MODE" in grep -q 'wall_clock 600 node tools/ci/overlap-check.mjs' "$0" || { echo "self-test failed: the overlap sweep does not run under the wall clock"; fails=1; } # master takes only CI-passed commits: a merge asks about its second parent, a plain commit about itself; red, pending, none and unknown refuse grep -qE 'master_ci_ok "\$lsha" "\$rsha" \|\| exit 1' "$0" || { echo "self-test failed: the hook does not ask ci-state before a push to master"; fails=1; } + grep -qE 'tools/ci/gh-account-check.sh" \|\| exit 1' "$0" || { echo "self-test failed: the hook does not check gh's active account before a push"; fails=1; } grep -qE 'branch_red_line "\$\{rref#refs/heads/\}"' "$0" || { echo "self-test failed: the feature-branch hook does not print the branch's previous red"; fails=1; } fx=$(mktemp -d); ( cd "$fx" && git init -q -b master . && git -c user.name=t -c user.email=t@t commit -q --allow-empty -m a ) 2>/dev/null A=$(git -C "$fx" rev-parse HEAD); git -C "$fx" checkout -q -b b; git -C "$fx" -c user.name=t -c user.email=t@t commit -q --allow-empty -m b; B=$(git -C "$fx" rev-parse HEAD) @@ -285,6 +287,8 @@ FAKEGH list) grep -E '^\s+run "' "$0" | sed -E 's/^\s+run "([^"]+)".*/\1/' ;; 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 REFS="$(cat)"; which="$(printf '%s\n' "$REFS" | gated_refs)" 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)