igneum/docs/plans/build-job.md
igneum-labs 7eed16a29a Pre-public scrub, the text pass (7 October 2026, 19:5x UK): no founder name, personal login, earlier business or personal address in any tracked text file, and a gate check that keeps it so
The sweep (main's item 1): 199 tracked text files, 783 lines. The founder's full name, first name and possessive become "the founder" (sentence starts capitalised); the lowercase operating-system user name in WSL paths and commands becomes <user>; the second owner login becomes "the second owner login"; the three earlier businesses and the two other brands become "the other business", "the earlier entity", "the earlier business" and "another brand"; the Chrome profile rule names the igneum.network profile, not the profile's label. The standing commit login igneum-labs is not a founder term here: the fresh-repository step renames it in the history (docs/plans/history-rewrite.md, tools/repo/fresh-repo.sh).

The patterns never appear in plain text in the tree (a plaintext list would be the hit): tools/ci/founder-strings.b64 (perl regex, tab, a sample per row) is read by tools/ci/founder-strings-check.sh (every tracked text file, perl, known-failed first: the self-test plants each row's sample in a fixture and the hit must name the file), by tools/community/discord-hooks.mjs (the guard's founder and business rows; the test takes its fixtures from the samples) and by tools/repo/fresh-repo.sh (the business names of the rewrite rules). site/forbidden-strings.txt carries the same patterns as b64: lines, decoded case-insensitive by site/scrub.mjs and tools/ci/launch-gates-check.mjs (whose fixture now plants an encoded made-up name). The check runs in the gate's tree checks on every merge.

Not in this commit, by main's word: the 105 commit messages and 40 personal-identity commits that need the history rewrite (listed, not run), and the secrets found by gitleaks over the history (reported with owners).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-07 18:39:50 +00:00

149 lines
14 KiB
Markdown

# The `build` job: a PC builds the node and the app, nobody at the keyboard
4 October 2026, evening. The founder's ask ("efficiency"): every Windows node and app build went through a GitHub runner at
15 to 25 minutes a round, and every Linux binary was cross-compiled on this Mac under the build lock. The two
RTX 5090 PCs (PC 1 `ae432dc7`, PC 2 `1ccfe586`) run Igneum Miner 0.3.3 with the signed job channel and each has a
WSL2 Ubuntu 24.04 owned by the app (root, cargo and the SP1 toolchain under /root from the shard jobs). So the build
goes to them: a `build` job, mining untouched.
## What is built (branch `build-job`)
| Piece | Where | State |
|---|---|---|
| Job kind `build`: params, validation, the 40-minute default budget, `parse_lenient` (an unknown kind is skipped by the app instead of rejecting the file; the signer stays strict) | `app/igneum-app/src/jobs.rs` | unit tests on the Mac |
| The plan: parameters, the inputs manifest (what to build, what to test; shell-unsafe names refused), the bash stage scripts (setup, extract, linux, windows, test, pack), per-stage caps, parsers for free space, the outputs file and the relay replies | `app/igneum-app/src/jobbuild.rs` (new) | unit tests on the Mac |
| The runner: free-space check on the Windows drive and inside the distro, fetch with sha256, the manifest out of the zip, one `wsl.exe` call per stage under its cap (kill of the Linux side on a cap), zstd outputs uploaded to the relay (fn=upload, Blob PUT, fn=drop), RESULT and STAGE lines with UTC times, closing SUMMARY with `outputs[]` | `app/igneum-app/src/jobrun.rs` (`run_build`, `build_stage`, `relay_upload`) | compiles for macOS and `x86_64-pc-windows-gnu`; not run on a PC |
| Packer: the fork worktree, `app/igneum-app`, `brand/icons`, `proto-cuda` (no redist) into `build-inputs.zip` with `.sha256` and `.json` (branch, commit, dirty, date, builds, tests) on the downloads host | `packaging/windows/push-build-inputs.sh` (new) | run against a scratch folder: 7.9 MB, 1937 files, no target dirs |
| Publisher: `publish-jobs.sh add --kind build` (platform windows, requires `wsl`, `--budget-minutes`, `--stage-minutes`, `--targets`, `--no-tests`, `--min-free-gb`, `--relay-url`, `--nice`, `--cargo-jobs`) | `packaging/ota/publish-jobs.sh` | see "Tested" |
| Mac tool: pack + publish + watch + fetch; sha256 of the zst and of the unpacked file against the PC's RESULT lines; PE header check of every exe (MZ, PE, x86-64, PE32+, .text, over 1 MB); `verify-exe.py --version` on igneum-app.exe; placement where `push-inputs.sh`, `make-payload.sh` and the cloud-devnet scripts look | `tools/build-job.mjs` (new) | fetch path run against the live relay: a real exe round-tripped, a wrong sha256 refused |
| `relay.mjs drop <file> --body` carries the body (the fetch fallback reads the sha256 from it) | `tools/relay.mjs` | run live |
| Dashboard label, README row | `app/igneum-app/ui/app.js`, `packaging/ota/README.md` | |
### What the job does on the PC
| Stage | What | Cap |
|---|---|---|
| check | free GB on the drive that holds the app data (PowerShell `DriveInfo`) and under `/root/igneum-build` inside the distro (`df`); both must be at or over `min_free_gb` (20) | 2 min |
| fetch | `build-inputs.zip` by https, sha256 and size as signed in the job; `manifest.json` read out of it with `tar` | curl, 60 min |
| setup | `apt-get install` of what `dpkg -s` says is missing (build-essential, clang and libclang for bindgen, protobuf-compiler, zstd, unzip, `gcc-mingw-w64-x86-64`, `g++-mingw-w64-x86-64`, `binutils-mingw-w64-x86-64`, `mingw-w64-x86-64-dev`), rustup when cargo is missing, `rustup target add x86_64-pc-windows-gnu` | stage cap |
| extract | `/root/igneum-build/src` replaced by the zip; `/root/igneum-build/target` (CARGO_TARGET_DIR) persists, so the ~500 dependency crates compile once | stage cap |
| linux | per manifest unit: `nice -n 19 cargo build --release -p kaspad -p igneum-miner --features kaspad/igneum-pow`, then the app crate (optional on Linux: a failure is a RESULT line, not a job failure) | stage cap |
| windows | the same with `--target x86_64-pc-windows-gnu`, `CC/CXX = x86_64-w64-mingw32-gcc-posix/g++-posix`, `LIBCLANG_PATH` and `BINDGEN_EXTRA_CLANG_ARGS` for librocksdb-sys, `-C link-arg=-static -C link-arg=-static-libgcc`, `IGNEUM_WINDRES` for the coin icon | stage cap |
| test | `cargo test --release` per manifest test unit (default `igneum-app`, `igneum-miner`); a failure marks the job failed but the binaries still ship | stage cap |
| pack | `zstd -T0 -12` per binary (`igneumd.linux.zst`, `igneumd.exe.zst`, ...), sha256 of both forms, `build-outputs.json`, copied to the job folder on the Windows side | stage cap |
| upload | each `.zst` under 50 MB to the relay: `fn=upload` with the intake key (allowed for upload and drop; round 4 X23 keeps the key away from run and task posts), PUT to Vercel Blob, `fn=drop` to `mac` titled `build-job <id> <file>` with the sha256 lines in the body | stage cap |
Every stage prints `STAGE <name> start <utc> cap N min` and `STAGE <name> end|timeout <utc> N s exit M`; every
binary a `RESULT <target> <name> <bytes> bytes sha256 <hex>` line and later `RESULT output ...` with the zst sha256
and `RESULT upload ... relay item <id>`. The console's Jobs tab shows these as they arrive; the job is final only on
the app's closing SUMMARY, whose `outputs[]` carries the relay item ids and both sha256 per file.
Guard rails, as asked:
- The whole job runs under `budget_minutes` (default 40; `MAX_RUN_TIMEOUT_MIN` 600). Each stage takes
`stage_minutes.<stage>` when given, never more than what is left of the budget. A cap ends `wsl.exe` and then
`cargo`, `rustc`, `cc1plus` and `zstd` inside the distro.
- 20 GB free on both sides before anything is fetched.
- The app's runner is serial: `Jobs.tick` starts a queued job only when `self.active.is_none()`
(`src/jobrun.rs`), so a build never overlaps a shard benchmark; the second job waits in the queue.
- Nothing stops the miners: `needs_miners_stopped` is true only for `shard-benchmark` and a `run` with
`stop_miners_first`. The report says so twice ("the miners keep mining", "the miners were never stopped").
## The order of events (the main session does this)
The app on both PCs is 0.3.3, whose parser rejects the WHOLE jobs file when any job has a kind it does not know
("one bad job rejects the file"). So the `build` kind must reach the PCs before the first build job is published:
1. Merge `build-job`, bump the app to 0.3.4 (`app/igneum-app/Cargo.toml`, `resources/igneum-app.rc` x2, `app/windows/version.h`, as the 0.3.3 commit did) and cut it. This last round still goes through the GitHub runner (`fetch-ci-artifacts.sh`, `publish-manifest.sh`). From 0.3.4 on, an unknown kind is skipped, so the next new kind needs no such dance.
2. Rebuild the signer in the checkout that publishes: `cd app/igneum-app && cargo build --release --bin igneum-ota-sign` (the old binary refuses `kind 'build' is unknown`).
3. Confirm 0.3.4 on PC 1 in the console (Machines tab, app version) or `node tools/jobs.mjs status` after an `update-now` job.
4. Publish the first job (below) and watch.
## The first job: PC 1, which is idle on jobs
packaging/windows/push-build-inputs.sh --node vendor/igneum-node-v4
packaging/ota/publish-jobs.sh add --kind build --target ae432dc7 \
--title "First PC build: node devnet-v4 and app, Linux and Windows" \
--budget-minutes 150 --stage-minutes '{"setup":20,"linux":50,"windows":50,"test":20,"upload":15}' --deploy
or in one go, watching and fetching included:
node tools/build-job.mjs run --node vendor/igneum-node-v4 --target ae432dc7 --budget-minutes 150 \
--stage-minutes '{"setup":20,"linux":50,"windows":50,"test":20,"upload":15}'
The first job is cold: the distro has no mingw, no clang, no `/root/igneum-build/target`, so the node's ~500
crates compile for both targets. The Mac's cross-builds of the same tree take 8 to 15 minutes per target with 4 to 6
jobs at nice 19 (`infra/cross/build-linux.sh`, `proto-cuda/windows-node/cross-build.sh`, 4 October); the PC's CPU is
unknown to me (approximate: comparable), and the ext4 vhdx is slower than the Mac's SSD. Hence 150 minutes for the
first job, the 40-minute default for the warm ones after it.
What success looks like, in order, on the console's Jobs tab (or `node tools/build-job.mjs watch <id>`):
| Line | Means |
|---|---|
| `RESULT check <utc> drive C: N GB free, Ubuntu-24.04 /root: M GB free, floor 20 GB` | both sides over 20 GB |
| `RESULT fetch <utc> 79xxxxx bytes sha256 ok, node devnet-v4 3bfe346f, app 0.3.4, 2 build units, 2 test units` | the zip is the signed one |
| `RESULT setup ok cargo 1.x | rustc 1.x | mingw x86_64-w64-mingw32-gcc-posix (GCC) 13.x` | toolchain in place (first run installs; later runs say "apt packages present") |
| `RESULT linux node build exit 0 NNN s`, `RESULT linux igneumd NNN bytes sha256 ...`, `RESULT linux igneum-miner ...` | the Linux node |
| `RESULT linux app/igneum-app build exit 0` or `... optional on linux: not fatal` | the engine on Linux, best effort |
| `RESULT windows node build exit 0 NNN s`, `RESULT windows igneumd.exe ...`, `igneum-miner.exe`, `igneum-app.exe` | the Windows binaries |
| `RESULT test app/igneum-app [igneum-app] exit 0`, `RESULT test node [igneum-miner] exit 0` | tests |
| `RESULT output ...` x5, `RESULT upload <utc> igneumd.exe.zst relay item N ...` x5 | on the relay |
| `RESULT build <utc> done node devnet-v4 3bfe346f app 0.3.4: every stage ok; 5 files uploaded (NN MB); NN min of 150` | final |
| SUMMARY: `status done, exit 0` | the job is closed; `uploaded_files` 5 in the extras |
Then on the Mac: `node tools/build-job.mjs fetch <id>` prints one line per file ("matches the PC PE ok ...",
"coin icon and version block ok" for igneum-app.exe) and places them:
| File | Lands in |
|---|---|
| `igneumd.exe`, `igneum-miner.exe` | `vendor/igneum-node/target-integration/x86_64-pc-windows-gnu/release/` (what `push-inputs.sh` and `make-payload.sh` read) |
| `igneum-app.exe` | `app/igneum-app/target/x86_64-pc-windows-gnu/release/` (`make-payload.sh`'s default) |
| `igneumd`, `igneum-miner`, `igneum-app` (Linux) | `infra/cross/out/` with `version.txt` (where `build-linux.sh` leaves them for the cloud devnet) |
Also check: the Machines tab still shows PC 1 mining through the whole job (hash rate, accepted blocks), and the
Relay tab shows five `build-job <id> ...` file items from `DESKTOP-KMCV30N-ae432dc7` to `mac`.
Failure signatures to expect on a first run, and what they mean:
| Line | Likely cause | Fix |
|---|---|---|
| `check`: distro free space unknown, "does not answer" | WSL not reachable from the app's account (PC 2's 4 Oct `getpwnam` class) | the job's `wsl_user`/`distro` params; the engine log's `probe wsl` lines |
| `RESULT setup apt failed for: gcc-mingw-w64-x86-64 ...` | apt mirror or package names on 24.04 | `run` job with `apt-cache policy gcc-mingw-w64-x86-64` |
| windows node build: `undefined reference to pthread_...` or libstdc++ errors | the mingw thread model (win32 vs posix); the script names the `-posix` compilers on purpose | `run` job: `x86_64-w64-mingw32-g++-posix --version`, `ls /usr/x86_64-w64-mingw32/lib/libwinpthread.a` |
| windows node build: `bindgen` cannot find `stddef.h` | `BINDGEN_EXTRA_CLANG_ARGS` sysroot | the sysroot path on the PC (`/usr/x86_64-w64-mingw32`) |
| app windows build: `no windres found` | `binutils-mingw-w64-x86-64` missing | setup stage output |
| `RESULT upload ... over the relay's 50 MB cap` | `igneumd.exe` zst over 50 MB (today's Mac cross-build: 50.5 MB exe, zst approximate 16 MB; fine) | split or a bigger cap in `relay/lib/relay.mjs` |
| `STAGE windows timeout` | the cold build under the cap | re-add with a bigger `stage_minutes.windows`; the target dir keeps what compiled |
A failed job still uploads whatever target built (the pack and upload stages run when any target succeeded), and
the Mac tool fetches those; the job status says `failed` with the stage in `failures[]`.
## What was tested on the Mac, and what was not
Tested:
- `cargo test` of the app crate: 33 tests pass (the ones that existed plus 6 new: build params refused and accepted,
unknown kinds refused by the signer and skipped by the runner with the signature still checked, the plan from the
manifest with shell-unsafe names refused, the stage scripts carrying the plan, the parsers); the signer binary's
21 pass.
- `cargo check --target x86_64-pc-windows-gnu` of the app crate (the Windows runner code compiles).
- The packer against a scratch folder; the zip inspected (manifest, Cargo files, icon present; no target dirs).
- The publisher: `add --kind build` against a scratch `--dest` with the rebuilt signer (signs, verifies, lists;
the job carries budget 150, the five stage caps, the zip's sha256 and size, target `ae432dc7`, platform windows,
requires `wsl`). The signer built before this branch refuses the same file with "kind 'build' is unknown", which
is exactly what a 0.3.3 app would do: hence the rollout order above. A build job with a bad sha256 is refused at
signing.
- The Mac tool's fetch path against the live relay: a real `igneum-app.exe` zstd-compressed, posted as a build
output with the sha256 lines, fetched, decompressed, both sha256 matched, PE header and `verify-exe.py` passed; a
second post with a wrong sha256 refused (exit 1). Both test items deleted from the relay afterwards.
- The PE check on today's real `igneumd.exe` and `igneum-app.exe` (ok) and on a Linux binary (refused).
Not tested (only a PC can):
- The job itself: wsl.exe output through the sink per stage, the free-space probes, the apt install on 24.04, the
mingw posix toolchain with rocksdb, bindgen against `/usr/x86_64-w64-mingw32`, the static link, build times
against the caps, `taskkill` plus the in-distro `pkill` on a cap, the relay upload from Windows curl (the PUT with
`--data-binary @file`), the 50 MB limit against the real zst sizes.
- `cargo test` of `igneum-miner` inside the distro (what tests it has, how long).
- The dashboard strip with a `build` job running.
- Whether the engine builds on Linux at all (optional on purpose).
The main session publishes the first job and cuts 0.3.4; this branch stops here.