---
phase: 260321-ql9
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - deploy/opendeviationbar.env
  - python/opendeviationbar/sidecar.py
  - python/opendeviationbar/clickhouse/maintenance.py
  - scripts/heartbeat/checks/stathera.py
  - scripts/heartbeat/config.py
  - scripts/heartbeat/formatter.py
  - tests/test_heartbeat_stathera.py
  - tests/test_heartbeat_health_levels.py
autonomous: true
requirements: [P0-1a, P0-4, P0-2, P0-3]

must_haves:
  truths:
    - "Ring buffer capacity is 100K via env var, preventing silent bar drops during fill_from_rest"
    - "_delete_today_bars failure crashes the sidecar instead of continuing with corrupted state"
    - "INSERT failure in _do_replace saves bars to dead-letter before re-raising, preventing permanent data loss"
    - "Heartbeat distinguishes today's Stathera anomalies from historical ones and never suppresses today's alerts via kintsugi_healing"
    - "stathera_today_gaps and stathera_today_overlaps in CRITICAL_KEYS gate health level to CRITICAL"
    - "Today-gap LOUD alert fires regardless of kintsugi_healing state"
  artifacts:
    - path: "deploy/opendeviationbar.env"
      provides: "OPENDEVIATIONBAR_MAX_PENDING_BARS=100000"
      contains: "MAX_PENDING_BARS"
    - path: "python/opendeviationbar/sidecar.py"
      provides: "_delete_today_bars without bare except"
    - path: "python/opendeviationbar/clickhouse/maintenance.py"
      provides: "Dead-letter on INSERT failure in _do_replace"
      contains: "_dead_letter_replace_failure"
    - path: "scripts/heartbeat/checks/stathera.py"
      provides: "Today-only anomaly query + split health logic"
      contains: "stathera_today_gaps"
    - path: "scripts/heartbeat/config.py"
      provides: "Today keys in CRITICAL_KEYS"
      contains: "stathera_today_gaps"
    - path: "scripts/heartbeat/formatter.py"
      provides: "Remediation hints for today keys"
      contains: "stathera_today_gaps"
  key_links:
    - from: "scripts/heartbeat/checks/stathera.py"
      to: "scripts/heartbeat/config.py"
      via: "stathera_today_gaps and stathera_today_overlaps added to CRITICAL_KEYS"
      pattern: "stathera_today_(gaps|overlaps)"
    - from: "python/opendeviationbar/clickhouse/maintenance.py"
      to: "/tmp/opendeviationbar-dead-letter/"
      via: "_dead_letter_replace_failure writes Parquet for Charon replay"
      pattern: "_dead_letter_replace_failure"
---

