Skip to content

#352 — domain: PR-scope DAG node + connector computation (Slice A) - #357

Merged
cmbays merged 3 commits into
mainfrom
domain-352-prdag-core
Jun 13, 2026
Merged

cmbays merged 3 commits into
mainfrom
domain-352-prdag-core

Conversation

@cmbays

@cmbays cmbays commented Jun 13, 2026 •

Copy link
Copy Markdown
Contributor

Part of #352.

Slice A of the PR-scope lineage mini-DAG: the pure-domain graph core only. No render surface, no CLI wiring — src/domain/pr_dag.rs plus its mod.rs registration and a crap4rs strict-threshold entry.

What it computes

compute_pr_dag(manifest, modified, new, removed) -> PrDagGraph:

  • Node set = modified ∪ connectors ∪ removed. Connectors = (descendants(M) ∩ ancestors(M)) \ M over the model→model depends_on graph, keeping only nodes reached forward from one seed and backward from a different seed (the differing-seed "between" predicate). An isolated modified model renders alone; a convergence sink (downstream-of-both, between-neither) is correctly excluded.
  • Edges = the induced model→model subgraph (both endpoints in-set), self-edge-guarded, deterministically ordered (BTreeSet), acyclic.
  • Per-node state reuses the scope taxonomy: New / Modified / Deleted, plus an is_connector quiet-tier marker.

Discipline

  • Consumes the existing modified set (ModelInScopeSet) and the caller-derived removed set — no comparator / state.rs / scope.rs rewrite.
  • Multi-source BFS (one shared queue + visited set per direction) collapses the naive O(|M| × (N+E)) to O(N+E), mirroring the governance reverse-adjacency precompute-once idiom.
  • Domain-pure (std + serde derive only). Serialize-only render PODs. Versioned-id-safe bare_name (fallback strips a trailing vN for removed nodes absent from the current manifest).
  • lines ± per node is deliberately out of this slice (needs the diff; later render-side concern).

Tests

21 property-style invariant tests covering every Slice-A invariant: connector-between-two, 1-hop-context excluded, convergence-sink excluded, isolated-alone, exact induced-edge set, DELETED ghosts, model-only projection, NEW/MODIFIED taxonomy, acyclicity (Kahn), determinism (insertion-order-independent), and a brute-force pairwise-intersection oracle for the multi-source BFS.

Gates

fmt, clippy --all-targets --locked -D warnings, full cargo nextest (1936 passed), cargo doc -D warnings, cargo deny check — all green. crap4rs strict <15 keyed for the new module; worst function 11.00.

🤖 Generated with Claude Code


Open in Stage

Summary by CodeRabbit

  • New Features
    • Introduced PR-scoped dependency graph analysis to visualize how models in a pull request impact other project models, including automatic detection of intermediate connector nodes and tracking of model lifecycle states for enhanced pull request review capabilities.

Add `src/domain/pr_dag.rs` — a pure-domain function computing the
PR-scope lineage mini-DAG: the modified set unioned with the
connectors between distinct modified models, plus DELETED ghosts.

- Node set = modified ∪ connectors ∪ removed. Connectors =
  (descendants(M) ∩ ancestors(M)) \ M over the model→model
  `depends_on` graph, keeping only nodes reached forward from one seed
  and backward from a different seed (the differing-seed "between"
  predicate). An isolated modified model renders alone; a convergence
  sink (downstream-of-both, between-neither) is correctly excluded.
- Multi-source BFS (one shared queue + visited set per direction)
  collapses the naive O(|M| × (N+E)) to O(N+E), mirroring the
  governance reverse-adjacency precompute-once idiom.
- Edges = the induced model→model subgraph (both endpoints in-set),
  self-edge-guarded, deterministically ordered (BTreeSet), acyclic.
- Per-node state reuses the scope taxonomy: New / Modified / Deleted,
  plus an `is_connector` quiet-tier marker. Consumes the existing
  modified set (ModelInScopeSet) and the caller-derived removed set —
  no comparator / state.rs / scope.rs rewrite.
- Serialize-only render PODs (PrDagGraph / PrDagNode / PrDagEdge);
  versioned-id-safe `bare_name` via Node::bare_name (fallback strips a
  trailing vN for removed nodes absent from the current manifest).

Property tests cover every Slice-A invariant: connector-between-two,
1-hop-context excluded, convergence-sink excluded, isolated-alone,
exact induced-edge set, DELETED ghosts, model-only projection,
NEW/MODIFIED taxonomy, acyclicity, determinism, and a brute-force
pairwise-intersection oracle for the multi-source BFS.

