F14, the relay and publisher half (the founder's ruling, 8 October 2026, 19:57 UK: the public miner carries no remote execution; the fleet runs the lab build under its own signing root, separate from the OTA key). publish-manifest.sh: the key follows the channel (igneum-2.0-devnet-lab signs with ~/.config/igneum/lab-signing/lab-signing-key, --lab names it; every other channel the OTA key; a channel naming a lab that is not the lab channel is refused); publish-jobs.sh: --channel lab|public picks the root, anything else refused. igneum-agent.ps1: App-Product reads api/state .product and Lab-Refusal refuses every task beside a public engine ("Igneum Miner"), an engine without the field (pre-2.0.2) runs as before. Release rule 16 names the split (feature lab, channel igneum-2.0-devnet-lab, product "Igneum Miner Lab", the lab kits, the per-PC exes wrapping the lab Setup, the lab root). Known-failed first: the publishers' cross refusals, relay/test/service.test.mjs

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-08 19:07:34 +00:00
parent 4dd8d1816e
commit 60d684cd89
4 changed files with 40 additions and 0 deletions

View file

@ -19,3 +19,5 @@ Every cut of the Igneum Miner app and its node runs under these. The dated plan
12. **The Discord card only when every platform is live.** Live manifest changes beyond the binaries (a moved consensus floor) go out only on the founder's explicit word, staged beside the release with their digest and a one-line diff.
13. **Ship on green:** no calendar waits; when the gates are green, publish and state the clock time (UK). Checkpoints are for slips, not for waiting.
15. **The version bump is the release branch's first commit (7 October 2026, after the 0.3.23 miss).** When a release-0.3.N branch opens, its first commit moves the six version places (app/igneum-app/Cargo.toml and Cargo.lock, app/windows/version.h, app/igneum-app/resources/igneum-app.rc's four fields, packaging/mac/app/Info.plist, the installer's AppVersion), never left to the cut: release-0.3.23 opened at 4cdcab31 and carried 0.3.22 in every place until 21:26 BST, so the app exes and the window host crossed from its first closed tip read 0.3.22 and were void. Gate check: on a push to release-0.3.N the pre-push gate (tools/ci/release-version-check.sh, self-test on tonight's shape first) reads all six places against the branch name and goes red on any mismatch; the .rc was the second layer of the same miss (the box cross failed in build.rs at 21:30 BST).
16. **The public miner carries no remote execution; the fleet runs the lab build (the founder's ruling on review B F14, 8 October 2026, 19:57 UK).** From 2.0.2 the public build has no run-script, fetch, collect, restart or update-now jobs, no jobs feed, no wake listener and no relay client (cargo feature `lab`, off by default), and documents its least-privilege boundary; "automatic updates off" stays off for an urgent manifest (pause and notify only). Our fleet, PC 1, PC 2 and the mini run the lab build only (product "Igneum Miner Lab", channel `igneum-2.0-devnet-lab`; the kits Igneum-Miner-Lab-Setup-<v>.exe, the lab DMG, the lab hive; the per-PC combined exes wrap the lab Setup), which verifies its manifest and jobs feed against the lab root (~/.config/igneum/lab-signing on the Mac, never in the repo), separate from the OTA key; publish-manifest.sh and publish-jobs.sh sign the lab channel with the lab key and a public channel with the public key, never cross (`--lab` / `--channel lab`). The relay agent refuses to run anything beside a public engine, and the jobs publisher refuses a target whose last upload is not a lab build.

View file

@ -46,6 +46,12 @@ ROOT="$(cd "$HERE/../.." && pwd)"
export PATH="$HOME/.cargo/bin:$PATH"
KEY="$HOME/.config/igneum/ota-signing-key"
PUB="$HOME/.config/igneum/ota-signing-key.pub"
# F14 (8 October 2026): the jobs feed is a LAB thing (the public build has no jobrun); a jobs file is signed with the lab root
# when --channel lab is given (the 2.0.2 lab builds verify against it), with the OTA key for the 0.3.x and 2.0.0/2.0.1 engines
# that still carry the jobs feed; never both roots on one file, never the lab key on a public channel
LAB_KEY="$HOME/.config/igneum/lab-signing/lab-signing-key"
LAB_PUB="$HOME/.config/igneum/lab-signing/lab-signing-key.pub"
JOBS_CHANNEL="public"
TOKEN_FILE="$HOME/.config/igneum/dl-token"
SIGNER="$ROOT/app/igneum-app/target/release/igneum-ota-sign"
@ -96,6 +102,7 @@ while [ $# -gt 0 ]; do
--glob) GLOBS+=("$2"); shift 2 ;;
--command) COMMAND="$2"; shift 2 ;;
--what) WHAT="$2"; shift 2 ;;
--channel) JOBS_CHANNEL="$2"; shift 2 ;;
--allow-app-restart) ALLOW_APP_RESTART=1; shift ;;
--zip) ZIP="$2"; shift 2 ;;
--fixtures) FIXTURES="$2"; shift 2 ;;
@ -128,6 +135,11 @@ if [ "$CMD" = add ] && [ "${KIND:-}" = restart ] && [ "${WHAT:-app}" = app ] &&
fi
case "$CMD" in add|list|remove|sign|verify) ;; *) sed -n '2,35p' "$0" | sed 's/^# \{0,1\}//'; exit 2 ;; esac
case "$JOBS_CHANNEL" in
lab) KEY="$LAB_KEY"; PUB="$LAB_PUB"; [ -f "$LAB_KEY" ] || { echo "no $LAB_KEY: the lab channel signs with the lab root only" >&2; exit 1; } ;;
public) ;;
*) echo "refused: --channel must be lab or public (got '$JOBS_CHANNEL'); a cross of root and channel never signs" >&2; exit 2 ;;
esac
[ -f "$KEY" ] || { echo "no $KEY (see packaging/ota/README.md, Keys)" >&2; exit 1; }
[ -f "$PUB" ] || { echo "no $PUB" >&2; exit 1; }
[ -f "$TOKEN_FILE" ] || { echo "no $TOKEN_FILE" >&2; exit 1; }

