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

nordisk/edda

crates/nornir-testmatrix/.nornir/testmatrix-utfallsrum-guide.md

utfallsrum — adoption guide (how to score a function's outcome space)

A 6-step recipe to give any function first-class outcome-space coverage.

1. Identify the outcome space

List the equivalence partitions + boundary values of the function's output. Derive them from the function's OWN branches/clamps/divides + the degenerate cases of its types (the same analysis the (input boundaries → exact contract bound) table in agent-ui-test-fixing.md drives). For clamp01(x):

class contract (a correct output)
below-0 input < 0 ⇒ output == 0.0
in-range 0 ≤ input ≤ 1 ⇒ output == input
above-1 input > 1 ⇒ output == 1.0

2. Declare it

use nornir_testmatrix::utfallsrum::Outcome;

let mut o = Outcome::for_fn("clamp01")
    .class("below-0",  "input < 0  ⇒  output == 0.0")
    .class("in-range", "0 ≤ input ≤ 1  ⇒  output == input")
    .class("above-1",  "input > 1  ⇒  output == 1.0");

3. Sweep + record — one case per class, with the EXACT output

Assert the contract for a representative value of EACH class (boundaries first), and record the class with the exact asserted output as evidence:

assert_eq!(clamp01(-0.5), 0.0);  o.hit("below-0",  "clamp01(-0.5) == 0.0");
assert_eq!(clamp01(0.3),  0.3);  o.hit("in-range", "clamp01(0.3) == 0.3");
assert_eq!(clamp01(1.7),  1.0);  o.hit("above-1",  "clamp01(1.7) == 1.0");

(Want a full grid? Sweep many values per class as usual — the SCORE moves with the number of CLASSES covered, so cover every class at least once; extra values inside a class harden the assertion but don't raise the score.)

4. Score + assert it

assert_eq!(o.covered(), 1.0);          // every declared class swept
assert!(o.is_complete());
assert!(o.summary().meets_threshold(2)); // ≥2 classes — passes the gate bar

A partial sweep is honest: o.covered() == 2.0/3.0, o.unexercised() == ["above-1"].

5. Emit it into the matrix

#[cfg(feature = "testmatrix")]
o.emit("my-component");   // per-class rows + a scored `<fn>/utfallsrum` roll-up row

nornir test drains those into Aspect::Functional rows, so the warehouse carries the outcome-space coverage; release builds (feature off) strip the call to a no-op.

6. Let the gate consume it (surface coverage)

For a UI/CLI/MCP surface, map surface_key → o.summary() and feed it to the gate's covered-set computation:

use nornir_testmatrix::coverage::{covered_with_utfallsrum, rows_for_with_utfallsrum, DEFAULT_UTFALLSRUM_THRESHOLD};

let covered = covered_with_utfallsrum(&ran, &utfallsrum, DEFAULT_UTFALLSRUM_THRESHOLD);
let rows = rows_for_with_utfallsrum(run_id, ws, &surface, &covered, &allowlist, &utfallsrum, ts);
// nornir: append_surface_coverage(wh, &rows) → persisted with utfallsrum_covered.

A surface is covered only when it ran end-to-end AND swept ≥K classes — a one-value smoke stays in the gap until the rest of its outcome space is swept.

The two-axis rule (why this matters)

Every check is assert(invariant(f(input))) and has two orthogonal ways to be worthless: weak input coverage (one value) and a loose oracle (a slack threshold). utfallsrum measures the FIRST axis as a number — it cannot be faked by asserting harder on one input. Combine it with the contract-tight oracle (Axis B in agent-ui-test-fixing.md) for a check that is both broad and exact.

Reference

Worked end-to-end slice: tests/viz_utfallsrum_slice.rs (two workspaces × {Test,Bench}@{fat,thin}, clean-slate→populate→verify, recording one/many classes across the workspace pair, scoring + persisting). Design: testmatrix-utfallsrum.md.