`lines ±` per node is deliberately out of this slice (needs the diff;
later render-side concern). Domain-pure (std + serde derive only).
crap4rs strict <15 keyed for the new module (worst fn 11.00).

Part of #352

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 13, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@cmbays, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 40 minutes and 59 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 17037110-a06f-44fa-b7eb-2872364cb95d

📥 Commits

Reviewing files that changed from the base of the PR and between 8c2e671 and c762135.

📒 Files selected for processing (2)
  • src/domain/mod.rs
  • src/domain/pr_dag.rs
📝 Walkthrough

Walkthrough

This PR introduces src/domain/pr_dag.rs, a new module that computes a deterministic, serialization-ready subgraph of models and their lineage connectors affected by a PR. The module exports a core compute_pr_dag function that accepts a manifest, modified model set, new subset, and removed node list to produce a PrDagGraph containing nodes (modified ∪ connectors ∪ removed ghosts) and their induced edges. Connector nodes are selected via multi-source reachability between distinct modified seeds, excluding pure 1-hop context. The module is gated with strict complexity threshold (15) and includes comprehensive tests validating semantics, determinism, and edge cases.

Changes

PR-scope lineage mini-DAG computation

Layer / File(s) Summary
Module setup & public API exports
crap4rs.toml, src/domain/mod.rs
Strict complexity threshold (15) for src/domain/pr_dag.rs enforces review rigor; new public pr_dag submodule and re-exports of PrDagState, PrDagNode, PrDagEdge, PrDagGraph, and compute_pr_dag extend domain API surface.
Data contracts: state, node, edge, & graph
src/domain/pr_dag.rs
PrDagState enum (New | Modified | Deleted) and as_str serialization; PrDagNode payload with id, name, state, is_connector flag; PrDagEdge with from/to endpoints; PrDagGraph container with deterministic node and edge collections.
DAG computation & connector selection
src/domain/pr_dag.rs
compute_pr_dag orchestrates the flow: builds model→model forward/backward adjacency, computes connector nodes via multi-source BFS (reachable from one seed forward and a different seed backward, excluding seeds and pure 1-hop context), and prepares node/edge collections for assembly.
Node & edge assembly
src/domain/pr_dag.rs
assemble_nodes builds deterministic node set with seed nodes marked New or Modified, connectors as is_connector=true placeholders, and removed nodes as Deleted ghosts; induced_edges restricts precomputed adjacency to assembled node set, drops self-edges, and produces lexicographically ordered deduped edges; node construction helpers recover bare names from manifest or parse and strip dbt version suffixes from NodeId strings.
Comprehensive test coverage
src/domain/pr_dag.rs
Unit tests validate connector inclusion/exclusion semantics, isolated/empty-modified cases, induced edge exactness and determinism (ordering independent of manifest insertion order), deleted ghost state behavior, New vs Modified taxonomy, model-only projection, acyclicity/self-edge sanity, and multi-source BFS against brute-force oracle.

Sequence Diagram

sequenceDiagram
  participant Caller
  participant compute_pr_dag
  participant ModelAdjacency
  participant connectors_between
  participant reach
  participant assemble_nodes
  Caller->>compute_pr_dag: manifest, modified set, new subset, removed list
  compute_pr_dag->>ModelAdjacency: build forward/backward adjacency
  ModelAdjacency-->>compute_pr_dag: model→model edge maps
  compute_pr_dag->>connectors_between: adjacency, modified seeds
  connectors_between->>reach: forward reachability from seeds
  reach-->>connectors_between: reachable nodes per seed
  connectors_between->>reach: backward reachability from seeds
  reach-->>connectors_between: backward-reachable nodes per seed
  connectors_between-->>compute_pr_dag: connector node ids
  compute_pr_dag->>assemble_nodes: seeds, connectors, removed ids
  assemble_nodes-->>compute_pr_dag: deterministic node collection
  compute_pr_dag-->>Caller: PrDagGraph (nodes, edges)
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly Related Issues

Suggested Labels

type:feature

Poem

🐰 A rabbit graphs the PR's lineage,
Connectors found through reachability's cadence,
No cycles block the deterministic trail,
Modified, new, and deleted tell the tale,
Through tests both thorough—the code shall not fail! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: adding PR-scope DAG node and connector computation to the domain module as part of task #352.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch domain-352-prdag-core

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ghost

ghost commented Jun 13, 2026 •

Copy link
Copy Markdown

Ready to review this PR? Stage has broken it down into 3 individual chapters for you:

Title
1 Implement PR-scope lineage DAG computation
2 Register PR DAG module and exports
3 Configure complexity thresholds for new module
Open in Stage

