feat(variant): Phase 2 — config loading + validate/coverage wiring (#287)#291
Open
avrabe wants to merge 3 commits into
Open
feat(variant): Phase 2 — config loading + validate/coverage wiring (#287)#291avrabe wants to merge 3 commits into
avrabe wants to merge 3 commits into
Conversation
Add `feature_model::load_variant_configs_from_dir` so callers can walk `artifacts/variants/*.yaml`, deserialize each entry as a `VariantConfig`, and feed the resulting list to validate / coverage / list / query. This is the loader-side primitive for issue #287. It: - ignores non-yaml files, sorts results by file name for reproducible output across platforms, - returns an empty list (not an error) for a missing directory so callers can use it unconditionally, - rejects duplicate `name:` keys across files so downstream consumers never have to guess which variant a name refers to. Tests cover all four behaviours (missing dir, sorted load, duplicate rejection, parse-error surfacing). Implements: REQ-007
Pure rustfmt result — no functional changes. Tightens spacing on multi-arg `std::fs::write` calls and a few `assert!` macros. Was generated by running `cargo fmt --all` after the prior commit. Trace: skip
Implements issue #287 Phase 2 acceptance criteria 1-3: 1. Variant configs in `artifacts/variants/*.yaml` are loaded on every command invocation (via the loader added in the previous commit). 2. `--variant <NAME_OR_PATH>` is now accepted by `rivet validate`, `rivet coverage`, `rivet list`, and `rivet query`. The argument resolves first as a filesystem path, then as a bare name against `<project>/artifacts/variants/<NAME>.yaml`; a bad name errors with the list of available variants. 3. Per-variant field overlays are now validated. - `validate_variants` (new) does two passes: * cross-check each `fields-per-variant:` key against the project's known-variants set (declared configs + features) — warning by default, error under the new `--strict-variants` flag, matching the `Variant key 'foo' ...` error class spelled out in `docs/design/variant-aware-properties.md` §5.6. * type-check every variant overlay's merged view against the same required-field / allowed-values rules as the default view, emitting diagnostics like `field 'X' has value 'V' (variant: industrial), allowed: [...]`. - Per design doc §5.5 the default view is still validated by the existing pipeline; the overlay layer is purely additive. CLI surface: - `rivet validate --variant industrial` validates default + overlay. - `rivet validate --strict-variants` promotes unknown-key warnings to errors (CI hygiene). - `rivet list --variant industrial --format json` emits each artifact's merged `fields:` plus a top-level `"variant": "..."`. - `rivet query --sexpr '(= max-temp-c "100")' --variant industrial` filters against the merged view, so an overlay value satisfies the filter where the default would not. - `rivet coverage --variant industrial` stamps the active variant in both text and JSON output (delegated-chain scoping deferred — issue #287 acceptance criterion 4). Tests cover every new behaviour: - 4 lib tests for `load_variant_configs_from_dir` (in prior commit), - 7 lib tests for `validate_variants` (unknown key warning, strict promotion, allowed-values failure on overlay, clean pass, required field missing in merged view, empty-overlay fast path, known set accepts features), - 8 CLI integration tests for the end-to-end `--variant` flag. Implements: REQ-004, REQ-007 Refs: FEAT-001
📐 Rivet artifact deltaNo artifact changes in this PR. Code-only changes (renderer, CLI wiring, tests) don't touch the artifact graph. |
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.
Closes #287.
Phase 2 of the variant-aware-properties work (#287). Phase 1 (#285)
landed
fields_per_variant+fields_for_variantonArtifact; nothingin the rest of the codebase consumed it. This PR wires the rest of the
flow per
docs/design/variant-aware-properties.md§5.5.Scope (issue #287 acceptance criteria)
1. Load variant configs from
artifacts/variants/*.yaml— DONErivet_core::feature_model::load_variant_configs_from_dir(path).*.yaml/*.ymlin the directory, sorts deterministically byfilename, returns
Vec<VariantConfig>.Ok(vec[])so callers can use it unconditionally.name:across files → error (auditor-friendly).2.
--variant <NAME>on validate / coverage / list / query — DONE<project>/artifacts/variants/<NAME>.yaml. Bad name errors with thelist of available variants in the helpful-error pattern.
rivet validate --variant industrialvalidates the default view ANDthe overlaid merged view (design doc §5.5: "both must pass").
rivet list --variant industrial --format jsonemits each artifact'smerged
fields:map plus a top-level"variant": "...".rivet query --sexpr '(= max-temp-c "100")' --variant industrialrewrites each artifact's
fields:to the merged view before thes-expression evaluator runs, so a filter that requires the overlay
value matches under
--variantand not without.rivet coverage --variant industrialstamps the active variant namein text header and JSON
"variant"field.3. Validate honours per-variant overlays — DONE
rivet_core::validate::validate_variants(...). Pure post-pass ontop of the existing default-view validator.
fields_per_variantmustappear in the project's known-variant set (configs ∪ feature names).
Warning by default, error under new
--strict-variantsflag.Message matches the design doc literally:
through the same required-fields / allowed-values rules as the
default view. Diagnostics name the variant explicitly, e.g.
field 'max-temp-c' has value '999' (variant: consumer), allowed: ["70", "80", "100"]— matches the design doc's second error class.--strict-variantsis a separate flag from--strict-cited-sourcesso each CI gate can be tightened independently.
failure on overlay, clean pass, required-field-in-merged-view check,
empty-overlay fast path, and feature-name-as-key acceptance.
4. Coverage scopes counts by active variant — DEFERRED
The
--variantflag on coverage currently stamps the active variant inthe output (text + JSON) so coverage runs are variant-tagged for the
auditor. Actually rewriting delegated chains by the variant's effective
feature set requires touching
compute_coverageand the linkgraphwalk; that's a self-contained follow-up rather than a 2-line addition,
and the brief explicitly marks items 4 and 5 as lower priority than
1-3. Tracked as a follow-up issue against this work.
5. Serve / dashboard variant selector — DEFERRED
The dashboard renderer changes (variant selector at the top, override
marker per cell, "show all variants" toggle) are non-trivial UI work
and intentionally postponed per the brief's priority guidance. The
overlay data the dashboard needs is fully available via
Artifact:: fields_for_variant, so a follow-up PR can land the renderer withoutneeding to revisit the core.
How to read the diff
feat(variant): load variant configs from a directory):the loader function + 4 unit tests. Standalone, no public-API
changes elsewhere.
style(variant): apply rustfmt): purecargo fmtresulton commit 1.
Trace: skipbecause no source-of-truth content moved.feat(validate): --variant flag and per-variant overlay validation): the validator + every CLI surface change + 7 new libtests + 8 new CLI integration tests in
rivet-cli/tests/variant_phase2.rs.Test plan
cargo build --workspacecargo clippy --workspace --all-targets -- -D warnings(clean)cargo test --workspace(passes; one pre-existing flake inexport_html_generates_static_sitewas a transient disk-spaceissue in the sandbox, recovers on retry — not related to this
change).
cargo fmt --all -- --check(clean).rivet validateshows identical diagnostics onmainvs this branch when no
fields_per_variantis present (the newvalidator is a pure-addition: if nothing has overlays, nothing
is emitted).
Deferred to follow-up
Both are deferred deliberately, not omitted by oversight; see the
"Scope" section above for the reasoning. Items 1-3 — the highest-
leverage subset called out in the brief — are fully complete with
tests and clean lints.
https://claude.ai/code/session_01Ms4nZDTtdfzvzTu8m3ghSj
Generated by Claude Code