Skip to content

feat(variant): Phase 2 — thread fields_per_variant through validate (#287)#298

Open
avrabe wants to merge 1 commit into
mainfrom
feat/287-variant-phase2
Open

feat(variant): Phase 2 — thread fields_per_variant through validate (#287)#298
avrabe wants to merge 1 commit into
mainfrom
feat/287-variant-phase2

Conversation

@avrabe
Copy link
Copy Markdown
Contributor

@avrabe avrabe commented May 17, 2026

Summary

Closes #287. v0.10.0 shipped `Artifact::fields_per_variant` + the `fields_for_variant` resolver as a building block but nothing consumed variant overlays during validate. This PR wires the resolver through the validation engine.

What ships

rivet-core/src/schema.rs — variant-aware helpers (additive):

  • `get_field_value_for_variant` resolves through `fields_for_variant` when variant is Some; zero-alloc fallback when None.
  • `Condition::matches_artifact_for_variant_with`.
  • `Requirement::check_for_variant` (RequiredFields only; RequiredLinks stays variant-flat per design §6 Phase 2 scope).

rivet-core/src/validate.rs — additive wrappers + threading:

  • New: `validate_with_variant`, `validate_with_externals_and_variant`, `validate_structural_with_variant`, `validate_structural_with_externals_and_variant`.
  • Existing `validate*` functions thin-wrap with `variant: None` — no breaking signature changes.
  • Required-fields, allowed-values, conditional-rules now read through `artifact.fields_for_variant(variant)`.

rivet-cli/src/main.rs — `--variant ` flag now actually flows through to the validate engine. Salsa doesn't yet take variant as a tracked input, so variant-active falls through to the direct path.

Tests

Both pass:

  • `conditional_rule_respects_variant_field_overlay` — rule fires on `priority==must` without variant, doesn't fire with `automotive` overlay.
  • `required_field_satisfied_by_variant_overlay` — required field `asil` provided by automotive overlay; default flavour errors, automotive passes.

Full suite: 996 lib tests + 2 new = green. Clippy clean. Format clean.

NOT in this PR

  • `rivet list --variant` / `rivet coverage --variant` (deferred, separate PRs).
  • Variant-aware s-expr validation rules (phase 9 of validate.rs).
  • Salsa-tracked variant input.
  • Cross-product multi-axis variants.
  • `when:` clause on external-anchor (variant-aware supplier scope).

Test plan

  • `cargo test --workspace --lib` — 996 pass.
  • `cargo test -p rivet-core --lib variant_field_overlay` — pass.
  • `cargo test -p rivet-core --lib required_field_satisfied` — pass.
  • `cargo clippy --workspace --all-targets` — clean.
  • `cargo fmt --check` — clean.

🤖 Generated with Claude Code

…287)

Closes #287. v0.10.0 (PR #285) shipped `Artifact::fields_per_variant`
and the `fields_for_variant(Option<&str>) -> Cow<'_, BTreeMap<...>>`
resolver as a building block but nothing consumed variant overlays
during validate. This PR wires the resolver through the validation
engine.

**rivet-core/src/schema.rs** — variant-aware helpers (additive):
- `get_field_value_for_variant(artifact, field, variant)` resolves
  through `Artifact::fields_for_variant` when `variant` is `Some(_)`,
  delegates to the existing borrowed-Cow path when `None` (zero
  allocations on the no-variant path).
- `Condition::matches_artifact_for_variant_with(...)`.
- `Requirement::check_for_variant(...)` for `RequiredFields`. Per
  design §6 Phase 2 scope ("fields only"), `RequiredLinks` stays
  variant-flat — links aren't overlayed.

**rivet-core/src/validate.rs** — additive wrappers + threading:
- New public APIs: `validate_with_variant`,
  `validate_with_externals_and_variant`,
  `validate_structural_with_variant`,
  `validate_structural_with_externals_and_variant`.
- Existing `validate*` functions thin-wrap the variant-aware version
  with `variant: None`. No breaking signature changes.
- Required-fields and allowed-values reads now go through
  `artifact.fields_for_variant(variant)` (resolved once per artifact
  as `effective_fields`).
- Conditional rules (phase 8) use
  `cond.matches_artifact_for_variant_with` +
  `rule.then.check_for_variant`.

**rivet-cli/src/main.rs cmd_validate** — threads the active variant:
- When `--variant <name>` is set, falls through to the direct path
  (salsa doesn't yet take variant as a tracked input) and calls
  `validate_with_externals_and_variant(..., active_variant)`.
- Baseline-only / no-variant / `--direct` paths preserved.

Tests added (rivet-core/src/validate.rs, mod tests):
1. `conditional_rule_respects_variant_field_overlay` — artifact has
   `fields.priority=must` and `fields-per-variant.automotive.priority=
   should`. Conditional rule fires on `priority==must`. Without
   variant: rule fires. With `Some("automotive")`: doesn't fire.
   With unknown variant: behaves like no variant (1 diag).
2. `required_field_satisfied_by_variant_overlay` — required field
   `asil` absent from `fields` but present in
   `fields-per-variant.automotive.asil=D`. Without variant: 1
   required-field error. With `Some("automotive")`: 0 errors.

NOT in this PR (deliberately):
- `rivet list --variant <name>` filtering.
- `rivet coverage --variant <name>` scoping.
- Variant-aware s-expr validation rules (phase 9 in validate.rs).
- Salsa-tracked variant input (direct-path fallback for now).
- Cross-product multi-axis variants.
- `when:` clause on external-anchor.

Implements: REQ-004, REQ-007
Refs: FEAT-001, #287

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@avrabe avrabe force-pushed the feat/287-variant-phase2 branch from 11a4d84 to 58295cb Compare May 17, 2026 06:36
@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.

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.

feat(variant): Phase 2 — variant config loading + validate/coverage wiring

1 participant