igneum/packaging/README-ship.md
igneum-labs b2e6d6159e CI: run jobs test their fetched kit before use (the wiped-jobs-folder class)
The app's install clears the jobs folder on a PC, so a run job whose kit was fetched by an earlier fetch job finds
nothing after an update and fails in seconds (5 October 2026, 21:49Z, the AMD kit; bench-log 4df339f). Rule: a run
playbook that reaches a path under the jobs folder other than its own tests the kit is there before its first use,
and the fetch is republished under a new id after any app update.

tools/ci/kit-path-check.sh reads every *.ps1 under relay/playbooks/ and tools/. A kit root is a path derived from
the jobs folder (`$jobs = Split-Path $env:IGNEUM_JOB_DIR` then `Join-Path $jobs '<fetch id>'`, the race-5090.ps1
shape) or one carrying a literal `jobs\` (the amd-card-test.ps1 shape); every path built from it belongs to that kit.
A presence check (Test-Path, [IO.File]::Exists, [IO.Directory]::Exists, Get-Item or Get-ChildItem with -ErrorAction)
on the root or anything under it covers the whole kit. A use before that line fails with "kit path used before a
presence check: republish the fetch after any app update", as does a literal jobs\ path in a command with no check.
The job's own folder ($env:IGNEUM_JOB_DIR) is not a kit path.

Fixtures: kit-path-ok.ps1 (both shapes, checked; a sibling pack file covered by the worker's check) and
kit-path-unchecked.ps1 (the worker run before its check, a literal never checked); --self-test asserts the lines.
Wired into ci.yml after the bash-body step, and into publish-jobs.sh add --kind run beside the other two checks;
test-publish-jobs.sh gains the refusal (34 passed, 0 failed). The current tree: race-5090.ps1 is the one playbook
with a kit, checked before use. README-ship.md: the rule.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-05 22:37:29 +00:00

10 KiB

Shipping an Igneum Miner version (packaging/README-ship.md)

One command cuts a version for both platforms. 4 October 2026: 0.3.1, 0.3.2 and 0.3.3 each took eight hand steps and an hour; tools/ship-app.mjs is those steps in order, each one checked and resumable.

node tools/ship-app.mjs 0.3.4 --node vendor/igneum-node-v4 --notes "one line for the changelog"

Add --dry-run first: it reads everything, prints the plan and writes nothing.

What it does

Step What happens Skips itself when
preflight this tree clean and on master, the fork worktree clean (--node-commit <sha> pins it), tools, binaries, secrets present, gh account, what is live never (reads only)
bump the six version files, written by one function and read back the files already say the version
inputs packaging/windows/push-inputs.sh with the fork's Windows exes (IGNEUM_WIN_RELEASE, IGNEUM_NODE_SRC) the live payload-inputs.json carries these exact files from this fork commit
commit Igneum Miner <v>: <notes>, push master (the push starts the Windows build) committed and on origin/master
ci the windows.yml run for that commit (dispatched when the push started none), polled every 30 s a green run for the commit exists
fetch packaging/windows/fetch-ci-artifacts.sh <run>: installer and payload zip into the downloads folder the installer from that run is there
dmg packaging/mac/build-dmg.sh under tools/lock/with-lock.sh build the DMG is newer than the bump (--rebuild forces)
copy the DMG into the downloads folder same sha256 already there
manifest packaging/ota/publish-manifest.sh --no-deploy: signed, signature verified locally never (cheap)
deploy the downloads folder, one Vercel deploy for the files and the manifest together never
verify HEAD and GET of the DMG, the installer and the zip (size and sha256 against the local copies); the live manifest through igneum-ota-sign verify never
console one build item on the console (version, sizes, hashes, run, commit), then sync-dl never (upsert on ship:<v>)

The six version files: app/igneum-app/Cargo.toml, app/igneum-app/Cargo.lock, app/windows/version.h, app/igneum-app/resources/igneum-app.rc, packaging/windows/Igneum-Miner.iss, packaging/mac/app/Info.plist. node tools/ship-app.mjs --check says whether they agree; --self-test runs the bump on a scratch copy.

Flags

Flag Meaning
--node <dir> the igneum-node worktree the node and miner were built from (required); binaries from its target-integration/, else target/
--notes "..." the manifest's changelog line and the commit message
--dry-run reads only, prints the plan (exit 1 when preflight would stop the real run)
--from <step> resume at that step (preflight runs again first); the failure message prints this command
--skip-windows, --skip-mac one platform only (the other entry is carried over when the live manifest is the same version)
--node-commit <sha> the fork must be on this commit
--win-release <dir>, --mac-release <dir> other binary folders
--min-supported, --activation-height, --deadline-note, --channel passed to publish-manifest.sh
--rebuild build the DMG again even when a fresh one exists
--branch <name> accept another branch than master (a dry run from a worktree; the Windows build still runs on pushes to master)
--public the manifest step also publishes the version into dl/public/ (no token in any URL; its own signed manifest; the /public/ aliases rewritten), the same deploy carries it, verify checks every public file and alias. The token folders are untouched

The public downloads path (5 October 2026, testnet launch)

dl/public/ in the downloads folder holds only the current installers, the HiveOS package and the two signed manifests (app and wallet) with URLs under https://dl.igneum.network/dl/public/, plus an unsigned index igneum-downloads.json that the site build reads. Four stable aliases are rewrites in the downloads folder's vercel.json, written from what the folder holds, so they follow every release by themselves:

Alias Rewrites to
https://dl.igneum.network/public/igneum-miner-windows.exe dl/public/Igneum-Miner-Setup-<v>.exe
https://dl.igneum.network/public/igneum-miner-mac.dmg dl/public/Igneum-Miner-<v>.dmg
https://dl.igneum.network/public/igneum-miner-hive.tar.gz dl/public/igneum-hive-<v>.tar.gz
https://dl.igneum.network/public/igneum-wallet-mac.dmg dl/public/Igneum-Wallet-<v>.dmg

packaging/ota/publish-public.sh --app | --wallet | --hive <tar.gz> [--deploy] [--verify] [--dry-run] does the work; publish-manifest.sh --public and ship-app.mjs --public call it, so every future release lands there too. The HiveOS package comes from packaging/hive/make-hive-package.sh (Linux node and miner from a PC build job, the two GPU workers from infra/cross/build-workers-linux.sh) and is published with --hive. The site (site/build.mjs) reads the index at build time and stamps every download button with the version and size; TESTNET_OPEN there removes the "Public testnet: not yet open" line on the go.

When a step fails

The tool stops, prints why and the --from command to retry. Nothing is skipped silently. State that is not a secret (commit, run id, bump time, the hashes) is in ~/.cache/igneum/ship/<version>.json.

Secrets come from ~/.config/igneum (dl-token, dlsite-dir, ota-signing-key, relay token and key, the Vercel login) and are never printed; every output line is scrubbed. gh auth switch --user igneum-labs runs before every gh call and before the push.

What a cut needs before it starts

  • The node fork built for both targets in --node: target-integration/release/{igneumd,igneum-miner} and target-integration/x86_64-pc-windows-gnu/release/{igneumd,igneum-miner}.exe (proto-cuda/windows-node/cross-build.sh).
  • The prebuilt workers (proto-cuda/nvrtc/igneum-worker-cuda.exe, proto-opencl/igneum-worker-opencl.exe) and the prover (proving/igneum-prove/target/release/igneum-prove-{host,export}); missing ones are noted, not fatal.
  • The Mac on mains, nothing else building (the DMG step waits for the build lock).

The site project on Vercel (4 October 2026, checked by hand)

Two Vercel projects are called igneum, in two teams, and the link file under site/.vercel points at the wrong one:

Team Login (global config) Project Holds Deploys
igneum igneum-labs (~/.config/igneum/vercel) igneum (prj_HcNO2NLY4jkhikmLMugpJ8nLcWNW, root directory site, build node build.mjs) igneum.network, igneum.com; env DATABASE_URL, LOG_INTAKE_KEY GitHub integration: every push to master is a production deploy, every branch push a preview (igneum-git-<branch>-igneum.vercel.app)
[other-business] [user]-4826 (the default ~/.vercel) igneum (prj_y5lQg3C87fXAgoj3vug6iwWU3KwV, same settings) the 13 redirect domains (igneum.app, .art, .co.uk, .email, .info, .io, .net, .online, .org, .pro, .shop, .store, .vip, .xyz) nothing live

So vercel env add from site/ fails with "Could not retrieve Project Settings" under the igneum login (the link file names the [other-business] project, which that login cannot read), and would silently add the value to the wrong project under the default login. The working commands, from site/ in the shared checkout (the link step rewrites site/.vercel/project.json, which is ignored by git):

cd site
npx --yes vercel@latest --global-config ~/.config/igneum/vercel link --scope igneum --project igneum --yes
npx --yes vercel@latest --global-config ~/.config/igneum/vercel env ls --scope igneum                    # names only
npx --yes vercel@latest --global-config ~/.config/igneum/vercel env add LOG_INTAKE_KEY_NEXT production --scope igneum

A production deploy by hand is npx --yes vercel@latest --global-config ~/.config/igneum/vercel deploy --prod --yes --scope igneum from the repository root (the project's root directory is site), but the normal path is a push to master. The relay (igneum-relay) and the downloads folder (igneum-dl) are the other two projects in the igneum team; both deploy by CLI from their own folders (relay/README.md, packaging/ota/README.md). CLAUDE.md still says the site sits in the [other-business] team and deploys with --scope [other-business] from site/: that was true on 3 October and is not now.

Job scripts (5 October 2026, the lost-quote class)

A bash body in a PowerShell job (relay/playbooks/*.ps1, tools/**/*.ps1) is written to a file and run with bash <file>, never inline. That is the 0.3.6 cut's rule. Twice on 5 October a body travelled inside a PowerShell string, a quote was lost on the way through PowerShell, bash refused the whole body, and the job either reported exit 0 having done nothing (tools/amd-prove/pc1-cpu-prove.ps1, first version: an apostrophe inside a single-quoted awk program) or failed in 4 s (the 0.3.10 installer job). The file shape keeps the body readable by bash -n before it runs; pc1-cpu-prove.ps1 does that in the job itself. tools/ci/bash-body-check.sh fails CI on an inline body that does not parse: it finds every body handed to bash (bash -c "...", bash -lc $var, a + concatenation, a here-string written to a file and run), unescapes it the way PowerShell would, and runs bash -n on it. A body it sees but cannot read fails too. --self-test shows it firing on the fixtures under tools/ci/fixtures/. A job script is published from a worktree and never passes CI before it runs, so packaging/ota/publish-jobs.sh add --kind run runs the same check (and tools/ci/prover-socket-check.sh, the root-socket class; bash -n for a .sh script) on the script before anything is signed, and refuses the publish with the check's output. packaging/ota/test-publish-jobs.sh proves the refusals. The wiped-jobs-folder class (5 October 2026, 21:49Z): an app install clears the jobs folder, so a run job that uses a kit fetched by an earlier fetch job tests the kit is there (Test-Path on the kit root or any file under it) before its first use, and the fetch is republished under a new id after any app update. tools/ci/kit-path-check.sh fails CI and the publish on a kit path used before that check.