Chapters generated by Stage for commit c762135 on Jun 13, 2026 4:12pm UTC.

@github-actions

github-actions Bot commented Jun 13, 2026 •

Copy link
Copy Markdown
Contributor

📄 Rendered report preview

All golden examples regenerated cleanly.

🟡 Golden examples

Committed to examples/ and byte-identity gated — the canonical reports contributors and consumers browse. Stable across PRs.

Report View Download
jaffle-shop-report.html ▶ Open ↗ ⬇ Download
playground-report.html ▶ Open ↗ ⬇ Download
diff-showcase-report.html ▶ Open ↗ ⬇ Download

🐶 Live dogfood preview

This PR doesn't touch dbt-project/, so there's no live dogfood preview.

🧭 Explore preview

The two-page cute-dbt explore explorer — dag.html (model lineage) + tests.html (unit-test viewer). Same golden/live split as the report.

🟡 Golden explore

The committed examples/explore/ playground golden (the full synthetic playground manifest). Byte-identity gated in Example report check. Stable across PRs.

Page View Download
explore/dag.html ▶ Open ↗ ⬇ Download
explore/tests.html ▶ Open ↗ ⬇ Download

🐶 Live explore

This PR doesn't touch dbt-project/, so there's no live explore preview.

▶ Open ↗ opens the report or explorer in your browser in one
click — published to this repo's GitHub Pages under
/pr-357/.
⬇ Download fetches the same self-contained HTML as a workflow
artifact (auth-gated; works fully offline). Either way the report
makes zero external resource requests.

The Pages preview may take ~1 min to update after this comment
posts. On PRs from forks the Open link is unavailable (read-only
token) — use Download.

Alternative: GitHub CLI
# gh CLI >= 2.63 extracts into ./report-preview-playground/.
gh run download 27472025225 -R breezy-bays-labs/cute-dbt -n report-preview-playground
open report-preview-playground/playground-report.html

