---
phase: 40-barkey-orchestration-kintsugi
plan: 01
type: execute
wave: 1
depends_on: ["39-01"]
files_modified:
  - python/opendeviationbar/orchestration/open_deviation_bars.py
  - python/opendeviationbar/orchestration/open_deviation_bars_cache.py
  - python/opendeviationbar/orchestration/count_bounded.py
  - python/opendeviationbar/orchestration/precompute.py
  - python/opendeviationbar/orchestration/helpers.py
  - python/opendeviationbar/kintsugi/discovery.py
autonomous: true
requirements: [BARKEY-03, BARKEY-04]

must_haves:
  truths:
    - "Orchestration functions construct BarKey and pass it to ClickHouse layer calls"
    - "Shard dataclass exposes bar_key computed property"
    - "All existing callers of orchestration functions work unchanged (no public API change)"
    - "ClickHouse deprecated individual-param paths are removed"
  artifacts:
    - path: "python/opendeviationbar/orchestration/open_deviation_bars.py"
      provides: "BarKey construction in get_open_deviation_bars and get_open_deviation_bars_pandas"
      contains: "BarKey("
    - path: "python/opendeviationbar/orchestration/open_deviation_bars_cache.py"
      provides: "BarKey propagation in try_cache_read, try_cache_write, _do_fatal_cache_write, fatal_cache_write"
      contains: "bar_key"
    - path: "python/opendeviationbar/orchestration/count_bounded.py"
      provides: "BarKey construction in get_n_open_deviation_bars"
      contains: "BarKey("
    - path: "python/opendeviationbar/orchestration/precompute.py"
      provides: "BarKey construction in precompute_open_deviation_bars"
      contains: "BarKey("
    - path: "python/opendeviationbar/orchestration/helpers.py"
      provides: "BarKey in _stream_open_deviation_bars_binance and _process_exness_ticks"
      contains: "bar_key"
    - path: "python/opendeviationbar/kintsugi/discovery.py"
      provides: "bar_key computed property on Shard"
      contains: "def bar_key"
  key_links:
    - from: "orchestration/open_deviation_bars.py"
      to: "orchestration/open_deviation_bars_cache.py"
      via: "passes BarKey instead of (symbol, threshold, ouroboros_mode) triple"
      pattern: "try_cache_read.*bar_key|try_cache_write.*bar_key"
    - from: "orchestration/open_deviation_bars_cache.py"
      to: "clickhouse/query_operations.py"
      via: "passes BarKey to get_bars_by_timestamp_range"
      pattern: "get_bars_by_timestamp_range.*bar_key"
    - from: "orchestration/open_deviation_bars_cache.py"
      to: "clickhouse/bulk_operations.py"
      via: "passes BarKey to store_bars_batch"
      pattern: "store_bars_batch.*bar_key"
---

<objective>
Propagate BarKey from orchestration layer callers into ClickHouse layer calls, add bar_key property to kintsugi Shard, then remove deprecated individual-param paths from the ClickHouse layer.

Purpose: Complete the BarKey adoption chain so all ClickHouse calls use the structured type, enabling the deprecated expand-contract paths to be removed.
Output: 10 orchestration functions construct/pass BarKey, Shard has bar_key property, ClickHouse layer cleaned of deprecated paths.
</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/39/39-01-SUMMARY.md

@python/opendeviationbar/types.py
@python/opendeviationbar/orchestration/open_deviation_bars.py
@python/opendeviationbar/orchestration/open_deviation_bars_cache.py
@python/opendeviationbar/orchestration/count_bounded.py
@python/opendeviationbar/orchestration/precompute.py
@python/opendeviationbar/orchestration/helpers.py
@python/opendeviationbar/kintsugi/discovery.py

<interfaces>
<!-- BarKey type and resolver from Phase 39 (types.py) -->

```python
@dataclass(frozen=True)
class BarKey:
    symbol: str
    threshold_decimal_bps: int
    ouroboros_mode: str = "aion"

def _resolve_bar_key(
    symbol_or_key: str | BarKey,
    threshold_decimal_bps: int | None = None,
    ouroboros_mode: str | None = None,
) -> BarKey:
```

<!-- ClickHouse layer functions already accept BarKey (Phase 39 expand-contract) -->
<!-- Example signatures from bulk_operations.py and query_operations.py: -->

