Problem
The first concrete AMS knob to put under the #8172 loop. The opportunity ranker records a full per-issue scoring breakdown (miner_ranked_candidates: rank_score + the five signals) and the discovery pass records skips (skippedBelowMinRank in lib/portfolio-discovery.ts), but the skip threshold itself has never been validated against what actually happened to comparable taken opportunities. The ranker's score is an equal-weight clamped product (packages/loopover-engine/src/opportunity-ranker.ts — no scalar weights), so the threshold is the tunable, not weights.
Requirements
⚠️ Required pattern. Same discipline as ORB's #8138/#8139: report-only advisory backtest, Pareto floor via compareBacktestScores (any regressed axis ⇒ REGRESSED), held-out split via splitBacktestCorpus, rendered with the shared comparison renderer. No knob movement in this issue.
- Backtest core (engine-pure): given the corpus from the corpus-adapter issue and a candidate threshold, replay which taken opportunities would have been skipped and score the counterfactual against realized outcomes (merged = good take; closed/disengaged = bad take).
- Miner CLI entry (
loopover-miner calibration backtest-threshold --candidate <x>): reads local ledgers, prints the rendered comparison, exits nonzero only on operational error — never on verdict.
- Persist each run as a typed event in
miner_event_ledger (the AMS analog of calibration.threshold_backtest_run) so the track-record issue can aggregate.
Deliverables
Links & Resources
#8172 (epic), #8138/#8139 (ORB analogs), packages/loopover-engine/src/opportunity-ranker.ts, packages/loopover-miner/lib/ranked-candidates.ts, lib/portfolio-discovery.ts
Boundaries
Report-only: no threshold change ships here. Local ledgers only.
maintainer-only — epic decomposition of #8172.
Problem
The first concrete AMS knob to put under the #8172 loop. The opportunity ranker records a full per-issue scoring breakdown (
miner_ranked_candidates: rank_score + the five signals) and the discovery pass records skips (skippedBelowMinRankin lib/portfolio-discovery.ts), but the skip threshold itself has never been validated against what actually happened to comparable taken opportunities. The ranker's score is an equal-weight clamped product (packages/loopover-engine/src/opportunity-ranker.ts — no scalar weights), so the threshold is the tunable, not weights.Requirements
loopover-miner calibration backtest-threshold --candidate <x>): reads local ledgers, prints the rendered comparison, exits nonzero only on operational error — never on verdict.miner_event_ledger(the AMS analog ofcalibration.threshold_backtest_run) so the track-record issue can aggregate.Deliverables
Links & Resources
#8172 (epic), #8138/#8139 (ORB analogs), packages/loopover-engine/src/opportunity-ranker.ts, packages/loopover-miner/lib/ranked-candidates.ts, lib/portfolio-discovery.ts
Boundaries
Report-only: no threshold change ships here. Local ledgers only.
maintainer-only — epic decomposition of #8172.