---
phase: 26-exchange-gap-classification-dismiss
plan: 01
subsystem: kintsugi
tags: [kintsugi, exchange-gap, checksum, stathera, clickhouse]

requires:
  - phase: v6.0
    provides: ChecksumRegistry with per-file verification state
  - phase: v7.0
    provides: _repair_aion() TID-range repair for Aion shards

provides:
  - Exchange gap classification in _repair_aion() via ChecksumRegistry
  - Permanent dismiss of exchange_gap shards in _get_dismissed_set()
  - result='exchange_gap' telemetry in kintsugi_log

affects: [27-stathera-classification, 28-retroactive-sweep]

tech-stack:
  added: []
  patterns: [thread-local flag for cross-function exchange gap signal]

key-files:
  created:
    - tests/test_kintsugi_exchange_gap.py
  modified:
    - python/opendeviationbar/kintsugi/discovery.py
    - python/opendeviationbar/kintsugi/repair.py
    - python/opendeviationbar/kintsugi/telemetry.py

key-decisions:
  - "ChecksumRegistry instantiated inside _repair_aion() to avoid import cycles and keep it lazy"
  - "Exchange gap classification uses thread-local flag (_thread_local.is_exchange_gap) following existing last_source pattern"
  - "Permanent dismiss via separate SQL query (no INTERVAL filter) rather than modifying existing clean query"

patterns-established:
  - "Exchange gap detection: parquet_available + all_dates_verified + 0_trades = is_exchange_gap"
  - "Permanent dismiss in _get_dismissed_set(): union of 30-day clean + permanent exchange_gap queries"

requirements-completed: [EXGAP-01, EXGAP-02, DISMISS-01, DISMISS-02]

duration: 7min
completed: 2026-03-28
---

# Phase 26 Plan 01: Exchange Gap Classification and Dismiss Summary

**Kintsugi classifies 0-trade TID ranges as exchange gaps when all Parquet dates are checksum-verified, permanently dismissing them from repair queue**

## Performance

- **Duration:** 7 min
- **Started:** 2026-03-28T00:35:37Z
- **Completed:** 2026-03-28T00:42:42Z
- **Tasks:** 2
- **Files modified:** 4

## Accomplishments

- Added `is_exchange_gap` field to KintsugiResult dataclass with backward-compatible default
- Implemented exchange gap classification in `_repair_aion()`: when Parquet is available, trades_df is empty, and ALL dates are checksum-verified via ChecksumRegistry, the shard is flagged as exchange gap
- Updated `_log_repair()` to write `result='exchange_gap'` to kintsugi_log table (auditable)
- Added permanent dismiss query in `_get_dismissed_set()` that unions 30-day clean shards with permanent exchange_gap shards (no time window)
- All 6 new tests pass; all 22 existing kintsugi tests pass unchanged

## Task Commits

Each task was committed atomically:

1. **Task 1 (RED): Failing tests** - `a600fad` (test)
2. **Task 1 (GREEN): Implementation** - `91f2f2b` (feat)
3. **Task 2: Verify existing tests** - verification only, no code changes

**Plan metadata:** (pending)

## Files Created/Modified

- `tests/test_kintsugi_exchange_gap.py` - 6 tests covering EXGAP-01, EXGAP-02, DISMISS-01, DISMISS-02
- `python/opendeviationbar/kintsugi/discovery.py` - KintsugiResult.is_exchange_gap field + permanent dismiss query
- `python/opendeviationbar/kintsugi/repair.py` - ChecksumRegistry verification in \_repair_aion() + thread-local flag
- `python/opendeviationbar/kintsugi/telemetry.py` - result='exchange_gap' in_log_repair()

## Decisions Made

- Used ChecksumRegistry instantiated inside `_repair_aion()` (lazy import, avoids module-level coupling)
- Exchange gap flag uses `_thread_local.is_exchange_gap` following the existing `_thread_local.last_source` pattern
- Permanent dismiss uses a separate SQL query without INTERVAL filter, unioned with the existing 30-day clean query

## Deviations from Plan

### Auto-fixed Issues

**1. [Rule 1 - Bug] Fixed mock patch targets in tests**

- **Found during:** Task 1 (RED/GREEN phase)
- **Issue:** Tests patched `get_min_threshold_for_symbol` and `OpenDeviationBarCache` on wrong modules (module-level vs function-level imports)
- **Fix:** Patched at correct module locations where names are resolved
- **Files modified:** tests/test_kintsugi_exchange_gap.py
- **Verification:** All 6 tests pass
- **Committed in:** 91f2f2b (part of GREEN commit)

---

**Total deviations:** 1 auto-fixed (1 bug)
**Impact on plan:** Test-only fix for correct mock targeting. No scope creep.

## Issues Encountered

None

## Known Stubs

None - all code paths are fully wired with real ChecksumRegistry integration.

## User Setup Required

None - no external service configuration required.

## Next Phase Readiness

- Exchange gap classification ready for Phase 27 (Stathera separation in heartbeat)
- kintsugi_log now has result='exchange_gap' entries available for Phase 28 (retroactive sweep)

---

_Phase: 26-exchange-gap-classification-dismiss_
_Completed: 2026-03-28_
