---
phase: 38-module-directory-splits
plan: 02
type: execute
wave: 1
depends_on: []
files_modified:
  - src/graph/mod.rs
  - src/graph/relationship.rs
  - src/graph/toposort.rs
  - src/graph/facts.rs
  - src/graph/derived_metrics.rs
  - src/graph/using.rs
  - src/graph/test_helpers.rs
autonomous: true
requirements: [REF-02, REF-05]

must_haves:
  truths:
    - "src/graph/ exists as a module directory with mod.rs and 5 submodules"
    - "src/graph.rs no longer exists (deleted to avoid E0761)"
    - "All external consumers compile without import changes"
    - "All 59 graph tests pass in their new submodule locations"
    - "cargo test passes with zero behavior changes"
  artifacts:
    - path: "src/graph/mod.rs"
      provides: "Re-exports preserving exact prior public API"
      contains: "pub use relationship::{RelationshipGraph, validate_graph}"
    - path: "src/graph/relationship.rs"
      provides: "RelationshipGraph struct + from_definition, check_no_diamonds, check_no_orphans, validate_graph, validate_fk_references, check_source_tables_reachable"
      contains: "pub struct RelationshipGraph"
    - path: "src/graph/toposort.rs"
      provides: "RelationshipGraph::toposort() + find_cycle_path helper"
      contains: "pub fn toposort"
    - path: "src/graph/facts.rs"
      provides: "validate_facts, find_fact_references, fact DAG helpers"
      contains: "pub fn validate_facts"
    - path: "src/graph/derived_metrics.rs"
      provides: "validate_derived_metrics, contains_aggregate_function, AGGREGATE_FUNCTIONS"
      contains: "pub fn contains_aggregate_function"
    - path: "src/graph/using.rs"
      provides: "validate_using_relationships"
      contains: "pub fn validate_using_relationships"
    - path: "src/graph/test_helpers.rs"
      provides: "Shared make_def, make_def_with_facts, make_def_with_derived_metrics, make_def_with_named_joins helpers"
      contains: "pub(super) fn make_def("
  key_links:
    - from: "src/graph/mod.rs"
      to: "src/graph/relationship.rs"
      via: "pub use relationship::{RelationshipGraph, validate_graph}"
      pattern: "pub use relationship::"
    - from: "src/graph/mod.rs"
      to: "src/graph/facts.rs"
      via: "pub use facts::{find_fact_references, validate_facts}"
      pattern: "pub use facts::"
    - from: "src/graph/mod.rs"
      to: "src/graph/derived_metrics.rs"
      via: "pub use derived_metrics::{contains_aggregate_function, validate_derived_metrics}"
      pattern: "pub use derived_metrics::"
    - from: "src/graph/mod.rs"
      to: "src/graph/using.rs"
      via: "pub use using::validate_using_relationships"
      pattern: "pub use using::"
    - from: "src/graph/derived_metrics.rs"
      to: "src/graph/facts.rs"
      via: "use super::facts::find_fact_references"
      pattern: "use super::facts::find_fact_references"
    - from: "src/graph/toposort.rs"
      to: "src/graph/relationship.rs"
      via: "use super::relationship::RelationshipGraph"
      pattern: "use super::relationship::RelationshipGraph"
    - from: "src/expand/sql_gen.rs"
      to: "src/graph/mod.rs"
      via: "use crate::graph::RelationshipGraph"
      pattern: "use crate::graph::RelationshipGraph"
    - from: "src/ddl/show_dims_for_metric.rs"
      to: "src/graph/mod.rs"
      via: "use crate::graph::RelationshipGraph"
      pattern: "use crate::graph::RelationshipGraph"
---

<objective>
Split src/graph.rs (2,333 lines) into a module directory src/graph/ with 5 single-responsibility submodules, preserving the exact public API surface via mod.rs re-exports.

