Skip to content

Harvester v1 — план валидации ПЕРЕД длинным запуском ​

ИСТОРИЧЕСКИЙ ПЛАН. Replay, SimOrderClient и capture-пункты ниже отменены решением оператора 2026-08-22. Документ сохранён как история планирования и не является текущей очередью работ или процедурой проверки.

Директива оператора 2026-06-24: НИКАКОГО «запустить и ждать 2 недели» (это 3-месячный цикл «ой опять баг, рестарт»). Порядок: (A) найти ВСЕ баги; (B) покрыть код E2E+интеграционными тестами на РЕАЛЬНЫХ данных (не синтетика), каждый метод валидирован на данных; (C) записать реальный трафик за часы, прогнать код, проверить каждую строку/переменную/логическую цепочку; (D) только потом длинный запуск. Совпадает с CLAUDE.md quality-bar.

Ключевой факт ​

Полный реальный capture с Gamma уже есть: .tmp/captures/01KTYQT30JD0V7BEP0T289AE60 (3.6-дневный погодный, 8.3G: rest_gamma.jsonl.zst 3.1G c реальными outcomePrices/ endDate/gameStartTime, ws_market.jsonl.zst 5.1G book/trade/resolved, rest_clob, rtds). Значит реальные тесты можно начинать БЕЗ починки native --capture-only (у того нет Gamma — это отдельный форвард-трек A5, нужен позже для post-fix записи).

Граница точности (честно, не маскировать): EngineReplayDriver гоняет РЕАЛЬНЫЕ DirectionalStrategy + SimOrderClient + PositionRegistry + OrderRouter, но wall-clock tokio::select! StrategyDriver-цикл НЕ frame-steppable — драйвер ЗЕРКАЛИТ его детерминированное ядро. Любое block-D «построчно» — в рамках реальных компонентов; зеркалированный цикл помечается явно.

SCOPE: реплей этого баг-эра capture (2026-06-16) сертифицирует КОРРЕКТНОСТЬ/ СОГЛАСОВАННОСТЬ учёта (инварианты, числа самосогласованы), НЕ +EV (+EV требует форвард post-fix данных). Все block-B тесты проверяют учётные тождества, не доходность.

Критический путь ​

A1 → A2 → A3 → A4 → (B + C-real-traffic) → D → verdict (после сходимости ResolutionSweep)
              A5 параллельно (форвард-трек); C-code-trace параллельно с A

Блок A — harness (чтобы реальный трафик → проверяемый engine.db) [АВТОНОМНО] ​

  • A1 (keystone): file-backed SqliteStorage в EngineReplayDriver (сейчас open_memory() → in-memory отчёт; нужен open(path) на bare ::new, replay/mod.rs). Без файла engine.db forensics.sh --engine/harvester-ev не читают.
  • A2 (основной объём): reader мульти-стримов .jsonl.zst → ReplayFrame. Сейчас такого нет (прежний pm-bot Parquet-мост удалён при cutover 2026-07-22). Должен: слить все стримы по ts_ns/monotonic_seq в один глобальный порядок; восстановить asset_id→condition_id из MarketEvent::NewMarket/Gamma clobTokenIds (book-кадры имеют пустой condition_id); раскрыть PriceChange multi-level в per-level BookDelta; подавать GammaResponse (иначе build_snapshot=None). Parse-половина уже есть и real-data-протестирована (parse_gamma_body_to_metadata/apply_gamma_body).
  • A3: CLI pm-engine replay --capture <dir> --out engine.db.
  • A4 (ОБЯЗАТЕЛЬНЫЙ guard): детерминизм ×2 на bare real-LDI (::new, НЕ new_ldi_forced). Не bit-identical → документировать как ask_persistence_decay hazard, НЕ маскировать. ЭСКАЛАЦИЯ.
  • A5 (параллельно, build автономно / deploy operator): native --capture-only Gamma-tee для БУДУЩИХ форвард-записей (не на крит-пути первого теста).

Блок B — реальные E2E/интеграционные тесты [АВТОНОМНО] ​

ЖЁСТКО: каждый admitting-тест на ::new (bare real LDI), НИКОГДА new_ldi_forced. Сейчас ВСЕ P&L-тесты на new_ldi_forced → реальный LDI-гейт (решает весь набор позиций, что читает вердикт) имеет НОЛЬ покрытия. Оракул — руками из глубины стакана capture, НЕ из вывода кода. Стадии: 1 ingestion→snapshot (sort/None-инварианты); 2/3 decision→ entry→order→sim-fill (entry_cost==price×size/10^4; min-size; FOK без resting-leak; reserve→fill→release баланс); 4 sim-matcher на РЕАЛЬНОЙ thin-book глубине (зона phantom-fill бага); 5 positions/budget (realized==exit−entry; oversold reject); 6 closure/resolution (winner только по WS winning_token_id/чистый Gamma 1.0/0.0, никогда default-loser; каждая held на resolved → non-NULL realized).

Блок C — адверсариальный bug-hunt ​

  • C1 [АВТОНОМНО, ТОП]: winner-классификация (resolve_winner/book_resolution_pnl) forward-audit на реальных resolved-строках — точный 1.0/0.0 знак, ноль default-loser, ноль вечно-NULL realized. Доминирует: r=0 ⇒ лузеры досиживают; mis-winner → знак вердикта.
  • C2 [АВТОНОМНО]: allocate add/sub паритет (order_router) — счётчик на реальном трафике, нет double-release при закрытии двумя путями (stop SELL + resolution).
  • C3 [OPERATOR]: 24h VPS shadow на live WS — все *_err/*_missing_total==0.
  • C4 [INFERRED→certify]: stop realized полный цикл на реальном recovery-трафике (r/s_eff измерить, не утверждать; FOK без phantom-loss).
  • C5 [OPERATOR]: канон sim-fill (partial vs all-or-nothing + ≥5-или-0) — 3 матчера расходятся 5/7; ≥5-или-0 residual НЕ реализован в engine entry_sizing.
  • C6 [OPERATOR]: LDI-асимметрия vs прежний pm-bot (tick-7 структурное расхождение, историческое сравнение) — разные входные популяции → разный p_e/winrate.
  • C7 [АВТОНОМНО, LOW]: TODO/unimplemented — подтвердить, что не на dry-run hot-path, закрыть.

Блок D — построчная верификация на часах реального трафика [АВТОНОМНО] ​

На file-backed engine.db из A3. Через forensics.sh --engine/skill trade-forensics, НЕ ad-hoc. D1 per-position ledger (каждый realized → реальный BUY FILLED, нет phantom-booking); D2 variable-инварианты (entry_cost, realized, budget-identity, нет negative-allocated); D3 logic-chain (порядок entry-гейтов на реальных тиках; snapshot Some только после Gamma; один snapshot для decision И sim-rematch); D4 детерминизм bit-identical (иначе hazard live → ЭСКАЛАЦИЯ); D5 convergence-gate (ноль resolved с NULL realized) перед чтением вердикта. Вердикт = CONSISTENCY на баг-эра данных, НЕ +EV.

Что требует operator sign-off (не решать самому) ​

C3 (24h live shadow), C5 (канон sim-fill/entry), C6 (канон LDI), ЛЮБОЙ +EV-вердикт (только форвард post-fix), любой non-bit-identical A4 (hazard), любой sources-disagree.

Оценка ​

Автономная инженерия ~12–18 дней по крит-пути A→B/C→D (A5 + C-code-traces параллельно); исключает operator-gated shadow/adjudication. Полная детализация: workflow-вывод tasks/wxy5h8wps.output.