---
phase: 50-code-quality-test-coverage
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - src/expand/join_resolver.rs
  - src/expand/fan_trap.rs
  - src/expand/facts.rs
  - src/expand/sql_gen.rs
autonomous: true
requirements: [QUAL-01, QUAL-06]
must_haves:
  truths:
    - "join_resolver.rs has unit tests covering synthesize_on_clause, synthesize_on_clause_scoped, and resolve_joins_pkfk"
    - "fan_trap.rs has unit tests covering check_fan_traps, ancestors_to_root, validate_fact_table_path, and path_from_ancestor_to_node"
    - "facts.rs has additional unit tests covering collect_derived_metric_using, toposort_facts, inline_facts, and collect_derived_metric_source_tables"
    - "sql_gen.rs exact-match tests are converted to property assertions where appropriate, with one golden-file anchor retained"
  artifacts:
    - path: "src/expand/join_resolver.rs"
      provides: "Unit test module for join resolution functions"
      contains: "#[cfg(test)]"
    - path: "src/expand/fan_trap.rs"
      provides: "Unit test module for fan trap detection functions"
      contains: "#[cfg(test)]"
    - path: "src/expand/facts.rs"
      provides: "Additional unit tests for fact helper functions"
      contains: "collect_derived_metric_using"
    - path: "src/expand/sql_gen.rs"
      provides: "Property-based test assertions"
      contains: "assert!(sql.contains("
  key_links:
    - from: "src/expand/join_resolver.rs tests"
      to: "src/expand/test_helpers.rs"
      via: "TestFixtureExt builder methods"
      pattern: "TestFixtureExt"
    - from: "src/expand/fan_trap.rs tests"
      to: "src/expand/test_helpers.rs"
      via: "TestFixtureExt builder methods"
      pattern: "with_pkfk_join"
---

<objective>
Add comprehensive unit test coverage for three untested expand modules (join_resolver.rs, fan_trap.rs, facts.rs) and convert brittle exact-string test assertions in sql_gen.rs to structural property assertions.

Purpose: Establish regression safety net before refactoring in Plan 02. These tests will catch any behavioral changes introduced by the deduplication and newtype refactors.
Output: Test modules in join_resolver.rs, fan_trap.rs, additional tests in facts.rs, improved assertions in sql_gen.rs.
</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/50-code-quality-test-coverage/50-RESEARCH.md

@src/expand/mod.rs
@src/expand/test_helpers.rs
@src/expand/types.rs
@src/expand/resolution.rs

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

From src/expand/test_helpers.rs:
```rust
pub(super) fn orders_view() -> SemanticViewDefinition;
pub(super) fn minimal_def(base_table, dim_name, dim_expr, metric_name, metric_expr) -> SemanticViewDefinition;
pub(super) trait TestFixtureExt {
    fn with_base_table(self, base_table: &str) -> Self;
    fn with_dimension(self, name: &str, expr: &str, source_table: Option<&str>) -> Self;
    fn with_metric(self, name: &str, expr: &str, source_table: Option<&str>) -> Self;
    fn with_table(self, alias: &str, table: &str, pk_columns: &[&str]) -> Self;
    fn with_fact(self, name: &str, expr: &str, source_table: &str) -> Self;
    fn with_private_metric(self, name: &str, expr: &str, source_table: Option<&str>) -> Self;
    fn with_private_fact(self, name: &str, expr: &str, source_table: &str) -> Self;
    fn with_using_relationship(self, metric_name: &str, relationships: &[&str]) -> Self;
    fn with_pkfk_join(self, name: &str, from_alias: &str, to_alias: &str, fk_columns: &[&str], ref_columns: &[&str]) -> Self;
    fn with_non_additive_by(self, metric_name: &str, dims: &[(&str, SortOrder, NullsOrder)]) -> Self;
    fn with_window_spec(self, metric_name: &str, spec: WindowSpec) -> Self;
    fn clear_dimensions(self) -> Self;
    fn clear_metrics(self) -> Self;
}
```

From src/expand/types.rs:
```rust
pub struct QueryRequest {
    pub dimensions: Vec<String>,
    pub metrics: Vec<String>,
    pub facts: Vec<String>,
}
pub enum ExpandError {
    EmptyRequest, UnknownDimension, UnknownMetric, DuplicateDimension, DuplicateMetric,
    FanTrap, AmbiguousPath, PrivateMetric, PrivateFact, FactsMetricsMutualExclusion,
    UnknownFact, DuplicateFact, FactPathViolation, WindowAggregateMixing,
    WindowMetricRequiredDimension, CatalogPoisoned, CycleDetected, MaxDepthExceeded,
}
```

