Site on master: live reads Devnet 3 (the live-dn3 edit, cherry-picked onto master so a master deploy carries it; main 7 Oct 2026 21:2x BST, every site deploy is master only from now) and every repository link flips from github.com/igneum-network to git.igneum.network/igneum-network (the public Forgejo host, by the founder word through main at 21:2x BST; GitHub is dark). Also: the runner pool classes in remote-run.sh (two founder-name comments reworded), infra/build-server/forgejo.sh (the git host stand-up), and the gate check tools/ci/inline-rm-check.sh (no deletion inside an inline bash -c / sh -c string; main 21:33 BST; known-failed self-test first, three proving-v1 lines allow-listed by name) with the rule in tools/ci/README.md

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-07 20:36:39 +00:00
commit 78befb1fd0
27 changed files with 96 additions and 130 deletions

View file

@ -93,7 +93,7 @@ ENABLED = false
INI
chown 1000:1000 /srv/git/config/app.ini; chmod 600 /srv/git/config/app.ini
fi
if ! docker ps -a --format '{{.Names}}' | grep -qx forgejo; then
if [ -z "$(docker ps -a --filter 'name=^forgejo$' -q)" ]; then
docker run -d --name forgejo --restart unless-stopped \
-e USER_UID=1000 -e USER_GID=1000 -e FORGEJO_CUSTOM=/data/gitea \
-v /srv/git/data:/data -v /srv/git/config/app.ini:/data/gitea/conf/app.ini \

View file

