Polling Manager Invariants
These invariants are runtime contracts. Breaking them changes system behavior and should be treated as a breaking internal change.
Invariant Map

1. One key maps to one task/timer
Multiple subscribers for the same key must share one internal task and one interval schedule.
2. Ref-counted lifecycle is authoritative
- First subscription initializes the task
- Last unsubscribe tears task down completely
- No timer may survive a zero-subscriber task
3. Immediate first poll is mandatory
subscribe(...) must execute a poll immediately for the first subscriber of a key and then continue on interval ticks.
Later same-key subscribers attach without triggering additional immediate polls.
4. No in-flight overlap for a key
If an interval tick fires while the previous poll is still pending, that tick is skipped.
5. Subscriber snapshot before callback iteration
Callback iteration must use a snapshot array to avoid mutation hazards:
const subscribers = [...task.subscribers.values()];
This protects iteration when callbacks unsubscribe during execution.
6. Callback updates are non-structural
subscription.update({ onResult, onError }) may replace callback references only.
It must not:
- create new tasks
- restart timers
- alter ref counts
- affect dedup semantics
7. Visibility behavior is opt-in by dependency
Visibility pause/resume works only when documentRef is injected.
No implicit global document fallback is allowed.
While hidden, visibility pause has precedence over global resume().
8. Errors do not auto-stop by default
pollFn errors are propagated to onError callbacks; task lifecycle continues unless terminal conditions are met through shouldStop on successful results.
9. Public API remains minimal
No public ID-based unsubscription path. Consumers own only the returned unsubscribe() function.
10. Same-key task definition is immutable
For an existing key, pollFn, intervalMs, shouldStop, and pauseWhenHidden must remain consistent.
Mismatched subscriptions fail fast and do not attach.