---
phase: 03-expansion-engine
plan: "03"
type: execute
wave: 3
depends_on:
  - "03-02"
files_modified:
  - Cargo.toml
  - tests/expand_proptest.rs
autonomous: true
requirements:
  - TEST-02

must_haves:
  truths:
    - "For any valid dimension+metric subset, all requested dimensions appear in the GROUP BY section of the emitted SQL"
    - "For any valid dimension+metric subset, the emitted SQL contains SELECT, FROM, and (if dimensions non-empty) GROUP BY keywords"
    - "Empty dimension subset produces SQL without GROUP BY"
    - "Property tests run via cargo test and pass consistently (no flaky failures)"
  artifacts:
    - path: "tests/expand_proptest.rs"
      provides: "Property-based tests for expansion invariants using proptest"
      contains: "proptest!"
    - path: "Cargo.toml"
      provides: "proptest dev-dependency"
      contains: "proptest"
  key_links:
    - from: "tests/expand_proptest.rs"
      to: "src/expand.rs"
      via: "uses expand(), QueryRequest, SemanticViewDefinition"
      pattern: "semantic_views::expand"
    - from: "tests/expand_proptest.rs"
      to: "proptest"
      via: "proptest! macro and subsequence strategy"
      pattern: "proptest::prelude"
---

<objective>
Add property-based tests (proptest) to verify expansion engine invariants hold for arbitrary valid dimension+metric combinations.

Purpose: Unit tests cover specific known-answer cases, but the expansion engine must handle any valid combination of dimensions and metrics correctly. Property-based tests generate thousands of random valid subsets and verify structural invariants (GROUP BY includes all dimensions, SQL has correct structure), catching edge cases that hand-written tests miss.

Output: `tests/expand_proptest.rs` integration test file with proptest strategies and invariant assertions. `proptest` added as a dev-dependency.
</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/03-expansion-engine/03-CONTEXT.md
@.planning/phases/03-expansion-engine/03-RESEARCH.md
@.planning/phases/03-expansion-engine/03-01-SUMMARY.md
@.planning/phases/03-expansion-engine/03-02-SUMMARY.md
@src/expand.rs
@src/model.rs
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add proptest dev-dependency and create test fixtures</name>
  <files>Cargo.toml, tests/expand_proptest.rs</files>
  <action>
**Cargo.toml:**
- Add `proptest = "1.9"` to `[dev-dependencies]` section (create section if it only has cargo-husky)

**tests/expand_proptest.rs creation:**

Create an integration test file (in the `tests/` directory, not in src/) that tests expansion invariants using proptest.

Define test fixture definitions as constants or helper functions:

1. `simple_definition()` -- base_table "orders", 3 dimensions (region, month with expr `date_trunc('month', created_at)`, status), 3 metrics (total_revenue with expr `sum(amount)`, order_count with expr `count(*)`, avg_amount with expr `avg(amount)`), 1 filter ("status = 'active'"), no joins. No source_table on any dim/metric.

2. `joined_definition()` -- base_table "orders", 4 dimensions (region, customer_name with source_table "customers", month, product_category with source_table "products"), 3 metrics (total_revenue, customer_count with source_table "customers" and expr `count(DISTINCT customer_id)`, product_count with source_table "products" and expr `count(DISTINCT product_id)`), 2 joins (customers ON "orders"."customer_id" = "customers"."id", products ON "orders"."product_id" = "products"."id"), 1 filter. Use the full `SemanticViewDefinition` struct (not JSON parsing) to build these.

Define a helper function `arb_query_request(def: &SemanticViewDefinition) -> impl Strategy<Value = QueryRequest>`:
- Use `proptest::sample::subsequence` to generate a subset of dimension names (0 to all)
- Use `proptest::sample::subsequence` to generate a non-empty subset of metric names (1 to all)
- Combine with `prop_map` into a `QueryRequest`

**Important proptest note:** `proptest::sample::subsequence` requires a `Vec` of clonable items and a range. It produces a `Vec` containing a random subsequence of the input. This is perfect for generating valid dimension/metric subsets.
  </action>
  <verify>
    <automated>cargo test --test expand_proptest -- --list 2>&1 | head -20</automated>
    <manual>Test file compiles and proptest discovers test functions</manual>
  </verify>
  <done>
