Skip to content

fix(feature-model): don't mis-report a shared child as a cycle (REQ-269)#716

Merged
avrabe merged 1 commit into
mainfrom
fix/req-269-diamond-cycle
Jul 16, 2026
Merged

fix(feature-model): don't mis-report a shared child as a cycle (REQ-269)#716
avrabe merged 1 commit into
mainfrom
fix/req-269-diamond-cycle

Conversation

@avrabe

@avrabe avrabe commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

REQ-269 — shared child (diamond/DAG) no longer mis-reported as a cycle (v0.29.0)

Surfaced by REQ-266's coverage. validate_tree's cycle detection used a single
global visited set + BFS and reported every second encounter of a node as
a cycle. A feature reachable through two parents — a diamond, root -> {a, b}
with a -> shared and b -> shared — was enqueued from both parents, dequeued
twice, and wrongly rejected: cycle detected involving feature shared``. Real
feature models legitimately share a sub-feature under two branches.

Fix

Iterative DFS tracking the current path (recursion stack) vs fully-explored
nodes:

  • Re-encounter on the current path → genuine cycle (back edge, a feature that
    is its own ancestor). Still rejected.
  • Re-encounter by a disjoint path → shared child, fully explored → skipped,
    not flagged.

Parent link stays last-writer-wins (unchanged, acceptable); only the traversal's
cycle criterion changes.

Tests

  • Un-ignored diamond_shared_child_is_not_a_cycle — now passes.
  • Added genuine_cycle_is_rejectedroot -> mid -> root must still error.

Verification

Full feature-model + variant suites green (proptest 14, lib feature_model
62, variant_gap_check 2, variant_phase2 8); clippy --all-targets
(1.97.0) clean; wasm seam buildscargo build -p rivet-cli --features wasm
OK (composition core touched, WIT/component unchanged so no cargo-component
rebuild).

Fixes: REQ-269 · Refs: REQ-266

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown

📐 Rivet artifact delta

Change Count
Added 0
Removed 0
Modified 1
Downstream impacted (depth ≤ 5) 0

Graph

graph LR
  REQ_269["REQ-269"]:::modified
  classDef added fill:#d4edda,stroke:#28a745,color:#155724
  classDef removed fill:#f8d7da,stroke:#dc3545,color:#721c24
  classDef modified fill:#fff3cd,stroke:#ffc107,color:#856404
  classDef overflow fill:#e2e3e5,stroke:#6c757d,color:#495057,stroke-dasharray: 3 3
Loading
Modified
ID Changes
REQ-269

📎 Full HTML dashboard attached as workflow artifact rivet-delta-pr-716download from the workflow run.

Posted by rivet-delta workflow. The graph shows only changed artifacts; open the HTML dashboard (above) for full context.

@codecov

codecov Bot commented Jul 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.95918% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
rivet-core/src/feature_model.rs 97.95% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

`validate_tree`'s cycle detection used a single global `visited` set with a
BFS and reported EVERY second encounter of a node as a cycle. A feature
reachable through two parents — a diamond / DAG, e.g. `root -> {a, b}` with
`a -> shared` and `b -> shared` — was enqueued from both parents, dequeued
twice, and wrongly rejected with "cycle detected involving feature `shared`".
Real feature models legitimately share a sub-feature under two branches.

Replace the BFS with an iterative DFS that tracks the current path (the
recursion stack) separately from fully-explored nodes:
- A node re-encountered while still ON the current path is a genuine cycle
  (a back edge — a feature that is its own ancestor). Still rejected.
- A node reached again by a disjoint path (a shared child) is fully
  explored, so it is skipped, not flagged.

The parent link remains last-writer-wins (unchanged, acceptable); only the
traversal's cycle criterion changes.

Tests: un-ignored `diamond_shared_child_is_not_a_cycle` (now passing) and
added `genuine_cycle_is_rejected` as a guard that real cycles still error.

Confirmed with the full feature_model + variant suites (proptest 14, lib
feature_model 62, variant_gap_check 2, variant_phase2 8 — all green),
clippy --all-targets clean, and the wasm seam builds
(`cargo build -p rivet-cli --features wasm` OK — composition core touched,
WIT/component unchanged so no cargo-component rebuild needed).

Fixes: REQ-269
Verifies: REQ-269
Refs: REQ-266
@avrabe
avrabe force-pushed the fix/req-269-diamond-cycle branch from 4a47e63 to 6ab32f7 Compare July 16, 2026 15:04

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ 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: 6ab32f7 Previous: ab9b24c Ratio
traceability_matrix/1000 59877 ns/iter (± 349) 43980 ns/iter (± 781) 1.36
query/10000 339650 ns/iter (± 5633) 223850 ns/iter (± 972) 1.52

This comment was automatically generated by workflow using github-action-benchmark.

@avrabe
avrabe merged commit 2ff1c09 into main Jul 16, 2026
29 of 30 checks passed
@avrabe
avrabe deleted the fix/req-269-diamond-cycle branch July 16, 2026 15:20
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