Тема
Phase 0 Sign-Off — Spec-Driven Coverage (2026-05-06)
Status: BLOCKING gate — User decision required before Phase 1 starts. Worktree: feat/spec-driven-coverage at /home/me/RustroverProjects/polymarket-engine-tests (forked from main @ commit 940e9ca). Trigger: User request 2026-05-06 — 100% spec-driven coverage of trading business logic; lessons from RTDS-incident-2026-05-06 (silent-broken class).
1. Phase 0 deliverables (in worktree)
| Artefact | Path | Size | State |
|---|---|---|---|
| Worktree branch | feat/spec-driven-coverage | — | created from main 940e9ca |
pm-test-utils crate | crates/pm-test-utils/ | 2985 LOC, 73 unit tests | green; clippy -D warnings clean |
| Rule matrix | docs/operations/test-coverage-matrix.md | 271 lines, 172 rule-IDs (G/BA/DIR/MK/MR/MA) | all unmapped (Phase 1 maps tests) |
| Liveness matrix | docs/operations/feature-liveness-matrix.md | 546 lines, 143 LIV-IDs | 16 mapped (incl. LIV-1.4, LIV-1.7 promoted post-TD-31/32 closure), 2 partial (LIV-1.3, LIV-1.9), 125 unmapped |
| Acceptance criteria | docs/operations/acceptance-criteria-trading.md | 934 lines, 10 AC (Gherkin) | all unmapped |
| Test audit report | docs/operations/test-audit-report.md | 1059 lines, Bucket A / B / C | done |
| CI workflows | .github/workflows/{coverage,mutation-nightly}.yml | complements existing parity.yml, rtds-smoke.yml | scaffolded |
| Helper scripts | scripts/{check-requirement-coverage,check-liveness-coverage}.py, scripts/replay-determinism.sh | stdlib only (no pip) | executable |
2. Audit summary (test-audit-report.md)
Workspace census: 1572 tests. Bucket distribution:
- Bucket A — kept (~210 tests): observe a side-effect boundary (placement_log / journal row / SSE envelope / log line / metric counter) AND derived from a business rule. Promote LIV-IDs / rule-IDs to
mapped. - Bucket B — rewrite candidates (~1230 tests): pure-function asserts without rule-ID binding (potential code-derived tautology). Of those: 280 MEDIUM rewrite priority, 830 LOW (math correctness — keep but bind), 120 DELETE candidates (dead code per memory
max_hours_dead_code, removed-feature stubs, construction-only smoke). - Bucket C — silent-broken-suspect (47 LIV-IDs): feature claimed in ADR/FSD but no existing test observes the boundary. Distribution: 8 HIGH, 24 MEDIUM, 15 LOW.
3. HIGH-risk silent-broken candidates (escalation)
8 HIGH-risk LIV-IDs requiring per-row User decision (test gap → write liveness test in Phase 2 cross-cutting; OR suspected code bug → separate trading PR).
| LIV-ID | Feature | Hypothesis | TD ref |
|---|---|---|---|
| LIV-3.4 | Directional evaluate_periodic consults TimezoneCache | Code gap — ADR-016 §D5 explicitly defers StrategyDeps wiring; directional.rs:1913 has TODO. Periodic harvester may misclassify "today vs tomorrow" markets per memory feedback_max_hours_expiry. | TD-37 NEW |
| LIV-4.4 | Paired maker-rebate BothFilled → Decision::CallMergePositions reaches executor | Test gap; possible silent-broken on first MAKER_REBATE_PAIRED_ENABLED=true activation. Only Vec<Decision> length asserted; executor wiring MOC.merge_calls.len() == 1 not. | TD-34 existing |
| LIV-5.3 | merge-arb standard CTF dispatch (neg_risk=false) | Test gap; financial-system risk per memory project_neg_risk_audit (5+ rounds of neg-risk inversion bugs). Decision-shape asserted but adapter dispatch not. | TD-34 existing |
| LIV-5.4 | merge-arb NegRiskAdapter dispatch (neg_risk=true) | Test gap; MERGE_ARB_NEG_RISK_ENABLED=false default → branch has zero coverage. HIGH RISK on first activation. | TD-34 existing |
| LIV-6.4 | Recovery: no duplicate placement on first scan tick | Test gap; dup placement = financial loss. tests/restart_attempt_recovery.rs covers attempt counter, doesn't directly assert MOC stays empty for N ticks post-recovery. | TD-39 NEW |
| LIV-14.3 | Paranoid bulk-cancel CANCEL token gate (>5 rows) | Test gap; operator-protective gate may be silently disabled. Backend integration test for 6-row cancel without token does not exist. | TD-34 existing |
| LIV-16.2 | BOT_WALLET_LABEL set + empty WALLETS_MASTER_PASSPHRASE ⇒ panic | Test gap; financial-system invariant. #[should_panic] test for INV-2 fail-fast does not exist. | TD-34 existing |
| LIV-16.3 | INV-3 cross-check strategy.wallet_label == BOT_WALLET_LABEL ⇒ panic | Same — INV-3 panic not asserted. Wallet-budget aliasing across strategies = silent over-allocation. | TD-34 existing |
4. CLOSED in worktree since 2026-05-06 RCA
Three RCA gaps from liveness-audit-2026-05-06.md §3 already addressed in main:
| TD | Status | Closure artefact |
|---|---|---|
| TD-31 | CLOSED | tests/rtds_hook_e2e.rs (3 tests, Bucket A) — RTDS parser → ScannerHooks::on_rtds_trade end-to-end |
| TD-32 | CLOSED | tests/rtds_active_cancel.rs (4 tests, Bucket A) — toxic-flow active-cancel branch |
| TD-33 | PARTIALLY CLOSED | application/alerts/rule.rs::t_rtds_health_* (8 tests, Bucket A). Still open: LIV-22.1 daily smoke-rtds CI cron + LIV-28.2 Q3 timestamp magnitude automated gate (P0 per RCA §6.2). |
rtds-smoke.yml exists with daily 06:00 UTC cron — verify enabled in repo settings.
5. P0 still OPEN (RCA §6.2 must close before next live trading)
- LIV-22.1 — daily
--smoke-rtdsCI cron actually wired intortds-smoke.ymland active in repo. Check that the workflow is not indisabledstate. - LIV-28.2 — Q3 timestamp-magnitude invariant as automated gate (not just SmokeReport pass condition) —
timestamp_format != MillisecondsMUST fail the build, not just emit a warning.
6. TD entries to merge into tech-debt-backlog.md
26 entries surfaced. Cross-mapping with existing liveness-audit-2026-05-06.md recommendations:
| TD | Source | Priority | LIV-IDs | Status |
|---|---|---|---|---|
| TD-31 | existing audit | HIGH | LIV-1.3, 1.4 | CLOSED in worktree |
| TD-32 | existing audit | HIGH | LIV-1.6, 1.7 | CLOSED in worktree |
| TD-33 | existing audit, extended | HIGH (P0 outstanding) | LIV-1.8, 1.9, 22.1, 28.2 | PARTIALLY CLOSED — extend |
| TD-34 | existing audit, bundle | MEDIUM | 13 LIV-IDs (see audit §Cross-mapping) | OPEN |
| TD-35..TD-56 | this audit (NEW) | mixed (HIGH×2, MEDIUM×11, LOW×9) | 30+ LIV-IDs | OPEN |
Detail of NEW TDs in test-audit-report.md §"Recommended TD entries". User sign-off required per memory feedback_no_autonomous_changes before any test PR lands against these TDs.
7. Decision points (User must answer)
D1. Phase 0 sign-off: approve / reject?
- Approve ⇒ Phase 1 P0 starts (merge-arb gap closure: 3 unit → ~80 spec-driven, e2e replay, MA-001/MA-005 fixtures).
- Reject ⇒ which artefact to revisit (matrix, audit, signoff body)?
D2. HIGH-risk gaps (LIV-3.4, LIV-4.4, LIV-5.3, LIV-5.4, LIV-6.4, LIV-14.3, LIV-16.2, LIV-16.3) — for each:
- Test gap (write liveness in Phase 2)? — default for 6 of 8.
- Suspected code bug → separate trading PR? — possible for LIV-3.4 (TODO acknowledged in code) and possibly LIV-5.4 (zero coverage on activation branch suggests untested code).
D3. P0 RCA outstanding: confirm rtds-smoke.yml scheduled cron is active in repo settings; confirm Q3 invariant gate works as a exit 1 not just SmokeReport text.
D4. TD-31/32/33 closure verification — re-run on main branch to confirm tests/rtds_hook_e2e.rs + tests/rtds_active_cancel.rs + t_rtds_health_* are green; promote LIV-1.3..1.9 to mapped in matrices.
D5. Worktree merge cadence:
- (a) PR per phase (recommended — Phase 0 deliverables as one PR, Phase 1 P0 merge-arb as next, etc.).
- (b) Single big PR at end (more reviewer load, slower iteration).
- (c) Continue work directly in worktree without merging until Phase 4 done (highest risk of merge conflict with active main work).
D6. Bucket B rewrite/delete decisions — 120 DELETE candidates and 280 MEDIUM rewrites need per-file owner approval. Phase 1 P0 will surface concrete file lists for sign-off.
8. What Phase 0 deliberately did NOT do
Per spec-driven principle (memory feedback_spec_driven_tests):
- No new test written (test-PR scope = Phase 1+).
- No production code modified (any code change = separate trading PR).
- No reclassification of existing tests in matrices (rule-ID / LIV-ID test refs stay TBD until Phase 1 mapping).
- No frontend Vitest audit (out of scope per
test-audit-report.md§"Out of scope"). - No mutation-testing pass (Phase 3 will run cargo-mutants nightly).
- No live-network probe re-run (RCA §6.4 daily Polymarket contract probe is design-phase).
9. Verification commands (run from worktree)
bash
cd /home/me/RustroverProjects/polymarket-engine-tests
# pm-test-utils green
cargo test -p pm-test-utils # 73 passed
cargo clippy -p pm-test-utils --all-targets -- -D warnings # zero warnings
# matrix parsers reachable + ratchet baselines (see coverage.yml)
python3 scripts/check-requirement-coverage.py \
docs/operations/test-coverage-matrix.md \
--allow-unmapped 172 # 0/172 mapped → exit 0 at Phase 0 baseline
python3 scripts/check-liveness-coverage.py \
docs/operations/feature-liveness-matrix.md \
--allow-unmapped 127 # 16 mapped (14 baseline + LIV-1.4 + LIV-1.7), 2 partial (LIV-1.3, LIV-1.9), 125 strict unmapped; parser counts partial as unmapped → 127 ≤ 127 → exit 0
# replay determinism (existing parity gate)
./scripts/replay-determinism.sh bucket-arb directional maker maker-rebate # merge-arb skipped: no fixture yet
# RCA-class regression sanity (TD-31/32/33 closure verification)
cargo test -p pm-bot --test rtds_hook_e2e # 3/3 — TD-31
cargo test -p pm-bot --test rtds_active_cancel # 4/4 — TD-32
cargo test -p pm-bot --lib t_rtds_health_ # 8/8 — TD-33 unit-side
# NB: --lib bypasses integration-test compile;
# use `--no-run --tests` separately when needed
cargo test -p pm-bot --tests --no-run # all integration tests compileThe --lib form for TD-33 is intentional: cargo test -p pm-bot t_rtds_health_ (without --lib) attempts to compile every integration target first, which would historically fail on the two ManagedOrder test initializers in tests/manual_cancel_attempt.rs:49 and tests/bucket_arb_no_exit_sell.rs:207 (missing rationale / book_snapshot fields added to domain/order_lifecycle/types.rs:90 on 2026-05-06). Both initializers were patched in this Phase 0 worktree; cargo test -p pm-bot --tests --no-run now compiles clean.
10. Review-finding fixes (post-initial sign-off, this revision)
User review of the initial sign-off (2026-05-06) surfaced 3 BLOCKERs + 1 HIGH + 2 MEDIUM. Each addressed in this revision:
| # | Severity | Finding | Resolution |
|---|---|---|---|
| 1 | BLOCKER | coverage.yml ran the matrix parsers without --allow-unmapped, so any PR fails at 0/172 + 14/143. | coverage.yml now sets REQUIREMENT_UNMAPPED_BASELINE=172 and LIVENESS_UNMAPPED_BASELINE=127 as ratchet env vars; gate fails only when unmapped EXCEEDS baseline. PRs that map an ID drop the baseline by exactly 1 in the same commit. |
| 2 | BLOCKER | feature-liveness-matrix.md still listed LIV-1.4 / 1.7 / 1.9 as unmapped despite TD-31/32/33 having been closed in worktree. | LIV-1.4 → mapped (tests/rtds_hook_e2e.rs::*); LIV-1.7 → mapped (tests/rtds_active_cancel.rs::*); LIV-1.9 → partial (alerts/rule.rs::tests::t_rtds_health_*, with CI cron LIV-22.1 + Q3 hard-gate LIV-28.2 still open per RCA §6.2); LIV-1.3 → partial (magnitude detection covered by parse.rs tests). Liveness baseline is now 127 (from 129), reflecting the +2 net mapping. |
| 3 | BLOCKER | cargo test -p pm-bot t_rtds_health_ failed at compile time due to missing rationale + book_snapshot on two ManagedOrder initializers in integration-test files. Verification command misleading. | Both initializers patched (tests/manual_cancel_attempt.rs:67 and tests/bucket_arb_no_exit_sell.rs:226 now set rationale: None, book_snapshot: None). §9 verification commands rewritten to use --lib for the TD-33 unit case and --tests --no-run for the integration compile sanity, with the dependency between them documented. |
| 4 | HIGH | pm-test-utils boundary recorders are scaffold-grade: JournalTail::from_path returns an empty in-memory tail; RecordingOrderClient::list_orders/get_order/get_balance forward without recording. | Documented as Phase 0 deliberate scope: "MVP scaffold for liveness scenarios that need book/event/decision boundaries; SQLite journal tail and full per-method recording are Phase 1 follow-ups". Tracked as TD-57 (NEW). Production paths in pm-test-utils (OrderBookBuilder, RecordingOrderClient::place/cancel/cancel_all/merge, MetricsSnapshot) are complete and exercised by 73 unit tests. |
| 5 | MEDIUM | mutation-nightly.yml ran cargo mutants ... || true — informational, not a gate. | Workflow rewritten: continue-on-error: true on the mutants steps tolerates exit code, then a Python step parses outcomes.json, computes caught / (caught + missed + timeout), and sys.exit(1) on miss. Initial floor 60% (Phase 0 starting baseline); ratchet to 85% (domain/strategies/**) and 90% (pm-common::{pricing,neg_risk}) over Phase 3. |
| 6 | MEDIUM | Q3 timestamp-magnitude hard-gate lives in the workflow, not in the binary — pm-bot --smoke-rtds returns Ok(()) regardless of timestamp_format. | Documented authoritative gate location: .github/workflows/rtds-smoke.yml:133 parses SmokeReport text and exit 1 on timestamp_format != Milliseconds OR schema_parity != true OR trades_parsed < 1. Decision deferred to User: lift the gate into main.rs::smoke_rtds (return non-zero when any pass-condition fails) OR keep at workflow layer with periodic operator review. Tracked as TD-58 (NEW). |
After these fixes, the worktree is internally consistent: matrices, ratchet env vars, sign-off verification commands, and the actual workspace state agree.
11. References
docs/operations/incident-2026-05-06-rtds.md— RCA, P0/P1/P2 corrective actions.docs/operations/liveness-audit-2026-05-06.md— original 18-ADR audit, 16 ranked gaps.docs/operations/test-audit-report.md— this Phase 0 audit, 1572 tests classified.docs/operations/test-coverage-matrix.md— 172 rule-IDs.docs/operations/feature-liveness-matrix.md— 143 LIV-IDs.docs/operations/acceptance-criteria-trading.md— 10 Gherkin AC.~/.claude/plans/qa-lead-agent-system-analyst-agent-nifty-globe.md— approved Phase 0..4 plan.- Memory entries:
feedback_silent_feature_broken,feedback_spec_driven_tests,feedback_worktree_isolation.