feat(variant): cross-repo feature model composition core (REQ-085 v1)#321
Merged
Conversation
Adds the core API for mounting feature-model sub-models from external repositories without the binding file re-declaring git sources — the binding only names a prefix the consumer's rivet.yaml already declares and `rivet sync` has fetched. Per the REQ-085 design, rivet.yaml stays the single source of truth for "where external repos come from"; the binding only says "which file, mounted where, under what prefix." New API: FeatureModel::load_composed_with_externals(binding_path, externals_map) FeatureModel::load_with_externals(path, externals_map) A mount's `model:` field is now either a local relative path (REQ-083 behaviour, unchanged) OR `<external-prefix>:<inner-path>` resolved via externals[prefix].join(inner-path). The path-resolution helper (`resolve_model_path`) fails loudly when a prefix-shaped reference does not match any declared external — no silent fall-back to local-path resolution that won't find the file (F2 ethos inherited from REQ-083). Three new tests cover: a happy-path mount resolving through an external prefix, an unknown prefix erroring with the declared set named, and backward compat (`load_composed_with_externals` with an empty externals map behaves identically to `load_composed` for local-only bindings). All 51 existing REQ-083 composition tests still green. CLI threading (so `rivet variant` commands automatically build the externals map from the project's `rivet.yaml` via ProjectContext) is the v2 follow-on increment — same boundary as REQ-086 v1's core-first-CLI-later shape. Implements: REQ-085 Verifies: REQ-085
📐 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: 951f458 | Previous: 86bf482 | Ratio |
|---|---|---|---|
traceability_matrix/1000 |
62715 ns/iter (± 684) |
44577 ns/iter (± 158) |
1.41 |
query/10000 |
119122 ns/iter (± 1010) |
98968 ns/iter (± 405) |
1.20 |
This comment was automatically generated by workflow using github-action-benchmark.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
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
Core API for REQ-085 (cross-repo feature model composition). v1
delivers the rivet-core API + tests; CLI threading (so `rivet
variant` automatically builds the externals map from
`rivet.yaml`'s `externals:`) is the v2 follow-on, same
core-first-CLI-later shape as REQ-086 v1.
What's new
```rust
FeatureModel::load_composed_with_externals(binding, externals_map)
FeatureModel::load_with_externals(path, externals_map)
```
A mount's `model:` is now either a local relative path
(REQ-083 behaviour, unchanged) or `:`
resolved via `externals[prefix].join(inner-path)`.
```yaml
binding.yaml — the binding names the prefix, NOT the git source
compose:
mount:
powertrain:
model: acme-pwt:powertrain.yaml # rivet.yaml's externals[acme-pwt]
prefix: pwt
```
This is the design REQ-085 captured: `rivet.yaml` is the single
source of truth for "where external repos come from"; the binding only
says "which file, mounted where, under what prefix" — and composition
rides the existing `rivet sync` plumbing entirely.
Fail-loud (F2 ethos inherited from REQ-083)
`resolve_model_path` rejects a prefix-shaped reference that doesn't
match any declared external — never a silent fall-back to local-path
resolution that won't find the file. Error names the offending prefix
and the declared set.
Test plan
`compose_with_externals_resolves_prefixed_mount` (happy path),
`compose_with_externals_unknown_prefix_is_error` (loud failure),
`compose_with_empty_externals_treats_paths_as_local` (backward
compat). All pass.
preserves backward compat — `load_composed` is now a thin
wrapper).
🤖 Generated with Claude Code