Saga — integration guide (app side)
Audience: an app engineer (e.g. on Njord). Goal: record the user's navigation in your app so the nornir test matrix can later replay it as a robot test — for bug-finding and regression.
The matrix already records and replays Saga sessions. This guide is only what you do on the app side to feed it. Works for egui apps and web apps (Leptos + Axum).
What the matrix needs from you
One ordered stream of SagaEvents per user session, written to the nornir warehouse table saga_events. Navigation is the primary event. Each event carries the route + component + action + a state_json snapshot of what the user saw. That's enough for the matrix to deterministically replay the path.
Step 1 — turn capture on
Depend on nornir-saga and enable the saga feature in the build where you want capture (it compiles to nothing when off):
nornir-saga = { version = "0.1", features = ["saga"] }
Step 2 — open a session once
let ctx = nornir_saga::SessionCtx::new(
&session_id, // your per-user/session id
&user, // user id (or "anon")
"njord", // app name
env!("CARGO_PKG_VERSION"),
git_sha, // &str — the commit you shipped (see note below)
);
git_sha is a plain &str, not read from any environment variable — you pass whatever commit your build shipped. Most builds get it from a build script (e.g. option_env!("VERGEN_GIT_SHA").unwrap_or("unknown")) or bake it in at release; if you don't track a sha, pass "unknown". Saga never reads GIT_SHA for you.
Step 3 — record navigation at the right boundary
Emit a nav on every route/view change and a click on every meaningful activation, with a small JSON snapshot of the visible state:
nornir_saga::record(
nornir_saga::SagaEvent::nav(route, component, action, &state_json).in_session(&ctx)
);
egui app
Tap it at your navigation boundary (the facett nav/Navigable seam): on view switch → nav(...); on button/row activation → click(...).
web app (Leptos + Axum)
- Client (Leptos): subscribe to the router; on each route change →
nav(...)with the new path asroute; on tracked UI actions →click(...). - Server (Axum): add a middleware layer that records the request route + outcome (and
error(...)on failures) so server-side steps are in the same stream.
Keep state_json small and PII-free — it is what replay asserts against.
Step 4 — flush to the warehouse
On a cadence (timer / page-unload / request end), drain and write the batch:
let batch = nornir_saga::drain_saga_events();
sink.write(&batch)?; // SagaSink::write → nornir warehouse `saga_events`
sink is any SagaSink implementor. Saga ships JsonFileSink (append events to a file — handy for local capture and tests) and NullSink (drop them); nornir provides the warehouse-backed IcebergSagaSink over the saga_events table.
That's it. Once the events are in saga_events, nornir replays the session and emits a standing robot test (egui → kittest; web app → WebDriver against the live Leptos app). A failing replay is a RED matrix row that names the exact step.
Checklist
sagafeature on in the capture build- one
SessionCtxper session nav(...)on every route/view changeclick(...)on meaningful activationserror(...)on failures (server + client)- small, PII-free
state_json - periodic
drain_saga_events()→ warehouse