Posted by report-preview.yml for c762135077e27c953a7571b190d9c08d1b2421fb. Affordance only — never blocks merge.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a new module src/domain/pr_dag.rs to compute a focused, PR-scope cross-model lineage mini-DAG that highlights modified, connector, and deleted models. Feedback on the implementation focuses on performance optimizations and codebase consistency: avoiding heap allocations by querying the manifest directly instead of building a temporary BTreeSet in ModelAdjacency::build, replacing VecDeque with a Vec and manual index tracking in reach to match established idioms, optimizing has_distinct_pair to O(1) complexity, and utilizing binary search on the sorted nodes slice in induced_edges to eliminate another temporary BTreeSet allocation.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/domain/pr_dag.rs
Comment thread src/domain/pr_dag.rs
Comment thread src/domain/pr_dag.rs
Comment thread src/domain/pr_dag.rs

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/domain/pr_dag.rs`:
- Around line 553-1018: Add exhaustive table-driven JSON serde round-trip tests
for the new serialized types PrDagState, PrDagNode, PrDagEdge, and PrDagGraph:
create unit tests in src/domain/pr_dag.rs that enumerate combinatorial cases
(all enum variants of PrDagState; nodes with/without is_connector, different
state values, name/id variations; edges with typical from/to and self-edge
guard; graphs with empty and populated nodes/edges, deleted/new/modified mixes)
and for each case serialize with serde_json::to_string and deserialize with
serde_json::from_str, asserting equality (or expected normalized equality where
name-stripping for removed versions applies) to validate round-trip and union
semantics; structure tests as table-driven vectors of cases and include explicit
checks for StateComparator/union-like behavior (e.g., New vs Modified
precedence) and ensure coverage of empty and edge-case payloads.
- Around line 272-293: The current reach function does a full BFS per seed
causing O(|seeds|*(N+E)) work; change it to a single multi-source BFS:
initialize a VecDeque queue seeded with (seed, seed) for each seed, pop
(current, origin_seed) and attempt to insert origin_seed into reached[current],
and if insertion succeeds push (neighbor, origin_seed) for each neighbor from
adjacency; stop propagation for that origin_seed when insertion fails. Update
the queue type and push/pop logic accordingly (instead of looping seeds and
running separate BFSs), keep reached as BTreeMap<&NodeId, BTreeSet<&NodeId>>,
and ensure adjacency access uses get(current).into_iter().flatten() or
unwrap_or(&empty_vec) to iterate neighbors safely. This yields O(N+E +
total_tags) instead of per-seed repetition.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b5bba79d-39ac-4d95-a94f-d734b0987056

📥 Commits

Reviewing files that changed from the base of the PR and between c3bd746 and 8c2e671.

📒 Files selected for processing (3)
  • crap4rs.toml
  • src/domain/mod.rs
  • src/domain/pr_dag.rs

Comment thread src/domain/pr_dag.rs
Comment thread src/domain/pr_dag.rs
cmbays and others added 2 commits June 13, 2026 12:06
Replace the O(|forward| × |backward|) nested-loop scan in
has_distinct_pair with the provably-equivalent O(1) form (gemini on
PR #357): either side empty ⇒ no pair; both singletons ⇒ distinct iff
the lone elements differ (set inequality ⟺ element inequality for
singletons); otherwise one side has ≥2 elements so a distinct pair is
guaranteed. The predicate runs once per scanned node in the connector
walk, so this matters on large PRs.

Behaviorally identical — all 21 existing pr_dag invariant tests pass
unchanged. Adds a focused unit test for the both-singleton-equal
({a} vs {a} ⇒ false), distinct-singleton ({a} vs {b} ⇒ true),
asymmetric-multi ({a,b} vs {a} ⇒ true), and empty cases.

Part of #352

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cmbays
cmbays merged commit a094704 into main Jun 13, 2026
35 checks passed
@cmbays
cmbays deleted the domain-352-prdag-core branch June 13, 2026 16:16
@cmbays

cmbays commented Jun 13, 2026

Copy link
Copy Markdown
Contributor Author

🧾 Merge debrief — #357 (#352 Slice A: PR-scope DAG domain core)

Merged a094704 · Part of #352 (epic stays open; render slices remain) · feat → patch. Domain core only — file-disjoint (ran concurrently with the macro render lane, zero collision).

What shipped: new pure-domain src/domain/pr_dag.rs — the PR-scope DAG node set (modified models ∪ connectors = models on directed paths between two modified models, i.e. descendants(modified) ∩ ancestors(modified) over the model depends_on graph), the induced edges, and per-node NEW/MODIFIED/DELETED state. Serialize-only render PODs (PrDagGraph/PrDagNode/PrDagEdge/PrDagState) + 22 property/unit tests. No render/CLI surface yet (that's a later render slice).

Deviation (correct, founder-relevant): the strict descendants ∩ ancestors connector definition means a convergence sink below two modified models is NOT a connector (A→C, B→C: C is a descendant of both but ancestor of neither). That's 1-hop downstream context, not a "between" connector — pinned by a test. Open product question for the #352 render slice: do you want convergence sinks / 1-hop downstream context shown too? (Deferred Q1 — I'll surface it when the PR-DAG render slice comes up.)

Bot disposition — 6 threads: 1 accepted, 5 rejected, all probe-first:

  • ✅ ACCEPTED — gemini @308 has_distinct_pair O(1): the nested loop was O(modified²) per node in the connector scan (real cost on large PRs); replaced with the provably-equivalent O(1) form (applied in debd677, re-gated).
  • ❌ CodeRabbit "Major" serde round-trip: FALSE POSITIVE — the PrDag types are Serialize-only (zero Deserialize), write-only render payloads that can't round-trip (identical to the render: config-driven meta + tags governance chips (per-tag chips + aggregate meta chip w/ tooltip) #348 governance payloads). Covered by the 22 topology tests.
  • ❌ gemini binary_search-over-sorted-nodes: rejected as a robustness loss — it depends on an unstated sort invariant a future refactor could silently break; the explicit BTreeSet membership is correct independent of ordering.
  • ❌ CodeRabbit multi-source BFS: premature for bounded (PR-sized) seed sets; keeps the single-source idiom.
  • ❌ gemini Vec-vs-VecDeque: the probe confirmed the codebase uses Vec+manual-index BFS (scan_macro_closure, macro_lens.rs), so the consistency point is real — but VecDeque is clearer and perf-identical; not worth a cosmetic re-gate.
  • ❌ gemini models-set allocation: built once, clear; the inline-node-map rewrite carries a reuse-verification risk for no real gain.

Gates (green): clippy via rtk proxy (raw exit code), nextest 1937 (incl. the new has_distinct_pair test), doc -D, deny, crap4rs pr_dag.rs all fns ≤11 (has_distinct_pair now 5.00). Branch-updated against main after #356 + re-gated before merge.

Tooling find (now durable): the builder caught that rtk's cargo clippy wrapper masks a real exit-101 behind "No issues found" — a false-green risk for local clippy gating. Captured as a memory + baked into all builder/gate briefs (judge clippy via rtk proxy / raw exit code).

Risks / follow-ups: none filed (the convergence-sink question is a scoping decision for the render slice, not a bug). Next: the #352 PR-DAG render slice comes later in the render-lane queue (after the macro lane); builder-prdag is done for now.

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