Summary (adversarial review of #618, finding F3 — MEDIUM)
internal/bench/bench.go builds the aggregator from all rule windows (aggregate.New(ruleEng.Windows(), 0)) with no LongWindowCutoff split, so MaxWindow() is 7 days and the flush added by #618 (agg.Flush(ctx, clock.Add(-agg.MaxWindow()))) evicts nothing for any scenario shorter than a week: bench output is byte-identical to dev. #618's claim "benchmark replays call Flush on the flush cadence, 0 FP unchanged" therefore proved nothing, and docs/internal/INVARIANTS.md still says the bench never flushes (which is effectively true). The legit corpus's longest scenario is 2.5 minutes, so no hourly-tier false positive can ever surface there.
Fix
- Apply the daemon's split in the bench: windows ≤ 1 h in memory (flushed on the 10-min cadence at
MaxWindow), long windows served from a real :memory: store's hourly counters exactly as the daemon does (IncrEventCount on the virtual clock, evaluateLongRules-equivalent), so the bench runs the pipeline shape production runs.
- Add hour-scale legit scenarios: admin login/logout ×5 in 45 min, Jetpack xmlrpc 9/h, office NAT with a missing asset (60 404/h). All three currently produce a ban (see the strike-ladder issue) — the bench must fail until that is fixed, which is the point.
- Update INVARIANTS.md §"What the tests cover".
Refs: #605, #610, #618, #216.
Summary (adversarial review of #618, finding F3 — MEDIUM)
internal/bench/bench.gobuilds the aggregator from all rule windows (aggregate.New(ruleEng.Windows(), 0)) with noLongWindowCutoffsplit, soMaxWindow()is 7 days and the flush added by #618 (agg.Flush(ctx, clock.Add(-agg.MaxWindow()))) evicts nothing for any scenario shorter than a week: bench output is byte-identical todev. #618's claim "benchmark replays call Flush on the flush cadence, 0 FP unchanged" therefore proved nothing, anddocs/internal/INVARIANTS.mdstill says the bench never flushes (which is effectively true). The legit corpus's longest scenario is 2.5 minutes, so no hourly-tier false positive can ever surface there.Fix
MaxWindow), long windows served from a real:memory:store's hourly counters exactly as the daemon does (IncrEventCounton the virtual clock,evaluateLongRules-equivalent), so the bench runs the pipeline shape production runs.Refs: #605, #610, #618, #216.