---
phase: 36-fan-out-pyo3-bridge
plan: "02"
subsystem: streaming
tags: [rust, pyo3, metrics, clickhouse, stream-manager, feature-flags]

requires:
  - phase: 36-fan-out-pyo3-bridge
    provides: ch_writer_metrics() on LiveBarEngine, FlushThreadMetrics Arc reference

provides:
  - StreamManager.get_combined_metrics() with 6 ch_writer_* counters
  - Root Cargo.toml streaming feature propagating clickhouse-sink
  - Python heartbeat can monitor Rust CH writer health via get_metrics() dict

affects: [sidecar, heartbeat, deploy]

tech-stack:
  added: []
  patterns:
    - "cfg-gated field + conditional metrics insertion in get_combined_metrics()"
    - "Root feature propagation: streaming -> clickhouse-sink for automatic PyO3 inclusion"

key-files:
  created: []
  modified:
    - crates/opendeviationbar-streaming/src/stream_manager.rs
    - Cargo.toml

key-decisions:
  - "No PyO3 binding changes needed -- existing get_combined_metrics() -> PyDict conversion auto-exposes new keys"
  - "ch_writer_metrics stored as Option<Arc<FlushThreadMetrics>> on StreamManager (cloned from engine in new())"

patterns-established:
  - "Metrics bridge pattern: extract Arc<Metrics> from engine -> store on manager -> merge in get_combined_metrics()"

requirements-completed: [INTEG-04]

duration: 12min
completed: 2026-03-29
---

# Phase 36 Plan 02: Metrics Bridge + Cargo.toml Wiring Summary

**StreamManager.get*combined_metrics() extended with 6 ch_writer*\* counters from FlushThreadMetrics, root Cargo.toml wiring clickhouse-sink into streaming feature for automatic PyO3 propagation**

## Performance

- **Duration:** 12 min
- **Started:** 2026-03-29T10:51:07Z
- **Completed:** 2026-03-29T11:03:00Z
- **Tasks:** 1 (TDD)
- **Files modified:** 2

## Accomplishments

- Metrics bridge (INTEG-04): StreamManager stores `ch_writer_metrics` Arc from LiveBarEngine, merges 6 counters into `get_combined_metrics()` HashMap
- Feature propagation: Root Cargo.toml `streaming` feature now includes `clickhouse-sink`, so `maturin develop` automatically enables CH writer in PyO3 extension
- Zero PyO3 changes: Python `stream_manager.get_metrics()` automatically exposes `ch_writer_*` keys via existing HashMap -> PyDict conversion
- TDD: 2 tests verify keys present with injected metrics and absent when None

## Task Commits

Each task was committed atomically:

1. **Task 1: Metrics bridge in StreamManager + Cargo.toml wiring** - `a0e1c14` (feat) -- TDD: RED (compile error) -> GREEN (2 tests pass) -> verified 183/183 streaming tests pass

## Files Created/Modified

- `crates/opendeviationbar-streaming/src/stream_manager.rs` - Added `ch_writer_metrics` field, extraction in `new()`, 6 metrics in `get_combined_metrics()`, 2 tests
- `Cargo.toml` - Added `opendeviationbar-streaming/clickhouse-sink` to `streaming` feature

## Decisions Made

- **No PyO3 binding changes**: Verified `stream_manager_bindings.rs::get_metrics()` already converts entire HashMap to PyDict -- adding keys on Rust side flows to Python automatically
- **Direct Arc clone from engine**: `engine.ch_writer_metrics().cloned()` in `StreamManager::new()` extracts the shared metrics reference before any streaming starts

## Deviations from Plan

### Auto-fixed Issues

**1. [Rule 1 - Bug] Fixed test for env-dependent ch_writer_metrics initialization**

- **Found during:** Task 1 (GREEN phase)
- **Issue:** `test_combined_metrics_no_ch_writer_keys_when_none` assumed `OPENDEVIATIONBAR_CH_HOSTS` is unset, but it was set in the dev environment, causing `ch_writer_metrics` to be `Some` by default
- **Fix:** Test explicitly sets `sm.ch_writer_metrics = None` to simulate no-CH-hosts scenario
- **Files modified:** `stream_manager.rs`
- **Verification:** Both tests pass in all environments
- **Committed in:** a0e1c14

---

**Total deviations:** 1 auto-fixed (Rule 1 - bug)
**Impact on plan:** Test correctness fix -- test now properly isolates the no-CH-hosts code path regardless of environment.

## Issues Encountered

None.

## User Setup Required

None - no external service configuration required.

## Next Phase Readiness

- Phase 36 complete: all INTEG requirements delivered (INTEG-01, INTEG-02, INTEG-04, INTEG-05, INTEG-07)
- Python heartbeat can now read `ch_writer_bars_flushed`, `ch_writer_bars_failed`, `ch_writer_flush_count`, `ch_writer_last_flush_latency_us`, `ch_writer_dead_letter_count`, `ch_writer_dead_letter_replayed` from `stream_manager.get_metrics()` dict
- Ready for sidecar integration: wire Rust writer into production sidecar startup

## Known Stubs

None - all wiring is functional with no placeholder data.

---

_Phase: 36-fan-out-pyo3-bridge_
_Completed: 2026-03-29_