<objective>
Fix all 4 P0 findings from the 9-agent guard investigation (#295). These are bugs that can cause data loss in production TODAY.

Purpose: Eliminate silent data loss vectors — ring buffer overflow, swallowed delete failures, unrecoverable INSERT failures, and suppressed today-gap alerts.
Output: 4 hardened code paths + tests, deployable as hotfixes.
</objective>

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

<context>
@.planning/quick/guard-investigation/SYNTHESIS.md
@deploy/opendeviationbar.env
@python/opendeviationbar/sidecar.py
@python/opendeviationbar/clickhouse/maintenance.py
@scripts/heartbeat/checks/stathera.py
@scripts/heartbeat/config.py
@scripts/heartbeat/formatter.py
@tests/test_heartbeat_stathera.py
@tests/test_heartbeat_health_levels.py

<interfaces>
<!-- From scripts/heartbeat/checks/stathera.py -->
from opendeviationbar.clickhouse.stathera_query import (
    FULL_TABLE,
    VALID_TID_FILTER,
    cross_pair_filter,
    cross_pair_window,
)

def collect_stathera(client, results: dict) -> None: ...
def_check_today_gaps(client, results, \*, gap_delta, kintsugi_healing, full_table, valid_tid_filter, enabled_symbols_sql_fn) -> None: ...

<!-- From scripts/heartbeat/config.py -->

CRITICAL_KEYS = frozenset({"clickhouse", "sidecar", "kintsugi", "disk", "stathera_overlaps", "stathera_gaps", "circuit_breaker_healthy"})

<!-- From python/opendeviationbar/clickhouse/maintenance.py -->

class MaintenanceMixin:
def \_do_replace(self, symbol, threshold_decimal_bps, bars_df, \*, ouroboros_mode, version, new_first, new_last) -> int: ...
def store_bars_batch(self, symbol, threshold_decimal_bps, bars_df, \*\*kwargs) -> int: ...
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: P0-1a ring buffer capacity + P0-4 delete crash-on-failure</name>
  <files>deploy/opendeviationbar.env, python/opendeviationbar/sidecar.py</files>
  <action>
**P0-1a** (deploy/opendeviationbar.env):
Add after the seeder section (after line 59):

```
# Issue #295 P0-1a: Ring buffer capacity increase.
# fill_from_rest can produce 30K-50K bars; default 10K drops oldest silently.
# 100K = ~50MB memory (negligible on bigblack's 64GB). Future P0-1b will add
# inline per-symbol drain in Rust for bounded memory regardless of fill size.
OPENDEVIATIONBAR_MAX_PENDING_BARS=100000
```

**P0-4** (python/opendeviationbar/sidecar.py):
In `_delete_today_bars()` (~line 728-746), remove the try/except wrapper entirely. The function body should be:

```python
def _delete_today_bars(valid_pairs: set[tuple[str, int]] | None) -> None:
    """Delete today's bars for all streaming pairs before fill_from_rest (#296).

    Called when checkpoints are cleared (deploy or crash). Ensures fill_from_rest
    rebuilds today from a clean slate - no interleaving with bars from prior
    processor sessions that have different TID boundaries.

    Uses lightweight DELETE with mutations_sync=1 (blocks until complete).
    Without sync, the async mutation races with subsequent INSERTs - bars
    matching today() get deleted after being written (#N48).

    Failure is FATAL (#295 P0-4): continuing with stale bars corrupts seed TIDs
    and causes overlapping bars. Let exception propagate -> systemd restart.
    """
    if not valid_pairs:
        return
    from opendeviationbar.clickhouse.cache import OpenDeviationBarCache
    from opendeviationbar.ouroboros import get_operational_ouroboros_mode

    ouroboros_mode = get_operational_ouroboros_mode()
    with OpenDeviationBarCache() as cache:
        cache.client.command(
            "DELETE FROM opendeviationbar_cache.open_deviation_bars "
            f"WHERE ouroboros_mode = '{ouroboros_mode}' "
            "AND toDate(open_time_ms / 1000) = today() "
            "SETTINGS lightweight_mutation_projection_mode = 'drop', mutations_sync = 1"
        )
        logger.info(
            "clean_slate: deleted today's bars for %d pairs before fill_from_rest",
            len(valid_pairs),
        )
```

Key change: NO try/except. Exception propagates to `_create_engine()` -> `run_sidecar()` -> systemd restart. This is safer than running with corrupted state.
</action>
<verify>
<automated>cd /Users/terryli/eon/opendeviationbar-py && grep -q "MAX_PENDING_BARS=100000" deploy/opendeviationbar.env && python3 -c "
import ast, inspect, textwrap

# Verify no bare except in \_delete_today_bars

src = open('python/opendeviationbar/sidecar.py').read()

# Find the function

start = src.index('def \_delete_today_bars(')

# Find next def at same indentation

end = src.index('\ndef ', start + 1)
func_src = src[start:end]
assert 'except' not in func_src, f'Found except in \_delete_today_bars: should crash on failure'
print('P0-1a env var: OK')
print('P0-4 no bare except: OK')
"</automated>
</verify>
<done> - deploy/opendeviationbar.env contains OPENDEVIATIONBAR_MAX_PENDING_BARS=100000 - \_delete_today_bars has no try/except — exceptions propagate to crash the sidecar
</done>
</task>

<task type="auto">
  <name>Task 2: P0-2 dead-letter on INSERT failure in _do_replace</name>
  <files>python/opendeviationbar/clickhouse/maintenance.py</files>
  <action>
In `maintenance.py`, modify `_do_replace()` (~line 407-416) to wrap the INSERT step in try/except that saves to dead-letter before re-raising.

Replace lines 407-416 with:

```python
        # Step 2: INSERT new bars (DELETE already completed synchronously).
        # If INSERT fails, bars are permanently lost without dead-letter (#295 P0-2).
        try:
            rows_inserted = self.store_bars_batch(
                symbol, threshold_decimal_bps, bars_df,
                version=version, ouroboros_mode=ouroboros_mode,
                skip_dedup=True, overlap_strategy="off", _skip_lock=True,
            )
        except Exception:
            _dead_letter_replace_failure(
                symbol, threshold_decimal_bps, bars_df, new_first, new_last,
            )
            raise  # Re-raise so kintsugi marks shard as failed

        return rows_inserted
```

Add the helper function BEFORE the class definition (module-level, near the top of the file after imports):

```python
import datetime as _dt
from pathlib import Path as _Path

_DEAD_LETTER_DIR = _Path("/tmp/opendeviationbar-dead-letter")


def _dead_letter_replace_failure(
    symbol: str,
    threshold_decimal_bps: int,
    bars_df: object,
    new_first: int,
    new_last: int,
) -> None:
    """Save bars to dead-letter Parquet after INSERT failure in _do_replace.

    Uses the same directory as Niffler/Charon (/tmp/opendeviationbar-dead-letter/),
    so Charon's 5-min replay loop will pick these up automatically.

    #295 P0-2: DELETE succeeded but INSERT failed = bars permanently lost
    without this safety net.
    """
    try:
        _DEAD_LETTER_DIR.mkdir(parents=True, exist_ok=True)
        ts = _dt.datetime.now(_dt.timezone.utc).strftime("%Y%m%dT%H%M%S")
        filename = f"{symbol}@{threshold_decimal_bps}_{new_first}_{new_last}_{ts}.parquet"
        path = _DEAD_LETTER_DIR / filename

        import polars as pl
        if isinstance(bars_df, pl.DataFrame):
            bars_df.write_parquet(path)
        else:
            # pandas DataFrame
            import pandas as pd
            if isinstance(bars_df, pd.DataFrame):
                bars_df.to_parquet(path, engine="pyarrow")
            else:
                _logger.error(
                    "dead_letter_replace: unknown DataFrame type %s, cannot save",
                    type(bars_df).__name__,
                )
                return

        _logger.critical(
            "dead_letter_replace: saved %d bars to %s (DELETE succeeded, INSERT failed)",
            len(bars_df), path,
        )
    except Exception:
        _logger.error("dead_letter_replace: failed to save dead-letter file", exc_info=True)
```

Make sure `_logger` is available at module level. Check existing logger usage in maintenance.py — it likely uses `logger` (find the existing logger variable name and use it, e.g., `logger = logging.getLogger(__name__)`). Use the SAME logger variable name as the rest of the file.

Also add `import datetime as _dt` and `from pathlib import Path as _Path` to the imports at the top of the file if not already present. Use underscore-prefixed aliases to avoid polluting the module namespace.
</action>
<verify>
<automated>cd /Users/terryli/eon/opendeviationbar-py && python3 -c "
import importlib, sys

# Verify the function exists and is importable

sys.path.insert(0, 'python')
from opendeviationbar.clickhouse.maintenance import \_dead_letter_replace_failure
print('\_dead_letter_replace_failure importable: OK')

# Verify \_do_replace has try/except around store_bars_batch

src = open('python/opendeviationbar/clickhouse/maintenance.py').read()

# Find \_do_replace

start = src.index('def \_do_replace(')
end = src.index('\n def ', start + 1)
func_src = src[start:end]
assert '\_dead_letter_replace_failure' in func_src, 'Missing dead-letter call in \_do_replace'
assert 'raise' in func_src.split['\_dead_letter_replace_failure'](1), 'Must re-raise after dead-letter'
print('\_do_replace dead-letter + re-raise: OK')
"</automated>
</verify>
<done>
-\_do_replace wraps INSERT in try/except, saves to dead-letter Parquet on failure, then re-raises
-\_dead_letter_replace_failure function exists at module level - Dead-letter files go to /tmp/opendeviationbar-dead-letter/ (Charon replay path)
</done>
</task>

<task type="auto">
  <name>Task 3: P0-3 heartbeat today-gap awareness with writer-ownership health logic</name>
  <files>scripts/heartbeat/checks/stathera.py, scripts/heartbeat/config.py, scripts/heartbeat/formatter.py</files>
  <action>
**Part 1 — stathera.py: Add today-only anomaly counts to collect_stathera()**

In `collect_stathera()`, after the existing health logic block (after line ~104 where `results["stathera_gaps"]` is set), add a new today-anomaly query:

```python
    # Today-only anomaly counts (#295 P0-3): writer-ownership-aware.
    # Kintsugi CANNOT heal today (#280 today-guard), so today's anomalies
    # must NEVER be suppressed by kintsugi_healing.
    try:
        sym_filter = ""
        enabled_syms = enabled_symbols_sql()
        if enabled_syms:
            sym_filter = f"AND symbol IN ({enabled_syms})"

        today_result = client.query(
            f"""
            SELECT countIf(td > 0) AS today_gaps, countIf(td < 0) AS today_overlaps
            FROM (
                SELECT first_agg_trade_id - lagInFrame(last_agg_trade_id, 1) OVER w - 1 AS td,
                       row_number() OVER w AS rn
                FROM {FULL_TABLE} FINAL
                WHERE toDate(open_time_ms / 1000) = today()
                  AND {VALID_TID_FILTER} {sym_filter}
                WINDOW w AS (PARTITION BY symbol, threshold_decimal_bps, ouroboros_mode
                             ORDER BY first_agg_trade_id, close_time_ms)
            ) WHERE rn > 1 AND td != 0
            """
        )
        today_gaps_count, today_overlaps_count = today_result.result_rows[0] if today_result.result_rows else (0, 0)
    except Exception:
        logger.warning("Today-anomaly count query failed", exc_info=True)
        today_gaps_count, today_overlaps_count = 0, 0

    # These are NEVER suppressed by kintsugi_healing — kintsugi can't touch today
    results["stathera_today_gaps"] = (today_gaps_count == 0)
    results["stathera_today_overlaps"] = (today_overlaps_count == 0)
    results["stathera_today_gap_count"] = str(today_gaps_count)
    results["stathera_today_overlap_count"] = str(today_overlaps_count)
```

Note: `enabled_symbols_sql` is already available in the function scope — it is imported from `opendeviationbar.clickhouse.stathera_query` and called elsewhere in the same function. Check the exact import name used (might be `enabled_symbols_sql` or similar). Use the same pattern as the existing per-symbol query.

**Part 2 — stathera.py: Fix \_check_today_gaps suppression (line ~278)**

In `_check_today_gaps()`, change line 278 from:

```python
        if gap_delta <= 0 or kintsugi_healing:
            return
```

to:

```python
        if gap_delta <= 0:
            return
```

Remove `or kintsugi_healing` — today's gap LOUD alert must fire regardless of kintsugi state, because kintsugi cannot repair today (#280 today-guard).

**Part 3 — config.py: Add today keys to CRITICAL_KEYS**

In `config.py`, add to the `CRITICAL_KEYS` frozenset:

```python
CRITICAL_KEYS = frozenset({
    "clickhouse",
    "sidecar",
    "kintsugi",
    "disk",
    "stathera_overlaps",
    "stathera_gaps",
    "stathera_today_gaps",        # #295 P0-3: kintsugi can't fix today (#280)
    "stathera_today_overlaps",    # #295 P0-3: kintsugi can't fix today (#280)
    "circuit_breaker_healthy",
})
```

**Part 4 — formatter.py: Add remediation hints**

In `formatter.py`, add to the `_remediation` dict:

```python
        "stathera_today_gaps": "TODAY's gaps - sidecar issue, kintsugi cannot fix (#280). Check: journalctl --user -u opendeviationbar-sidecar -n 50",
        "stathera_today_overlaps": "TODAY's overlaps - check for mutation cascade or straddle bars. Check: journalctl --user -u opendeviationbar-sidecar -n 50",
```

  </action>
  <verify>
    <automated>cd /Users/terryli/eon/opendeviationbar-py && python3 -c "
# Verify CRITICAL_KEYS contains today keys
import sys; sys.path.insert(0, 'scripts')
from heartbeat.config import CRITICAL_KEYS
assert 'stathera_today_gaps' in CRITICAL_KEYS, 'Missing stathera_today_gaps in CRITICAL_KEYS'
assert 'stathera_today_overlaps' in CRITICAL_KEYS, 'Missing stathera_today_overlaps in CRITICAL_KEYS'
print('CRITICAL_KEYS: OK')

# Verify formatter has remediation hints

src = open('scripts/heartbeat/formatter.py').read()
assert 'stathera_today_gaps' in src, 'Missing remediation hint for stathera_today_gaps'
assert 'stathera_today_overlaps' in src, 'Missing remediation hint for stathera_today_overlaps'
print('Formatter hints: OK')

# Verify \_check_today_gaps no longer suppresses via kintsugi_healing

stathera_src = open('scripts/heartbeat/checks/stathera.py').read()

# Find the \_check_today_gaps function

func_start = stathera_src.index('def \_check_today_gaps(')
func_body = stathera_src[func_start:stathera_src.index('\ndef ', func_start + 1) if '\ndef ' in stathera_src[func_start + 1:] else len(stathera_src)]

# The old pattern was 'gap_delta <= 0 or kintsugi_healing'

assert 'gap_delta <= 0 or kintsugi_healing' not in func_body, 'kintsugi_healing still suppresses today-gap alert'
print('\_check_today_gaps suppression removed: OK')

# Verify collect_stathera sets stathera_today_gaps

assert 'stathera_today_gaps' in stathera_src, 'Missing stathera_today_gaps in collect_stathera'
assert 'stathera_today_overlaps' in stathera_src, 'Missing stathera_today_overlaps in collect_stathera'
print('collect_stathera today keys: OK')
"</automated>
</verify>
<done> - collect_stathera() sets stathera_today_gaps and stathera_today_overlaps (never suppressed by kintsugi_healing)
-\_check_today_gaps LOUD alert fires regardless of kintsugi_healing - CRITICAL_KEYS includes stathera_today_gaps and stathera_today_overlaps - Formatter has remediation hints for both new keys
</done>
</task>

<task type="auto" tdd="true">
  <name>Task 4: Tests for P0-3 heartbeat today-gap awareness</name>
  <files>tests/test_heartbeat_stathera.py, tests/test_heartbeat_health_levels.py</files>
  <behavior>
    - Test 1: Today gaps + kintsugi healing -> stathera_today_gaps=False (CRITICAL), historical stathera_gaps suppressed by kintsugi
    - Test 2: Historical gaps only + kintsugi healing -> stathera_today_gaps=True, stathera_gaps=True (both healthy)
    - Test 3: Zero anomalies -> stathera_today_gaps=True, stathera_today_overlaps=True
    - Test 4: stathera_today_gaps=False in health level test -> level=critical
    - Test 5: _check_today_gaps fires LOUD alert even when kintsugi_healing=True (gap_delta > 0, today gaps exist)
  </behavior>
  <action>
**tests/test_heartbeat_stathera.py** — Add new test class after existing tests:

```python
class TestTodayAnomalyAwareness:
    """#295 P0-3: Today anomalies never suppressed by kintsugi_healing."""

    def test_today_gaps_with_kintsugi_healing(self):
        """Today gaps + kintsugi healing -> stathera_today_gaps=False, stathera_gaps=True (suppressed)."""
        client = _make_client({
            "is_done":      [[0]],       # no pending mutations
            "GROUP BY symbol": [         # per-symbol: gaps exist
                ["BTCUSDT", 250, 3, 0],
            ],
            "countIf(td > 0)": [[2, 0]],  # 2 today gaps, 0 today overlaps
            "today()": [                 # today-gap detail rows for LOUD alert
                ["BTCUSDT", 250, 100, 103, 2],
            ],
            "violations": [[0]],         # no cross-midnight
        })
        results: dict = {"kintsugi": True, "kintsugi_stuck": True}

        with patch("heartbeat.checks.stathera.load_stathera_counts",
                    return_value={"gap_count": 0, "overlap_count": 0}), \
             patch("heartbeat.checks.stathera.save_stathera_counts"), \
             patch.dict(os.environ, {"OPENDEVIATIONBAR_TELEGRAM_TOKEN": ""}):
            from heartbeat.checks.stathera import collect_stathera
            collect_stathera(client, results)

        # Today keys are NEVER suppressed
        assert results["stathera_today_gaps"] is False
        assert results["stathera_today_overlaps"] is True
        # Historical gaps suppressed by kintsugi_healing
        assert results["stathera_gaps"] is True

    def test_historical_gaps_only_with_kintsugi_healing(self):
        """Historical gaps + kintsugi healing, no today anomalies -> all healthy."""
        client = _make_client({
            "is_done":      [[0]],
            "GROUP BY symbol": [
                ["BTCUSDT", 250, 5, 0],
            ],
            "countIf(td > 0)": [[0, 0]],  # 0 today gaps, 0 today overlaps
            "today()": [],
            "violations": [[0]],
        })
        results: dict = {"kintsugi": True, "kintsugi_stuck": True}

        with patch("heartbeat.checks.stathera.load_stathera_counts",
                    return_value={"gap_count": 0, "overlap_count": 0}), \
             patch("heartbeat.checks.stathera.save_stathera_counts"), \
             patch.dict(os.environ, {"OPENDEVIATIONBAR_TELEGRAM_TOKEN": ""}):
            from heartbeat.checks.stathera import collect_stathera
            collect_stathera(client, results)

        assert results["stathera_today_gaps"] is True
        assert results["stathera_today_overlaps"] is True

    def test_zero_anomalies(self):
        """No anomalies at all -> all today keys True."""
        client = _make_client({
            "is_done":      [[0]],
            "GROUP BY symbol": [],
            "countIf(td > 0)": [[0, 0]],
            "today()": [],
            "violations": [[0]],
        })
        results: dict = {}

        with patch("heartbeat.checks.stathera.load_stathera_counts", return_value={}), \
             patch("heartbeat.checks.stathera.save_stathera_counts"), \
             patch.dict(os.environ, {"OPENDEVIATIONBAR_TELEGRAM_TOKEN": ""}):
            from heartbeat.checks.stathera import collect_stathera
            collect_stathera(client, results)

        assert results["stathera_today_gaps"] is True
        assert results["stathera_today_overlaps"] is True
```

The mock setup uses `"countIf(td > 0)"` as the query substring key for the new today-anomaly query. Verify this substring is unique enough to match only the new query and not the existing ones. If the existing queries also contain this substring, use a more specific key like `"countIf(td > 0) AS today_gaps"`.

**tests/test_heartbeat_health_levels.py** — Add test to existing class:

```python
    def test_stathera_today_gaps_is_critical(self):
        """stathera_today_gaps=False -> CRITICAL (not WARNING)."""
        results = {"stathera_today_gaps": False}
        r = self._run_with_results(results)
        assert r["level"] == "critical"
        assert "stathera_today_gaps" in r["critical"]

    def test_stathera_today_overlaps_is_critical(self):
        """stathera_today_overlaps=False -> CRITICAL."""
        results = {"stathera_today_overlaps": False}
        r = self._run_with_results(results)
        assert r["level"] == "critical"
        assert "stathera_today_overlaps" in r["critical"]
```

  </action>
  <verify>
    <automated>cd /Users/terryli/eon/opendeviationbar-py && uv run --python 3.13 pytest tests/test_heartbeat_stathera.py tests/test_heartbeat_health_levels.py -x -v 2>&1 | tail -30</automated>
  </verify>
  <done>
    - 5 new tests pass covering today-gap awareness, kintsugi suppression split, and health level gating
    - Existing heartbeat tests still pass (no regressions)
  </done>
</task>

</tasks>

<verification>
All 4 P0 fixes verified:

1. **P0-1a**: `grep MAX_PENDING_BARS deploy/opendeviationbar.env` shows 100000
2. **P0-4**: `_delete_today_bars` has no try/except (grep confirms)
3. **P0-2**: `_do_replace` calls `_dead_letter_replace_failure` on INSERT failure
4. **P0-3**: `CRITICAL_KEYS` contains `stathera_today_gaps` + `stathera_today_overlaps`, \_check_today_gaps does not suppress via kintsugi_healing, 5 new tests pass

```bash
# Run all heartbeat tests (lightweight, no ClickHouse needed)
cd /Users/terryli/eon/opendeviationbar-py && uv run --python 3.13 pytest tests/test_heartbeat_stathera.py tests/test_heartbeat_health_levels.py -x -v
```

</verification>

<success_criteria>

- OPENDEVIATIONBAR_MAX_PENDING_BARS=100000 in deploy/opendeviationbar.env
- \_delete_today_bars contains zero except clauses
- \_do_replace wraps INSERT in try/except with dead-letter + re-raise
- stathera_today_gaps and stathera_today_overlaps set in collect_stathera, in CRITICAL_KEYS, in formatter hints
- \_check_today_gaps LOUD alert fires regardless of kintsugi_healing
- All heartbeat tests pass (existing + 5 new)
  </success_criteria>

<output>
After completion, create `.planning/quick/260321-ql9-sprint-1-guard-hardening-p0-fixes/260321-ql9-SUMMARY.md`
</output>
