Skip to content

fix(validate): scope cross-repo UnknownPrefix to consumer-owned links (#649)#658

Open
avrabe wants to merge 3 commits into
mainfrom
fix/issue-649-transitive-external-refs
Open

fix(validate): scope cross-repo UnknownPrefix to consumer-owned links (#649)#658
avrabe wants to merge 3 commits into
mainfrom
fix/issue-649-transitive-external-refs

Conversation

@avrabe

@avrabe avrabe commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Closes #649.

What

rivet validate walked EVERY artifact's outgoing links when computing broken_cross_refs, so any supplier's OWN external reference (e.g. super:REQ-EXT in an artifact synced under sup:) leaked into the consumer as UnknownPrefix("super") — 51 broken cross-refs in the reported ordeal case for prefixes the consumer never declared.

That contradicts two of rivet's own documented positions:

  • DD-017 (rivet docs cross-repo): "declare direct deps only"
  • REQ-065 / AoU-X1 (rivet validate --help, on --with-externals-validate): "the consumer's PASS does not otherwise reflect the supplier's validation state"

The cross-ref walk in cmd_validate (rivet-cli/src/main.rs:5819-5824) now filters external (prefix:ID) artifacts out of both the local_ids set and the all_refs collection before calling validate_refs, mirroring the REQ-082 is_external_artifact convention already used by every per-artifact validation pass in rivet-core/src/validate.rs:572-574 and by the lifecycle-completeness check a few lines below on the same page (main.rs:5901-5905).

-                        let local_ids: std::collections::HashSet<String> =
-                            store.iter().map(|a| a.id.clone()).collect();
-                        let all_refs: Vec<&str> = store
-                            .iter()
-                            .flat_map(|a| a.links.iter().map(|l| l.target.as_str()))
-                            .collect();
+                        let local_ids: std::collections::HashSet<String> = store
+                            .iter()
+                            .filter(|a| !a.id.contains(':'))
+                            .map(|a| a.id.clone())
+                            .collect();
+                        let all_refs: Vec<&str> = store
+                            .iter()
+                            .filter(|a| !a.id.contains(':'))
+                            .flat_map(|a| a.links.iter().map(|l| l.target.as_str()))
+                            .collect();

Why this direction

Semantic (a) from the triage sketch — "strict transitive-scope: broken cross-refs classify as UnknownPrefix only for links originating in the consumer's own artifacts". The issue body already prescribes exactly this shape ("cross-ref checking should scope UnknownPrefix/broken-ref errors to links originating in the local project"), and it matches the letter of DD-017 / REQ-065 without adding a new config knob (semantic (b) would have needed a new opt-in externals.validate-transitive flag; not worth the surface for a fix that removes the wrong behavior). The --with-externals-validate opt-in (REQ-065 / AoU-X1) is the remaining path if you want to walk supplier internals — untouched by this PR.

Acceptance criteria (from #649)

# Criterion How this PR satisfies it
1 The repro (cd ordeal; rivet sync; rivet validate) yields PASS on default rivet validate. The cross-ref walk skips supplier-owned artifacts, so UnknownPrefix("gale"/"kiln"/"jess"/"scry") — all originating in loom's and synth's own graphs — never fire.
2 rivet validate still FAILS on a genuinely broken loom: ref written in the consumer's own artifacts. Consumer-owned links (id without :) are still walked in full. Covered by the counter-assertion in the new test (xyz:REQ-BOGUS in the consumer surfaces as before).
3 Regression test asserts the fix on a realistic supplier / transitive-supplier scenario. rivet-cli/tests/cli_commands.rs::validate_does_not_leak_transitive_external_prefixes — spins two rivet init --preset dev projects, wires supplier as sup:, has supplier link to super:REQ-EXT (undeclared by the consumer), then asserts broken_cross_refs contains no super: entries and DOES contain the consumer's own xyz:REQ-BOGUS.
4 --with-externals-validate (REQ-065 / AoU-X1) still surfaces supplier diagnostics when opted in. Its block (main.rs:5872-5897) is untouched; the existing validate_with_externals_validate_surfaces_supplier_diagnostics test continues to guard it.
5 rivet validate on this repo remains PASS. The fix only removes findings; no new diagnostics are introduced. Verified by CI.

Why draft

This session's egress policy is scoped to pulseengine/rivet only. The workspace transitively depends on pulseengine/rowan (git dependency pinned at 9e7abd1…, the maintenance fork), which the proxy denies with a 403 — same pattern as PRs #657 and #599. I could not run cargo build -p rivet-cli --tests or cargo test locally in this session — the fetch of rowan cannot succeed. Please flip to Ready-for-review after CI (which has full scope) confirms:

  • cargo test -p rivet-cli --test cli_commands validate_does_not_leak_transitive_external_prefixes passes on this branch,
  • the same test fails on main (proves the regression test is real — the super: UnknownPrefix would fire, hitting the leaked.is_empty() assertion),
  • rivet validate on the rivet repo remains PASS,
  • validate_with_externals_validate_surfaces_supplier_diagnostics and validate_does_not_count_external_repo_violations (the REQ-065 / REQ-082 guardrails) still pass.

Test plan

  • CI Format / Clippy / Test gates green on this branch
  • Verify validate_does_not_leak_transitive_external_prefixes fails on main (revert the two .filter(...) lines, expect leaked populated with a super: UnknownPrefix entry)
  • Downstream: run rivet validate on a real cross-repo consumer (e.g. ordeal after rivet sync of loom + synth) and confirm the 51 broken cross-refs are gone; a genuinely broken consumer-owned ref still fails

Blog process pre-check

Re-fetched https://pulseengine.eu/blog/ at the start of this run — no posts on team workflow / branching / PR flow / testing conventions (the blog focuses on WebAssembly tooling, formal verification, and AI-assisted spec-driven verification). Nothing there overrides this PR.


Generated by Claude Code — issue-triage agent run 2026-07-02.


Generated by Claude Code

…#649)

`rivet validate` walked EVERY artifact's outgoing links when computing
`broken_cross_refs`, so any supplier's own external reference (e.g.
`super:REQ-EXT` in an artifact synced under `sup:`) leaked into the
consumer as `UnknownPrefix("super")` — 51 broken cross-refs in the
reported ordeal case for prefixes the consumer never declared.

That contradicts two of rivet's own documented positions:
- DD-017 (`rivet docs cross-repo`): "declare direct deps only"
- REQ-065 / AoU-X1 (`rivet validate --help`): "the consumer's PASS
  does not otherwise reflect the supplier's validation state"

The cross-ref walk in `cmd_validate` now filters external (`prefix:ID`)
artifacts out of both the `local_ids` set and the `all_refs` collection
before calling `validate_refs`, mirroring the REQ-082 `is_external_artifact`
convention already used across the per-artifact validation passes and by
the lifecycle-completeness check on the same page. Consumer-owned broken
prefixes still surface (the fix does not silently accept the consumer's
own bogus refs); supplier internals stay in the supplier's validation
gate, opt-in via `--with-externals-validate` (REQ-065 / AoU-X1).

Regression: `validate_does_not_leak_transitive_external_prefixes` spins
a supplier whose own artifact links to `super:REQ-EXT` (a prefix the
consumer never declares) and a consumer whose own artifact links to a
locally-broken `xyz:REQ-BOGUS`. After the fix: `broken_cross_refs`
contains no `super:` findings but still contains the `xyz:` finding.

Fixes: REQ-065
Refs: DD-017, #649
@github-actions

github-actions Bot commented Jul 2, 2026

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.

@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: ca3cefe Previous: 632eb8a Ratio
link_graph_build/10000 33762787 ns/iter (± 2544519) 25008993 ns/iter (± 252673) 1.35

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

@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@avrabe avrabe marked this pull request as ready for review July 2, 2026 17:33
avrabe added 2 commits July 3, 2026 07:53
Picks up the quick-xml 0.41 security fix (RustSec) and the CI paths-filter.

Trace: skip
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.

friction: cross-repo validate fails the consumer on its externals' own external refs (UnknownPrefix), contradicting DD-017 / REQ-065

2 participants