- proptest 1.9 added to Cargo.toml dev-dependencies
- tests/expand_proptest.rs exists with fixture definitions and arb_query_request strategy
- File compiles without errors
  </done>
</task>

<task type="auto">
  <name>Task 2: Implement property-based tests for expansion invariants</name>
  <files>tests/expand_proptest.rs</files>
  <action>
Add the following property-based tests using the `proptest!` macro:

**Property 1: `all_dimensions_in_group_by`**
- Strategy: `arb_query_request(&simple_definition())`
- Expand with the generated request
- If dimensions is empty: assert SQL does NOT contain "GROUP BY"
- If dimensions is non-empty: for each requested dimension name, find the definition dimension, and assert its `expr` appears in the text after "GROUP BY" in the SQL output
- This is the core GROUP BY inference invariant (EXPAND-01)

**Property 2: `all_dimensions_in_select`**
- Strategy: `arb_query_request(&simple_definition())`
- Expand with the generated request
- For each requested dimension name, assert `AS "{dim_name}"` appears in the SQL output
- For each requested metric name, assert `AS "{metric_name}"` appears in the SQL output
- This verifies the SELECT clause includes all requested columns

**Property 3: `sql_structure_valid`**
- Strategy: `arb_query_request(&simple_definition())`
- Expand with the generated request
- Assert SQL starts with `WITH "_base" AS (`
- Assert SQL contains `FROM "_base"`
- Assert SQL contains `SELECT`
- If dimensions non-empty, assert SQL contains `GROUP BY`
- This verifies basic SQL structural validity

**Property 4: `joins_only_when_needed`**
- Strategy: `arb_query_request(&joined_definition())`
- Expand with the generated request
- For each join table in the definition, check if any requested dimension/metric has that source_table. If none do, assert the SQL does NOT contain `JOIN "{table_name}"`.
- This verifies EXPAND-02 (only needed joins are included)

**Property 5: `filters_always_present`**
- Strategy: `arb_query_request(&simple_definition())`
- Expand with the generated request
- Assert the SQL contains every filter expression from the definition (since filters are always applied)
- This verifies MODEL-04 (filters always applied)

**Property 6: `global_aggregate_no_group_by`** (regression guard)
- Specifically test with empty dimensions (use `Just(QueryRequest { dimensions: vec![], metrics: vec!["total_revenue".into()] })`)
- Assert SQL does NOT contain "GROUP BY"
- Assert SQL DOES contain the metric expression

**proptest config:** Use default config (256 cases per property). This is sufficient for the dimension/metric subset space.

Run the tests and verify they all pass. If any fail, investigate and fix the expand() implementation (should be solid from Plans 01+02, but proptest may find edge cases).
  </action>
  <verify>
    <automated>cargo test --test expand_proptest 2>&1 | tail -20</automated>
    <manual>All 6 property tests pass with 256 cases each. No failures or shrinking reported.</manual>
  </verify>
  <done>
- 6 property-based tests verify expansion invariants
- GROUP BY contains all requested dimensions (or is absent for global aggregates)
- SELECT contains all requested dimension and metric aliases
- SQL structure is valid (WITH, SELECT, FROM present)
- Joins only included when source_table references require them
- Filters always present regardless of dimension/metric selection
- All proptest tests pass with 256 cases each
- cargo test runs all tests (unit + proptest) cleanly
  </done>
</task>

</tasks>

<verification>
```bash
# Property tests pass
cargo test --test expand_proptest

# All unit tests still pass (regression)
cargo test --lib

# Full test suite green
cargo test

# Clippy clean
cargo clippy -- -D warnings
```
</verification>

<success_criteria>
- proptest dev-dependency added to Cargo.toml
- 6 property-based tests cover all expansion invariants
- Tests pass consistently (no flaky failures)
- Full test suite (`cargo test`) passes including unit tests and property tests
- `cargo clippy` passes cleanly
</success_criteria>

<output>
After completion, create `.planning/phases/03-expansion-engine/03-03-SUMMARY.md`
</output>
