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 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-07 21:03:29 +00:00
parent 0cdf15b17a
commit 2855f37858
4 changed files with 59 additions and 0 deletions

View file

@ -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 <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 |

52
tools/ci/gh-account-check.sh Executable file
View file

@ -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

View file

@ -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)"

View file

@ -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)