fix(alarms): dedup reconcile/live duplicate broadcasts on the alarm feed (NEXT-03)
A periodic reconcile can synthesize a repair transition whose matching live transition is still buffered in the alarm lease; both then broadcast as indistinguishable duplicates on StreamAlarms and the dashboard hub. Nothing serializes the two paths, and a correct serialization needs a worker-side high-water mark on QueryActiveAlarms (proto + worker change + a stall path), so this closes the common case with a local best-effort dedup instead: both paths already carry the same worker-derived identity — the worker stamps record.TransitionTimestampUtc into both OnAlarmTransitionEvent's transition_timestamp and ActiveAlarmSnapshot.last_transition_timestamp — so ApplyTransition suppresses a live transition whose (timestamp, resulting state) the cache already carries from a repair. The Clear leg has no cache entry left to compare, so ApplyReconcile tombstones each synthesized Clear by the instance's original_raise_timestamp for one reconcile generation; a matching live Clear consumes the tombstone, while a new raise/clear cycle carries a newer raise timestamp and passes. Suppression fires only on a positive marker match — unset timestamps keep today's behavior — so the documented at-least-once consumer contract stands (gateway.md, Sessions.md updated in the same change). Tests: two new regressions drive the exact race through the GWC-26 harness (repair-then-buffered-live for Raise and for Clear, each with a genuine follow-up transition proving no over-suppression); GatewayAlarmMonitor suites 18/18.
This commit is contained in:
+9
-3
@@ -173,9 +173,15 @@ the worker's current state while the corresponding live transition may still be
|
||||
buffered in the monitor's lease, so both can broadcast and the two are
|
||||
indistinguishable on the feed. This applies to the acked-state delta and equally
|
||||
to the older Raise/Clear presence repair: nothing serializes a reconcile pass
|
||||
against the in-flight live stream. Alarm-feed consumers (`StreamAlarms` clients
|
||||
and the dashboard alarm hub) must apply transitions idempotently — treat one as
|
||||
"set this alarm to this state", never as an increment or a toggle.
|
||||
against the in-flight live stream. The monitor narrows that window with a
|
||||
best-effort dedup (NEXT-03): a buffered live transition whose worker timestamp
|
||||
and resulting state the cache already carries from a repair — or whose Clear
|
||||
matches a one-reconcile-generation tombstone keyed on the instance's original
|
||||
raise timestamp — is suppressed instead of re-broadcast. The dedup fires only on
|
||||
a positive marker match (unset timestamps never suppress), so the contract stays
|
||||
at-least-once: alarm-feed consumers (`StreamAlarms` clients and the dashboard
|
||||
alarm hub) must apply transitions idempotently — treat one as "set this alarm to
|
||||
this state", never as an increment or a toggle.
|
||||
|
||||
### Alarm providers and failover
|
||||
|
||||
|
||||
Reference in New Issue
Block a user