the project lead: "a site now what is on par with our current miner and wallet app look and feel with the new animation ... simple and
beautiful ... apple and windows icons everywhere needed, GPU brand icons ... easy access to all of our pages without a header
that is crazy".
- site.css section 10: the miner and wallet apps' treatments (app.css miner-ui-5, wallet-0.1.5) over the package: buttons
(Plex Sans 600, radius 10), the status pill, graphite cards at 16 and rows at 12 on the hairline, the wallet lock screen's
ember glow, the mark wells; the fourteen palette tokens stay in the scene-tokens block (scene/tokens.css); light and dark.
- Navigation: the seven-item header is gone. The slim bar carries the mark, a DEVNET pill with the live dot, Mine / Network /
Learn (each opens a panel listing every route of its group with one line), the theme toggle and Download; on phones one
button opens a full-screen sheet with the same groups. Keyboard: arrows between groups, Escape closes and returns focus,
Tab walks a panel, the sheet is a dialog with its focus kept. The gate's header check is the new rule
(tools/ci/site-nav-check.mjs: the bar, the three groups with their aria, 18 routes in the panels and the sheet with their
descriptions, Download, the burger; self-test known-failed first).
- Home: one fold. The mark on the ember glow, one line, Download for Windows and Download for macOS with the OS glyphs and the
stamped version (the visitor's platform first), "Linux and HiveOS" to /download, IgneumDag 2.0.3 painting underneath from
/api/live through IgneumFeed (window 60, fps 60, poll false, no fork). Below: three lines on what a miner gets (E5 and X35
verbatim), the live network in three tiles (hash rate, blocks a second, locked checkpoint; X2 and X31 verbatim), the
community row (Discord, GitHub, Reddit), the footer (X7). The scene scripts are deferred; fonts preload with swap.
- /download: a new route. Windows, macOS, Linux and HiveOS cards with their marks, the stamped version, size and sha256, the
HiveOS flight sheet, the three steps, the card picker with the vendor badge, the wallet for macOS; Windows wallet named as
next with no file.
- Marks: brand/marks/vendor-marks.mjs (gpu-logos lane, a94dd192) copied byte for byte to site/lib/marks.mjs (the build and a
gate line refuse drift); site/lib/os-marks.mjs adds Linux and HiveOS in the same treatment. The build stamps
<span data-os> and <span data-gpu> and puts the vendor's mini badge in front of every table cell that names a card
(/miner, /miners, the bench table), writes the --mark-* tokens into site.css between markers, and hands yourcard.js the
badges as JSON for the card picker. The vendor well is scoped to [data-mark] so the ledger's and the journey's own
.badge keep their look.
- The footer carries the four OS download chips; the nav's group wrappers are .nav-group (the wallet page owns .group).
- tools/site/serve.mjs and capture.sh: the site as Vercel serves it with /api passed to the live observer, captured on
igneum-build-2 at 390 and 1440, dark and light (docs/plans/site-ui-5-shots, the home set in this commit). The overlap
lane's detector ran over every page on build-2: 0 overlaps after the two fixes here (the explorer's hash titles wrap, the
ledger's status badges wrap at 390).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
165 lines
15 KiB
Bash
Executable file
165 lines
15 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# The one gate. Every fast tree check CI runs, in one script, so the local gate and CI can never drift: the `site` job
|
|
# of .github/workflows/ci.yml calls `tools/ci/pre-push.sh --ci`, and the pre-push hook (tools/ci/install-hooks.sh) calls
|
|
# `tools/ci/pre-push.sh --hook` before any push to master or a release-* branch and refuses the push on red.
|
|
#
|
|
# tools/ci/pre-push.sh # the full gate over this working tree (any worktree; reads the tree, writes nothing in it)
|
|
# tools/ci/pre-push.sh --ci # the same, with the site built in place (a CI checkout is disposable)
|
|
# tools/ci/pre-push.sh --hook # from .git/hooks/pre-push: full gate for master and release-*; the light gate for every
|
|
# # other ref: the two structural checks (conflict markers, Windows paths) and the two
|
|
# # never-push classes (the no-secrets check, the identity grep), about 20 s on the Mac
|
|
# tools/ci/pre-push.sh --self-test # the runner fires on a known failure, passes a known success, the hook picks the
|
|
# # right gate from the ref lines, and the light gate carries the never-push classes
|
|
# tools/ci/pre-push.sh --list # the check names, one per line
|
|
#
|
|
# What is NOT here, on purpose: the three compile-or-compute jobs (igneum-pow tests, igneum-census, the simulators) and
|
|
# the live public API check (master only, a network call), which stay separate steps in ci.yml. Everything here finished
|
|
# in under 25 s on the Mac on 6 October 2026 (no-secrets 10 s, everything else under 3 s each).
|
|
#
|
|
# Local mode never writes into the worktree: the site is built in a temporary copy with SITE_DOWNLOADS_OFFLINE=1
|
|
# (063bbca, 6 October 2026: a hook that built in place rewrote the downloads snapshot in five worktrees). The link, ledger
|
|
# and identity checks then read the committed pages; CI builds in place and checks the rebuilt pages, the one difference.
|
|
set -uo pipefail
|
|
cd "$(git rev-parse --show-toplevel)" || exit 1
|
|
# git hands a hook its own repository through the environment (GIT_DIR, GIT_INDEX_FILE, GIT_PREFIX, ...). Left in place,
|
|
# every nested git inside the self-tests (remote-run.sh builds a mirror and pushes into it) would act on THIS repository
|
|
# 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#--}"
|
|
RED=0; N=0; LOG="$(mktemp)"; trap 'rm -rf "$LOG" "${SITE_TMP:-}"' EXIT
|
|
T0=$(date +%s)
|
|
|
|
run() {
|
|
# run <name> <command...>: one line per check; the output of a red check is shown in full
|
|
local name="$1"; shift; N=$((N + 1))
|
|
local s=$(date +%s)
|
|
if "$@" >"$LOG" 2>&1; then
|
|
printf ' ok %3ds %s\n' "$(( $(date +%s) - s ))" "$name"
|
|
else
|
|
printf ' RED %3ds %s\n' "$(( $(date +%s) - s ))" "$name"; sed 's/^/ /' "$LOG" | cut -c1-240; RED=1
|
|
fi
|
|
}
|
|
run_quiet() { "$@" >/dev/null 2>&1; }
|
|
|
|
site_build() {
|
|
if [ "$MODE" = ci ]; then node site/build.mjs; return; fi
|
|
SITE_TMP="$(mktemp -d)"; cp -R site "$SITE_TMP/site"
|
|
(cd "$SITE_TMP/site" && SITE_DOWNLOADS_OFFLINE=1 node build.mjs)
|
|
}
|
|
|
|
structural_checks() {
|
|
run "no conflict markers in tracked files" bash tools/ci/no-conflict-markers.sh
|
|
run "every tracked path is valid on Windows (colon, trailing dot, reserved names, length)" bash tools/ci/windows-paths-check.sh
|
|
}
|
|
|
|
never_push_checks() {
|
|
# The two never-push classes, on EVERY ref (7 October 2026: three gate summaries on ca3-v4-node carried a 64-hex key
|
|
# through eight red CI runs in 80 minutes; the feature-branch hook ran only the structural checks, so no lane saw it).
|
|
# 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'
|
|
}
|
|
|
|
tree_checks() {
|
|
run "site build (in a temporary copy locally, in place in CI)" site_build
|
|
run "internal link check of site/*.html" node tools/ci/link-check.mjs
|
|
run "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)" bash -c 'node tools/ci/site-nav-check.mjs --self-test && node tools/ci/site-nav-check.mjs'
|
|
run "vendor marks: site/lib/marks.mjs is brand/marks/vendor-marks.mjs byte for byte (the app and the site draw one set)" cmp brand/marks/vendor-marks.mjs site/lib/marks.mjs
|
|
run "ledger sentences present verbatim on their public pages" node tools/ci/ledger-text-check.mjs
|
|
never_push_checks
|
|
run "shell inside .github/workflows parses (bash -n, the PowerShell 5.1 rule)" node tools/ci/check-workflow-shell.mjs
|
|
run "PowerShell drive-reference check (\$name: in a double-quoted string)" bash tools/ci/ps-drive-ref-check.sh
|
|
run "copied sources are re-stamped before a build" bash tools/ci/copied-sources-check.sh
|
|
run "override params files parse with no duplicate key" bash tools/ci/override-json-check.sh
|
|
run "second-engine playbooks log to a file and end their tree (C35)" bash tools/ci/second-engine-check.sh
|
|
run "no playbook quits, pauses or resumes the installed app" bash -c 'bash tools/ci/playbook-quit-check.sh --self-test && bash tools/ci/playbook-quit-check.sh'
|
|
run "no script writes into another worktree or walks Projects" bash -c 'bash tools/ci/no-foreign-tree-writes.sh --self-test && bash tools/ci/no-foreign-tree-writes.sh'
|
|
run "the signer is never piped into head" bash tools/ci/signer-pipe-check.sh
|
|
run "bash bodies in PowerShell job scripts pass bash -n" bash -c 'bash tools/ci/bash-body-check.sh --self-test && bash tools/ci/bash-body-check.sh'
|
|
run "run jobs test their fetched kit before use" bash -c 'bash tools/ci/kit-path-check.sh --self-test && bash tools/ci/kit-path-check.sh'
|
|
run "every Windows spawn of the app runs with a hidden console" bash -c 'node tools/ci/windows-spawn-check.mjs --self-test && node tools/ci/windows-spawn-check.mjs'
|
|
run "pinned guest programs match their manifest" bash tools/ci/pinned-guests-check.sh
|
|
run "root prover playbooks kill the GPU server and unlink its socket" bash tools/ci/prover-socket-check.sh
|
|
run "commit-string gate self-test" bash tools/ci/commit-string-check.sh --self-test
|
|
run "build server remote checkout self-test" bash infra/build-server/remote-run.sh --self-test
|
|
run "a slot holder keeps its own line for the whole run (the watcher-trust rule)" bash infra/build-server/remote-run.sh --self-test-keeper
|
|
run "the remote checkout resets the mirror's tree before the branch checkout (the stale-overlay class)" bash -c 'bash tools/ci/mirror-reset-check.sh --self-test && bash tools/ci/mirror-reset-check.sh'
|
|
run "the remote checkout's clean spares a lane's scratch (.igneum-scratch-spare, the fixed prefixes, never -x; the lost-scratch class)" bash -c 'bash tools/ci/scratch-spare-check.sh --self-test && bash tools/ci/scratch-spare-check.sh'
|
|
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 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 "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
|
|
run "faucet unit tests" node --test site/api/faucet.test.mjs
|
|
run "redesign package data tests (the /api/live contract the pages read)" node tools/site-redesign/tests/data-tests.cjs
|
|
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 "explorer, emission and public stats unit tests" node --test site/lib/explorer.test.mjs site/lib/emission.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 "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
|
|
}
|
|
|
|
gated_refs() {
|
|
# stdin: the pre-push hook's lines "<local ref> <local sha> <remote ref> <remote sha>". Prints "full" when any remote
|
|
# ref is master or release-*, else "light".
|
|
local lref lsha rref rsha full=0
|
|
while read -r lref lsha rref rsha; do
|
|
case "$rref" in refs/heads/master|refs/heads/release-*) full=1 ;; esac
|
|
done
|
|
[ "$full" = 1 ] && echo full || echo light
|
|
}
|
|
|
|
finish() {
|
|
local what="$1" secs=$(( $(date +%s) - T0 ))
|
|
if [ "$RED" = 0 ]; then echo "pre-push gate ($what): GREEN, $N checks in ${secs}s"; exit 0; fi
|
|
echo "pre-push gate ($what): RED after $N checks in ${secs}s. Fix it before pushing (every check above is one CI runs; the same script runs there)." >&2
|
|
exit 1
|
|
}
|
|
|
|
case "$MODE" in
|
|
self-test)
|
|
fails=0
|
|
st="$(mktemp)" # run in this shell, not a $(...) subshell, so RED is visible here
|
|
run "known failure" false >"$st" 2>&1; out="$(cat "$st")"; case "$out" in *"RED"*"known failure"*) ;; *) echo "self-test failed: a failing check was not reported RED"; fails=1 ;; esac
|
|
[ "$RED" = 1 ] || { echo "self-test failed: a failing check did not set RED"; fails=1; }
|
|
RED=0
|
|
run "known success" true >"$st" 2>&1; out="$(cat "$st")"; rm -f "$st"; case "$out" in *"ok"*"known success"*) ;; *) echo "self-test failed: a passing check was not reported ok"; fails=1 ;; esac
|
|
[ "$RED" = 0 ] || { echo "self-test failed: a passing check set RED"; fails=1; }
|
|
[ "$(printf 'refs/heads/x 1 refs/heads/master 2\n' | gated_refs)" = full ] || { echo "self-test failed: a push to master did not select the full gate"; fails=1; }
|
|
[ "$(printf 'refs/heads/x 1 refs/heads/release-0.3.15 2\n' | gated_refs)" = full ] || { echo "self-test failed: a push to release-0.3.15 did not select the full gate"; fails=1; }
|
|
[ "$(printf 'refs/heads/x 1 refs/heads/x 2\n' | gated_refs)" = light ] || { echo "self-test failed: a push to a feature branch selected the full gate"; fails=1; }
|
|
[ "$(printf '' | gated_refs)" = light ] || { echo "self-test failed: a push with no refs selected the full gate"; fails=1; }
|
|
# 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; }
|
|
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; }
|
|
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 (structural checks, no-secrets, identity grep)"
|
|
exit $fails ;;
|
|
list)
|
|
grep -E '^\s+run "' "$0" | sed -E 's/^\s+run "([^"]+)".*/\1/' ;;
|
|
hook)
|
|
which="$(gated_refs)"
|
|
if [ "$which" = full ]; then
|
|
echo "pre-push gate: a push to master or release-*, the full gate (the same checks CI runs):"
|
|
structural_checks; tree_checks; finish "push to master or release-*"
|
|
else
|
|
echo "pre-push gate: a feature branch, the light gate (the two structural checks, the no-secrets check, the identity grep):"
|
|
structural_checks; never_push_checks; finish "feature branch"
|
|
fi ;;
|
|
ci|local)
|
|
[ "$MODE" = ci ] && echo "pre-push gate in CI (the same script as the local hook):" || echo "pre-push gate over this working tree:"
|
|
structural_checks; tree_checks; finish "$MODE" ;;
|
|
*) echo "usage: tools/ci/pre-push.sh [--ci|--hook|--self-test|--list]" >&2; exit 2 ;;
|
|
esac
|