---
phase: 32-role-playing-using
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - src/model.rs
  - src/body_parser.rs
  - src/graph.rs
  - src/ddl/define.rs
  - tests/parse_proptest.rs
autonomous: true
requirements: [JOIN-01, JOIN-02, JOIN-04]

must_haves:
  truths:
    - "Multiple named relationships to the same table are accepted at define time (diamond rejection relaxed)"
    - "Metrics can declare USING (relationship_name) in DDL and the relationship name is stored in the model"
    - "USING references to non-existent relationships produce a clear define-time error"
    - "Derived metrics with USING produce a define-time error (Snowflake constraint)"
    - "Old definitions without using_relationships deserialize with empty Vec (backward compat)"
  artifacts:
    - path: "src/model.rs"
      provides: "using_relationships Vec<String> field on Metric struct"
      contains: "using_relationships"
    - path: "src/body_parser.rs"
      provides: "USING clause parsing in parse_single_metric_entry"
      contains: "USING"
    - path: "src/graph.rs"
      provides: "Relaxed diamond check + validate_using_relationships"
      contains: "validate_using_relationships"
    - path: "src/ddl/define.rs"
      provides: "Call to validate_using_relationships in bind()"
      contains: "validate_using_relationships"
  key_links:
    - from: "src/body_parser.rs"
      to: "src/model.rs"
      via: "parse_metrics_clause returns Vec with using_relationships"
      pattern: "using_relationships"
    - from: "src/ddl/define.rs"
      to: "src/graph.rs"
      via: "validate_using_relationships called after validate_derived_metrics"
      pattern: "validate_using_relationships"
    - from: "src/graph.rs"
      to: "src/model.rs"
      via: "check_no_diamonds reads Join.name for named relationship detection"
      pattern: "j\\.name"
---

<objective>
Add USING clause support to the metric model and parser, relax diamond rejection for named multi-path relationships, and validate USING references at define time.

Purpose: This plan establishes the data model, parsing, and validation foundations that the expansion engine (Plan 02) will build on. After this plan, users can declare `f.departure_count USING (dep_airport) AS COUNT(*)` in DDL and the system stores the relationship reference, validates it exists, and allows multiple named relationships to the same target table.

Output: Extended Metric model with using_relationships, USING clause parsing in body_parser, relaxed diamond check in graph.rs, and validate_using_relationships validation function.
</objective>

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

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/32-role-playing-using/32-RESEARCH.md

<interfaces>
<!-- Key types and contracts the executor needs. Extracted from codebase. -->

From src/model.rs:
```rust
pub struct Metric {
    pub name: String,
    pub expr: String,
    pub source_table: Option<String>,
    pub output_type: Option<String>,
    // Phase 32 adds: pub using_relationships: Vec<String>,
}

pub struct Join {
    pub table: String,            // to_alias
    pub on: String,
    pub from_cols: Vec<String>,
    pub join_columns: Vec<JoinColumn>,
    pub from_alias: String,
    pub fk_columns: Vec<String>,
    pub name: Option<String>,     // relationship name
    pub cardinality: Cardinality,
}
```

From src/body_parser.rs:
```rust
// Current return type for parse_metrics_clause:
pub(crate) fn parse_metrics_clause(
    body: &str,
    base_offset: usize,
) -> Result<Vec<(Option<String>, String, String)>, ParseError>

// parse_single_metric_entry returns (source_alias, bare_name, expr)
fn parse_single_metric_entry(
    entry: &str,
    entry_offset: usize,
) -> Result<(Option<String>, String, String), ParseError>

// Existing helper used for keyword detection:
fn find_keyword_ci(text: &str, keyword: &str) -> Option<usize>
```

From src/graph.rs:
```rust
pub struct RelationshipGraph {
    pub edges: HashMap<String, Vec<String>>,
    pub reverse: HashMap<String, Vec<String>>,
    pub all_nodes: HashSet<String>,
    pub root: String,
}

impl RelationshipGraph {
    pub fn check_no_diamonds(&self) -> Result<(), String>
}

pub fn validate_graph(def: &SemanticViewDefinition) -> Result<RelationshipGraph, String>
```

