Merge master 3de23ddeb into tv-04-security-budget under the master-landing lock

This commit is contained in:
igneum-labs 2026-10-09 08:30:17 +00:00
commit 7f02f1b2f3
42 changed files with 2463 additions and 35 deletions

View file

@ -298,7 +298,7 @@ Pin: Leadership tests, first box (Ergo).
Status: Conceded (8 October 2026): no "number one" claim before comparative results and adoption exist.
Answer: No present-tense rank is served. Published claims name the evaluated release, evidence and boundary conditions. An independently substantiated pass of the acceptance standard would support a serious case that Igneum is in contention for leadership among GPU-first networks and would not award a numerical rank; it would not guarantee adoption, perpetual GPU profitability, defeat of all future chips or a numerical number-one ranking. The tests that would earn it are miners staying through hard conditions, customers repeatedly paying for proofs, and the network running without the founding team (D5); none is met yet.
Answer: No present-tense rank is served. Published claims name the evaluated release, evidence and boundary conditions. Designed to compete for leadership among GPU-first networks. That is the scoped wording until a ranking is measured (the Token Value volume’s rule VR-30 and decision D06, scoped claims only, ratified 9 October 2026): an independently substantiated pass of the acceptance standard would not award a numerical rank, nor guarantee adoption, perpetual GPU profitability or the defeat of every future chip. The tests that would earn it are miners staying through hard conditions, customers repeatedly paying for proofs, and the network running without the founding team (D5); none is met yet.
Evidence: `docs/plans/igneum-2.0.md` (Leadership tests, second to fifth boxes).

47
docs/ops/build-queue.md Normal file
View file

@ -0,0 +1,47 @@
# Build queue: the nine build boxes and their standing work
Set up 9 October 2026, 09:1x UK, on the order relayed by the coordinator: every box has one owner lane and a standing queue, a reader on
build-1 reads this file every 30 minutes against the merged workers.json and reports an idle box as a fault. The live copy is
build-1:/srv/queue/build-queue.md (the reader reads that one; this file is its source on master, landed through the gate).
## Rules that bind every entry
- Kill by recorded pid or pid file only, never by a name or a pattern (`pkill` and `killall` refuse on every box, exit 97;
tools/ci/kill-by-name-check.sh reads the tree). Every job writes its pid file before it starts; a stop reads that file.
- A box never builds for the fleet: fleet binaries come only from the cross-build kit (x86-64-v3, the ISA gate at 0 AVX-512 lines).
- Nothing on the chain by hand; the devnet, hub-1 and every node are the node and fleet lanes' through their own harnesses.
- An entry carries its owner lane, its box, its pid-file path and its clock (UK). A box with no live pid file from its queue and
a load under 1.0 for a 30-minute read is a fault, reported by the reader to the coordinator (/srv/queue/faults.log).
- A lane that finishes an entry replaces it with the next or hands the box back here with a line; an empty box is the fault.
## Owners (one lane per box)
| box | threads | owner lane | standing use |
|---|---|---|---|
| build-1 | 96 | build-server lane | cuts and kits, the hands (observer-node, node1), the capacity fuzz slices, the workers page, the queue reader |
| build-2 | 96 | site lane | the site gate (Playwright), the scene-parity suites; spare slots for the node lane's suites |
| build-3 | 32 | node lane | the long consensus fuzz and property suites (kaspa-consensus, kaspa-consensus-core) |
| build-4 | 96 | adversary lane | the chip model (OpenROAD, kepler-formal): the 20 to 25 percent floorplan for the converged SPEF row |
| build-5 | 32 vCPU | research lane | TV-02's two independent supply-replay implementations, CPU only |
| build-6 | 32 vCPU | HEAL lane | the long p2p and exec property suites (kaspa-p2p-flows, igneum-exec) |
| build-7 | 96 | node lane | the 2.0.3 flows items: their suites and known-failed tests; the heal-on harness |
| build-8 | 96 | fleet lane | 2.0.3 kit canaries the moment a chain moves; the heal-off harness; the dn4 roll node |
| build-9 | 96 | steward | the board's rows that need only a box (ZKP, EVM, VER fixtures), the pow fuzz on the side |
## Queue (first entries, 9 October 2026)
| # | entry | owner | box | pid file | clock (UK) |
|---|---|---|---|---|---|
| 1 | 2.0.3 flows items: the suites and the known-failed tests on release-2.0.3-node-k6 (bf60948a and after) | node lane | build-7 | /srv/queue/pids/build-7-node-flows.pid | from 09:30, continuous |
| 2 | long fuzz and property suites, consensus (kaspa-consensus, kaspa-consensus-core) | node lane | build-3 | /srv/queue/pids/build-3-consensus-fuzz.pid | from 09:45, continuous |
| 3 | long fuzz and property suites, exec and p2p (igneum-exec, kaspa-p2p-flows, kaspa-p2p-lib) | HEAL lane | build-6 | /srv/queue/pids/build-6-exec-p2p-fuzz.pid | from 09:45, continuous |
| 4 | long fuzz and property suites, pow (kaspa-pow, igneum-pow mixer and scratch) | node lane | build-9 | /srv/queue/pids/build-9-pow-fuzz.pid | from 09:45, continuous |
| 5 | TV-02: two independent supply-replay implementations, CPU only | research lane | build-5 | /srv/queue/pids/build-5-tv02-replay.pid | from 10:00, until both agree |
| 6 | chip model: the 20 to 25 percent floorplan for the converged SPEF row (about four host-hours) | adversary lane | build-4 | /srv/queue/pids/build-4-floorplan.pid | running (OpenROAD 844081, kepler-formal 1797623), about 13:30 |
| 7 | the board's rows that need only a box: ZKP, EVM and VER fixtures (the steward names them) | steward | build-9 | /srv/queue/pids/build-9-board-rows.pid | from 10:00, by row |
| 8 | 2.0.3 kit canaries the moment a chain moves (the evidence kits under /srv/workers/fleet) | fleet lane | build-8 | /srv/queue/pids/build-8-kit-canary.pid | on the chain's move |
| 9 | the site gate and the scene-parity suites | site lane | build-2 | /srv/queue/pids/build-2-site-gate.pid | on each landing |
| 10 | cuts, kits, the hands, the capacity fuzz slices, the workers page, the queue reader | build-server lane | build-1 | /srv/queue/queue-reader.pid and the cut pid files under the cutter's scratch | continuous |
A lane starts its entry by writing its pid file (`<pid> <start UTC> <label>`, the format of the slot files) under /srv/queue/pids on
build-1 (or on its own box, mirrored there by the lane) and ends it by removing the file and replacing the entry.

File diff suppressed because one or more lines are too long

View file

@ -1,6 +1,6 @@
# Igneum 2.0 test harness map
Generated from tools/ci/test-map.json by tools/ci/test-map-doc.mjs; edit the JSON, never this page. Registry: docs/plans/igneum-2.0-test-registry.json (190 cases).
Generated from tools/ci/test-map.json by tools/ci/test-map-doc.mjs; edit the JSON, never this page. Registry: docs/plans/igneum-2.0-test-registry.json (262 cases).
Rule: a case maps to a cell only where the cell's tests visibly answer it; coverage names what the cell proves and what remains; a mapped cell's green writes RUNNING, PASS only when coverage is full and the evidence file exists; an automated case with no cell reads NOT RUN with its reason, never PASS by inference.
@ -573,7 +573,39 @@ Rule: a case maps to a cell only where the cell's tests visibly answer it; cover
- R2-F14-R01 Public build has no default arbitrary remote execution and a documented least-privilege boundary.: R2-F14 (P1, the external review): the regression's harness is the owner lane's (app lane, relay lane); not yet named in the map
- R2-F14-R02 Automatic updates off remains off for urgent manifests until explicit action.: R2-F14 (P1, the external review): the regression's harness is the owner lane's (app lane, relay lane); not yet named in the map
- R2-F14-R03 A compromised fleet/update signing key cannot silently acquire wallet access or activate a consensus change.: R2-F14 (P1, the external review): the regression's harness is the owner lane's (app lane, relay lane); not yet named in the map
- TV-01 Resolve and freeze the monetary contract: TV-01 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Protocol + independent panel); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-02 Reproduce complete supply accounting: TV-02 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Protocol + measurement); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-03 Fair launch and early distribution: TV-03 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Release + ecosystem); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-04 Security budget without price rescue: TV-04 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Economics + protocol); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-05 Strongest surviving specialist: TV-05 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Hardware reviewer + economics); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-06 Every fee and payment reconciles: TV-06 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Economics + application); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-07 Ownership-critical engineering closure: TV-07 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Protocol + independent security); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-08 Custody and recovery usability: TV-08 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Wallet + independent users); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-09 Independently verifiable settlement: TV-09 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Wallet + consensus); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-10 Minimum-node and sponsored-use limits: TV-10 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Protocol + application); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-11 Pooled payments without authority loss: TV-11 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Pool + independent operators); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-12 Reproducible and consented software: TV-12 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Release + independent security); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-13 Committed maintenance coverage: TV-13 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Maintainers + independent finance); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-14 Founder-absent operating exercise: TV-14 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Independent operations panel); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-15 Governance and authority boundaries: TV-15 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Protocol + governance); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-16 Two functional independent access routes: TV-16 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Ecosystem + independent reviewer); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-17 Supply-price-liquidity measurement: TV-17 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Measurement steward); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-18 Real versus incentivised demand: TV-18 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Economics + measurement); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-19 Independent developer adoption: TV-19 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Ecosystem + external developers); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-20 Programmable payment acceptance: TV-20 (token value gate, scope PAYMENT CAPABILITY): no gate executed; the harness is the owner role's (Application + wallet); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-21 Useful paid proof demand: TV-21 (token value gate, scope PAID WORK CAPABILITY): no gate executed; the harness is the owner role's (Proving + independent customers); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-22 Replaceable work-market gateway: TV-22 (token value gate, scope WORK MARKET CAPABILITY): no gate executed; the harness is the owner role's (Application + independent operators); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-23 Obligation-based deposits: TV-23 (token value gate, scope SERVICE BONDS IF ENABLED): no gate executed; the harness is the owner role's (Economics + security); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-24 AI earns a separate commercial case: TV-24 (token value gate, scope AI IF ENABLED): no gate executed; the harness is the owner role's (Compute + independent reviewer); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-25 Bounded machine purchasing: TV-25 (token value gate, scope AGENTS IF ENABLED): no gate executed; the harness is the owner role's (Application + security); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-26 Cryptographic migration readiness: TV-26 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Cryptography + protocol); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-27 Private data and oracle assurance: TV-27 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Security + product); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-28 Stable assets and bridge isolation: TV-28 (token value gate, scope STABLE/BRIDGE IF ENABLED): no gate executed; the harness is the owner role's (Application + security + counsel); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-29 Growth without inaccessible verification: TV-29 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Protocol + operations); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-30 Energy and claims discipline: TV-30 (token value gate, scope CORE): no gate executed; the harness is the owner role's (GPU + measurement); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-31 Public-claim and activity review: TV-31 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Qualified counsel); a supplemental layer, never counted as independent evidence; not yet named in the map
- TV-32 Observed leadership, not a roadmap certificate: TV-32 (token value gate, scope LEADERSHIP CLAIM): no gate executed; the harness is the owner role's (Independent acceptance panel); a supplemental layer, never counted as independent evidence; not yet named in the map
## Count
171 automated cases: 88 mapped to a cell, 141 NOT RUN with a reason.
203 automated cases: 88 mapped to a cell, 173 NOT RUN with a reason.

File diff suppressed because it is too large Load diff

View file

