← back to AbramsEgo

build-queue/done/21-kamatera-staleness-note.md

28 lines

# AbramsEgo task 21 — Kamatera panel: surface cache staleness (silent-stale gap, task-17 class)

Read SPEC.md. `collectKamateraFleet()` (`server.js` ~lines 146–166) serves the panel entirely from
the 5-min disk cache (`data/kamatera-fleet.json`). When the SSH refresh FAILS, it silently keeps
serving the old cache — no `note`, no `stale` flag — and the frontend never renders `k.fetchedAt`,
so an hours-stale fleet reads as live. This is the exact failure class task 17 fixed for Fleet·Local
(which now shows "⚠ pm2 slow — cached snapshot"). Kamatera has the data (`fetchedAt` is in the
payload, verified live) but zero staleness surface.

Surgical, ~10 min, two touch points:

1. `server.js` — in `collectKamateraFleet()`: on SSH failure (`!r.ok` or JSON parse throw) record
   the error on a module-level `kamLastErr`; in the return, compute
   `ageMin = Math.round((Date.now()-Date.parse(c.fetchedAt))/60000)` and add
   `stale: ageMin > 12` and, when stale or `kamLastErr`, `note: 'ssh refresh failing — cached '+ageMin+'m ago'`.
   Clear `kamLastErr` on a successful refresh.
2. `public/index.html` — `$('kam-up')` render (~line 340): append the panel-level freshness chip +
   warn note, same pattern as `loc-up` one line below:
   `+(whenChip(k.fetchedAt)||'')+(k.note?' <span class="warn" style="font-size:11px" title="'+esc(k.error||'')+'">⚠ '+esc(k.note)+'</span>':'')`

Verify: `pm2 restart abramsego && sleep 3 && curl -s -u admin:'DW2024!' http://127.0.0.1:9773/api/data | node -e "const d=JSON.parse(require('fs').readFileSync(0));console.log('fetchedAt',d.kamatera.fetchedAt,'stale',d.kamatera.stale)"`
— expect fresh fetchedAt + `stale false` on a healthy run, and the 🕓 chip visible next to the
Kamatera up-count in the browser.

Commit: `git add -A && git commit -m "fix: Kamatera fleet panel surfaces cache age + ssh-failure note (no more silent stale)" --author="Steve <steve@designerwallcoverings.com>"`

Do NOT touch the local-fleet path (task 17 owns it). Local only, no deploy.