---
phase: 09-verification-registry
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - python/opendeviationbar/checksum_registry.py
  - tests/test_checksum_registry.py
autonomous: true
requirements: [TELEM-03]

must_haves:
  truths:
    - "Registry stores per-file records with symbol, date, sha256, verified_at, and source fields"
    - "Registry is queryable by symbol and by date range"
    - "Seeder can write a verification record; sweep can read existing records to skip already-verified files"
    - "Registry persists across process restarts as a JSON file on disk"
  artifacts:
    - path: "python/opendeviationbar/checksum_registry.py"
      provides: "ChecksumRegistry class with add/query/has_verified/save/load API"
      exports: ["ChecksumRegistry", "VerificationRecord"]
    - path: "tests/test_checksum_registry.py"
      provides: "Unit tests covering add, query, persistence, dedup, edge cases"
      min_lines: 80
  key_links:
    - from: "python/opendeviationbar/checksum_registry.py"
      to: "~/.cache/opendeviationbar/checksum-registry.json"
      via: "JSON load/save on disk"
      pattern: "json\\.(load|dump)"
    - from: "python/opendeviationbar/checksum_registry.py"
      to: "platformdirs.user_cache_dir"
      via: "get_cache_dir() for base path"
      pattern: "get_cache_dir"
---

<objective>
Create the centralized checksum verification registry -- a per-file verification state store that tracks which Parquet cache files have been SHA-256 verified against their Vision CHECKSUM files.

Purpose: Foundation layer for Phase 10 (seeder writes records), Phase 11 (sweep reads to skip already-verified), and Phase 12 (heartbeat queries status). Without this, there is no SSoT for verification state.

Output: `checksum_registry.py` module + comprehensive test suite.
</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/08-checksum-spike/08-01-SUMMARY.md

@python/opendeviationbar/storage/parquet_io.py (get_cache_dir pattern)
@python/opendeviationbar/storage/parquet.py (TickStorage pattern for file-based state)

<interfaces>
<!-- Executor needs these existing utilities -->

From python/opendeviationbar/storage/parquet_io.py:

```python
def get_cache_dir() -> Path:
    """Cross-platform cache dir: ~/.cache/opendeviationbar/ on Linux."""
```

Existing cache layout convention:

```
~/.cache/opendeviationbar/
├── ticks/           # Parquet tick cache
├── checkpoints/     # populate_cache_resumable state
├── heartbeat/       # pending_messages.json
└── seeder-state/    # boundary markers
```

The registry file should live at: ~/.cache/opendeviationbar/checksum-registry.json
</interfaces>
</context>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: ChecksumRegistry data model, read/write API, and persistence</name>
  <files>python/opendeviationbar/checksum_registry.py, tests/test_checksum_registry.py</files>
  <read_first>
    - python/opendeviationbar/storage/parquet_io.py (get_cache_dir pattern, lines 235-258)
    - python/opendeviationbar/storage/parquet.py (TickStorage class structure for module conventions)
    - tests/test_checksum_spike.py (test conventions in this project)
  </read_first>
  <behavior>
    - Test 1: add() stores a record and get_records(symbol) returns it
    - Test 2: has_verified(symbol, date_str) returns True after add(), False before
    - Test 3: query_by_symbol(symbol) returns only records for that symbol
    - Test 4: query_by_date_range(start, end) returns records within range (inclusive)
    - Test 5: query_by_date_range with symbol filter narrows results further
    - Test 6: Duplicate add (same symbol+date) updates existing record (upsert, not duplicate)
    - Test 7: save() writes JSON to disk, load() restores identical state
    - Test 8: load() on missing file returns empty registry (no crash)
    - Test 9: Registry round-trips through save/load with multiple symbols and dates
    - Test 10: summary() returns dict with total_verified count, per-symbol counts, and latest_verified_at
    - Test 11: VerificationRecord stores symbol, date, sha256, verified_at (ISO 8601), source ("seeder"|"sweep")
  </behavior>
  <action>
Create `python/opendeviationbar/checksum_registry.py` with:

1. **VerificationRecord** dataclass:
   - `symbol: str` (e.g., "BTCUSDT")
   - `date: str` (ISO date "YYYY-MM-DD")
   - `sha256: str` (hex digest from Vision CHECKSUM)
   - `verified_at: str` (ISO 8601 datetime with UTC timezone)
   - `source: str` ("seeder" or "sweep")
   - Methods: `to_dict()`, `from_dict(cls, d)` classmethod