@ -302,6 +302,8 @@ open(out, "w").write(json.dumps(m, sort_keys=True, separators=(",", ":"), ensure
PY
"$SIGNER" sign "$KEY" "$NEW" > "$NEW.sig"
"$SIGNER" verify "$PUB" "$NEW" "$NEW.sig"
# the served-file identity guard (9 October 2026, 09:15 UK class): the manifest is a served file; a hit refuses the publish and is logged
bash "$TOOLS/ci/served-identity-guard.sh" "$NEW" || { echo "publish-manifest: REFUSED by the served-file identity guard (above); nothing written" >&2; rm -f "$NEW"; exit 1; }
mv "$NEW" "$DEST/$MF"
mv "$NEW.sig" "$DEST/$MF.sig"
echo "manifest: $DEST/$MF"

View file

@ -102,6 +102,7 @@ PY
log " $name: would write $(python3 -c 'import json,sys; m=json.load(open(sys.argv[1])); print(m.get("version"), "+".join(m.get("platforms",{})))' "$tmp") with URLs under $BASE_PUB, signed"
rm -f "$tmp"; return 0
fi
bash "$(cd "$(dirname "$0")/../.." && pwd)/tools/ci/served-identity-guard.sh" "$tmp" | scrub || { echo "publish-public: REFUSED by the served-file identity guard (above); nothing written" >&2; rm -f "$tmp"; return 1; } # a served file (9 October 2026 class)
"$SIGNER" sign "$KEY" "$tmp" > "$tmp.sig"
"$SIGNER" verify "$PUB_KEY" "$tmp" "$tmp.sig" >/dev/null
chmod 644 "$tmp" "$tmp.sig"; mv "$tmp" "$out"; mv "$tmp.sig" "$out.sig"

View file

@ -52,7 +52,7 @@ What the 5 October rotation already did: the relay token (4 October), the intake
|---|---|---|
| Items (text, title, who, kind, flags, read and done marks) | Neon table `relay_items` (database `igneum`) | body 1 MB |
| Machines (name, hostname, role, GPU and WSL facts, last seen) | Neon table `relay_machines` | |
| Files | Vercel Blob store `igneum-relay` (public URLs with random path and suffix, London) | 50 MB per file through a client token; 4 MB when pushed through the function |
| Files | Vercel Blob store `igneum-relay` (public URLs with random path and suffix, London) | 128 MB per file through a client token; 4 MB when pushed through the function |
| The token and keys | `~/.config/igneum/relay-token`, `relay-key`, `relay-run-key`, `relay-machines/<name>`; project env (`RELAY_TOKEN`, `RELAY_KEY`, `RELAY_RUN_PUB`, `LOG_INTAKE_KEY`, `LOG_INTAKE_KEY_NEXT`) | never in the repo |
| Retention | rows older than 30 days are deleted with their blobs, checked on a feed read at most every 10 minutes per instance; `delete` removes the blob with the row (X26) | 30 days |

View file

@ -7,7 +7,7 @@ import { randomBytes } from 'node:crypto';
import { authVia } from './guard.mjs';
export const MAX_INLINE = 4 * 1024 * 1024; // raw upload through the function (Vercel body cap is 4.5 MB)
export const MAX_BLOB = 50 * 1024 * 1024; // direct-to-Blob upload with a client token
export const MAX_BLOB = 128 * 1024 * 1024; // direct-to-Blob upload with a client token (128 MB since 9 October 2026: a 2.0.2 Setup is 63.9 MB and the 50 MB token refused it with a 403)
export const MAX_BODY = 1024 * 1024; // text body per item
// start-app (MF-11, 7 October 2026): a signed, tagged task like `run`, but the agent executes nothing from its body: it
// starts the installed Igneum Miner (hidden console, as the user) and reports whether an engine answered. The body

View file

@ -291,7 +291,7 @@ main{padding-bottom:100px}.card{background:var(--row);border:1px solid var(--lin
<div class="tbl"><table><thead><tr><th>Block</th><th class="n">Number</th><th class="n">DAA</th><th class="n">Blue score</th><th>Colour</th><th class="n">Subsidy</th><th class="n">Txs</th><th>Time</th></tr></thead><tbody id="blocks"></tbody></table></div>
</div>
</div>
<p class="note" data-four-words><b>Four words, used exactly.</b> <b>Included</b>: the block carries the transaction. <b>Executed</b>: the EVM ran it and the result is on the record. <b>Proven</b>: a valid proof record for its block was carried and paid. <b>Finalised</b>: the block sits under a certified checkpoint, two thirds of active and of total weight. A pause in finality is shown as a pause, never as a block losing its lock. <b>Recovery lock</b>: after a full weight window with no lock, a checkpoint signed by more than half of the anchored weight; shown as a recovery lock, never as finalised. On the devnet’s current node line the lock kind is reported by the forming node only: a node that received the certificate records an ordinary lock, so a recovery lock can read as final on a receiving node until the 2.0.3 node line lands. The definitions are the finality specification’s: <a href="https://git.igneum.network/igneum-network/igneum/src/branch/master/docs/spec/finality-guarantees.md">section 9, the guarantees</a>.</p>
<p class="note" data-four-words><b>Four words, used exactly.</b> <b>Included</b>: the block carries the transaction. <b>Executed</b>: the EVM ran it and the result is on the record. <b>Proven</b>: a valid proof record for its block was carried and paid. <b>Finalised</b>: the block sits under a certified checkpoint, two thirds of active and of total weight. A pause in finality is shown as a pause, never as a block losing its lock. <b>Recovery lock</b>: after a full weight window with no lock, a checkpoint signed by more than half of the anchored weight; shown as a recovery lock, never as final (the Token Value volume’s D05). On the devnet’s current node line the lock kind is reported by the forming node only: a node that received the certificate records an ordinary lock, so a recovery lock can read as final on a receiving node until the 2.0.3 node line lands. The definitions are the finality specification’s: <a href="https://git.igneum.network/igneum-network/igneum/src/branch/master/docs/spec/finality-guarantees.md">section 9, the guarantees</a>.</p>
</main>
<!-- footer:start -->
<footer class="site-footer">

View file

@ -284,7 +284,7 @@ main{padding-bottom:100px}.card{background:var(--row);border:1px solid var(--lin
<div class="card"><div class="viz-head"><h2>Shard plan and proofs</h2><div class="eyebrow" id="proof-eyebrow"></div></div><div id="proofs"></div></div>
<div class="card"><div class="viz-head"><h2>Finality</h2><div class="eyebrow" id="fin-eyebrow"></div></div><div id="finality"></div><div id="recheck"></div></div>
</div>
<p class="note" data-four-words><b>Four words, used exactly.</b> <b>Included</b>: the block carries the transaction. <b>Executed</b>: the EVM ran it and the result is on the record. <b>Proven</b>: a valid proof record for its block was carried and paid. <b>Finalised</b>: the block sits under a certified checkpoint, two thirds of active and of total weight. A pause in finality is shown as a pause, never as a block losing its lock. <b>Recovery lock</b>: after a full weight window with no lock, a checkpoint signed by more than half of the anchored weight; shown as a recovery lock, never as finalised. On the devnet’s current node line the lock kind is reported by the forming node only: a node that received the certificate records an ordinary lock, so a recovery lock can read as final on a receiving node until the 2.0.3 node line lands. The definitions are the finality specification’s: <a href="https://git.igneum.network/igneum-network/igneum/src/branch/master/docs/spec/finality-guarantees.md">section 9, the guarantees</a>.</p>
<p class="note" data-four-words><b>Four words, used exactly.</b> <b>Included</b>: the block carries the transaction. <b>Executed</b>: the EVM ran it and the result is on the record. <b>Proven</b>: a valid proof record for its block was carried and paid. <b>Finalised</b>: the block sits under a certified checkpoint, two thirds of active and of total weight. A pause in finality is shown as a pause, never as a block losing its lock. <b>Recovery lock</b>: after a full weight window with no lock, a checkpoint signed by more than half of the anchored weight; shown as a recovery lock, never as final (the Token Value volume’s D05). On the devnet’s current node line the lock kind is reported by the forming node only: a node that received the certificate records an ordinary lock, so a recovery lock can read as final on a receiving node until the 2.0.3 node line lands. The definitions are the finality specification’s: <a href="https://git.igneum.network/igneum-network/igneum/src/branch/master/docs/spec/finality-guarantees.md">section 9, the guarantees</a>.</p>
</main>
<!-- footer:start -->
<footer class="site-footer">

View file

@ -263,7 +263,7 @@
<li><strong>Proof verification in consensus.</strong> On the Igneum 2.0 devnet, yes, from block zero (verifier_in_consensus set, the node's own start line; running since its first block at 17:14 UK on 8 October 2026). It is the prerequisite of the no-rescue network exercise (Deliverable 5), which is still owed; an earlier devnet ran with the rule off.</li>
<li><strong>Proving on every card.</strong> No. NVIDIA proves; AMD and Apple mine. The proving stack is judged on the full pipeline: inputs, proving, aggregation, verification, payment, memory and the mining income forgone.</li>
<li><strong>What proofs do not give.</strong> Proven execution is not automatically finality. EVM compatibility is not Ethereum security. ZK technology does not automatically make transactions private.</li>
<li><strong>A ranking.</strong> No. Igneum makes no leading or number-one claim. Published claims name the evaluated release, evidence and boundary conditions. An independently substantiated pass of the acceptance standard would support a serious case that Igneum is in contention for leadership among GPU-first networks and would not award a numerical rank; it would not guarantee adoption, perpetual GPU profitability, defeat of all future chips or a numerical number-one ranking. Benchmarks against Ravencoin’s KAWPOW, Ergo and Firo’s reference miner are owed work; no result exists yet.</li>
<li><strong>A ranking.</strong> No. Igneum makes no leading or number-one claim. Published claims name the evaluated release, evidence and boundary conditions. Designed to compete for leadership among GPU-first networks. That is the scoped wording until a ranking is measured (the Token Value volume’s rule VR-30 and decision D06, scoped claims only): an independently substantiated pass of the acceptance standard would not award a numerical rank, nor guarantee adoption, perpetual GPU profitability or the defeat of every future chip. Benchmarks against Ravencoin’s KAWPOW, Ergo and Firo’s reference miner are owed work; no result exists yet.</li>
<li><strong>A finished protocol.</strong> The sustained-mining finality rule is the newest piece and the one that external review will try hardest to break. The specification, the review and the benchmarks are published as they happen.</li>
</ul>
<p>Everything in this document is subject to the gates on the roadmap. Nothing in it is an offer to sell anything. Found an error, or a criticism this document does not answer? Email <a href="mailto:hello@igneum.network">hello@igneum.network</a>, or open an issue on the public specification repository: <a href="https://git.igneum.network/igneum-network/spec/issues" rel="noopener">git.igneum.network/igneum-network/spec/issues</a>. Post reaches Igneum Labs LTD, Unit IH-00-01-01-OF-01, Level 01, Innovation One, Dubai International Financial Centre.</p></div>

View file

@ -226,7 +226,7 @@
<tr><td>Launch rate</td><td class="num"><span data-rm="fees.emission.launch_rate_base_units_per_daa_second">31,688,087,810,000,000,000</span> base units a DAA second at 18 decimals (the literal 3,168,808,781 of <code>emission.rs</code> 104, 10<sup>9</sup> IGN x 10<sup>8</sup> / 31,557,600 floored, widened by <code>rescaled(DEVNET_DECIMALS, EVM_DECIMALS)</code> at <code>emission.rs</code> 129 in the devnet4 parameters: one billion IGN in year one, the same rate as mainnet)</td><td><code>emission.rs</code> 85 to 123; <code>igneum.rs</code> 34</td></tr>
<tr><td>Ramp</td><td class="num">2,592,000 s (30 days) from 10 percent</td><td><code>igneum.rs</code> 76 <code>launch_ramp</code></td></tr>
<tr><td>Step</td><td class="num">63,115,200 s (two years), the rate halved each step (decay 2<sup>31</sup> of 2<sup>32</sup>)</td><td><code>emission.rs</code> 85 to 123</td></tr>
<tr><td>Tail</td><td class="num">none: the curve runs to zero and the sum is the hard cap, 4,000,000,000 IGN</td><td><code>emission.rs</code> 69 to 71</td></tr>
<tr><td>Tail</td><td class="num">none in the code: the curve runs to zero and the sum is the hard cap, 4,000,000,000 IGN; the cap is fixed with no tail emission and no vote that expands it (the Token Value volume’s D01, subject to the pre-launch security-funding gate)</td><td><code>emission.rs</code> 69 to 71</td></tr>
<tr><td>Where each runs</td><td>The Igneum 2.0 devnet (18 decimals, the rescaled schedule); mainnet (18)</td><td><code>params.rs</code> 2158, 1764</td></tr>
</tbody>
</table></div>
@ -250,13 +250,13 @@
<tr><td>Base fee, execution gas</td><td>burned in full: gas used times the execution base fee, debited and credited to no one</td><td>in the code on the devnet</td><td><code>igneum/exec/src/executor.rs</code> 320 to 371 (327 and 357, 328 and 360)</td></tr>
<tr><td>Priority fee (the tip)</td><td><span data-rm="fees.priority_fee.miner_percent">80</span> percent to the block’s miner; <span data-rm="fees.priority_fee.developer_percent">20</span> percent to the developer registrations of the contracts whose code ran, pro rata by each frame’s gas; an unregistered frame’s part is credited to nobody, which is a burn</td><td>in the code on the devnet</td><td><code>executor.rs</code> 335 to 339; <code>igneum/exec/src/pgas.rs</code> 290; <code>igneum/exec/src/config.rs</code> 76 (<code>DEVELOPER_SHARE_PERCENT = 20</code>)</td></tr>
<tr><td>The proving payment</td><td>pgas used times the proving base fee, the congestion price of proving capacity: 90 percent to the block’s proving pool, paid per shard to its provers by consensus proving cost; 10 percent burned</td><td>designed, in the code behind the constant (<code>Params::proving_payment_activation_daa</code>, never on every object, on the fork branch proving-payment); the shard guest’s mirror of the split is owed before any height; on the 2.0 devnet the proving charge burns in full</td><td>spec 05 sections 5.1 and 5.3; <code>docs/design/proving-payment.md</code> (8 October 2026)</td></tr>
<tr><td>External proving jobs</td><td>90 percent to the provers who delivered, 10 percent burned, once jobs settle in IGN</td><td>designed, not in the code: no constant exists; at launch a job is paid on the customer’s own chain</td><td>spec 05 section 5.4; the litepaper’s Proving section</td></tr>
<tr><td>External proving jobs</td><td>90 percent to the provers who delivered, 10 percent burned, once jobs settle in IGN</td><td>designed, not in the code: no constant exists; at launch a job is paid on the customer’s own chain and the charge is not yet set (D04)</td><td>spec 05 section 5.4; the litepaper’s Proving section</td></tr>
<tr><td>The provers’ part of the tip</td><td>none: no part of the tip reaches the provers; the tip stays whole to the block (spec O-5.7 closed at zero, 8 October 2026)</td><td>in the code</td><td><code>docs/design/proving-payment.md</code>; <code>executor.rs</code> 335 to 339</td></tr>
</tbody>
</table></div>
<h2>The proving-fee market</h2>
<p>A card’s second income is the proving pool: 20 percent of every block, paid per shard against a valid proof record, plus 90 percent of every block’s proving payment, which users pay at the congestion price of proving capacity (designed; on the devnet that payment is still burned in full). Nothing from the priority fee, which stays whole to the block. The reason the design routes the proving charge to the provers: the operator simulation reads a fixed internal pool as a subsidy, not a price, and only the congestion-priced user-funded fee restores service after a lasting proving spike (35 periods against never; modelled). The price a prover must charge an outside customer is the subsidy it forgoes while it proves, which falls as one over the network’s hash rate; the market itself is designed and not built. The hard cap and the absence of any development fund are unchanged.</p>
<p>A card’s second income is the proving pool: 20 percent of every block, paid per shard against a valid proof record, plus 90 percent of every block’s proving payment, which users pay at the congestion price of proving capacity (designed; on the devnet that payment is still burned in full). Nothing from the priority fee, which stays whole to the block. The reason the design routes the proving charge to the provers: the operator simulation reads a fixed internal pool as a subsidy, not a price, and only the congestion-priced user-funded fee restores service after a lasting proving spike (35 periods against never; modelled). The price a prover must charge an outside customer is the subsidy it forgoes while it proves, which falls as one over the network’s hash rate; the market itself is designed and not built. The hard cap and the absence of any development fund are unchanged: the cap is fixed (D01); every fee and burn route on this page is explicit, the 80/20 coinbase is an emission allocation only, and external-job charges are not yet set (D04).</p>
<h2>The client fee and the fund it fills</h2>
<div class="tbl"><table>

View file

@ -326,7 +326,7 @@ main{padding-bottom:100px}.card{background:var(--row);border:1px solid var(--lin
<p class="note">Every answer names the network (the Igneum 2.0 devnet) and the chain id the node reports (4465). The RPC itself is at rpc.devnet.igneum.network (read methods and eth_sendRawTransaction, 20 requests per second per address).</p>
</div>
</div>
<p class="note" data-four-words><b>Four words, used exactly.</b> <b>Included</b>: the block carries the transaction. <b>Executed</b>: the EVM ran it and the result is on the record. <b>Proven</b>: a valid proof record for its block was carried and paid. <b>Finalised</b>: the block sits under a certified checkpoint, two thirds of active and of total weight. A pause in finality is shown as a pause, never as a block losing its lock. <b>Recovery lock</b>: after a full weight window with no lock, a checkpoint signed by more than half of the anchored weight; shown as a recovery lock, never as finalised. On the devnet’s current node line the lock kind is reported by the forming node only: a node that received the certificate records an ordinary lock, so a recovery lock can read as final on a receiving node until the 2.0.3 node line lands. The definitions are the finality specification’s: <a href="https://git.igneum.network/igneum-network/igneum/src/branch/master/docs/spec/finality-guarantees.md">section 9, the guarantees</a>.</p>
<p class="note" data-four-words><b>Four words, used exactly.</b> <b>Included</b>: the block carries the transaction. <b>Executed</b>: the EVM ran it and the result is on the record. <b>Proven</b>: a valid proof record for its block was carried and paid. <b>Finalised</b>: the block sits under a certified checkpoint, two thirds of active and of total weight. A pause in finality is shown as a pause, never as a block losing its lock. <b>Recovery lock</b>: after a full weight window with no lock, a checkpoint signed by more than half of the anchored weight; shown as a recovery lock, never as final (the Token Value volume’s D05). On the devnet’s current node line the lock kind is reported by the forming node only: a node that received the certificate records an ordinary lock, so a recovery lock can read as final on a receiving node until the 2.0.3 node line lands. The definitions are the finality specification’s: <a href="https://git.igneum.network/igneum-network/igneum/src/branch/master/docs/spec/finality-guarantees.md">section 9, the guarantees</a>.</p>
</main>
<!-- footer:start -->
<footer class="site-footer">

View file

@ -446,7 +446,7 @@ blockquote{margin:10px 0;padding:10px 14px;border-left:3px solid var(--line-2);c
<blockquote>You claim to be the best GPU network before anyone outside has checked anything.</blockquote>
<div class="status"><span class="badge b-conceded">Conceded</span> <span class="did">8 October 2026): no &quot;number one&quot; claim before comparative results and adoption exist.</span></div>
<div class="pin"><b>Pin</b> Leadership tests, fifth box (served ranking language); second to fourth boxes.</div>
<details><summary>The answer as first written</summary><p>No present-tense rank is served. Published claims name the evaluated release, evidence and boundary conditions. An independently substantiated pass of the acceptance standard would support a serious case that Igneum is in contention for leadership among GPU-first networks and would not award a numerical rank; it would not guarantee adoption, perpetual GPU profitability, defeat of all future chips or a numerical number-one ranking. The tests that would earn it are miners staying through hard conditions, customers repeatedly paying for proofs, and the network running without the founding team (D5); none is met yet.</p></details>
<details><summary>The answer as first written</summary><p>No present-tense rank is served. Published claims name the evaluated release, evidence and boundary conditions. Designed to compete for leadership among GPU-first networks. That is the scoped wording until a ranking is measured (the Token Value volume’s rule VR-30 and decision D06, scoped claims only, ratified 9 October 2026): an independently substantiated pass of the acceptance standard would not award a numerical rank, nor guarantee adoption, perpetual GPU profitability or the defeat of every future chip. The tests that would earn it are miners staying through hard conditions, customers repeatedly paying for proofs, and the network running without the founding team (D5); none is met yet.</p></details>
</article>
<p class="intro" style="margin-top:var(--sec)">Source: the project's Igneum 2.0 criticism ledger, a file in the repository, rendered to this page at build time. A criticism that is not here, or that shows an entry is wrong, is added with credit if wanted: <a href="mailto:hello@igneum.network">hello@igneum.network</a> or <a href="https://git.igneum.network/igneum-network/spec/issues" rel="noopener">an issue on the specification repository</a>.</p>

View file

@ -303,7 +303,7 @@ body.all .pager{display:none}
</div>
<div class="meta">
<span>Published <b>3 October 2026</b> · updated <b>8 October 2026</b></span>
<span>Coin <b>IGN</b> · cap <b>4,000,000,000</b></span>
<span>Coin <b>IGN</b> · cap <b>4,000,000,000</b> (fixed, D01)</span>
<span>Status <b>The Igneum 2.0 devnet is live</b></span>
<span>Method <b>one founder with AI systems</b> · external review before gate 3</span>
<span>This is not an offer to sell anything</span>
@ -361,7 +361,7 @@ body.all .pager{display:none}
<tr><td>The mining card does paid, useful, verifiable work</td><td>Primecoin's prime chains in 2013 were not useful. Aleo ran proving as consensus and the fastest prover won (both approximate)</td><td>Proving kept apart from the lottery; shards assigned by sortition, not by speed</td><td>Implemented: proving v0 and v1 on the devnet from block zero, on NVIDIA cards. The job market for other chains is Designed</td></tr>
<tr><td>A proof-of-work chain where every block is proven</td><td>Proven-execution EVM chains run as rollups on proof-of-stake Ethereum. Conflux has run GPU-mined EVM apps on a DAG since 2020, without proofs (approximate)</td><td>Proven state on a proof-of-work base layer, produced by the miners themselves</td><td>Implemented: shards proven and paid on the devnet; proof verification enforced in consensus from block zero on the Igneum 2.0 devnet (running since 17:14 UK on 8 October 2026)</td></tr>
<tr><td>Finality held by miners and not moved by hour-long rentals</td><td>Decred votes with stake. Horizen penalises hidden chains. Kaspa limits merge depth (approximate)</td><td>Vote weight is 30 days of blocks per key. Hashrate that appeared today has no vote</td><td>Implemented: rule v3 live on the devnet from block zero. External review is owed at gate 3</td></tr>
<tr><td>100% of emission to the people running the hardware</td><td>Kaspa's fair launch. Zcash and Decred fund developers from emission (approximate)</td><td>No fee to any team, foundation or fund in the protocol. The miner software's optional 1% dev fee is the one payment to the project, off with one flag</td><td>Implemented in consensus: the 80/20 coinbase on the devnet</td></tr>
<tr><td>100% of emission to the people running the hardware</td><td>Kaspa's fair launch. Zcash and Decred fund developers from emission (approximate)</td><td>No fee to any team, foundation or fund in the protocol. The miner software's optional 1% dev fee is the one payment to the project, off with one flag</td><td>Implemented in consensus: the 80/20 coinbase on the devnet (an emission allocation only, D04)</td></tr>
<tr><td>A chain your browser verifies by itself</td><td>Light clients trust a committee, as Ethereum's trust a sync committee (approximate)</td><td>At launch, one execution proof plus a certificate the client is given. The consensus proof that makes the checkpoint self-verifying is phase two</td><td>Designed. The home page's card verifies a devnet certificate in the browser today, with the voter list taken from a node</td></tr>
</tbody>
</table></div>
@ -533,7 +533,7 @@ body.all .pager{display:none}
<h3>The proving budget</h3>
<p>Gas prices execution. Proving cost is a different number, so Igneum meters it separately: every transaction pays in both dimensions, and each block has a proving-cost budget set in consensus from measured prover throughput. A transaction that is cheap to run and expensive to prove pays for what it costs the provers. Measured on 5 October 2026 (an RTX 5090 under SP1 6.8.1's GPU prover, the shard size the chain adopts from its fee switch, 30,000 proving gas, about 4.7 million prover cycles): one full shard proves in 4.3 seconds and needs 20.4 GB of GPU memory with the card to itself, so a 24 GB card proves full shards and a 12 GB or 16 GB card does not on this prover build, whose floor is 13.9 GB for even an empty shard; mining and proving on one card needs 32 GB today (the prototype-size shard beside the miner peaked at 30.1 GB) and 24 GB once the adopted shard size is live (22.2 GB beside the miner, 13.2 seconds a shard, measured on the 32 GB card; a 24 GB card has not run it yet). The old 12 GB gate on the roadmap was withdrawn on 5 October until a prover build with a smaller floor was measured; on 6 October a patched server proved the same shard at 7.4 to 8.0 GB alone on eleven rented cards from the RTX 3060 to the RTX 5090 (the real-card table), so the gate returns as measured and the patched server is not yet in the shipped app. The first proofs exist: on 4 October 2026 an RTX 5090 proved a small two-transaction block in 1.4 seconds (2.7 seconds compressed), verified in 0.22 and 0.038 seconds, and a laptop CPU proved a three-shard block end to end in 19 minutes. Later that day the same card proved a full shard at the provisional size, 6.75 million prover gas, which executed in 60.8 million cycles: core proof 8.3 seconds, compressed proof 10.9 seconds, verified in 0.040 seconds; a four-shard block took 44.5 seconds of GPU stages end to end. Shards are assigned and proven on the devnet from block zero. The gate asks for a mid-range card, and an RTX 5090 is not one, so the gate stands open. Once the gate is measured, the budget rises by schedule as hardware improves. The proof system is hash-based, which is what runs on consumer cards, and sits behind a versioned interface. SP1 is the one backend. A replacement is adopted only where justified, by a miner-signalled release, never as an interchangeable second backend, and the chain runs for ever on the current one if none is adopted.</p>
<h3>Proving for everyone else</h3>
<p>The job market for other chains is Designed, not built, and stays out of every revenue assumption until it is. The order: Igneum's own execution first; then one external customer's exact workload with repeat paid jobs; further workloads only where the fleet has a demonstrated edge. As designed, a customer posts a job, a miner wins it, proves it, and is paid, and the market is permissionless. At launch a job is paid on the customer's own chain, in the customer's currency, to a payout contract keyed by miner address, because Igneum cannot yet see Ethereum. Settlement in IGN, with 10% of each fee burned, follows when the proof bridge lets Igneum see the payment, in phase two. The Igneum miner client can also bid on other proving networks and take the best price, where a miner chooses to hold their collateral: Boundless provers post ZKC and Succinct provers stake PROVE (approximate, from their documentation). The proving market is small today. Igneum does not depend on it. We know of no proof-of-work chain that sells proofs to other chains.</p>
<p>The job market for other chains is Designed, not built, and stays out of every revenue assumption until it is. The order: Igneum's own execution first; then one external customer's exact workload with repeat paid jobs; further workloads only where the fleet has a demonstrated edge. As designed, a customer posts a job, a miner wins it, proves it, and is paid, and the market is permissionless. At launch a job is paid on the customer's own chain, in the customer's currency, to a payout contract keyed by miner address, because Igneum cannot yet see Ethereum. The charge itself is not yet set (D04). Settlement in IGN, with 10% of each fee burned, follows when the proof bridge lets Igneum see the payment, in phase two. The Igneum miner client can also bid on other proving networks and take the best price, where a miner chooses to hold their collateral: Boundless provers post ZKC and Succinct provers stake PROVE (approximate, from their documentation). The proving market is small today. Igneum does not depend on it. We know of no proof-of-work chain that sells proofs to other chains.</p>
</section>
<section id="finality">
@ -575,7 +575,7 @@ body.all .pager{display:none}
<h2>Economics</h2>
<p>The coin is IGN. It is gas and the proving currency, and part of every payment on Igneum is burned. Outside customers pay in their own currency on their own chain at launch; settlement in IGN with a 10% burn follows when the proof bridge lets Igneum see the payment, in phase two.</p>
<h3>Supply</h3>
<p>Fair launch. No premine, no pre-sale, no allocation to anyone. Hard cap of 4 billion IGN, approached and never reached, because emission starts at 1 billion a year and halves every two years for ever. Nearly a quarter of all supply is mined in the first year and half in the first two. Emission ramps from 10% to 100% over the first 30 days so that nobody takes the first month before the rest of the world hears about it.</p>
<p>Fair launch. No premine, no pre-sale, no allocation to anyone. The cap, the schedule, the fee routes and the finality display are the Token Value volume’s decisions, ratified on 9 October 2026 (D01 to D06): the cap is fixed with no tail emission and no vote that expands it, subject to the pre-launch security-funding gate (D01); the schedule’s pacing is under comparison and not final (D02); every fee and burn route is published explicitly, the 80/20 figure is an emission allocation only, and external-job charges are not yet set (D04); a recovery certificate is never shown as final (D05). A ratified decision is a rule, not a promise of value. Hard cap of 4 billion IGN, approached and never reached, because emission starts at 1 billion a year and halves every two years for ever; the pacing is under comparison and not final (D02). Nearly a quarter of all supply is mined in the first year and half in the first two. Emission ramps from 10% to 100% over the first 30 days so that nobody takes the first month before the rest of the world hears about it.</p>
<div class="figure">
<svg viewBox="0 0 760 300" role="img" aria-label="Half of the 4 billion cap is mined in the first two years" font-family="IBM Plex Mono, monospace" font-size="12">
<text x="40" y="26" font-family="IBM Plex Sans, system-ui, sans-serif" font-size="15" font-weight="600" fill="var(--ink)">Half of the 4 billion cap is mined in the first two years</text>
@ -611,13 +611,13 @@ body.all .pager{display:none}
</tbody>
</table></div>
<h3>Where fees go</h3>
<p>The base fee of every transaction is burned in full, Ethereum's rule, so a miner cannot fill blocks with its own transactions for free. The priority fee splits two ways: 80% to the miner and provers of that block, 20% to the apps whose code ran, by gas consumed inside each. External proving fees, once they settle on Igneum, pay 90% to the provers who delivered and burn 10%. The hard cap fixes supply. Emission is untouched by any of this: every coin minted still goes to miners and provers.</p>
<p>The base fee of every transaction is burned in full, Ethereum's rule, so a miner cannot fill blocks with its own transactions for free. The priority fee splits two ways: 80% to the miner and provers of that block, 20% to the apps whose code ran, by gas consumed inside each. External proving fees, once they settle on Igneum, pay 90% to the provers who delivered and burn 10%. The hard cap fixes supply (D01). Emission is untouched by any of this: every coin minted still goes to miners and provers.</p>
<h3>Every payment route</h3>
<p>One row per route, so operator income and protocol income never blur. The protocol pays no address of its own, and a burn pays nobody. Rows 1 to 5 are the protocol. Row 6 is the project's software, outside the protocol, and is never added to the other five.</p>
<div class="tbl"><table>
<thead><tr><th>Route</th><th>Currency</th><th>Recipient</th><th>Fee</th><th>Burn</th></tr></thead>
<tbody>
<tr><td>1. Emission, per block</td><td>IGN, new coins on the schedule above</td><td>80% the block's miner, 20% the proving pool for the provers of that block</td><td>None</td><td>None. Implemented in consensus: the 80/20 coinbase on the devnet</td></tr>
<tr><td>1. Emission, per block</td><td>IGN, new coins on the schedule above</td><td>80% the block's miner, 20% the proving pool for the provers of that block</td><td>None</td><td>None. Implemented in consensus: the 80/20 coinbase on the devnet (an emission allocation only, D04)</td></tr>
<tr><td>2. Base fee, both gas dimensions</td><td>IGN</td><td>Nobody</td><td>The base fee the chain sets per block</td><td>All of it. Implemented on the devnet</td></tr>
<tr><td>3. Priority fee</td><td>IGN</td><td>80% the block's miner and provers; 20% the apps whose code ran, per call frame</td><td>The tip the sender sets</td><td>The share of any frame in an unregistered contract. Implemented on the devnet</td></tr>
<tr><td>4. External job, at launch</td><td>The customer's currency, on the customer's chain</td><td>The miner who delivered, through a payout contract keyed by miner address</td><td>Priced in the customer's money per proof, at or above the subsidy the prover forgoes (a formula in network hash, under Building on Igneum, never a fixed number); the customer chain's own bond and slashing apply</td><td>None; Igneum cannot see the payment. Designed</td></tr>
@ -627,7 +627,7 @@ body.all .pager{display:none}
</table></div>
<p class="src"><b>Sources:</b> specification sections 2.5 and 5.1 to 5.4; the measurement record in the repository (docs/bench-log.md) for the earlier devnet's receipts and the dev-fee count.</p>
<h3>Security after the subsidy</h3>
<p>The cap stays at 4 billion. There is no tail emission. The schedule is a bet, not a measurement: a halving halves emission income overnight if price and fees do nothing. Kaspa's steeper monthly reduction kept its hashrate while its price rose (approximate). Long term, security has to be paid by fees and, if it is built and bought, the proving market. The external market is Designed, not built, and is out of the numbers below. As designed, outside customers buy proofs as dollars-priced work settled in IGN, and 90% of every job goes to the provers who delivered it. The table shows the first year in which the block subsidy on its own pays miners less than the power of about 3,000 consumer cards, at three flat prices. The prices are inputs chosen to span two orders of magnitude. The model (modelled, 3 October 2026) runs a 300 W card at 124 MH/s on electricity at USD 0.12 per kWh. One rule sits beside the cap. If external proving revenue is under one fifth of the block subsidy over any 90-day window after year 5, the question of a tail reward goes to the miners' signalling vote. The protocol never changes emission by itself.</p>
<p>The cap is fixed at 4 billion, with no tail emission and no vote that expands it (D01, subject to the pre-launch security-funding gate). The schedule is a bet, not a measurement: a halving halves emission income overnight if price and fees do nothing. Kaspa's steeper monthly reduction kept its hashrate while its price rose (approximate). Long term, security has to be paid by fees and, if it is built and bought, the proving market. The external market is Designed, not built, and is out of the numbers below. As designed, outside customers buy proofs as dollars-priced work settled in IGN, and 90% of every job goes to the provers who delivered it. The table shows the first year in which the block subsidy on its own pays miners less than the power of about 3,000 consumer cards, at three flat prices. The prices are inputs chosen to span two orders of magnitude. The model (modelled, 3 October 2026) runs a 300 W card at 124 MH/s on electricity at USD 0.12 per kWh. One rule sits beside the cap. If external proving revenue is under one fifth of the block subsidy over any 90-day window after year 5, the answer is the security-funding plan under D01, never a tail emission or a vote that expands the cap. The protocol never changes emission by itself.</p>
<div class="tbl"><table>
<thead><tr><th>Price per IGN</th><th>First year the subsidy alone pays under the power of 3,000 cards</th><th>Subsidy to miners that year</th></tr></thead>
<tbody>
@ -686,7 +686,7 @@ body.all .pager{display:none}
<section id="ember">
<h2>Ember, the miner</h2>
<p class="lead">Igneum Ember is the one-click miner. It runs the node, mines the hourly program, proves shards and keeps your key, on Windows, macOS and Linux.</p>
<p>Ember is a supervisor with a face. It starts a node, waits until it is synced, starts one miner per card, reads every line they print into the dashboard, and restarts what fails. It has run the devnet's cards since 4 October 2026. Everything in the first table is in the shipped app. The second table is the six levers set on 4 October 2026 to make it the fastest miner for this chain, each with its state and what has been measured.</p>
<p>Ember is a supervisor with a face. It starts a node, waits until it is synced, starts one miner per card, reads every line they print into the dashboard, and restarts what fails. It has run the devnet's cards since 4 October 2026. Everything in the first table is in the shipped app. The second table is the six levers set on 4 October 2026 to raise the miner’s rate on this chain, each with its state and what has been measured.</p>
<h3>What it does</h3>
<div class="tbl"><table>
<thead><tr><th>Feature</th><th>What happens</th></tr></thead>
@ -868,7 +868,7 @@ body.all .pager{display:none}
<li><strong>Proof verification in consensus.</strong> On the Igneum 2.0 devnet, yes, from block zero (verifier_in_consensus set, the node's own start line; running since its first block at 17:14 UK on 8 October 2026). It is the prerequisite of the no-rescue network exercise (Deliverable 5), which is still owed; an earlier devnet ran with the rule off.</li>
<li><strong>Proving on every card.</strong> No. NVIDIA proves; AMD and Apple mine. The proving stack is judged on the full pipeline: inputs, proving, aggregation, verification, payment, memory and the mining income forgone.</li>
<li><strong>What proofs do not give.</strong> Proven execution is not automatically finality. EVM compatibility is not Ethereum security. ZK technology does not automatically make transactions private.</li>
<li><strong>A ranking.</strong> No. Igneum makes no leading or number-one claim. Published claims name the evaluated release, evidence and boundary conditions. An independently substantiated pass of the acceptance standard would support a serious case that Igneum is in contention for leadership among GPU-first networks and would not award a numerical rank; it would not guarantee adoption, perpetual GPU profitability, defeat of all future chips or a numerical number-one ranking. Benchmarks against Ravencoin’s KAWPOW, Ergo and Firo’s reference miner are owed work; no result exists yet.</li>
<li><strong>A ranking.</strong> No. Igneum makes no leading or number-one claim. Published claims name the evaluated release, evidence and boundary conditions. Designed to compete for leadership among GPU-first networks. That is the scoped wording until a ranking is measured (the Token Value volume’s rule VR-30 and decision D06, scoped claims only): an independently substantiated pass of the acceptance standard would not award a numerical rank, nor guarantee adoption, perpetual GPU profitability or the defeat of every future chip. Benchmarks against Ravencoin’s KAWPOW, Ergo and Firo’s reference miner are owed work; no result exists yet.</li>
<li><strong>A finished protocol.</strong> The sustained-mining finality rule is the newest piece and the one that external review will try hardest to break. The specification, the review and the benchmarks are published as they happen.</li>
</ul>
<p>Everything in this document is subject to the gates on the roadmap. Nothing in it is an offer to sell anything. Found an error, or a criticism this document does not answer? Email <a href="mailto:hello@igneum.network">hello@igneum.network</a>, or open an issue on the public specification repository: <a href="https://git.igneum.network/igneum-network/spec/issues" rel="noopener">git.igneum.network/igneum-network/spec/issues</a>. Post reaches Igneum Labs LTD, Unit IH-00-01-01-OF-01, Level 01, Innovation One, Dubai International Financial Centre.</p>

View file

@ -513,7 +513,7 @@ details.tablebar summary{display:flex;align-items:center}
</details>
<div class="obs-foot"><span>Igneum / observatory</span><span>One node read every 2 s. Nothing here is a replay.</span></div>
<p class="note" data-four-words><b>Four words, used exactly.</b> <b>Included</b>: the block carries the transaction. <b>Executed</b>: the EVM ran it and the result is on the record. <b>Proven</b>: a valid proof record for its block was carried and paid. <b>Finalised</b>: the block sits under a certified checkpoint, two thirds of active and of total weight. A pause in finality is shown as a pause, never as a block losing its lock. <b>Recovery lock</b>: after a full weight window with no lock, a checkpoint signed by more than half of the anchored weight; shown as a recovery lock, never as finalised. On the devnet’s current node line the lock kind is reported by the forming node only: a node that received the certificate records an ordinary lock, so a recovery lock can read as final on a receiving node until the 2.0.3 node line lands. The definitions are the finality specification’s: <a href="https://git.igneum.network/igneum-network/igneum/src/branch/master/docs/spec/finality-guarantees.md">section 9, the guarantees</a>.</p>
<p class="note" data-four-words><b>Four words, used exactly.</b> <b>Included</b>: the block carries the transaction. <b>Executed</b>: the EVM ran it and the result is on the record. <b>Proven</b>: a valid proof record for its block was carried and paid. <b>Finalised</b>: the block sits under a certified checkpoint, two thirds of active and of total weight. A pause in finality is shown as a pause, never as a block losing its lock. <b>Recovery lock</b>: after a full weight window with no lock, a checkpoint signed by more than half of the anchored weight; shown as a recovery lock, never as final (the Token Value volume’s D05). On the devnet’s current node line the lock kind is reported by the forming node only: a node that received the certificate records an ordinary lock, so a recovery lock can read as final on a receiving node until the 2.0.3 node line lands. The definitions are the finality specification’s: <a href="https://git.igneum.network/igneum-network/igneum/src/branch/master/docs/spec/finality-guarantees.md">section 9, the guarantees</a>.</p>
</main>
<!-- footer:start -->

View file

@ -303,7 +303,7 @@ main{padding-bottom:100px}.card{background:var(--row);border:1px solid var(--lin
<dl id="totals"></dl>
</div>
</div>
<p class="note" data-four-words><b>Four words, used exactly.</b> <b>Included</b>: the block carries the transaction. <b>Executed</b>: the EVM ran it and the result is on the record. <b>Proven</b>: a valid proof record for its block was carried and paid. <b>Finalised</b>: the block sits under a certified checkpoint, two thirds of active and of total weight. A pause in finality is shown as a pause, never as a block losing its lock. <b>Recovery lock</b>: after a full weight window with no lock, a checkpoint signed by more than half of the anchored weight; shown as a recovery lock, never as finalised. On the devnet’s current node line the lock kind is reported by the forming node only: a node that received the certificate records an ordinary lock, so a recovery lock can read as final on a receiving node until the 2.0.3 node line lands. The definitions are the finality specification’s: <a href="https://git.igneum.network/igneum-network/igneum/src/branch/master/docs/spec/finality-guarantees.md">section 9, the guarantees</a>.</p>
<p class="note" data-four-words><b>Four words, used exactly.</b> <b>Included</b>: the block carries the transaction. <b>Executed</b>: the EVM ran it and the result is on the record. <b>Proven</b>: a valid proof record for its block was carried and paid. <b>Finalised</b>: the block sits under a certified checkpoint, two thirds of active and of total weight. A pause in finality is shown as a pause, never as a block losing its lock. <b>Recovery lock</b>: after a full weight window with no lock, a checkpoint signed by more than half of the anchored weight; shown as a recovery lock, never as final (the Token Value volume’s D05). On the devnet’s current node line the lock kind is reported by the forming node only: a node that received the certificate records an ordinary lock, so a recovery lock can read as final on a receiving node until the 2.0.3 node line lands. The definitions are the finality specification’s: <a href="https://git.igneum.network/igneum-network/igneum/src/branch/master/docs/spec/finality-guarantees.md">section 9, the guarantees</a>.</p>
</main>
<!-- footer:start -->
<footer class="site-footer">

View file

@ -265,7 +265,7 @@
<p class="note">One node is read every 2 s; the window is the last 120 s. Under reduced motion each scene draws a still frame on every reply. Add <code>?only=a</code>, <code>b</code> or <code>c</code> to the address for one scene full width.</p>
</div>
</section>
<p class="note" data-four-words><b>Four words, used exactly.</b> <b>Included</b>: the block carries the transaction. <b>Executed</b>: the EVM ran it and the result is on the record. <b>Proven</b>: a valid proof record for its block was carried and paid. <b>Finalised</b>: the block sits under a certified checkpoint, two thirds of active and of total weight. A pause in finality is shown as a pause, never as a block losing its lock. <b>Recovery lock</b>: after a full weight window with no lock, a checkpoint signed by more than half of the anchored weight; shown as a recovery lock, never as finalised. On the devnet’s current node line the lock kind is reported by the forming node only: a node that received the certificate records an ordinary lock, so a recovery lock can read as final on a receiving node until the 2.0.3 node line lands. The definitions are the finality specification’s: <a href="https://git.igneum.network/igneum-network/igneum/src/branch/master/docs/spec/finality-guarantees.md">section 9, the guarantees</a>.</p>
<p class="note" data-four-words><b>Four words, used exactly.</b> <b>Included</b>: the block carries the transaction. <b>Executed</b>: the EVM ran it and the result is on the record. <b>Proven</b>: a valid proof record for its block was carried and paid. <b>Finalised</b>: the block sits under a certified checkpoint, two thirds of active and of total weight. A pause in finality is shown as a pause, never as a block losing its lock. <b>Recovery lock</b>: after a full weight window with no lock, a checkpoint signed by more than half of the anchored weight; shown as a recovery lock, never as final (the Token Value volume’s D05). On the devnet’s current node line the lock kind is reported by the forming node only: a node that received the certificate records an ordinary lock, so a recovery lock can read as final on a receiving node until the 2.0.3 node line lands. The definitions are the finality specification’s: <a href="https://git.igneum.network/igneum-network/igneum/src/branch/master/docs/spec/finality-guarantees.md">section 9, the guarantees</a>.</p>
</main>
<!-- footer:start -->
<footer class="site-footer">

View file

@ -230,7 +230,7 @@
<tr><td><b>Independent operation</b><div class="kv">D5</div></td><td>No-founder exercise on the real implementation.</td><td>A centrally supported demo.</td><td>PENDING: the no-rescue network exercise is owed; the devnet is the project’s own machines, its rented fleet and a few outside laptops.</td></tr>
<tr><td><b>Commercial demand</b><div class="kv">Architecture and product</div></td><td>Repeat paid external jobs and workable operator margins.</td><td>Devnet payouts or subsidised volume.</td><td>EXCLUDED from every revenue figure today: the external proving market is designed, not built; no external job has been paid.</td></tr>
<tr><td><b>Long-term funding</b><div class="kv">D4</div></td><td>Internal proving and security payments and maintenance runway.</td><td>Burn accounting or assumed appreciation.</td><td>MODELLED: the proving model on the economics page walks the halvings at today’s shard and key counts; the proving payment’s routing is designed behind its constant, burned in full today.</td></tr>
<tr><td><b>Leadership</b><div class="kv">Leadership tests</div></td><td>Comparative results, adoption, retention and reliability over time.</td><td>A roadmap or unsupported rank.</td><td>EXCLUDED: no rank is claimed; the comparison set (Ravencoin KAWPOW, Ergo, Firo’s reference miner) is owed work, not a result.</td></tr>
<tr><td><b>Leadership</b><div class="kv">Leadership tests</div></td><td>Comparative results, adoption, retention and reliability over time.</td><td>A roadmap or unsupported rank.</td><td>EXCLUDED: no rank is claimed; the served wording until a ranking is measured is “Designed to compete for leadership among GPU-first networks.” (VR-30, D06); the comparison set (Ravencoin KAWPOW, Ergo, Firo’s reference miner) is owed work, not a result.</td></tr>
</tbody>
</table></div>
<p class="src"><b>Source:</b> <code>docs/plans/igneum-2.0-plan.txt</code>, section 23 (the acceptance scorecard) and the pins in <code>docs/plans/igneum-2.0.md</code>; the Today column is the facts page’s label for the rows each gate cites, and moves only with those rows. The five labels: TEAM-REPORTED, MODELLED, PROPOSED, PENDING, EXCLUDED. The public mirror follows master; a link that answers 404 is a file not yet synced.</p>

View file

@ -279,7 +279,7 @@ main{padding-bottom:100px}.card{background:var(--row);border:1px solid var(--lin
<div class="card"><div class="viz-head"><h2>Block and proof</h2><div class="eyebrow" id="blk-eyebrow"></div></div><dl id="blk"></dl></div>
<div class="card"><div class="viz-head"><h2>Logs</h2><div class="eyebrow" id="log-n"></div></div><div class="tbl" id="logs"></div></div>
</div>
<p class="note" data-four-words><b>Four words, used exactly.</b> <b>Included</b>: the block carries the transaction. <b>Executed</b>: the EVM ran it and the result is on the record. <b>Proven</b>: a valid proof record for its block was carried and paid. <b>Finalised</b>: the block sits under a certified checkpoint, two thirds of active and of total weight. A pause in finality is shown as a pause, never as a block losing its lock. <b>Recovery lock</b>: after a full weight window with no lock, a checkpoint signed by more than half of the anchored weight; shown as a recovery lock, never as finalised. On the devnet’s current node line the lock kind is reported by the forming node only: a node that received the certificate records an ordinary lock, so a recovery lock can read as final on a receiving node until the 2.0.3 node line lands. The definitions are the finality specification’s: <a href="https://git.igneum.network/igneum-network/igneum/src/branch/master/docs/spec/finality-guarantees.md">section 9, the guarantees</a>.</p>
<p class="note" data-four-words><b>Four words, used exactly.</b> <b>Included</b>: the block carries the transaction. <b>Executed</b>: the EVM ran it and the result is on the record. <b>Proven</b>: a valid proof record for its block was carried and paid. <b>Finalised</b>: the block sits under a certified checkpoint, two thirds of active and of total weight. A pause in finality is shown as a pause, never as a block losing its lock. <b>Recovery lock</b>: after a full weight window with no lock, a checkpoint signed by more than half of the anchored weight; shown as a recovery lock, never as final (the Token Value volume’s D05). On the devnet’s current node line the lock kind is reported by the forming node only: a node that received the certificate records an ordinary lock, so a recovery lock can read as final on a receiving node until the 2.0.3 node line lands. The definitions are the finality specification’s: <a href="https://git.igneum.network/igneum-network/igneum/src/branch/master/docs/spec/finality-guarantees.md">section 9, the guarantees</a>.</p>
</main>
<!-- footer:start -->
<footer class="site-footer">

View file

@ -18,6 +18,7 @@
| kill-by-name rule 5 and the no-kill shim (`kill-by-name-check.sh`, `no-kill-shim/{pkill,killall}` exit 97 when first in PATH) | A `pgrep -f`/`pkill -f` pattern that is a bare path, a log name or an unanchored word; only `^`-anchored command patterns, the bracket form, a variable, -x or -F pass (fifteen Mac processes died to a grep, 8 Oct 2026) | 8 Oct 2026 |
| a "cut" batch needs its read-back (`test-record.mjs`) | A batch declaring `cut` without the binary's build-N:/srv path, its commit string read back equal to the manifest sha, and the kit ISA check's clean line; a sha is cut only when its binary exists on build-1 with its commit string read back | 8 Oct 2026 |
| rule 33: a release entry publishes only with a PASS fresh-install canary record for its sha (`canary-check.sh`; `tools/ci/canary/<sha>.json`, the form from `--form`; refused without by `packaging/ota/publish-manifest.sh --release-sha`, `publish-public.sh` and deploy-win.sh; the canary cell maps INT-07) | A release entry published before its sha had run a fresh install on a non-AVX-512 box with an empty datadir, synced, mined five minutes with zero refusals, claimed, proved and paid or queued one shard and quit inside a bound, every line read back (the founder's "no more lost time", 8 October 2026) |
| the served-file identity guard (`served-identity-guard.sh` at every publisher of a served file built from reports or merges: the dl manifests in publish-manifest.sh and publish-public.sh, workers.json, the PC intake merge, release.json, the feed; `served-identity-daily.sh` over the edge copies; the box form `served-identity-hashes.sh`) | A served workers.json that carried the owner's first name from a PC report's free-text note (9 October 2026, 09:15 UK): the identity grep had guarded committed text, not served JSON built from reports |
| the INT suite is generated from the master edition's integration gates (`int-suite.mjs`; the owner per the coordinator's crosswalk) and INT-17 is a rule of the writer: a cell declaring a missing oracle, pinned keys or mandatory real-proof fixture writes BLOCKED, never PASS | A registry whose INT suite drifts from traceability.json; a batch cell with `prereqs` where any is not "present" written as anything but BLOCKED | 8 Oct 2026 |
| the REV suite is generated from an external review's findings.json and dispatch.md (`review-suite.mjs`; one case per required regression, NOT RUN, the owner from the dispatch table) | A registry whose REV suite differs from the generator's output (--check) | 8 Oct 2026 |
| F03 (Review B): one release manifest (`packaging/release-manifest.json` on a release branch) and every component built from it (`release-manifest-check.sh`, `build-from-manifest.sh`) | A tree whose own pins disagree with the manifest: the Windows node-source pin, the proving manifest's elf and vk sha256s and the files' hashes, the node fork's freeze fingerprint, the pool's vendored node checkout, a redefined EpochSeeds in the pool (the shadow_reps seam closes by a build against the pinned node); the build script puts the fork at the manifest's node sha and checks kaspad with igneum-pow (rule 19), the miner, the pool, the app and the prove host on a box | 8 Oct 2026 |

View file

@ -80,6 +80,7 @@ the registry's evidence rules: a PASS names evidence that exists, a touched evid
the kit ISA check: a distribution kit's binaries carry no AVX-512 encoding (the x86-64-v3 baseline; self-test with a fake objdump)
F02 (Review B): the proof-rule test bypass cannot reach a release build: an env read with no cfg guard or under a default feature is red; a release binary carrying the bypass string is red (self-test)
the test map merges structurally at a landing: master's cells plus the branch's, minus what the branch removed and master left (self-test)
the served-file identity guard: a served file built from reports or merges is refused at its publish step on an identity hit, logged redacted; the box form matches hashed tokens under a salt (self-test)
rule 33: a release entry publishes only with a PASS fresh-install canary record for its sha (the record check and the publish guard, self-tests)
the guest input format moves with the pinned guests: the elf manifest's guest_input_format equals the source constant where the field exists, a format bump without a guest change is refused at the merge (self-test, then the tree)
the REV suite is generated from Review B's findings and dispatch and matches them (self-test, then the tree)

View file

@ -173,6 +173,10 @@ merge_with_batches() { # <tip> <sha> <msg> [branch]: in a worktree at <tip> me
node tools/ci/int-suite.mjs --traceability docs/plans/igneum-2.0-master/traceability.json --write >/dev/null || { echo "merge-to-master: the INT suite does not regenerate on the merged tree" >&2; git merge --abort 2>/dev/null; return 1; }
echo "merge-to-master: regenerated the INT suite on master's registry"
fi
if [ -f tools/ci/token-value-suite.mjs ] && [ -f docs/plans/igneum-2.0-master/token-value/rules-and-gates.json ]; then
node tools/ci/token-value-suite.mjs --write >/dev/null || { echo "merge-to-master: the VR and TV suites do not regenerate on the merged tree" >&2; git merge --abort 2>/dev/null; return 1; }
echo "merge-to-master: regenerated the VR and TV suites on master's registry"
fi
for f in $BATCHES; do
[ "$f" = . ] && continue
git checkout -q "$( [ "$mode" = branch ] && echo "$tip" || echo "$sha" )" -- "$f"
@ -377,7 +381,7 @@ bash tools/ci/rule24-crate-gate.sh "$BASE" "$SHA" || { echo "merge-to-master: RE
REGISTRY_PATH="${REGISTRY_PATH:-docs/plans/igneum-2.0-test-registry.json}"
BATCHES=$(git diff --name-only --diff-filter=AM "$BASE" "$SHA" -- tools/ci/batches/ | grep -E '\.json$' || true)
NOTES=$(git diff --name-only --diff-filter=AM "$BASE" "$SHA" -- tools/ci/notes/ | grep -E '\.json$' || true) # {suite, text}, replayed through test-record.mjs --note-file
REVGEN=0; git diff --quiet "$BASE" "$SHA" -- tools/ci/review-suite.mjs tools/ci/int-suite.mjs docs/analysis/review-2026-10-08-b/findings.json docs/analysis/review-2026-10-08-b/dispatch.md docs/plans/igneum-2.0-master/traceability.json 2>/dev/null || REVGEN=1 # the REV and INT suites regenerate on the merged tree
REVGEN=0; git diff --quiet "$BASE" "$SHA" -- tools/ci/review-suite.mjs tools/ci/int-suite.mjs tools/ci/token-value-suite.mjs docs/analysis/review-2026-10-08-b/findings.json docs/analysis/review-2026-10-08-b/dispatch.md docs/plans/igneum-2.0-master/traceability.json docs/plans/igneum-2.0-master/token-value/rules-and-gates.json 2>/dev/null || REVGEN=1 # the REV and INT suites regenerate on the merged tree
[ -n "$NOTES" ] || [ "$REVGEN" = 1 ] && BATCHES="${BATCHES:-.}" # the registry is rebuilt from master's copy whenever any transform rides
RULE26_SKIP_PATHS=""; [ -n "$BATCHES" ] && RULE26_SKIP_PATHS="$REGISTRY_PATH"
# the harness map page is generated from tools/ci/test-map.json (test-map-doc.mjs); a branch that changed the map regenerated the

View file

@ -0,0 +1,8 @@
{
"decision": "D01",
"status": "RATIFIED",
"word": "capped issuance, no tail escape clause, no forecast-triggered minting, no ordinary parameter vote that expands the cap; subject to the pre-launch security-funding gate TV-04 (a fail means delay or an open reconsideration, never an undisclosed rescue)",
"by": "founder",
"recorded_against": "its specification, release and acceptance tests before activation (the coordinator's relay of the founder's ratification, 9 October 2026, 09:2x UK)",
"condition": "subject to TV-04"
}

View file

@ -0,0 +1,8 @@
{
"decision": "D02",
"status": "RATIFIED WITH PARAMETER PENDING",
"word": "the same-cap emission comparison is approved (current pacing vs a longer distribution; code-generated figures for years 1, 2, 5, 10, 20 with rounding and terminal behaviour; no founder reserve); the final schedule stays PENDING EVIDENCE, neither alternative frozen",
"by": "founder",
"recorded_against": "its specification, release and acceptance tests before activation (the coordinator's relay of the founder's ratification, 9 October 2026, 09:2x UK)",
"condition": "the final schedule PENDING EVIDENCE"
}

View file

@ -0,0 +1,7 @@
{
"decision": "D03",
"status": "RATIFIED",
"word": "separate accountable budgets for hashing, internal proving, external work and maintenance, every payment counted once; external prover revenue never counted as miner security, maintenance and Labs profit at once",
"by": "founder",
"recorded_against": "its specification, release and acceptance tests before activation (the coordinator's relay of the founder's ratification, 9 October 2026, 09:2x UK)"
}

View file

@ -0,0 +1,8 @@
{
"decision": "D04",
"status": "RATIFIED WITH PARAMETER PENDING",
"word": "explicit auditable routing with no hidden company allocation; the 80/20 split is emission allocation only; the external-job fee or burn rate is NOT ratified, comparative pricing tests decide",
"by": "founder",
"recorded_against": "its specification, release and acceptance tests before activation (the coordinator's relay of the founder's ratification, 9 October 2026, 09:2x UK)",
"condition": "the external-job fee or burn rate not ratified; comparative pricing tests decide"
}

View file

@ -0,0 +1,7 @@
{
"decision": "D05",
"status": "RATIFIED",
"word": "a recovery certificate is never ordinary finality on any wallet, receipt, API or bridge; labels do not repair an unsafe recovery rule, which still needs its tests",
"by": "founder",
"recorded_against": "its specification, release and acceptance tests before activation (the coordinator's relay of the founder's ratification, 9 October 2026, 09:2x UK)"
}

View file

@ -0,0 +1,7 @@
{
"decision": "D06",
"status": "RATIFIED",
"word": "separate claims; public wording \"Designed to compete for leadership among GPU-first networks.\"; after testing, the demonstrated advantage is named, never a blanket number one",
"by": "founder",
"recorded_against": "its specification, release and acceptance tests before activation (the coordinator's relay of the founder's ratification, 9 October 2026, 09:2x UK)"
}

View file

@ -0,0 +1,4 @@
{
"suite": "TV",
"text": "Counting note (main's order, 9 October 2026, by 11:00 UK): the VR rules (40) and TV gates (32) are a supplemental layer over the master's 128 cases, the 18 integration gates and the 44 review regressions, never added to them as independent evidence; no TV gate has executed; every VR rule is PROPOSED and requires ratification by the founder and its owner role; the decisions D01 to D06 are UNAPPROVED, the founder's."
}

View file

@ -0,0 +1,4 @@
{
"suite": "registry",
"text": "Counting note (9 October 2026): the registry carries 128 master cases, 18 integration gates (INT), 44 review regressions (REV), and the token value layer (VR 40 rules, TV 32 gates) as a supplement that is never counted as independent evidence; the decisions block D01 to D06 is UNAPPROVED."
}

View file

@ -179,6 +179,7 @@ tree_checks() {
run "the kit ISA check: a distribution kit's binaries carry no AVX-512 encoding (the x86-64-v3 baseline; self-test with a fake objdump)" bash tools/ci/kit-isa-check.sh --self-test
run "F02 (Review B): the proof-rule test bypass cannot reach a release build: an env read with no cfg guard or under a default feature is red; a release binary carrying the bypass string is red (self-test)" bash tools/ci/proof-rule-bypass-check.sh --self-test
run "the test map merges structurally at a landing: master's cells plus the branch's, minus what the branch removed and master left (self-test)" python3 tools/ci/test-map-merge.py --self-test
run "the served-file identity guard: a served file built from reports or merges is refused at its publish step on an identity hit, logged redacted; the box form matches hashed tokens under a salt (self-test)" bash tools/ci/served-identity-guard.sh --self-test
run "rule 33: a release entry publishes only with a PASS fresh-install canary record for its sha (the record check and the publish guard, self-tests)" bash -c 'bash tools/ci/canary-check.sh --self-test >/dev/null && bash packaging/ota/publish-manifest.sh --self-test-canary-guard >/dev/null'
run "the guest input format moves with the pinned guests: the elf manifest's guest_input_format equals the source constant where the field exists, a format bump without a guest change is refused at the merge (self-test, then the tree)" bash -c 'bash tools/ci/guest-format-check.sh --self-test >/dev/null && bash tools/ci/guest-format-check.sh --tree .'
run "the REV suite is generated from Review B's findings and dispatch and matches them (self-test, then the tree)" bash tools/ci/review-suite-check.sh

View file

@ -4,3 +4,4 @@ set -euo pipefail
node tools/ci/review-suite.mjs --self-test | grep -v '^self-test passed' || true
node tools/ci/review-suite.mjs --findings docs/analysis/review-2026-10-08-b/findings.json --dispatch docs/analysis/review-2026-10-08-b/dispatch.md --prefix REV --check
if [ -f docs/plans/igneum-2.0-master/traceability.json ]; then node tools/ci/int-suite.mjs --self-test | grep -v '^self-test passed' || true; node tools/ci/int-suite.mjs --traceability docs/plans/igneum-2.0-master/traceability.json --check; fi
if [ -f docs/plans/igneum-2.0-master/token-value/rules-and-gates.json ]; then node tools/ci/token-value-suite.mjs --self-test | grep -v '^self-test passed' || true; node tools/ci/token-value-suite.mjs --check; fi

View file

@ -0,0 +1,26 @@
#!/usr/bin/env bash
# The daily identity check over the EDGE copies of the served generated files (the coordinator's class, 9 October 2026): fetch each
# served file from its public URL and run tools/ci/served-identity-guard.sh over it; a hit is a red to main the same hour. Runs on
# build-1 from cron (the build-server lane's unit) and by hand; the list of guarded files is here and nowhere else.
#
# tools/ci/served-identity-daily.sh exit 0 clean, 1 a hit (named), 2 a fetch failed or no private list
# tools/ci/served-identity-daily.sh --list print the guarded URLs
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd -P)"
URLS=(
https://build.igneum.network/workers.json # the workers page's merged reports (tools/workers/push.mjs, the build-server lane)
https://build.igneum.network/intake.json # the PC intake merge (the build-server lane)
https://igneum.network/release.json # the site's release manifest (site/build.mjs from site/release-manifest.json, the site lane)
https://dl.igneum.network/public/igneum-app-latest.json # the public app manifest (packaging/ota/publish-public.sh)
https://dl.igneum.network/public/igneum-wallet-latest.json # the public wallet manifest (packaging/ota/publish-public.sh)
https://build.igneum.network/feed.json # the hourly feed on build-1 (the build-server lane's unit)
)
if [ "${1:-}" = --list ]; then printf '%s\n' "${URLS[@]}" | sed 's/ *#.*//'; exit 0; fi
d=$(mktemp -d); trap 'rm -rf "$d"' EXIT; rc=0; files=()
for u in "${URLS[@]}"; do
u="${u%% *}"; f="$d/$(printf '%s' "$u" | sed 's#https://##; s#/#_#g')"
if curl -fsSL --max-time 20 -o "$f" "$u" 2>/dev/null; then files+=("$f"); else echo "served-identity-daily: fetch failed or absent: $u" >&2; [ "$rc" = 0 ] && rc=2; fi
done
[ "${#files[@]}" -gt 0 ] || { echo "served-identity-daily: nothing fetched" >&2; exit 2; }
bash "$HERE/served-identity-guard.sh" "${files[@]}" || rc=1
exit $rc

View file

@ -0,0 +1,99 @@
#!/usr/bin/env bash
# The identity guard on SERVED files built from machine reports or merges (the coordinator's class, 9 October 2026, 09:15 UK: the served
# workers.json on build.igneum.network carried the owner's first name in a free-text note from a PC report; the identity grep guarded
# committed text, not served JSON built from reports). Every publisher of such a file calls this before its write and refuses the
# publish on a hit; the daily edge check (tools/ci/served-identity-daily.sh) runs the same guard over the edge copies.
#
# tools/ci/served-identity-guard.sh <file> [<file> ...] exit 0 clean; 1 a hit (the publish is refused, the refusal logged); 2 no list
# tools/ci/served-identity-guard.sh --self-test
#
# Patterns: the committed lists (tools/ci/forbidden-strings.txt, site/forbidden-strings.txt: machine model names, LAN addresses, home
# paths, rig names, the zone word, the owner's name inside a host name) plus the PRIVATE list the export uses (the owner's names, machine
# local names with his name, the home IP), read from IGNEUM_IDENTITY_LOCAL, else ~/.config/igneum/identity.local on the Mac, else the
# public mirror's own list (~/Projects/igneum-public/tools/identity.local), else /srv/identity/identity.local on a box: one extended regular expression per line, comments with #. The private list never enters the
# repository. A publish host without the private list is refused (exit 2) unless IGNEUM_IDENTITY_LOCAL_OPTIONAL=1 names the run as a
# tree check, never a publish. Refusals are appended to IGNEUM_IDENTITY_REFUSALS (default ~/.config/igneum/identity-refusals.log, or
# /srv/identity/refusals.log on a box) as "<utc> <file> <pattern class> <line>" with the matched text replaced by <redacted>.
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd -P)"; ME="$HERE/$(basename "$0")"; REPO="${IDENTITY_GUARD_REPO:-$(cd "$HERE/../.." && pwd -P)}"
# the BOX form (9 October 2026, 09:3x UK, the build-server lane's objection under the standing rule that a box holds no secret): on a box
# the private list is a HASHED token list, /srv/identity/identity.hashes (one HMAC-SHA256 hex per literal token, under the 32-byte salt
# at /srv/identity/salt; both mode 600), written on the Mac by tools/ci/served-identity-hashes.sh from the clear list and copied over,
# never from the repository; the guard tokenizes the served file and matches digests, so the box carries no name and no address.
# IGNEUM_IDENTITY_HASHES and IGNEUM_IDENTITY_SALT override the paths.
hashed_hits() { # <file> -> prints "line" numbers whose tokens match a digest in the hashed list; empty when no hashed list
local hf="${IGNEUM_IDENTITY_HASHES:-/srv/identity/identity.hashes}" sf="${IGNEUM_IDENTITY_SALT:-/srv/identity/salt}"
[ -f "$hf" ] && [ -f "$sf" ] || return 0
python3 - "$1" "$hf" "$sf" <<'PY2'
import sys, hmac, hashlib, re
f, hf, sf = sys.argv[1:4]; digests = {l.strip().lower() for l in open(hf) if l.strip() and not l.startswith('#')}; salt = open(sf, 'rb').read()
for n, line in enumerate(open(f, errors='replace'), 1):
for tok in re.findall(r"[A-Za-z0-9][A-Za-z0-9._-]*", line):
t = tok.lower().strip('.-')
if t and hmac.new(salt, t.encode(), hashlib.sha256).hexdigest() in digests: print(n); break
PY2
}
have_hashed() { [ -f "${IGNEUM_IDENTITY_HASHES:-/srv/identity/identity.hashes}" ] && [ -f "${IGNEUM_IDENTITY_SALT:-/srv/identity/salt}" ]; }
lists() { # prints every pattern, one per line: the committed lists, then the private one
for f in "$REPO/tools/ci/forbidden-strings.txt" "$REPO/site/forbidden-strings.txt"; do [ -f "$f" ] && grep -vE '^\s*(#|$)' "$f" || true; done
local priv="${IGNEUM_IDENTITY_LOCAL:-}"
if [ -z "$priv" ]; then for c in "$HOME/.config/igneum/identity.local" "$HOME/Projects/igneum-public/tools/identity.local" /srv/identity/identity.local; do [ -f "$c" ] && { priv="$c"; break; }; done; fi # the public mirror's export list is the same private list on this Mac
if [ -n "$priv" ] && [ -f "$priv" ]; then grep -vE '^\s*(#|$)' "$priv" || true; printf '%s\n' "__PRIVATE_LIST_PRESENT__"; fi
}
refusals_log() { if [ -n "${IGNEUM_IDENTITY_REFUSALS:-}" ]; then printf '%s' "$IGNEUM_IDENTITY_REFUSALS"; elif [ -d /srv/identity ]; then printf '%s' /srv/identity/refusals.log; else printf '%s' "$HOME/.config/igneum/identity-refusals.log"; fi; }
guard() { # <file...> -> 0 clean, 1 hit, 2 no private list
local all pats have_priv=0 f hits rc=0 log; all="$(lists)"
case "$all" in *__PRIVATE_LIST_PRESENT__*) have_priv=1 ;; esac
pats="$(printf '%s\n' "$all" | grep -v '^__PRIVATE_LIST_PRESENT__$' || true)"
if [ "$have_priv" = 0 ] && have_hashed; then have_priv=2; fi # the box form
if [ "$have_priv" = 0 ] && [ "${IGNEUM_IDENTITY_LOCAL_OPTIONAL:-0}" != 1 ]; then
echo "served-identity-guard: REFUSED: no private identity list on this host (the clear list on the Mac: IGNEUM_IDENTITY_LOCAL or ~/.config/igneum/identity.local; the hashed list on a box: /srv/identity/identity.hashes with its salt); a publish never runs without one" >&2; return 2
fi
[ -n "$pats" ] || { echo "served-identity-guard: no patterns at all; refusing" >&2; return 2; }
log="$(refusals_log)"; mkdir -p "$(dirname "$log")" 2>/dev/null || true
for f in "$@"; do
[ -f "$f" ] || { echo "served-identity-guard: no such file $f" >&2; rc=1; continue; }
hits="$(grep -nE -f <(printf '%s\n' "$pats") "$f" 2>/dev/null || true)"
if [ "$have_priv" = 2 ]; then local hh; hh="$(hashed_hits "$f" | sed 's/$/:<hashed token>/')"; [ -n "$hh" ] && hits="$(printf '%s\n%s\n' "$hits" "$hh" | grep -v '^$')"; fi
if [ -n "$hits" ]; then
rc=1
printf '%s\n' "$hits" | while IFS= read -r line; do
local n="${line%%:*}"
echo "served-identity-guard: REFUSED: $f:$n carries a forbidden identity pattern (the publish is refused; the line is in the refusals log, redacted)" >&2
printf '%s %s line %s %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$f" "$n" "<redacted>" >> "$log" 2>/dev/null || true
done
fi
done
[ "$rc" = 0 ] && echo "served-identity-guard: $# file(s) clean under $(printf '%s\n' "$pats" | grep -c .) patterns (private list: $([ "$have_priv" = 1 ] && echo "clear, this Mac" || { [ "$have_priv" = 2 ] && echo "hashed, this box" || echo "absent, tree check"; }))"
return $rc
}
if [ "${1:-}" = --self-test ]; then
d=$(mktemp -d); trap 'rm -rf "$d"' EXIT; fails=0
mkdir -p "$d/repo/tools/ci" "$d/repo/site"; printf '\\bPC [12]\\b\n' > "$d/repo/site/forbidden-strings.txt"; printf '/Users/[a-z]+\n' > "$d/repo/tools/ci/forbidden-strings.txt"
printf '# private\n\\bOwnerName\\b\n10\\.0\\.0\\.77\n' > "$d/identity.local"
export IDENTITY_GUARD_REPO="$d/repo" IGNEUM_IDENTITY_REFUSALS="$d/refusals.log"
printf '{"note": "a clean report", "host": "lp-4090-11"}\n' > "$d/clean.json"
printf '{"note": "reported by OwnerName at 10.0.0.77"}\n' > "$d/leak.json"
printf '{"note": "ran on PC 2"}\n' > "$d/rig.json"
IGNEUM_IDENTITY_LOCAL="$d/identity.local" bash "$ME" "$d/clean.json" >/dev/null 2>&1 || { echo "self-test failed: a clean served file was refused: $(IGNEUM_IDENTITY_LOCAL="$d/identity.local" bash "$ME" "$d/clean.json" 2>&1)"; fails=1; }
IGNEUM_IDENTITY_LOCAL="$d/identity.local" bash "$ME" "$d/leak.json" >/dev/null 2>&1 && { echo "self-test failed: a served file carrying the owner's name and the home IP passed"; fails=1; }
grep -q 'leak.json line 1 <redacted>' "$d/refusals.log" 2>/dev/null || { echo "self-test failed: the refusal was not logged redacted"; fails=1; }
grep -q 'OwnerName' "$d/refusals.log" 2>/dev/null && { echo "self-test failed: the refusals log carries the matched text"; fails=1; }
IGNEUM_IDENTITY_LOCAL="$d/identity.local" bash "$ME" "$d/rig.json" >/dev/null 2>&1 && { echo "self-test failed: a served file carrying a rig name passed"; fails=1; }
rc=0; IGNEUM_IDENTITY_LOCAL="$d/missing.local" HOME="$d" bash "$ME" "$d/clean.json" >/dev/null 2>&1 || rc=$?; [ "$rc" = 2 ] || { echo "self-test failed: a publish host without the private list was not refused with exit 2 (got $rc)"; fails=1; }
IGNEUM_IDENTITY_LOCAL="$d/missing.local" HOME="$d" IGNEUM_IDENTITY_LOCAL_OPTIONAL=1 bash "$ME" "$d/clean.json" >/dev/null 2>&1 || { echo "self-test failed: a tree check without the private list was refused"; fails=1; }
# the box form: a hashed token list and a salt, no clear list; the owner's name token and the home IP are refused, a clean file passes
printf 'saltsaltsaltsaltsaltsaltsaltsalt' > "$d/salt"; python3 -c "
import hmac,hashlib; salt=open('$d/salt','rb').read()
open('$d/identity.hashes','w').write('\n'.join(hmac.new(salt, t.encode(), hashlib.sha256).hexdigest() for t in ('ownername','10.0.0.77'))+'\n')"
IGNEUM_IDENTITY_LOCAL="$d/missing.local" HOME="$d" IGNEUM_IDENTITY_HASHES="$d/identity.hashes" IGNEUM_IDENTITY_SALT="$d/salt" bash "$ME" "$d/clean.json" >/dev/null 2>&1 || { echo "self-test failed: the box form refused a clean file"; fails=1; }
IGNEUM_IDENTITY_LOCAL="$d/missing.local" HOME="$d" IGNEUM_IDENTITY_HASHES="$d/identity.hashes" IGNEUM_IDENTITY_SALT="$d/salt" bash "$ME" "$d/leak.json" >/dev/null 2>&1 && { echo "self-test failed: the box form passed the owner's name and the home IP by their hashes"; fails=1; }
grep -q 'ownername\|OwnerName' "$d/identity.hashes" && { echo "self-test failed: the hashed list carries a name in clear"; fails=1; }
[ "$fails" = 0 ] && echo "self-test passed: a clean served file passes; the owner's name, the home IP (the private list) and a rig name (the committed list) are refused and logged redacted; a publish host without the private list is refused with exit 2, a tree check passes without it; the box form (a hashed token list and a salt, no name in clear) refuses the same leaks and passes the clean file"
exit $fails
fi
[ $# -ge 1 ] || { echo "usage: $0 <file> [<file> ...] | --self-test" >&2; exit 2; }
guard "$@"

View file

@ -0,0 +1,26 @@
#!/usr/bin/env bash
# Writes the BOX form of the private identity list (9 October 2026): one HMAC-SHA256 hex digest per literal token in the clear list, under
# a 32-byte salt, so a box carries no name and no address. Regex entries (metacharacters) are skipped and counted; literal names, local
# machine names and addresses are tokenized the way the guard tokenizes a served file (lowercase, [A-Za-z0-9._-] runs). Run on the Mac;
# copy the two files to the box by scp (never through the repository): /srv/identity/identity.hashes and /srv/identity/salt, mode 600.
# tools/ci/served-identity-hashes.sh <identity.local> <salt file (created if absent, 32 random bytes)> > identity.hashes
set -euo pipefail
[ $# -eq 2 ] || { echo "usage: $0 <identity.local> <salt file>" >&2; exit 2; }
[ -f "$1" ] || { echo "no $1" >&2; exit 2; }
[ -f "$2" ] || { head -c 32 /dev/urandom > "$2"; chmod 600 "$2"; echo "served-identity-hashes: a new salt at $2" >&2; }
python3 - "$1" "$2" <<'PY'
import sys, hmac, hashlib, re
lst, sf = sys.argv[1:3]; salt = open(sf, 'rb').read(); n = 0; skipped = 0; out = set()
for raw in open(lst):
line = raw.strip()
if not line or line.startswith('#'): continue
if re.search(r'[\\^$.|?*+()\[\]{}]', line) and not re.fullmatch(r'[A-Za-z0-9._-]+', line.replace('\\.', '.')):
skipped += 1; continue
lit = line.replace('\\.', '.')
for tok in re.findall(r"[A-Za-z0-9][A-Za-z0-9._-]*", lit):
t = tok.lower().strip('.-')
if t: out.add(hmac.new(salt, t.encode(), hashlib.sha256).hexdigest()); n += 1
print('# served-identity hashed token list (HMAC-SHA256 under the host salt); no name in clear')
for h in sorted(out): print(h)
print(f'served-identity-hashes: {len(out)} digests from {n} tokens; {skipped} regex entries skipped (the committed pattern lists carry those forms)', file=sys.stderr)
PY

View file

@ -882,6 +882,38 @@
"R2-F13-R03": "R2-F13 (P1, the external review): the regression's harness is the owner lane's (pool lane (new)); not yet named in the map",
"R2-F14-R01": "R2-F14 (P1, the external review): the regression's harness is the owner lane's (app lane, relay lane); not yet named in the map",
"R2-F14-R02": "R2-F14 (P1, the external review): the regression's harness is the owner lane's (app lane, relay lane); not yet named in the map",
"R2-F14-R03": "R2-F14 (P1, the external review): the regression's harness is the owner lane's (app lane, relay lane); not yet named in the map"
"R2-F14-R03": "R2-F14 (P1, the external review): the regression's harness is the owner lane's (app lane, relay lane); not yet named in the map",
"TV-01": "TV-01 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Protocol + independent panel); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-02": "TV-02 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Protocol + measurement); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-03": "TV-03 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Release + ecosystem); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-04": "TV-04 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Economics + protocol); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-05": "TV-05 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Hardware reviewer + economics); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-06": "TV-06 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Economics + application); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-07": "TV-07 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Protocol + independent security); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-08": "TV-08 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Wallet + independent users); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-09": "TV-09 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Wallet + consensus); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-10": "TV-10 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Protocol + application); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-11": "TV-11 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Pool + independent operators); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-12": "TV-12 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Release + independent security); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-13": "TV-13 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Maintainers + independent finance); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-14": "TV-14 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Independent operations panel); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-15": "TV-15 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Protocol + governance); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-16": "TV-16 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Ecosystem + independent reviewer); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-17": "TV-17 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Measurement steward); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-18": "TV-18 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Economics + measurement); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-19": "TV-19 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Ecosystem + external developers); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-20": "TV-20 (token value gate, scope PAYMENT CAPABILITY): no gate executed; the harness is the owner role's (Application + wallet); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-21": "TV-21 (token value gate, scope PAID WORK CAPABILITY): no gate executed; the harness is the owner role's (Proving + independent customers); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-22": "TV-22 (token value gate, scope WORK MARKET CAPABILITY): no gate executed; the harness is the owner role's (Application + independent operators); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-23": "TV-23 (token value gate, scope SERVICE BONDS IF ENABLED): no gate executed; the harness is the owner role's (Economics + security); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-24": "TV-24 (token value gate, scope AI IF ENABLED): no gate executed; the harness is the owner role's (Compute + independent reviewer); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-25": "TV-25 (token value gate, scope AGENTS IF ENABLED): no gate executed; the harness is the owner role's (Application + security); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-26": "TV-26 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Cryptography + protocol); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-27": "TV-27 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Security + product); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-28": "TV-28 (token value gate, scope STABLE/BRIDGE IF ENABLED): no gate executed; the harness is the owner role's (Application + security + counsel); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-29": "TV-29 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Protocol + operations); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-30": "TV-30 (token value gate, scope CORE): no gate executed; the harness is the owner role's (GPU + measurement); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-31": "TV-31 (token value gate, scope CORE): no gate executed; the harness is the owner role's (Qualified counsel); a supplemental layer, never counted as independent evidence; not yet named in the map",
"TV-32": "TV-32 (token value gate, scope LEADERSHIP CLAIM): no gate executed; the harness is the owner role's (Independent acceptance panel); a supplemental layer, never counted as independent evidence; not yet named in the map"
}
}

View file

@ -207,6 +207,14 @@ if (args.includes('--self-test')) {
if (reg.cases[2].run_status) { console.log('self-test failed: a manual case was given a run status'); fails = 1; }
const regS = { suites: [{ code: 'X', tests: [{ id: 'X-1', method: 'Automated', accept: 'a' }] }] }; if (casesOf(regS).length !== 1) { console.log('self-test failed: the suites/tests registry shape was not read'); fails = 1; }
const regN = { suites: [{ code: 'FIN', tests: [{ id: 'F-1', method: 'Automated', accept: 'keep' }] }] }; fs.writeFileSync(`${d}/regn.json`, JSON.stringify(regN));
const regD = { suites: [], decisions: [{ id: 'D01', title: 't', required_resolution: 'r', status: 'UNAPPROVED', owner: 'founder' }] }; fs.writeFileSync(`${d}/regd.json`, JSON.stringify(regD));
fs.writeFileSync(`${d}/dn.json`, JSON.stringify({ decision: 'D01', status: 'RATIFIED', word: 'capped issuance', by: 'founder', recorded_against: 'spec' }));
const dr = child_process.spawnSync(process.execPath, [new URL(import.meta.url).pathname, '--note-file', `${d}/dn.json`], { env: { ...process.env, TEST_REGISTRY: `${d}/regd.json`, TEST_MAP: `${d}/map.json` }, encoding: 'utf8' });
const regD2 = JSON.parse(fs.readFileSync(`${d}/regd.json`, 'utf8'));
if (!(dr.status === 0 && regD2.decisions[0].status === 'RATIFIED' && regD2.decisions[0].word === 'capped issuance' && regD2.decisions[0].ratified_by === 'founder' && regD2.decisions[0].ratified_at)) { console.log(`self-test failed: a decision note file did not ratify D01: ${dr.stdout} ${dr.stderr}`); fails = 1; }
fs.writeFileSync(`${d}/dnb.json`, JSON.stringify({ decision: 'D01', status: 'APPROVED-ISH', word: 'x', by: 'founder' }));
const drb = child_process.spawnSync(process.execPath, [new URL(import.meta.url).pathname, '--note-file', `${d}/dnb.json`], { env: { ...process.env, TEST_REGISTRY: `${d}/regd.json`, TEST_MAP: `${d}/map.json` }, encoding: 'utf8' });
if (drb.status === 0) { console.log('self-test failed: a decision status outside the vocabulary was accepted'); fails = 1; }
const nr = child_process.spawnSync(process.execPath, [new URL(import.meta.url).pathname, '--note', 'FIN', 'the ruling'], { env: { ...process.env, TEST_REGISTRY: `${d}/regn.json`, TEST_MAP: `${d}/map.json` }, encoding: 'utf8' });
const regN2 = JSON.parse(fs.readFileSync(`${d}/regn.json`, 'utf8'));
if (!(nr.status === 0 && regN2.suites[0].notes?.length === 1 && regN2.suites[0].notes[0].text === 'the ruling' && regN2.suites[0].tests[0].accept === 'keep')) { console.log(`self-test failed: --note did not append a dated note to the suite and keep the accept text: ${nr.stdout} ${nr.stderr}`); fails = 1; }
@ -243,7 +251,19 @@ if (args.includes('--check')) { const r = check(reg, map); for (const l of r.lin
if (args.includes('--label-islands')) { const n = labelIslands(reg); fs.writeFileSync(REG, JSON.stringify(reg, null, 2) + '\n'); console.log(`test-record: ${n} record(s) measured on the 2.0 devnet since ${ISLAND_SINCE} labelled "${ISLAND_LABEL}"`); process.exit(0); }
if (args.includes('--normalize')) { const n = normalize(reg); fs.writeFileSync(REG, JSON.stringify(reg, null, 2) + '\n'); console.log(`test-record: normalized ${n} rows to the decision vocabulary and the record schema`); process.exit(0); }
if (arg('--cases')) { console.log(((map.cells || {})[arg('--cases')]?.cases || []).join(',')); process.exit(0); }
if (arg('--note-file')) { const n = load(arg('--note-file')); args.push('--note', n.suite, n.text); }
if (arg('--note-file')) { const n = load(arg('--note-file'));
if (n.decision) { // the decision form (9 October 2026, 09:3x UK): the founder's ratification of a decision in the registry's decisions block, replayed at the merge like any note
const STATUSES = new Set(['UNAPPROVED', 'RATIFIED', 'RATIFIED WITH PARAMETER PENDING', 'PENDING EVIDENCE', 'REJECTED']);
if (!STATUSES.has(String(n.status))) { console.error(`test-record: decision ${n.decision}: status ${n.status} is not one of ${[...STATUSES].join(' | ')}`); process.exit(2); }
if (!n.word || !n.by) { console.error(`test-record: decision ${n.decision}: a ratification carries the founder's word verbatim (word) and who said it (by)`); process.exit(2); }
reg.decisions = reg.decisions || []; const dec = reg.decisions.find((x) => x.id === n.decision);
if (!dec) { console.error(`test-record: decision ${n.decision} is not in the registry's decisions block (the VR and TV suites bring D01 to D06)`); process.exit(2); }
const before = acceptSnapshot(reg);
dec.status = n.status; dec.word = n.word; dec.ratified_by = n.by; dec.ratified_at = n.at || new Date().toISOString(); if (n.recorded_against) dec.recorded_against = n.recorded_against; if (n.condition) dec.condition = n.condition;
if (acceptSnapshot(reg) !== before) { console.error('test-record: REFUSED: a decision changed an accept text'); process.exit(1); }
fs.writeFileSync(REG, JSON.stringify(reg, null, 2) + '\n'); console.log(`test-record: decision ${n.decision} ${n.status} (${n.by})`); process.exit(0);
}
args.push('--note', n.suite, n.text); }
if (arg('--note')) {
const code = arg('--note'); const text = args[args.indexOf('--note') + 2];
if (code === 'registry' && text) { const before = acceptSnapshot(reg); reg.notes = reg.notes || []; reg.notes.push({ at: new Date().toISOString(), text }); if (acceptSnapshot(reg) !== before) { console.error('test-record: REFUSED'); process.exit(1); } fs.writeFileSync(REG, JSON.stringify(reg, null, 2) + '\n'); console.log(`test-record: registry note ${reg.notes.length}: ${text.slice(0, 80)}`); process.exit(0); }

View file

@ -0,0 +1,98 @@
#!/usr/bin/env node
// The VR and TV suites of the acceptance registry (main's order through the coordinator, 9 October 2026, by 11:00 UK): generated from
// docs/plans/igneum-2.0-master/token-value/rules-and-gates.json, never by hand; --check refuses drift.
// VR: the 40 normative rules, each a row whose rule_status is the file's (PROPOSED - REQUIRES RATIFICATION), run_status NOT RUN with the
// reason "requires ratification", method "Ratification" (not Automated: no harness exists for a rule; ratification is the founder's
// and the owner role's), the owner role, the requirement verbatim as title and accept, the file's evidence line as evidence.
// TV: the 32 gates, each a row NOT RUN, method "Automated + independent review", the scope as the file gives it (CORE, CAPABILITY IF
// ENABLED, LEADERSHIP CLAIM), inherited_links kept as cross-references to the master's suites and profiles, the pass criterion
// verbatim as accept, setup and procedure carried, the threshold_status carried.
// decisions: D01 to D06 as the registry's top-level `decisions` block, every one UNAPPROVED, the founder's.
// The counting note stays: these are a supplemental layer, never added to the 128 + 18 + 44 as independent evidence (tools/ci/notes).
// node tools/ci/token-value-suite.mjs --file <rules-and-gates.json> [--write | --check] node tools/ci/token-value-suite.mjs --self-test
import fs from 'node:fs'; import path from 'node:path';
const args = process.argv.slice(2); const arg = (n) => { const i = args.indexOf(n); return i >= 0 ? args[i + 1] : undefined; };
const ROOT = process.env.TEST_RECORD_ROOT || path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', '..');
const REG = process.env.TEST_REGISTRY || path.join(ROOT, 'docs/plans/igneum-2.0-test-registry.json');
const MAP = process.env.TEST_MAP || path.join(ROOT, 'tools/ci/test-map.json');
const SRC = 'docs/plans/igneum-2.0-master/token-value/rules-and-gates.json';
export function suites(d) {
const vr = { code: 'VR', title: 'VR: the token value and network leadership rules (normative, proposed, requiring ratification)', source: `${SRC} rules`,
gate: 'Ratification by the founder and each rule\'s owner role', owner: 'founder (ratification); the owner role per rule', fixtures: ['F0'],
summary: `${(d.rules || []).length} normative rules from ${d.document} ${d.version} (snapshot ${d.snapshot}); ${d.scope}`,
tests: (d.rules || []).map((r) => ({ id: r.id, title: r.title, requirement: r.requirement, accept: r.requirement, evidence: r.evidence || '',
priority: 'P0', profile: 'P00', cadence: 'Once, at ratification; again on any change of the rule', method: 'Ratification', source: ['TV'],
gate: 'Ratified', owner: r.owner_role || 'unassigned', owner_role: r.owner_role || 'unassigned', manual_page: null,
rule_status: r.status || 'PROPOSED - REQUIRES RATIFICATION', run_status: 'NOT RUN' })) };
const tv = { code: 'TV', title: 'TV: the token value and network leadership gates (proposed; no gate executed)', source: `${SRC} gates`,
gate: 'Token value gates closed', owner: 'the owner role per gate', fixtures: ['F0'],
summary: `${(d.gates || []).length} gates from ${d.document} ${d.version} (snapshot ${d.snapshot}); scope CORE, CAPABILITY IF ENABLED or LEADERSHIP CLAIM as the file gives it; ${d.scope}`,
tests: (d.gates || []).map((g) => ({ id: g.id, title: g.title, setup: g.setup || '', procedure: g.procedure || '', accept: g.pass_criterion || '', evidence: g.evidence || '',
priority: g.scope === 'CORE' ? 'P0' : 'P1', profile: 'P00', cadence: 'Every release candidate', method: 'Automated + independent review', source: ['TV'],
gate: 'Token value gates closed', owner: g.owner_role || 'unassigned', owner_role: g.owner_role || 'unassigned', manual_page: null,
scope: g.scope || '', inherited_links: Array.isArray(g.inherited_links) ? g.inherited_links.slice() : [], threshold_status: g.threshold_status || '',
run_status: 'NOT RUN', master_status: g.status || 'NOT RUN' })) };
const decisions = (d.decisions || []).map((x) => ({ id: x.id, title: x.title, required_resolution: x.required_resolution, status: 'UNAPPROVED', owner: 'founder' }));
return { vr, tv, decisions };
}
const LIVE = ['run_status', 'run_id', 'evidence_path', 'updated', 'evidence_record', 'evidence_records', 'blocked_on', 'method_recorded', 'in_progress_since', 'deferral_note', 'approvals', 'ratified_at', 'ratified_by'];
export function merge(reg, s) {
const old = (reg.suites || []).find((x) => x.code === s.code); const live = new Map((old?.tests || []).map((t) => [t.id, t]));
for (const t of s.tests) { const o = live.get(t.id); if (o) for (const k of LIVE) if (k in o) t[k] = o[k]; }
if (old?.notes) s.notes = old.notes;
reg.suites = [...(reg.suites || []).filter((x) => x.code !== s.code), s]; return reg;
}
export function mergeDecisions(reg, decisions) {
const old = new Map((reg.decisions || []).map((x) => [x.id, x]));
const KEEP = ['status', 'word', 'ratified_at', 'ratified_by', 'recorded_against', 'condition', 'approved_at', 'approved_by'];
reg.decisions = decisions.map((x) => { const o = old.get(x.id); if (!(o && o.status && o.status !== 'UNAPPROVED')) return x; const k = { ...x }; for (const f of KEEP) if (f in o) k[f] = o[f]; return k; });
return reg;
}
const canon = (o) => JSON.stringify(o, (k, v) => (v && typeof v === 'object' && !Array.isArray(v)) ? Object.fromEntries(Object.keys(v).sort().map((x) => [x, v[x]])) : v);
export function mapReasons(map, tv) { // a TV gate with no cell reads NOT RUN with its reason (the check's rule for Automated cases)
const mapped = new Set(Object.values(map.cells || {}).flatMap((c) => c.cases || [])); map.not_run = map.not_run || {}; let n = 0;
for (const t of tv.tests) { if (mapped.has(t.id)) { delete map.not_run[t.id]; continue; } map.not_run[t.id] = `${t.id} (token value gate, scope ${t.scope}): no gate executed; the harness is the owner role's (${t.owner_role}); a supplemental layer, never counted as independent evidence; not yet named in the map`; n++; }
return n;
}
function stripLive(s) { return { ...s, notes: undefined, tests: s.tests.map((t) => { const c = { ...t }; for (const k of LIVE) delete c[k]; return c; }) }; }
if (args.includes('--self-test')) {
let fails = 0;
const d = { document: 'doc', version: '1.0-proposed', snapshot: '2026-10-08', scope: 'a supplement', status: 'PROPOSED',
rules: [{ id: 'VR-01', title: 'one', requirement: 'ratify one contract', evidence: 'e', owner_role: 'Protocol + governance', status: 'PROPOSED - REQUIRES RATIFICATION' }],
gates: [{ id: 'TV-01', title: 'freeze', setup: 's', procedure: 'p', pass_criterion: 'no conflict', evidence: 'e', owner_role: 'Protocol + independent panel', inherited_links: ['GOV', 'VR-01'], scope: 'CORE', status: 'NOT RUN', threshold_status: 'PROPOSED' },
{ id: 'TV-02', title: 'cap', setup: 's', procedure: 'p', pass_criterion: 'c', evidence: 'e', owner_role: 'x', inherited_links: [], scope: 'CAPABILITY IF ENABLED', status: 'NOT RUN', threshold_status: '' }],
decisions: [{ id: 'D01', title: 'cap', required_resolution: 'choose', status: 'UNAPPROVED' }] };
const { vr, tv, decisions } = suites(d);
if (!(vr.code === 'VR' && vr.tests.length === 1 && vr.tests[0].method === 'Ratification' && vr.tests[0].run_status === 'NOT RUN' && vr.tests[0].rule_status === 'PROPOSED - REQUIRES RATIFICATION' && vr.tests[0].accept === 'ratify one contract' && vr.tests[0].owner_role === 'Protocol + governance')) { console.log('self-test failed: the VR row is not the rule verbatim, NOT RUN, Ratification, with its owner role and rule_status'); fails = 1; }
if (!(tv.code === 'TV' && tv.tests.length === 2 && tv.tests[0].scope === 'CORE' && tv.tests[0].priority === 'P0' && tv.tests[1].priority === 'P1' && tv.tests[0].inherited_links.join() === 'GOV,VR-01' && tv.tests[0].accept === 'no conflict' && tv.tests[0].run_status === 'NOT RUN')) { console.log('self-test failed: the TV row does not carry the scope, the links, the criterion and NOT RUN'); fails = 1; }
if (!(decisions.length === 1 && decisions[0].status === 'UNAPPROVED' && decisions[0].owner === 'founder')) { console.log('self-test failed: the decisions block is not UNAPPROVED and the founder\'s'); fails = 1; }
const reg = { suites: [{ code: 'TV', notes: [{ text: 'n' }], tests: [{ id: 'TV-01', run_status: 'NOT RUN', in_progress_since: 't', evidence_records: { 'c:x': { decision: 'PASS' } }, run_id: 'r' }] }], decisions: [{ id: 'D01', status: 'RATIFIED', word: 'w', ratified_by: 'founder', ratified_at: 't' }] };
merge(reg, tv); mergeDecisions(reg, decisions); const t = reg.suites[0];
if (!(t.tests[0].in_progress_since === 't' && t.tests[0].evidence_records?.['c:x']?.decision === 'PASS' && t.tests[1].run_status === 'NOT RUN' && t.notes?.length === 1 && reg.decisions[0].status === 'RATIFIED' && reg.decisions[0].word === 'w' && reg.decisions[0].ratified_at === 't')) { console.log('self-test failed: regenerating lost live fields, notes or an approved decision'); fails = 1; }
const map = { cells: { c: { cases: ['TV-01'] } }, not_run: {} }; if (!(mapReasons(map, tv) === 1 && !map.not_run['TV-01'] && /CAPABILITY IF ENABLED/.test(map.not_run['TV-02']))) { console.log('self-test failed: the map reasons for unmapped gates'); fails = 1; }
const s2 = suites(d); if (canon(stripLive(s2.tv)) !== canon(stripLive(tv))) { console.log('self-test failed: the generator is not deterministic'); fails = 1; }
if (!fails) console.log('self-test passed: one VR row per rule (the requirement verbatim, Ratification, NOT RUN, the rule_status and owner role), one TV row per gate (the scope, the links, the criterion, NOT RUN), the decisions block UNAPPROVED and the founder\'s; regenerating keeps live fields, notes and an approved decision; unmapped gates get their map reasons; the output is deterministic');
process.exit(fails);
}
const file = arg('--file') ? path.resolve(arg('--file')) : path.join(ROOT, SRC);
if (!fs.existsSync(file)) { console.error(`token-value-suite: no ${file}`); process.exit(2); }
const d = JSON.parse(fs.readFileSync(file, 'utf8')); const { vr, tv, decisions } = suites(d);
const reg = JSON.parse(fs.readFileSync(REG, 'utf8')); const map = fs.existsSync(MAP) ? JSON.parse(fs.readFileSync(MAP, 'utf8')) : { cells: {}, not_run: {} };
if (args.includes('--check')) {
let bad = 0;
for (const s of [vr, tv]) { const cur = (reg.suites || []).find((x) => x.code === s.code); if (!cur) { console.error(`token-value-suite: the registry has no ${s.code} suite; run --write`); bad++; continue; }
if (canon(stripLive(cur)) !== canon(stripLive(s))) { console.error(`token-value-suite: the registry's ${s.code} suite differs from the generator's output; run --write and commit`); bad++; } }
const curD = (reg.decisions || []).map((x) => ({ id: x.id, title: x.title, required_resolution: x.required_resolution })); const wantD = decisions.map((x) => ({ id: x.id, title: x.title, required_resolution: x.required_resolution }));
if (canon(curD) !== canon(wantD)) { console.error('token-value-suite: the registry\'s decisions block differs from the file\'s D01 to D06; run --write'); bad++; }
if (!bad) console.log(`token-value-suite: the VR (${vr.tests.length}) and TV (${tv.tests.length}) suites and the decisions block (${decisions.length}) match ${SRC}`);
process.exit(bad ? 1 : 0);
}
if (args.includes('--write')) {
merge(reg, vr); merge(reg, tv); mergeDecisions(reg, decisions); const n = mapReasons(map, tv);
fs.writeFileSync(REG, JSON.stringify(reg, null, 2) + '\n'); fs.writeFileSync(MAP, JSON.stringify(map, null, 2) + '\n');
console.log(`token-value-suite: VR written (${vr.tests.length} rules), TV written (${tv.tests.length} gates), decisions ${decisions.length} (UNAPPROVED); ${n} NOT RUN reasons in the map`);
process.exit(0);
}
console.error('usage: token-value-suite.mjs --file <rules-and-gates.json> [--write | --check] | --self-test'); process.exit(2);

View file

@ -66,6 +66,52 @@ function slots(now) {
const files = safeList(LOCKS).map(n => ({ name: n, text: read(join(LOCKS, n)), held: /^(build-\d+|run-\d+|build|measure)$/.test(n) ? flockHeld(join(LOCKS, n)) : undefined }));
return parseLockDir(files, { now, aliveFn: alive });
}
// ---- every job by pid, not only the cargo builds (the founder's order, 9 October 2026 09:15 UK: the page reads node, suite and
// fuzz load by pid per box). A process is listed when its command is one of the known binaries (igneumd, igneum-miner, the prover
// pair, the GPU workers, the chip tools), a test binary under a cargo target dir (a suite; "fuzz" in its arguments makes it a fuzz
// run), a script (bash, sh, node, python) whose path lies under /srv, /tmp or /home/build (a harness, a reader, a collector), or
// an archive stream into /srv/archive. Kernel threads, sshd, systemd and the shells of this collector are not jobs. Fields are read
// from /proc: the start time from stat, the resident set from statm, the CPU seconds from utime + stime. Capped at 48 by RSS.
const PROC_SKIP = new Set(['sshd', 'systemd', 'systemd-journal', 'systemd-logind', 'systemd-resolve', 'systemd-timesyn', 'systemd-network', 'systemd-udevd', 'cron', 'dbus-daemon', 'agetty', 'login', 'kthreadd', 'sleep', 'ps', 'awk', 'grep', 'sed', 'cut', 'sort', 'tail', 'head', 'cat', 'flock', 'sccache', 'caddy', 'gitea', 'docker-proxy', 'dockerd', 'containerd', 'containerd-shim', 'unattended-upgr', 'polkitd', 'rsyslogd', 'chronyd', 'multipathd', 'snapd', 'qemu-ga', 'irqbalance', 'packagekitd', 'fwupd', 'collect.mjs']);
function procKind(comm, args) {
const a0 = args[0] || '', line = args.join(' ');
if (comm === 'igneumd') return 'node';
if (comm === 'igneum-miner') return 'miner';
if (/^igneum-prove/.test(comm)) return 'prover';
if (/^igneum-worker/.test(comm)) return 'worker';
if (/^(openroad|kepler-formal|yosys|klayout|magic|ngspice)/.test(comm)) return 'chip';
if (/\/target[^ ]*\/deps\//.test(a0) || /\/target\/[^ ]*\/(release|debug)\//.test(a0)) return /\bfuzz\b/i.test(line) ? 'fuzz' : 'suite';
if (/^(rsync|tar|zstd|pigz|gzip|xz)$/.test(comm) && /\/srv\/archive/.test(line)) return 'archive';
if (/^(headless_shell|chrome|chromium)/.test(comm)) return 'browser';
if (/^(bash|sh|zsh|node|python3?|perl)$/.test(comm)) {
const script = args.find((x, i) => i > 0 && /^\/(srv|tmp|home\/build)\//.test(x) && !/^-/.test(x)) || (/^\/(srv|tmp|home\/build)\//.test(a0) ? a0 : null);
if (!script) return null;
if (/harness|canary|roll|heal|fuzz|suite|gate/.test(script)) return 'harness';
if (/reader|collect|merge|observer|intake|keep-alive|serve/.test(script)) return 'service';
return 'script';
}
if (/^\/(srv|home\/build)\//.test(a0) && !/\/\.cargo\/|\/\.rustup\//.test(a0)) return 'other';
return null;
}
function jobs(bootSec, clk, now, selfPid) {
const out = [], counts = {};
for (const d of safeList('/proc')) {
if (!/^\d+$/.test(d)) continue;
const pid = Number(d); if (pid === selfPid) continue;
const comm = read(`/proc/${d}/comm`).trim(); if (!comm || PROC_SKIP.has(comm)) continue;
const args = read(`/proc/${d}/cmdline`).split('\0').filter(Boolean); if (!args.length) continue; // a kernel thread
const kind = procKind(comm, args); if (!kind) continue;
const stat = read(`/proc/${d}/stat`); const rest = stat.slice(stat.lastIndexOf(')') + 2).split(' ');
const utime = Number(rest[11]) || 0, stime = Number(rest[12]) || 0, startTicks = Number(rest[19]);
const started = Number.isFinite(startTicks) ? (bootSec + startTicks / clk) * 1000 : null;
const rssPages = Number((read(`/proc/${d}/statm`).split(' ')[1]) || 0);
counts[kind] = (counts[kind] || 0) + 1;
if (kind === 'browser') continue; // counted, not listed: a site gate spawns a dozen renderer processes
out.push({ pid, kind, comm, cmd: args.join(' ').slice(0, 160), started_at: started ? iso(started) : null, elapsed_s: started ? Math.max(0, Math.round((now - started) / 1000)) : null, rss_mb: Math.round(rssPages * 4096 / 1048576), cpu_s: Math.round((utime + stime) / clk) });
}
out.sort((a, b) => b.rss_mb - a.rss_mb);
return { list: out.slice(0, 48), counts };
}
function procs(bootSec, clk, now) {
const running = [], waiters = []; let compilers = 0;
const flockParents = new Map(); // ppid -> since, from the flock -w processes the waiters run
@ -127,6 +173,7 @@ export function collect() {
const bootSec = Math.round((now - m.uptime_s * 1000) / 1000);
const s = slots(now);
const p = procs(bootSec, m.clk, now);
const j = jobs(bootSec, m.clk, now, process.pid);
// attach the slot holder to its build (the label names the worktree and crate the cargo runs in)
for (const r of p.running) {
const h = s.held.find(h => h.worktree === r.worktree && (!h.crate || h.crate === r.crate || !r.crate));
@ -157,6 +204,8 @@ export function collect() {
running: p.running,
queue: p.waiters,
compilers: p.compilers,
jobs: j.list, // every job by pid (node, miner, prover, worker, suite, fuzz, harness, service, chip, archive, other)
jobs_by_kind: j.counts, // counts per kind, browsers counted here only
recent: recent.rows,
log: { path: LOG, present: !!logText, bad_lines: recent.bad, total: recent.total, note: logText ? null : 'builds.jsonl is not written yet: the build-server agent adds the append to bs_remote_run; until then this lane fills from nothing' },
},

View file

@ -150,6 +150,19 @@
.sources b { color: var(--good); font-weight: 500; } .sources b.no { color: var(--bad); } .sources b.warn { color: var(--warn); }
a:focus-visible, button:focus-visible { outline: 2px solid var(--ember-2); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .live b, .server.hot::after, .job .pulse, .cores i.hot::after { animation: none !important; } .cores i::after, .g .arc { transition: none; } }
.jobs { margin: 8px 0 2px; font-size: 12px; line-height: 1.5; }
.jobshead { color: var(--ember, #f2541b); margin-bottom: 2px; }
.jobrow { display: flex; gap: 10px; flex-wrap: wrap; border-top: 1px solid rgba(255,255,255,.06); padding: 2px 0; }
.jobrow b { min-width: 56px; }
.muted { opacity: .7; }
.server > .row { display: flex; gap: 14px; align-items: baseline; flex-wrap: wrap; cursor: pointer; padding: 10px 12px; font-size: 13px; }
.server > .row:hover { background: rgba(255,255,255,.03); }
.server > .row .chev { width: 12px; opacity: .8; }
.server > .row .rname { font-weight: 600; }
.server > .row small { opacity: .6; }
.server > .row .rstat { flex: 1 1 auto; opacity: .9; }
.server > .row .rage { opacity: .7; font-size: 12px; }
.server:not(.open) > .panel { display: none; }
</style></head><body>
<div class="wrap">
<header>
@ -218,6 +231,26 @@ function boxState(b) { // live | down | provisioning, with the line the cards
}
const buildHeld = b => (b.slots && b.slots.held || []).filter(h => /^build-\d+$/.test(h.slot)).length;
const boxesOf = d => (Array.isArray(d.boxes) && d.boxes.length ? d.boxes : d.box ? [{ ...d.box, source: d.sources && d.sources.box, headline: d.headline }] : []);
// load by pid (9 October 2026): every job the box's collector lists from /proc (node, miner, prover, worker, suite, fuzz, harness,
// service, chip, archive), not only the cargo builds; counts per kind, then the list with pid, age and resident set
const JOB_ORDER = ['node', 'fuzz', 'suite', 'harness', 'prover', 'miner', 'worker', 'chip', 'archive', 'service', 'script', 'other', 'browser'];
function jobsSummary(b) {
const c = (b && b.jobs_by_kind) || {}; const parts = JOB_ORDER.filter(k => c[k]).map(k => `${c[k]} ${k}${c[k] === 1 ? '' : (k === 'browser' ? 's' : k.endsWith('s') ? '' : 's')}`);
return parts.join(', ');
}
function jobsBlock(b) {
const jobs = (b && b.jobs) || []; const sum = jobsSummary(b);
if (!jobs.length && !sum) return `<div class="jobs"><span class="muted">load by pid: nothing but the collector (no node, suite, fuzz or harness process on this box)</span></div>`;
const rows = jobs.slice(0, 14).map(j => `<div class="jobrow"><b>${esc(j.kind)}</b> <span>pid ${j.pid}</span> <span>${esc(j.comm)}</span> <span>${j.elapsed_s === null ? '' : esc(fmtDurShort(j.elapsed_s)) + ' up'}</span> <span>${j.rss_mb} MB</span> <span class="muted" title="${esc(j.cmd)}">${esc(j.cmd.slice(0, 96))}</span></div>`).join('');
return `<div class="jobs"><div class="jobshead">load by pid: ${esc(sum || 'none')}${jobs.length > 14 ? ` (${jobs.length - 14} more in workers.json)` : ''}</div>${rows}</div>`;
}
function miniMinerLine(m) {
const x = m && m.miner; if (!x) return 'no miner report';
const daa = x.node_daa === null || x.node_daa === undefined ? '?' : Number(x.node_daa).toLocaleString('en-GB');
const rate = x.rate_mhs === null || x.rate_mhs === undefined ? '0 MH/s' : `${Number(x.rate_mhs).toFixed(1)} MH/s`;
const last = x.last_accepted_at ? String(x.last_accepted_at).replace(/^\d{4}-\d{2}-\d{2}T/, '') : 'none';
return `igneum-app ${esc(x.app_version || '?')} · node DAA ${daa} (${esc(x.node_state || (x.synced ? 'synced' : 'unknown'))}) · ${rate} ${esc(x.mining || '')} · last block ${esc(last)} · node pid ${x.node_pid ?? '?'}`;
}
function serverSection(b, i) {
const id = `srv${i}`;
const st = boxState(b);
@ -227,19 +260,32 @@ function serverSection(b, i) {
const m = b.mem || {}, d = b.disk || {}, sc = b.sccache;
const hit = sc && sc.hit_rate_pct !== null && sc.hit_rate_pct !== undefined ? sc.hit_rate_pct : null;
const stale = (b.source && !b.source.ok) || st.stale;
return `<section class="server ${b.running && b.running.length ? 'hot' : ''}" aria-label="${esc(b.name)}">
const key = String(b.name || ''); const open = isOpen(key);
const rowStatus = b.running && b.running.length ? `building: ${esc(kindLabel(b.running[0].kind))}${b.running.length > 1 ? ` +${b.running.length - 1}` : ''}` : (jobsSummary(b) || 'nothing but the collector');
return `<section class="server ${b.running && b.running.length ? 'hot' : ''} ${open ? 'open' : ''}" aria-label="${esc(b.name)}" data-box="${esc(key)}">
<div class="row" role="button" tabindex="0" aria-expanded="${open ? 'true' : 'false'}" title="click to ${open ? 'close' : 'open'} the detail"><span class="chev">${open ? '▾' : '▸'}</span><span class="rname">${esc(b.name)}</span><small>${esc(b.os || 'build server')}</small><span><b>${b.cores}</b> threads</span><span>load <b>${b.load ? Number(b.load[0]).toFixed(1) : '?'}</b></span><span class="rstat">${rowStatus}</span><span class="rage" ${stale ? 'style="color:var(--warn)"' : ''}>${stale ? 'STALE, ' : ''}read ${esc(ago(b.collected_at))}</span></div>
<div class="panel">
<div class="head"><div><div class="name">${esc(b.name)}<small>${esc(b.os || 'build server')}</small></div><div class="facts">${[`<b>${b.cores}</b> threads`, `<b>${esc(fmtUptime(b.uptime_s))}</b> up`, `load <b>${(b.load || []).map(x => Number(x).toFixed(1)).join(' / ')}</b>`, b.kernel ? `kernel <b>${esc(b.kernel)}</b>` : ''].filter(Boolean).map(x => `<span>${x}</span>`).join('')}</div></div>
<div class="facts">${[...temps, b.net ? `net ↓<b>${esc(fmtBps(b.net.rx_bps))}</b> ↑<b>${esc(fmtBps(b.net.tx_bps))}</b>` : '', `<span title="${esc(b.collected_at)}" ${stale ? 'style="color:var(--warn)"' : ''}>${stale ? 'STALE, ' : ''}read ${esc(ago(b.collected_at))}</span>`].filter(Boolean).map(x => `<span>${x}</span>`).join('')}</div></div>
<div class="cores" style="grid-template-columns:repeat(${n > 64 ? 48 : n > 32 ? 32 : n}, 1fr)" aria-label="${n} cores, busy share per core">${Array.from({ length: n }, (_, i) => { const p = per[i] ?? 0; return `<i class="${p >= 50 ? 'hot' : ''}" style="--h:${Math.max(2, p)}%" title="core ${i}: ${p}%"></i>`; }).join('')}</div>
<div class="corelabel"><span>${n} cores, ${b.cpu ? b.cpu.cores_busy : 0} busy</span><span>${b.cpu ? `${b.cpu.busy_pct}% of the box over the last second, ${b.compilers || 0} compilers running` : ''}</span></div>
${jobsBlock(b)}
<div class="gauges">
<div class="g">${arc(b.cpu ? b.cpu.busy_pct : 0)}<div><div class="l">CPU</div><div class="v">${b.cpu ? b.cpu.busy_pct : 0}%</div><div class="s">load 1 min ${b.load ? Number(b.load[0]).toFixed(1) : '?'} of ${b.cores}</div></div></div>
<div class="g">${arc(m.used_pct, tone(m.used_pct))}<div><div class="l">Memory</div><div class="v">${m.used_pct ?? '?'}%</div><div class="s">${esc(fmtBytes((m.used_kb || 0) * 1024))} of ${esc(fmtBytes((m.total_kb || 0) * 1024))}</div></div></div>
<div class="g">${arc(d.used_pct, tone(d.used_pct, 80, 92))}<div><div class="l">Disk ${esc(d.mount || '/srv')}</div><div class="v">${d.used_pct ?? '?'}%</div><div class="s">${esc(fmtBytes(d.used_bytes))} used, ${esc(fmtBytes(d.avail_bytes))} free</div></div></div>
<div class="g">${arc(hit ?? 0, 'cool')}<div><div class="l">sccache hits</div><div class="v">${hit === null ? '–' : hit + '%'}</div><div class="s">${sc ? `${sc.hits ?? 0} hit, ${sc.misses ?? 0} miss, ${esc(sc.cache_size || '?')} of ${esc(sc.max_size || '?')}` : 'sccache not answering'}</div></div></div>
<div class="g">${arc(b.slots ? (buildHeld(b) / Math.max(1, b.slots.count)) * 100 : 0, 'good')}<div><div class="l">Build slots</div><div class="v">${b.slots ? `${buildHeld(b)}/${b.slots.count}` : '–'}</div><div class="s">${b.queue && b.queue.length ? `${b.queue.length} waiting${b.queue.some(q => q.priority === 'gate') ? ', a gate first' : ''}` : 'nobody waiting'}</div></div></div>
</div></section>`;
</div></div></section>`;
}
// the per-box detail panels are collapsed by default (the founder, 9 October 2026 09:5x UK): one compact row per box, the panel
// opens on click, several at once, the open set kept per viewer in localStorage (nothing leaves the browser)
const OPEN_KEY = 'workers.open-boxes';
function openSet() { try { return new Set(JSON.parse(localStorage.getItem(OPEN_KEY) || '[]')); } catch { return new Set(); } }
function isOpen(name) { return openSet().has(name); }
function toggleBox(name) { const o = openSet(); if (o.has(name)) o.delete(name); else o.add(name); try { localStorage.setItem(OPEN_KEY, JSON.stringify([...o])); } catch {} renderServers(); }
document.addEventListener('click', e => { const row = e.target.closest('.server > .row'); if (!row) return; toggleBox(row.parentElement.dataset.box); });
document.addEventListener('keydown', e => { if (e.key !== 'Enter' && e.key !== ' ') return; const row = e.target.closest && e.target.closest('.server > .row'); if (!row) return; e.preventDefault(); toggleBox(row.parentElement.dataset.box); });
// the Mac mini (igneum-mini, the Mac build box, M6): its own collector pushes mini.json to build-1; until then its card reads no report yet
const MINI = { name: 'igneum-mini', label: 'the Mac build box, M6' };
const miniLive = () => { const m = D && D.mini; return m && m.source && m.source.ok && m.cores ? m : null; };
@ -263,7 +309,7 @@ function renderCrew() {
const st = boxState(bx);
if (st.kind === 'down') { cards.push(`<div class="w"><div class="top"><div><div class="card">${esc(bx.name)}</div><div class="meta">build server</div></div>${pill('down', 'error')}</div><div class="doing">${esc(st.note)}</div></div>`); continue; }
if (st.kind === 'provisioning' && !st.live) { cards.push(`<div class="w"><div class="top"><div><div class="card">${esc(bx && bx.name || 'build server')}</div><div class="meta">build server</div></div>${pill('provisioning', 'queued')}</div><div class="doing">${esc(st.note)}</div></div>`); continue; }
if (bx && bx.cores) cards.push(`<div class="w"><div class="top"><div><div class="card">${esc(bx.name)}</div><div class="meta">Hetzner, ${bx.cores} threads, ${esc(fmtBytes((bx.mem && bx.mem.total_kb || 0) * 1024))}, RAID 1 NVMe</div></div>${pill(bx.running && bx.running.length ? 'building' : 'idle', bx.running && bx.running.length ? 'running' : '')}</div><div class="doing">${bx.running && bx.running.length ? bx.running.map(r => esc(`${r.worktree || '?'}: ${kindLabel(r.kind)}`)).join('<br>') : (st.kind === 'provisioning' ? esc(st.note) : 'Slot free. The next build-remote.sh or cross-remote.sh routed here takes it.')}</div>${slotBar(bx.slots, bx.slots ? bx.slots.count : 1)}<div class="foot"><span>${bx.recent ? bx.recent.length : 0} builds logged</span><span>${esc(ago(bx.collected_at))}</span></div></div>`);
if (bx && bx.cores) cards.push(`<div class="w"><div class="top"><div><div class="card">${esc(bx.name)}</div><div class="meta">Hetzner, ${bx.cores} threads, ${esc(fmtBytes((bx.mem && bx.mem.total_kb || 0) * 1024))}, RAID 1 NVMe</div></div>${pill(bx.running && bx.running.length ? 'building' : jobsSummary(bx) ? 'busy' : 'idle', bx.running && bx.running.length ? 'running' : '')}</div><div class="doing">${bx.running && bx.running.length ? bx.running.map(r => esc(`${r.worktree || '?'}: ${kindLabel(r.kind)}`)).join('<br>') : (st.kind === 'provisioning' ? esc(st.note) : 'Slot free. The next build-remote.sh or cross-remote.sh routed here takes it.')}</div>${slotBar(bx.slots, bx.slots ? bx.slots.count : 1)}<div class="foot"><span>${bx.recent ? bx.recent.length : 0} builds logged</span><span>${esc(ago(bx.collected_at))}</span></div></div>`);
else cards.push(`<div class="w"><div class="top"><div><div class="card">${esc(bx && bx.name || 'build server')}</div><div class="meta">build server</div></div>${pill('unreachable', 'error')}</div><div class="doing">${esc(bx && bx.source && bx.source.error || 'no facts from this box')}</div></div>`);
}
if (mac) {
@ -272,10 +318,23 @@ function renderCrew() {
cards.push(`<div class="w"><div class="top"><div><div class="card">${esc(mac.name)}</div><div class="meta">this MacBook, ${mac.build_slots} build slot${mac.build_slots === 1 ? '' : 's'}, 3 run slots</div></div>${pill(meas.length ? 'measuring' : builds.length ? 'building' : runs.length ? 'running' : 'idle', held.length ? 'running' : '')}</div><div class="doing">${doing}</div><div class="slots">${['build-0', 'build-1', 'build-2', 'run-0', 'run-1', 'run-2', 'measure'].map(nm => { const h = held.find(x => x.slot === nm); return `<i class="${h ? (nm === 'measure' ? 'measure' : nm.startsWith('run') ? 'run' : 'held') : ''}" title="${esc(nm)}${h ? ': ' + esc(h.label) : ': free'}"></i>`; }).join('')}</div><div class="foot"><span>${mac.queue.length ? mac.queue.length + ' waiting for a slot' : 'nobody waiting'}</span><span>${esc(ago(mac.collected_at))}</span></div></div>`);
} else cards.push(`<div class="w"><div class="top"><div><div class="card">MacBook-Pro</div><div class="meta">this MacBook</div></div>${pill('no push', 'error')}</div><div class="doing">${MAC_COPY}</div></div>`);
{ const m = miniLive(); const held = m && m.slots && m.slots.held || [];
cards.push(`<div class="w"><div class="top"><div><div class="card">${MINI.name}</div><div class="meta">${MINI.label}</div></div>${pill(!m ? 'no report' : held.length ? 'building' : 'idle', !m ? 'queued' : held.length ? 'running' : '')}</div><div class="doing">${!m ? 'no report yet' : held.length ? held.map(h => esc(`${h.slot}: ${h.worktree || h.label || ''}`)).join('<br>') : 'macOS binaries build here from tonight; slot free.'}</div>${m && m.slots ? slotBar(m.slots, m.slots.count || 1) : ''}<div class="foot"><span>${m ? `${(m.recent || []).length} builds logged` : 'its collector pushes to build-1'}</span><span>${m ? esc(ago(m.collected_at)) : ''}</span></div></div>`); }
cards.push(`<div class="w"><div class="top"><div><div class="card">${MINI.name}</div><div class="meta">${MINI.label}</div></div>${pill(!m ? 'no report' : held.length ? 'building' : 'idle', !m ? 'queued' : held.length ? 'running' : '')}</div><div class="doing">${!m ? 'no report yet' : (esc(miniMinerLine(m)) + '<br>') + (held.length ? held.map(h => esc(`${h.slot}: ${h.worktree || h.label || ''}`)).join('<br>') : 'macOS binaries build here from tonight; slot free.')}</div>${m && m.slots ? slotBar(m.slots, m.slots.count || 1) : ''}<div class="foot"><span>${m ? `${(m.recent || []).length} builds logged` : 'its collector pushes to build-1'}</span><span>${m ? esc(ago(m.collected_at)) : ''}</span></div></div>`); }
const pcsAt = D.pcs && D.pcs.collected_at ? Date.parse(D.pcs.collected_at) : null; const pcsAge = pcsAt ? Math.round((Date.now() - pcsAt) / 1000) : null;
const pcsAgeText = pcsAge === null ? 'report age unknown' : `report ${fmtDurShort(pcsAge)} old${pcsAge > 600 ? ', STALE' : ''}`;
// a PC's five-minute report through the intake (9 October 2026): the known fields on one line, the rest of the report left in workers.json
const pcReportLine = pc => { const r = pc.report; if (!r || !Object.keys(r).length) return ''; const f = [];
if (r.app_version) f.push(`igneum-app ${esc(String(r.app_version))}`);
if (r.node_daa !== undefined && r.node_daa !== null) f.push(`node DAA ${Number(r.node_daa).toLocaleString('en-GB')}${r.node_state ? ' (' + esc(String(r.node_state)) + ')' : r.synced === false ? ' (behind)' : ''}`);
if (r.rate_mhs !== undefined && r.rate_mhs !== null) f.push(`${Number(r.rate_mhs).toFixed(1)} MH/s`);
if (Array.isArray(r.cards)) f.push(r.cards.map(c => `${esc(String(c.name || c.id || 'card'))} ${c.rate_mhs === undefined || c.rate_mhs === null ? '?' : Number(c.rate_mhs).toFixed(1)} MH/s`).join(', '));
if (r.last_accepted_at) f.push(`last block ${esc(String(r.last_accepted_at).replace(/^\d{4}-\d{2}-\d{2}T/, ''))}`);
if (r.agent_pid || r.node_pid) f.push(`pids agent ${r.agent_pid ?? '?'} node ${r.node_pid ?? '?'}`);
return f.join(' · '); };
for (const pc of pcs) {
const r = pc.running;
cards.push(`<div class="w"><div class="top"><div><div class="card">${esc(pc.name)}</div><div class="meta">${esc(pc.machine || pc.id)} · ${esc(pc.role)}</div></div>${pill(r ? 'running' : pc.queue.length ? 'queued' : 'idle', r ? 'running' : pc.queue.length ? 'queued' : '')}</div><div class="doing">${r ? `${esc(r.kind)} ${esc(r.job)}${r.title ? '<br>' + esc(r.title) : ''}${r.stage ? `<br><span class="pill">stage ${esc(r.stage)}</span>` : ''}` : pc.queue.length ? `Next: ${esc(pc.queue[0].kind)} ${esc(pc.queue[0].job)}${pc.queue[0].title ? ', ' + esc(pc.queue[0].title) : ''}` : esc(pc.note || 'idle on jobs; the relay polls every 10 min')}</div><div class="foot"><span>${pc.recent.length} reports kept</span><span>${pc.last_report_at ? 'last report ' + esc(ago(pc.last_report_at)) : 'no report in 7 days'}</span></div></div>`);
const pcAge = pc.report_age_s === undefined || pc.report_age_s === null ? pcsAge : pc.report_age_s;
const pcAgeText = pcAge === null ? 'report age unknown' : `report ${fmtDurShort(pcAge)} old${pcAge > 600 ? ', STALE' : ''}${pc.source === 'intake' ? ', from the PC' : ''}`;
cards.push(`<div class="w"><div class="top"><div><div class="card">${esc(pc.name)}</div><div class="meta">${esc(pc.machine || pc.id)} · ${esc(pc.role)} · <span ${pcAge !== null && pcAge > 600 ? 'style="color:var(--warn)"' : ''}>${esc(pcAgeText)}</span></div></div>${pill(r ? 'running' : pc.queue.length ? 'queued' : 'idle', r ? 'running' : pc.queue.length ? 'queued' : '')}</div><div class="doing">${pcReportLine(pc) ? pcReportLine(pc) + '<br>' : ''}${r ? `${esc(r.kind)} ${esc(r.job)}${r.title ? '<br>' + esc(r.title) : ''}${r.stage ? `<br><span class="pill">stage ${esc(r.stage)}</span>` : ''}` : pc.queue.length ? `Next: ${esc(pc.queue[0].kind)} ${esc(pc.queue[0].job)}${pc.queue[0].title ? ', ' + esc(pc.queue[0].title) : ''}` : esc(pc.note || 'idle on jobs; the relay polls every 10 min')}</div><div class="foot"><span>${pc.recent.length} reports kept</span><span>${pc.last_report_at ? 'last report ' + esc(ago(pc.last_report_at)) : 'no report in 7 days'}</span></div></div>`);
}
if (!pcs.length) cards.push(`<div class="w"><div class="top"><div><div class="card">PC 1 and PC 2</div><div class="meta">relay jobs</div></div>${pill('no data', 'error')}</div><div class="doing">${esc(D.sources && D.sources.pcs && D.sources.pcs.error || 'the Mac pusher reads the relay intake; nothing has arrived')}</div></div>`);
$('crew').innerHTML = cards.join('');