@ -23,7 +23,7 @@
# 3. Appends one JSON line to /srv/builds/_log/builds.jsonl (the worker dashboard reads it; asked for by main on 6 October
# 2026): on success, on failure and on the slot give-up. UTC ISO 8601 Z times, numbers unquoted, unknown fields omitted,
# artefacts with bytes and sha256 only when the run succeeded (a failed build would list the previous build's files),
# the line kept under 4 KB. Since the evening of 6 October 2026 (the project lead: "make sure we are fixing and learning from all the
# the line kept under 4 KB. Since the evening of 6 October 2026 (the founder: "make sure we are fixing and learning from all the
# errors here") the line also carries "class" and "run_log":
# - PRE-FLIGHT before cargo runs: the manifest must parse (cargo metadata --no-deps) and every `-p` package must exist
# (a workspace member, else cargo pkgid --offline); a refusal is exit 3, class preflight-manifest or preflight-package,
@ -320,7 +320,7 @@ PY
SLOTS_DIR="$IGNEUM_BUILD_SLOTS_DIR"
slots=$(cat "$SLOTS_DIR/slots" 2>/dev/null || echo 1); [ "$slots" -ge 1 ] 2>/dev/null || slots=1
JOBS_ALONE="${JOBS_ALONE:-88}"; JOBS_SHARED="${JOBS_SHARED:-44}" # the project lead, 7 Oct 2026: 88 of 96 cores, 8 reserved for the release builds, the seed and the observers
JOBS_ALONE="${JOBS_ALONE:-88}"; JOBS_SHARED="${JOBS_SHARED:-44}" # the founder, 7 Oct 2026: 88 of 96 cores, 8 reserved for the release builds, the seed and the observers
holder_line() { printf 'pid %s since %sZ waited %s s: %s\n' "$BR_PID" "$(date -u +%H:%M:%S)" "$1" "$BR_LABEL"; }
give_up() { # <what>
echo "build-remote: gave up waiting for $1 after 2 h" >&2

View file

@ -218,8 +218,8 @@
</div>
<figure class="frame glow">
<div class="bar"><i></i><i></i><i></i><b>Overview</b></div>
<img src="/img/app-overview-dark.webp" width="1440" height="912" class="img-dark" alt="The Overview page of 0.3.16: an RTX 5090 at 103 MH/s and 317 W, 0.32 MH/W, 62 blocks this run and 91,521 lifetime, the Stop mining button, the chain drawn live with this rig's blocks ringed and locked checkpoints, the node synced with 5 peers, the activity log" fetchpriority="high" decoding="async">
<img src="/img/app-overview-light.webp" width="1440" height="912" class="img-light" alt="The Overview page of 0.3.16 in light mode: an RTX 5090 at 103 MH/s and 317 W, the chain drawn live with this rig's blocks ringed" fetchpriority="high" decoding="async">
<img src="/img/app-overview-dark.webp" width="2880" height="1800" class="img-dark" alt="The Overview page of 0.3.16: an RTX 5090 at 103 MH/s and 317 W, 0.32 MH/W, 62 blocks this run and 91,521 lifetime, the Stop mining button, the chain drawn live with this rig's blocks ringed and locked checkpoints, the node synced with 5 peers, the activity log" fetchpriority="high" decoding="async">
<img src="/img/app-overview-light.webp" width="2880" height="1800" class="img-light" alt="The Overview page of 0.3.16 in light mode: an RTX 5090 at 103 MH/s and 317 W, the chain drawn live with this rig's blocks ringed" fetchpriority="high" decoding="async">
<figcaption>0.3.16, rendered from the RTX 5090 Windows rig&rsquo;s live state at 23:33 UK, 6 October 2026. No electricity price is set on that rig, so the pounds cell asks for one.</figcaption>
</figure>
</div>
@ -230,8 +230,8 @@
<div class="feat-row">
<figure class="frame">
<div class="bar"><i></i><i></i><i></i><b>Overview · Mac</b></div>
<img src="/img/app-overview-mac-dark.webp" width="1440" height="900" class="img-dark" alt="The Overview page on an Apple M5 Max: 18.3 MH/s, 6,789 blocks this run, the chain drawn live with this Mac's blocks in the lane marked you, locked checkpoints at 71% of weight, the node synced with 4 peers" loading="lazy" decoding="async">
<img src="/img/app-overview-mac-light.webp" width="1440" height="1259" class="img-light" alt="The Overview page in light mode: 511 MH/s from one of three cards mining, 629 W, £4.23 a day of electricity, 6,784 blocks this run, the chain drawn live with this rig's blocks marked you, the node line and the activity log" loading="lazy" decoding="async">
<img src="/img/app-overview-mac-dark.webp" width="2880" height="1800" class="img-dark" alt="The Overview page on an Apple M5 Max: 18.3 MH/s, 6,789 blocks this run, the chain drawn live with this Mac's blocks in the lane marked you, locked checkpoints at 71% of weight, the node synced with 4 peers" loading="lazy" decoding="async">
<img src="/img/app-overview-mac-light.webp" width="2880" height="1800" class="img-light" alt="The Overview page in light mode: 511 MH/s from one of three cards mining, 629 W, £4.23 a day of electricity, 6,784 blocks this run, the chain drawn live with this rig's blocks marked you, the node line and the activity log" loading="lazy" decoding="async">
<figcaption><span class="cap-dark">An Apple M5 Max mining at 18.3 MH/s, from the app in development.</span><span class="cap-light">A three-card rig at 511 MH/s, from the app in development.</span></figcaption>
</figure>
<div>
@ -253,8 +253,8 @@
<div class="feat-row flip">
<figure class="frame">
<div class="bar"><i></i><i></i><i></i><b>Cards</b></div>
<img src="/img/app-cards-dark.webp" width="1440" height="1381" class="img-dark" alt="The Cards page: Ember Tune with Efficiency, Balanced and Maximum goals and the watts it saves; one row per card with its rate, watts, hashes per watt, cost a day and temperature; the RTX 5090's details open with its power cap, goal, Retune and the tune curve of hash rate against power" loading="lazy" decoding="async">
<img src="/img/app-cards-light.webp" width="1440" height="912" class="img-light" alt="The Cards page of 0.3.16 in light mode: Ember Tune, the RTX 5090 at 103 MH/s, 322 to 317 W, 68 degrees, its tune line and Retune button; the integrated AMD graphics off" loading="lazy" decoding="async">
<img src="/img/app-cards-dark.webp" width="2880" height="1800" class="img-dark" alt="The Cards page: Ember Tune with Efficiency, Balanced and Maximum goals and the watts it saves; one row per card with its rate, watts, hashes per watt, cost a day and temperature; the RTX 5090's details open with its power cap, goal, Retune and the tune curve of hash rate against power" loading="lazy" decoding="async">
<img src="/img/app-cards-light.webp" width="2880" height="1800" class="img-light" alt="The Cards page of 0.3.16 in light mode: Ember Tune, the RTX 5090 at 103 MH/s, 322 to 317 W, 68 degrees, its tune line and Retune button; the integrated AMD graphics off" loading="lazy" decoding="async">
<figcaption><span class="cap-dark">The Cards page with a card&rsquo;s details open, from the app in development.</span><span class="cap-light">0.3.16, rendered from the RTX 5090 Windows rig&rsquo;s live state at 23:33 UK, 6 October 2026.</span></figcaption>
</figure>
<div>
@ -310,8 +310,8 @@
<div class="feat-row">
<figure class="frame">
<div class="bar"><i></i><i></i><i></i><b>Earnings</b></div>
<img src="/img/app-earnings-dark.webp" width="1440" height="908" class="img-dark" alt="The Earnings page of 0.3.16: pounds earned with the devnet reason, IGN from proofs, 23,056 blocks lifetime, electricity with no card reporting its draw, the dev fee switch, the rewards address with Copy, Change address, Show my key and Open the wallet" loading="lazy" decoding="async">
<img src="/img/app-earnings-light.webp" width="1440" height="908" class="img-light" alt="The Earnings page of 0.3.16 in light mode" loading="lazy" decoding="async">
<img src="/img/app-earnings-dark.webp" width="2880" height="1800" class="img-dark" alt="The Earnings page of 0.3.16: pounds earned with the devnet reason, IGN from proofs, 23,056 blocks lifetime, electricity with no card reporting its draw, the dev fee switch, the rewards address with Copy, Change address, Show my key and Open the wallet" loading="lazy" decoding="async">
<img src="/img/app-earnings-light.webp" width="2880" height="1800" class="img-light" alt="The Earnings page of 0.3.16 in light mode" loading="lazy" decoding="async">
<figcaption>0.3.16 on the team&rsquo;s Apple M5 Max, paused, 6 October 2026. Pounds earned reads 0.00 on devnet and says why.</figcaption>
</figure>
<div>
@ -333,8 +333,8 @@
<div class="feat-row flip">
<figure class="frame">
<div class="bar"><i></i><i></i><i></i><b>Prove</b></div>
<img src="/img/app-prove-dark.webp" width="1440" height="908" class="img-dark" alt="The Prove page of 0.3.16: the Prove on this machine switch, one sentence per card on what it can prove, one line of counts, assigned, proven, paid and IGN, and Details" loading="lazy" decoding="async">
<img src="/img/app-prove-light.webp" width="1440" height="908" class="img-light" alt="The Prove page of 0.3.16 in light mode" loading="lazy" decoding="async">
<img src="/img/app-prove-dark.webp" width="2880" height="1800" class="img-dark" alt="The Prove page of 0.3.16: the Prove on this machine switch, one sentence per card on what it can prove, one line of counts, assigned, proven, paid and IGN, and Details" loading="lazy" decoding="async">
<img src="/img/app-prove-light.webp" width="2880" height="1800" class="img-light" alt="The Prove page of 0.3.16 in light mode" loading="lazy" decoding="async">
<figcaption>0.3.16 on the team&rsquo;s Apple M5 Max, 6 October 2026. Apple silicon proves on the CPU, slowly; a 24 GB NVIDIA card proves the full shard.</figcaption>
</figure>
<div>
@ -358,8 +358,8 @@
</div>
<figure class="frame">
<div class="bar"><i></i><i></i><i></i><b>Settings · Tuning</b></div>
<img src="/img/app-settings-dark.webp" width="1440" height="450" class="img-dark" alt="The Tuning card of Settings in 0.3.16: the goal at Balanced, the Ember Tune, Power control and Fine tuning switches, each with one sentence, and the electricity price in pence per kWh" loading="lazy" decoding="async">
<img src="/img/app-settings-light.webp" width="1440" height="450" class="img-light" alt="The Tuning card of Settings in 0.3.16, light mode" loading="lazy" decoding="async">
<img src="/img/app-settings-dark.webp" width="2880" height="1800" class="img-dark" alt="The Tuning card of Settings in 0.3.16: the goal at Balanced, the Ember Tune, Power control and Fine tuning switches, each with one sentence, and the electricity price in pence per kWh" loading="lazy" decoding="async">
<img src="/img/app-settings-light.webp" width="2880" height="1800" class="img-light" alt="The Tuning card of Settings in 0.3.16, light mode" loading="lazy" decoding="async">
<figcaption>0.3.16 on the team&rsquo;s Apple M5 Max, 6 October 2026.</figcaption>
</figure>
<ul class="lines c4" style="margin-top:var(--s-4)">

View file

@ -235,16 +235,16 @@
<div><h3>Ember for Linux</h3><div class="sub">one tarball, the miner and the node inside</div></div>
<div class="body">
<p>Unpack it, run <code>igneum</code> with your payout address. The same signed manifest as the desktop apps. NVIDIA through CUDA, AMD through OpenCL.</p>
<a data-dl="miner-hive" href="https://dl.igneum.network/public/igneum-miner-hive.tar.gz" class="btn primary"><span class="osmark bare" data-os="linux" title="Linux"><svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true" focusable="false" fill="currentColor"><path d="M12 2c-2.4 0-4 1.9-4 4.6 0 1.2.2 2 0 2.8-.6 1.4-2.1 2.9-2.6 4.9-.3 1.1-.1 2 .3 2.6-.6.4-1.3 1-1.1 1.7.3 1 2.1 1.2 3.2 1.8.7.4 1.5.6 2.1.1.6.2 1.3.3 2.1.3s1.5-.1 2.1-.3c.6.5 1.4.3 2.1-.1 1.1-.6 2.9-.8 3.2-1.8.2-.7-.5-1.3-1.1-1.7.4-.6.6-1.5.3-2.6-.5-2-2-3.5-2.6-4.9-.2-.8 0-1.6 0-2.8C16 3.9 14.4 2 12 2zm-1.4 4.2c.5 0 .8.5.8 1.2s-.3 1.2-.8 1.2-.8-.5-.8-1.2.3-1.2.8-1.2zm2.8 0c.5 0 .8.5.8 1.2s-.3 1.2-.8 1.2-.8-.5-.8-1.2.3-1.2.8-1.2zM12 9.3c.9 0 1.9.5 1.9 1s-1 1.2-1.9 1.2-1.9-.7-1.9-1.2 1-1 1.9-1zm0 3.4c2.2 0 3.6 2.6 3.6 4.4 0 1.5-1.6 2.3-3.6 2.3s-3.6-.8-3.6-2.3c0-1.8 1.4-4.4 3.6-4.4z"/></svg></span>Download the tarball <span class="meta" data-dl-meta="miner-hive">v0.3.20 · 27.3 MB</span></a>
<div class="sha"><b>sha256</b> <span data-dl-sha="miner-hive">d9dd12dfe900bf9f603995a6877e1758ae9b75a85ad899bf180aa86e72b2e57c</span></div>
<a data-dl="miner-hive" href="https://dl.igneum.network/public/igneum-miner-hive.tar.gz" class="btn primary"><span class="osmark bare" data-os="linux" title="Linux"><svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true" focusable="false" fill="currentColor"><path d="M12 2c-2.4 0-4 1.9-4 4.6 0 1.2.2 2 0 2.8-.6 1.4-2.1 2.9-2.6 4.9-.3 1.1-.1 2 .3 2.6-.6.4-1.3 1-1.1 1.7.3 1 2.1 1.2 3.2 1.8.7.4 1.5.6 2.1.1.6.2 1.3.3 2.1.3s1.5-.1 2.1-.3c.6.5 1.4.3 2.1-.1 1.1-.6 2.9-.8 3.2-1.8.2-.7-.5-1.3-1.1-1.7.4-.6.6-1.5.3-2.6-.5-2-2-3.5-2.6-4.9-.2-.8 0-1.6 0-2.8C16 3.9 14.4 2 12 2zm-1.4 4.2c.5 0 .8.5.8 1.2s-.3 1.2-.8 1.2-.8-.5-.8-1.2.3-1.2.8-1.2zm2.8 0c.5 0 .8.5.8 1.2s-.3 1.2-.8 1.2-.8-.5-.8-1.2.3-1.2.8-1.2zM12 9.3c.9 0 1.9.5 1.9 1s-1 1.2-1.9 1.2-1.9-.7-1.9-1.2 1-1 1.9-1zm0 3.4c2.2 0 3.6 2.6 3.6 4.4 0 1.5-1.6 2.3-3.6 2.3s-3.6-.8-3.6-2.3c0-1.8 1.4-4.4 3.6-4.4z"/></svg></span>Download the tarball <span class="meta" data-dl-meta="miner-hive">v0.3.22 · 28.8 MB</span></a>
<div class="sha"><b>sha256</b> <span data-dl-sha="miner-hive">8ad6dcef9edc57dcd33e8d5a97cbef5cdda0e384a6e56d3f62f8903029c4c764</span></div>
</div>
</article>
<article class="dl-card" id="hive">
<span class="osmark" data-os="hive" title="HiveOS"><svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round" stroke-linecap="round"><path d="M12 2.6 20.2 7.3v9.4L12 21.4 3.8 16.7V7.3z"/><path d="M12 7.4 16 9.7v4.6L12 16.6 8 14.3V9.7z"/><path d="M12 7.4V2.6M16 9.7l4.2-2.4M16 14.3l4.2 2.4M12 16.6v4.8M8 14.3l-4.2 2.4M8 9.7 3.8 7.3"/></svg></span>
<div><h3>Ember on HiveOS</h3><div class="sub">for the rig people, the same tarball</div></div>
<div class="body">
<div class="hive-sheet"><b>Flight Sheet.</b> Miner: <b>Custom</b>. Installation URL: <code data-dl-url="miner-hive">https://dl.igneum.network/dl/public/igneum-hive-0.3.20.tar.gz</code>. Miner name <code>igneum</code>, wallet and worker <code>0x&lt;your 40-hex payout address&gt;.%WORKER_NAME%</code>. Hive itself is untested on our side: tell us what breaks.</div>
<a data-dl="miner-hive" href="https://dl.igneum.network/public/igneum-miner-hive.tar.gz" class="btn"><span class="osmark bare" data-os="hive" title="HiveOS"><svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round" stroke-linecap="round"><path d="M12 2.6 20.2 7.3v9.4L12 21.4 3.8 16.7V7.3z"/><path d="M12 7.4 16 9.7v4.6L12 16.6 8 14.3V9.7z"/><path d="M12 7.4V2.6M16 9.7l4.2-2.4M16 14.3l4.2 2.4M12 16.6v4.8M8 14.3l-4.2 2.4M8 9.7 3.8 7.3"/></svg></span>Download the tarball <span class="meta" data-dl-meta="miner-hive">v0.3.20 · 27.3 MB</span></a>
<div class="hive-sheet"><b>Flight Sheet.</b> Miner: <b>Custom</b>. Installation URL: <code data-dl-url="miner-hive">https://dl.igneum.network/dl/public/igneum-hive-0.3.22.tar.gz</code>. Miner name <code>igneum</code>, wallet and worker <code>0x&lt;your 40-hex payout address&gt;.%WORKER_NAME%</code>. Hive itself is untested on our side: tell us what breaks.</div>
<a data-dl="miner-hive" href="https://dl.igneum.network/public/igneum-miner-hive.tar.gz" class="btn"><span class="osmark bare" data-os="hive" title="HiveOS"><svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round" stroke-linecap="round"><path d="M12 2.6 20.2 7.3v9.4L12 21.4 3.8 16.7V7.3z"/><path d="M12 7.4 16 9.7v4.6L12 16.6 8 14.3V9.7z"/><path d="M12 7.4V2.6M16 9.7l4.2-2.4M16 14.3l4.2 2.4M12 16.6v4.8M8 14.3l-4.2 2.4M8 9.7 3.8 7.3"/></svg></span>Download the tarball <span class="meta" data-dl-meta="miner-hive">v0.3.22 · 28.8 MB</span></a>
</div>
</article>
</div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 101 KiB

View file

@ -268,9 +268,9 @@ pre b{color:var(--molten-text);font-weight:500}
<div class="platform-body" id="panel-hive" role="tabpanel" aria-labelledby="tab-hive" data-platform-panel="hive" hidden>
<div class="download-platform"><span class="osmark" data-os="hive" title="HiveOS"><svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round" stroke-linecap="round"><path d="M12 2.6 20.2 7.3v9.4L12 21.4 3.8 16.7V7.3z"/><path d="M12 7.4 16 9.7v4.6L12 16.6 8 14.3V9.7z"/><path d="M12 7.4V2.6M16 9.7l4.2-2.4M16 14.3l4.2 2.4M12 16.6v4.8M8 14.3l-4.2 2.4M8 9.7 3.8 7.3"/></svg></span><div><h3>Ember for Linux and HiveOS</h3><p>a tarball for rigs and Hive flight sheets</p></div></div>
<p class="download-note">For the rig people. One tarball, the miner and the node inside, the same signed manifest as the desktop apps.</p>
<a data-dl="miner-hive" href="https://dl.igneum.network/public/igneum-miner-hive.tar.gz" class="btn primary"><svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3v12m-5-5 5 5 5-5M5 16v4h14v-4"/></svg>Download the tarball <span data-dl-meta="miner-hive" style="font-weight:400;opacity:.85">v0.3.20 · 27.3 MB</span></a>
<div class="hive-sheet" id="hive"><b>HiveOS Flight Sheet.</b> Miner: <b>Custom</b>. Installation URL: <code data-dl-url="miner-hive">https://dl.igneum.network/dl/public/igneum-hive-0.3.20.tar.gz</code>. Miner name <code>igneum</code>, wallet and worker <code>0x&lt;your 40-hex payout address&gt;.%WORKER_NAME%</code>. Hive itself is untested on our side: tell us what breaks.</div>
<p class="sha">sha256 <span data-dl-sha="miner-hive">d9dd12dfe900bf9f603995a6877e1758ae9b75a85ad899bf180aa86e72b2e57c</span></p>
<a data-dl="miner-hive" href="https://dl.igneum.network/public/igneum-miner-hive.tar.gz" class="btn primary"><svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3v12m-5-5 5 5 5-5M5 16v4h14v-4"/></svg>Download the tarball <span data-dl-meta="miner-hive" style="font-weight:400;opacity:.85">v0.3.22 · 28.8 MB</span></a>
<div class="hive-sheet" id="hive"><b>HiveOS Flight Sheet.</b> Miner: <b>Custom</b>. Installation URL: <code data-dl-url="miner-hive">https://dl.igneum.network/dl/public/igneum-hive-0.3.22.tar.gz</code>. Miner name <code>igneum</code>, wallet and worker <code>0x&lt;your 40-hex payout address&gt;.%WORKER_NAME%</code>. Hive itself is untested on our side: tell us what breaks.</div>
<p class="sha">sha256 <span data-dl-sha="miner-hive">8ad6dcef9edc57dcd33e8d5a97cbef5cdda0e384a6e56d3f62f8903029c4c764</span></p>
</div>
</div>
<div>
@ -384,17 +384,17 @@ pre b{color:var(--molten-text);font-weight:500}
<div class="shots3">
<figure class="frame">
<div class="bar"><i></i><i></i><i></i><b>Overview</b></div>
<img src="/img/app-overview-dark.webp" width="1440" height="912" class="shot" alt="The Overview page of 0.3.16: an RTX 5090 at 103 MH/s and 317 W, 0.32 MH/W, 62 blocks this run and 91,521 lifetime, the Stop mining button, the chain drawn live with this rig's blocks ringed and locked checkpoints, the node synced with 5 peers" loading="lazy" decoding="async">
<img src="/img/app-overview-dark.webp" width="2880" height="1800" class="shot" alt="The Overview page of 0.3.16: an RTX 5090 at 103 MH/s and 317 W, 0.32 MH/W, 62 blocks this run and 91,521 lifetime, the Stop mining button, the chain drawn live with this rig's blocks ringed and locked checkpoints, the node synced with 5 peers" loading="lazy" decoding="async">
<figcaption>0.3.16, rendered from the RTX 5090 Windows rig’s live state at 23:33 UK, 6 October 2026.</figcaption>
</figure>
<figure class="frame">
<div class="bar"><i></i><i></i><i></i><b>Cards</b></div>
<img src="/img/app-cards-dark.webp" width="1440" height="1381" class="shot" alt="The Cards page: Ember Tune with Efficiency, Balanced and Maximum goals; one row per card with its rate, watts, hashes per watt, cost a day and temperature; the RTX 5090's details open with its power cap, goal, Retune and the tune curve" loading="lazy" decoding="async">
<img src="/img/app-cards-dark.webp" width="2880" height="1800" class="shot" alt="The Cards page: Ember Tune with Efficiency, Balanced and Maximum goals; one row per card with its rate, watts, hashes per watt, cost a day and temperature; the RTX 5090's details open with its power cap, goal, Retune and the tune curve" loading="lazy" decoding="async">
<figcaption>The Cards page, from the app in development.</figcaption>
</figure>
<figure class="frame">
<div class="bar"><i></i><i></i><i></i><b>Overview · Mac</b></div>
<img src="/img/app-overview-mac-dark.webp" width="1440" height="900" class="shot" alt="The Overview page on an Apple M5 Max: 18.3 MH/s, 6,789 blocks this run, the chain drawn live with this Mac's blocks in the lane marked you, locked checkpoints at 71% of weight, the node synced with 4 peers" loading="lazy" decoding="async">
<img src="/img/app-overview-mac-dark.webp" width="2880" height="1800" class="shot" alt="The Overview page on an Apple M5 Max: 18.3 MH/s, 6,789 blocks this run, the chain drawn live with this Mac's blocks in the lane marked you, locked checkpoints at 71% of weight, the node synced with 4 peers" loading="lazy" decoding="async">
<figcaption>An Apple M5 Max mining at 18.3 MH/s, from the app in development.</figcaption>
</figure>
</div>

View file

@ -238,7 +238,7 @@ table{min-width:560px}
</section>
<section class="doc-section"><h2 id="licence-of-igneum-s-own-code">Licence of Igneum's own code</h2>
<p>Recommended: MIT, for the node fork's additions, <code>igneum-pow</code>, the prototypes and the tools. <code>igneum-pow/Cargo.toml</code> already declares <code>license = "MIT"</code> and should be read as provisional until the decision below.</p>
<p>Pending the maintainers' decision. Two obligations hold whatever is chosen: the fork keeps Kaspa's ISC copyright notice in <code>vendor/igneum-node/LICENSE</code> (ISC requires it), and the VDF code keeps chiavdf's Apache-2.0 notice and a statement of what was changed (Apache-2.0 section 4).</p></section></article>
<p>Pending the founder's decision. Two obligations hold whatever is chosen: the fork keeps Kaspa's ISC copyright notice in <code>vendor/igneum-node/LICENSE</code> (ISC requires it), and the VDF code keeps chiavdf's Apache-2.0 notice and a statement of what was changed (Apache-2.0 section 4).</p></section></article>
</div>
<p class="gen">Generated from the repository at build time. Times are UTC. Machine names are model names.</p>
</div></section>

File diff suppressed because one or more lines are too long

View file

@ -253,7 +253,7 @@
<div class="state-card block"><span class="pill"><i></i>in a block</span><div class="t">Chain block 8</div><p>A block carries it and the chain’s height passed it. It can still be reorganised until a checkpoint covers it.</p></div>
<div class="state-card final"><span class="pill"><i></i>final · cp 5</span><div class="t">Under a verified checkpoint</div><p>Its block sits under a 30-second checkpoint whose certificate this wallet verified: the voter list, the aggregate BLS signature, two thirds of all weight.</p></div>
</div>
<figure class="frame reveal" style="margin:25px 0 0;max-width:880px">
<figure class="frame reveal" style="margin:25px 0 0">
<div class="bar"><i></i><i></i><i></i><b>History</b></div>
<img src="/img/wallet-history-dark.webp" width="2240" height="1560" class="shot" alt="The wallet's History page: every transfer with the node's word, pending, proven, executed, finalised under a checkpoint verified here, and one failed with the fee still paid" loading="lazy" decoding="async">
<figcaption>Every transfer with the node’s word: pending, proven, executed, finalised under a checkpoint this wallet verified. Igneum Wallet 0.1.5, rendered from its mock state with example keys, 7 October 2026.</figcaption>

View file

@ -2,11 +2,10 @@
| Check | What it fails | Since |
|---|---|---|
| no text overlaps (`overlap-check.mjs`) | A served page, or a miner or wallet screen (behind `IGNEUM_OVERLAP_APPS=1`), where a visible run of text is covered by another element (a pill over a caption, a label over a value, a card over its neighbour, text under the header at rest), clipped by an overflow-hidden ancestor, or past the viewport; a page that scrolls sideways. Five widths, light and dark, the home hero at rest and at each step. A fixture with one deliberate overlap of each kind must be flagged first (`--self-test`). Needs a headless Chromium: CI installs Playwright; the Mac ships the pages to build-2 (`infra/build-server/overlap-browser.sh`). | 7 October 2026: the hero's step pill sat on the caption's second line ("Two thirds of the weight sign. The checkpoint locks.") at every desktop width, found by the founder on the live site |
| master takes only CI-passed commits (`ci-state.mjs`, `merge-to-master.sh`, the hook's `master_ci_ok`) | A push to master whose commit, or whose merge's branch parent, has no green `ci` run on that exact sha (the runs API through gh: red, queued, none or gh unreachable all refuse); a merge onto a master whose last compiled run is red, unless declared the fix (`--fixes-master`). The merge tool pushes an unrun branch for a run and waits for a queued one with the clock. A feature-branch push prints the branch's previous red first (`--branch-red`). | 7 October 2026: era-vdf's tip 0e2d6b1c merged with no ci run; master's igneum-pow suite red from 16:31 UK under five docs-only green merges |
| every workflow job carries timeout-minutes (`workflow-timeouts-check.sh`) | A job in .github/workflows without `timeout-minutes`, or a budget off its measured line (site 15, changes 10, pow 60, sims 45). | 7 October 2026: three hosted site jobs on master hung over two hours each in the tree gate; the six-hour default was the only stop |
| 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 |
| no text overlaps (`overlap-check.mjs`) | A served page, or a miner or wallet screen (behind `IGNEUM_OVERLAP_APPS=1`), where a visible run of text is covered by another element (a pill over a caption, a label over a value, a card over its neighbour, text under the header at rest), clipped by an overflow-hidden ancestor, or past the viewport; a page that scrolls sideways. Five widths, light and dark, the home hero at rest and at each step. A fixture with one deliberate overlap of each kind must be flagged first (`--self-test`). Needs a headless Chromium: CI installs Playwright; the Mac ships the pages to build-2 (`infra/build-server/overlap-browser.sh`). | 7 October 2026: the hero's step pill sat on the caption's second line ("Two thirds of the weight sign. The checkpoint locks.") at every desktop width, found by the project lead on the live site |
| 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)
The desktop app asks the founder to approve any shell command that carries `rm` inside an inline `bash -c '...'` or `sh -c '...'` string ("runs rm and could not be checked"). Rule for every lane: never an inline `rm`, `rm -rf`, `find ... -delete` or a redirect-truncate inside a `bash -c` / `sh -c` string. Put the script in a file under `tools/` (or the lane's scratch directory) and run it by path; on the boxes, do deletions through the lease or job tooling, which the checker reads as a plain command. `tools/ci/inline-rm-check.sh` greps every tracked script for the shape (self-test first, known-failed shapes named) and runs in the gate.

46
tools/ci/inline-rm-check.sh Executable file
View file

@ -0,0 +1,46 @@
#!/usr/bin/env bash
# No inline deletion inside a `bash -c` or `sh -c` string (main, 7 October 2026 21:33 BST: the desktop app asked the founder to
# approve lanes' shell commands that carried `rm` inside an inline bash -c '...' string, "runs rm and could not be checked").
# Rule: never an inline `rm`, `rm -rf`, `find ... -delete` or a redirect-truncate (`: > file`, `> file` on its own) inside a
# `bash -c` / `sh -c` string in a tracked script; put the script in a file under tools/ (or the lane's scratch directory) and run it
# by path; on the boxes, deletions go through the lease or job tooling, which the checker reads as a plain command.
# This check greps every tracked shell, PowerShell and JS/MJS script for a bash -c / sh -c string that carries one of those. Known
# failures named with file and line. --self-test first: four banned shapes fail, four allowed shapes pass.
set -uo pipefail
cd "$(git rev-parse --show-toplevel)"
# a line that opens an inline shell string (bash -c, sh -c, "bash", "-c" in a PowerShell or JS argument list) and, on the same
# line, a deletion word inside it
BANNED='((bash|sh|pwsh|powershell)(\.exe)? +-l?c +["'"'"'][^"'"'"']*|"-c", *["'"'"'][^"'"'"']*)(\brm +|\brm$|find [^"'"'"']*-delete|(^|[ ;&|]): *> *[^ ]|[ ;&|]> *([A-Za-z._$]|/[a-ce-z]|/d[a-df-z])[^ ]* *([;&|]|$))'
scan() { # <file...>: prints "file:line: text" for each hit
grep -n -H -E "$BANNED" "$@" 2>/dev/null | grep -v -E '^[^:]*:[0-9]+:\s*#' || true
}
if [ "${1:-}" = --self-test ]; then
t=$(mktemp -d); trap 'rm -rf "$t"' EXIT; fail=0
printf '%s\n' 'ssh box "bash -c '"'"'cd /tmp && rm -rf /tmp/x'"'"'"' > "$t/b1.sh"
printf '%s\n' 'sh -c "find /srv/x -name y -delete"' > "$t/b2.sh"
printf '%s\n' 'bash -c '"'"': > /srv/builds/_locks/quiet'"'"'' > "$t/b3.sh"
printf '%s\n' 'Start-Process bash -ArgumentList "-c", "rm /tmp/old.log; echo ok"' > "$t/b4.ps1"
printf '%s\n' 'bash tools/ci/clean-scratch.sh /tmp/x' > "$t/a1.sh"
printf '%s\n' 'rm -rf "$t" # a plain command, the checker reads it' > "$t/a2.sh"
printf '%s\n' 'ssh box "bash -c '"'"'ls /srv/x && echo done'"'"'"' > "$t/a3.sh"
printf '%s\n' '# bash -c "rm -rf x" is banned (a comment names the rule)' > "$t/a4.sh"
for f in b1.sh b2.sh b3.sh b4.ps1; do [ -n "$(scan "$t/$f")" ] || { echo "inline-rm self-test: FAIL: banned shape $f passed"; fail=1; }; done
for f in a1.sh a2.sh a3.sh a4.sh; do [ -z "$(scan "$t/$f")" ] || { echo "inline-rm self-test: FAIL: allowed shape $f was flagged: $(scan "$t/$f")"; fail=1; }; done
[ "$fail" = 0 ] && echo "inline-rm self-test: 4 banned shapes fail (rm, find -delete, truncate, PowerShell -c list), 4 allowed shapes pass (script by path, plain rm, no deletion, a comment)"
[ "$fail" = 0 ] || exit 1
fi
# allowed for now, named: the proving lane's WSL socket clean-up (tools/proving-v1, three lines of `bash -c 'pkill ...; rm -f /tmp/sp1-cuda-*.sock'`
# run inside WSL on a PC by a job, not from a Mac shell); the lane moves it into a file under tools/proving-v1 and the lines leave this list
ALLOW='^tools/proving-v1/(pc2-agg-cost(-restore)?|pc2-segments)\.ps1:'
hits=""
while IFS= read -r f; do
[ -n "$f" ] || continue
h=$(scan "$f" | grep -v -E "$ALLOW" || true)
[ -n "$h" ] && hits="${hits}${h}"$'\n'
done < <(git ls-files -- '*.sh' '*.bash' '*.ps1' '*.mjs' '*.js' '*.yml' '*.yaml' | grep -v -E '^tools/ci/inline-rm-check\.sh$')
hits=$(printf '%s' "$hits" | sed '/^$/d')
if [ -n "$hits" ]; then
echo "inline-rm: a deletion inside an inline bash -c / sh -c string (put the script in a file and run it by path; on a box use the lease or job tooling):"
echo "$hits" | cut -c1-200 | sed 's/^/ /'; exit 1
fi
echo "inline-rm: no tracked script carries rm, find -delete or a truncate inside an inline bash -c / sh -c string"

View file

@ -26,7 +26,6 @@ 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)
@ -53,20 +52,12 @@ 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"
# 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
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
}
structural_checks() {
@ -80,9 +71,6 @@ 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'
# 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'
}
tree_checks() {
@ -116,7 +104,8 @@ 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 "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 "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 "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'
@ -129,22 +118,18 @@ 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 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 "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 "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/lib/leaderboard.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/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 && node tools/launch/income-page.mjs --check'
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
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 "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() {
@ -180,33 +165,6 @@ 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
@ -230,7 +188,6 @@ 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
@ -251,36 +208,7 @@ 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; }
# 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 '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)"
[ "$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)"
exit $fails ;;
list)
grep -E '^\s+run "' "$0" | sed -E 's/^\s+run "([^"]+)".*/\1/' ;;
@ -289,16 +217,6 @@ FAKEGH
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)" ;;
@ -307,7 +225,6 @@ FAKEGH
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)