Skip to content

fix(validate): surface skipped artifact files as Errors (REQ-062, P0)#305

Merged
avrabe merged 1 commit into
mainfrom
fix/req-062-validate-parse-errors
May 20, 2026
Merged

fix(validate): surface skipped artifact files as Errors (REQ-062, P0)#305
avrabe merged 1 commit into
mainfrom
fix/req-062-validate-parse-errors

Conversation

@avrabe
Copy link
Copy Markdown
Contributor

@avrabe avrabe commented May 20, 2026

Summary

Wave 1 / PR-A of the cross-git investigation follow-ups (#304
FEAT-135). Closes the P0 finding REQ-062.

rivet validate reported Result: PASS (0 warnings) when artifact
files failed to parse — the skip was swallowed to a stderr
log::warn! and never reached the diagnostics. A user who wrote an
artifact file with the wrong shape got a green PASS over an empty
load. The cross-git investigation called this the canonical
Cederqvist cliff; running rivet validate on rivet's own repo
reproduced it.

The F2a / F2b distinction

Not every YAML file under a generic-yaml source path is an artifact
file — artifacts/bindings.yaml, feature-model.yaml, and
variants/*.yaml legitimately live there. Promoting every skip to an
error would break rivet's own validate. New classify_skip:

Input Verdict
not valid YAML ParseError (F2a)
top-level artifacts: key (malformed list) ParseError (F2a)
top-level id and type (artifact missing the artifacts: wrapper) ParseError (F2a)
anything else NotArtifactFile (F2b — silent skip)

Non-breaking plumbing

load_artifacts's signature is unchanged (rivet-core has a semver
gate). New load_artifacts_with_skips returns (Vec<Artifact>, Vec<SkippedFile>). cmd_validate collects ParseError skips and
emits one artifact-parse-error Error diagnostic per skip — flowing
into errors, the text/JSON output, and the exit code. The validate
--format json diagnostic serializer also gained a rule field
(REQ-062's acceptance requires rule to be visible to a JSON
consumer).

Test plan

  • rivet-core: scan_skipped_files_classifies_malformed_vs_non_artifact, classify_skip_treats_corrupt_yaml_as_parse_error
  • rivet-cli: validate_surfaces_parse_error_on_malformed_artifact_file — oracle-gated: one good + one malformed artifact file → exit non-zero, result: FAIL, errors >= 1, an artifact-parse-error diagnostic naming the file; plus an F2b assertion that bindings.yaml adds no error.
  • test_dogfood_validate — the F2b regression guard. rivet's own bindings.yaml/feature-model.yaml/variants/*.yaml must not error.
  • stats_json_counts_match_validate — still green.
  • cargo build --workspace, cargo clippy --workspace --all-targets -- -D warnings (exit 0).

REQ-062 acceptance — met

Each criterion in artifacts/cross-git-investigation.yaml REQ-062's Acceptance: block is exercised by the integration test above.

Implements: REQ-004
Verifies: REQ-062
Refs: FEAT-135

🤖 Generated with Claude Code

…nly WARNs (REQ-062)

The P0 from the cross-git investigation (FEAT-135 / REQ-062). `rivet
validate` reported `Result: PASS (0 warnings)` when artifact files
failed to parse — the skip was swallowed to a stderr `log::warn!` in
`formats::generic::import_generic_directory` and never reached the
diagnostics. A user who wrote an artifact file with the wrong shape
got a green PASS over an empty load. The textbook Cederqvist cliff:
textual success over a semantically-failed operation.

── classification: F2a (error) vs F2b (legitimate skip) ──

Not every YAML file under a `generic-yaml` source path is an artifact
file. `artifacts/bindings.yaml`, `artifacts/feature-model.yaml`, and
`artifacts/variants/*.yaml` legitimately live there. Promoting every
skip to an error would make rivet's own repo fail validate. New
`classify_skip` (rivet-core/src/formats/generic.rs) re-parses a
failed file as generic YAML and decides:

  1. not valid YAML at all                 -> ParseError   (F2a)
  2. top-level mapping has an `artifacts:`  -> ParseError   (malformed list)
  3. top-level mapping has `id` AND `type`  -> ParseError   (artifact
       written without the `artifacts:` wrapper — the F2a reproducer)
  4. anything else                         -> NotArtifactFile (F2b — skip)

── plumbing: non-breaking ──

`load_artifacts`'s signature is unchanged (the repo has a semver gate
on the rivet-core public API). New `load_artifacts_with_skips` returns
`(Vec<Artifact>, Vec<SkippedFile>)`; `SkipKind`/`SkippedFile` are
re-exported from the crate root. `cmd_validate` calls it, collects the
`ParseError` skips, and pushes one `artifact-parse-error` Error
diagnostic per skip into the diagnostics vec — flowing into `errors`,
the text/JSON output, and the exit code. `NotArtifactFile` skips stay
silent.

The validate `--format json` diagnostic serializer also gained a
`rule` field (it previously emitted only `severity`/`artifact_id`/
`message`) — REQ-062's acceptance requires `rule: artifact-parse-error`
to be visible to a JSON consumer.

── tests ──

  rivet-core: scan_skipped_files_classifies_malformed_vs_non_artifact,
              classify_skip_treats_corrupt_yaml_as_parse_error
  rivet-cli:  validate_surfaces_parse_error_on_malformed_artifact_file
              — oracle-gated: one good + one malformed artifact file
              -> exit non-zero, result FAIL, errors>=1, an
              artifact-parse-error diagnostic naming the file; plus an
              F2b assertion that a bindings.yaml adds no error.

Verified green: cargo build --workspace, clippy -D warnings (exit 0),
the three tests above, and test_dogfood_validate (the F2b regression
guard — rivet's own bindings/feature-model/variant files must not
error).

Implements: REQ-004
Verifies: REQ-062
Refs: FEAT-135

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

📐 Rivet artifact delta

No artifact changes in this PR. Code-only changes (renderer, CLI wiring, tests) don't touch the artifact graph.

@avrabe avrabe merged commit 4848961 into main May 20, 2026
20 of 38 checks passed
@avrabe avrabe deleted the fix/req-062-validate-parse-errors branch May 20, 2026 17:50
@codecov
Copy link
Copy Markdown

codecov Bot commented May 20, 2026

Codecov Report

❌ Patch coverage is 78.02198% with 20 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
rivet-core/src/lib.rs 0.00% 15 Missing ⚠️
rivet-core/src/formats/generic.rs 93.42% 5 Missing ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant