---
phase: 01-atomic-replace-heal-scoping
plan: 01
subsystem: streaming
tags: [clickhouse, atomic-replace, sidecar, polars, stathera]

requires: []
provides:
  - "Atomic replace in _sync_flush_rest_bars() via store_bars_replacing()"
  - "Fallback to _write_batch_with_retry() on exception"
  - "Tests verifying atomic replace behavior (4 tests)"
affects: [01-02, legacy-pruning]

tech-stack:
  added: []
  patterns:
    - "store_bars_replacing() pattern for sidecar sync_flush writes"
    - "Polars DataFrame conversion before atomic replace"

key-files:
  created:
    - tests/test_sync_flush_atomic_replace.py
  modified:
    - python/opendeviationbar/sidecar.py

key-decisions:
  - "Patch at opendeviationbar.clickhouse.cache.OpenDeviationBarCache for lazy import mocking"
  - "_skip_lock=False to ensure Redis write lock acquired during sync_flush atomic replace"

patterns-established:
  - "Atomic replace pattern: drain ring buffer -> convert to Polars -> store_bars_replacing() -> fallback on exception"

requirements-completed: [ATOM-01, ATOM-02]

duration: 11min
completed: 2026-03-20
---

# Phase 01 Plan 01: Atomic Replace in sync_flush Summary

**Atomic DELETE+INSERT via store_bars_replacing() in \_sync_flush_rest_bars() with Polars conversion, Redis lock, and \_write_batch_with_retry fallback**

## Performance

- **Duration:** 11 min
- **Started:** 2026-03-20T19:28:53Z
- **Completed:** 2026-03-20T19:40:09Z
- **Tasks:** 1 (TDD: RED -> GREEN)
- **Files modified:** 2

## Accomplishments

- \_sync_flush_rest_bars() now atomically replaces stale bars via store_bars_replacing() instead of \_write_batch_with_retry()
- Bar dicts converted to Polars DataFrame before store_bars_replacing call
- \_skip_lock=False ensures Redis write lock acquired for the full DELETE+INSERT sequence
- Fallback to \_write_batch_with_retry() on any exception preserves reliability
- 4 tests cover: happy path, empty buffer, fallback on exception, cross-midnight filtering

## Task Commits

Each task was committed atomically:

1. **Task 1 (RED): Failing tests for atomic replace** - `b4046c8` (test)
2. **Task 1 (GREEN): Implementation + passing tests** - `f5ccbc5` (feat)

## Files Created/Modified

- `tests/test_sync_flush_atomic_replace.py` - 4 tests verifying atomic replace behavior in sync_flush
- `python/opendeviationbar/sidecar.py` - \_sync_flush_rest_bars() rewritten to use store_bars_replacing() with fallback

## Decisions Made

- Used `_skip_lock=False` so sync_flush acquires its own Redis write lock (not caller's responsibility)
- Lazy import of `OpenDeviationBarCache` inside function body (consistent with existing sidecar patterns)
- Context manager `with OpenDeviationBarCache() as cache` wraps all batch writes (single connection for all pairs)

## Deviations from Plan

### Auto-fixed Issues

**1. [Rule 3 - Blocking] Fixed test mock patch target for lazy imports**

- **Found during:** Task 1 (TDD RED phase)
- **Issue:** Tests patched `opendeviationbar.sidecar.OpenDeviationBarCache` but the import is lazy (inside function body), so the attribute doesn't exist at module level
- **Fix:** Changed patch target to `opendeviationbar.clickhouse.cache.OpenDeviationBarCache` which patches at the source module
- **Files modified:** tests/test_sync_flush_atomic_replace.py
- **Verification:** All 4 tests pass
- **Committed in:** f5ccbc5 (GREEN commit)

---

**Total deviations:** 1 auto-fixed (1 blocking)
**Impact on plan:** Standard mock patching adjustment for lazy imports. No scope creep.

## Issues Encountered

None beyond the mock patch target adjustment documented above.

## User Setup Required

None - no external service configuration required.

## Next Phase Readiness

- Atomic replace mechanism is in place for sync_flush
- Plan 01-02 (heal scoping) can proceed -- \_heal_day_boundary_gaps() can now trust that sync_flush wrote clean bars
- store_bars_replacing() pattern proven for sidecar use case

---

_Phase: 01-atomic-replace-heal-scoping_
_Completed: 2026-03-20_
