drivertable::install_hold_keys holds every enabled card of the vendor being installed (the other vendors' cards keep mining; a card already off has nothing to stop); the row says so before the click and while it runs, with the enclosure warning kept on an external row; the toast and the engine's event name the vendor. The x1 gen1 link signature is not on the card state and was not added. Test known-failed first (the 5090 inside the case was not held for an NVIDIA install; red on build-2, then green); box gate 259 + 33 + 8; UI 51.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The rule (drivertable::install_hold_keys): a driver install on a card the app reads as external (the eGPU kind from update-return-21b) stops that card's worker before the installer starts, through the cards path with the restore choice kept (the --cards-off shape); the vendor's cards inside the case and every other vendor's card keep mining. The row says so before the click and while it runs (the display reset can take the machine for a minute and may need a restart; save your work first). When the installer ends the held card goes back as it was; a card the install took away comes back when the card does (driver_release_held on the detection that lists it again). The app never restarts the machine.
Tests: the known-failed rule test first (an install on an eGPU card with the worker still running held nothing: red on build-2, then green), the view test for the two sentences; box gate 258 + 33 + 8 (test --release on build-2). Mock scenarios drivers-egpu and drivers-egpu-running; captures 13 to 16 (light and dark). Branch rebased onto release-0.3.21, which already carries the driver-check commits; the earlier tip is kept as driver-check-0320.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The drift the app carried against the site's home fold and /live, and what moved:
- renderer: both were 2.0.2 byte for byte; the app now takes the shared scene/live-dag.js 2.0.3 (paint on push whatever the
document's visibility says: the blank /live; the phone rule on the viewport width) and proof-core.js through tools/scene/sync.mjs,
and the gate refuses a drifted copy.
- palette: the dark tokens were equal; the light theme's --ember was #E04A14 and --ember-hi #F2541B against the brand package's
#D0420D / #E04A14. The fourteen scene tokens now sit in the scene-tokens block app.css takes from scene/tokens.css (dark,
[data-theme="light"], prefers-color-scheme light) and are defined nowhere else in the file.
- phone rule: the app passed narrow: window.innerWidth < 720 at mount time and never again; the site keyed it on the canvas
width (a 640 px hero on a laptop rendered as a phone). Both now leave it to the renderer: the viewport, live on resize.
- feed window: the app asked the engine for 120 s, the site 300 s; both 300 now, so the viewer can pan the same range.
- Inspect view: 360 px tall against /live's 420 (five lanes against seven); 420 now.
- feed shape: the engine rewrote this machine's blocks to miner: "you" (a word the observer never emits) and its node-only
fallback carried now as a float of seconds, rows with timestamp_ms / is_chain_block / vote_key_hash / timestamp_source and no
number or rx, miners as {id, vote_key_hash, blocks_10m}, no proving, a finality with checkpoints alone. live.rs now passes the
observer's rows through untouched (state.you_blocks counts them; the UI's mine function marks the lane from the card ids, which
is the overlay: own blocks glow, the lane reads YOUR KEY) and node_only_reply builds the fallback in the contract's shape
(every key of scene/feed-contract.json, null where the node cannot know, partial: true, state.source "node", ISO now). The
test the_node_only_reply_has_the_contract_shape reads the contract file itself (include_str!), so the Rust side and
tools/scene/feed-contract.mjs cannot drift.
App gate on build-2 (test --release, --priority gate): 228 + 32 + 8 passed. Parity on build-2: app Inspect = /live = home fold
at T+0, T+2, T+4 s, the overlay identical when both surfaces know the key.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
docs/plans/miner-faults.md: MF-1 to MF-7, each with its rule, test and gate line.
- MF-1/MF-2: a worker starts and is judged only when the node is READY (synced and igneum_getExecStatus reports an
executed tip; execrpc::probe every 5 s off the engine thread); the node watchdog never counts the catch-up (settled
once read synced; 30 min cap before that; any RPC answer is a sign of life); the watchdog restarts on a ladder 10 s,
30 s, 2 min, 5 min, then every 5 min for ever (watchdog::RETRY_LADDER_S); the faulted state and the one-restart
budget are gone (tools/ci/permanent-fault-check.sh in the gate); a node-caused restart resets the ladder at sync.
- MF-3: the hot-plug pass starts a recovered or revived card's worker (unchanged rule, now in the register).
- MF-4: the status clock starts at ready (program loaded), loading bounded by 300 s; a self-test failure holds the
card 30 min with the reason on its row, released on a driver change; a crash loop climbs the ladder; the pack is
exported once a minute for every card (a refused pack forces one).
- MF-5: the app reads template_wait=, template_ms=, identities_active= from the 0.3.20 miner's STATUS; waiting on
the node is never the card's fault; the row says node slow; every node-wait label clears on the first rate.
- MF-6: a miners hold belongs to the job that took it and releases when that job is gone or at its own cap.
- MF-7: the engine owns every igneum-miner it started: an untracked one on this engine's node RPC is killed at start,
after every stop and every minute, one line and one fault report per kill; a restart kills the old process first.
- Every fault line posts one FAULT line to the log intake (label fault-<id8>, app and node version, 60/h cap).
- The signed cards job kind (per card enabled, identities, power_pct; refused for a card the machine lacks; applied
through the app's own card path, persisted, read back): packaging/ota/publish-jobs.sh add --kind cards.
- LG-4 as a job: relay/playbooks/first-share.ps1 and tools/fleet/first-share-gate.mjs (no Windows box yet).
- tools/reliability: the fault injector with one step per class (catch-up, card-appears, own-restart, zero-ladder,
no-status, node-silent, one-card-fails, orphan-miner); fake-worker.mjs lists devices and fails self-tests on command.
- master's build tooling (97255a4e) and release-0.3.20's igneum-pow taken into the worktree for the box routes.
Box: app 198 + 27 + 8 tests green on igneum-build-2; the tree gate green (33 checks).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
the project lead: can the drivers be packaged with the miner, for all cards, with the system knowing which to install if not
present. Not bundled: detected and installed on one click. A per-vendor table rides the signed manifest (drivers.json:
min_version, the version on offer, the vendor's URL, size, sha256 and its source page, the silent arguments, the
restart exit codes, the Authenticode signer, the Linux and HiveOS package), validated by the signer and the app alike
(src/drivertable.rs, shared), written to <app data>/drivers.json by ota.rs. Each card's driver version is read at
every detection (nvidia-smi's driver_version; Windows' DriverVersion for AMD and Intel) and compared; a missing or
old driver puts the offer on the card's row, on the dashboard and on the first-run list. The click downloads with
curl (resume), checks size, sha256 and the Authenticode subject, runs the installer through one elevated prompt
(platform::elevated_command, the PC 1 driver job's shape), reports restart required with a Restart now button, and
never restarts by itself; the miners keep mining. macOS: no step; Linux and HiveOS: the package line. Dry run through
IGNEUM_DRIVER_DRY_RUN or the table. Tests: the table, the versions, the offers per tier, the exit codes, the
Authenticode verdicts, the download against a mocked vendor server on 127.0.0.1, the UI's strip per state; the mock's
drivers scenarios; captures light and dark in docs/plans/driver-check-shots. publish-manifest.sh --drivers carries the
table. Also the doubled #[test] in detect.rs from the cherry-pick.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 3f0ef65786)
First run gains step 2 of 3 (the region list, the price per kWh prefilled from the public table and typed, never
fetched, the restricted line for Russia's regions and China from future.md 4.4 and 8.3 with the standing sentence);
Settings gains Electricity and Heat mode; the Cards strip and every row's Ember line show the duty and the heat in
watts; a resting card, the pill and the big button say heat mode; Earnings gains the miner's cost per MH/s-hour at
their price beside the bench log's measured rental rate with the verdict. The money symbol follows the region.
heat-region.test.mjs checks the Russian entry, the bench-log rate and the words; the mock gains heat and heat-rest.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 8099bbfd46)
"Interface <version>, over the air, <date>" or "built in", with the help line for pending, refused, held back and
skipped; the "Use the built-in interface" switch (POST /api/ui/builtin). The page posts /api/ui/health once after its
first paint, or the first script error it catches before that; when the served interface changes under it, the page
reloads itself only when idle (no input focused, no sheet or update card open, on the dashboard). ui-ota.test.mjs
covers the words, the reload rule, the ping and that the page loads no remote script.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit b6a0fd0d75)
View: poisson, ignPerDay, ladderRungs (every threshold from the node's params: dust, weightWindow, presenceWindow),
blockCard, earningsLines (IGN first, the typed price never fetched, the share and rank, IGN per kWh), cardIgnDay,
timeline, profileWords; FIELDS names every number's RPC field for the title on hover. Overview: the Poisson
count-up until the first block, the block card in place with Save the card (a canvas PNG, no network call), Copy the
link and the explorer, the ladder strip. Earnings: the six lines, the ladder card, Your first hour. Prove: the shard
card. Cards: IGN a day per card. Settings: Make my page public. The chain scene is the site lane's EMBER 02 pack
(live-dag.js and proof-core.js byte-identical): the compact card fed by the page's own api/live read, Inspect opens
the full scene with the block inspector in the site's words. ui-mock: scenarios firstwait, firstblock, ladder,
api/ladder, api/card. view.test.mjs: a Devnet 2 key walks every rung; 47 UI tests.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 32793b686a)
Only update.rs from 0f6b4650; its version bumps stay with the shipper's cut.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 032e1f8717)
src/merge.rs, pure: every network sink more than the merge depth ahead and none of our blocks in the network's view for 120 s. Engine polls the observer's view every 30 s while mining and runs the check after sync_decision_v2; state behind, sync_cause not merging, one error event. Known-failed test first; 172 box tests, 42 UI tests.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 302d6e7055)
The mode (own | external | none) is re-decided every 5 s while the app reads or refuses another node; gone for 60 s, the app starts its own node and says so in Activity. node.mode and node.mode_reason in api/state and on the Node details. Pure extnode::step with the goes-away test first.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
View.finalityWords with Ember's test (41 UI tests). extnode reads the node lane's reply shape: params compared value by value, network must match, a stub engine is refused and a fault on the app's own node. 168 box tests green.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
engine::sync_decision_v2 gates the old decision on the watch line's tip_age_s (<= 120 s; -1 on an older node gates
nothing) and peers >= 1, with the cause word frozen | no peers | syncing | behind on node.sync_cause; the node state
reads 'behind' or 'no peers', never 'synced' on a tip that stopped moving. engine::is_stall_exit: code 45 or a
"STALLED '" tail line (the node lane, ca3-v4-0316); the first restarts the miner, the second since the node started
restarts the node and re-dials its peers (restart_node). The known-failed case is the first test: the old rule says
synced on a tip frozen 3,180 s with one peer. UI: 'Node behind · 1 peer · 133,000 blocks · last block 53 min ago'
with the cause line, 'Node no peers', the pill 'Node behind' in ember, the big button 'waiting: the node is behind
the chain'. 39 UI tests pass; the app crate's tests run on the box.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Main's correction: the app uses the same words as the site, the litepaper and the ledger for the chain's own objects,
or users cannot match the app to the docs. Shard (not piece), segment (not run of blocks, 'runs of 8 blocks' as the
explanation), checkpoint and 'locked checkpoint' (not lock point), aggregator (not combiner), verifying (not checking
proofs). Card, app, Default · Balanced, the Ember Tune strip, the state words and the one-sentence rule stay. 37 UI
tests pass.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
src/live.rs reads the observer's /api/live through curl (the URL ota.rs already polls), caches it 2 s per window,
marks this machine's blocks as the lane 'you' by matching the miner id against every card's vote-key ids, and
answers {ok:false} when the observer is unreachable so the UI draws its own strip; four unit tests shape a fixture
in the observer's reply shape. The local node speaks gRPC and wRPC only, so a local-node source stays owed.
The copy sweep: no fleet, lane, identities, prior, hill climb, sweep, DAA, digest, manifest, shard, segment,
aggregator, verifier, worker or engine on a user surface; every toast and event line one sentence; the full list
in docs/plans/miner-ui-4-copy.md. The per-card goal's inherit option reads 'Default · Balanced' and follows the
shared goal; the cap's unpin is 'Back to Default'; the Cards strip is titled Ember Tune; each goal carries its
meaning as a tooltip. 37 UI tests pass.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Rail: Overview, Cards, Earnings, Prove, Settings. Overview: the fleet rate at 84 px on the fade with W, £ a day and
blocks, Start/Stop as the bar under it, the site's live-dag.js on the engine's api/live with this machine's lane as
'you' (the app's blocks strip as the fallback until the engine serves api/live), the node line, the activity feed.
Cards: the Tuning strip (goal with its £ a day, the fleet saving in W and £, Tune all, the schedule, Power control
when off), the rows with a flame mark that fills with the tune's progress, before -> after watts, a sparkline of the
ladder, 'Tuned 2 h ago · 2470 MHz at 80% · next check Sunday · saves 84 W, 0.16% of rate', Retune, and under the
chevron the cap, the card's own goal, the curve with the chosen point ringed. Reads Ember's tune_before_watts,
tune_before_mhs, tune_goal, sweep_watts, sweep_mhs, tune_curve. Rust: one route, GET /live-dag.js. The ui-mock gains
/api/live. 37 UI tests pass.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Variant C and the Earnings reshaping reverted: Mine and Earnings are exactly as shipped in 0.3.14. Kept from the
polish round: every strip, ask and clock card on the brand tokens (the row surface, a 1 px ember hairline on top,
ash text, ember only on the dot and the chevron; no filled brown or mustard field anywhere; the update strip and the
job strip share .notice), the plain-language strip and event sentences with the technical line behind the chevron,
the header baseline and the pill wording. 36 UI tests pass.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
the project lead picked C. The Mine hero is the fleet rate at 84 px on a graphite-to-obsidian fade with W, £ a day and blocks
beside it, Start/Stop as a full-width bar under it; Earnings carries the same shape with the £ figure big. The
?variant switch is gone. Every strip, ask and clock card is one component: the row surface, a 1 px ember hairline on
top, ash text, ember only on the dot and the chevron; no filled brown or mustard field anywhere in the app (the rail's
active item, the pill, the ghost-on button, the option, the ring, the log hit and mark lost their tints). The update
strip and the job strip share .notice. 36 UI tests pass.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
No raw job, manifest or relay text on a user surface: the job strip reads 'A job from the team ran: update check,
0 min.' with the technical line behind a chevron; 'Updated to 0.3.14 at 18:52.'; the Activity feed maps every engine
event string to a sentence (View.plainEvent, 80 patterns, the engine line as the tooltip) with a kind dot. The header
puts the title, subtitle and pill on one baseline; the pill is a sentence with a coloured dot (Mining · 511 MH/s,
Paused, Syncing the node, Node restarting, Engine not answering). Three Mine layouts behind ?variant=a|b|c (quieter,
denser, hero number) for the project lead to pick. 390 px strip on one line. 36 UI tests pass.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The audit (docs/plans/miner-ui-3-audit.md, 27 screenshots): no money anywhere, three red n/a per Apple row,
tuning split across two pages, 370 px of controls per card, Prove's 95 words and four zeros, Node's eleven
numbers, the jobs table on every Updates page, fixes two taps away, no light mode, no layout under 900 px,
developer lines on user surfaces.
The design (docs/plans/miner-ui-3.md): four sections (Mine, Earnings, Prove, Settings); the card row carries the
state word with its reason, MH/s, W with MH/W, £ a day at the user's electricity price, °C, Tune, the switch,
and its details under a chevron (cap slider, identities, pin, telemetry, the tune table); the tune line per row
(not tuned yet / tuning: step k of n with a bar / tuned N ago · point · next check <weekday> / measured only and
why / pinned / stopped / fleet pause); Tune all; the goal (Efficiency, Balanced, Maximum) with its £ a day; Power
control as one switch where it is the fix; the node as one line with Details; updates as one card; earnings money
first, IGN second; proving as a tier sentence per card; every costly action confirmed in place (quit, change
address, show key, trust mode), no dialogs; light and dark; a bottom tab bar under 720 px.
The build: index.html, app.css, app.js rewritten on the same engine routes; the pure blocks keep their API and
carry ember-tune's tuneNotice, tuneWord, toggle states and tune-line test (0bf27b1) so the merge is clean; the
update card is named Igneum Miner; the Rust side is untouched. node --test on the four UI files: 35 pass.
Screenshots of the result: docs/plans/miner-ui-3/n00 to n26.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
the project lead, 6 October 2026: on PC 1 the integrated card sat between the 5090 and the 9070 XT. The list now shows
usable cards first, ordered by the measured rate since the start in 5 MH/s buckets (no flicker), discrete,
external and Apple before integrated, then memory; removed and unusable cards last; ties keep the detection
order. The row signature follows the order, so a reorder re-renders. Three UI tests. For 0.3.12.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
One prover at 2.2% coverage never had 8 consecutive proven blocks (C47, 6 October 2026); claiming whole segments makes it complete about 1 segment in 75 instead of none. The choice is deterministic per key (FNV of first block and key hash) over the untouched whole segments inside their deadline by a margin (240 DAA or 1.5x the last segment's time); the per-block path stays as the fallback. Unit tests for the grid, the grouping, the margin, the attempted set and the per-key order; 120 app tests, 8 core and 9 host tests pass.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
the project lead, 5 October 2026, 22:45 BST: "make sure we have ember tuning every single card for efficiency out of the box, the
more data = the better the tune, make an awesome system." Built on lever 3 (docs/plans/miner-eff.md), lever 2's signed
tuning section (docs/design/miner-tuning.md), the AMD telemetry helper (423936b, its --tune/--set-gmax/--set-plimit/
--reset contract) and the Power control switch (057f0ec). Design, data flow, tiers and the privacy line:
docs/plans/ember-tune.md.
- src/ember.rs (new): two knobs per card (power limit %, core clock cap MHz; memory clock never touched), the full plan
(power ladder 100..50%, then the clock ladder 90..60% at the chosen power), the confirm plan (the fleet prior and one
neighbour), the baseline plan (measure only), the marks (faulted, hot, memory_clock_dropped, unapplied, no_readings),
the choice (best MH/W within 1% of the top rate, then rate, then draw), the fleet record (a hash of the install id,
no address), the prior lookup and the kill switch (tuning.ember), the state machine on a fake clock. 9 unit tests.
- engine.rs: tick_sweep schedules every NVIDIA, AMD and Apple card (120 s steady, 600 s to the boundary, no job hold,
no pause, weekly, again after a driver major or program-class change, never under the manifest kill switch); the
probe (nvidia-smi clocks.max.gr + driver_version and the direct/helper mode; igneum-gpu-telemetry --tune for AMD);
tune_apply (nvidia-smi -pl / -lgc 0,<MHz> / -rgc directly or through the helper; the AMD helper per request);
Cmd::TuneProbe, Cmd::TuneSet; faults from rejected and mismatched hashes mark the step; the TUNE lines and the TUNE
{json} record, uploaded with the log; the Tuned line on the card state. The NVIDIA helper starts only with Power
control on: the --sweep job never counts as permission (no prompt on a PC with nobody there).
- sweep.rs: the helper protocol gains lgc/rgc (clock cap and reset) and resets the clocks after 20 idle minutes.
- state.rs, config.rs: the tune fields (clock cap, driver, class, source, the Tuned line); the nvidia-smi telemetry
query carries clocks.gr and clocks.mem; the AMD sample line's plimit_pct and gmax_mhz are parsed.
- ui: "Tuned: X MH/s at Y W (Z MH/W)" with the point, the source and when; measure-only cards say why; the Ember Tune
switch; tune-line.test.mjs.
- relay/lib/ember.mjs + relay/test/ember.test.mjs: the aggregation per (card model | driver major | program class):
median point, MH/W, spread, samples, machines; five samples converge, an outlier does not move the median, baselines
make no prior, de-duplication, the manifest merge keeps lever 2's cards. api/console.mjs fn=tuning and
tools/console.mjs tuning; tools/tuning.mjs --priors [--write tuning.json] [--site] [--tuning-off].
- site: the fleet priors table on /miners (site/miner-priors.json), the lever text.
- relay/playbooks/ember-tune-pc1.ps1: the PC 1 run (second engine with --sweep from a scratch copy of the install).
Measured tonight: see the bench log entry that follows the PC 1 run. The 9070 XT left PC 1's bus at 20:40 UTC and the
5090 needs the administrator prompt the project lead cannot answer asleep, so tonight's PC 1 run is the baseline plan on the 5090
through the whole pipeline; the two-knob tune on both cards is owed.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>