Merge remote-tracking branch 'build/master' into ledger-close-23

This commit is contained in:
igneum-labs 2026-10-07 21:41:29 +00:00
commit 78c9cdbcba
9 changed files with 281 additions and 17 deletions

View file

@ -128,3 +128,13 @@ Three commits on 96161037: bd710a36 the sub-version 3 re-pin (byte 7, igneum-pow
The 0.3.23 node's heights move Devnet 3's digest to ba75bf6f, and the one-box-at-a-time sweep to it split the network: dn3-g1 flipped at 21:18 BST and sat alone twenty minutes on its own fork (blocks 12,553 to 12,793, peers 0; the 83eb50cd nodes refuse ba75bf6f at the handshake; build-1's seed logged 27 digest-mismatch rejects by 21:37); the fleet stopped the pass at 21:31 after that one box (the Vast ssh proxies ssh1, 3, 4, 5, 6 refused the next five, so nothing else flipped) and reverted dn3-g1 to 34a2dbaa on its kept datadir, where it reorgs onto the network's heavier chain; no record or share of the network's sat on the solo blocks; the pool pair, the provers and the second nodes stayed on 83eb50cd. The shipper's part of the fault: the 0.3.23 Mac entry (both folders, 21:39:09 BST, DMG 4ada2807 on 7c7489ac) and the 0.3.23 HiveOS alias (0d716ed9, 21:37) were published ahead of the fleet's move, against rule 5's own word; both pulled back to 0.3.22 (live again at 21:41:08 and 21:44:48 BST; the Mac still runs 0.3.22; the 0.3.23 hive package held in scratch r0323/held-public). The move to 2720d8d2 is the 0.3.20 form (rule 5 extended): binaries pre-placed on every Devnet 3 node (the fleet's thirty, build-1's three, the pool pair's two), every node restarted inside one minute at a clock the fleet names ten minutes ahead once the proxies answer, the digest read back per node, the first lock on the new side as the line; the 0.3.23 Mac entry, the hive alias and the Windows entry publish at that minute or after. Pool split: the hour's 13,209 + 7,200 = 20,409 lands before the 0.3.24 publish minute, so the split goes into the 0.3.24 object at or after the v5 floor on IGNH/IGNW (pending main); the node lane holds it out of the v5 object commit. PC 1: the 5090 floor grid exited 0 at 21:41 BST but the Power Helper hung at its 1,000 MHz step and the 5090 mines at the 1,100 MHz lock until the hash lane's unelevated restore job runs; the grid gets the fix (stop at the first helper timeout, restart the task before the reset).
**Decimals (the founder, 21:40 BST): 18.** The testnet GO object re-cut goes on the 0.3.24 line as its second item on top of the v5 object commit (the 0.3.23 pin 2720d8d2 is frozen in the move): base_unit_decimals 18 with the UTXO/EVM scale fixed and tested, the 16-byte subsidy layout, the emission rescaled, the final 5 October message, the cache-rung field at 0, class v5 at 0 only if its Devnet 3 crossing reads clean, chain id 4462, the override file refused; the seeds re-armed on the dry run, nothing mining until the founder's go. **Pre-place for the one-minute move (the fleet, 21:5x BST):** the 2720d8d2 pair as .new on all 34 Devnet 3 nodes (20 rented, 14 standing second nodes), 2 of 34 placed while the Vast ssh proxies ssh1 to ssh9 refuse or time out since about 21:30; build-1's three pre-placed at 21:46 BST with the restart armed on the minute; dn3-g1 re-peered on 34a2dbaa at 21:43:32 BST (13 peers, three reorg lines, the 25 minutes of solo blocks orphaned). One miner per card restored by the fleet's v2 pass (p2-4090-3 first at 21:51:55 BST: the Devnet 3 miner alone at 55.96 MH/s, supervisors 4 to 1). **A pool-mode miner finding (0.3.24 item, the pool lane):** --exit-on-seed-change does not fire in pool mode; pool-b hashed the epoch-2 pack for 56 minutes after the 20:53 BST seed change (47,700 shares wrong_hash) until a re-export and restart by hand; the settled pool hour holds pool-b's shares to 20:53:09 only. PC 1: the Power Helper hang cleared by the hash lane's restore job at 21:45:58 BST (the 5090 back at 2,865 MHz); the 0.3.23 host slot open from 21:51 BST.
## 13. The one-minute move to 2720d8d2 and the 0.3.23 Mac entry (22:1x to 22:3x BST)
**The pull path (main's order, the fleet's build, live 21:59 BST):** every Devnet 3 box runs a puller that fetches a signed move file from build-1's /fleet/ each minute (the fleet ssh key, namespace igneum-fleet-move, verified against the key the box already trusts), downloads the 2720d8d2 pair as a tarball from /srv/artefacts, sha-checks it, posts FETCHED to the intake, and at the named minute restarts node and miner together from its running environment, posting APPLIED with the version, digest, peers and synced; the puller's install rides ssh once per box, after which a move never needs ssh; the dl host's moves/2720d8d2/ folder (both tokens, the hands pair f2cf6a87/fb147dd1, SHA256SUMS, 21:56 BST) stays the apps' path. **The minute: 22:30:00 BST**, published in the move file at 22:16 BST with 33 of 34 nodes fetched (dn3-g1 and g2 on the node-lane pair df6476ed/dfdc6883, every other node on the hands pair); dn3-j1 behind the dead proxy ssh6 falls off onto 83eb50cd alone and rejoins by the pull when its proxy answers (main's cut, named in the move file's notes); the new 3090 prover dn3-q05 applies on its first fetch. build-1's seed, node1-dn3 and observer restarted at 22:30:54 BST on the hands pair, each "igneumd/2.1.0-2720d8d2", digest ba75bf6f, the N15 latency-ladder line, 21 peers within the first minute. The fleet's per-node APPLIED lines and the first lock on the ba75bf6f side follow.
**0.3.23 Mac entry LIVE 22:31:50 BST (the copy at 22:30:28, the minute), both token folders, channel devnet-3:** Igneum-Miner-0.3.23-2720d8d2.dmg 4ada2807 (45,465,754 B; app 7c7489ac, the Mac node pair efff01cd/45c4c68f from 2720d8d2; packaged on the new token), interface 1.0.2 with ui_version stamped, the floor file kept, both manifests same fields, read back from both folders; the HiveOS alias on igneum-hive-0.3.23.tar.gz 0d716ed9 (the 2720d8d2 hive pair, both kit zips) at the same deploy; armed by r0323/publish-at-minute.sh on the fleet's minute (rule 5's digest-moving form). The Windows entry and the card wait on take 2.
**0.3.23 Windows take 1 FAIL (22:07 BST), install-close-23's own gap:** the installer sent the engine api/quit, the window host (alive since 21:20 BST) never stopped because stop-igneum.ps1 sits only in [UninstallRun] and CloseApplications cannot close it with the message box suppressed; the running 0.3.21 engine does not read the new install-running flag (expected) and relaunched 45 s after the quit from the host; every file stayed 0.3.21. Two more: the installer came out named Igneum-Miner-Setup-0.3.0.exe (BUILD-INSTALLER.bat passes no version; the job now passes -Version from version.h and asserts the output name; the batch fix is a packaging commit), and the stale flag is removed by a one-file job. The fix (the update-return lane): the .iss install path runs the stop step itself (the host by path after the quit, then the unlock wait), packaging only, the crate unchanged at 7c7489ac, so it rides 0.3.23; the kit re-cuts from it, take 2 in the same shape, the smoke read the gate (the known-failed shape: an install over a running 0.3.21 host, read on PC 2). The test the lane wrote asserted the stop script's text, not the install path's execution.
**PC 1 (22:2x BST):** the Power Helper task's process dies at once after each start since 22:08 BST (six "helper started" lines, no command processed, the task Running with no process; its last processed command 21:45, its idle exit at 22:05 clean; the app's tune idle), so the 5080 grids took unlocked rows only and the hash lane runs the lock-free jobs first (the 9070 XT G1 and ladder from 22:27 BST, then the family run and the v5 bench), the locked grids and the SM-sparse job returning when the helper answers; the helper's death is a 0.3.24 item for the update-return lane (the helper writes its reason before exiting, the engine shows "power control: helper not running (<reason>)"). The pool lane's 0.3.24 strings: fork pool-prepare-node 95ae3e50 on the v5 object tip (the pool-mode seed-change exit and the prepare deadline), daemon pool-finish-22 05f86a7f (the node connections reconnect by themselves after a node restart; the rejection counters), register MF-15 ff17216d; the split switch stays never. The 0.3.24 object pairs with the frozen class-v5 1c420786; the post-freeze fix 8ca66afa (AP-F4-1's weak-day redraw, the verified last resort; the pinned packs and fingerprint unchanged) is 0.3.25's.

View file

@ -2,11 +2,12 @@
// {now, state (incl. height = chain block number), blocks (last 90 s, or ?window=N up to 1800, at most 1,800 blocks), miners (last 10 min), events (last 30), finality (checkpoints, newest lock),
// proving (activation and 10-minute counts; every chain block carries shards: [{i, state, prover, lag, payout}])}.
// Five queries, all on indexed columns. Cached one second at the edge.
// LIVE_TABLE_PREFIX (default empty) reads a test observer's tables (fintest_live_*, provtest_live_*) instead of the devnet's.
// LIVE_TABLE_PREFIX (default dn3_: Devnet 3, the igneum-observer-dn3 unit on build-1, chain id 4463, igneum-devnet-3, from
// 7 October 2026 evening by the founder word) reads another observer's tables (empty = the first devnet's, fintest_live_*, provtest_live_*).
// Zero dependencies: Neon's HTTP SQL endpoint over Node's built-in fetch.
const STALE_AFTER_S = 30;
const T = (process.env.LIVE_TABLE_PREFIX || '').replace(/[^a-z0-9_]/gi, '');
const T = (process.env.LIVE_TABLE_PREFIX === undefined ? 'dn3_' : process.env.LIVE_TABLE_PREFIX).replace(/[^a-z0-9_]/gi, '');
function neon() {
const url = process.env.DATABASE_URL;

View file

@ -222,7 +222,7 @@ details.tablebar summary{display:flex;align-items:center}
<svg class="brand-mark" viewBox="0 0 1024 1024" aria-hidden="true"><rect width="1024" height="1024" rx="160" fill="#0C0C0E"></rect><g transform="translate(166.95 166.95) scale(6.901)"><polygon points="50,4 74,34 67,58 80,54 61,96 39,96 20,54 33,58 26,34" fill="#F2541B"></polygon><polygon points="50,42 59,58 50,82 41,58" fill="#0C0C0E"></polygon></g></svg>
<span class="word">IGNEUM</span>
</a>
<span class="devnet" id="nav-devnet" title="Coins have no value. The chain may reset."><span class="dot off" id="foot-dot"></span><span id="foot-state">devnet</span></span>
<span class="devnet" id="nav-devnet" title="igneum-devnet-3, chain id 4463. Coins have no value. The chain may reset."><span class="dot off" id="foot-dot"></span><span id="foot-state">devnet</span></span>
<div class="nav-groups" id="nav-groups" role="list">
<div class="nav-group" role="listitem"><button type="button" class="group-btn" id="nav-btn-mine" data-group="mine" aria-expanded="false" aria-controls="nav-panel-mine" aria-haspopup="true">Mine<svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="m7 10 5 5 5-5"/></svg></button></div>
<div class="nav-group" role="listitem"><button type="button" class="group-btn" id="nav-btn-network" data-group="network" data-active aria-expanded="false" aria-controls="nav-panel-network" aria-haspopup="true">Network<svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="m7 10 5 5 5-5"/></svg></button></div>
@ -298,7 +298,7 @@ details.tablebar summary{display:flex;align-items:center}
</div>
<div class="sheet-foot">
<a href="/download" class="btn primary big" data-nav="download">Download</a>
<div class="sheet-social"><a href="https://discord.gg/igneum" target="_blank" rel="noopener">Discord</a><a href="https://git.igneum.network/igneum-network/spec" target="_blank" rel="noopener">GitHub</a><a href="https://www.reddit.com/user/Igneum_network/" target="_blank" rel="noopener">Reddit</a></div>
<div class="sheet-social"><a href="https://discord.gg/igneum" target="_blank" rel="noopener">Discord</a><a href="https://github.com/igneum-network/spec" target="_blank" rel="noopener">GitHub</a><a href="https://www.reddit.com/user/Igneum_network/" target="_blank" rel="noopener">Reddit</a></div>
</div>
</div>
</div>
@ -496,12 +496,12 @@ details.tablebar summary{display:flex;align-items:center}
<a href="/" class="logo" aria-label="Igneum home"><svg class="brand-mark" viewBox="0 0 1024 1024" aria-hidden="true"><rect width="1024" height="1024" rx="160" fill="#0C0C0E"></rect><g transform="translate(166.95 166.95) scale(6.901)"><polygon points="50,4 74,34 67,58 80,54 61,96 39,96 20,54 33,58 26,34" fill="#F2541B"></polygon><polygon points="50,42 59,58 50,82 41,58" fill="#0C0C0E"></polygon></g></svg><span class="word">IGNEUM</span><span class="stop">.</span></a>
<p>Mined by GPUs. Proven by fire.</p>
<div class="social" aria-label="Igneum elsewhere">
<a href="https://git.igneum.network/igneum-network/spec" target="_blank" rel="noopener" aria-label="Igneum on GitHub: the spec, the vectors and the issues"><svg viewBox="0 0 24 24" width="22" height="22" fill="currentColor" aria-hidden="true"><path d="M12 .5C5.7.5.5 5.7.5 12c0 5.1 3.3 9.4 7.9 10.9.6.1.8-.3.8-.6v-2.1c-3.2.7-3.9-1.4-3.9-1.4-.5-1.3-1.3-1.7-1.3-1.7-1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1 1.8 2.7 1.3 3.4 1 .1-.8.4-1.3.7-1.6-2.6-.3-5.3-1.3-5.3-5.7 0-1.3.5-2.3 1.2-3.1-.1-.3-.5-1.5.1-3.1 0 0 1-.3 3.2 1.2.9-.3 1.9-.4 2.9-.4s2 .1 2.9.4c2.2-1.5 3.2-1.2 3.2-1.2.6 1.6.2 2.8.1 3.1.8.8 1.2 1.8 1.2 3.1 0 4.4-2.7 5.4-5.3 5.7.4.4.8 1.1.8 2.2v3.2c0 .3.2.7.8.6 4.6-1.5 7.9-5.8 7.9-10.9C23.5 5.7 18.3.5 12 .5z"></path></svg></a>
<a href="https://github.com/igneum-network/spec" target="_blank" rel="noopener" aria-label="Igneum on GitHub: the spec, the vectors and the issues"><svg viewBox="0 0 24 24" width="22" height="22" fill="currentColor" aria-hidden="true"><path d="M12 .5C5.7.5.5 5.7.5 12c0 5.1 3.3 9.4 7.9 10.9.6.1.8-.3.8-.6v-2.1c-3.2.7-3.9-1.4-3.9-1.4-.5-1.3-1.3-1.7-1.3-1.7-1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1 1.8 2.7 1.3 3.4 1 .1-.8.4-1.3.7-1.6-2.6-.3-5.3-1.3-5.3-5.7 0-1.3.5-2.3 1.2-3.1-.1-.3-.5-1.5.1-3.1 0 0 1-.3 3.2 1.2.9-.3 1.9-.4 2.9-.4s2 .1 2.9.4c2.2-1.5 3.2-1.2 3.2-1.2.6 1.6.2 2.8.1 3.1.8.8 1.2 1.8 1.2 3.1 0 4.4-2.7 5.4-5.3 5.7.4.4.8 1.1.8 2.2v3.2c0 .3.2.7.8.6 4.6-1.5 7.9-5.8 7.9-10.9C23.5 5.7 18.3.5 12 .5z"></path></svg></a>
<a href="https://discord.gg/igneum" target="_blank" rel="noopener" aria-label="The Igneum Discord"><svg viewBox="0 0 24 24" width="22" height="22" fill="currentColor" aria-hidden="true"><path d="M19.6 5.3A17 17 0 0 0 15.4 4l-.2.4a15.6 15.6 0 0 1 3.9 1.9 13.6 13.6 0 0 0-14.2 0A15.6 15.6 0 0 1 8.8 4.4L8.6 4a17 17 0 0 0-4.2 1.3C1.8 9.2 1.1 13 1.4 16.7a17.1 17.1 0 0 0 5.2 2.6l1.1-1.8a10.8 10.8 0 0 1-1.7-.8l.4-.3a12.2 12.2 0 0 0 11.2 0l.4.3-1.7.8 1.1 1.8a17 17 0 0 0 5.2-2.6c.4-4.3-.7-8-3-11.4zM8.7 14.4c-1 0-1.8-.9-1.8-2.1s.8-2.1 1.8-2.1 1.9 1 1.8 2.1c0 1.2-.8 2.1-1.8 2.1zm6.6 0c-1 0-1.8-.9-1.8-2.1s.8-2.1 1.8-2.1 1.9 1 1.8 2.1c0 1.2-.8 2.1-1.8 2.1z"></path></svg></a>
<a data-social="reddit" href="https://www.reddit.com/user/Igneum_network/" target="_blank" rel="noopener" aria-label="Igneum on Reddit"><svg viewBox="0 0 24 24" width="22" height="22" fill="currentColor" aria-hidden="true"><path d="M22 12.1a2.2 2.2 0 0 0-3.7-1.6 10.8 10.8 0 0 0-5.8-1.8l1-4.6 3.2.7a1.5 1.5 0 1 0 .2-.9l-3.6-.8a.5.5 0 0 0-.5.4l-1.1 5.2a10.8 10.8 0 0 0-5.9 1.8A2.2 2.2 0 1 0 3.4 14a4.3 4.3 0 0 0 0 .7c0 3.4 3.9 6.1 8.6 6.1s8.6-2.7 8.6-6.1a4.3 4.3 0 0 0 0-.7 2.2 2.2 0 0 0 1.4-1.9zM7 13.6a1.5 1.5 0 1 1 1.5 1.5A1.5 1.5 0 0 1 7 13.6zm8.6 4.1a5.7 5.7 0 0 1-3.6 1.1 5.7 5.7 0 0 1-3.6-1.1.4.4 0 0 1 .6-.6 4.9 4.9 0 0 0 3 .9 4.9 4.9 0 0 0 3-.9.4.4 0 0 1 .6.6zm-.3-2.6a1.5 1.5 0 1 1 1.5-1.5 1.5 1.5 0 0 1-1.5 1.5z"></path></svg></a>
</div>
<div class="footer-dl" aria-label="Download Ember"><a href="/download#windows"><span class="osmark mini" data-os="windows" title="Windows"><svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true" focusable="false" fill="currentColor"><path d="M3 5.6l7.3-1v7.1H3zM11.4 4.4L21 3v8.7h-9.6zM3 12.3h7.3v7.1L3 18.4zM11.4 12.3H21V21l-9.6-1.4z"/></svg></span>Windows</a><a href="/download#mac"><span class="osmark mini" data-os="mac" title="macOS"><svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true" focusable="false" fill="currentColor"><path d="M16.4 12.6c0-2.5 2-3.6 2.1-3.7-1.2-1.7-3-1.9-3.6-2-1.5-.2-3 .9-3.8.9-.8 0-2-.9-3.3-.8-1.7 0-3.2 1-4.1 2.5-1.8 3-.5 7.6 1.3 10.1.9 1.2 1.9 2.6 3.2 2.5 1.3 0 1.8-.8 3.3-.8 1.6 0 2 .8 3.3.8 1.4 0 2.3-1.2 3.1-2.5 1-1.4 1.4-2.8 1.4-2.9 0 0-2.7-1-2.9-4.1zM13.9 5.3c.7-.8 1.2-2 1-3.2-1 0-2.2.7-2.9 1.5-.6.7-1.2 1.9-1 3 1.1.1 2.2-.5 2.9-1.3z"/></svg></span>macOS</a><a href="/download#linux"><span class="osmark mini" 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>Linux</a><a href="/download#hive"><span class="osmark mini" 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>HiveOS</a></div>
<a href="https://git.igneum.network/igneum-network/spec" target="_blank" rel="noopener" class="text-link">Specification and test vectors<svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 18 18 6M6 6h12v12"/></svg></a>
<a href="https://github.com/igneum-network/spec" target="_blank" rel="noopener" class="text-link">Specification and test vectors<svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 18 18 6M6 6h12v12"/></svg></a>
</div>
<div class="footer-column"><h4>Run</h4><div>
<a href="/download">Download Ember</a>
@ -529,7 +529,7 @@ details.tablebar summary{display:flex;align-items:center}
<a href="/download">Downloads: devnet build</a>
<a href="https://discord.gg/igneum" target="_blank" rel="noopener">Community Discord</a>
<a href="mailto:hello@igneum.network">hello@igneum.network</a>
<a href="https://git.igneum.network/igneum-network/spec/issues" rel="noopener">An issue on the spec</a>
<a href="https://github.com/igneum-network/spec/issues" rel="noopener">An issue on the spec</a>
</div></div>
</div>
<div class="footer-bottom">

View file

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

@ -0,0 +1,52 @@
#!/usr/bin/env bash
# gh's ACTIVE account on this Mac is the stored Igneum entry and nothing else (main's rule, 7 October 2026, 21:5x UK: at 21:41 a lane
# switched gh to the second owner's login, which belongs to other projects and must never touch Igneum; nobody could say which lane).
# The stored entry's name is in ~/.config/igneum/gh-user (the login's pre-rename spelling until a re-login; never in the repository).
# Runs before a push (tools/ci/pre-push.sh --hook) and before a landing (tools/ci/merge-to-master.sh); refuses with the line otherwise.
# A machine without gh, or without the stored-name file, is not this Mac: skip with a line (CI runners, the boxes).
#
# tools/ci/gh-account-check.sh # exit 0 when gh's active account is the stored entry (or gh / the file is absent, with a skip line); exit 1 with the line otherwise
# tools/ci/gh-account-check.sh --self-test # a fake gh whose active account is another login is refused and named; the stored one passes;
# # a status with no active account is refused; no gh on PATH skips
set -uo pipefail
STORED_FILE="${IGNEUM_GH_USER_FILE:-$HOME/.config/igneum/gh-user}"
active_account() { # from `gh auth status`: the account whose block carries "Active account: true"
gh auth status 2>&1 | awk '
/account [^ ]+ \(/ { for (i = 1; i <= NF; i++) if ($i == "account") { acct = $(i + 1) } }
/Active account: true/ { print acct; exit }'
}
check() {
local stored active
command -v gh >/dev/null 2>&1 || { echo "gh-account: skipped, no gh on this machine (the rule binds the Mac that pushes)"; return 0; }
[ -s "$STORED_FILE" ] || { echo "gh-account: skipped, no stored-name file at $STORED_FILE (not the pushing Mac)"; return 0; }
stored="$(tr -d '[:space:]' < "$STORED_FILE")"
active="$(active_account)"
if [ -z "$active" ]; then echo "gh-account: REFUSED. gh has no active account (gh auth status); the Igneum rule: the stored entry, and only it, is active on this Mac: gh auth switch --user $stored" >&2; return 1; fi
if [ "$active" != "$stored" ]; then echo "gh-account: REFUSED. gh's active account is $active, not the stored Igneum entry; that login must never touch Igneum (rule of 7 October 2026). Run: gh auth switch --user $stored" >&2; return 1; fi
echo "gh-account: gh's active account is the stored Igneum entry"
}
if [ "${1:-}" = --self-test ]; then
d="$(mktemp -d)"; trap 'rm -rf "$d"' EXIT
printf 'stored-login\n' > "$d/gh-user"
fake="$d/bin/gh"; mkdir -p "$d/bin"
cat > "$fake" <<'FAKE'
#!/usr/bin/env bash
# fake gh: the status text, with the active account named by $FAKE_ACTIVE (empty = none active)
printf 'github.com\n'
printf ' X Failed to log in to github.com account stored-login (keyring)\n - Active account: %s\n - Token: gho_x\n\n' "$([ "${FAKE_ACTIVE:-}" = stored-login ] && echo true || echo false)"
printf ' %s Logged in to github.com account other-login (keyring)\n - Active account: %s\n - Token: gho_y\n' "$([ "${FAKE_ACTIVE:-}" = other-login ] && echo '✓' || echo '✓')" "$([ "${FAKE_ACTIVE:-}" = other-login ] && echo true || echo false)"
FAKE
chmod +x "$fake"; fails=0
out="$(PATH="$d/bin:$PATH" IGNEUM_GH_USER_FILE="$d/gh-user" FAKE_ACTIVE=other-login bash "$0" 2>&1)" && { echo "self-test failed: another active login was not refused"; fails=1; }
case "$out" in *"REFUSED. gh's active account is other-login"*) ;; *) echo "self-test failed: the refusal did not name the active login: $out"; fails=1 ;; esac
PATH="$d/bin:$PATH" IGNEUM_GH_USER_FILE="$d/gh-user" FAKE_ACTIVE=stored-login bash "$0" >/dev/null 2>&1 || { echo "self-test failed: the stored active login was refused"; fails=1; }
PATH="$d/bin:$PATH" IGNEUM_GH_USER_FILE="$d/gh-user" FAKE_ACTIVE= bash "$0" >/dev/null 2>&1 && { echo "self-test failed: no active account was let through"; fails=1; }
# a machine without gh: the system binaries on PATH, no gh
out="$(PATH="/usr/bin:/bin" IGNEUM_GH_USER_FILE="$d/gh-user" bash "$0" 2>&1)" || { echo "self-test failed: a machine without gh did not skip"; fails=1; }
case "$out" in *"skipped, no gh"*) ;; *) echo "self-test failed: no skip line without gh: $out"; fails=1 ;; esac
[ "$fails" = 0 ] && echo "self-test passed: another active login is refused and named, the stored one passes, no active account is refused, a machine without gh skips with its line"
exit $fails
fi
check

View file

@ -138,6 +138,7 @@ success 4 u push run
exit $fails
fi
[ -z "$(git status --porcelain --untracked-files=no)" ] || { echo "merge-to-master: the tree has uncommitted tracked changes; commit first" >&2; exit 1; }
bash tools/ci/gh-account-check.sh || exit 1 # gh's active account on this Mac is the stored Igneum entry (main's rule, 7 October 2026, 21:5x UK)
SHA=$(git rev-parse "$BRANCH"); G=$(cd "$(git rev-parse --git-common-dir)" && pwd -P)
if [ ! -f "$G/igneum-gate-green/$SHA" ]; then
echo "merge-to-master: no green stamp for ${SHA:0:8}; running the full gate on the branch first (then CI's own verdict on it is read)"

View file

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