# Igneum Miner 0.3.6: the testnet adoption, staged 5 October 2026 Prepared by the integration agent on the morning of 5 October 2026 after the owner's decision. Nothing was deployed, published or merged to master. The app and document work sits on branch `testnet-adopt` (pushed to origin), worktree `/Users/joshm/Projects/igneum-wt-adopt`; the node work sits on the fork branch `release-0.3.6` (local only; the fork has no remote), worktree `vendor/igneum-node-036`. The cut is one command (section 4) after the inputs push of section 3, which must come first. ## 0. The decision (owner, 5 October 2026) The proposed testnet identity (`docs/testnet/README.md`) and the proposed fee floors and prover-gas table (`docs/analysis/base-fee-floor.md`, spec 05 section 5.11) are ADOPTED as proposed. The three documents now say so; the fork's `FeeParams::CALIBRATED_V1` and `TESTNET_PARAMS` are unchanged from the proposal. One rule the adoption added, so the live devnet is never forked by a node update: the devnet keeps its fee rules until a height switch says otherwise (section 5). ## 1. What is in ### 1a. The app repository (branch `testnet-adopt`, from origin/master 2054ae3 = the 0.3.5 cut) | Merged | Branch head | What it carries | Conflicts and how they were resolved | |---|---|---|---| | `origin/testnet-prep` | beed743 (3 commits over e22068f) | `docs/testnet/README.md`, `docs/analysis/base-fee-floor.md`, spec 05 section 5.11, `docs/plans/history-rewrite.md` (G14, not acted on here), G13 signed build inputs (`app/igneum-app/src/inputs.rs`, `igneum-ota-sign sign-inputs` / `verify-inputs`, `packaging/windows/push-inputs.sh`, `inputs-manifest.sh`, `node-source.pin`, `test-inputs-signing.sh`, `fetch-ci-artifacts.sh --sign-manifest`, `.github/workflows/windows.yml`), `tools/ci/check-workflow-shell.mjs`, the testnet terms on the download section, `site/wallet.html`, the litepaper's app paragraph, the fast-time profile's `fees` object | four generated site files. `site/index.html`: the dev-fee sentence of 0.3.5 kept, testnet-prep's `#testnet-terms` card and wallet link kept (both sides of the download section); the inlined journey block is regenerated. `site/litepaper.html`: 0.3.5's two-paragraph dev-fee text kept (it is the fuller one; testnet-prep's one-sentence version dropped), testnet-prep's MetaMask and Igneum Wallet paragraph kept. `site/journey.json`: 0.3.5's log kept (the feed is the newest 40 entries; testnet-prep's older entries had already aged out of it). `site/sitemap.xml`: both `/miners` and `/wallet`. Then `node site/build.mjs` | On top of the merge, in the same branch: | Change | Where | |---|---| | "proposed" to "adopted 5 October 2026" with the sign-off noted, and the per-network fee rule written in | `docs/testnet/README.md`, `docs/analysis/base-fee-floor.md`, `docs/spec/05-fees-and-economics.md` (section 5.10 and the parameter table) | | `fees_v1_activation_daa: 0` added to the 60x fast-time profile (the fork's `fast_time_60x_file_is_the_devnet_at_60x` test wants every override field present) | `infra/fast-time/override-60x.json` | | this file | `docs/plans/release-0.3.6.md` | ### 1b. The node fork (branch `release-0.3.6` in `vendor/igneum-node-036`, from `release-0.3.5` 20139145) | Fork merge | Head | What it carries | Conflicts | |---|---|---|---| | `testnet-params` (worktree `vendor/igneum-node-testnet`, forked from `finality-fixes` 6aa69a45) | 11e86144 | `consensus/core/src/fees.rs` (`FeeParams`, `PgasTable`, `CALIBRATED_V1`, `PROTOTYPE`), `Params.fees` and the override file's `fees` object, the testnet identity (`igneum-testnet-1`, chain id 4462, ports 268xx, the frozen genesis, `FinalityParams::MAINNET`, every switch at 0), the override file refused on the testnet, the execution layer reading the installed set (`B_p`, `S_p`, the intrinsic, modexp, the floors) | `consensus/core/src/config/params.rs`: both methods kept (`apply_env_pow_schedule` from G12 and `install_fee_params`); the M31 comment on `max_coinbase_payload_len` kept. `kaspad/src/daemon.rs`: the 0.3.5 start-up block kept (environment schedule on devnet and simnet, `install_pow_schedule`, the digest line) and `install_fee_params` with its own print added after it | On top of the merge, the fee height switch (section 5), in the same branch: | Change | Where | |---|---| | `FeeParams::DEVNET` and `SIMNET` = `PROTOTYPE`; `TESTNET` and `MAINNET` = `CALIBRATED_V1`. `FeeSchedule { base, v1_activation_daa }` with `at(daa)`; `install_fee_params(base, switch)`; `fee_params_at(daa)` replaces `fee_params()` | `consensus/core/src/fees.rs` | | `Params.fees_v1_activation_daa` (devnet and simnet `u64::MAX`, testnet and mainnet 0), `OverrideParams.fees_v1_activation_daa`, the digest rule of section 5, four new or changed tests | `consensus/core/src/config/params.rs` | | the switch and a `fees` object printed from the override file; the installed schedule printed with the network | `kaspad/src/daemon.rs` | | `shard_proving_gas_budget_at(daa)` | `consensus/core/src/proving.rs` | | every reader takes a DAA score; `execute_segment` picks the set by the block's DAA score and raises the carried base fees to its floors; the inspector carries the block's pgas table (modexp reads it); `next_base_fee` takes floor and denominator; the pool's `add` and `select` and every RPC quote use the tip's DAA score plus one; the shard plan uses the segment's; one new executor test | `igneum/exec/src/{config,executor,pgas,pool,proving,rpc,service}.rs` | On top of that, two more fork commits: | Commit | What | |---|---| | cf369022 | `DEV_FEE_ADDRESS` = `0x7F45d7d7272e57639BeBb739A60B05bB2CD4C126`, the release dev-fee payout address (testnet and mainnet; the project lead, 5 October 2026, EIP-55 checksum verified, on file at `~/.config/igneum/dev-fee-release.json`); `DEV_FEE_ADDRESS_DEVNET` unchanged; the placeholder test replaced by `the_release_address_pays_the_fee_outside_the_devnet` (`cargo test -p igneum-miner dev_fee`: 5 passed). `docs/design/miner-dev-fee.md` updated on this branch | | 2b6d23ef | the merge of fork `miner-latency` (section 2), the final tip of `release-0.3.6` | ### 1c. What is out | Branch | Why | |---|---| | the app repository's local `miner-latency` (dd47ff3, worktree `igneum-wt-latency`: `tools/miner-latency/run.mjs`, `docs/plans/miner-latency.md`, two bench-log entries; never pushed to origin) | not part of this task; it conflicts with `testnet-adopt` only in `docs/bench-log.md` (both appended entries) and can be merged at any time. The harness was run from that worktree for section 2 | ## 2. The miner-latency decision: IN Fork `miner-latency` (0f88b6d6, `vendor/igneum-node-latency`, one commit over `devnet-v4`: the NewBlockTemplate subscription in the miner, node-side template prewarm, workers switching without draining the batch, `--poll-templates` and `--job-ms`) was merged on a side branch first (`release-0.3.6-latency`, worktree `vendor/igneum-node-036-lat`, c07093ce) and landed on `release-0.3.6` (2b6d23ef) only after both gates passed on the merged tree: | Gate | Command | Result | |---|---|---| | Miner tests | `cargo test -p igneum-miner` on c07093ce | ok: 15 passed, 0 failed | | 3-node fast-time run | `node tools/miner-latency/run.mjs --mode subscribe` (from `igneum-wt-latency`, `IGNEUMD` and `IGNEUM_MINER` = the release build of c07093ce in `vendor/igneum-node/target-036-lat/release`, under the `run` lock, 08:11 to 08:16Z) | 3 nodes up (peers 2/1/1 throughout), 3 CPU miners at 2 threads for 295 s: 82 + 81 + 80 blocks accepted, 0 rejected; chain blocks 228, merged blue 15, merged red 0, tips mean 1.03 max 2, sinks agree; 236 to 241 template switches per miner, notified p50 14 to 16 ms, template p50 20 to 24 ms, switched p50 46 to 52 ms (p90 118 to 130 ms, max 194 to 372 ms); node prewarm 52 to 56 builds a minute, mean 3 to 4.5 ms; 1 to 2 stale submits per miner; no error, panic or WORKER FAULT line in any miner log | | The dev fee through the new feed | `node tools/dev-fee/run.mjs --secs 180` on the same binaries (two nodes, three stub miners, two at the default fee and one at `--dev-fee 0`, then `igneum-miner payouts`; 08:17 to 08:20Z) | pass: miner-a found=327 fee=4, miner-b found=301 fee=4, control miner-c found=309 fee=0 (0 `dev-fee block` lines); `payouts` on both nodes: 938 blocks, the devnet dev address 8 blocks = the miners' counters, 1.27% of the fee-paying miners' blocks against the 1% expectation | The conflicts (13 hunks in `igneum/miner/src/main.rs`, the 0.3.5 dev fee and X21 mismatch guard against the latency rewrite of the template path) were resolved by keeping both sides: the feed takes the dev fee and requests one template in 100 with it, as the 0.3.5 fetcher did; the continuous CPU loop carries the fee through `Work.dev_fee` and counts fee blocks; the found path runs `check_found` into the mismatch guard; `WorkEngine::Real` holds the day-keyed `EpochRef` of M30. The 3-node run above showed `fee=0` on every miner: 243 blocks found at 1 in 100 templates expects about 2.4 fee blocks, and zero has a probability of about 9%, so the dev-fee harness was run as the third gate; it showed the fee riding through the new feed (8 of 8 fee blocks on chain, the control at 0). ## 3. Test results, with the command Every build ran through the main checkout's lock, `/Users/joshm/Projects/igneum/tools/lock/with-lock.sh build`, at `nice -n 19` with `-j 4`. The fork's builds used `CARGO_TARGET_DIR=vendor/igneum-node/target-036`, an APFS clone of `target-release` (48 s to clone). The rustup cargo (1.99.0) on `~/.cargo/bin`. ### 3a. The app repository (worktree `igneum-wt-adopt`) | Check | Command | Result | |---|---|---| | Site | `node site/build.mjs` | built: bench, journey (40 entries), index, litepaper, live, evidence, wallet, 404, miners (6 rows) | | Links | `node tools/ci/link-check.mjs` | 324 internal links across 8 pages, 0 broken | | Workflow shell | `node tools/ci/check-workflow-shell.mjs` | self-test fires on the fixture; 11 run blocks in 2 workflows, 25 `.ps1` files, 0 findings | | Signed inputs | `packaging/windows/test-inputs-signing.sh` with `igneum-ota-sign` built from this tree (`cargo build --release -j 4 --bin igneum-ota-sign`, 8 s with the cloned target) | 16 passed, 0 failed: the four positive cases, the ten refusals (changed zip byte, changed manifest byte, changed unpacked file, unlisted file, missing file, wrong pin, short pin, another key, the embedded key against the throwaway signature, the two sign-time refusals), and the Mac's real OTA key verified by the key compiled into the app | | Shell parses | `bash -n` on `push-inputs.sh`, `fetch-ci-artifacts.sh`, `inputs-manifest.sh`, `test-inputs-signing.sh` | ok | | JavaScript parses | `node --check` on `tools/ci/check-workflow-shell.mjs`, `tools/ci/link-check.mjs`, `site/build.mjs` | ok | | PowerShell parse rule (no `$var:` inside double quotes) | the 0.3.5 scanner over every tracked `.ps1` and the PowerShell strings in `ota.rs`, `jobrun.rs`, `jobbuild.rs` | the 3 baseline hits of 0.3.5 only (two comments in `check-ps51.ps1` that quote the rule; `ota.rs` line 1190, the Mac helper's bash); testnet-prep changed no `.ps1` | ### 3b. The node fork (worktree `vendor/igneum-node-036`) | Fork tip | Command | Result | |---|---|---| | a11455e7 (testnet-params merged, the fee switch) | `cargo test -p kaspa-consensus-core` with `IGNEUM_FAST_TIME_FILE=/infra/fast-time/override-60x.json` (the fast-time test wants every override field; the main checkout's file predates `fees`) | ok: 101 passed, 0 failed, 2 ignored (lib) + 7 (db_compat); among them `igneum_testnet_identity`, `override_params_carry_the_fees`, `override_params_carry_the_fees_v1_activation`, `consensus_digest_keeps_the_0_3_5_value_until_the_fee_switch_is_set` (pins 9409dedac4bf... for the devnet params), `consensus_digest_covers_every_consensus_field_and_nothing_else` (the fee switch in its list), `test_genesis_hashes`, `fast_time_60x_file_is_the_devnet_at_60x`; 07:41Z | | a11455e7 | `cargo test -p igneum-exec` | ok: 11 passed, 0 failed; among them `the_fee_switch_meters_by_the_block_daa_score` (a block below the switch meters 200 intrinsic pgas and keeps its base fees; the block at the switch meters 300 and its base fees jump to the v1 floors) and the bomb tests re-sized to fit both tables; 07:43Z | | a11455e7 | `cargo test -p kaspa-consensus --features igneum-pow -- finality` | ok: 8 passed, 0 failed (92 filtered), 125.9 s | | a11455e7 | `cargo test -p kaspa-consensus --features igneum-pow -- difficulty` | ok: 15 passed, 0 failed | | a11455e7 | `cargo test -p igneum-miner` | ok: 12 passed, 0 failed (the guard and dev-fee tests) | | c07093ce (miner-latency merged on the side branch `release-0.3.6-latency`, worktree `vendor/igneum-node-036-lat`, `CARGO_TARGET_DIR=target-036-lat`) | `cargo test -p igneum-miner` | ok: 15 passed, 0 failed (the 12 above and `job_size_follows_the_target_ms`, `cpu_found_nonce_is_matched_to_its_own_work`, `worker_found_hash_is_matched_to_its_own_template`) | | c07093ce | 3-node fast-time run (section 2) | pass (section 2) | | cf369022 (the release dev-fee address) | `cargo test -p igneum-miner dev_fee` | ok: 5 passed | | 2b6d23ef (the final tip: cf369022 plus the latency merge) | `cargo test -p igneum-miner` | ok: 15 passed, 0 failed | Not built here: the release binaries for the three platforms (section 4b). ## 4. The morning cut, in order ### 4a. The signed inputs come first (G13) The workflow and `push-inputs.sh` changed as a pair. From this tree on, `.github/workflows/windows.yml` fetches `payload-inputs.json.sig` from the downloads host and refuses to build until `igneum-ota-sign verify-inputs embedded` accepts the manifest's signature, the zip's sha256, every unpacked file and the node commit pinned in `packaging/windows/node-source.pin` in the commit it builds. The downloads host holds no signature today (0.3.5 pushed an unsigned `inputs.json` and a bare `.sha256`), and the pin in this tree still reads 6aa69a45 (the 0.3.4 node, written by the old script). So the FIRST push of the new workflow would fail at "payload inputs" unless the inputs are pushed from this tree before the workflow runs: ``` cd /Users/joshm/Projects/igneum-wt-adopt gh auth switch --user igneum-labs packaging/windows/test-inputs-signing.sh # 16 of 16, with the real key at the end IGNEUM_NODE_SRC=vendor/igneum-node-036 IGNEUM_WIN_RELEASE= \ packaging/windows/push-inputs.sh --deploy # signs payload-inputs.json, writes node-source.pin git add packaging/windows/node-source.pin && git commit -m "inputs: pin node for 0.3.6" ``` What the workflow needs, and where it comes from: | Need | Source | |---|---| | `DL_TOKEN` repository secret | already set for 0.3.5 (`gh secret set DL_TOKEN < ~/.config/igneum/dl-token`); the workflow fetches the zip, the manifest and the signature with it | | the embedded public key | compiled into the app (`app/igneum-app/src/manifest.rs`); `igneum-ota-sign embedded` prints it; `push-inputs.sh` refuses to sign if `~/.config/igneum/ota-signing-key.pub` is not that key | | `~/.config/igneum/ota-signing-key` (0600) and `.pub` | the OTA key the apps already trust; signs the manifest on the Mac | | `packaging/windows/node-source.pin` | written by `push-inputs.sh`, committed with the push; the runner compares it with the manifest's `node_source_commit` | | the Windows node exes | `IGNEUM_WIN_RELEASE` names the folder with `igneumd.exe` and `igneum-miner.exe` built from the fork commit the pin names (the 0.3.6 cross-build; `proto-cuda/windows-node/cross-build.sh` as in 0.3.5) | Then the update manifest is signed only on request: `packaging/windows/fetch-ci-artifacts.sh --sign-manifest --deploy` after the run is green, which re-verifies the run's inputs artifact against the key and the pin at the run's commit before `publish-manifest.sh` runs. ### 4b. The merge and the ship From the MAIN checkout, on master, after 4a's pin commit is on `testnet-adopt`: ``` cd /Users/joshm/Projects/igneum gh auth switch --user igneum-labs git fetch origin && git checkout master && git pull --ff-only git merge --ff-only origin/testnet-adopt # testnet-adopt contains master 2054ae3 node tools/ship-app.mjs --check # 0.3.5 in all 6 files node tools/ship-app.mjs 0.3.6 \ --node vendor/igneum-node-036 \ --win-release \ --mac-release \ --notes "Testnet identity and the adopted fee table in the node (devnet unchanged until the fee switch), signed build inputs" ``` The 0.3.6 binaries are NOT built yet (this plan stops at the suites; section 3b says what was and was not built). Build them from fork 2b6d23ef as 0.3.5's section 4a did: Mac `cargo build --release -j 4 -p kaspad -p igneum-miner --features kaspad/igneum-pow` with `CARGO_TARGET_DIR=vendor/igneum-node/target-036` (the release build in `target-036-lat` is of c07093ce, one commit short: no release dev-fee address); Windows through `proto-cuda/windows-node/cross-build.sh`; Linux through `infra/cross/build-linux.sh`. The push to master triggers `windows.yml`, which now verifies the inputs of 4a before it builds the app. ## 5. The devnet rollout of the fee floor: a height switch The live devnet runs the prototype fee set (`B_p` 30,000,000, 1 gwei floors, intrinsic 200). A 0.3.6 node on the devnet runs exactly that until told otherwise, so 0.3.5 and 0.3.6 nodes build and accept the same segments through the whole rolling update. The adopted set reaches the devnet by `fees_v1_activation_daa`, the same pattern as `difficulty_v2_activation_daa` (33,000, 4 October 2026): | Step | What | Check | |---|---|---| | 1 | Ship 0.3.6 (section 4). Every node updates with the switch at its default, never. The consensus digest does not move (the fee fields enter the digest only once the switch or the set leaves the 0.3.5 state), so 0.3.5 and 0.3.6 peers keep handshaking | `Consensus params digest: 9409dedac4bf...` on a node with no override file, the same line 0.3.5 printed; the live devnet nodes (override file with the difficulty and finality switches) print their own unchanged value | | 2 | Wait until every devnet node is on 0.3.6: the console's Machines card and the user agents in the peer list (`igneumd/2.1.0-<0.3.6 fork commit>`) | no `-20139145` user agent left | | 3 | Pick the switch height: a DAA score at least 24 hours ahead (86,400 blocks), on a round number, announced in the engineering log and the app's update note | | | 4 | Publish the switch with the other switches in the update manifest's `consensus.override` (`packaging/ota/publish-manifest.sh --override '{"difficulty_v2_activation_daa": 33000, ..., "fees_v1_activation_daa": N}' --deploy`), and add the line to every hand-run node's override file. The digest moves the moment a node restarts with it; a node that has not restarted is refused by those that have, which is why the restart must sweep every node before N | `Calibrated v1 fees from the override file: ... from DAA score N` in every node's start-up log; one digest across the peer list | | 5 | At N: the first chain block at or above N meters with v1 (intrinsic 300, `B_p` 120,000, `S_p` 30,000, modexp 10 + 1 per 10 bytes) and its base fees jump to 100 gwei per gas and 10,000 gwei per pgas. Nothing resets; history stays | `igneum_getBudgets` returns `provingGasLimit` 120,000 and the two base fees at the floors; `eth_getBlockByNumber` of the switch block shows `provingBaseFeePerGas` 0x9184e72a000 | | 6 | The prover's mirror of the table (`proving/igneum-prove/core/src/config.rs`, `pgas.rs`: prototype values at b7fca5a0) must carry v1 before N, or every shard statement after N differs from the node's plan. This is the one change this plan does not make; it re-cuts every fixture at `S_p` 30,000 and changes the guest program id | the fixtures and the guest id in `docs/analysis/base-fee-floor.md` section 4 | Why a switch and not a fresh chain: the devnet has 12 cloud nodes, three PCs and outside machines on it, with the difficulty v2 rollout as the precedent that a height switch over a live chain works. Why not the override file's `fees` object: that changes the rules of every block including the past, so a node restarted with it could not replay its own history. ## 6. Open after this plan | Item | State | |---|---| | `--netsuffix` default 1 under `--testnet` | one line in `kaspad/src/args.rs`, not done; `igneumd --testnet --netsuffix 1` until then | | the testnet genesis message | reads `proposed, not final` (the proposal's words, now part of the hashed genesis 52a3e6a9...). Adopted as computed. If the owner wants the words changed, it is one `print_genesis_hashes` run, new hash and merkle root in `genesis.rs`, `test_genesis_hashes` and the README, and it must happen before the first public node, never after | | the prover's table mirror | section 5 step 6 | | seed nodes, public RPC, the explorer, the app's testnet build | `docs/testnet/README.md` section 5, unchanged | | G14 history rewrite | `docs/plans/history-rewrite.md`, merged as a plan, not acted on | | the inputs push (4a) and the 0.3.6 binaries | not done here | ## 7. What the proving activation taught us (written on master, 5 October 2026, 08:45 BST) Written 5 October 2026, 08:45 BST, while proving v0 went live on the devnet at DAA 84,100. ### Must ship in 0.3.6 | Item | Why | Where | |---|---|---| | The app sets `IGNEUM_PROOF_VERIFIER` for its node | On 5 October no node on the network ran a verifier (`verifier: Off` on every app node and the seed). Spec 7.7 item 4: a producer without a verifier never includes a record, so proofs were stored and never paid. The Mac app was relaunched by hand with the variable; Windows apps cannot be. | `app/igneum-app/src/engine.rs` node spawn: Mac and Linux point at the shipped `igneum-prove-host`; Windows points at a small wrapper exe that runs the WSL host (`wsl -d Ubuntu-24.04 -- /opt/igneum/igneum-prove-host --mode verify ...` with `wslpath` for the proof file). Until the wrapper exists, devnet Windows nodes run `IGNEUM_PROOF_VERIFY=trust` (devnet only, never testnet). | | Windows payload ships `wsl2/bin/igneum-prove-host` and `igneum-prove-export` | The app's probe looks there first; today a PC needs the 20-minute WSL setup or a hand-installed `/opt/igneum`. The Linux binaries come from `infra/cross/build-linux.sh` (CUDA feature needs the CUDA toolchain in the cross image, else ship the CPU build and let setup-wsl.sh build the CUDA one). | `packaging/windows/make-payload.sh`, `Igneum-Miner.iss` | | The proving tile shows the verifier state | A node that relays but never includes should say so on the tile and on `/live`. | `app/igneum-app/src/state.rs`, `site/api/live.mjs` (`verifier` is already in the observer report) | | Rotation phase 2 | Branch `rotation-2` (5317305): `--dl-both`, `tools/logs.mjs --rotation`, fresh-repo script. Plan: `docs/plans/rotation-phase-2.md`. | | | Testnet parameters behind `fees_v1_activation_daa` | Branch `testnet-prep` and the fork's `testnet-params` (agent in progress). | | ### Done (5 October 2026, branch `proving-app`, app side only; the node is unchanged) | Item | Done | Commit | |---|---|---| | The app sets `IGNEUM_PROOF_VERIFIER` for its node | `app/igneum-app/src/verifier.rs` decides once per node start and `engine.rs` passes it to the igneumd spawn. macOS and Linux: `igneum-prove-host` next to the engine's binaries (the DMG's Contents/Resources/bin). Windows: the new `igneum-prove-verify.exe` (`src/bin/prove-verify.rs`, a bin target of the app crate, shipped by `make-payload.sh` next to the engine) is set only when its `--probe` finds a host inside WSL2; it rewrites `--proof` with `wslpath -a`, runs the host in the order of `src/wslhost.rs` and returns its exit code, 2 when there is no host. Trust mode is never the default: the setting `proof_verify_trust` (Settings, "devnet only") sets `IGNEUM_PROOF_VERIFY=trust` only when no verifier was found, and changing it restarts the node. After the WSL2 setup runs on a PC, the prover thread asks for one node restart so the verifier is picked up. | 063a9e7 | | The prover's WSL2 probe checks both layouts | Order: the payload's `wsl2/bin`, `~/igneum-prove/proving/igneum-prove/target/release/igneum-prove-host` (what setup-wsl.sh builds), `~/igneum-prove/target/release/igneum-prove-host`, `/opt/igneum/igneum-prove-host`; one list in `src/wslhost.rs`, shared with the wrapper. The tile's message names every path it looked at, and says when WSL2 did not answer. | 063a9e7 | | The proving tile shows the verifier state | The prover thread reads `igneum_getProvingStatus().verifier` every 30 s whether proving is on or off; `/api/state` carries `proving.verifier` (the node's words), `verifier_mode` (off, trust, command, unknown), `verifier_set` (what the app passed), `verifier_reason`, `verifier_note` and the pool counts. The tile has a `verifier` row and a note: "This node relays proofs but does not verify them, so it never includes a proof record in its blocks: ", "Devnet only: this node trusts proof records without verifying them", or "This node verifies proof records with igneum-prove-host". `site/api/live.mjs` already carried `verifier`; untouched. | 063a9e7 | The three items are one commit because they share `src/prover.rs` and `src/state.rs`. Not done here: the Windows payload's `wsl2/bin` host binaries (item 2 of the table above, needs the Linux cross-build), rotation phase 2, testnet parameters. The version in `app/igneum-app/Cargo.toml` is still 0.3.5; the ship script bumps it. ### Operational lessons from the activation (5 October 2026) - Consensus override changes must land on every node at once: a hand node restarted early with a different `proving_v0_activation_daa` was refused by every peer (digest handshake) and sat isolated at a lower height for 20 minutes. Order that works: publish the manifest override, `update-now` to every app, wait for every app node to log the new parameters, then restart the hand nodes and the seed with the same file. - `scratchpad/restart-hand-nodes.sh` died silently after `igneumd --version` (the 0.3.5 binary exits 1 after printing) under `set -e`; the restart it reported never happened. Every restart script ends by printing the new pids and their start times. - Switching proving on needs no app restart: `POST /api/prove {"on":true}` (the job `prove-on-pc2-84100` does this after installing the CUDA host into `/opt/igneum` for the app's WSL user). ### Instant jobs (5 October 2026) the project lead: "why is it taking so long for pc2 and pc1s tasks to spin up? can we speed it up?". Before 0.3.6 every app polled `igneum-jobs.json` every 10 minutes (`CHECK_EVERY_S = 600`), so a job published from the Mac waited up to 10 minutes on each PC. The PCs have no inbound ports and one miner is off the LAN, so the fix is a wake signal the app pulls over an outbound connection. Branch `job-wake`. | Piece | What it does | Where | |---|---|---| | Wake endpoint | `GET /wake?since=`: public (the apps hold no token), 30 a minute per IP, held up to 45 s, answers `{stamp, at, added, changed, held_ms}` the moment the stored stamp differs from `since`, else the unchanged stamp at the deadline. `POST /r//wake {stamp, added}` (the relay's auth) records a stamp. Rows in Neon `relay_wake`, created by the first POST. `maxDuration` 60 s. | `relay/api/wake.mjs`, `relay/lib/wake.mjs`, `relay/vercel.json`, test `relay/test/wake.test.mjs` (fake database and clock; in CI's site job) | | App waker | One thread per app long-polls the endpoint with the last stamp; a changed stamp sends `Event::Wake` and the engine fetches the jobs file at once (signature check unchanged). First reply seeds the stamp. Backoff 5, 15, 60 s while the relay is unreachable, 10 s floor between requests, a full hold when the relay has no stamp yet. One log line per wake, one when it falls back, one when it recovers. `IGNEUM_APP_JOBS_WAKE_URL` overrides the URL (set and empty: no waker). | `app/igneum-app/src/jobrun.rs` (`WakeState`, `wake_loop`), unit tests for the stamp and backoff logic | | Fallback poll | 2 minutes (was 10), the first poll still 40 to 60 s after start. An unchanged file is no longer logged on every poll. | `jobrun.rs` `CHECK_EVERY_S` | | Publisher | After the live file verifies, `--deploy` POSTs the stamp (`published_at` plus 8 hex of the file's sha256) and the added id; the token goes in a header file, never on the command line; prints "woke the apps" or a one-line warning. | `packaging/ota/publish-jobs.sh` `wake_apps` | | Status | `node tools/jobs.mjs status` shows "woken +N s after the publish" per machine for a job a publish added. | `tools/jobs.mjs` | Expected latency, publish to job start: the relay re-reads the stamp every 2 s inside the hold, the app's curl returns at once, the engine fetches the file (two small downloads) and starts the job on its next tick. About 3 to 6 s when the app is mid-hold, plus up to 10 s if the app was inside its floor after an earlier reply; the 2-minute poll is the ceiling when the relay is down. Numbers below are measured, not estimated. | Machine | Publish to started_at | Date | Source | |---|---|---|---| | PC 1 (ae432dc7) | TODO (measure with `node tools/jobs.mjs status` after the first 0.3.6 job) | | | | PC 2 (1ccfe586) | TODO | | | | Mac | TODO | | | Not yet done on this branch: the relay deploy (`relay/`, by the owner; the `/wake` route and the 60 s `maxDuration` go live with it, the `relay_wake` table appears on the first POST), the first live publish, and the Windows curl path of the long-poll (curl.exe 8.x in System32; the 58 s `--max-time` was reviewed, not run). ## 8. The cut, 5 October 2026 (release engineer, from 09:30 BST) the project lead at 09:30 BST: "can we push 0.3.6 through?". Worktree `/Users/joshm/Projects/igneum-wt-ship036`, branch `release-0.3.6` from master 31c1b34. Every build through `/Users/joshm/Projects/igneum/tools/lock/with-lock.sh build` at `nice -n 19` with `-j 4`, the rustup cargo 1.99.0 on `~/.cargo/bin`. Times are UTC unless marked BST. ### 8a. The merges, in order | Merge | Head | Commit | Conflicts and how they were resolved | |---|---|---|---| | `origin/testnet-adopt` | 09baf8e | c9bc6d4 | `docs/spec/05-fees-and-economics.md`: master's 5.10 (security budget, E15) kept as 5.10, the adopted fee table from testnet-adopt became 5.11; its cross references in `docs/analysis/base-fee-floor.md` and this file moved to 5.11. `docs/plans/release-0.3.6.md` (add/add): testnet-adopt's plan is the body, master's "what the proving activation taught us" follows as section 7. `site/index.html`, `site/journey.json`: generated, rebuilt with `node site/build.mjs` (bench, journey 40 entries, index, litepaper, live, evidence, wallet, 404, miners 6 rows) | | `app-ui` | ce31cbb | 356b3e6 | `.github/workflows/ci.yml`: both test steps kept (the wake test from master, the notice-strip test from app-ui); `node --test app/igneum-app/ui/notices.test.mjs`: 6 pass | | `proving-app` | 010a372 | ada90aa | `app/igneum-app/src/prover.rs`: proving-app's probe taken (the same hidden `wsl` call as master's 31c1b34, through the shared `wslhost::lookup_script`). This file: proving-app's Done table kept under section 7 | | `rotation-2` | 5317305 | 50920da | `.github/workflows/windows.yml`: both header comments; rotation-2's "packaged configuration" step runs first, then master's G13 "payload inputs" step (signature, hashes, node commit); the duplicate `dl-token` write in the inputs step dropped (the configuration step writes it). `node tools/ci/check-workflow-shell.mjs`: 12 run blocks, 25 `.ps1`, 0 findings | `engine.rs`, `state.rs` and `ui/app.js` auto-merged. job-wake was already in master. ### 8b. Changes on top of the merges | Commit | What | |---|---| | 9541543 | Every process the engine starts on Windows is hidden. The four helpers (`detect::run_timeout`, `jobrun::run_capture`, `jobrun::run_streamed`, `procs::spawn`) already set `CREATE_NO_WINDOW`, which is why only 15 of the 89 `Command::new` sites were visibly wrapped; the direct `.output()`/`.spawn()` sites were not: the window host launch (`main.rs`), `icacls` and the three `reg` calls (`platform.rs`), the setup's `cmd /c start` (`prover.rs`), the Mac-only `xattr`/`hdiutil` calls (`ota.rs`, wrapped for a clean audit; `quiet` is a no-op off Windows). `igneum-prove-verify.exe` is now a windows-subsystem binary: the fork's `igneum/exec/src/proving.rs:498` spawns it with a bare `Command::new` from a node that has no console, so a console-subsystem wrapper opened a window on every verification. Sites left as they are: inside `#[cfg(target_os = "macos")]` or `#[cfg(target_os = "linux")]` blocks (scutil, caffeinate, osascript, open, pkexec, xdg-open) and the test-only bash in `manifest.rs` | | 3811d8e | Every WSL script runs from a file (coordinator, 09:4x BST; PC 2 on 0.3.5 reported "proving needs the WSL2 setup" with `/opt/igneum/igneum-prove-host` present: the inline `bash -lc "