```python
def store_bars_batch(self, symbol_or_key: str | BarKey, threshold_decimal_bps: int | None = None, bars: pl.DataFrame | None = None, version: str | None = None, ouroboros_mode: str | None = None, *, skip_dedup: bool = False) -> int:

def get_bars_by_timestamp_range(self, symbol_or_key: str | BarKey, threshold_decimal_bps: int | None = None, start_ts: int = 0, end_ts: int = 0, include_microstructure: bool = False, include_exchange_sessions: bool = False, ouroboros_mode: str | None = None) -> pd.DataFrame | None:
```

</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Construct and pass BarKey in all 10 orchestration functions + add Shard.bar_key property</name>
  <files>
    python/opendeviationbar/orchestration/open_deviation_bars.py
    python/opendeviationbar/orchestration/open_deviation_bars_cache.py
    python/opendeviationbar/orchestration/count_bounded.py
    python/opendeviationbar/orchestration/precompute.py
    python/opendeviationbar/orchestration/helpers.py
    python/opendeviationbar/kintsugi/discovery.py
  </files>
  <action>
For each orchestration function, construct a BarKey from the already-resolved (symbol, threshold_decimal_bps, ouroboros_mode) locals and pass it to ClickHouse layer calls. The orchestration functions' PUBLIC signatures do NOT change — they still accept individual params. The change is internal: construct BarKey and pass it downstream.

**Pattern (same for all 10 functions):**
After threshold and ouroboros_mode are resolved to final int/str values, add:

```python
from opendeviationbar.types import BarKey
bar_key = BarKey(symbol, threshold_decimal_bps, ouroboros_mode)
```

Then replace downstream ClickHouse calls from `(symbol=symbol, threshold_decimal_bps=threshold_decimal_bps, ouroboros_mode=ouroboros_mode)` to just `bar_key`.

**Specific changes:**

1. `open_deviation_bars.py::get_open_deviation_bars()` (~L329 after ouroboros resolution):
   - Construct `bar_key = BarKey(symbol, threshold_decimal_bps, ouroboros_mode)`
   - Update `try_cache_read(...)` call (~L445) to pass `bar_key` instead of symbol/threshold/ouroboros_mode individually
   - Update `try_cache_write(...)` call (~L715) to pass `bar_key` instead of individual params
   - Update `_stream_open_deviation_bars_binance()` call (~L425) — no change needed, it doesn't call CH directly
   - Update the inline `cache.get_bars_by_timestamp_range()` call (~L375) to pass `bar_key`

2. `open_deviation_bars.py::get_open_deviation_bars_pandas()` (~L725):
   - This is a thin shim that delegates to `get_open_deviation_bars()` — no BarKey construction needed here (it passes through)

3. `open_deviation_bars_cache.py::try_cache_read()` (~L105):
   - Change signature: replace `symbol: str, threshold_decimal_bps: int, ... ouroboros_mode: str` with `bar_key: BarKey` (import BarKey from types). Keep start_ts, end_ts, include_microstructure, trace_id.
   - Inside: destructure `symbol = bar_key.symbol`, `threshold_decimal_bps = bar_key.threshold_decimal_bps`, `ouroboros_mode = bar_key.ouroboros_mode` for the CH call and logging
   - Pass `bar_key` to `cache.get_bars_by_timestamp_range(bar_key, ...)`

4. `open_deviation_bars_cache.py::try_cache_write()` (~L203):
   - Change signature: replace `symbol: str, threshold_decimal_bps: int, ouroboros_mode: str` with `bar_key: BarKey`. Keep bars_df.
   - Inside: pass `bar_key` to `cache.store_bars_batch(bar_key, ...)`

5. `open_deviation_bars_cache.py::_do_fatal_cache_write()` (~L266):
   - Change signature: replace `symbol: str, threshold_decimal_bps: int, ouroboros_mode: str` with `bar_key: BarKey`. Keep bars, version.
   - Inside: pass `bar_key` to `cache.store_bars_batch(bar_key, ...)`

6. `open_deviation_bars_cache.py::fatal_cache_write()` (~L323):
   - Change signature: replace `symbol: str, threshold_decimal_bps: int, ouroboros_mode: str` with `bar_key: BarKey`. Keep bars, version.
   - Inside: pass `bar_key` to `_do_fatal_cache_write(bars, bar_key, ...)`

7. `count_bounded.py::get_n_open_deviation_bars()` (~L48):
   - After ouroboros_mode resolution, construct `bar_key = BarKey(symbol, threshold_decimal_bps, ouroboros_mode)`
   - Pass `bar_key` to any CH calls (count_bars, get_n_bars, etc.)

8. `precompute.py::precompute_open_deviation_bars()` (~L57):
   - After ouroboros_mode resolution, construct `bar_key = BarKey(symbol, threshold_decimal_bps, ouroboros_mode)`
   - Pass `bar_key` to any CH calls

