fix(GWC-26): attach the alarm monitor's lease before SubscribeAlarms

RunMonitorAsync issued SubscribeAlarms and the first reconcile before the
internal distributor subscriber was attached (via ISessionManager
.ReadAlarmEventsAsync). The pump has been running since MarkReady started the
dashboard mirror and only fans to subscribers registered at fan-out time, so
every transition raised in that two-round-trip window bypassed the alarm feed —
and a missed Acknowledge was never repaired, because ApplyReconcile broadcast
presence deltas only.

- The monitor now takes the internal lease directly from its session BEFORE
  SubscribeAlarms and drains it after the first reconcile; window transitions
  buffer in the lease's bounded channel. Processing them after ApplyReconcile is
  order-safe (ApplyTransition handles alarms the snapshot already placed).
- ISessionManager.ReadAlarmEventsAsync removed — zero remaining callers.
- ApplyReconcile broadcasts an Acknowledge feed transition when a both-present
  alarm's state advanced to ActiveAcked. This is a feed-level repair on the
  AlarmFeedMessage/StreamAlarms surface rebuilt from the worker's own snapshot,
  not MxEvent emission, so the "never synthesize events" rule is untouched;
  the reasoning is recorded on ApplyReconcile.

The alarm-monitor test fakes now hand the monitor a real Ready GatewaySession
with a dashboard mirror, which is what makes the window reproducible.

Docs: docs/Sessions.md and gateway.md alarm-monitor ordering notes.

Refs: archreview/2026-07-12/remediation/10-gateway-core.md GWC-26
This commit is contained in:
Joseph Doherty
2026-08-07 05:40:33 -04:00
parent 1a63fdd7db
commit 3b6a239ed6
16 changed files with 674 additions and 127 deletions
+14
View File
@@ -154,6 +154,20 @@ session. The worker event channel is single-reader and asserts it (a second
`WorkerClient.ReadEventsAsync` consumer throws), so a regression cannot silently
split the event stream between two drains.
The monitor takes that internal lease **before** it sends `SubscribeAlarms`, and
drains it after the first reconcile. The pump is already running by then (the
dashboard mirror starts it at `MarkReady`) and the distributor fans only to
subscribers registered at fan-out time, so attaching after the subscribe +
reconcile round trips would drop every transition raised in that window —
including an `Acknowledge`, which the presence-only reconcile deltas would never
repair. Transitions arriving during the window buffer in the lease's bounded
channel instead. As defense in depth for any window this ordering cannot cover
(worker restart, internal-subscriber overflow disconnect), a reconcile that finds
a known alarm now reported `ActiveAcked` broadcasts an `Acknowledge` transition on
the alarm feed. That is a feed-level repair rebuilt from the worker's own
snapshot on the `StreamAlarms` surface — it is not an `MxEvent` and never reaches
`StreamEvents`, so the "never synthesize events" rule is untouched.
### Alarm providers and failover
The alarm feed has two providers, both implemented worker-side: