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

127 lines
10 KiB
Markdown

# 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.