---
phase: 01-atomic-replace-heal-scoping
plan: 02
type: execute
wave: 2
depends_on: ["01-01"]
files_modified:
  - python/opendeviationbar/sidecar.py
  - tests/test_heal_scoping.py
autonomous: true
requirements: [HEAL-01, HEAL-02]
must_haves:
  truths:
    - "_heal_day_boundary_gaps() only queries today's UTC bars, never historical"
    - "_check_and_heal_overlaps() call site uses valid_pairs, not config.symbols x config.thresholds"
  artifacts:
    - path: "python/opendeviationbar/sidecar.py"
      provides: "Scoped heal queries"
      contains: "toDate(open_time_ms/1000) = today()"
    - path: "tests/test_heal_scoping.py"
      provides: "Tests verifying heal scoping"
      min_lines: 30
  key_links:
    - from: "python/opendeviationbar/sidecar.py (_heal_day_boundary_gaps)"
      to: "ClickHouse query"
      via: "today() date filter in WHERE clause"
      pattern: "toDate.*today\\(\\)"
    - from: "python/opendeviationbar/sidecar.py (main loop)"
      to: "_check_and_heal_overlaps"
      via: "valid_pairs argument"
      pattern: "_check_and_heal_overlaps.*valid_pairs"
---

<objective>
Scope heal operations to today-only and valid streaming pairs. `_heal_day_boundary_gaps()` adds a `toDate(open_time_ms/1000) = today()` filter to its Stathera query. `_check_and_heal_overlaps()` call site passes `valid_pairs` instead of the cartesian product of `config.symbols x config.thresholds`.

