feat(variant): fields-per-variant MVP — Artifact overlay + resolver (#255)#285
Merged
Conversation
MVP for the variant-aware properties track (docs/design/variant-aware-properties.md): - `Artifact::fields_per_variant: BTreeMap<variant_name, BTreeMap<field, value>>` serialized as `fields-per-variant:` (kebab-case) at the artifact level. - `Artifact::fields_for_variant(Option<&str>) -> Cow<'_, BTreeMap<...>>` resolves the effective fields map for a variant. Zero-alloc `Borrowed` fallback when no overlay applies (None, unknown variant, empty overlay). Allocates only when a known variant has overrides. - `#[derive(Default)]` on `Artifact` so tests can use the struct-update pattern instead of carrying every new field forward at each call site. Wired through: - yaml_hir schema-driven parser recognizes `fields-per-variant` and unpacks the nested mapping into the typed field (does NOT fall through to the generic `fields` smuggler). - generic-yaml adapter round-trips through a typed `fields_per_variant` on `GenericArtifact`. - Resolution semantics per design doc §5.2: overlay merge — variant keys override default keys; default keys not mentioned in the variant carry through. Unknown variants silently inherit default fields (graceful degradation when variant configs aren't loaded). Tests cover: - Resolver: None / unknown / known / overlay-only-some-keys / new-keys. - Parser: nested mapping extraction populates the typed field, not the generic `fields` map; both variants present after parse. Implements: REQ-010, REQ-028, REQ-029 Refs: FEAT-001 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
📐 Rivet artifact deltaNo artifact changes in this PR. Code-only changes (renderer, CLI wiring, tests) don't touch the artifact graph. |
There was a problem hiding this comment.
⚠️ Performance Alert ⚠️
Possible performance regression was detected for benchmark 'Rivet Criterion Benchmarks'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.20.
| Benchmark suite | Current: 703f490 | Previous: 01c84a9 | Ratio |
|---|---|---|---|
traceability_matrix/1000 |
64088 ns/iter (± 412) |
44286 ns/iter (± 617) |
1.45 |
query/10000 |
119942 ns/iter (± 1116) |
94732 ns/iter (± 1317) |
1.27 |
This comment was automatically generated by workflow using github-action-benchmark.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
avrabe
added a commit
that referenced
this pull request
May 16, 2026
…290) * release(v0.10.0): variant + supplier + AI session + TCL workstream A Workspace version bump 0.9.0 → 0.10.0. Theme: audit-grade story — three orthogonal features that together move rivet from "trace your project" to "describe the boundary and defend the tool's role across it." Highlights (full notes in CHANGELOG.md): - Variant-aware properties — per-variant field values (#285, #255). - Cross-org / supplier-boundary coverage MVP (#286, #253). - AI session provenance — schema half (#289, partially #127). - Tool-qualification workstream A — typed claim + dossier (#289). - rivet stats --qualification + --qualification-mode flag (#289). - TCL/TQL numbering convention fix in dogfood STPA (#289). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(release): docs-check violations on v0.10.0 release commit Two docs-check violations on PR #290: - VersionConsistency: vscode-rivet/package.json bumped 0.9.0 → 0.10.0 (it has its own version field, not workspace-inherited). - SubcommandReferences: CHANGELOG mentioned `rivet audit` which is a Phase 2 future subcommand. Rephrased to "audit-side enforcement subcommand" so the literal `rivet audit` no longer parses as a current-cli reference. Local `rivet docs check` now passes (54 files, 0 violations). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
5 tasks
avrabe
added a commit
that referenced
this pull request
May 17, 2026
…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
added a commit
that referenced
this pull request
May 18, 2026
…287) (#298) 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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Variant-MVP (track #1 from docs/design/variant-aware-properties.md). Single-master + per-variant overlay model for artifact fields.
Artifact::fields_per_variant: BTreeMap<variant_name, BTreeMap<field, value>>serialized asfields-per-variant:.Artifact::fields_for_variant(Option<&str>) -> Cow<'_, BTreeMap<...>>— zero-allocBorrowedfallback for None / unknown / empty overlay;Ownedonly on real variant lookups.#[derive(Default)]onArtifactso test fixtures use struct-update pattern instead of carrying every new field through call sites.fields-per-variantas a typed top-level key; unpacks the nested mapping into the typed field instead of falling through to the genericfieldssmuggler.GenericYamlAdaptertyped pass-through.Resolution semantics (design doc §5.2): variant keys override default keys; unmentioned default keys carry through. Unknown variants silently inherit defaults — graceful degradation when variant configs aren't loaded.
What's not in this PR (deliberate scope cut)
artifacts/variants/*.yaml(consumed in ci(release-npm): switch to workflow_run trigger so npm publish auto-fires #261 / variant-solve work; the resolver here just takes a string).fields-per-variantinschemas/common.yaml(purely cosmetic; the parser doesn't require it and validate's unknown-field check iteratesfields, not the typed sibling).Test plan
cargo test -p rivet-core --lib— 992 pass (5 new variant tests in model::tests + 1 in yaml_hir::tests).cargo test --workspace --lib— green.cargo test -p rivet-cli— green.cargo clippy --workspace --all-targets— clean.Value::Number/Value::Stringper YAML 1.2 core schema.🤖 Generated with Claude Code