From src/ddl/define.rs (bind validation chain, lines 115-128):
```rust
crate::graph::validate_graph(&def)?;
crate::graph::validate_facts(&def)?;
crate::graph::validate_hierarchies(&def)?;
crate::graph::validate_derived_metrics(&def)?;
// Phase 32: crate::graph::validate_using_relationships(&def)?;
```
</interfaces>
</context>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: Add using_relationships to Metric model and extend parser</name>
  <files>src/model.rs, src/body_parser.rs, tests/parse_proptest.rs</files>
  <behavior>
    - Test: Metric with using_relationships serializes and deserializes correctly
    - Test: Old JSON without using_relationships field deserializes with empty Vec
    - Test: Metric with empty using_relationships does not emit the field in JSON (skip_serializing_if)
    - Test: parse_single_metric_entry parses `f.departure_count USING (dep_airport) AS COUNT(*)` into (Some("f"), "departure_count", "COUNT(*)", vec!["dep_airport"])
    - Test: parse_single_metric_entry parses `f.met USING (rel1, rel2) AS SUM(x)` with multiple relationships
    - Test: Derived metric (no dot prefix) with USING produces ParseError
    - Test: Metric without USING still parses correctly (backward compat)
    - Test: USING keyword is case-insensitive
    - Test: Proptest for USING clause parsing with adversarial identifiers
  </behavior>
  <action>
1. In `src/model.rs`, add `using_relationships: Vec<String>` to the `Metric` struct with `#[serde(default, skip_serializing_if = "Vec::is_empty")]`. This matches the pattern used by `hierarchies` on `SemanticViewDefinition` and `cardinality` on `Join`.

2. In `src/body_parser.rs`, modify `parse_single_metric_entry()`:
   - After finding `AS` keyword position and extracting `before_as`, check for `USING` keyword in `before_as` (case-insensitive, word boundary via `find_keyword_ci`).
   - If `USING` is found in a qualified entry (has dot): extract the parenthesized relationship list between `USING` and end of `before_as`. Split on commas, trim whitespace, remove outer parens. Store as `Vec<String>`.
   - If `USING` is found in an unqualified entry (derived metric): return `ParseError` -- derived metrics must not have USING (Snowflake constraint).
   - The name portion is the part of `before_as` between the dot and the USING keyword (or end of `before_as` if no USING).

3. Change `parse_metrics_clause()` return type from `Vec<(Option<String>, String, String)>` to `Vec<(Option<String>, String, String, Vec<String>)>` adding the using_relationships list as the 4th tuple element.

4. Update ALL callers of `parse_metrics_clause()` to handle the new 4-tuple. Search codebase for all call sites (likely in `src/body_parser.rs` integration tests and `src/ddl/define.rs` or wherever metrics are constructed from parsed output). The using_relationships from the parse result must be set on the `Metric` model when constructing the definition.

5. Add unit tests in `src/model.rs` (phase32 test module) for serde roundtrip of using_relationships.

6. Add unit tests in `src/body_parser.rs` for USING parsing (qualified, multiple, error on derived, case-insensitive, backward compat).

7. Add proptest in `tests/parse_proptest.rs` for USING clause: generate random relationship names, wrap in `alias.metric USING ({names}) AS SUM(x)`, verify parse succeeds and roundtrips.
  </action>
  <verify>
    <automated>cargo test -- --test-threads=1 2>&1 | tail -5</automated>
  </verify>
  <done>
    - Metric.using_relationships field exists with serde annotations
    - parse_metrics_clause returns 4-tuple with using_relationships
    - USING (rel_name) parsed from qualified metrics, rejected on derived metrics
    - Old JSON without using_relationships deserializes with empty Vec
    - All existing tests pass (no regression)
  </done>
</task>

