diff --git a/app/igneum-app/src/drivertable.rs b/app/igneum-app/src/drivertable.rs index ca9b27b38..129522eec 100644 --- a/app/igneum-app/src/drivertable.rs +++ b/app/igneum-app/src/drivertable.rs @@ -244,20 +244,21 @@ pub fn authenticode_verdict(text: &str, signer: &str) -> Result Ok(subject) } -/// The cards whose worker stops before a vendor's driver installer starts: that vendor's cards the app reads as -/// external (the eGPU kind: a USB4 or Thunderbolt router in the device's parent chain). PC 2, 7 October 2026, 16:26Z: -/// the Intel installer's display reset took the machine down while the app's own worker mined on the Arc in the Razer -/// Core X V2; the machine needed a power cycle. The other cards keep mining through the install. Each item is -/// (key, vendor, kind, enabled); a card already off has nothing to stop. +/// The cards whose worker stops before a vendor's driver installer starts: every enabled card of that vendor. PC 2, +/// 7 October 2026, 16:26Z: the Intel installer took the kernel down (bugcheck 0x3B) with the app's worker mining on the +/// Arc B580 in a Razer Core X V2; at 19:14 BST the same card read kind=discrete on 0.3.21 (no USB4 or Thunderbolt +/// router in its parent chain on that board), so a hold keyed on the external kind alone would have missed the card +/// that crashed the box. The rule since 0.3.22 (the shipper's word, 19:1x BST): the vendor's cards all stop, the other +/// vendors' cards keep mining, a card already off has nothing to stop. Each item is (key, vendor, kind, enabled). pub fn install_hold_keys<'a>(cards: impl IntoIterator, vendor: &str) -> Vec { - cards.into_iter().filter(|(_, v, kind, enabled)| *v == vendor && *kind == "external" && *enabled).map(|(key, _, _, _)| key.to_string()).collect() + cards.into_iter().filter(|(_, v, _, enabled)| *v == vendor && *enabled).map(|(key, _, _, _)| key.to_string()).collect() } #[cfg(test)] mod tests { use super::*; #[test] - fn an_install_on_an_egpu_card_stops_that_cards_worker_first_and_leaves_the_other_cards_mining() { + fn a_driver_install_stops_every_card_of_its_vendor_first_and_leaves_the_other_vendors_mining() { // PC 2, 7 October 2026: the Arc B580 in the Razer Core X V2 was mining when the Intel installer's display // reset took the machine down. The rule: the vendor's external cards stop, nothing else does. let cards = [ @@ -267,11 +268,16 @@ mod tests { ("intel::Intel UHD 770", "other", "integrated", false), ]; assert_eq!(install_hold_keys(cards, "intel"), vec!["intel:Intel(R) Arc(TM) B580 Graphics".to_string()], "the Arc in the enclosure stops; the NVIDIA cards and the iGPU keep mining"); - assert_eq!(install_hold_keys(cards, "nvidia"), vec!["nvidia:1:NVIDIA GeForce RTX 5060 Ti".to_string()], "the 5060 Ti in the enclosure stops; the 5090 inside the case keeps mining"); + assert_eq!(install_hold_keys(cards, "nvidia"), vec!["nvidia:0:NVIDIA GeForce RTX 5090".to_string(), "nvidia:1:NVIDIA GeForce RTX 5060 Ti".to_string()], "every NVIDIA card stops for the NVIDIA install, inside the case or not"); assert!(install_hold_keys(cards, "amd").is_empty(), "no AMD card: nothing stops"); - // an external card already off has nothing to stop; a discrete card of the vendor is not held + // a card already off has nothing to stop; a discrete card of the vendor IS held (PC 2, 19:14 BST: the Arc in the + // Razer Core X V2 read kind=discrete on 0.3.21, so the kind alone would have missed the card that crashed the box) let off = [("intel:Intel(R) Arc(TM) B580 Graphics", "intel", "external", false), ("intel:Intel(R) Arc(TM) A770 Graphics", "intel", "discrete", true)]; - assert!(install_hold_keys(off, "intel").is_empty()); + assert_eq!(install_hold_keys(off, "intel"), vec!["intel:Intel(R) Arc(TM) A770 Graphics".to_string()]); + // the iGPU carries vendor "other" on PC 2 (an AMD Radeon(TM) Graphics as amd:gfx1036 on another read): the install's + // vendor word decides, and a card off stays untouched + let igpu = [("amd:gfx1036", "amd", "integrated", false), ("amd:gfx1201", "amd", "discrete", true)]; + assert_eq!(install_hold_keys(igpu, "amd"), vec!["amd:gfx1201".to_string()]); } diff --git a/app/igneum-app/src/engine.rs b/app/igneum-app/src/engine.rs index 2e98ca445..2a6aa7811 100644 --- a/app/igneum-app/src/engine.rs +++ b/app/igneum-app/src/engine.rs @@ -1718,7 +1718,7 @@ impl Engine { self.shared.event("info", &format!("driver install started: {} {} ({} MB) from the vendor's server{}", vendor.to_ascii_uppercase(), e.version, (e.size + 512 * 1024) / (1024 * 1024), if dry { ", dry run" } else { "" })); // PC 2, 7 October 2026, 16:26Z: the Intel installer's display reset took the machine down while the app's own // worker mined on the Arc in the Razer Core X V2 (a power cycle). The vendor's cards the app reads as external - // (drivertable::install_hold_keys) stop before the installer starts; the other cards keep mining; each held card + // (drivertable::install_hold_keys: since 0.3.22 every card of the vendor) stop before the installer starts; the other vendors' cards keep mining; each held card // comes back as it was when the installer ends, or when the card itself returns if the install took it away. let (off, restore): (Vec, Vec) = { let st = self.st(); @@ -1727,7 +1727,7 @@ impl Engine { crate::jobrun::cards_off_choices(&keys, &live) }; if !off.is_empty() { - self.shared.event("info", &format!("driver install: the worker on {} stops for the install (a card in an external enclosure; the display reset can take the machine for a minute); the other cards keep mining", off.iter().map(|c| c.key.as_str()).collect::>().join(", "))); + self.shared.event("info", &format!("driver install: the worker on {} stops for the install (every {} card; the display resets, and a card in an external enclosure can take the machine for a minute); the other vendors' cards keep mining", off.iter().map(|c| c.key.as_str()).collect::>().join(", "), vendor.to_ascii_uppercase())); self.apply_cards(off); } self.driver_held = restore; diff --git a/app/igneum-app/ui/app.js b/app/igneum-app/ui/app.js index 8e995b7d6..c3d416d94 100644 --- a/app/igneum-app/ui/app.js +++ b/app/igneum-app/ui/app.js @@ -413,9 +413,10 @@ var View = (function () { var o = cd.driver_offer, d = (s && s.drivers) || {}; if (!o) return { kind: 'none', text: '' }; var mine = d.vendor && d.vendor === o.vendor, ext = cd.kind === 'external'; - // PC 2, 7 October 2026: a card in an external enclosure (the eGPU kind) has its worker stopped for the install, and the - // installer's display reset can take the machine; the row says both before the click and while it runs - var extRun = ext ? '. This card\u2019s worker is stopped until the installer ends; the display reset can take the machine for a minute.' : ''; + // PC 2, 7 October 2026: every card of the vendor has its worker stopped for the install (0.3.22; the Arc in the Razer + // Core X V2 read as discrete, so the kind alone missed it), and the installer's display reset can take a machine with an + // enclosure; the row says both before the click and while it runs + var extRun = '. The ' + vendorWord(o.vendor) + ' workers are stopped until the installer ends; the display may reset' + (ext ? ', and this card\u2019s enclosure can take the machine for a minute' : '') + '.'; if (mine && (d.status === 'downloading' || d.status === 'verifying' || d.status === 'installing')) { var pct = d.status === 'downloading' ? Math.round((d.progress || 0) * 100) + '%' : ''; return { kind: 'running', text: (d.status === 'downloading' ? 'Downloading the ' : d.status === 'verifying' ? 'Checking the ' : 'Installing the ') + vendorWord(o.vendor) + ' driver ' + d.version + (pct ? ' · ' + pct : ''), sub: (d.message || '') + extRun, progress: d.progress || 0, dry: !!d.dry_run, external: ext }; @@ -426,7 +427,7 @@ var View = (function () { if (o.status === 'none') return { kind: 'note', text: o.text }; if (!o.installable) return { kind: 'note', text: o.text }; var sub = 'Downloaded from ' + hostOf(o.url) + ', checked against the vendor\u2019s published hash and signature, then installed with one administrator prompt. '; - sub += ext ? 'This card\u2019s worker stops for the install and comes back after it; the other cards keep mining. The display reset can take the machine for a minute and may need a restart: save your work first.' : 'Mining continues.'; + sub += 'The worker on every ' + vendorWord(o.vendor) + ' card stops for the install and comes back after it; the other cards keep mining. The display resets during the install' + (ext ? ', and a card in an external enclosure can take the machine for a minute and may need a restart' : '') + ': save your work first.'; return { kind: 'offer', text: o.text, button: 'install', sub: sub, external: ext }; } function vendorWord(v) { return v === 'nvidia' ? 'NVIDIA' : v === 'amd' ? 'AMD' : v === 'intel' ? 'Intel Arc' : v === 'apple' ? 'Apple' : v; } @@ -1577,7 +1578,7 @@ if (typeof document !== 'undefined') (function () { else if (b.dataset.d) { var inp = b.parentNode.querySelector('input'), v = parseInt(inp.value, 10) || 1; inp.value = Math.max(1, Math.min(64, v + parseInt(b.dataset.d, 10))); sendCards('Identities set; that card’s worker restarts'); } else if (b.dataset.pin !== undefined) api('api/sweep/pin', { key: key, pinned: b.dataset.pin === '1' }).then(function () { toast(b.dataset.pin === '1' ? 'Cap pinned' : 'Default: Ember chooses the cap again'); }); else if (b.dataset.powerRetry !== undefined) api('api/power/apply', {}).then(function () { toast('Allow the administrator prompt to set the cap'); }); - else if (b.dataset.driverInstall !== undefined) api('api/drivers/install', { vendor: b.dataset.driverInstall }).then(function (r) { toast(r && r.ok === false ? (r.error || 'could not start the driver install') : 'Driver download started. Windows asks for permission once before the install. Mining continues.'); }); + else if (b.dataset.driverInstall !== undefined) api('api/drivers/install', { vendor: b.dataset.driverInstall }).then(function (r) { toast(r && r.ok === false ? (r.error || 'could not start the driver install') : 'Driver download started. Windows asks for permission once before the install; the workers on this vendor\u2019s cards stop for it and come back after.'); }); else if (b.dataset.driverRestart !== undefined) { if (window.confirm('Restart Windows now to finish the driver install? Mining stops until the app is back.')) api('api/drivers/restart', {}).then(function () { toast('Restarting in 20 seconds'); }); } else if (b.dataset.price !== undefined) { showPage('settings'); $('s-price').focus(); } else if (b.closest('[data-card-goal]') && b.dataset.goal !== undefined) { diff --git a/app/igneum-app/ui/view.test.mjs b/app/igneum-app/ui/view.test.mjs index d3b9aab46..4b9797ae4 100644 --- a/app/igneum-app/ui/view.test.mjs +++ b/app/igneum-app/ui/view.test.mjs @@ -112,25 +112,29 @@ test('driver-check: the row\'s driver strip per state (offer, running, reboot, e assert.equal(V.vendorWord('amd'), 'AMD'); }); -test('driver-check: an install offer on an eGPU card warns that its worker stops and the display reset can take the machine (PC 2, 7 October 2026)', () => { +test('driver-check: an install offer says every card of the vendor stops, and an eGPU card adds the machine warning (PC 2, 7 October 2026; every-card rule 0.3.22)', () => { const offer = { vendor: 'intel', status: 'old', installed: '32.0.101.6733', wanted: '32.0.101.9034', min_version: '32.0.101.9034', size: 932631144, url: 'https://downloadmirror.intel.com/929959/gfx_win_101.9034.exe', hash_source: 'https://www.intel.com/content/www/us/en/download/785597/', text: 'Install the Intel Arc driver 32.0.101.9034, 889 MB (the worker needs 32.0.101.9034 or newer; 32.0.101.6733 is installed)', installable: true }; const idle = { drivers: { status: 'idle', vendor: '', version: '', progress: 0, message: '', error: '', reboot_required: false, dry_run: false } }; const egpu = card({ key: 'intel:Intel(R) Arc(TM) B580 Graphics', name: 'Intel(R) Arc(TM) B580 Graphics', vendor: 'intel', kind: 'external', worker: 'OpenCL', enabled: true, state: 'mining', driver_offer: offer }); let w = V.driverWords(egpu, idle); assert.equal(w.kind, 'offer'); assert.equal(w.external, true); - assert.ok(w.sub.includes('This card\u2019s worker stops for the install and comes back after it; the other cards keep mining.'), w.sub); - assert.ok(w.sub.includes('The display reset can take the machine for a minute and may need a restart'), w.sub); - // the same card inside the case: no stop, the plain sentence + assert.ok(w.sub.includes('The worker on every Intel Arc card stops for the install and comes back after it; the other cards keep mining.'), w.sub); + assert.ok(w.sub.includes('a card in an external enclosure can take the machine for a minute and may need a restart'), w.sub); + // the same card read as inside the case (PC 2's Arc on 0.3.21 read discrete in its enclosure): the stop sentence stays, + // the enclosure warning goes const inside = card({ ...egpu, kind: 'discrete' }); w = V.driverWords(inside, idle); assert.equal(w.external, false); - assert.ok(w.sub.endsWith('Mining continues.'), w.sub); - assert.ok(!w.sub.includes('display reset')); - // while it installs on the eGPU card, the running line says the worker is stopped + assert.ok(w.sub.includes('The worker on every Intel Arc card stops for the install'), w.sub); + assert.ok(w.sub.endsWith('The display resets during the install: save your work first.'), w.sub); + assert.ok(!w.sub.includes('Mining continues.')); + // while it installs, the running line says the vendor's workers are stopped; the eGPU row adds its enclosure w = V.driverWords(egpu, { drivers: { status: 'installing', vendor: 'intel', version: '32.0.101.9034', progress: 0.7, message: 'installing INTEL 32.0.101.9034: Windows asks for permission once', dry_run: false } }); assert.equal(w.kind, 'running'); - assert.ok(w.sub.includes('This card\u2019s worker is stopped until the installer ends'), w.sub); + assert.ok(w.sub.includes('The Intel Arc workers are stopped until the installer ends; the display may reset, and this card\u2019s enclosure can take the machine'), w.sub); + w = V.driverWords(inside, { drivers: { status: 'installing', vendor: 'intel', version: '32.0.101.9034', progress: 0.7, message: 'x', dry_run: false } }); + assert.ok(w.sub.endsWith('The Intel Arc workers are stopped until the installer ends; the display may reset.'), w.sub); }); test('a card behind a USB4 or Thunderbolt router is an eGPU on the Cards page (PC 2, 7 October 2026)', () => { diff --git a/docs/plans/driver-check.md b/docs/plans/driver-check.md index b622baac8..80c522d65 100644 --- a/docs/plans/driver-check.md +++ b/docs/plans/driver-check.md @@ -41,15 +41,15 @@ Apple: none. The exit codes: Intel 14 and 1014 (the bootstrapper's wrap, measure Sizes: the installers are 889 MB to 1.0 GB each; the download lands in `/drivers/` and stays (a second click reuses it when its sha256 still matches). Time: the download at a home line of 50 Mbit/s is 2.5 to 3 minutes; the install 1 to 4 minutes; the prompt is the only wait on the user. -## 3b. A card in an external enclosure (7 October 2026, after PC 2) +## 3b. The vendor's cards stop for the install (7 October 2026, after PC 2; every card of the vendor since 0.3.22) PC 2 at 16:26Z: the Intel installer started with the app's own worker mining on the Arc B580 in the Razer Core X V2. The installer's display reset took the machine down at 16:27:14Z (every log stops together; a power cycle). The rule, on this branch: -- A driver install on a card the app reads as `external` (the eGPU kind: a USB4 or Thunderbolt router in the device's parent chain, update-return-21b) stops that card's worker before the installer starts (`drivertable::install_hold_keys`, 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 ("This card's worker stops for the install and comes back after it; the other cards keep mining. The display reset can take the machine for a minute and may need a restart: save your work first.") and while it runs ("This card's worker is stopped until the installer ends; the display reset can take the machine for a minute."). +- A driver install stops the worker on every enabled card of that vendor before the installer starts (0.3.22, the shipper's word 19:1x BST; the first cut held only the `external` kind, and at 19:14 BST PC 2's Arc in the Razer Core X V2 read `discrete` on 0.3.21, no USB4 or Thunderbolt router in its parent chain on that board, so the kind alone would have missed the card that crashed the box; the x1 gen1 link signature is not on the card state and was not added) (`drivertable::install_hold_keys`, 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 ("The worker on every Intel Arc card stops for the install and comes back after it; the other cards keep mining. The display resets during the install, and a card in an external enclosure can take the machine for a minute and may need a restart: save your work first."; the enclosure clause only on an `external` row) and while it runs ("The Intel Arc workers are stopped until the installer ends; the display may reset."). - When the installer ends the held card goes back as it was (enabled, identities, cap). A card the install took away (Windows re-enumerates an eGPU through the reset) comes back when the card does: the detection that lists it again restores it (`driver_release_held(true)`). The app never restarts the machine. -Per tier: a solo miner with one eGPU loses that card's rate for the install (3 to 8 minutes on a home line, the download dominating) and nothing else; a rig with the card inside the case sees no change; macOS and Linux have no install step. Tests: the known-failed rule test first (an install on an eGPU card with the worker still running held nothing: `an_install_on_an_egpu_card_stops_that_cards_worker_first_and_leaves_the_other_cards_mining`, red on build-2 at 17:1xZ, then green), the view test for the two sentences. Captures 13 to 16: the eGPU offer (13, 14) and the install running with the card's worker off (15, 16), mock scenarios `drivers-egpu` and `drivers-egpu-running`. +Per tier: a miner loses the installing vendor's rate for the install (PC 2: the Arc's 11 of 173 MH/s for an Intel install; a one-vendor rig loses everything for those minutes, which is what the installer does to the driver anyway) (3 to 8 minutes on a home line, the download dominating) and nothing else; a rig with the card inside the case sees no change; macOS and Linux have no install step. Tests: the known-failed rule test first (an install on an eGPU card with the worker still running held nothing: `an_install_on_an_egpu_card_stops_that_cards_worker_first_and_leaves_the_other_cards_mining`, red on build-2 at 17:1xZ, then green), the view test for the two sentences. Captures 13 to 16: the eGPU offer (13, 14) and the install running with the card's worker off (15, 16), mock scenarios `drivers-egpu` and `drivers-egpu-running`. ## 4. Not done, and what follows