fix(server): poller correctness — stale-market alerts, overlapping ticks, live poll interval #2

Merged
pregno merged 3 commits from fix/poller-stale-and-overlap into main 2026-06-11 18:43:01 +02:00
Owner

Fixes three Important findings from the repo-wide review:

  1. Stale/expiring markets no longer fire market alerts or vote in trends. getScannerRows() returns every market with a snapshot; markets that drop out of discovery keep a frozen delta15m, which re-fired a market alert every cooldown window for up to retentionDays and biased symbol trends. runTick now filters both consumers to the active discovery set, and the expiry guard (previously trends-only) also gates market-alert input — settlement convergence is not a repricing signal. Scanner SSE output is unchanged (stale markets stay visible).

  2. Poll ticks can no longer overlap. Replaced both setIntervals with self-rescheduling setTimeout loops: the next run is scheduled only after the current one completes, eliminating duplicate snapshots and the read-then-write races in alert dedup/cooldown state when a slow upstream call outlasts the 8s interval. startPoller/stopPoller API unchanged; stop cancels pending timers.

  3. settings.pollIntervalMs now takes effect. It was editable via PUT /api/settings but the poller captured the env value at startup — the only settings field not read live. The tick loop now reads the interval from the settings store when scheduling each next tick.

Each fix has a regression test that reproduced the bug before the change (TDD red/green). Server suite 199 tests green; full repo 298 tests green; typecheck clean.

Fixes three Important findings from the repo-wide review: 1. **Stale/expiring markets no longer fire market alerts or vote in trends.** `getScannerRows()` returns every market with a snapshot; markets that drop out of discovery keep a frozen `delta15m`, which re-fired a market alert every cooldown window for up to retentionDays and biased symbol trends. `runTick` now filters both consumers to the active discovery set, and the expiry guard (previously trends-only) also gates market-alert input — settlement convergence is not a repricing signal. Scanner SSE output is unchanged (stale markets stay visible). 2. **Poll ticks can no longer overlap.** Replaced both `setInterval`s with self-rescheduling `setTimeout` loops: the next run is scheduled only after the current one completes, eliminating duplicate snapshots and the read-then-write races in alert dedup/cooldown state when a slow upstream call outlasts the 8s interval. `startPoller`/`stopPoller` API unchanged; stop cancels pending timers. 3. **`settings.pollIntervalMs` now takes effect.** It was editable via PUT /api/settings but the poller captured the env value at startup — the only settings field not read live. The tick loop now reads the interval from the settings store when scheduling each next tick. Each fix has a regression test that reproduced the bug before the change (TDD red/green). Server suite 199 tests green; full repo 298 tests green; typecheck clean.
Scanner rows for markets that dropped out of the discovery set carry a
frozen delta15m/score, so they re-fired a market alert every cooldown
window and kept voting in symbol trends until retention pruned them.
Filter both the MarketAlerter input and the trend inputs down to the
currently active market set, and apply the existing expiry guard to the
MarketAlerter input as well: settlement convergence is not a repricing
signal.
setInterval kept firing while a slow tick was still awaiting its
sequential network calls, so runs could overlap — duplicating snapshots
and racing the alert dedup state. Replace both intervals with
self-rescheduling setTimeout loops that schedule the next run only
after the current one finishes; stopPoller cancels the pending timeout
and a throwing run still cannot kill the loop.
pollIntervalMs is editable via PUT /api/settings but the poller captured
the env value at startup, so edits never took effect. PollerConfig now
takes a getter (matching how every other live setting is threaded) and
the self-rescheduling tick loop reads the delay for the next tick on
each iteration.
pregno force-pushed fix/poller-stale-and-overlap from 82bdd62569 to 236cd9005b 2026-06-11 18:36:12 +02:00 Compare
pregno merged commit b15e2f13a4 into main 2026-06-11 18:43:01 +02:00
pregno deleted branch fix/poller-stale-and-overlap 2026-06-11 18:43:01 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
pregno/polymarket-screener!2
No description provided.