9. `helpers.py::_stream_open_deviation_bars_binance()` (~L397):
   - This function does NOT call ClickHouse directly. No BarKey construction needed here — skip.

10. `helpers.py::_process_exness_ticks()` (~L727):
    - This function does NOT call ClickHouse directly. No BarKey construction needed here — skip.

**IMPORTANT for helpers.py (#9 and #10):** These two functions are listed in the phase scope but they do NOT make ClickHouse calls. They process trades via Rust processor. Do NOT add BarKey construction if there is no downstream CH call. If they DO pass symbol/threshold to any CH function (verify by reading the full function), then add BarKey. Otherwise skip them.

**Kintsugi Shard (BARKEY-04):**

1. `kintsugi/discovery.py::Shard`:
   - Add a computed `@property` named `bar_key` that returns `BarKey(self.symbol, self.threshold_decimal_bps)`:

   ```python
   @property
   def bar_key(self) -> BarKey:
       from opendeviationbar.types import BarKey
       return BarKey(self.symbol, self.threshold_decimal_bps)
   ```

   Note: ouroboros_mode defaults to "aion" in BarKey, which is correct since Aion is the only active mode.

**Also update all callers of fatal_cache_write():**
Since `fatal_cache_write()` signature changes, grep for all call sites and update them to pass `bar_key` instead of individual params. Key callers:

- `python/opendeviationbar/checkpoint.py` (populate_cache_resumable)
- `python/opendeviationbar/kintsugi/repair.py` (shard repair)
  Check each caller and construct BarKey before the call.

For the TYPE_CHECKING import pattern, follow the Phase 39 convention:

```python
from __future__ import annotations
# ...
if TYPE_CHECKING:
    from opendeviationbar.types import BarKey
```

And import BarKey at runtime only where constructing it. For function signatures that receive BarKey, the TYPE_CHECKING import is sufficient thanks to `from __future__ import annotations`.
</action>
<verify>
<automated>cd /Users/terryli/eon/opendeviationbar-py && uv run --python 3.13 python -c "from opendeviationbar.types import BarKey; bk = BarKey('BTCUSDT', 250); print(f'BarKey: {bk}'); from opendeviationbar.kintsugi.discovery import Shard; s = Shard('BTCUSDT', 250, 0, 0, 0.0); print(f'Shard.bar_key: {s.bar_key}')" && uv run --python 3.13 ruff check python/opendeviationbar/orchestration/ python/opendeviationbar/kintsugi/discovery.py</automated>
</verify>
<done>All 10 orchestration functions (or the subset that actually call CH) construct BarKey and pass it to ClickHouse layer. Shard.bar_key property returns BarKey. All callers of fatal_cache_write updated. Ruff passes. No public API signature changes.</done>
</task>

<task type="auto">
  <name>Task 2: Remove deprecated individual-param paths from ClickHouse layer</name>
  <files>
    python/opendeviationbar/clickhouse/bulk_operations.py
    python/opendeviationbar/clickhouse/query_operations.py
    python/opendeviationbar/clickhouse/maintenance.py
    python/opendeviationbar/clickhouse/bar_count_api.py
    python/opendeviationbar/clickhouse/checkpoint_operations.py
    python/opendeviationbar/clickhouse/cache.py
    python/opendeviationbar/clickhouse/write_lock.py
    python/opendeviationbar/types.py
  </files>
  <action>
Now that all callers pass BarKey, remove the expand-contract compatibility layer from ClickHouse functions.

**For each of the 27 ClickHouse functions updated in Phase 39:**

1. Change `symbol_or_key: str | BarKey` to `bar_key: BarKey` (first positional param)
2. Remove `threshold_decimal_bps: int | None = None` param
3. Remove `ouroboros_mode: str | None = None` param (where applicable)
4. Remove the 3-line `_resolve_bar_key` block at the top of each function:

   ```python
   # DELETE these lines:
   from opendeviationbar.types import _resolve_bar_key
   key = _resolve_bar_key(symbol_or_key, threshold_decimal_bps, ouroboros_mode)
   symbol, threshold_decimal_bps, ouroboros_mode = key.symbol, key.threshold_decimal_bps, key.ouroboros_mode
   ```

5. Replace references to destructured locals with `bar_key.symbol`, `bar_key.threshold_decimal_bps`, `bar_key.ouroboros_mode` — OR keep the destructure as `symbol, threshold_decimal_bps, ouroboros_mode = bar_key.symbol, bar_key.threshold_decimal_bps, bar_key.ouroboros_mode` for readability (this avoids changing every SQL query string that uses these locals).

**Recommended approach:** Keep the destructure-to-locals line but remove the `_resolve_bar_key` import and call. This minimizes diff size:

```python
def store_bars_batch(self, bar_key: BarKey, bars: pl.DataFrame, ...) -> int:
    symbol = bar_key.symbol
    threshold_decimal_bps = bar_key.threshold_decimal_bps
    ouroboros_mode = bar_key.ouroboros_mode
    # ... rest of function unchanged
```

**Special case — deduplicate_bars:** Check if it uses isinstance check for None-able params (per Phase 39 summary). If so, it may need bar_key to be Optional[BarKey] = None for the "optimize all" mode.

**Also remove `_resolve_bar_key` from types.py** — it is no longer needed after this cleanup. Keep BarKey.

**Update docstrings** in each function to reflect the new `bar_key: BarKey` parameter instead of the old `symbol_or_key` description.

**Verify no remaining callers pass individual params:**

```bash
grep -rn "symbol_or_key" python/opendeviationbar/clickhouse/
```

Should return zero matches after cleanup.
</action>
<verify>
<automated>cd /Users/terryli/eon/opendeviationbar-py && uv run --python 3.13 ruff check python/opendeviationbar/clickhouse/ python/opendeviationbar/types.py && uv run --python 3.13 python -c "from opendeviationbar.types import BarKey; bk = BarKey('BTCUSDT', 250); print('BarKey import OK')" && grep -c "symbol_or_key" python/opendeviationbar/clickhouse/_.py python/opendeviationbar/clickhouse/\*\*/_.py 2>/dev/null | grep -v ":0$" || echo "All symbol_or_key references removed"</automated>
</verify>
<done>All 27 ClickHouse functions accept bar_key: BarKey directly.\_resolve_bar_key removed from types.py. No expand-contract compatibility code remains. grep for "symbol_or_key" in clickhouse/ returns 0 matches. Ruff passes.</done>
</task>

<task type="auto">
  <name>Task 3: Verify full backward compatibility with test suite</name>
  <files></files>
  <action>
Run the Python test suite to verify no regressions from the BarKey propagation and deprecated path removal. Focus on:

1. `mise run test-py` — full Python test suite
2. If tests fail due to callers still using the old individual-param API that was just removed in Task 2, fix those callers to construct BarKey first.

Key test files to watch:

- tests/test_cache\*.py — ClickHouse cache operations
- tests/test_get_open_deviation_bars\*.py — orchestration layer
- tests/test_kintsugi\*.py — kintsugi
- tests/test_count_bounded\*.py — count-bounded API

If any test constructs ClickHouse cache calls with individual params (symbol, threshold), update them to use BarKey.
</action>
<verify>
<automated>cd /Users/terryli/eon/opendeviationbar-py && uv run --python 3.13 ruff check python/opendeviationbar/ && uv run --python 3.13 python -c "from opendeviationbar import get_open_deviation_bars, BarKey; print('Public API imports OK')"</automated>
</verify>
<done>Ruff passes across the entire python/opendeviationbar/ directory. Public API imports work. Any test failures from the signature change are fixed.</done>
</task>

</tasks>

<verification>
1. `ruff check python/opendeviationbar/` passes with no errors
2. `grep -rn "symbol_or_key" python/opendeviationbar/clickhouse/` returns 0 matches
3. `grep -rn "_resolve_bar_key" python/opendeviationbar/` returns 0 matches
4. `python -c "from opendeviationbar.kintsugi.discovery import Shard; s = Shard('BTCUSDT', 250, 0, 0, 0.0); print(s.bar_key)"` prints `BarKey(symbol='BTCUSDT', threshold_decimal_bps=250, ouroboros_mode='aion')`
5. `python -c "from opendeviationbar.types import BarKey; print(BarKey.__dataclass_fields__.keys())"` works, `_resolve_bar_key` is not importable
</verification>

<success_criteria>

- BARKEY-03: All orchestration ClickHouse calls pass BarKey instead of individual params
- BARKEY-04: Shard.bar_key property returns BarKey(symbol, threshold_decimal_bps)
- Deprecated expand-contract paths fully removed from ClickHouse layer
- \_resolve_bar_key helper removed from types.py
- No public API changes (orchestration function signatures unchanged)
- Ruff clean
  </success_criteria>

<output>
After completion, create `.planning/phases/40/40-01-SUMMARY.md`
</output>