Purpose: HEAL-01 and HEAL-02 -- heal operations never touch historical data (kintsugi's domain) and only operate on pairs that are actively streaming.
Output: Modified heal functions with today-only scoping, plus tests.
</objective>

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

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/01-atomic-replace-heal-scoping/01-CONTEXT.md
@.planning/phases/01-atomic-replace-heal-scoping/01-01-SUMMARY.md
@python/opendeviationbar/sidecar.py

<interfaces>
<!-- Key functions the executor needs to understand -->

From python/opendeviationbar/sidecar.py (lines 618-705) -- \_heal_day_boundary_gaps:

```python
def _heal_day_boundary_gaps(
    engine: object,
    valid_pairs: set[tuple[str, int]],
) -> None:
```

The Stathera query at lines 651-673 currently queries ALL bars for a (symbol, threshold) pair.
The WHERE clause uses `per_pair_filter()` which filters by symbol/threshold/ouroboros but does NOT filter by date.

From python/opendeviationbar/clickhouse/stathera_query.py:

```python
FULL_TABLE = "opendeviationbar_cache.open_deviation_bars"
per_pair_filter()  # Returns: WHERE symbol = {symbol} AND threshold = {threshold} AND ouroboros = {ouroboros}
per_pair_window()  # Returns: WINDOW w AS (ORDER BY first_agg_trade_id)
```

From python/opendeviationbar/sidecar.py (lines 2660-2680) -- \_check_and_heal_overlaps call site:

```python
pairs = [
    (s, t)
    for s in config.symbols
    for t in config.thresholds
]
healed = _check_and_heal_overlaps(pairs)
```

This should use `valid_pairs` (the set maintained in the main loop) instead of the raw cartesian product.

`valid_pairs` is already available in the main loop scope -- it is computed earlier and passed to other functions like `_sync_flush_rest_bars()` and `_heal_day_boundary_gaps()`.
</interfaces>
</context>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: Scope _heal_day_boundary_gaps query to today only (HEAL-01)</name>
  <files>python/opendeviationbar/sidecar.py, tests/test_heal_scoping.py</files>
  <read_first>
    - python/opendeviationbar/sidecar.py (lines 618-705: _heal_day_boundary_gaps)
    - python/opendeviationbar/clickhouse/stathera_query.py (per_pair_filter, per_pair_window, FULL_TABLE)
  </read_first>
  <behavior>
    - Test 1: The Stathera query string passed to cache.client.query() contains "AND toDate(open_time_ms/1000) = today()"
    - Test 2: The query still filters by symbol, threshold, and ouroboros_mode (existing behavior preserved)
  </behavior>
  <action>
    **Step 1: Create test file** `tests/test_heal_scoping.py`

    Mock `OpenDeviationBarCache` and its `client.query()` method. Capture the SQL string passed to `query()`. Assert it contains the today-only filter `toDate(open_time_ms/1000) = today()`.

    Also mock `filter_pairs_by_registry` to return the input pairs unchanged, and `get_operational_ouroboros_mode` to return `"day"`.

    **Step 2: Add today-only filter to _heal_day_boundary_gaps query**

    In `_heal_day_boundary_gaps()` (sidecar.py around line 661-664), add the date filter to the inner subquery WHERE clause. The current query has:

    ```sql
    FROM {FULL_TABLE} FINAL
    {per_pair_filter()}
    {per_pair_window()}
    ```

    Change to:

    ```sql
    FROM {FULL_TABLE} FINAL
    {per_pair_filter()}
    AND toDate(open_time_ms / 1000) = today()
    {per_pair_window()}
    ```

    This ensures the Stathera gap detection only sees today's bars. Historical gaps are kintsugi's domain.

    **Note**: `per_pair_filter()` returns a `WHERE ...` clause, so `AND toDate(...)` appends correctly to it. `per_pair_window()` returns `WINDOW w AS (...)` which must come after all WHERE/AND clauses.

  </action>
  <verify>
    <automated>cd /Users/terryli/eon/opendeviationbar-py && uv run --python 3.13 pytest tests/test_heal_scoping.py -x -v -k "heal_day" 2>&1 | tail -15</automated>
  </verify>
  <acceptance_criteria>
    - `grep -n "toDate(open_time_ms" python/opendeviationbar/sidecar.py` shows the today() filter inside _heal_day_boundary_gaps
    - `grep -c "today()" python/opendeviationbar/sidecar.py` returns at least 1 (the new filter)
    - Tests pass confirming the query string contains the date filter
  </acceptance_criteria>
  <done>
    _heal_day_boundary_gaps() Stathera query includes `AND toDate(open_time_ms/1000) = today()` filter. Historical bars are excluded from heal detection.
  </done>
</task>

<task type="auto">
  <name>Task 2: Fix _check_and_heal_overlaps call site to use valid_pairs (HEAL-02)</name>
  <files>python/opendeviationbar/sidecar.py, tests/test_heal_scoping.py</files>
  <read_first>
    - python/opendeviationbar/sidecar.py (lines 2660-2680: _check_and_heal_overlaps call site)
    - python/opendeviationbar/sidecar.py (lines 1849-1860: _check_and_heal_overlaps signature)
    - python/opendeviationbar/sidecar.py (search for "valid_pairs" to find where it's defined in the main loop)
  </read_first>
  <action>
    **Step 1: Find valid_pairs in the main loop scope**

    Search for where `valid_pairs` is assigned in the main event loop of `run_sidecar()`. It should be a `set[tuple[str, int]]` available in the same scope as the overlap heal block.

    **Step 2: Replace cartesian product with valid_pairs**

    At sidecar.py lines 2665-2669, replace:

    ```python
    pairs = [
        (s, t)
        for s in config.symbols
        for t in config.thresholds
    ]
    healed = _check_and_heal_overlaps(pairs)
    ```

    With:

    ```python
    healed = _check_and_heal_overlaps(
        list(valid_pairs) if valid_pairs else []
    )
    ```

    `valid_pairs` is already a `set[tuple[str, int]]` in scope. Converting to list satisfies the `list[tuple[str, int]]` parameter type of `_check_and_heal_overlaps`.

    If `valid_pairs` is not directly available at line 2665 (e.g., it's in a different scope level), trace the variable through the function and use whatever variable name holds the active streaming pairs at that point.

    **Step 3: Add test to test_heal_scoping.py**

    Add a test that verifies the overlap heal call site passes only valid_pairs, not the full cartesian product. This can be a targeted grep-based acceptance test or a mock-based unit test.

  </action>
  <verify>
    <automated>cd /Users/terryli/eon/opendeviationbar-py && uv run --python 3.13 pytest tests/test_heal_scoping.py -x -v 2>&1 | tail -15</automated>
  </verify>
  <acceptance_criteria>
    - `grep -B2 -A2 "_check_and_heal_overlaps" python/opendeviationbar/sidecar.py | grep -v "^def "` shows `valid_pairs` in the call, NOT `config.symbols` or `config.thresholds`
    - The cartesian product `for s in config.symbols for t in config.thresholds` is NOT present near the _check_and_heal_overlaps call
    - All tests pass
  </acceptance_criteria>
  <done>
    _check_and_heal_overlaps() call site in the main loop uses valid_pairs (actively streaming pairs) instead of the raw cartesian product of config.symbols x config.thresholds. Only pairs that have active engines are checked for overlaps.
  </done>
</task>

</tasks>

<verification>
```bash
# 1. Verify today-only filter in heal query
grep -n "toDate(open_time_ms" python/opendeviationbar/sidecar.py

# 2. Verify valid_pairs used at overlap heal call site

grep -B5 "\_check_and_heal_overlaps" python/opendeviationbar/sidecar.py | grep -v "^def " | grep "valid_pairs"

# 3. Verify NO cartesian product near overlap heal

grep -A10 "v1.1: Overlap self-heal" python/opendeviationbar/sidecar.py | grep -c "config.symbols"

# Expected: 0

# 4. Run all heal scoping tests

cd /Users/terryli/eon/opendeviationbar-py && uv run --python 3.13 pytest tests/test_heal_scoping.py -x -v

# 5. Full test suite

cd /Users/terryli/eon/opendeviationbar-py && uv run --python 3.13 pytest tests/ -x --timeout=120 -q 2>&1 | tail -5

```
</verification>

<success_criteria>
- `_heal_day_boundary_gaps()` query includes `AND toDate(open_time_ms/1000) = today()` (HEAL-01)
- `_check_and_heal_overlaps()` call site uses `valid_pairs` not cartesian product (HEAL-02)
- Historical data is never queried by heal operations
- Only actively streaming pairs are checked for overlaps
- All new and existing tests pass
</success_criteria>

<output>
After completion, create `.planning/phases/01-atomic-replace-heal-scoping/01-02-SUMMARY.md`
</output>
```