Purpose: Decompose the second-largest source file into navigable, single-responsibility submodules. This is a purely mechanical, behavior-preserving refactoring -- no logic changes.
Output: src/graph/ directory with mod.rs, 5 submodules (relationship, toposort, facts, derived_metrics, using), and test_helpers.rs. Old src/graph.rs deleted.
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/38-module-directory-splits/38-RESEARCH.md

@src/graph.rs (the file being split -- read in full before any changes)
@src/lib.rs (declares pub mod graph -- no changes needed, Rust resolves to directory)

<interfaces>
<!-- External consumers that MUST continue to compile without changes -->

From src/ddl/show_dims_for_metric.rs:
  use crate::graph::RelationshipGraph;

From src/ddl/define.rs (via extension feature):
  use crate::graph::{validate_graph, validate_facts, validate_derived_metrics, validate_using_relationships};

From src/expand.rs (or src/expand/sql_gen.rs after Plan 01):
  use crate::graph::RelationshipGraph;

From src/graph.rs internal:
  use crate::model::SemanticViewDefinition;
  use crate::util::suggest_closest;
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Create graph/ production code submodules and mod.rs</name>
  <files>
    src/graph/mod.rs
    src/graph/relationship.rs
    src/graph/toposort.rs
    src/graph/facts.rs
    src/graph/derived_metrics.rs
    src/graph/using.rs
  </files>
  <read_first>
    src/graph.rs (FULL read -- this is the source of all code being moved)
    src/lib.rs (verify pub mod graph declaration -- no changes needed)
    src/ddl/show_dims_for_metric.rs (verify RelationshipGraph import path)
  </read_first>
  <action>
    Create src/graph/ directory. Create each submodule file by extracting the corresponding lines from graph.rs. Then delete src/graph.rs. The exact decomposition:

    **src/graph/relationship.rs** -- Lines 1-216 + 225-335 of graph.rs (struct, impl, validation):
    - Move: `pub struct RelationshipGraph` (line 21), `impl RelationshipGraph` block (lines 32-216) EXCEPT the `toposort()` method (lines 90-148 go to toposort.rs), `fn validate_fk_references()` (line 225), `fn check_source_tables_reachable()` (line 287), `pub fn validate_graph()` (line 336)
    - The `impl RelationshipGraph` block in this file contains: `from_definition` (line 39), `check_no_diamonds` (line 151), `check_no_orphans` (line 192)
    - `validate_fk_references` and `check_source_tables_reachable` stay private (only used by `validate_graph` in this same file)
    - `validate_graph` stays `pub` (re-exported from mod.rs)
    - Imports needed: `use std::collections::{HashMap, HashSet, VecDeque};`, `use std::fmt::Write as _;`, `use crate::model::SemanticViewDefinition;`, `use crate::util::suggest_closest;`
    - The module-level doc comment from graph.rs (lines 1-8) goes at the top of this file

    **src/graph/toposort.rs** -- Lines 90-148 + 995-1035 of graph.rs (toposort impl + cycle helper):
    - Move: `impl RelationshipGraph { pub fn toposort() }` as a separate impl block, and `fn find_cycle_path()` (line 995)
    - The `impl RelationshipGraph` block is a split impl -- this is idiomatic Rust (write `impl RelationshipGraph` in a separate file, Rust merges them)
    - `find_cycle_path` stays private (only used by toposort in this file)
    - Imports needed: `use std::collections::{HashMap, HashSet, VecDeque};`, `use super::relationship::RelationshipGraph;`

    **src/graph/facts.rs** -- Lines 368-586 of graph.rs (fact validation):
    - Move: `fn is_word_boundary_byte()` (line 373), `pub fn find_fact_references()` (line 383), `pub fn validate_facts()` (line 424), `fn check_fact_source_tables()` (line 446), private `type FactDag` (line 471), `fn build_fact_dag()` (line 474), `fn check_fact_references_exist()` (line 506), `fn check_fact_cycles()` (line 527)
    - `is_word_boundary_byte`, `check_fact_source_tables`, `build_fact_dag`, `check_fact_references_exist`, `check_fact_cycles` stay private
    - `find_fact_references` and `validate_facts` stay `pub` (re-exported from mod.rs)
    - Imports needed: `use std::collections::{HashMap, HashSet};`, `use std::fmt::Write as _;`, `use crate::model::SemanticViewDefinition;`, `use crate::util::suggest_closest;`

    **src/graph/derived_metrics.rs** -- Lines 588-993 of graph.rs (derived metric validation):
    - Move: `const AGGREGATE_FUNCTIONS` (line 593), `pub fn contains_aggregate_function()` (line 641), `pub fn validate_derived_metrics()` (line 701), `fn check_metric_name_uniqueness()` (line 733), `fn check_no_aggregates_in_derived()` (line 745), `fn check_derived_metric_references()` (line 759), `fn check_derived_metric_cycles()` (line 800), `fn extract_identifiers()` (line 880), `fn is_sql_keyword_or_builtin()` (line 934)
    - `AGGREGATE_FUNCTIONS`, `check_metric_name_uniqueness`, `check_no_aggregates_in_derived`, `check_derived_metric_references`, `check_derived_metric_cycles`, `extract_identifiers`, `is_sql_keyword_or_builtin` stay private
    - `contains_aggregate_function` and `validate_derived_metrics` stay `pub` (re-exported from mod.rs)
    - Imports needed: `use std::collections::{HashMap, HashSet};`, `use std::fmt::Write as _;`, `use crate::model::SemanticViewDefinition;`, `use crate::util::suggest_closest;`, `use super::facts::find_fact_references;`

    **src/graph/using.rs** -- Lines 1037-1106 of graph.rs (USING relationship validation):
    - Move: `pub fn validate_using_relationships()` (line 1049)
    - Stays `pub` (re-exported from mod.rs)
    - Imports needed: `use crate::model::SemanticViewDefinition;`
    - Include the section comment/doc from the original (lines ~1037-1048)

    **src/graph/mod.rs** -- Module declarations and re-exports:
    ```rust
    //! Relationship graph validation and topological sort for semantic view definitions.

    mod relationship;
    mod toposort;
    mod facts;
    mod derived_metrics;
    mod using;

    #[cfg(test)]
    mod test_helpers;

    // Public API (matches prior graph.rs surface exactly)
    pub use relationship::{RelationshipGraph, validate_graph};
    pub use facts::{find_fact_references, validate_facts};
    pub use derived_metrics::{contains_aggregate_function, validate_derived_metrics};
    pub use using::validate_using_relationships;
    ```

    **Delete src/graph.rs** after all submodule files are created. This MUST happen in the same commit -- Rust E0761 forbids both src/graph.rs and src/graph/mod.rs existing simultaneously.

    **Cross-submodule dependencies** (minimal for graph/):
    - `toposort.rs` imports `RelationshipGraph` from `super::relationship` (split impl block)
    - `derived_metrics.rs` imports `find_fact_references` from `super::facts` -- `find_fact_references` is already `pub`, so this works via `super::facts::find_fact_references`

    **Do NOT modify any external consumer files.** The mod.rs re-exports ensure all existing `use crate::graph::*` import paths continue to work.

    **Do NOT modify src/lib.rs.** The `pub mod graph;` declaration resolves to src/graph/mod.rs automatically.
  </action>
  <verify>
    <automated>cd /Users/paul/Documents/Dev/Personal/duckdb-semantic-views && cargo check 2>&1 | tail -5</automated>
  </verify>
  <acceptance_criteria>
    - src/graph/mod.rs exists and contains `pub use relationship::{RelationshipGraph, validate_graph}`
    - src/graph/mod.rs contains `pub use facts::{find_fact_references, validate_facts}`
    - src/graph/mod.rs contains `pub use derived_metrics::{contains_aggregate_function, validate_derived_metrics}`
    - src/graph/mod.rs contains `pub use using::validate_using_relationships`
    - src/graph/relationship.rs contains `pub struct RelationshipGraph`
    - src/graph/relationship.rs contains `pub fn from_definition`
    - src/graph/relationship.rs contains `pub fn validate_graph`
    - src/graph/relationship.rs does NOT contain `fn toposort` (that is in toposort.rs)
    - src/graph/toposort.rs contains `impl RelationshipGraph`
    - src/graph/toposort.rs contains `pub fn toposort`
    - src/graph/toposort.rs contains `fn find_cycle_path`
    - src/graph/facts.rs contains `pub fn validate_facts`
    - src/graph/facts.rs contains `pub fn find_fact_references`
    - src/graph/derived_metrics.rs contains `pub fn contains_aggregate_function`
    - src/graph/derived_metrics.rs contains `pub fn validate_derived_metrics`
    - src/graph/derived_metrics.rs contains `const AGGREGATE_FUNCTIONS`
    - src/graph/using.rs contains `pub fn validate_using_relationships`
    - src/graph.rs does NOT exist (ls returns error)
    - `cargo check` exits 0 (no compilation errors)
  </acceptance_criteria>
  <done>All 5 production code submodules created, toposort as split impl block, mod.rs re-exports the full prior public API, old graph.rs deleted, cargo check passes</done>
