Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/nornir

.nornir/live-run-warehouse.md

📡 Live Run — hydrated from the warehouse (C3)

Epic C, item C3 of THE_BIG_PLAN. The viz Live Run pane's durable source is now the warehouse release_events table, not /tmp. A live file/stream tail remains only as a fast accelerator on top of that durable baseline. This satisfies the project LAW persist-to-warehouse-stream-not-tmp.

Why this changed

Before C3 the Live Run pane (src/viz/live.rs) read a release run only from <workspace>/.nornir/logs/release-run-*.events.ndjson — a /tmp-class file the producer flushes per line. That file is a stopgap sink: it is GC'd, lost on a reboot, and invisible to any other client. So a run that had already happened — and is durably recorded in the warehouse — showed an empty Live Run pane on load. That violates the LAW: the durable record must be the warehouse, and the viz must read primarily from the warehouse.

The 🚀 Release tab already does the right thing: it reads release_events via query_release_events. C3 makes Live Run read from the same table by the same path, so the two surfaces share one durable source of truth and Live Run survives a reboot / is visible to other clients.

The two-layer model

Live Run now has a durable source and a live-tail accelerator layered on top of it:

durable (source of truth)            live-tail (fast accelerator, optional)
─────────────────────────            ──────────────────────────────────────
warehouse release_events     ──▶     local:  .nornir/logs/release-run-*.events.ndjson
(read on load + every reload)        remote: nornir-server Release.Progress stream
  1. Durable hydrate — on first draw and on every reload/workspace-switch, the pane opens the warehouse read-only (lock-tolerant, coexisting with a live server, exactly like nornir release events) and calls query_release_events(.., EventSelector::All). It picks the newest run (max ts_micros, the same "newest run on top" rule the Release tab uses), sorts its rows by seq, and converts each release_events row into the LiveEvent the pane already renders. This is the baseline the pane shows even when no /tmp log exists.

  2. Live-tail accelerator — a background thread keeps the pane moving at sub-second latency while a run is in flight:

    • local mode tails the newest release-run-*.events.ndjson (cheaper than a warehouse append per event); when a live file appears it supersedes the hydrated baseline (it is the same run, just faster).
    • remote (thin-client) mode streams the run from a nornir-server over Release.Progress — there the server's warehouse is the durable source and the stream is both the hydrate and the tail.

The tail is a fallback accelerator, never the source of truth: if the /tmp log is gone (reboot, GC) the warehouse-hydrated events still render, and a reload re-reads the warehouse.

Row → event mapping

events_from_release_rows (in src/viz/live.rs) folds a durable release_events row's op × phase × status boundary into the live event stream:

release_events row LiveEvent
op == "run", phase == start RunStart (run_id, workspace from detail)
op == "run", phase == end RunEnd (ok = status == ok)
any op, phase == start PhaseStart { repo = component, phase = op }
any op, phase == end PhaseEnd { ok, duration_ms = elapsed_ms }
phase == skip / other (not rendered)

So the warehouse table and the /tmp/SSE live tail feed one identical render path — the pane never branches on where an event came from.

What it exposes (LAW #6 — see-what-the-user-sees as data)

LiveRunState::state_json() is folded into the app's state_json under "live", carrying the rendered run + proof of its source:

"live": {
  "durable_source": "warehouse /…/warehouse",   // or "server … (release_events)"
  "hydrated_from_warehouse": true,               // the run was read from the table
  "hydrated_run_id": "run-…",
  "live_tail": "(none yet)",                      // file/stream once a live run arrives
  "run_id": "run-…", "workspace": "holger", "run_ok": null,
  "current_repo": "holger", "current_phase": "gate",
  "total_pass": 3, "total_fail": 0, "events": 4,
  "lines": ["▶ run run-… (holger)", "… znippy test start",
            "✓ znippy test (1.2s)", "… holger gate start"]
}

It also emits the $NORNIR_VIZ_TRACE stream (src/viz/trace.rs): live.hydrate IN {source: "warehouse.release_events", root} → OUT {run_id, events} (or {error}), so an external agent reads back exactly the durable run the pane hydrated.

How to verify

Files