From src/expand/join_resolver.rs:
```rust
pub(super) fn synthesize_on_clause(join: &Join, tables: &[TableRef]) -> String;
pub(super) fn synthesize_on_clause_scoped(join: &Join, tables: &[TableRef], to_alias: &str) -> String;
pub(super) fn resolve_joins_pkfk(def: &SemanticViewDefinition, resolved_dims: &[&Dimension], resolved_mets: &[&Metric]) -> Vec<String>;
```

From src/expand/fan_trap.rs:
```rust
pub(super) fn check_fan_traps(view_name: &str, def: &SemanticViewDefinition, resolved_dims: &[&Dimension], resolved_mets: &[&Metric]) -> Result<(), ExpandError>;
pub(crate) fn ancestors_to_root(node: &str, parent_map: &HashMap<String, String>) -> Vec<String>;
pub(super) fn validate_fact_table_path(view_name: &str, def: &SemanticViewDefinition, fact_tables: &[String], dim_tables: &[String]) -> Result<(), ExpandError>;
fn path_from_ancestor_to_node(ancestor: &str, target: &str, target_ancestors: &[String]) -> Vec<String>;
fn check_path_up(...) -> Option<ExpandError>;
fn check_path_down(...) -> Option<ExpandError>;
```

From src/expand/facts.rs:
```rust
pub(super) fn collect_derived_metric_using(met: &Metric, all_metrics: &[Metric]) -> Vec<String>;
pub(super) fn toposort_facts(facts: &[Fact]) -> Result<Vec<usize>, String>;
pub(super) fn inline_facts(expr: &str, facts: &[Fact], topo_order: &[usize]) -> String;
fn toposort_derived(derived: &[(usize, &Metric)], resolved_names: &HashMap<String, String>) -> Result<Vec<usize>, String>;
pub(super) fn inline_derived_metrics(metrics: &[Metric], facts: &[Fact], fact_topo_order: &[usize]) -> Result<HashMap<String, String>, String>;
pub(crate) fn collect_derived_metric_source_tables(met: &Metric, all_metrics: &[Metric]) -> Vec<String>;
```

From src/model.rs (key types for tests):
```rust
pub struct Join { pub table: String, pub from_alias: String, pub fk_columns: Vec<String>, pub ref_columns: Vec<String>, pub name: Option<String>, pub cardinality: Cardinality, ... }
pub enum Cardinality { ManyToOne, OneToOne }
pub struct Fact { pub name: String, pub expr: String, pub source_table: Option<String>, pub access: AccessModifier, ... }
pub enum AccessModifier { Public, Private }
pub struct NonAdditiveDim { pub dimension: String, pub order: SortOrder, pub nulls: NullsOrder }
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add unit tests for join_resolver.rs and fan_trap.rs</name>
  <files>src/expand/join_resolver.rs, src/expand/fan_trap.rs</files>
  <read_first>
    src/expand/join_resolver.rs
    src/expand/fan_trap.rs
    src/expand/test_helpers.rs
    src/expand/types.rs
    src/model.rs
    src/expand/mod.rs
  </read_first>
  <action>
Add `#[cfg(test)] mod tests { ... }` blocks at the bottom of both files.

**For join_resolver.rs** -- add a test module with these test functions:

1. `test_synthesize_on_clause_single_column` -- Create a Join with `from_alias="orders"`, `table="customers"`, `fk_columns=["customer_id"]`, `ref_columns=[]`. Create a TableRef for "customers" with `pk_columns=["id"]`. Call `synthesize_on_clause(&join, &tables)`. Assert result equals `"\"orders\".\"customer_id\" = \"customers\".\"id\""`.

2. `test_synthesize_on_clause_composite_keys` -- Create a Join with `fk_columns=["a", "b"]` and a target table with `pk_columns=["x", "y"]`. Assert the result contains both `= "target"."x"` and `AND` and `= "target"."y"`.

3. `test_synthesize_on_clause_empty_fk_columns` -- Join with empty `fk_columns`, empty `ref_columns`. Assert result is an empty string.