</task>

<task type="auto">
  <name>Task 2: Distribute graph tests and create test_helpers.rs</name>
  <files>
    src/graph/test_helpers.rs
    src/graph/relationship.rs
    src/graph/facts.rs
    src/graph/derived_metrics.rs
    src/graph/using.rs
  </files>
  <read_first>
    src/graph.rs (read the #[cfg(test)] section starting at line 1108 to end-of-file via `git show HEAD:src/graph.rs` -- this is the source of all test code, since the original file was deleted in Task 1)
    src/graph/mod.rs (verify test_helpers module declaration)
    src/graph/relationship.rs (will receive graph validation tests)
    src/graph/facts.rs (will receive fact validation tests)
    src/graph/derived_metrics.rs (will receive derived metric tests)
    src/graph/using.rs (will receive USING validation tests)
  </read_first>
  <action>
    Distribute the ~1,225 lines of test code (lines 1108-2333 of the original graph.rs) into the appropriate submodule files, and create a shared test_helpers.rs module.

    NOTE: The original graph.rs was deleted in Task 1. Recover test code via `git show HEAD:src/graph.rs` or from executor's context window.

    **src/graph/test_helpers.rs** -- Shared test builder functions:
    - Extract all shared helper functions from the test section (lines ~1114-1168 of original graph.rs):
      - `pub(super) fn make_def(...)` -- builds a basic SemanticViewDefinition for testing
      - `pub(super) fn make_def_with_facts(...)` -- builds a definition with facts
      - `pub(super) fn make_def_with_derived_metrics(...)` -- builds a definition with derived metrics
      - `pub(super) fn make_def_with_named_joins(...)` -- builds a definition with named joins (Phase 32)
    - Import: `use crate::model::*;`
    - All helpers are `pub(super)` -- visible to sibling submodule tests but not outside the graph module

    **Test distribution** -- Add `#[cfg(test)] mod tests { ... }` blocks to submodules:

    1. **relationship.rs** gets: graph validation tests (~380 lines) + phase33_fk_reference_tests (~215 lines) + diamond/named relationship tests (~70 lines)
       - These test `validate_graph`, `from_definition`, `check_no_diamonds`, `check_no_orphans`, `toposort` (via the public API), and `validate_fk_references` (via `validate_graph`)
       - Tests need: `use crate::graph::{validate_graph, RelationshipGraph};` and `use super::super::test_helpers::*;` (or `use crate::graph::test_helpers::*;`)
       - Note: toposort tests also belong here since they test via the public `RelationshipGraph::toposort()` method

    2. **facts.rs** gets: validate_facts tests (~200 lines) + find_fact_references tests (~60 lines)
       - These test `validate_facts`, `find_fact_references`, and internal fact DAG logic
       - Tests need: `use crate::graph::{validate_facts, find_fact_references};` and `use super::super::test_helpers::*;`

    3. **derived_metrics.rs** gets: contains_aggregate tests (~50 lines) + validate_derived_metrics tests (~150 lines)
       - These test `contains_aggregate_function` and `validate_derived_metrics`
       - Tests need: `use crate::graph::{contains_aggregate_function, validate_derived_metrics};` and `use super::super::test_helpers::*;`

    4. **using.rs** gets: validate_using tests (~80 lines)
       - These test `validate_using_relationships`
       - Tests need: `use crate::graph::validate_using_relationships;` and `use super::super::test_helpers::*;`

    **Import adjustments in test blocks:**
    - Replace `use super::*;` with explicit imports from `crate::graph::*` or `super::super::test_helpers::*`
    - Each test module imports the public functions it tests from `crate::graph::*` (through mod.rs re-exports)
    - Each test module imports shared helpers from `super::super::test_helpers::*` (up to mod.rs, then into test_helpers)

    **Do NOT change any test logic or assertions.** This is a pure move operation. Every `#[test]` function body stays identical.
  </action>
  <verify>
    <automated>cd /Users/paul/Documents/Dev/Personal/duckdb-semantic-views && cargo test --lib -- graph 2>&1 | tail -20</automated>
  </verify>
  <acceptance_criteria>
    - src/graph/test_helpers.rs exists and contains `pub(super) fn make_def(`
    - src/graph/test_helpers.rs contains `pub(super) fn make_def_with_facts(`
    - src/graph/test_helpers.rs contains `pub(super) fn make_def_with_derived_metrics(`
    - src/graph/test_helpers.rs is gated by `#[cfg(test)]` (declared in mod.rs as `#[cfg(test)] mod test_helpers;`)
    - src/graph/relationship.rs contains `#[cfg(test)]` and `mod tests`
    - src/graph/facts.rs contains `#[cfg(test)]` and `mod tests`
    - src/graph/derived_metrics.rs contains `#[cfg(test)]` and `mod tests`
    - src/graph/using.rs contains `#[cfg(test)]` and `mod tests`
    - `cargo test --lib -- graph` exits 0 with 59 tests passing (all original graph tests)
    - `cargo test` exits 0 (full unit test suite)
  </acceptance_criteria>
  <done>All 59 graph.rs tests distributed to their correct submodules, test_helpers.rs created with shared helpers, all tests pass with zero behavior changes</done>
</task>

</tasks>

<verification>
After both tasks complete:

1. `cargo check` passes (no compilation errors)
2. `cargo test --lib -- graph` passes all 59 graph tests
3. `cargo test` passes all 482+ tests
4. `ls src/graph.rs` returns "No such file or directory"
5. `ls src/graph/mod.rs src/graph/relationship.rs src/graph/toposort.rs src/graph/facts.rs src/graph/derived_metrics.rs src/graph/using.rs src/graph/test_helpers.rs` all exist
6. No external consumer files were modified (ddl/show_dims_for_metric.rs, ddl/define.rs, expand/sql_gen.rs unchanged)
</verification>

<success_criteria>
- src/graph/ module directory exists with 5 submodules + mod.rs + test_helpers.rs
- src/graph.rs deleted (no E0761 ambiguity)
- mod.rs re-exports: RelationshipGraph, validate_graph, find_fact_references, validate_facts, contains_aggregate_function, validate_derived_metrics, validate_using_relationships (all pub)
- All 59 graph tests pass in new locations
- cargo test passes with zero behavior changes
- No external import paths changed
</success_criteria>

<output>
After completion, create `.planning/phases/38-module-directory-splits/38-02-SUMMARY.md`
</output>
