Console: a finished update shows as the OTA state ('update check: X is current'), not 'none' (PC 2 on 0.3.4); test
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
2d356dccc9
commit
797a844c49
4 changed files with 19 additions and 2 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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: (.+)$/);
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -274,7 +274,7 @@ input[type=text]{width:100%}
|
|||
<h3>${esc(mc.name)}<span class="id">${esc(mc.id8 || '')}${mc.host ? ' ' + esc(mc.host) : ''}${mc.legacy ? ' (old launcher)' : ''}</span><span class="seen ${mc.silent ? 'bad' : ''}">${silentTxt}</span></h3>
|
||||
${mc.never ? '<div class="note" style="margin-top:8px">nothing from this machine in the log intake in the last 48 h</div>' : kvs([
|
||||
['app', mc.app_version ? esc(mc.app_version) : '<small>not in the intake</small>'],
|
||||
['ota', mc.app && mc.app.ota ? esc(mc.app.ota.state) : (mc.app_version ? 'no update pending' : '?'), mc.app && mc.app.ota ? (mc.app.ota.state === 'updated' ? 'good' : 'warm') : ''],
|
||||
['ota', mc.app && mc.app.ota ? esc(mc.app.ota.state) : (mc.app_version ? 'no update pending' : '?'), mc.app && mc.app.ota ? ((mc.app.ota.state === 'updated' || mc.app.ota.state === 'current') ? 'good' : 'warm') : ''],
|
||||
['node', n.version ? esc(n.version) : '?'],
|
||||
['height (daa)', n.daa != null ? nf(n.daa) : '?', ''],
|
||||
['synced', n.at ? (n.synced ? 'yes' : (n.ibd ? 'IBD' : 'no')) : '?', n.at ? (n.synced ? 'good' : 'bad') : ''],
|
||||
|
|
@ -290,7 +290,7 @@ input[type=text]{width:100%}
|
|||
${cardTable(mc.cards)}${tele}
|
||||
${mc.app && (mc.app.ota || mc.app.power_cap || mc.app.jobs_file || (mc.app.jobs && mc.app.jobs.length) || mc.app.status) ? `<div style="margin-top:8px">
|
||||
${mc.app.status ? `<div class="ev"><span class="t">${when(mc.app.status.at)}</span><span class="k">status</span><span>${esc(mc.app.status.miner)} | node ${esc(mc.app.status.node)} | up ${esc(mc.app.status.up)}</span></div>` : ''}
|
||||
${mc.app.ota ? `<div class="ev"><span class="t">${when(mc.app.ota.at)}</span><span class="k ${mc.app.ota.state === 'updated' ? 'ok' : ''}">ota</span><span>${esc(mc.app.ota.text)}</span></div>` : ''}
|
||||
${mc.app.ota ? `<div class="ev"><span class="t">${when(mc.app.ota.at)}</span><span class="k ${(mc.app.ota.state === 'updated' || mc.app.ota.state === 'current') ? 'ok' : ''}">ota</span><span>${esc(mc.app.ota.text)}</span></div>` : ''}
|
||||
${mc.app.power_cap ? `<div class="ev"><span class="t">${when(mc.app.power_cap.at)}</span><span class="k ${mc.app.power_cap.ok ? 'ok' : 'error'}">power</span><span>${esc(mc.app.power_cap.text)}</span></div>` : ''}
|
||||
${mc.app.jobs_file ? `<div class="ev"><span class="t">${when(mc.app.jobs_file.at)}</span><span class="k">jobs</span><span>${esc(mc.app.jobs_file.text)}</span></div>` : ''}
|
||||
${(mc.app.jobs || []).map(j => `<div class="ev"><span class="t">${when(j.at)}</span><span class="k">job</span><span>${esc(j.text)}</span></div>`).join('')}
|
||||
|
|
|
|||
Loading…
Reference in a new issue