diff --git a/app/igneum-app/src/rights.rs b/app/igneum-app/src/rights.rs index 8c5ff31a9..08ad26464 100644 --- a/app/igneum-app/src/rights.rs +++ b/app/igneum-app/src/rights.rs @@ -28,6 +28,10 @@ pub const RIGHTS: &[(&str, &str)] = &[ // 0.3.24: the same task registered to run whether or not a user is logged on (S4U); a new id, so an install that holds the // interactive-only form takes it again once at the update (main, 7 October 2026, PC 1) ("power-helper-task@unattended", "the Igneum Power Helper task in its unattended form: it runs whether or not a user is logged on, so a job's or the engine's own start is accepted (src/powertask.rs)"), + // V6-06 (8 October 2026): the same task pointed at the protected copy in %ProgramData%\Igneum\helper; a new id, so an + // install that holds the LOCALAPPDATA form takes it again once (by the running helper's own reregister where the + // signed manifest names the engine hash, else one prompt at the next interactive start) + ("power-helper-task@protected", "the Igneum Power Helper task run from a protected copy of the engine that only administrators can write (src/powertask.rs)"), ("boot-task", "the Igneum Miner (boot) task: the engine starts at boot with nobody logged on (src/boot.rs)"), ("firewall-node", "the inbound firewall rule for igneumd.exe (other nodes can dial in)"), ("firewall-miner", "the inbound firewall rule for igneum-miner.exe (a pool's stratum port)"), @@ -278,7 +282,7 @@ pub fn webview2_script(install_dir: &Path) -> String { /// verb (the helper re-registers its own task from the running build's script: src/powertask.rs). 0.3.24: the task's /// unattended form is such a right, so an update by job on a PC with nobody at the desk (PC 2, 7 October 2026) takes it /// without a click, where the deferred rule alone would have left it to "the next interactive start". -pub const HELPER_TAKEABLE: &[&str] = &["power-helper-task@unattended"]; +pub const HELPER_TAKEABLE: &[&str] = &["power-helper-task@unattended", "power-helper-task@protected"]; pub fn helper_can_take(missing: &[String], task_registered: bool) -> bool { task_registered && !missing.is_empty() && missing.iter().all(|m| HELPER_TAKEABLE.contains(&m.as_str())) @@ -419,7 +423,7 @@ mod tests { assert!(RIGHTS.iter().any(|(id, _)| *id == WEBVIEW2_RIGHT)); // absent runtime: one prompt at install; present at or above the minimum: the right still has to be taken once // (the manifest records it), but the script installs nothing (its own check); a raised minimum is a new id - let old = m(&["power-helper-task", "power-helper-task@unattended", "boot-task", "firewall-node", "firewall-miner", "webview2-runtime@100.0.0.0", "driver-install-task", WSL2_RIGHT]); + let old = m(&["power-helper-task", "power-helper-task@unattended", "power-helper-task@protected", "boot-task", "firewall-node", "firewall-miner", "webview2-runtime@100.0.0.0", "driver-install-task", WSL2_RIGHT]); assert_eq!(missing(Some(&old), RIGHTS), vec![WEBVIEW2_RIGHT.to_string()], "a raised minimum is exactly one new right"); assert_eq!(prompts(Event::Install, Some(&old), RIGHTS), 1); let now = m(&RIGHTS.iter().map(|(i, _)| *i).collect::>()); @@ -467,19 +471,33 @@ mod tests { #[test] fn the_unattended_task_form_is_one_new_right_for_a_0_3_23_install() { let r0323 = m(&["power-helper-task", "boot-task", "firewall-node", "firewall-miner", WEBVIEW2_RIGHT, "driver-install-task"]); - assert_eq!(missing(Some(&r0323), RIGHTS), vec!["power-helper-task@unattended".to_string(), WSL2_RIGHT.to_string()], "2.0: the WSL feature is the second right a 0.3.23 install lacks"); + assert_eq!(missing(Some(&r0323), RIGHTS), vec!["power-helper-task@unattended".to_string(), "power-helper-task@protected".to_string(), WSL2_RIGHT.to_string()], "2.0: the WSL feature and the protected copy are the other rights a 0.3.23 install lacks"); assert_eq!(prompts(Event::Install, Some(&r0323), RIGHTS), 1); let s = script(Path::new("C:\\p\\Igneum Miner\\igneum-app.exe"), Path::new("C:\\p\\Igneum Miner"), Path::new("C:\\u\\AppData\\Local\\igneum")); assert!(s.contains("-TaskName 'Igneum Power Helper'") && s.contains("-LogonType S4U -RunLevel Highest"), "the rights step registers the unattended form: {s}"); } + /// V6-06, known-failed first (8 October 2026): a changed script re-asks nothing (rights are by id), so an installed + /// 2.0.1 PC would have kept its task on the LOCALAPPDATA exe for ever. The protected copy is a new id; a 2.0.1 + /// install lacks exactly it; the running helper may take it itself (its reregister makes the copy, under the + /// signed manifest's engine hash) so an update by job asks no click, and an interactive start asks once otherwise. + #[test] + fn the_protected_helper_copy_is_one_new_right_for_a_2_0_1_install() { + assert!(RIGHTS.iter().any(|(id, _)| *id == "power-helper-task@protected")); + let r201 = m(&["power-helper-task", "power-helper-task@unattended", "boot-task", "firewall-node", "firewall-miner", WEBVIEW2_RIGHT, "driver-install-task", WSL2_RIGHT]); + assert_eq!(missing(Some(&r201), RIGHTS), vec!["power-helper-task@protected".to_string()]); + assert_eq!(prompts(Event::Install, Some(&r201), RIGHTS), 1); + assert!(helper_can_take(&["power-helper-task@protected".to_string()], true)); + assert!(!helper_can_take(&["power-helper-task@protected".to_string()], false), "no task registered: the one prompt"); + } + /// Igneum 2.0, known-failed first (main, 8 October 2026): the node refused to start on Windows without igneum-prove-host /// and nothing installed it. The WSL feature is a right the installer's elevated step takes (no distribution, no /// window); a 0.3.26 install lacks exactly it and asks once at the update. #[test] fn the_wsl2_feature_is_a_right_the_installer_takes_once() { assert!(RIGHTS.iter().any(|(id, _)| *id == WSL2_RIGHT)); - let r0326 = m(&["power-helper-task", "power-helper-task@unattended", "boot-task", "firewall-node", "firewall-miner", WEBVIEW2_RIGHT, "driver-install-task"]); + let r0326 = m(&["power-helper-task", "power-helper-task@unattended", "power-helper-task@protected", "boot-task", "firewall-node", "firewall-miner", WEBVIEW2_RIGHT, "driver-install-task"]); assert_eq!(missing(Some(&r0326), RIGHTS), vec![WSL2_RIGHT.to_string()]); assert_eq!(prompts(Event::Install, Some(&r0326), RIGHTS), 1); let s = script(Path::new("C:\\p\\Igneum Miner\\igneum-app.exe"), Path::new("C:\\p\\Igneum Miner"), Path::new("C:\\u\\AppData\\Local\\igneum"));