Merge scrub-3 71a2a5fe into master (gate: green on 71a2a5fe, recorded by tools/ci/pre-push.sh; landed on the box mirror under the exception declared by main: main's ruling, 7 Oct 2026 19:5x UK: the GitHub account is suspended, lanes land on the box mirror's master, the box gate stamp is the verdict; GitHub gets the fast-forward when it answers)
This commit is contained in:
commit
f66e66e73e
6 changed files with 263 additions and 10 deletions
|
|
@ -9,6 +9,8 @@
|
|||
| 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 |
|
||||
|
||||
## No inline deletion in a shell string (7 October 2026)
|
||||
|
|
|
|||
71
tools/ci/checks.txt
Normal file
71
tools/ci/checks.txt
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# every check tools/ci/pre-push.sh runs, one name per line (tools/ci/gate-manifest-check.sh --write regenerates it; commit it with the gate change)
|
||||
no lateral scroll: scrollWidth equals clientWidth on every route at five widths, both themes (self-test, then the site; skipped where there is no Playwright)
|
||||
no conflict markers in tracked files
|
||||
every tracked path is valid on Windows (colon, trailing dot, reserved names, length)
|
||||
identity grep of the public export list and the served site
|
||||
no secret file names and no 64-hex secrets in the tree
|
||||
the gate's manifest: every listed check has its run line and every run line is listed (never-push: a conflict resolution cannot drop a check)
|
||||
no founder name, personal login, earlier business or personal address in any tracked text file (the pre-public scrub; self-test first, encoded list)
|
||||
every check in tools/ci/checks.txt has its run line here and every run line is listed (a conflict resolution cannot drop a check unseen; self-test first)
|
||||
site build (in a temporary copy here, in place only inside GitHub Actions)
|
||||
internal link check of site/*.html
|
||||
every served page carries the slim bar (mark, Mine, Network, Learn, Download) with every route in its panels and the sheet (self-test, then the tree)
|
||||
vendor marks: site/lib/marks.mjs is brand/marks/vendor-marks.mjs byte for byte (the app and the site draw one set)
|
||||
the phone menu opens and is seen at 390 px on every page (self-test first; needs the box or CI browser, says so without one)
|
||||
padding and visuals: text 24 px from every band, card and section edge, section padding on the scale, no touching controls, media inside its frame, no heading under the bar, at 390 to 1600 px both themes (known-failed fixture first)
|
||||
ledger sentences present verbatim on their public pages
|
||||
shell inside .github/workflows parses (bash -n, the PowerShell 5.1 rule)
|
||||
PowerShell drive-reference check (\$name: in a double-quoted string)
|
||||
copied sources are re-stamped before a build
|
||||
override params files parse with no duplicate key
|
||||
second-engine playbooks log to a file and end their tree (C35)
|
||||
no playbook quits, pauses or resumes the installed app
|
||||
no script writes into another worktree or walks Projects
|
||||
the signer is never piped into head
|
||||
bash bodies in PowerShell job scripts pass bash -n
|
||||
run jobs test their fetched kit before use
|
||||
every Windows spawn of the app runs with a hidden console
|
||||
pinned guest programs match their manifest
|
||||
root prover playbooks kill the GPU server and unlink its socket
|
||||
commit-string gate self-test
|
||||
engine gate self-test (igneumd and igneum-miner must carry igneum-pow/src/ paths)
|
||||
build server remote checkout self-test
|
||||
a slot holder keeps its own line for the whole run (the watcher-trust rule)
|
||||
the remote checkout resets the mirror's tree before the branch checkout (the stale-overlay class)
|
||||
the remote checkout's clean spares a lane's scratch (.igneum-scratch-spare, the fixed prefixes, never -x; the lost-scratch class)
|
||||
long-running tools keep their body in one parsed block (the edited-while-running class)
|
||||
build-remote without a priority flag bounds suites and benches (nice 10, 32 cores); a gate runs unbounded
|
||||
the class router is a preference with spill-over (a held or overloaded box hands the job to the other one)
|
||||
per-core leases, the quiet class and the reaper pass on the box (lease.sh and remote-run.sh self-tests over ssh; skipped on a runner with no box; one retry)
|
||||
the simulators job runs on master and release-* pushes and pull requests into them only
|
||||
publish-jobs.sh never removes a running or installs-app job without --force (the PC 2 abort class)
|
||||
no shell assignment hides behind a trailing comment (the swallowed-defaults class)
|
||||
no script kills or finds a process by a plain name or a file name (pgrep/pkill -f literals, ps | grep)
|
||||
no deletion inside an inline bash -c / sh -c string in a tracked script (self-test first; the app cannot check it)
|
||||
the identity check's own self-test (excluded research path passes, exported leak fails)
|
||||
the Windows paths check's own self-test
|
||||
the red watcher's own self-test (one line per run, posted once)
|
||||
faucet unit tests
|
||||
redesign package data tests (the /api/live contract the pages read)
|
||||
the home hero's loop never idles in view, stops hidden, resumes without a jump
|
||||
chain scene: the site's and the app's copies are scene/ byte for byte, the palette tokens live once (self-test, then the tree)
|
||||
chain scene: a push paints with the document hidden and no animation frame (the blank /live of 7 Oct 2026; known-failed first)
|
||||
chain scene: the live feed contract (the recorded reply validates; a rewritten miner, a float now, a stray key refused)
|
||||
chain scene parity: one recorded feed through the home fold, /live and the app's Inspect view on build-2, three frames each pixel-equal apart from the app's own-key overlay (a changed token fails first; skipped with no box and no Playwright; one retry)
|
||||
no text overlaps: every served page at 390 to 1600 px, light and dark, the hero at each step (self-test first; IGNEUM_OVERLAP_APPS=1 adds the miner and wallet UIs)
|
||||
explorer, emission and public stats unit tests
|
||||
ship tool self-test
|
||||
relay unit tests
|
||||
miner app notice strip and update card tests
|
||||
launch gates: every row with its check, the handoff text clean (self-test, then the tree)
|
||||
income per tier: the public table equals its inputs, the schedule arithmetic
|
||||
hash-origin report: a known-finished day and a known-failed day
|
||||
harness summaries never carry a raw 64-hex key (the writer's own redaction and check)
|
||||
docs-only pushes skip the compile-or-compute CI jobs (the changes job's classifier)
|
||||
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)
|
||||
every workflow job carries timeout-minutes (site 15, changes 10, pow 60, sims 45; the hung-job class of 7 October 2026)
|
||||
a box or network check gets one retry before it is red (retry-once self-test)
|
||||
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)
|
||||
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)
|
||||
known failure
|
||||
known success
|
||||
39
tools/ci/gate-manifest-check.sh
Executable file
39
tools/ci/gate-manifest-check.sh
Executable file
|
|
@ -0,0 +1,39 @@
|
|||
#!/usr/bin/env bash
|
||||
# The gate's manifest: tools/ci/checks.txt names every check tools/ci/pre-push.sh runs (one name per line, the text inside run "..."),
|
||||
# and this check fails when a listed name has no run line in pre-push.sh or a run line carries a name the list lacks. Both ways, so a
|
||||
# conflict resolution that takes an old copy of pre-push.sh (7 October 2026, 21:43 UK: 88 lines of checks and functions gone from the
|
||||
# mirror's master for 40 minutes) and a new check added without its manifest line are both red. It runs in the never-push set: every
|
||||
# push on every branch, before the merge's own push too. Adding or removing a check means editing checks.txt in the same commit,
|
||||
# which is the point: a check leaves the gate only on purpose and in the diff.
|
||||
#
|
||||
# tools/ci/gate-manifest-check.sh # exit 1 naming each missing run line or unlisted check
|
||||
# tools/ci/gate-manifest-check.sh --self-test # a fixture gate missing one listed check fails and names it; one with an unlisted
|
||||
# # run line fails and names it; the real tree passes
|
||||
# tools/ci/gate-manifest-check.sh --write # rewrite checks.txt from the current pre-push.sh (then commit both together)
|
||||
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")
|
||||
return $rc
|
||||
}
|
||||
case "${1:-}" in
|
||||
--write) { echo "# every check tools/ci/pre-push.sh runs, one name per line (tools/ci/gate-manifest-check.sh --write regenerates it; commit it with the gate change)"; names_in "$HERE/pre-push.sh"; } > "$HERE/checks.txt"; echo "gate-manifest: wrote $HERE/checks.txt ($(grep -vc '^#' "$HERE/checks.txt") checks)"; exit 0 ;;
|
||||
--self-test)
|
||||
d="$(mktemp -d)"; trap 'rm -rf "$d"' EXIT; fails=0
|
||||
printf ' run "alpha check" true\n run "beta check" true\n' > "$d/gate.sh"; printf '# m\nalpha check\nbeta check\n' > "$d/m.txt"
|
||||
compare "$d/gate.sh" "$d/m.txt" >/dev/null 2>&1 || { echo "self-test failed: a matching gate and manifest were reported"; fails=1; }
|
||||
printf ' run "alpha check" true\n' > "$d/gate2.sh"
|
||||
out="$(compare "$d/gate2.sh" "$d/m.txt" 2>&1)" && { echo "self-test failed: a dropped check passed"; fails=1; }
|
||||
case "$out" in *"no run line"*"beta check"*) ;; *) echo "self-test failed: the dropped check was not named: $out"; fails=1 ;; esac
|
||||
printf ' run "alpha check" true\n run "beta check" true\n run "gamma check" true\n' > "$d/gate3.sh"
|
||||
out="$(compare "$d/gate3.sh" "$d/m.txt" 2>&1)" && { echo "self-test failed: an unlisted check passed"; fails=1; }
|
||||
case "$out" in *"not in"*"gamma check"*) ;; *) echo "self-test failed: the unlisted check was not named: $out"; fails=1 ;; esac
|
||||
compare "$HERE/pre-push.sh" "$HERE/checks.txt" >/dev/null 2>&1 || { echo "self-test failed: the tree's gate and manifest disagree (run tools/ci/gate-manifest-check.sh --write and commit)"; fails=1; }
|
||||
[ "$fails" = 0 ] && echo "self-test passed: a dropped check and an unlisted check are each red and named; the tree's gate matches its manifest"
|
||||
exit $fails ;;
|
||||
"") compare "$HERE/pre-push.sh" "$HERE/checks.txt" && echo "gate-manifest: every one of $(grep -vc '^#' "$HERE/checks.txt") listed checks has its run line and every run line is listed" ;;
|
||||
*) echo "usage: tools/ci/gate-manifest-check.sh [--self-test|--write]" >&2; exit 2 ;;
|
||||
esac
|
||||
52
tools/ci/gh-account-check.sh
Executable file
52
tools/ci/gh-account-check.sh
Executable 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
|
||||
|
|
@ -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)"
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ 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)" # the readers below are called from fixture repositories in the self-test, so by absolute path
|
||||
RED=0; N=0; LOG="$(mktemp)"; trap 'rm -rf "$LOG" "${SITE_TMP:-}"' EXIT
|
||||
T0=$(date +%s)
|
||||
|
||||
|
|
@ -52,12 +53,20 @@ site_build() {
|
|||
(cd "$SITE_TMP/site" && SITE_DOWNLOADS_OFFLINE=1 node build.mjs)
|
||||
}
|
||||
|
||||
wall_clock() { # <secs> <command...>: under GNU timeout where it exists (the runners); the Mac has none, and the job's timeout-minutes is the stop there.
|
||||
# No array here: an empty array expanded under set -u is "unbound variable" on the Mac's bash 3.2 and ended the gate with no RED line (17:3x UK, 7 October 2026).
|
||||
local secs="$1"; shift
|
||||
if command -v timeout >/dev/null 2>&1; then timeout "$secs" "$@"; else "$@"; fi
|
||||
}
|
||||
overlap_sweep() {
|
||||
run "no lateral scroll: scrollWidth equals clientWidth on every route at five widths, both themes (self-test, then the site; skipped where there is no Playwright)" bash -c 'node tools/ci/scroll-width-check.mjs --self-test && node tools/ci/scroll-width-check.mjs --site site'
|
||||
# tools/ci/overlap-check.mjs: the known-failed fixture first, then the built site (the gate's temporary copy locally, the tree in
|
||||
# CI). A browser is needed: CI installs Playwright in the workflow; a machine without one ships the pages to a build box
|
||||
# (nothing heavy on the Mac). IGNEUM_OVERLAP_APPS=1 adds the miner and wallet UIs through their mocks (slower, the box).
|
||||
local dir="site"; [ "$MODE" = ci ] || dir="$SITE_TMP/site"
|
||||
if [ "${IGNEUM_OVERLAP_APPS:-0}" = 1 ]; then node tools/ci/overlap-check.mjs --self-test --site "$dir" --apps .; else node tools/ci/overlap-check.mjs --self-test --site "$dir"; fi
|
||||
# a wall clock of 10 minutes where GNU timeout exists (the runners; the Mac ships the sweep to a box): the sweep took 192 s on a hosted
|
||||
# runner on 7 October 2026 and three master jobs hung in this step for over two hours each the same afternoon
|
||||
if [ "${IGNEUM_OVERLAP_APPS:-0}" = 1 ]; then wall_clock 600 node tools/ci/overlap-check.mjs --self-test --site "$dir" --apps .; else wall_clock 600 node tools/ci/overlap-check.mjs --self-test --site "$dir"; fi
|
||||
}
|
||||
|
||||
structural_checks() {
|
||||
|
|
@ -71,11 +80,14 @@ never_push_checks() {
|
|||
# A secret or an identity leak must not reach the remote on any branch; together about 20 s on the Mac.
|
||||
run "identity grep of the public export list and the served site" bash tools/ci/identity-check.sh
|
||||
run "no secret file names and no 64-hex secrets in the tree" bash -c 'bash tools/ci/no-secrets-check.sh --self-test && bash tools/ci/no-secrets-check.sh'
|
||||
run "the gate's manifest: every listed check has its run line and every run line is listed (never-push: a conflict resolution cannot drop a check)" bash tools/ci/gate-manifest-check.sh
|
||||
# the third never-push class (7 October 2026, 20:5x UK): a founder name on ANY branch, because every branch went to the public host's
|
||||
# mirror and a branch green-stamped before the check existed carried one onto master through the deferred merge
|
||||
run "no founder name, personal login, earlier business or personal address in any tracked text file (the pre-public scrub; self-test first, encoded list)" bash -c 'bash tools/ci/founder-strings-check.sh --self-test && bash tools/ci/founder-strings-check.sh'
|
||||
# rule 15 (7 October 2026): a release branch reads its own version in Cargo.toml, Cargo.lock and version.h from its first commit
|
||||
run "release-version: a release-0.3.N branch reads 0.3.N in Cargo.toml, Cargo.lock and version.h (self-test first)" bash -c 'bash tools/ci/release-version-check.sh --self-test && bash tools/ci/release-version-check.sh'
|
||||
# the gate's own manifest (7 October 2026, 22:2x UK): a merge on the mirror resolved a pre-push.sh conflict by taking an old copy and 88 lines of
|
||||
# checks and functions left master's gate unseen for 40 minutes; every check name is listed in tools/ci/checks.txt and a name without its run
|
||||
# line, or a run line without its name, is red on every push
|
||||
run "every check in tools/ci/checks.txt has its run line here and every run line is listed (a conflict resolution cannot drop a check unseen; self-test first)" bash -c 'bash tools/ci/gate-manifest-check.sh --self-test && bash tools/ci/gate-manifest-check.sh'
|
||||
}
|
||||
|
||||
tree_checks() {
|
||||
|
|
@ -109,12 +121,12 @@ tree_checks() {
|
|||
run "long-running tools keep their body in one parsed block (the edited-while-running class)" bash -c 'bash tools/ci/whole-body-check.sh --self-test && bash tools/ci/whole-body-check.sh'
|
||||
run "build-remote without a priority flag bounds suites and benches (nice 10, 32 cores); a gate runs unbounded" bash tools/ci/build-kind-default-check.sh
|
||||
run "the class router is a preference with spill-over (a held or overloaded box hands the job to the other one)" bash tools/ci/route-spill-check.sh
|
||||
run "no deletion inside an inline bash -c / sh -c string in a tracked script (self-test first; the app cannot check it)" bash tools/ci/inline-rm-check.sh --self-test
|
||||
run "per-core leases, the quiet class and the reaper pass on the box (lease.sh and remote-run.sh self-tests over ssh)" bash tools/ci/box-locks-check.sh $( [ "$MODE" = ci ] && echo --ci )
|
||||
run "per-core leases, the quiet class and the reaper pass on the box (lease.sh and remote-run.sh self-tests over ssh; skipped on a runner with no box; one retry)" bash tools/ci/retry-once.sh box-locks bash tools/ci/box-locks-check.sh $( [ "$MODE" = ci ] && echo --ci )
|
||||
run "the simulators job runs on master and release-* pushes and pull requests into them only" bash tools/ci/sims-branch-check.sh
|
||||
run "publish-jobs.sh never removes a running or installs-app job without --force (the PC 2 abort class)" bash tools/ci/publish-jobs-check.sh
|
||||
run "no shell assignment hides behind a trailing comment (the swallowed-defaults class)" bash -c 'bash tools/ci/defaults-line-check.sh --self-test && bash tools/ci/defaults-line-check.sh'
|
||||
run "no script kills or finds a process by a plain name or a file name (pgrep/pkill -f literals, ps | grep)" bash -c 'bash tools/ci/kill-by-name-check.sh --self-test && bash tools/ci/kill-by-name-check.sh'
|
||||
run "no deletion inside an inline bash -c / sh -c string in a tracked script (self-test first; the app cannot check it)" bash tools/ci/inline-rm-check.sh --self-test
|
||||
run "the identity check's own self-test (excluded research path passes, exported leak fails)" bash tools/ci/identity-check.sh --self-test
|
||||
run "the Windows paths check's own self-test" bash tools/ci/windows-paths-check.sh --self-test
|
||||
run "the red watcher's own self-test (one line per run, posted once)" node tools/ci/red-watch.mjs --self-test
|
||||
|
|
@ -123,18 +135,23 @@ tree_checks() {
|
|||
run "the home hero's loop never idles in view, stops hidden, resumes without a jump" node tools/site-redesign/tests/hero-loop-test.cjs
|
||||
run "chain scene: the site's and the app's copies are scene/ byte for byte, the palette tokens live once (self-test, then the tree)" bash -c 'node tools/scene/sync.mjs --self-test && node tools/scene/sync.mjs --check'
|
||||
run "chain scene: a push paints with the document hidden and no animation frame (the blank /live of 7 Oct 2026; known-failed first)" node tools/scene/paint-test.cjs
|
||||
run "chain scene: the live feed contract (the recorded reply validates; a rewritten miner, a float now, a stray key refused)" node --test tools/scene/feed-contract.test.mjs
|
||||
run "chain scene parity: one recorded feed through the home fold, /live and the app's Inspect view on build-2, three frames each pixel-equal apart from the app's own-key overlay (a changed token fails first; skipped with no box and no Playwright)" bash tools/scene/parity-remote.sh
|
||||
run "chain scene: the live feed contract (the recorded reply validates; a rewritten miner, a float now, a stray key refused)" node --test tools/scene/feed-contract.test.mjs tools/scene/legend.test.mjs tools/scene/shard-words.test.mjs
|
||||
run "chain scene parity: one recorded feed through the home fold, /live and the app's Inspect view on build-2, three frames each pixel-equal apart from the app's own-key overlay (a changed token fails first; skipped with no box and no Playwright; one retry)" bash tools/ci/retry-once.sh scene-parity bash tools/scene/parity-remote.sh
|
||||
run "no text overlaps: every served page at 390 to 1600 px, light and dark, the hero at each step (self-test first; IGNEUM_OVERLAP_APPS=1 adds the miner and wallet UIs)" overlap_sweep
|
||||
run "explorer, emission and public stats unit tests" node --test site/lib/explorer.test.mjs site/lib/emission.test.mjs site/lib/money.test.mjs site/api/public-stats.test.mjs
|
||||
run "explorer, emission and public stats unit tests" node --test site/lib/explorer.test.mjs site/lib/emission.test.mjs site/lib/money.test.mjs site/lib/leaderboard.test.mjs site/api/public-stats.test.mjs
|
||||
run "ship tool self-test" node tools/ship-app.mjs --self-test
|
||||
run "relay unit tests" node --test relay/test/parse.test.mjs relay/test/auth.test.mjs relay/test/wake.test.mjs relay/test/ember.test.mjs
|
||||
run "miner app notice strip and update card tests" node --test app/igneum-app/ui/notices.test.mjs app/igneum-app/ui/update-card.test.mjs app/igneum-app/ui/view.test.mjs app/igneum-app/ui/tune-line.test.mjs
|
||||
run "launch gates: every row with its check, the handoff text clean (self-test, then the tree)" bash -c 'node tools/ci/launch-gates-check.mjs --self-test && node tools/ci/launch-gates-check.mjs'
|
||||
run "income per tier: the public table equals its inputs, the schedule arithmetic" bash -c 'node tools/launch/income-tiers.mjs --check && node --test tools/launch/income-tiers.test.mjs'
|
||||
run "income per tier: the public table equals its inputs, the schedule arithmetic" bash -c 'node tools/launch/income-tiers.mjs --check && node --test tools/launch/income-tiers.test.mjs && node tools/launch/income-page.mjs --check'
|
||||
run "hash-origin report: a known-finished day and a known-failed day" node --test tools/observer/hash-origin.test.mjs
|
||||
run "harness summaries never carry a raw 64-hex key (the writer's own redaction and check)" node infra/fast-time/lib/redact-keys.mjs --self-test
|
||||
run "docs-only pushes skip the compile-or-compute CI jobs (the changes job's classifier)" bash tools/ci/docs-only-check.sh --self-test
|
||||
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
|
||||
}
|
||||
|
||||
gated_refs() {
|
||||
|
|
@ -170,6 +187,33 @@ deferred_merge() { # <local sha> <remote sha> [repo dir] -> "defer <branch sha
|
|||
age=$(( $(date +%s) - $(stat -f %m "$f" 2>/dev/null || stat -c %Y "$f") )); [ "$age" -le 43200 ] || { echo "full the stamp for ${p2:0:8} is $age s old"; return; }
|
||||
echo "defer $p2"
|
||||
}
|
||||
# Master takes only what CI has already passed (standing rule, 7 October 2026, 17:2x UK: era-vdf's tip 0e2d6b1c was merged with
|
||||
# no ci run at all and master's igneum-pow suite stayed red for 40 minutes under docs-only merges). For a push to master the hook
|
||||
# asks tools/ci/ci-state.mjs: a two-parent merge needs a green run on its SECOND parent (the branch's own run on the exact
|
||||
# commit), a plain commit needs a green run on itself (a fast-forward of a branch CI passed); anything else is refused with the
|
||||
# run's state, and the lane uses tools/ci/merge-to-master.sh, which waits for a queued run. gh unreachable = refused (unknown).
|
||||
# release-* branches keep the full local gate alone (the shipper's cuts carry their own box suite line).
|
||||
master_ci_ok() { # <local sha> <remote sha> -> 0 and a line, or 1 and the reason
|
||||
local lsha="$1" rsha="$2" parents p2 want line state
|
||||
parents=$(git rev-list --parents -n 1 "$lsha" 2>/dev/null | cut -d' ' -f2-); set -- $parents
|
||||
if [ -n "${2:-}" ] && [ -z "${3:-}" ] && [ "$1" = "$rsha" ]; then want="$2"; else want="$lsha"; fi
|
||||
line=$(node "$GATE_ROOT/tools/ci/ci-state.mjs" "$want" 2>&1); state="${line%% *}"
|
||||
if [ "$state" = success ]; then echo " master takes ${want:0:8}: ci $line"; return 0; fi
|
||||
echo "pre-push gate: REFUSED. master takes only a commit whose own ci run is green on that exact commit; ${want:0:8} is: $line" >&2
|
||||
echo " Use tools/ci/merge-to-master.sh (it pushes the branch for a run when there is none, waits for a queued run and refuses a red)." >&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
|
||||
case "$url" in *github.com*) return 0 ;; esac
|
||||
echo " the remote ${url:-?} is not GitHub (a mirror): the CI rule binds GitHub's master; the local gate is the verdict here"; return 1
|
||||
}
|
||||
branch_red_line() { # <branch>: the branch's newest completed ci run, when red, printed before the light gate (nothing on green or no gh)
|
||||
local line; line=$(node "$GATE_ROOT/tools/ci/ci-state.mjs" --branch-red "$1" 2>/dev/null) || return 0
|
||||
case "$line" in previous\ CI\ red*) echo " $line" ;; esac
|
||||
return 0
|
||||
}
|
||||
finish() {
|
||||
local what="$1" secs=$(( $(date +%s) - T0 ))
|
||||
if [ "$RED" = 0 ]; then echo "pre-push gate ($what): GREEN, $N checks in ${secs}s"; [ "${STAMP:-0}" = 1 ] && stamp_green; exit 0; fi
|
||||
|
|
@ -193,6 +237,7 @@ case "$MODE" in
|
|||
# the light gate carries the two never-push classes beside the structural checks, and the full gate runs them too
|
||||
declare -f never_push_checks | grep -q 'tools/ci/no-secrets-check.sh' || { echo "self-test failed: the never-push checks do not run the no-secrets check"; fails=1; }
|
||||
declare -f never_push_checks | grep -q 'tools/ci/identity-check.sh' || { echo "self-test failed: the never-push checks do not run the identity grep"; fails=1; }
|
||||
declare -f never_push_checks | grep -q 'tools/ci/founder-strings-check.sh' || { echo "self-test failed: the never-push checks do not run the founder-strings check"; fails=1; }
|
||||
grep -qE '^\s+structural_checks; never_push_checks; finish "feature branch"' "$0" || { echo "self-test failed: the hook's light gate does not run the never-push checks"; fails=1; }
|
||||
# the green stamp and the deferral, in a fixture repository: a merge of a stamped branch onto the remote tip defers; an
|
||||
# unstamped branch, a stale stamp, a merge onto an older tip and a plain commit all take the full gate
|
||||
|
|
@ -213,15 +258,57 @@ case "$MODE" in
|
|||
MODE=ci GITHUB_ACTIONS= site_in_place && { echo "self-test failed: --ci outside GitHub Actions chose the in-place build"; fails=1; }
|
||||
MODE=ci GITHUB_ACTIONS=true site_in_place || { echo "self-test failed: --ci inside GitHub Actions did not choose the in-place build"; fails=1; }
|
||||
declare -f tree_checks | grep -q 'never_push_checks' || { echo "self-test failed: the full gate does not run the never-push checks"; fails=1; }
|
||||
[ "$fails" = 0 ] && echo "self-test passed: a failing check is RED and fails the gate, a passing one is ok; master and release-* select the full gate, other refs the light one; a merge of a green-stamped branch onto the remote tip defers to CI, every other shape takes the full gate; a --ci site build outside GitHub Actions leaves the tree unchanged (structural checks, no-secrets, identity grep)"
|
||||
# the overlap sweep's wall clock runs the command with GNU timeout where it exists and plainly where it does not (bash 3.2 under set -u included)
|
||||
[ "$(wall_clock 5 /bin/echo clocked 2>&1)" = clocked ] || { echo "self-test failed: wall_clock did not run its command"; fails=1; }
|
||||
[ "$(PATH=/nonexistent wall_clock 5 /bin/echo plain 2>&1)" = plain ] || { echo "self-test failed: wall_clock without a timeout binary did not run its command plainly"; fails=1; }
|
||||
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)
|
||||
git -C "$fx" checkout -q master; git -C "$fx" -c user.name=t -c user.email=t@t merge -q --no-ff -m "merge b" b; M=$(git -C "$fx" rev-parse HEAD)
|
||||
fakebin=$(mktemp -d)
|
||||
cat > "$fakebin/gh" <<FAKEGH
|
||||
#!/usr/bin/env bash
|
||||
# the fake gh of the gate's self-test: a green run on the branch commit, a queued run on the merge commit, nothing elsewhere
|
||||
prev=""; key=""; for a in "\$@"; do [ "\$prev" = --commit ] && key="\$a"; prev="\$a"; done
|
||||
row() { printf '[{"databaseId":%s,"status":"%s","conclusion":%s,"headSha":"%s","url":"u","createdAt":"2026-10-07T15:00:00Z","event":"push"}]\\n' "\$1" "\$2" "\$3" "\$key"; }
|
||||
case "\$key" in $B) row 1 completed '"success"' ;; $M) row 2 queued null ;; *) echo "[]" ;; esac
|
||||
FAKEGH
|
||||
chmod +x "$fakebin/gh"
|
||||
( cd "$fx" && PATH="$fakebin:$PATH" master_ci_ok "$M" "$A" >/dev/null 2>&1 ) || { echo "self-test failed: a merge whose branch parent has a green run was refused"; fails=1; }
|
||||
( cd "$fx" && PATH="$fakebin:$PATH" master_ci_ok "$B" "$A" >/dev/null 2>&1 ) || { echo "self-test failed: a plain commit with its own green run was refused"; fails=1; }
|
||||
( cd "$fx" && PATH="$fakebin:$PATH" master_ci_ok "$M" "$B" >/dev/null 2>&1 ) && { echo "self-test failed: a merge onto another tip (its own run queued) was let through"; fails=1; }
|
||||
( cd "$fx" && PATH="$fakebin:$PATH" master_ci_ok "$A" "$A" >/dev/null 2>&1 ) && { echo "self-test failed: a commit with no ci run was let through to master"; fails=1; }
|
||||
rm -rf "$fx" "$fakebin"
|
||||
# the CI rule binds a GitHub remote; a mirror remote and a declared exception take the local gate, each with a printed line
|
||||
master_rule_binds https://github.com/igneum-network/igneum.git >/dev/null || { echo "self-test failed: the CI rule did not bind a GitHub remote"; 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
|
||||
[ "$fails" = 0 ] && echo "self-test passed: a failing check is RED and fails the gate, a passing one is ok; master and release-* select the full gate, other refs the light one; a merge of a green-stamped branch onto the remote tip defers to CI, every other shape takes the full gate; master takes only a commit (or a merge's branch parent) whose own ci run is green, and refuses red, queued and unrun ones (GitHub remotes; a mirror remote or a declared exception takes the local gate, printed); a feature-branch push prints the branch's previous red first; a --ci site build outside GitHub Actions leaves the tree unchanged (structural checks, no-secrets, identity grep)"
|
||||
exit $fails ;;
|
||||
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)
|
||||
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"
|
||||
# a push to master: the pushed commit (or its branch parent) must already have a green ci run on that exact commit. CI runs on
|
||||
# GitHub, so the rule binds a push whose remote is github.com; a push of master to a box mirror (build@<box>:/srv/igneum.git)
|
||||
# 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
|
||||
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"
|
||||
fi
|
||||
case "$verdict" in
|
||||
defer*) echo "pre-push gate: a merge of green-stamped ${verdict#defer } onto the remote tip: the light gate here, the full gate in CI on landing:"
|
||||
structural_checks; never_push_checks; finish "merge of a green branch (full gate deferred to CI)" ;;
|
||||
|
|
@ -230,6 +317,7 @@ case "$MODE" in
|
|||
esac
|
||||
else
|
||||
echo "pre-push gate: a feature branch, the light gate (the two structural checks, the no-secrets check, the identity grep):"
|
||||
while read -r lref lsha rref rsha; do case "$rref" in refs/heads/*) branch_red_line "${rref#refs/heads/}" ;; esac; done <<<"$REFS"
|
||||
structural_checks; never_push_checks; finish "feature branch"
|
||||
fi ;;
|
||||
ci|local)
|
||||
|
|
|
|||
Loading…
Reference in a new issue