4. `test_synthesize_on_clause_scoped_uses_to_alias` -- Call `synthesize_on_clause_scoped` with `to_alias="airports__dep_airport"`. Assert the generated ON clause uses `"airports__dep_airport"` as the right side, not `join.table`.

5. `test_synthesize_on_clause_scoped_prefers_ref_columns` -- Join with non-empty `ref_columns=["code"]` and a target table with `pk_columns=["id"]`. Assert result uses `"code"` (from ref_columns), not `"id"` (from pk_columns).

6. `test_synthesize_on_clause_scoped_falls_back_to_pk` -- Join with empty `ref_columns` and target table with `pk_columns=["id"]`. Assert result uses `"id"` (fallback to pk_columns).

7. `test_resolve_joins_pkfk_no_joins` -- Use `orders_view()` (no joins). Resolve dims and metrics. Assert `resolve_joins_pkfk` returns empty Vec.

8. `test_resolve_joins_pkfk_single_join` -- Build a multi-table def using `orders_view().with_table("orders", "orders", &["id"]).with_table("customers", "customers", &["id"]).with_dimension("cust_name", "name", Some("customers")).with_pkfk_join("orders_customers", "orders", "customers", &["customer_id"], &["id"])`. Resolve dims including "cust_name". Assert result contains `"customers"`.

9. `test_resolve_joins_pkfk_with_using_relationship` -- Build a def with two joins to same table (role-playing). Add a metric with `using_relationships`. Assert result contains scoped alias like `"airports__dep_airport"`.

For imports, use:
```rust
use super::*;
use crate::expand::test_helpers::{orders_view, TestFixtureExt};
use crate::model::{Cardinality, Join, TableRef};
```

Note: `resolve_joins_pkfk` takes `&[&Dimension]` and `&[&Metric]`, so you need to create references from the definition's owned items, e.g., `let resolved_dims: Vec<&_> = def.dimensions.iter().collect();`.

**For fan_trap.rs** -- add a test module with these test functions:

1. `test_ancestors_to_root_at_root` -- Empty parent_map. `ancestors_to_root("root", &map)` returns `["root"]`.

2. `test_ancestors_to_root_single_parent` -- parent_map: `"child" -> "root"`. Result = `["child", "root"]`.

3. `test_ancestors_to_root_multi_level` -- parent_map: `"leaf" -> "mid", "mid" -> "root"`. Result = `["leaf", "mid", "root"]`.

4. `test_check_fan_traps_no_joins_ok` -- Use `orders_view()` (no joins). Assert `check_fan_traps("test", &def, &dims, &mets)` is `Ok(())`.

5. `test_check_fan_traps_many_to_one_safe_direction` -- Build a def: orders (root) -> customers (ManyToOne from orders). Metric on orders, dimension on customers. This traverses FK direction (safe). Assert `Ok(())`.

6. `test_check_fan_traps_many_to_one_fan_out` -- Build a def: orders (root) -> line_items (ManyToOne from line_items to orders, meaning line_items has FK to orders). Metric on orders, dimension on line_items. Walking from orders down to line_items reverses the edge, causing fan-out. Assert `Err(FanTrap { .. })`.

7. `test_check_fan_traps_one_to_one_safe` -- Build a def with OneToOne cardinality join. Assert `Ok(())`.

8. `test_check_fan_traps_skips_semi_additive` -- Build a def with a fan-out scenario, but the metric has `non_additive_by` set. Assert `Ok(())` (semi-additive skip).

9. `test_check_fan_traps_skips_window` -- Build a def with a fan-out scenario, but the metric has `window_spec` set. Assert `Ok(())` (window skip).

10. `test_validate_fact_table_path_single_table_ok` -- Single table in both fact_tables and dim_tables. Assert `Ok(())`.

11. `test_validate_fact_table_path_ancestor_descendant_ok` -- Two tables on same root-to-leaf path. Assert `Ok(())`.

12. `test_validate_fact_table_path_divergent_tables_err` -- Two tables that are siblings (neither ancestor of the other). Assert `Err(FactPathViolation { .. })`.

13. `test_validate_fact_table_path_no_joins_ok` -- No joins in def. Assert `Ok(())`.

