Why every measurement engine on PC 1 ran on defaults: lock_permissions cut the app folder's inheritance with a non-inheritable user:F, which left files COPIED in before the start (settings.json, machine-id, wallet.json) with no ACE at all, unreadable by their owner; the folder grant is now user:(OI)(CI)F with /T (unit test on the argument shape); a --sweep engine never asks for the firewall rule; the playbook copies the firewall flag; the settings fixture test

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-josh 2026-10-06 15:39:18 +01:00
parent 0b7f9afa7b
commit d3e207b2ed
4 changed files with 46 additions and 7 deletions

View file

@ -500,3 +500,18 @@ mod tests {
assert!(p.node_override_params.is_none());
}
}
#[cfg(test)]
mod fixture_tests {
/// `IGNEUM_TEST_SETTINGS=<path> cargo test settings_fixture`: parses a real settings.json with this crate's
/// struct and prints what it read (6 October 2026: PC 1's copied file read as defaults; this names the field).
#[test]
fn settings_fixture_parses_when_given() {
let Ok(p) = std::env::var("IGNEUM_TEST_SETTINGS") else { return };
let t = std::fs::read_to_string(&p).unwrap();
match serde_json::from_str::<super::Settings>(&t) {
Ok(s) => println!("parsed: address {} cards {} remote_jobs {} setup_done {}", s.address, s.cards.len(), s.remote_jobs, s.setup_done),
Err(e) => panic!("the crate refuses the file: {e}"),
}
}
}

View file

@ -184,7 +184,11 @@ impl Updater {
if crate::platform::start_at_login_is_on() {
let _ = crate::platform::set_start_at_login(true);
}
firewall_first_run(shared);
// a measurement engine (--sweep) uses the installed app's node and asks for nothing: the rule is the
// installed app's (the dry run of 6 October 2026 raised a second UAC prompt from here)
if !shared.runtime.sweep_only {
firewall_first_run(shared);
}
}
u.failed_versions = std::fs::read_to_string(u.failed_path()).ok().and_then(|t| serde_json::from_str::<Vec<String>>(&t).ok()).unwrap_or_default();
// the cached manifest: the rollback floor and the consensus override are known before the first check

View file

@ -166,17 +166,26 @@ pub fn lock_permissions(path: &Path, dir: bool) {
}
#[cfg(windows)]
{
let _ = dir;
let user = std::env::var("USERNAME").unwrap_or_default();
if !user.is_empty() {
let _ = quiet(&mut Command::new(tool("icacls")))
.arg(path)
.args(["/inheritance:r", "/grant:r", &format!("{user}:F")])
.output();
let _ = quiet(&mut Command::new(tool("icacls"))).arg(path).args(icacls_lock_args(dir, &user)).output();
}
}
}
/// The icacls arguments that lock a path to the user. A folder gets an INHERITABLE grant applied to everything
/// inside (`(OI)(CI)F`, `/T`): the old non-inheritable `user:F` cut the folder's inheritance and left any file that
/// was COPIED in before the engine started with no entry at all (6 October 2026, PC 1: a measurement engine's
/// settings.json, machine-id and wallet.json copied by a job read as nothing, so the engine ran on defaults with no
/// payout address; the engine's own files, written after the lock, got the creator's default DACL and hid it).
pub fn icacls_lock_args(dir: bool, user: &str) -> Vec<String> {
if dir {
vec!["/inheritance:r".into(), "/grant:r".into(), format!("{user}:(OI)(CI)F"), "/T".into()]
} else {
vec!["/inheritance:r".into(), "/grant:r".into(), format!("{user}:F")]
}
}
/// Opens a URL in the default browser (the fallback when no window host runs).
pub fn open_url(url: &str) {
#[cfg(target_os = "macos")]
@ -500,6 +509,17 @@ pub fn quiet(cmd: &mut Command) -> &mut Command {
cmd
}
#[cfg(test)]
mod lock_tests {
#[test]
fn a_locked_folder_grants_the_user_inheritably_and_covers_what_is_inside() {
let d = super::icacls_lock_args(true, "Admin");
assert_eq!(d, vec!["/inheritance:r", "/grant:r", "Admin:(OI)(CI)F", "/T"]);
let f = super::icacls_lock_args(false, "Admin");
assert_eq!(f, vec!["/inheritance:r", "/grant:r", "Admin:F"]);
}
}
#[cfg(test)]
mod tests {
#[test]

View file

@ -71,7 +71,7 @@ Say ("engine: " + $exe + " (" + $ver + ")")
Write-Output ("RESULT TUNE engine " + $ver + " sha256=" + (Get-FileHash -LiteralPath $exe -Algorithm SHA256).Hash.ToLower())
if ($ver -notmatch 'igneum-app (\d+)\.(\d+)\.(\d+)') { Write-Output 'RESULT TUNE error=version_unknown'; exit 2 }
foreach ($f in @('settings.json', 'machine-id', 'wallet.json', 'tuning.json')) {
foreach ($f in @('settings.json', 'machine-id', 'wallet.json', 'tuning.json', 'firewall-rule.json')) { # the firewall flag too: an older kit then asks nothing
$src = Join-Path $appDir $f
if (Test-Path $src) { Copy-Item -LiteralPath $src -Destination (Join-Path $sApp $f) -Force }
}