2. **ChecksumRegistry** class:
   - `__init__(self, path: Path | None = None)` -- defaults to `get_cache_dir() / "checksum-registry.json"`. Calls `self.load()` in **init** to auto-load existing state.
   - Internal storage: `dict[str, VerificationRecord]` keyed by `"{symbol}:{date}"` (ensures upsert on duplicate symbol+date)
   - `add(self, symbol, date, sha256, source)` -- creates VerificationRecord with `verified_at=datetime.now(UTC).isoformat()`, stores in internal dict. Does NOT auto-save (callers batch writes then call save()).
   - `has_verified(self, symbol: str, date: str) -> bool` -- returns True if key exists
   - `query_by_symbol(self, symbol: str) -> list[VerificationRecord]` -- filter by symbol
   - `query_by_date_range(self, start: str, end: str, symbol: str | None = None) -> list[VerificationRecord]` -- filter by date range (inclusive), optionally by symbol
   - `summary(self) -> dict` -- returns `{"total_verified": int, "by_symbol": {symbol: count}, "latest_verified_at": str | None}`
   - `save(self)` -- atomic write: write to `.tmp` then `os.replace()` to target path (same pattern as TickStorage)
   - `load(self)` -- read JSON from disk into internal dict. Missing file = empty dict (no error). Invalid JSON = log warning + empty dict.
   - `__len__(self)` -- number of records

JSON format on disk:

```json
{
  "version": 1,
  "records": {
    "BTCUSDT:2024-01-15": {
      "symbol": "BTCUSDT",
      "date": "2024-01-15",
      "sha256": "abc123...",
      "verified_at": "2026-03-26T05:00:00+00:00",
      "source": "seeder"
    }
  }
}
```

Use `from __future__ import annotations` at top. Import `get_cache_dir` from `opendeviationbar.storage.parquet_io`. Use standard library only (json, dataclasses, pathlib, datetime, logging, os). No new dependencies.

Then create `tests/test_checksum_registry.py` with all 11 tests from the behavior block. Use `tmp_path` fixture for all persistence tests (never write to real cache dir). Test the full round-trip: create registry, add records, save, create new registry from same path, verify identical state.
</action>
<verify>
<automated>cd /Users/terryli/eon/opendeviationbar-py && uv run --python 3.13 pytest tests/test*checksum_registry.py -v</automated>
</verify>
<acceptance_criteria> - grep -c "class VerificationRecord" python/opendeviationbar/checksum_registry.py returns 1 - grep -c "class ChecksumRegistry" python/opendeviationbar/checksum_registry.py returns 1 - grep -c "def add" python/opendeviationbar/checksum_registry.py returns 1 - grep -c "def has_verified" python/opendeviationbar/checksum_registry.py returns 1 - grep -c "def query_by_symbol" python/opendeviationbar/checksum_registry.py returns 1 - grep -c "def query_by_date_range" python/opendeviationbar/checksum_registry.py returns 1 - grep -c "def save" python/opendeviationbar/checksum_registry.py returns 1 - grep -c "def load" python/opendeviationbar/checksum_registry.py returns 1 - grep -c "def summary" python/opendeviationbar/checksum_registry.py returns 1 - grep -c "def test*" tests/test_checksum_registry.py returns at least 11 - All tests pass (pytest exit code 0)
</acceptance_criteria>
<done> - ChecksumRegistry class with add/has_verified/query_by_symbol/query_by_date_range/summary/save/load API - VerificationRecord dataclass with symbol, date, sha256, verified_at, source fields - Upsert on duplicate symbol+date (no duplicates) - Atomic save via tmp+rename pattern - Load tolerates missing file and invalid JSON - 11+ passing tests covering all behaviors
</done>
</task>

</tasks>

<verification>
1. `uv run --python 3.13 pytest tests/test_checksum_registry.py -v` -- all tests pass
2. `python -c "from opendeviationbar.checksum_registry import ChecksumRegistry, VerificationRecord; print('import OK')"` -- module importable
3. `grep -c "get_cache_dir" python/opendeviationbar/checksum_registry.py` -- uses existing cache dir utility (not hardcoded path)
</verification>

<success_criteria>

- ChecksumRegistry is importable from `opendeviationbar.checksum_registry`
- Registry persists to JSON at `~/.cache/opendeviationbar/checksum-registry.json` (default path)
- Seeder use case: `registry.add("BTCUSDT", "2024-01-15", "abc...", "seeder")` then `registry.save()`
- Sweep use case: `registry.has_verified("BTCUSDT", "2024-01-15")` returns True to skip re-verification
- Heartbeat use case: `registry.summary()` returns total/per-symbol counts
- All 11+ unit tests pass
  </success_criteria>

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