For fan_trap.rs test imports:
```rust
use super::*;
use crate::expand::test_helpers::{orders_view, minimal_def, TestFixtureExt};
use crate::model::{Cardinality, NonAdditiveDim, SortOrder, NullsOrder, WindowSpec};
```

To test `check_fan_traps` with specific cardinalities, you need to manually set `join.cardinality` on the join. The `with_pkfk_join` helper creates joins with default cardinality (`ManyToOne`). After building the def with `with_pkfk_join`, mutate `def.joins.last_mut().unwrap().cardinality = Cardinality::OneToOne` for the OneToOne test.
  </action>
  <verify>
    <automated>cd /Users/paul/Documents/Dev/Personal/duckdb-semantic-views && cargo test expand::join_resolver --lib 2>&1 | tail -5 && cargo test expand::fan_trap --lib 2>&1 | tail -5</automated>
  </verify>
  <acceptance_criteria>
    - join_resolver.rs contains `#[cfg(test)]` and `mod tests`
    - join_resolver.rs contains at least 7 test functions: `test_synthesize_on_clause_single_column`, `test_synthesize_on_clause_composite_keys`, `test_synthesize_on_clause_empty_fk_columns`, `test_synthesize_on_clause_scoped_uses_to_alias`, `test_synthesize_on_clause_scoped_prefers_ref_columns`, `test_resolve_joins_pkfk_no_joins`, `test_resolve_joins_pkfk_single_join`
    - fan_trap.rs contains `#[cfg(test)]` and `mod tests`
    - fan_trap.rs contains at least 10 test functions: `test_ancestors_to_root_at_root`, `test_ancestors_to_root_single_parent`, `test_ancestors_to_root_multi_level`, `test_check_fan_traps_no_joins_ok`, `test_check_fan_traps_many_to_one_safe_direction`, `test_check_fan_traps_many_to_one_fan_out`, `test_check_fan_traps_skips_semi_additive`, `test_check_fan_traps_skips_window`, `test_validate_fact_table_path_single_table_ok`, `test_validate_fact_table_path_divergent_tables_err`
    - `cargo test expand::join_resolver --lib` exits 0 with all tests passing
    - `cargo test expand::fan_trap --lib` exits 0 with all tests passing
  </acceptance_criteria>
  <done>join_resolver.rs has 7+ unit tests covering all 3 public functions. fan_trap.rs has 10+ unit tests covering ancestors_to_root, check_fan_traps (including semi-additive and window skip), and validate_fact_table_path.</done>
</task>

<task type="auto">
  <name>Task 2: Add unit tests for untested facts.rs functions</name>
  <files>src/expand/facts.rs</files>
  <read_first>
    src/expand/facts.rs
    src/expand/test_helpers.rs
    src/model.rs
  </read_first>
  <action>
Add tests to the existing `#[cfg(test)] mod tests` block in facts.rs. The file already has 7 tests covering `toposort_derived`, `inline_derived_metrics`, and `MAX_DERIVATION_DEPTH`. Add tests for the 4 untested functions.

**For `collect_derived_metric_using`:**

1. `test_collect_derived_metric_using_base_with_using` -- Create a base metric with `source_table=Some("flights")` and `using_relationships=["dep_airport"]`. Call `collect_derived_metric_using(&met, &[met.clone()])`. Assert result contains `"dep_airport"`.

2. `test_collect_derived_metric_using_derived_transitive` -- Create base metric `base_met` with `using_relationships=["rel1"]` and derived metric `derived` with expr referencing `base_met` name. Call `collect_derived_metric_using(&derived, &[base_met, derived.clone()])`. Assert result contains `"rel1"`.

3. `test_collect_derived_metric_using_no_using` -- Base metric with empty `using_relationships`. Assert result is empty.

For the `make_metric` helper already in the test module, extend it or create a new helper:
```rust
fn make_metric_with_using(name: &str, expr: &str, source_table: Option<&str>, using: &[&str]) -> Metric {
    let mut m = make_metric(name, expr, source_table);
    m.using_relationships = using.iter().map(|s| s.to_string()).collect();
    m
}
```

**For `toposort_facts`:**

4. `test_toposort_facts_empty` -- Empty slice. Assert `Ok(vec![])`.

5. `test_toposort_facts_single` -- One fact with no dependencies. Assert `Ok(vec![0])`.

