utfallsrum — outcome-space coverage (design)
utfallsrum (Swedish: outcome space / sample space) makes a function's whole OUTCOME SPACE a measurable, recordable, persistable thing. It is classic boundary-value + equivalence-partition analysis, made first-class: a test declares the set/range/relation a correct output must satisfy, partitioned into input-classes + boundaries; records which classes it exercised with the exact output per class; and the framework scores
utfallsrum_covered ∈ [0,1]. A single-value test scores low even when green — coverage now measures the SHAPE of a check, not just its pass/fail.
Why
The matrix already records, per check, did it pass? (functional_status). What it could not see is how much of the function's outcome space did the test sweep? A correct function maps a partition of its inputs to a known region of its output; one value inside one partition proves nothing the next value in that partition would. The unit that actually moves the needle is one case per equivalence class + the boundaries between them. utfallsrum records coverage at exactly that granularity and turns it into a number the warehouse + gate can carry.
This is a positive quality feature: it rewards rigorous, multi-value, contract-aware tests and surfaces single-value smoke tests for what they are — a start, not a finish.
Model (src/utfallsrum.rs)
OutcomeClass { name, contract, exercised, evidence } // one equivalence partition / boundary
Outcome { function, classes: BTreeMap<name, OutcomeClass> }
Outcome::for_fn(f)begins a function's outcome space..class(name, contract)declares one class (partition / boundary) + the contract a correct output must satisfy ("input < 0 ⇒ output == 0.0"). Declaration is idempotent + order-free (re-declaring a name keeps the first contract and any recorded hit)..hit(class, evidence)records that a test exercised the class, with the exact asserted output ("clamp01(-0.5) == 0.0") — proving the output, not merely that it ran. Ahiton an undeclared class still counts but is flagged (undeclared_hits()) so an outcome the contract under-described is visible..covered()→exercised / declared ∈ [0,1]. An empty space scores0.0(declaring nothing is never "fully covered" — guards the0/0 = 1gaming)..unexercised()is the burn-down list (declared-but-unswept classes)..is_complete()⟺covered() == 1.0.
Emit + persist
to_rows()— oneTestResultRow(aspectutfallsrum) per declared class (passiff exercised, message = contract + evidence) PLUS a roll-up<fn>/utfallsrumrow whosemetricis the score and whose status ispassiff complete (a partial space is a RED roll-up even when every exercised class is green).emit(component)— feature-testmatrix-gated: pushes the per-class + roll-up rows to the process-global functional buffer, sonornir testdrains them like any other functional row (no-op in release).summary()→UtfallsrumSummary— the compact warehouse-facing fact (function + score + counts + burn-down list), round-trips through serde.meets_threshold(K)is the gate's "≥K classes swept" rule.
Gate consumption (n-005 / AUT2)
coverage.rs gained the utfallsrum-aware covered set:
covered_with_utfallsrum(ran, utfallsrum, min_classes):
a surface is COVERED ⟺ it is in `ran` (ran end-to-end) AND its outcome-space
summary meets `min_classes` (≥K classes swept). min_classes == 0 ⇒ legacy ran ⟺ covered.
DEFAULT_UTFALLSRUM_THRESHOLD = 2. The CoverageRow gained utfallsrum_covered: f64 (the per-surface score), rows_for_with_utfallsrum stamps it, and nornir persists it to the surface_coverage Iceberg table (additive field 10, evolution-safe). The workspace dimension is expressed via discover::workspace_surface_key / SERVED_WORKSPACES so the surface is workspace × {cli,viz@{fat,thin},mcp,grpc}.
Tests (inject-assert, across utfallsrum's own outcome space)
In src/utfallsrum.rs + src/coverage.rs:
- multi-value score (
covered_is_exercised_over_declared_multi_value), - the differentiator (
single_value_scores_low_even_when_green), - the
0/0 = 1guard (empty_outcome_space_scores_zero_not_one), - idempotent declaration, undeclared-hit flagging, the scored roll-up rows,
- serde round-trip of the summary, threshold enforcement,
- the feature-ON emit→drain path,
- the gate rule (
covered_requires_both_ran_and_utfallsrum_threshold), - the persisted score (
rows_carry_utfallsrum_score_per_surface), - the workspace dimension (
workspace_dimension_distinguishes_same_tab_across_workspaces).
nornir-side: warehouse/surface_coverage.rs::utfallsrum_score_round_trips_through_iceberg, autonom.rs::utfallsrum_threshold_unflips_a_single_value_smoke_then_reflips_on_second_class, and the worked slice tests/viz_utfallsrum_slice.rs.
Versioning
nornir-testmatrix adds the utfallsrum module + CoverageRow.utfallsrum_covered (additive). Not published to crates.io — release is human-gated.