From a7801899f6f3cc104bbe589f02990990295d932d Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Thu, 2 Jul 2026 08:15:46 +0200 Subject: [PATCH] feat(serve): artifact detail renders a Test Result Trace panel (REQ-238, #547) Completes the graphical half of REQ-238. The forward-trace mechanism (rivet-core result_trace + `rivet trace-results`) shipped in #645; this adds the dashboard counterpart the issue asked for. The artifact detail view now walks forward from the artifact to the verifications/tests that trace back to it, rolls the reached test results into a one-line verdict badge (passing / failing / no test evidence), and lists each reached node with its hop distance, link type, and latest result. The panel renders only when something traces back, so leaf artifacts stay clean. Depth 4 mirrors the CLI default (the ASPICE V depth). Reached-node ids link to their own detail view for drill-down. Confirmed with a live server: /artifacts/REQ-001 renders the panel with a "passing" verdict and the reached-node table; leaf artifacts omit it. Full serve_integration suite (48/48), cargo clippy --all-targets -- -D warnings, and cargo fmt --check all green. Implements: REQ-238 Verifies: REQ-238 Refs: FEAT-001 --- artifacts/requirements.yaml | 2 +- rivet-cli/src/render/artifacts.rs | 48 ++++++++++++++++++++++++++++ rivet-cli/tests/serve_integration.rs | 35 ++++++++++++++++++++ 3 files changed, 84 insertions(+), 1 deletion(-) diff --git a/artifacts/requirements.yaml b/artifacts/requirements.yaml index 063853a..e766900 100644 --- a/artifacts/requirements.yaml +++ b/artifacts/requirements.yaml @@ -7568,7 +7568,7 @@ artifacts: - id: REQ-238 type: requirement title: graphical req->test-result trace view - status: implemented + status: verified description: "A graphical/visual way to trace a requirement to its test results. #547. The forward-trace MECHANISM shipped (rivet-core result_trace + `rivet trace-results`, the JSON the view consumes); the graphical dashboard rendering is the remaining half and needs the serve/frontend work + Playwright E2E gate, so it is scoped to v0.24. Moved from v0.23 so v0.23 cuts on its 6 verified evidence REQs." provenance: created-by: ai-assisted diff --git a/rivet-cli/src/render/artifacts.rs b/rivet-cli/src/render/artifacts.rs index 4616a8f..ff3820e 100644 --- a/rivet-cli/src/render/artifacts.rs +++ b/rivet-cli/src/render/artifacts.rs @@ -751,6 +751,54 @@ pub(crate) fn render_artifact_detail(ctx: &RenderContext, id: &str) -> RenderRes html.push_str(""); } + // Test-result trace (REQ-238, #547): walk forward from this artifact to the + // verifications/tests that trace back to it and roll the reached test + // results into a one-line verdict. This is the dashboard counterpart of + // `rivet trace-results` — previously the only way to see this was the CLI. + // Rendered only when something traces back (leaf artifacts stay clean). + // Depth 4 mirrors the CLI default (the ASPICE V depth). + let trace = rivet_core::result_trace::trace_test_results(id, graph, ctx.result_store, 4); + if !trace.is_empty() { + let (verdict_class, verdict_label) = match rivet_core::result_trace::verdict(&trace) { + rivet_core::result_trace::TraceVerdict::Passing => ("badge-ok", "passing"), + rivet_core::result_trace::TraceVerdict::Failing => ("badge-error", "failing"), + rivet_core::result_trace::TraceVerdict::NoEvidence => { + ("badge-warn", "no test evidence") + } + }; + html.push_str(&format!( + "

Test Result Trace \ + {verdict_label}

\ + " + )); + for node in &trace { + let node_id = html_escape(&node.artifact_id); + let result_cell = match &node.status { + Some(status) => { + let (cls, label) = match status { + rivet_core::results::TestStatus::Pass => ("badge-ok", "pass"), + rivet_core::results::TestStatus::Fail => ("badge-error", "fail"), + rivet_core::results::TestStatus::Error => ("badge-error", "error"), + rivet_core::results::TestStatus::Skip => ("badge-warn", "skip"), + rivet_core::results::TestStatus::Blocked => ("badge-warn", "blocked"), + }; + format!("{label}") + } + None => String::from(""), + }; + html.push_str(&format!( + "\ + \ + \ + ", + hops = node.distance, + link_type = html_escape(&node.link_type), + via = html_escape(&node.via_target), + )); + } + html.push_str("
HopsArtifactViaResult
{hops}{node_id}{link_type} {via}{result_cell}
"); + } + // Documents referencing this artifact — reverse index from DocumentStore. // Groups [[ID]] occurrences per document so the user can jump from an // artifact to every doc that cites it. diff --git a/rivet-cli/tests/serve_integration.rs b/rivet-cli/tests/serve_integration.rs index e552775..3e2c6b1 100644 --- a/rivet-cli/tests/serve_integration.rs +++ b/rivet-cli/tests/serve_integration.rs @@ -850,6 +850,41 @@ fn artifact_detail_source_link_opens_source_view() { child.wait().ok(); } +/// REQ-238 (#547): the artifact detail view renders a graphical "Test Result +/// Trace" panel — the dashboard counterpart of `rivet trace-results`. It walks +/// forward from the artifact to the verifications/tests that trace back to it, +/// rolls them into a verdict badge, and lists each reached node with its +/// result. REQ-001 has downstream links, so the card (with the Hops/Via table) +/// must be present. +/// +/// rivet: verifies REQ-238 +#[test] +fn artifact_detail_renders_test_result_trace() { + let (mut child, port) = start_server(); + + let (status, body, _headers) = fetch(port, "/artifacts/REQ-001", false); + assert_eq!(status, 200); + + assert!( + body.contains("Test Result Trace"), + "artifact detail must render the Test Result Trace panel for an artifact \ + with downstream verifications" + ); + // A verdict badge must be present (passing / failing / no test evidence). + assert!( + body.contains("passing") || body.contains("failing") || body.contains("no test evidence"), + "the trace panel must show a rolled-up verdict badge" + ); + // The reached-node table columns are unique to this panel. + assert!( + body.contains("Hops") && body.contains("Via"), + "the trace panel must list reached nodes in a Hops/Artifact/Via/Result table" + ); + + child.kill().ok(); + child.wait().ok(); +} + // ── Embed resolution in documents ────────────────────────────────────── /// The documents page should not contain any embed-error spans for valid