src/autonom/ui_values.rs
//! # ui_values — the FIELD-level value-coverage dimension (GROUP B)
//!
//! `autonom` (the completeness gate, [`super`]) answers *"is every tab / CLI /
//! MCP tool / component wired to **some** test?"* — surface granularity. It does
//! NOT answer *"which on-screen `state_json` VALUE has no robot `Assert::State`?"*
//! — the field-level, sample-space dimension the robot-UI doctrine (LAW#3 / LAW#6)
//! actually cares about. Every `surface_coverage.utfallsrum_covered` was `0.0` and
//! there were zero field-level rows, so the value dimension was empty everywhere.
//!
//! This module fills it, in three pieces (all PURE — std + serde_json — so the
//! whole thing is unit-testable by feeding a `state_json` value + an asserted-set):
//!
//! 1. **ENUMERATE** — [`value_pointers`] walks a UI's `state_json()` and yields
//! one RFC-6901 JSON pointer per data-bearing leaf (every value a surface
//! emits). Fed the live headless snapshot (the `facett_probe` drive) or any
//! emitted dump.
//! 2. **SCORE** — [`ui_value_coverage_rows`] turns each emitted pointer into a
//! `surface_coverage` [`CoverageRow`] with `kind = "ui_value"`, marking it
//! **covered** (`utfallsrum_covered = 1.0`) iff some robot scenario asserts that
//! pointer (or an ancestor of it) via `Assert::State`/`Assert::StateContains`,
//! else **missing** (`utfallsrum_covered = 0.0`). The asserted set is gathered
//! from scenario source by [`asserted_pointers_in_source`].
//! 3. **WORKLIST** — [`rank_ui_value_worklist`] is the machine-readable version of
//! the hand-written gap doc: the emitted-but-unasserted pointers, RAGNARÖK /
//! failure-path first, ranked so the per-product loop is data-driven. This is
//! the SQL `WHERE utfallsrum_covered = 0 AND kind IN (ui_value, viz_tab,
//! facett_component)` in code, over rows read back from the warehouse.
//!
//! Rows land in the SAME `surface_coverage` Iceberg table via
//! [`append_surface_coverage`](crate::warehouse::surface_coverage::append_surface_coverage)
//! — no new table, no edda changes: `CoverageRow::kind` is a free string, so the
//! `ui_value` rows ride alongside the `viz_tab` / `facett_component` rows and roll
//! up through the same reader.
use std::collections::BTreeSet;
use serde_json::Value;
use crate::warehouse::surface_coverage::{CoverageRow, Verdict};
// ─── piece 1: the field-level pointer enumerator ────────────────────────────
/// Walk a UI's `state_json()` [`Value`] and return one **RFC-6901 JSON pointer**
/// per data-bearing leaf, sorted + deduped. A leaf is a scalar (`null` / bool /
/// number / string) OR an *empty* container (`[]` / `{}`) — an empty container is
/// itself a value a surface emits (LAW#2: empty must be assertable / go RED, never
/// green-by-vacuum). Non-empty objects/arrays are recursed into; only their leaves
/// surface. Object keys and array indices are escaped per RFC 6901 (`~` → `~0`,
/// `/` → `~1`).
///
/// This is the "every value pointer a surface emits" set — the field-level twin of
/// the tab/component enumerators in [`nornir_testmatrix::discover`].
pub fn value_pointers(state: &Value) -> Vec<String> {
let mut out: Vec<String> = Vec::new();
walk_pointers(state, &mut String::new(), &mut out);
out.sort();
out.dedup();
out
}
fn walk_pointers(v: &Value, prefix: &mut String, out: &mut Vec<String>) {
match v {
Value::Object(map) if !map.is_empty() => {
for (k, child) in map {
let saved = prefix.len();
prefix.push('/');
prefix.push_str(&escape_token(k));
walk_pointers(child, prefix, out);
prefix.truncate(saved);
}
}
Value::Array(items) if !items.is_empty() => {
for (i, child) in items.iter().enumerate() {
let saved = prefix.len();
prefix.push('/');
prefix.push_str(&i.to_string());
walk_pointers(child, prefix, out);
prefix.truncate(saved);
}
}
// Scalar OR empty container → a data-bearing leaf pointer.
_ => {
// A wholly-scalar state (no keys) has the empty-string root pointer;
// keep it out of the worklist (nothing to address) — only emit named
// leaves.
if !prefix.is_empty() {
out.push(prefix.clone());
}
}
}
}
/// RFC-6901 reference-token escape: `~` → `~0`, `/` → `~1`.
fn escape_token(tok: &str) -> String {
tok.replace('~', "~0").replace('/', "~1")
}
/// A short human hint for a value's kind — stamped into the row `reason` so the
/// worklist is self-describing (`str` / `num` / `bool` / `null` / `empty[]` /
/// `empty{}`). The container hints only ever apply to the empty-container leaves.
fn value_kind_hint(v: &Value) -> &'static str {
match v {
Value::Null => "null",
Value::Bool(_) => "bool",
Value::Number(_) => "num",
Value::String(_) => "str",
Value::Array(a) => {
if a.is_empty() {
"empty[]"
} else {
"arr"
}
}
Value::Object(o) => {
if o.is_empty() {
"empty{}"
} else {
"obj"
}
}
}
}
/// The value at `pointer` in `state` (RFC-6901), for the hint. `None` if the
/// pointer does not resolve (shouldn't happen for enumerated pointers).
fn value_at<'a>(state: &'a Value, pointer: &str) -> Option<&'a Value> {
state.pointer(pointer)
}
// ─── piece 2a: the asserted-pointer extractor (scorer input) ────────────────
/// Extract every JSON pointer some robot scenario ASSERTS as a value from Rust
/// source text: the string literal following `pointer:` (the `Assert::State` /
/// `Assert::StateContains` / `ReadStateAt` field) or `state_at(` (the cmd-json
/// helper). This is how the scorer learns "does any robot assert this value" —
/// `Assert::Drew` / `LabelVisible` carry no `pointer:` and correctly contribute
/// nothing (presence-only leaves the value uncovered).
///
/// Deliberately syntactic (a literal scan, not a full parse): a pointer only
/// counts when it appears as a real string literal argument, so a mention in a
/// comment or a dynamic (non-literal) pointer is ignored — the honest, if
/// conservative, "is this value pinned by a checked-in assertion" signal.
pub fn asserted_pointers_in_source(src: &str) -> BTreeSet<String> {
const MARKERS: &[&str] = &["pointer:", "state_at("];
let bytes = src.as_bytes();
let mut out: BTreeSet<String> = BTreeSet::new();
for marker in MARKERS {
let mut from = 0usize;
while let Some(rel) = src[from..].find(marker) {
let after = from + rel + marker.len();
// Skip whitespace to the opening quote.
let mut i = after;
while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
i += 1;
}
if i < bytes.len() && bytes[i] == b'"' {
// Read the string literal (no escape handling needed — pointers
// never contain a quote).
let start = i + 1;
let mut j = start;
while j < bytes.len() && bytes[j] != b'"' {
j += 1;
}
if j <= bytes.len() {
let lit = &src[start..j];
if lit.starts_with('/') {
out.insert(lit.to_string());
}
}
}
from = after;
}
}
out
}
/// Is emitted pointer `p` covered by the `asserted` set? Covered iff the set
/// contains `p` exactly OR an **ancestor** of it (asserting a parent object value
/// asserts its children — `Assert::State("/report", …)` covers `/report/passed`).
/// A deeper assert than the emitted leaf does NOT count (asserting `/data/error`
/// does not cover an emitted `/data` = null).
pub fn pointer_is_asserted(p: &str, asserted: &BTreeSet<String>) -> bool {
if asserted.contains(p) {
return true;
}
asserted
.iter()
.any(|a| p.len() > a.len() && p.starts_with(a.as_str()) && p.as_bytes()[a.len()] == b'/')
}
// ─── piece 2b: the value-assert scorer / producer ───────────────────────────
/// **The field-level value-coverage PRODUCER.** For one surface `surface_id`
/// (e.g. a facett component name or a viz tab), enumerate every value pointer its
/// emitted `state_json` produces ([`value_pointers`]) and score each against the
/// `asserted` pointer set, yielding one `surface_coverage` [`CoverageRow`] per
/// pointer:
///
/// - `kind = "ui_value"`, `id = "<surface_id><pointer>"`, `mode = "na"`,
/// `surface_key = "ui_value:<surface_id><pointer>@na"`.
/// - **covered** (`utfallsrum_covered = 1.0`) iff [`pointer_is_asserted`], else
/// **missing** (`utfallsrum_covered = 0.0`) — the exact "emitted-but-unasserted"
/// gap the doctrine's `enumerate-emit ∩ ¬assert` grep finds by hand.
/// - `reason` carries the value-kind hint (`str` / `null` / `empty[]` …) so the
/// worklist row is self-describing.
///
/// Append the rows via
/// [`append_surface_coverage`](crate::warehouse::surface_coverage::append_surface_coverage);
/// the gate / lens reads them back like any other surface row.
pub fn ui_value_coverage_rows(
run_id: &str,
workspace: &str,
surface_id: &str,
emitted: &Value,
asserted: &BTreeSet<String>,
ts_micros: i64,
) -> Vec<CoverageRow> {
let mut rows: Vec<CoverageRow> = value_pointers(emitted)
.into_iter()
.map(|ptr| {
let covered = pointer_is_asserted(&ptr, asserted);
let id = format!("{surface_id}{ptr}");
let surface_key = format!("ui_value:{id}@na");
let hint = value_at(emitted, &ptr).map(value_kind_hint).unwrap_or("?");
CoverageRow {
run_id: run_id.to_string(),
workspace: workspace.to_string(),
surface_key,
kind: "ui_value".to_string(),
id,
mode: "na".to_string(),
verdict: (if covered {
Verdict::Covered
} else {
Verdict::Missing
})
.label()
.to_string(),
reason: hint.to_string(),
ts_micros,
utfallsrum_covered: if covered { 1.0 } else { 0.0 },
}
})
.collect();
rows.sort_by(|a, b| a.surface_key.cmp(&b.surface_key));
rows
}
// ─── piece 3: the ranked value-gap worklist (the lens) ──────────────────────
/// The `surface_coverage` kinds the value-gap worklist reads: the new field-level
/// `ui_value` rows plus the surface-level `viz_tab` / `facett_component` rows (a
/// whole unasserted pane is a value gap too).
pub const WORKLIST_KINDS: &[&str] = &["ui_value", "viz_tab", "facett_component"];
/// Tokens that mark a **RAGNARÖK / failure-path** pointer — the highest-risk
/// unasserted values (LAW#2: the empty / error branch is exactly the one a green
/// reel skips). A pointer whose id contains any of these ranks first.
const RAGNAROK_TOKENS: &[&str] = &[
"error",
"err",
"fail",
"reason",
"empty",
"missing",
"null",
"warn",
"block",
"unavailable",
"denied",
"abort",
"panic",
"crash",
"down",
];
/// **The value-gap lens** — the machine-readable version of the hand-written gap
/// doc. Filters `rows` to the worklist SQL — `utfallsrum_covered == 0 AND kind IN
/// (ui_value, viz_tab, facett_component)` — and ranks them RAGNARÖK / failure-path
/// first, then by kind, then by id, so the per-product loop burns the riskiest
/// unasserted values down first.
pub fn rank_ui_value_worklist(rows: &[CoverageRow]) -> Vec<CoverageRow> {
let mut work: Vec<CoverageRow> = rows
.iter()
.filter(|r| r.utfallsrum_covered == 0.0 && WORKLIST_KINDS.contains(&r.kind.as_str()))
.cloned()
.collect();
work.sort_by(|a, b| rank_key(a).cmp(&rank_key(b)));
work
}
/// Sort key: `(failure_rank, kind_rank, id)`. `failure_rank` 0 = RAGNARÖK path,
/// 1 = ordinary; `kind_rank` orders `ui_value` (finest) before the coarser
/// surface kinds so the field-level gaps lead.
fn rank_key(r: &CoverageRow) -> (u8, u8, String) {
let id_lc = r.id.to_lowercase();
let failure_rank = if RAGNAROK_TOKENS.iter().any(|t| id_lc.contains(t)) {
0
} else {
1
};
let kind_rank = match r.kind.as_str() {
"ui_value" => 0,
"viz_tab" => 1,
"facett_component" => 2,
_ => 3,
};
(failure_rank, kind_rank, r.id.clone())
}
/// Keep only the newest run's rows per `kind` from a heterogeneous set (the
/// `ui_value` producer and the `viz_tab`/`facett` gate persist as separate runs,
/// so "latest" must be taken per-kind, not per-table). Rows within the kept run
/// are returned as-is.
pub fn latest_run_per_kind(rows: &[CoverageRow]) -> Vec<CoverageRow> {
use std::collections::BTreeMap;
// kind → (max ts, run_id of that ts)
let mut newest: BTreeMap<String, (i64, String)> = BTreeMap::new();
for r in rows {
let e = newest
.entry(r.kind.clone())
.or_insert((i64::MIN, String::new()));
if r.ts_micros > e.0 {
*e = (r.ts_micros, r.run_id.clone());
}
}
rows.iter()
.filter(|r| {
newest
.get(&r.kind)
.map(|(_, run)| run == &r.run_id)
.unwrap_or(false)
})
.cloned()
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn value_pointers_enumerates_every_data_bearing_leaf() {
let state = json!({
"phase": "failed",
"counts": { "nodes": 3, "edges": 0 },
"rows": [ {"ok": true}, {"ok": false} ],
"data": null,
"empty_list": [],
"empty_obj": {},
});
let ptrs = value_pointers(&state);
// Scalars.
assert!(ptrs.contains(&"/phase".to_string()));
assert!(ptrs.contains(&"/counts/nodes".to_string()));
assert!(ptrs.contains(&"/counts/edges".to_string()));
assert!(
ptrs.contains(&"/data".to_string()),
"null is a data-bearing leaf"
);
// Array indices recurse to leaves.
assert!(ptrs.contains(&"/rows/0/ok".to_string()));
assert!(ptrs.contains(&"/rows/1/ok".to_string()));
// Empty containers are themselves leaves (LAW#2 — empty is assertable).
assert!(ptrs.contains(&"/empty_list".to_string()));
assert!(ptrs.contains(&"/empty_obj".to_string()));
// A non-empty container is NOT itself a pointer (only its leaves).
assert!(!ptrs.contains(&"/counts".to_string()));
}
#[test]
fn pointer_escaping_is_rfc6901() {
let state = json!({ "a/b": 1, "c~d": 2 });
let ptrs = value_pointers(&state);
assert!(ptrs.contains(&"/a~1b".to_string()), "slash escaped to ~1");
assert!(ptrs.contains(&"/c~0d".to_string()), "tilde escaped to ~0");
}
#[test]
fn asserted_pointers_scanned_from_scenario_source() {
let src = r#"
Step::new("run").assert(Assert::State { pointer: "/report/passed".into(), expected: json!(true) });
Step::new("t").assert(Assert::StateContains { pointer: "/name".into(), needle: "Heimdall".into() });
// a Drew step contributes nothing:
Step::new("d").assert(Assert::Drew);
let v = r.state_at("/clicks")?;
"#;
let got = asserted_pointers_in_source(src);
assert!(got.contains("/report/passed"));
assert!(got.contains("/name"));
assert!(got.contains("/clicks"));
assert_eq!(got.len(), 3, "Drew and comments contribute no pointer");
}
#[test]
fn ancestor_assert_covers_child_pointer() {
let mut asserted = BTreeSet::new();
asserted.insert("/report".to_string());
assert!(
pointer_is_asserted("/report/passed", &asserted),
"asserting the parent covers the child"
);
assert!(pointer_is_asserted("/report", &asserted), "exact");
// A deeper assert does NOT cover a shallower emitted leaf.
let mut deep = BTreeSet::new();
deep.insert("/data/error".to_string());
assert!(!pointer_is_asserted("/data", &deep));
// Sibling prefix must not false-match (/reporting vs /report).
assert!(!pointer_is_asserted("/reporting", &asserted));
}
/// THE RED→GREEN sensitivity proof: a known emitted-but-UNASSERTED pointer is
/// reported as a value GAP (missing, score 0, on the worklist); a known
/// ASSERTED pointer is reported as COVERED (score 1, off the worklist).
#[test]
fn scorer_flags_unasserted_pointer_as_gap_asserted_as_covered() {
// The surface emits a top-line count AND a failure reason.
let emitted = json!({
"node_count": 7,
"fail_reason": "connection refused",
});
// A robot asserts only the top-line count, never the failure reason.
let mut asserted = BTreeSet::new();
asserted.insert("/node_count".to_string());
let rows = ui_value_coverage_rows("run1", "ws", "forge", &emitted, &asserted, 42);
assert_eq!(rows.len(), 2);
let counted = rows.iter().find(|r| r.id == "forge/node_count").unwrap();
assert_eq!(
counted.verdict(),
Verdict::Covered,
"asserted pointer is covered"
);
assert_eq!(counted.utfallsrum_covered, 1.0);
let gap = rows.iter().find(|r| r.id == "forge/fail_reason").unwrap();
assert_eq!(
gap.verdict(),
Verdict::Missing,
"unasserted pointer is a value gap"
);
assert_eq!(gap.utfallsrum_covered, 0.0);
assert_eq!(gap.reason, "str", "value-kind hint recorded");
assert_eq!(gap.surface_key, "ui_value:forge/fail_reason@na");
// The worklist reports the gap and NOT the covered pointer…
let work = rank_ui_value_worklist(&rows);
assert_eq!(
work.len(),
1,
"only the unasserted pointer is on the worklist"
);
assert_eq!(work[0].id, "forge/fail_reason");
// …and once the robot ALSO asserts the failure reason, the gap closes.
asserted.insert("/fail_reason".to_string());
let rows2 = ui_value_coverage_rows("run2", "ws", "forge", &emitted, &asserted, 43);
let work2 = rank_ui_value_worklist(&rows2);
assert!(
work2.is_empty(),
"asserting the reason removes it from the worklist (GREEN)"
);
}
#[test]
fn worklist_ranks_ragnarok_failure_paths_first() {
let emitted = json!({
"zzz_total": 1, // ordinary, sorts last alphabetically
"fail_reason": "x", // RAGNARÖK
"aaa_rows": 2, // ordinary, sorts first alphabetically
"error": null, // RAGNARÖK
});
let asserted = BTreeSet::new(); // nothing asserted → all gaps
let rows = ui_value_coverage_rows("r", "ws", "s", &emitted, &asserted, 1);
let work = rank_ui_value_worklist(&rows);
// The two failure-path pointers come first (in id order), then the two
// ordinary ones.
assert_eq!(work[0].id, "s/error");
assert_eq!(work[1].id, "s/fail_reason");
assert_eq!(work[2].id, "s/aaa_rows");
assert_eq!(work[3].id, "s/zzz_total");
}
#[test]
fn worklist_includes_uncovered_viz_and_facett_surface_rows() {
// A whole unasserted pane (viz_tab / facett_component with score 0) is a
// value gap too; a covered one (score 1) is not.
let rows = vec![
CoverageRow {
run_id: "r".into(),
workspace: "ws".into(),
surface_key: "viz_tab:Warehouse@fat".into(),
kind: "viz_tab".into(),
id: "Warehouse".into(),
mode: "fat".into(),
verdict: "covered".into(),
reason: String::new(),
ts_micros: 1,
utfallsrum_covered: 0.0,
},
CoverageRow {
run_id: "r".into(),
workspace: "ws".into(),
surface_key: "facett_component:facett-helix@na".into(),
kind: "facett_component".into(),
id: "facett-helix".into(),
mode: "na".into(),
verdict: "covered".into(),
reason: String::new(),
ts_micros: 1,
utfallsrum_covered: 1.0,
},
CoverageRow {
run_id: "r".into(),
workspace: "ws".into(),
surface_key: "cli_command:doctor@na".into(),
kind: "cli_command".into(),
id: "doctor".into(),
mode: "na".into(),
verdict: "missing".into(),
reason: String::new(),
ts_micros: 1,
utfallsrum_covered: 0.0,
},
];
let work = rank_ui_value_worklist(&rows);
// The viz_tab with score 0 is on the worklist; the facett with score 1 is
// not; the cli_command (not a worklist kind) is excluded even at score 0.
let ids: Vec<&str> = work.iter().map(|r| r.id.as_str()).collect();
assert_eq!(
ids,
vec!["Warehouse"],
"only the uncovered value-bearing surface row"
);
}
/// END-TO-END through the REAL `surface_coverage` Iceberg table: land the
/// field-level `ui_value` rows for a surface that emits a top-line count (a
/// robot asserts it) plus an `error` + `fail_reason` (NO robot asserts them —
/// the exact "asserts run/green but skips the empty/error state" gap the
/// hand gap doc flags for `/forge/{error,...}` and the `boot/fail_reason`
/// emit-hole), alongside an uncovered `viz_tab` gate row. Read it all back and
/// assert the ranked worklist reproduces the doc's finding: the RAGNARÖK
/// error/reason pointers first, then the uncovered pane — and the asserted
/// count is OFF the list (GREEN for it).
#[test]
fn worklist_round_trips_through_warehouse_reproducing_gap_doc_ranking() {
use crate::warehouse::iceberg::IcebergWarehouse;
use crate::warehouse::surface_coverage::{
CoverageSelector, append_surface_coverage, query_surface_coverage,
};
use serde_json::json;
let dir = tempfile::tempdir().unwrap();
let wh = IcebergWarehouse::open(dir.path()).unwrap();
// The forge surface emits its graph counts + an error + a failure reason.
let emitted = json!({
"node_count": 12,
"edge_count": 30,
"error": null,
"fail_reason": "connection refused",
});
// The inline robot asserts ONLY the top-line counts (run/green) — never the
// error / reason (the doc's exact gap).
let mut asserted = BTreeSet::new();
asserted.insert("/node_count".to_string());
asserted.insert("/edge_count".to_string());
let ui_rows = ui_value_coverage_rows("run-uv", "ws", "forge", &emitted, &asserted, 2000);
// Plus the surface-level gate: an uncovered Warehouse viz_tab.
let gate_row = CoverageRow {
run_id: "run-gate".into(),
workspace: "ws".into(),
surface_key: "viz_tab:Warehouse@fat".into(),
kind: "viz_tab".into(),
id: "Warehouse".into(),
mode: "fat".into(),
verdict: "covered".into(),
reason: String::new(),
ts_micros: 1000,
utfallsrum_covered: 0.0,
};
wh.block_on(append_surface_coverage(&wh, &ui_rows)).unwrap();
wh.block_on(append_surface_coverage(&wh, &[gate_row]))
.unwrap();
// Read the whole workspace back, newest-run-per-kind, and rank.
let all = wh
.block_on(query_surface_coverage(
&wh,
&CoverageSelector::Workspace("ws".into()),
))
.unwrap();
let latest = latest_run_per_kind(&all);
let work = rank_ui_value_worklist(&latest);
let ids: Vec<&str> = work.iter().map(|r| r.id.as_str()).collect();
// RAGNARÖK value gaps first (id-sorted within the failure rank), then the
// uncovered pane. The asserted counts are NOT on the worklist.
assert_eq!(
ids,
vec!["forge/error", "forge/fail_reason", "Warehouse"],
"worklist reproduces the gap-doc ranking through the real warehouse",
);
assert!(
!ids.contains(&"forge/node_count") && !ids.contains(&"forge/edge_count"),
"the value-asserted pointers are GREEN (off the worklist)",
);
}
#[test]
fn latest_run_per_kind_keeps_newest_run_of_each_kind() {
let mk = |kind: &str, run: &str, ts: i64| CoverageRow {
run_id: run.into(),
workspace: "ws".into(),
surface_key: format!("{kind}:x@na"),
kind: kind.into(),
id: "x".into(),
mode: "na".into(),
verdict: "missing".into(),
reason: String::new(),
ts_micros: ts,
utfallsrum_covered: 0.0,
};
let rows = vec![
mk("ui_value", "old", 100),
mk("ui_value", "new", 200),
mk("viz_tab", "gate", 150),
];
let kept = latest_run_per_kind(&rows);
// Only the newest ui_value run survives, plus the single viz_tab run.
assert_eq!(kept.len(), 2);
assert!(
kept.iter().all(|r| r.run_id != "old"),
"the stale ui_value run is dropped"
);
assert!(kept.iter().any(|r| r.run_id == "new"));
assert!(kept.iter().any(|r| r.run_id == "gate"));
}
}