6. `test_toposort_facts_chain` -- Two facts: `net_price = "price * (1 - discount)"`, `total = "net_price * quantity"`. `total` depends on `net_price`, so `net_price` must come first. Assert the order has `net_price`'s index before `total`'s index.

7. `test_toposort_facts_independent` -- Two facts with no inter-dependencies. Assert both appear in result (order doesn't matter).

8. `test_toposort_facts_cycle` -- Two facts each referencing the other. Assert `Err(...)` containing "cycle".

Use `Fact` struct directly:
```rust
fn make_fact(name: &str, expr: &str) -> Fact {
    Fact {
        name: name.to_string(),
        expr: expr.to_string(),
        source_table: Some("t".to_string()),
        output_type: None,
        comment: None,
        synonyms: vec![],
        access: AccessModifier::Public,
    }
}
```

**For `inline_facts`:**

9. `test_inline_facts_empty_facts` -- No facts. Assert input expr returned unchanged.

10. `test_inline_facts_single_substitution` -- Fact `net_price = "price * (1 - discount)"`. Expr = `"SUM(net_price)"`. Topo order = `[0]`. Assert result = `"SUM((price * (1 - discount)))"`.

11. `test_inline_facts_chained_substitution` -- Two facts in topo order. Assert inner fact is resolved before outer fact is applied.

12. `test_inline_facts_qualified_form` -- Fact with source_table="o", name="net_price". Expr = `"SUM(o.net_price)"`. Assert the qualified form `o.net_price` is replaced.

**For `collect_derived_metric_source_tables`:**

13. `test_collect_source_tables_base_metric` -- Base metric with `source_table=Some("orders")`. Assert result contains `"orders"`.

14. `test_collect_source_tables_derived_transitive` -- Derived metric referencing a base metric. Assert result contains the base metric's source table.

15. `test_collect_source_tables_cycle_handling` -- Two metrics referencing each other (defensive). Assert the function terminates (visited set prevents infinite loop) and returns whatever sources were found.
  </action>
  <verify>
    <automated>cd /Users/paul/Documents/Dev/Personal/duckdb-semantic-views && cargo test expand::facts --lib 2>&1 | tail -10</automated>
  </verify>
  <acceptance_criteria>
    - facts.rs contains at least 22 total test functions (7 existing + 15 new)
    - facts.rs contains `test_collect_derived_metric_using_base_with_using`
    - facts.rs contains `test_toposort_facts_empty`
    - facts.rs contains `test_toposort_facts_cycle`
    - facts.rs contains `test_inline_facts_single_substitution`
    - facts.rs contains `test_inline_facts_qualified_form`
    - facts.rs contains `test_collect_source_tables_base_metric`
    - facts.rs contains `test_collect_source_tables_derived_transitive`
    - `cargo test expand::facts --lib` exits 0 with all tests passing
  </acceptance_criteria>
  <done>facts.rs has 15+ new tests covering collect_derived_metric_using, toposort_facts, inline_facts, and collect_derived_metric_source_tables. Total test count is 22+.</done>
</task>

<task type="auto">
  <name>Task 3: Convert sql_gen.rs exact-match tests to property assertions</name>
  <files>src/expand/sql_gen.rs</files>
  <read_first>
    src/expand/sql_gen.rs
    src/expand/test_helpers.rs
    src/expand/types.rs
  </read_first>
  <action>
In the `expand_tests` submodule of `sql_gen.rs`, convert 4 of the 5 exact-match tests to property-based assertions. Keep `test_basic_single_dimension_single_metric` as the single golden-file regression anchor (it stays with `assert_eq!(sql, expected)`).

**Convert `test_multiple_dimensions_multiple_metrics` (line 528):**
Replace `assert_eq!(sql, expected)` with:
```rust
assert!(sql.starts_with("SELECT\n"), "Should start with SELECT");
assert!(sql.contains("region AS \"region\""), "Should include region dim");
assert!(sql.contains("status AS \"status\""), "Should include status dim");
assert!(sql.contains("sum(amount) AS \"total_revenue\""), "Should include revenue metric");
assert!(sql.contains("count(*) AS \"order_count\""), "Should include count metric");
assert!(sql.contains("FROM \"orders\""), "Should reference orders table");
assert!(sql.contains("GROUP BY"), "Should have GROUP BY");
assert!(sql.contains("1,"), "Should group by ordinal 1");
assert!(sql.contains("2"), "Should group by ordinal 2");
```

**Convert `test_global_aggregate_no_dimensions` (line 550):**
Replace `assert_eq!(sql, expected)` with:
```rust
assert!(sql.starts_with("SELECT\n"), "Should start with SELECT");
assert!(sql.contains("sum(amount) AS \"total_revenue\""), "Should include revenue metric");
assert!(sql.contains("FROM \"orders\""), "Should reference orders table");
assert!(!sql.contains("GROUP BY"), "No GROUP BY when no dimensions");
```

**Convert `test_dimensions_only_generates_distinct` (line 617):**
Replace `assert_eq!(sql, expected)` with:
```rust
assert!(sql.starts_with("SELECT DISTINCT\n"), "Should start with SELECT DISTINCT");
assert!(sql.contains("region AS \"region\""), "Should include region dim");
assert!(sql.contains("status AS \"status\""), "Should include status dim");
assert!(sql.contains("FROM \"orders\""), "Should reference orders table");
assert!(!sql.contains("GROUP BY"), "No GROUP BY for dims-only (DISTINCT instead)");
```

**Convert `test_metrics_only_still_works` (line 634):**
Replace `assert_eq!(sql, expected)` with:
```rust
assert!(sql.starts_with("SELECT\n"), "Should start with SELECT");
assert!(!sql.starts_with("SELECT DISTINCT"), "Should NOT be DISTINCT for metrics-only");
assert!(sql.contains("sum(amount) AS \"total_revenue\""), "Should include revenue metric");
assert!(sql.contains("count(*) AS \"order_count\""), "Should include count metric");
assert!(sql.contains("FROM \"orders\""), "Should reference orders table");
assert!(!sql.contains("GROUP BY"), "No GROUP BY when no dimensions");
```

Do NOT modify `test_basic_single_dimension_single_metric` -- it remains the golden-file anchor with its `assert_eq!` comparison.
  </action>
  <verify>
    <automated>cd /Users/paul/Documents/Dev/Personal/duckdb-semantic-views && cargo test expand::sql_gen::tests --lib 2>&1 | tail -5</automated>
  </verify>
  <acceptance_criteria>
    - sql_gen.rs `test_basic_single_dimension_single_metric` still contains `assert_eq!(sql, expected)` (golden file anchor preserved)
    - sql_gen.rs `test_multiple_dimensions_multiple_metrics` contains `assert!(sql.contains(` instead of `assert_eq!(sql, expected)`
    - sql_gen.rs `test_global_aggregate_no_dimensions` contains `assert!(sql.contains(` instead of `assert_eq!(sql, expected)`
    - sql_gen.rs `test_dimensions_only_generates_distinct` contains `assert!(sql.starts_with("SELECT DISTINCT` instead of `assert_eq!(sql, expected)`
    - sql_gen.rs `test_metrics_only_still_works` contains `assert!(sql.contains(` instead of `assert_eq!(sql, expected)`
    - `cargo test expand::sql_gen::tests --lib` exits 0 with all 95 tests passing
  </acceptance_criteria>
  <done>4 of 5 exact-match tests converted to property assertions. 1 golden-file anchor retained. All 95 sql_gen tests pass.</done>
</task>

</tasks>

<threat_model>
## Trust Boundaries

No new trust boundaries introduced. This plan adds unit tests and modifies test assertions only. No production code logic, DDL parsing, or FFI paths are changed.

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-50-01 | T (Tampering) | Test assertions | accept | Tests are internal quality checks; weakening assertions is low-risk since the golden-file anchor is preserved and `just test-all` remains the quality gate |
</threat_model>

<verification>
```bash
# Per-task verification
cargo test expand::join_resolver --lib
cargo test expand::fan_trap --lib
cargo test expand::facts --lib
cargo test expand::sql_gen::tests --lib

# Full suite (wave gate)
just test-all
```
</verification>

<success_criteria>
1. `cargo test expand::join_resolver --lib` shows 7+ tests passing
2. `cargo test expand::fan_trap --lib` shows 10+ tests passing
3. `cargo test expand::facts --lib` shows 22+ tests passing
4. `cargo test expand::sql_gen::tests --lib` shows 95 tests passing with only 1 exact-match test remaining
5. `just test-all` passes
</success_criteria>

<output>
After completion, create `.planning/phases/50-code-quality-test-coverage/50-01-SUMMARY.md`
</output>