<task type="auto" tdd="true">
  <name>Task 2: Relax diamond rejection and add USING validation</name>
  <files>src/graph.rs, src/ddl/define.rs</files>
  <behavior>
    - Test: Two named relationships to same table accepted by check_no_diamonds (no error)
    - Test: Two UNNAMED relationships to same table still rejected as diamond
    - Test: One named + one unnamed relationship to same table rejected as diamond
    - Test: validate_using_relationships passes when USING references existing named relationship
    - Test: validate_using_relationships rejects USING referencing non-existent relationship name
    - Test: validate_using_relationships rejects USING on derived metrics (source_table is None)
    - Test: validate_using_relationships checks that referenced relationship originates from metric's source_table
    - Test: Error message for unknown USING lists available relationship names
  </behavior>
  <action>
1. In `src/graph.rs`, modify `check_no_diamonds()`:
   - When a node has >1 parent in `self.reverse`, before returning Err, check whether ALL relationships pointing to that node are named with distinct names.
   - Look up the Join entries in the definition: filter `def.joins` where `j.table.to_ascii_lowercase() == node` and `!j.fk_columns.is_empty()`.
   - If ALL such joins have `j.name.is_some()` AND all names are unique (case-insensitive) -- allow it (this is role-playing).
   - Otherwise, return the existing diamond error.
   - Note: `check_no_diamonds()` currently takes `&self` only. It needs access to `def` to inspect Join names. Change signature to `check_no_diamonds(&self, def: &SemanticViewDefinition)` and update the single call site in `validate_graph()`.

2. In `src/graph.rs`, add `validate_using_relationships(def: &SemanticViewDefinition) -> Result<(), String>`:
   - For each metric with non-empty `using_relationships`:
     - If `metric.source_table.is_none()` (derived), return Err: "USING clause not allowed on derived metric '{name}'"
     - For each relationship name in `using_relationships`:
       - Check it exists in `def.joins` by matching `j.name` (case-insensitive)
       - If not found, return Err with available relationship names: "unknown relationship '{rel}' in USING clause of metric '{name}'. Available: [...]"
       - Check the relationship originates from the metric's source_table: `j.from_alias` must match `metric.source_table` (case-insensitive). If not, return Err: "relationship '{rel}' does not originate from table '{source}' (metric '{name}')"

3. In `src/ddl/define.rs`, add `crate::graph::validate_using_relationships(&def)?;` after the existing `validate_derived_metrics(&def)?;` call in `bind()`.

4. Add unit tests in `src/graph.rs` (phase32 test module):
   - Diamond relaxation: two named rels to same table -> Ok
   - Diamond rejection: unnamed rels -> Err
   - Diamond rejection: mixed named/unnamed -> Err
   - USING validation: valid reference -> Ok
   - USING validation: unknown relationship -> Err with suggestion
   - USING validation: wrong source table -> Err
   - USING validation: derived metric with USING -> Err
  </action>
  <verify>
    <automated>cargo test -- --test-threads=1 2>&1 | tail -5</automated>
  </verify>
  <done>
    - check_no_diamonds allows named multi-path relationships with distinct names
    - check_no_diamonds still rejects unnamed or duplicate-named diamonds
    - validate_using_relationships catches nonexistent and mismatched USING references
    - validate_using_relationships wired into define.rs bind() chain
    - All existing tests pass (no regression from diamond relaxation)
  </done>
</task>

</tasks>

<verification>
Run `cargo test` to verify all unit tests, proptests, and doc tests pass. This covers model serde, parser behavior, graph validation, and backward compatibility.

The full `just test-all` suite will be run after Plan 02 completes (sqllogictest needs the expansion engine changes from Plan 02 to exercise the full pipeline).
</verification>

<success_criteria>
- Metric model has using_relationships field with backward-compatible serde
- parse_metrics_clause returns 4-tuple including using_relationships
- USING clause parses correctly for qualified metrics and is rejected for derived metrics
- Diamond check allows multiple named relationships to same target table
- validate_using_relationships rejects unknown relationship names with clear error
- `cargo test` passes with zero failures
</success_criteria>

<output>
After completion, create `.planning/phases/32-role-playing-using/32-01-SUMMARY.md`
</output>
