8.4 KiB
Known-FQME Backfill Qualification
Purpose
Move backfiller_deep_fixes into the main checkout, reproduce the historical backfill failures against one known-problem market, and turn every confirmed failure into deterministic regression coverage before expanding live qualification.
This is a correctness-debugging pass, not a broad provider smoke test. One FQME and one timeframe remain fixed until their original failure is understood, covered, repaired, and requalified.
Current Repository State
The branch is currently checked out at:
/home/goodboy/repos/piker/.claude/wkts/backfiller_deep_fixes
Its head is 10e0ced7. The linked worktree has only generated, untracked commit-message metadata.
The main checkout is on flake_update at c6ec3d41 and has staged and untracked user work. Do not switch branches, stash, remove files, or close the linked worktree until that state has been explicitly preserved by the user.
Phase 1: Transfer Branch Ownership Safely
- Review the main checkout’s staged and untracked state.
- Preserve it using the user’s chosen mechanism: finish and commit it, move it to another branch/worktree, or explicitly create a stash that includes the required untracked files.
- Review the linked worktree’s generated
.claude/skills/commit-msg/artifacts. Archive or discard them explicitly; they are not branch source. - Remove the linked
backfiller_deep_fixesworktree while keeping its exact branch. - Switch the main checkout to
backfiller_deep_fixes. - Verify all entry points import from the main checkout and resolve to head
10e0ced7or its descendant. - Run the 31-test deterministic analysis/backfill/storage set before booting any daemon.
Gate: the main checkout owns backfiller_deep_fixes, tracked status is clean, user work from flake_update is preserved, and deterministic tests pass.
Phase 2: Record The Known Failure
Capture these details for the selected case:
- provider and exact FQME;
- timeframe, initially
60or1seconds; - known-bad date/time range and timezone;
- original symptom: destructive truncation, duplicate seam, null rows, out-of-order timestamps, hang, or another observed failure;
- command or chart workflow that exposed it;
- whether a known-bad parquet already exists;
- expected venue closure or continuous-session behavior;
- credentials, gateway, and network requirements;
- smallest reproducible provider response or persisted frame.
Do not start with multiple symbols, providers, or timeframes. Preserve a checksum of any known-bad parquet before copying it into disposable storage.
Gate: one case has an explicit expected result and enough source data to distinguish a real provider/venue gap from a piker persistence defect.
Phase 3: Build A Disposable Runtime
Use the main checkout’s Python environment and a dedicated XDG config root. The pikerd process and every client process must inherit the same config root and import the main checkout.
The disposable directory contains only:
- a minimal
conf.toml; - credentials required for the selected provider;
- a dedicated
nativedb/directory; - an optional copy of the single known-bad parquet;
- captured logs and before/after audit output.
Ensure no older pikerd is listening on the selected registry address. Verify piker.__file__, the branch, and the commit before starting the daemon.
Gate: deleting the disposable directory cannot affect normal user configuration, accounting data, or production history.
Phase 4: Establish A Read-Only Baseline
Before backfill, record:
- parquet checksum and readability;
- earliest and latest timestamp;
- total and unique timestamp counts;
- duplicate count;
- zero and non-finite timestamp counts;
- non-positive timestamp deltas;
- null OHLCV counts;
- contiguous persisted index status;
- positive sample gaps with explicit endpoints.
Use a read-only audit helper or focused Python test. Do not automate piker store anal or piker store ldshm; both can pause or mutate attached data.
Gate: the original symptom is visible in saved evidence, or the plan records why a fresh provider fetch is required to reproduce it.
Phase 5: Reproduce One Lifecycle At A Time
Run in this order:
- Load the known-bad persisted history without a provider write.
- Start
pikerdand request the chosen FQME/timeframe only. - Observe startup latest-frame query, pre-update TSDB snapshot, reverse backfill, provider-delta commit, SHM publication, and notification.
- Stop cleanly and rerun the read-only audit.
- Restart and append to the same disposable history.
- Compare earliest/latest timestamps, row counts, conflicts, and gaps against the baseline.
Capture logs around every update_ohlcv() call, provider frame boundary, SHM truncation, lock acquisition, temporary parquet, replacement, and cache publication.
Gate: the run either preserves every unaffected row and normalizes the known defect, or yields a bounded failure with enough data for a test.
Phase 6: Convert Failures Into Tests
For each reproduced defect:
- Save the smallest provider frames and persisted rows needed to reproduce it. Prefer synthetic arrays; use a trimmed fixture only when provider-specific boundaries matter.
- Write a failing deterministic test before changing production code.
- Assert exact timestamps, endpoint policy, persisted rows, and state transition ordering rather than only asserting completion.
- Keep actor-free tests in the existing focused modules where possible.
- Add subprocess integration tests only for process death, advisory locks, or atomic filesystem behavior that cannot be represented in one Trio runtime.
- Apply the smallest production fix and rerun both the focused test and the complete deterministic set.
- Rerun the exact manual FQME lifecycle that found the failure.
Potential deterministic modules:
tests/test_tsp_analysis.pyfor normalization and classification;tests/test_history_backfill.pyfor provider/SHM/storage ordering;tests/test_storage_nativedb.pyfor merge and atomic durability;- a new subprocess fault module for kill/restart and cross-process locks;
- provider-specific fixtures only when generic frames cannot reproduce the behavior.
Gate: no production fix lands without a failing-before, passing-after test or a written explanation for an unavoidable live-only condition.
Phase 7: Fault Qualification
After the normal lifecycle is stable, exercise:
- graceful Ctrl-C during provider query and storage update;
- SIGTERM at deterministic query/write barriers;
- SIGKILL after temp write, before replace, and after replace;
- provider disconnect and recovery;
- concurrent NativeDB writers for the same series;
- restart after every interruption;
- actual sampler backpressure and actor teardown;
- MarketStore separately from NativeDB.
Use explicit barriers or failpoints rather than timing sleeps whenever the failure position matters. Repeat the read-only audit after every restart.
Gate: the final parquet is always old-valid or new-valid, unaffected history remains present, indexes are contiguous, cache agrees with the visible file, and shutdown remains bounded.
Expansion Order
Only after the chosen FQME passes:
- qualify its second timeframe;
- qualify another continuous-session provider;
- run Binance, Kraken, and Kucoin cases;
- test IB with an available account and gateway;
- add expected venue-closure classification;
- widen to the remaining manual matrix.
Commit Boundaries
Keep follow-up commits narrow:
- captured fixture and failing regression;
- minimal analysis/backfill/storage correction;
- subprocess fault-injection harness;
- provider-specific adapter correction;
- qualification notes and reproducible audit output.
Every boundary must leave deterministic tests green. Do not combine annotation performance, unrelated providers, or chart UX with the known FQME debugging loop.
Exit Criteria
The selected FQME/timeframe is complete when:
- the original bug reproduces from preserved evidence;
- a deterministic regression covers it where technically possible;
- fresh and append backfills preserve unaffected history;
- provider seam conflicts follow the documented policy;
- no zero, duplicate, non-finite, or non-positive timestamps persist;
- indexes remain contiguous;
- clean shutdown and restart preserve readable parquet;
- failure injection leaves an old-valid or new-valid file;
- remaining live-only risks are explicitly recorded.