run 4's cause: the playbook's PowerShell JSON round trip rewrote the copied settings (big integers as doubles), the engine read the file as defaults (no payout address, every card off, 96 old remote jobs run in the scratch root). Fix: the copies are verbatim and a --sweep engine applies Settings::for_measurement in memory (remote jobs, updates, proving and Power control off, not paused, tune on, every card due and unpinned); the CI check fails any playbook that rewrites settings.json through ConvertTo-Json; unit test
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
5ca03da195
commit
0b7f9afa7b
5 changed files with 57 additions and 36 deletions
|
|
@ -124,6 +124,28 @@ impl Default for Settings {
|
|||
}
|
||||
|
||||
impl Settings {
|
||||
/// What a measurement engine (`--sweep`, started by a job beside the installed app) runs with, whatever the copied
|
||||
/// file says: no remote jobs (run 4, 6 October 2026: the second engine fetched the jobs file and ran 96 old jobs
|
||||
/// inside its scratch root), no updates, no proving, not paused, the tune on, Power control off (only an engine
|
||||
/// that is itself elevated controls NVIDIA, through the probe's `direct`), every card due and unpinned. The file
|
||||
/// on disk is never changed: the playbook copies the installed app's settings verbatim (a PowerShell JSON round
|
||||
/// trip rewrote big integers as doubles and the engine read the whole file as defaults: no payout address, every
|
||||
/// card off).
|
||||
pub fn for_measurement(mut self) -> Settings {
|
||||
self.remote_jobs = false;
|
||||
self.auto_update = false;
|
||||
self.prove = false;
|
||||
self.paused = false;
|
||||
self.sweep = true;
|
||||
self.power_control = false;
|
||||
self.setup_done = true;
|
||||
for p in self.cards.values_mut() {
|
||||
p.sweep_at = 0;
|
||||
p.pinned = false;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn load(path: &Path) -> Settings {
|
||||
let mut s: Settings = std::fs::read_to_string(path).ok().and_then(|t| serde_json::from_str(&t).ok()).unwrap_or_default();
|
||||
let mut dirty = false;
|
||||
|
|
@ -374,6 +396,18 @@ mod tests {
|
|||
out
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_measurement_engine_overrides_the_copied_settings_in_memory() {
|
||||
let mut s = Settings { remote_jobs: true, auto_update: true, prove: true, paused: true, sweep: false, power_control: true, address: "0xabc".into(), ..Default::default() };
|
||||
s.cards.insert("nvidia:0:x".into(), CardPref { enabled: true, identities: 8, sweep_at: 1_791_000_000, pinned: true, power_pct: 70, ..Default::default() });
|
||||
let m = s.for_measurement();
|
||||
assert!(!m.remote_jobs && !m.auto_update && !m.prove && !m.paused && m.sweep && !m.power_control && m.setup_done);
|
||||
assert_eq!(m.address, "0xabc", "the payout address is the installed app's");
|
||||
let c = &m.cards["nvidia:0:x"];
|
||||
assert!(c.enabled && c.identities == 8 && c.power_pct == 70, "the card's choices stay");
|
||||
assert!(c.sweep_at == 0 && !c.pinned, "every card is due and unpinned");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manifest_url_round_trips_through_the_token() {
|
||||
assert_eq!(manifest_url_for_token("abc123"), "https://dl.igneum.network/dl/abc123/igneum-app-latest.json");
|
||||
|
|
|
|||
|
|
@ -101,6 +101,8 @@ fn main() {
|
|||
}
|
||||
let packaged = config::Packaged::load(&candidates).with_env_overrides();
|
||||
let settings = config::Settings::load(&runtime.app_dir.join("settings.json"));
|
||||
// a measurement engine runs with the installed app's choices and its own switches (config.rs for_measurement)
|
||||
let settings = if sweep { settings.for_measurement() } else { settings };
|
||||
|
||||
// the per-launch token: 32 hex characters from the OS
|
||||
let mut raw = [0u8; 16];
|
||||
|
|
|
|||
|
|
@ -47,7 +47,11 @@ $installedVer = (& (Join-Path $installDir 'igneum-app.exe') --version 2>&1 | Out
|
|||
$installedHasEmber = $false
|
||||
if ($installedVer -match 'igneum-app (\d+)\.(\d+)\.(\d+)') { $installedHasEmber = ([int]$Matches[1] -gt 0) -or ([int]$Matches[2] -gt 3) -or (([int]$Matches[2] -eq 3) -and ([int]$Matches[3] -ge 12)) }
|
||||
$ember = $null
|
||||
if (-not $installedHasEmber) { foreach ($cand in @((Join-Path $appDir 'jobs\ember-kit-2\igneum-app-ember.exe'), (Join-Path $appDir 'jobs\ember-kit-1\igneum-app-ember.exe'))) { if (Test-Path $cand) { $ember = $cand; break } } }
|
||||
# ember-kit-3 (the engine with Settings::for_measurement) is preferred when present, whatever the installed version;
|
||||
# older kits only when the installed app predates Ember Tune
|
||||
$k3 = Join-Path $appDir 'jobs\ember-kit-3\igneum-app-ember.exe'
|
||||
if (Test-Path $k3) { $ember = $k3 }
|
||||
elseif (-not $installedHasEmber) { foreach ($cand in @((Join-Path $appDir 'jobs\ember-kit-2\igneum-app-ember.exe'), (Join-Path $appDir 'jobs\ember-kit-1\igneum-app-ember.exe'))) { if (Test-Path $cand) { $ember = $cand; break } } }
|
||||
if ($ember) {
|
||||
Copy-Item -LiteralPath $ember -Destination (Join-Path $bin 'igneum-app.exe') -Force
|
||||
Say ("engine: the Ember build from " + $ember)
|
||||
|
|
@ -71,33 +75,18 @@ foreach ($f in @('settings.json', 'machine-id', 'wallet.json', 'tuning.json')) {
|
|||
$src = Join-Path $appDir $f
|
||||
if (Test-Path $src) { Copy-Item -LiteralPath $src -Destination (Join-Path $sApp $f) -Force }
|
||||
}
|
||||
# the second engine must not poll jobs (it would see this one), update itself, or prove; the tune is on
|
||||
# the copies are VERBATIM (run 4, 6 October 2026: a PowerShell ConvertFrom-Json | ConvertTo-Json round trip rewrote big
|
||||
# integers as doubles, the engine read the file as defaults, no payout address, every card off, 96 old jobs run in
|
||||
# the scratch root); the engine itself switches remote jobs, updates, proving and Power control off under --sweep
|
||||
# (Settings::for_measurement) and makes every card due. Only a report line is read here.
|
||||
$sj = Join-Path $sApp 'settings.json'
|
||||
if (Test-Path $sj) {
|
||||
try {
|
||||
$j = Get-Content -LiteralPath $sj -Raw | ConvertFrom-Json
|
||||
$j.remote_jobs = $false; $j.auto_update = $false; $j.prove = $false; $j.paused = $false; $j.setup_done = $true; $j.sweep = $true
|
||||
# Josh, 6 October 2026, 07:20Z: never raise an administrator prompt. The installed app's Power control is READ and
|
||||
# reported, but the copy runs with it OFF so the second engine can never start the elevated helper; the 5090 is
|
||||
# measured as it runs either way (the two-knob tune is the installed engine's job once it carries Ember Tune)
|
||||
$installedPowerControl = $false
|
||||
try { $installedPowerControl = [bool]$j.power_control } catch { }
|
||||
Write-Output ('RESULT TUNE installed_power_control=' + $installedPowerControl.ToString().ToLower() + ' (the copy runs with it off: no prompt)')
|
||||
if ($j.PSObject.Properties.Name -contains 'power_control') { $j.power_control = $false } else { $j | Add-Member -NotePropertyName power_control -NotePropertyValue $false }
|
||||
# every card is due: the stored results are cleared in the COPY only
|
||||
if ($j.cards) { foreach ($p in $j.cards.PSObject.Properties) { $p.Value.sweep_at = 0; $p.Value.pinned = $false } }
|
||||
# PowerShell 5.1's Set-Content -Encoding utf8 writes a BOM, which the engine's JSON parser refuses: the copy then
|
||||
# read as defaults (no payout address, no cards) and the miners never started (runs 1 and 2, 5 and 6 October 2026)
|
||||
[IO.File]::WriteAllText($sj, ($j | ConvertTo-Json -Depth 8), (New-Object System.Text.UTF8Encoding $false))
|
||||
} catch { Say ("settings.json: " + $_.Exception.Message) }
|
||||
$back = $null
|
||||
try { $back = Get-Content -LiteralPath $sj -Raw | ConvertFrom-Json } catch { }
|
||||
$addr = ''; if ($back) { $addr = [string]$back.address }
|
||||
$bom = (Get-Content -LiteralPath $sj -Encoding Byte -TotalCount 3 -ErrorAction SilentlyContinue) -join ','
|
||||
Write-Output ('RESULT TUNE scratch settings: address ' + $(if ($addr) { $addr.Substring(0, [Math]::Min(10, $addr.Length)) + '...' } else { 'EMPTY' }) + ', cards ' + $(if ($back -and $back.cards) { @($back.cards.PSObject.Properties).Count } else { 0 }) + ', first bytes ' + $bom)
|
||||
if (-not $addr) { Write-Output 'RESULT TUNE error=no_address reason=the_copied_settings_carry_no_payout_address'; exit 2 }
|
||||
if ($bom -eq '239,187,191') { Write-Output 'RESULT TUNE error=bom reason=settings.json_starts_with_a_BOM'; exit 2 }
|
||||
} else { Write-Output 'RESULT TUNE error=no_settings reason=the_installed_app_has_no_settings.json'; exit 2 }
|
||||
if (-not (Test-Path -LiteralPath $sj)) { Write-Output 'RESULT TUNE error=no_settings reason=the_installed_app_has_no_settings.json'; exit 2 }
|
||||
$installedPowerControl = 'unknown'; $addr = ''; $ncards = 0
|
||||
try { $back = Get-Content -LiteralPath $sj -Raw | ConvertFrom-Json; $addr = [string]$back.address; if ($back.cards) { $ncards = @($back.cards.PSObject.Properties).Count }; if ($back.PSObject.Properties.Name -contains 'power_control') { $installedPowerControl = ([bool]$back.power_control).ToString().ToLower() } } catch { }
|
||||
$bom = (Get-Content -LiteralPath $sj -Encoding Byte -TotalCount 3 -ErrorAction SilentlyContinue) -join ','
|
||||
Write-Output ('RESULT TUNE installed_power_control=' + $installedPowerControl + ' (the tune engine runs with it off: no prompt unless the job itself is elevated)')
|
||||
Write-Output ('RESULT TUNE scratch settings (verbatim copy): address ' + $(if ($addr) { $addr.Substring(0, [Math]::Min(10, $addr.Length)) + '...' } else { 'EMPTY' }) + ', cards ' + $ncards + ', first bytes ' + $bom + ', ' + (Get-Item -LiteralPath $sj).Length + ' bytes')
|
||||
if (-not $addr -and -not (Test-Path (Join-Path $sApp 'wallet.json'))) { Write-Output 'RESULT TUNE error=no_address reason=the_copied_settings_carry_no_payout_address_and_no_wallet.json'; exit 2 }
|
||||
Remove-Item -LiteralPath (Join-Path $sApp 'app.url') -Force -ErrorAction SilentlyContinue
|
||||
|
||||
# the state before, for the report
|
||||
|
|
|
|||
|
|
@ -37,15 +37,8 @@ foreach ($f in @('settings.json', 'machine-id', 'wallet.json')) {
|
|||
$src = Join-Path $appDir $f
|
||||
if (Test-Path $src) { Copy-Item -LiteralPath $src -Destination (Join-Path $sApp $f) -Force }
|
||||
}
|
||||
# the second engine must not poll jobs (it would see this one), update itself, or prove
|
||||
$sj = Join-Path $sApp 'settings.json'
|
||||
if (Test-Path $sj) {
|
||||
try {
|
||||
$j = Get-Content -LiteralPath $sj -Raw | ConvertFrom-Json
|
||||
$j.remote_jobs = $false; $j.auto_update = $false; $j.prove = $false; $j.paused = $false; $j.setup_done = $true
|
||||
[IO.File]::WriteAllText($sj, ($j | ConvertTo-Json -Depth 8), (New-Object System.Text.UTF8Encoding $false)) # no BOM: the engine's JSON parser refuses one (C35, runs 1 and 2)
|
||||
} catch { Say ("settings.json: " + $_.Exception.Message) }
|
||||
} else { Write-Output 'RESULT SWEEP error=no_settings reason=the_installed_app_has_no_settings.json'; exit 2 }
|
||||
# the copies are verbatim: the engine switches jobs, updates and proving off itself under --sweep (Settings::for_measurement, 0.3.13)
|
||||
if (-not (Test-Path (Join-Path $sApp 'settings.json'))) { Write-Output 'RESULT SWEEP error=no_settings reason=the_installed_app_has_no_settings.json'; exit 2 }
|
||||
Remove-Item -LiteralPath (Join-Path $sApp 'app.url') -Force -ErrorAction SilentlyContinue
|
||||
|
||||
# the cap state before, for the report
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ while IFS= read -r f; do
|
|||
if grep -qE 'settings\.json|\.json' "$f" && grep -vE '^\s*#' "$f" | grep -qE 'Set-Content[^\n]*-Encoding +utf8'; then
|
||||
echo "second-engine: $f writes JSON with Set-Content -Encoding utf8 (a BOM the engine refuses: the copy read as defaults, no payout address, nothing mined); use [IO.File]::WriteAllText with UTF8Encoding(\$false)"; fail=1
|
||||
fi
|
||||
if grep -vE '^\s*#' "$f" | grep -qE 'ConvertTo-Json' && grep -qE 'settings\.json' "$f"; then
|
||||
echo "second-engine: $f rewrites settings.json through ConvertTo-Json (a lossy round trip: big integers become doubles and the engine reads the whole file as defaults; run 4, 6 October 2026); copy the file verbatim, the engine applies Settings::for_measurement under --sweep"; fail=1
|
||||
fi
|
||||
if ! grep -qE "IGNEUM_APP_NO_OTA *= *'1'" "$f"; then
|
||||
echo "second-engine: $f starts an engine without IGNEUM_APP_NO_OTA = '1' (its updater would run the installer, which quits the installed app: PC 1, 5 October 2026, 22:31 UTC)"; fail=1
|
||||
fi
|
||||
|
|
|
|||
Loading…
Reference in a new issue