Console disk events and pause
Epic: Node disk self-healing — no node ever fills (Task 5761). You build child C5 of 6: the console — node disk events history, the "Paused: disk" marks, and the gha pool's paused-by-disk state. Depends on child C1 ("CP disk gate and events") for the API below.
## Original requirement (verbatim, epic)
- G3. "The operator can see it happen: usage, what was reclaimed, and why a node was paused."
- Done means (epic): "A synthetic fill past 75% triggers reclaim within one cycle and the console shows the event"
## PRD requirements (verbatim, R8)
- AC-8.1 The node's page in the console shows a disk events history: each reclaim (time, trigger scheduled/warn, bytes freed per category: CI images, build cache, runner versions, _temp, orphan volumes, plugin-cache dedup, orphan users), each pause (time, usage %), each resume. Readable: categories in plain words, sizes in GB/MB, newest first.
- AC-8.2 A disk-paused node is visibly marked in the Nodes list and on its page with the reason ("Paused: disk 92% used" style), and the mark disappears on resume.
- AC-8.4 Usage stays visible as today (disk bar + Metrics tab).
## Approach (settled by the Architect — realize, do not reopen)
Node page "Disk" events list in the Health tab under the Metrics chart (`apps/web/src/pages/NodeDetailPage.tsx`, neighbour of `components/NodeMetrics.tsx`): one line per event — time, what happened in plain words, freed per category in GB/MB, newest first, empty state. "Paused: disk 92% used" chip in the Nodes list (`Nodes.tsx` status chips) and the node header. Gha pool card shows "Paused (disk)" vs "Paused". Types in `types.ts`.
API you consume (child C1, pinned contract §3.3):
- `GET /api/nodes/{id}/disk-events?limit=50` → {"events": [{"id","at","kind" ('reclaim'|'pause'|'resume'),"trigger" ('scheduled'|'warn'|'converge'|null),"used_pct","freed_bytes": {ci_images, build_cache, stopped_containers, orphan_volumes, runner_versions, runner_temp, plugin_dedup, orphan_users},"total_freed_bytes","source_node": {"id","name"}}]}, newest first. For a co-located vnode it also returns its HOST's reclaim rows (source_node = host) — say "on host <name>" for those.
- Node list and node detail payloads: "disk_gate": {"state": "open"|"paused","since": ISO,"used_pct": number|null}.
- Gha pool payload: "paused_by": "operator"|"disk"|null.
- Each event also carries "detail": {"used_pct_before","engines":[{"user","bytes_before","bytes_after","skipped","error"}],"removed_users":[...],"remaining_bytes":{"plugin_root"}} (keys present only when reported). Show it as an expandable per-event detail: per-engine before→after sizes (and "skipped: busy"), removed runner users by name, plugin root size.
Follow docs/standards/ui-copy-standards.md (fewer words; plain category names e.g. "CI images", "Build cache", "Old runner versions", "Runner temp", "Unused volumes", "Stopped containers", "Plugin copies", "Removed runner users"). Supported viewport widths and e2e budget: apps/web/e2e/README.md and docs/standards/product-testing-requirements.md.
Credentials & reach:
- Unit + e2e tests, `./dc check full` — nothing beyond the worktree — **verified**: needs nothing.
- Console flows AC-8.1, 8.2, 8.4 on a live control plane — `jaah/debug/main/operator-login` (operator; covers dev and prod) on `https://app.dev.jaah.ai` — **verified**. (The post-release end-to-end console proof after a synthetic fill is child C6's; you verify your build in the deployed dev console against real/seeded data.)
Out of scope: Do NOT change the existing disk bar, NodeLoadMeter or Metrics tab behaviour (AC-8.4: usage stays visible as today). Do NOT add operator controls to pause/resume/trigger reclaim or tune thresholds (not asked, R22). Do not change any API/backend (C1) or node code (C2–C4). No new notification channel. No feature flag (R11).
Done means:
- AC-8.1 — unit + e2e: with seeded events, the Health tab shows a "Disk" list under the Metrics chart, newest first, one line per event: reclaim lines read as plain words with trigger ("Scheduled cleanup" / "Cleanup at 81% used" style, where the % is `detail.used_pct_before`, and the after-% shown as "→ 63%") and only non-zero categories in plain words with sizes in GB/MB (never raw bytes, never JSON, never snake_case keys); pause lines show time + usage %; resume lines show time; host-sourced rows on a vnode say they ran on the host; an empty list shows an empty state; expanding a reclaim shows per-engine before→after sizes in GB/MB, removed runner user names and the plugin root size. A literally-compliant but unreadable list (raw bytes, JSON keys, all-zero categories) FAILS this criterion.
- AC-8.2 — unit + e2e: a node with disk_gate.state = paused shows a "Paused: disk 92% used" chip in the Nodes list row and in the node header, readable at a glance without opening the node; with state = open no chip; the gha pool card says "Paused (disk)" when paused_by = disk and "Paused" when paused_by = operator.
- AC-8.4 — the existing disk bar and Metrics tab render unchanged (existing tests stay green).
- Layout readable at the supported viewport widths in apps/web/e2e/README.md (no clipped/shredded category names or chip text, no horizontal page scroll).
- Seen working in the deployed dev console (https://app.dev.jaah.ai, operator login) after merge; any broken/inconvenient UI noticed is filed as a follow-up task per the project instructions.
- `./dc check full` green; PR merged into develop and the Jaah Deploy run for it succeeded.
Outcome
Node Health→Disk history, "Paused: disk N% used" chips (roster + header), gha "Paused (disk)". PR #5021 merged; Jaah Deploy green; seen on dev. Full gate green except 2 jaahd tests already red on develop (since fixed). Deferred: 3 UI notes in report.
Artifacts
Activity
- Draft
- Ready
- Runner started
- Runner ended (done)