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}
\
+
| Hops | Artifact | Via | Result |
"
+ ));
+ 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_id} | \
+ {link_type} {via} | \
+ {result_cell} |
",
+ hops = node.distance,
+ link_type = html_escape(&node.link_type),
+ via = html_escape(&node.via_target),
+ ));
+ }
+ html.push_str("
");
+ }
+
// 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