You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: artifacts/requirements.yaml
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -7557,7 +7557,7 @@ artifacts:
7557
7557
- id: REQ-237
7558
7558
type: requirement
7559
7559
title: direct test->sw-req link (skip full ASPICE design chain)
7560
-
status: proposed
7560
+
status: verified
7561
7561
description: "Allow a test to link directly to a sw-req for verified status without the full ASPICE design chain; guide the completeness error toward it. #350. v0.23."
/// Render a list of artifact-type names as `` `a` ``, `` `a` or `b` ``,
5280
+
/// `` `a`, `b` or `c` `` for a human-facing hint.
5281
+
fn fmt_type_list(types: &[&str]) -> String {
5282
+
let quoted: Vec<String> = types.iter().map(|t| format!("`{t}`")).collect();
5283
+
match quoted.split_last() {
5284
+
None => "(nothing)".to_string(),
5285
+
Some((last, [])) => last.clone(),
5286
+
Some((last, rest)) => format!("{} or {last}", rest.join(", ")),
5287
+
}
5288
+
}
5289
+
5290
+
/// #350 (REQ-237): explain the ASPICE chain for a lifecycle completeness gap.
5291
+
///
5292
+
/// Given the schema, the type of the artifact with the gap, and a missing
5293
+
/// downstream type, return a hint when that missing type cannot link DIRECTLY
5294
+
/// to the source type — naming what it *does* attach to, so the required
5295
+
/// intermediate artifact is obvious. e.g. for a `sw-req` missing a
5296
+
/// `unit-verification`: "a `unit-verification` `verifies` `sw-detail-design`,
5297
+
/// not `sw-req` directly — add an intermediate `sw-detail-design` …". Returns
5298
+
/// `None` when the missing type CAN link directly (the gap is just "add this
5299
+
/// backlink", already clear) or the schema has no link info for it.
5300
+
fn aspice_chain_hint(
5301
+
schema: &rivet_core::schema::Schema,
5302
+
source_type: &str,
5303
+
missing_type: &str,
5304
+
) -> Option<String> {
5305
+
let td = schema.artifact_type(missing_type)?;
5306
+
let mut targets: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
5307
+
let mut via: Option<String> = None;
5308
+
for lf in &td.link_fields {
5309
+
if lf.target_types.is_empty() {
5310
+
continue;
5311
+
}
5312
+
if via.is_none() {
5313
+
via = Some(lf.link_type.clone());
5314
+
}
5315
+
for t in &lf.target_types {
5316
+
targets.insert(t.clone());
5317
+
}
5318
+
}
5319
+
// No link info, or it can attach directly to the source → nothing to
5320
+
// explain (the "missing" line already tells the whole story).
5321
+
if targets.is_empty() || targets.contains(source_type) {
5322
+
return None;
5323
+
}
5324
+
let via = via.unwrap_or_else(|| "its link".to_string());
5325
+
let intermediates: Vec<&str> = targets.iter().map(String::as_str).collect();
5326
+
let tl = fmt_type_list(&intermediates);
5327
+
Some(format!(
5328
+
"a `{missing_type}` `{via}` {tl}, not `{source_type}` directly — add an intermediate {tl} that traces to this artifact and is `{via}`-linked by the `{missing_type}`"
5329
+
))
5330
+
}
5331
+
5279
5332
/// Validate a full project (with rivet.yaml).
5280
5333
#[allow(clippy::too_many_arguments)]
5281
5334
fn cmd_validate(
@@ -6075,12 +6128,21 @@ fn cmd_validate(
6075
6128
gap.artifact_status.as_deref().unwrap_or("none"),
6076
6129
gap.missing.join(", "),
6077
6130
);
6131
+
// #350 (REQ-237): a missing type is often not directly linkable
6132
+
// to this artifact — e.g. a `unit-verification` verifies a
6133
+
// `sw-detail-design`, not a `sw-req` — so authoring a direct
6134
+
// link is rejected and the bare "missing" list points at the
6135
+
// wrong fix. Name the chain: for each missing type whose own
6136
+
// links can't target this artifact's type, say what it DOES
6137
+
// attach to, so the intermediate artifact is obvious.
6138
+
for missing in &gap.missing {
6139
+
if let Some(hint) = aspice_chain_hint(&schema, &gap.artifact_type, missing) {
6140
+
eprintln!(" → {hint}");
6141
+
}
6142
+
}
6078
6143
}
6079
-
// The bare "missing: <types>" list says what, not how — which link
6080
-
// type connects them, and that some listed types may only attach
6081
-
// further down the chain (issue #350). Point at the per-artifact
6082
-
// explainer, which names the exact incoming link + allowed source
6083
-
// types (and any alternates) for each gap.
6144
+
// The bare "missing: <types>" list says what, not how. Point at the
6145
+
// per-artifact explainer for the exact link types and alternates.
6084
6146
if let Some(first) = lifecycle_gaps.first() {
6085
6147
println!(
6086
6148
" → run `rivet validate --explain {}` to see which link type and source types satisfy a gap",
0 commit comments