View file

@ -42,6 +42,13 @@ ROOT="$(cd "$HERE/../.." && pwd)"
export PATH="$HOME/.cargo/bin:$PATH"
KEY="$HOME/.config/igneum/ota-signing-key"
PUB="$HOME/.config/igneum/ota-signing-key.pub"
# F14 (the founder's ruling, 8 October 2026, 19:57 UK): the lab build (product "Igneum Miner Lab", our fleet, PC 1, PC 2, the
# mini) verifies against its OWN root; the public build carries only the public root. The key follows the channel: the lab
# channel igneum-2.0-devnet-lab signs with ~/.config/igneum/lab-signing/lab-signing-key, every other channel with the OTA key,
# and a cross (--lab-key on a public channel, the OTA key on the lab channel) is refused before anything is written.
LAB_CHANNEL="igneum-2.0-devnet-lab"
LAB_KEY="$HOME/.config/igneum/lab-signing/lab-signing-key"
LAB_PUB="$HOME/.config/igneum/lab-signing/lab-signing-key.pub"
TOKEN_FILE="$HOME/.config/igneum/dl-token"
SIGNER="$ROOT/app/igneum-app/target/release/igneum-ota-sign"
PRODUCT="app"
@ -60,6 +67,7 @@ while [ $# -gt 0 ]; do
--override) OVERRIDE="$2"; shift 2 ;; # consensus.override: the exact JSON object every app writes to its override.json (all height switches, not just the new one)
--min-supported) MIN_SUPPORTED="$2"; shift 2 ;;
--channel) CHANNEL="$2"; shift 2 ;;
--lab) CHANNEL="$LAB_CHANNEL"; shift ;;
--product) PRODUCT="$2"; shift 2 ;;
--tuning) TUNING_FILE="$2"; shift 2 ;;
--no-tuning) NO_TUNING=1; shift ;;
@ -115,6 +123,10 @@ MF="igneum-$PRODUCT-latest.json"
[ -n "$VERSION" ] || [ "$VERIFY_ONLY" = 1 ] || { echo "--version is required" >&2; exit 2; }
[ -n "$VERSION" ] || VERSION="(the folder's)"
[ "$VERIFY_ONLY" = 1 ] || case "$VERSION" in [0-9]*.[0-9]*.[0-9]*) ;; *) echo "--version must be major.minor.patch" >&2; exit 2 ;; esac
case "$CHANNEL" in
"$LAB_CHANNEL") KEY="$LAB_KEY"; PUB="$LAB_PUB"; [ -f "$LAB_KEY" ] || { echo "no $LAB_KEY: the lab channel signs with the lab root only (igneum-ota-sign keygen $LAB_KEY $LAB_PUB once, on this Mac)" >&2; exit 1; } ;;
*lab*) echo "refused: channel '$CHANNEL' names a lab but is not '$LAB_CHANNEL'; a public channel never signs with the lab root and a lab channel never with the OTA key" >&2; exit 2 ;;
esac
[ -f "$KEY" ] || { echo "no $KEY: run $SIGNER keygen $KEY $PUB once (the public key then goes into src/manifest.rs)" >&2; exit 1; }
[ -f "$PUB" ] || { echo "no $PUB" >&2; exit 1; }
[ -f "$TOKEN_FILE" ] || { echo "no $TOKEN_FILE" >&2; exit 1; }

