Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion artifacts/requirements.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7601,7 +7601,7 @@ artifacts:
- id: REQ-241
type: requirement
title: validate and validate --direct must produce identical results
status: proposed
status: verified
description: "A user reported `rivet validate` and `rivet validate --direct` returning different results on the same project (one with errors, one without). The self-link case was fixed in #627, but the reporter's broader example (salsa path vs library path divergence) may remain. Reproduce and close the remaining divergence so the two paths never disagree. #620, relates to REQ-089."
provenance:
created-by: ai-assisted
Expand Down
9 changes: 8 additions & 1 deletion rivet-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5810,8 +5810,15 @@ fn cmd_validate(
.filter(|a| !a.id.contains(':'))
.cloned()
.collect();
let lifecycle_gaps =
let mut lifecycle_gaps =
rivet_core::lifecycle::check_lifecycle_completeness(&all_artifacts, &schema, &graph);
// Sort by artifact id so the printed gap list AND the `--explain <id>`
// hint (which uses `.first()`) are deterministic regardless of the
// upstream artifact iteration order. Without this the default (salsa)
// and `--direct` paths, which collect artifacts in different orders,
// name a different example artifact in the hint — the last observable
// divergence between the two validate paths (#620, REQ-241).
lifecycle_gaps.sort_by(|a, b| a.artifact_id.cmp(&b.artifact_id));

// REQ-082: external (prefixed `prefix:ID`) artifacts are loaded into
// the store only so the consumer's `prefix:ID` cross-links resolve.
Expand Down
62 changes: 62 additions & 0 deletions rivet-cli/tests/cli_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,68 @@ fn validate_surfaces_parse_error_on_malformed_artifact_file() {
);
}

/// #620 (REQ-241): `rivet validate` (default salsa path) and
/// `rivet validate --direct` (library path) must produce IDENTICAL results
/// on the same project. A user reported them disagreeing — one flagging
/// errors the other didn't. The substantive divergence (self-links counted
/// as closing a rule) was fixed in #627; the last residual difference was
/// the `--explain <id>` hint naming a different example artifact because the
/// two paths collect artifacts in different orders. Both are now covered.
///
/// rivet: verifies REQ-241
#[test]
fn validate_and_direct_produce_identical_output() {
let tmp = tempfile::tempdir().expect("create temp dir");
let dir = tmp.path();
let dirs = dir.to_str().unwrap();
assert!(
Command::new(rivet_bin())
.args(["init", "--preset", "dev", "--dir", dirs])
.output()
.expect("init")
.status
.success()
);
// A project that exercises coverage gaps, a self-satisfying link (the
// #627 case), and multiple artifacts (so any order-dependent hint or
// list would diverge between the two paths).
std::fs::write(
dir.join("artifacts").join("reqs.yaml"),
"artifacts:\n \
- id: REQ-001\n type: requirement\n title: self-sat\n status: approved\n \
links:\n - type: satisfies\n target: REQ-001\n \
- id: REQ-002\n type: requirement\n title: second\n status: approved\n \
- id: REQ-003\n type: requirement\n title: third\n status: approved\n",
)
.unwrap();

let run = |extra: &[&str]| -> String {
let mut args = vec!["--project", dirs, "validate"];
args.extend_from_slice(extra);
let out = Command::new(rivet_bin())
.args(&args)
.output()
.expect("validate");
// Drop the `Schemas:` provenance footer (identical here, but not the
// subject of the test) and sort so ordering of independent diagnostic
// lines is not itself the assertion — the content must match.
let mut lines: Vec<String> = String::from_utf8_lossy(&out.stdout)
.lines()
.filter(|l| !l.starts_with("Schemas:"))
.map(str::to_string)
.collect();
lines.sort();
lines.join("\n")
};

let salsa = run(&[]);
let direct = run(&["--direct"]);
assert_eq!(
salsa, direct,
"`validate` and `validate --direct` must produce identical results (#620)"
);
}

/// REQ-064: a `derives-from-external` link (the cross-org variant of
/// `derives-from`, terminating at an `external-anchor`) must satisfy a
/// required `derives-from` link-field. Before the fix the cardinality
Expand Down
Loading