diff --git a/docs/bugs.md b/docs/bugs.md index 782c0ed22..3d3d2ebc1 100644 --- a/docs/bugs.md +++ b/docs/bugs.md @@ -20,6 +20,7 @@ shard run reported as exit 0, 7a7e873). | 4 Oct 2026 | the Mac card read 0.0 MH/s for a minute while its blocks were still accepted (after the stale mark shipped) | STALE_S 120 s left no margin: one missed 60 s upload (a 120 s gap at 19:45:59 UTC) plus a 30 s STATUS age plus the 10 s cache | STALE_S 180 s (one missed upload is not stale; PC 2's real case was a 1,860 s gap) | test: 150 s is fresh, 240 s stale | | 4 Oct 2026 | a machine stopped on purpose (Sam's Mac, 14:47 UTC) read "silent 4h" on the console, the same as a crash or a lost network | the app logs `quit: stopping the miners, then the node` and `stopped` and uploads once more before it exits (so it does report), but the console never read those lines | 4d208c9: `parseAppTail` (now in `relay/lib/parse.mjs`) sets `stopped {at, reason quit\|update}` when a quit or OTA-install line has no status line after it; the card says "stopped (quit) N ago" with a grey stripe, `tools/console.mjs` says STOPPED | unit test: quit, update, running, and a quit followed by a later status line; live: five running machines show no false stop; a live stopped case is pending the next real quit | | 4 Oct 2026 | `publish-manifest.sh --deploy` called the live manifest "verified" after one signature check: any validly signed manifest, including the previous version still at the edge, passed; a failed deploy was hidden by `\|\| true` | one-shot check, signature only, no byte compare | `verify_live_manifest`: up to `--tries` checks 5 s apart, byte-identical to the folder's file, then the signature, each failure named; `--verify-only` runs just the check; a failed deploy stops first (the ship tool's own verify step already compared bytes and version, so a cut through `tools/ship-app.mjs` was covered; a hand publish was not) | finished: `--verify-only --tries 2` against the live 0.3.3 (try 1); failed: a local server with an altered copy ("differs", both stamps named) and a closed port ("is not reachable") | +| 4 Oct 2026 | PC 2 came back on 0.3.4 (the canary) and its card's OTA state read "none" | the OTA state parsed only `OTA: …` lines, and the install lines sit in the previous run's log file; the new run only says `update check: 0.3.4 is current (manifest 0.3.4)` | that line is the OTA state (`current`, coloured as good) unless a newer `OTA:` line follows | test: current alone, a newer OTA line wins, an older one does not; live card after the deploy | ## Open diff --git a/relay/lib/parse.mjs b/relay/lib/parse.mjs index 9565ca731..2f605176c 100644 --- a/relay/lib/parse.mjs +++ b/relay/lib/parse.mjs @@ -81,6 +81,10 @@ export function parseAppTail(tail) { // OTA: the newest line decides the state shown on the card const ota = lastMatch(lines, /^\d+(?:\.\d+)? (?:\[\w+\] )?(OTA: .+)$/); if (ota) { const t = ota[1]; a.ota = { text: t.slice(0, 220), at: stampOf(ota[0]), state: /waiting for administrator approval|approval not given/.test(t) ? 'waiting for approval' : /deferred/.test(t) ? 'deferred' : /updated to|installed|applied/.test(t) ? 'updated' : /download/.test(t) ? 'downloading' : 'note' }; } + // After an update the new run's log has no "OTA:" line, only the check that found itself current (4 October 2026, + // 20:33: PC 2 came back on 0.3.4 and its card said nothing about it); that check is the OTA state until a newer OTA line + const current = lastMatch(lines, /^(\d+)(?:\.\d+)? update check: (\S+) is current \(manifest (\S+)\)$/); + if (current && (!ota || Number(current[1]) >= Number((/^(\d+)/.exec(ota[0]) || [])[1]))) a.ota = { text: `update check: ${current[2]} is current (manifest ${current[3]})`, at: stampOf(current[0]), state: 'current', version: current[2] }; const pc = lastMatch(lines, /^\d+(?:\.\d+)? (?:\[\w+\] )?(?:GPU )?(power cap.*)$/i); if (pc) a.power_cap = { text: pc[1].slice(0, 220), at: stampOf(pc[0]), ok: !/NOT applied|nothing to read back/.test(pc[1]) }; const jf = lastMatch(lines, /^\d+(?:\.\d+)? jobs: (.+)$/); diff --git a/relay/test/parse.test.mjs b/relay/test/parse.test.mjs index 991e0a774..326cae60d 100644 --- a/relay/test/parse.test.mjs +++ b/relay/test/parse.test.mjs @@ -63,3 +63,15 @@ test('app tail: a clean quit after the last status line is "stopped (quit)", an const resumed = quit + '\n1791126800 status: accepted 11 blocks (1 this run), 18.00 MH/s, mining | node 120 blocks, 4 peers, synced | up 00:01:00'; assert.equal(parseAppTail(resumed).stopped, undefined); }); + +test('app tail: after an update the "update check: X is current" line is the OTA state; an OTA line that is newer wins', async () => { + const { parseAppTail } = await import('../lib/parse.mjs'); + const after = ['1791146000 IGNEUM-APP version=0.3.4 machine=1ccfe586 platform=windows node=igneumd/2.1.0', '1791146426 update check: 0.3.4 is current (manifest 0.3.4)', + '1791146430 status: accepted 45 blocks (45 this run), 122.00 MH/s, mining | node 44306 blocks, 2 peers, synced | up 00:08:00'].join('\n'); + const a = parseAppTail(after); + assert.deepEqual([a.ota.state, a.ota.version, a.ota.at], ['current', '0.3.4', new Date(1791146426000).toISOString()]); + const newer = after + '\n1791146500 OTA: 0.3.5 downloaded, waiting for this machine\'s minute'; + assert.notEqual(parseAppTail(newer).ota.state, 'current'); + const older = '1791146400 OTA: 0.3.4 installed\n' + after; + assert.equal(parseAppTail(older).ota.state, 'current'); +}); diff --git a/relay/ui.html b/relay/ui.html index 46e971f4e..6946df10b 100644 --- a/relay/ui.html +++ b/relay/ui.html @@ -274,7 +274,7 @@ input[type=text]{width:100%}