View file

@ -134,6 +134,19 @@ function App-Version {
if (-not (Test-Path $urlFile)) { return '' }
try { $u = (Get-Content $urlFile -Raw).Trim(); $st = Invoke-RestMethod -Uri ($u + 'api/state') -TimeoutSec 5 -UseBasicParsing; return [string]$st.version } catch { return '' }
}
function App-Product {
# F14 (the founder's ruling, 8 October 2026): the public build carries no remote execution, so this agent runs nothing beside
# a public engine. The engine says what it is in api/state .product ("Igneum Miner Lab" | "Igneum Miner") from 2.0.2; an
# engine with no product field is older than that and runs as before. '' when nothing answers.
$urlFile = Join-Path $env:LOCALAPPDATA 'igneum\app\app.url'
if (-not (Test-Path $urlFile)) { return '' }
try { $u = (Get-Content $urlFile -Raw).Trim(); $st = Invoke-RestMethod -Uri ($u + 'api/state') -TimeoutSec 5 -UseBasicParsing; if ($null -ne $st.product) { return [string]$st.product } else { return '' } } catch { return '' }
}
function Lab-Refusal {
$p = App-Product
if ($p -and $p -ne 'Igneum Miner Lab') { return ('the engine beside this agent is the public build (' + $p + '); the public miner carries no remote execution (F14), install the lab build on a fleet PC') }
return ''
}
function Start-App {
# MF-11: start the installed Igneum Miner and read back that an engine answers. Never elevated: an elevated agent starts it
# through a one-off scheduled task at the limited run level (the app's own rule: it runs as the user). Never a quit,
@ -219,6 +232,7 @@ function Run-Task($task, [int] $pass) {
$rebootAllowed = $rebootContinue -or [bool]$task.flags.reboot
Log ("task #" + $id + " '" + $task.title + "' pass " + $pass + $(if ($elevated) { " elevated" } else { "" }) + $(if ($rebootContinue) { " reboot_continue" } elseif ($rebootAllowed) { " reboot" } else { "" }))
$why = Check-Task $task
if (-not $why) { $why = Lab-Refusal }
if ($why) {
Log ("task #" + $id + " REFUSED: " + $why)
Add-Content -Path $log -Value ("REFUSED: " + $why)