← back to AbramsEgo
build-queue/done/18-canary-verdict-fallback.md
36 lines
# AbramsEgo task 18 — Canary board: 6 of 12 verdicts render null "—" (nested-shape parser gap)
Read SPEC.md. The canary board (SPEC panel 2/6 surface) shows a dim `—` verdict for HALF the
canaries — disk-space-canary, dw-canary-meta-watchdog, keep-alive, dw-leak-scanner,
dw-scraper-canary, dw-backup-canary — because `collectCanaries()` (`server.js:151–173`, verdict
extraction at line 163) only reads a **top-level** `verdict`/`status`, but those skills write
nested shapes. Worse: dw-scraper-canary's latest.json currently has `"fail":1` (a REAL vendor
FAIL) and dw-backup-canary has `"warn":9` — both invisible on the board AND missed by the
alert banner (`index.html:370` only counts FAIL-ish verdicts).
Observed shapes in `~/.claude/skills/<name>/data/latest.json`:
- disk-space-canary: per-unit verdicts in `units[].verdict` (worst-of wins)
- dw-scraper-canary / dw-backup-canary / dw-canary-meta-watchdog: top-level `fail` / `warn` counters
- dw-leak-scanner: `leaking` counter (`leaking:0` = clean)
- keep-alive: `dead` / `restarted` counters
- timestamps live in `ts` (disk/swap) or `at` (keep-alive), which line 164 doesn't read.
1. In `server.js` after line 163, when `verdict` is still null, derive it:
```js
if (!verdict) {
const worst = (vs) => { const V = vs.filter(Boolean).map(v => String(v).toUpperCase());
return V.some(v => /FAIL|CRIT|DOWN/.test(v)) ? 'FAIL' : V.some(v => /WARN|DEGRAD/.test(v)) ? 'WARN' : V.length ? 'PASS' : null; };
if (Array.isArray(j.units)) verdict = worst(j.units.map(u => u.verdict));
else if ((j.fail > 0) || (j.leaking > 0) || (j.dead > 0)) verdict = 'FAIL';
else if (j.warn > 0) verdict = 'WARN';
else if (j.fail === 0 || j.leaking === 0 || j.dead === 0) verdict = 'PASS';
}
```
2. Line 164: extend the scanned fallback → `j.scanned_at || j.checked_at || j.ts || j.at || null`.
Verify: `pm2 restart abramsego && sleep 35 && curl -s -u admin:'DW2024!' http://127.0.0.1:9773/api/data | node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{JSON.parse(s).canaries.canaries.forEach(c=>console.log(c.name,c.verdict))})"`
— expect zero `null` verdicts; dw-scraper-canary = FAIL (and it now appears in the ⚠ alert banner),
dw-backup-canary = WARN, disk-space-canary = WARN (Mac2 unit is at 11% free).
Commit: `git add -A && git commit -m "fix: canary board — derive verdicts for nested shapes (6/12 were null, incl a live scraper FAIL)" --author="Steve <steve@designerwallcoverings.com>"`
Local only, no deploy. Do not restyle the board.