gateway: alarm-RPC dispatcher seam (PRs A.6 + A.7)

Replaces the inline diagnostic strings in PR A.3's AcknowledgeAlarm
+ QueryActiveAlarms handlers with an IAlarmRpcDispatcher seam.

- IAlarmRpcDispatcher (new) — gateway-side abstraction over the
  worker-RPC path that fronts AlarmClient.AlarmAckByGUID and the
  active-alarm walk. AcknowledgeAsync returns the
  AcknowledgeAlarmReply directly; QueryActiveAlarmsAsync yields an
  IAsyncEnumerable<ActiveAlarmSnapshot>.
- NotWiredAlarmRpcDispatcher (new, default impl) — returns
  PROTOCOL_STATUS_OK with a structured worker-pending diagnostic
  on Acknowledge, yields an empty stream on QueryActiveAlarms.
  Same observable shape as PR A.3, but the integration seam is
  now in code instead of hardcoded inside the handler.
- MxAccessGatewayService — handlers delegate to the dispatcher.
  Constructor accepts an optional IAlarmRpcDispatcher (default
  NotWiredAlarmRpcDispatcher); a future WorkerAlarmRpcDispatcher
  registration in DI swaps in the live worker-IPC routing without
  changing the public RPC surface.
- 2 new dispatcher tests pin the not-wired contract; 279 → 281
  total tests, all green.

Worker-side dispatch (translating Acknowledge / QueryActiveAlarms
to the IPC method that calls IMxAccessAlarmConsumer from PR A.5)
is the dev-rig follow-up — it depends on validating the AVEVA
GetAlarmChangesCompleted event subscription against a live alarm
provider before pinning a wire format.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-04-30 22:47:42 -04:00
parent c7d5b83390
commit 6b3c117d1e
4 changed files with 171 additions and 13 deletions
@@ -0,0 +1,41 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MxGateway.Contracts.Proto;
namespace MxGateway.Server.Sessions;
/// <summary>
/// PR A.6 / A.7 — gateway-side dispatcher for the alarm-RPC surface.
/// Bridges the public <c>AcknowledgeAlarm</c> + <c>QueryActiveAlarms</c>
/// gRPC handlers to the worker process that hosts
/// <c>IMxAccessAlarmConsumer</c>.
/// </summary>
/// <remarks>
/// <para>
/// Production implementations live in <c>WorkerAlarmRpcDispatcher</c>
/// (this PR ships a not-yet-wired default that returns a clear
/// worker-pending diagnostic) and route through the existing
/// worker-pipe IPC. Tests inject a fake to exercise the gateway
/// handler shape without spinning up a worker process.
/// </para>
/// <para>
/// The dispatcher is session-scoped: every call resolves the
/// session and forwards to that session's worker. The handler
/// constructs the <see cref="AcknowledgeAlarmReply"/> /
/// <see cref="ActiveAlarmSnapshot"/> stream from the dispatcher's
/// output without further translation.
/// </para>
/// </remarks>
public interface IAlarmRpcDispatcher
{
/// <summary>Forward an Acknowledge to the worker that owns the session.</summary>
Task<AcknowledgeAlarmReply> AcknowledgeAsync(
AcknowledgeAlarmRequest request,
CancellationToken cancellationToken);
/// <summary>Walk active alarms on the worker that owns the session.</summary>
IAsyncEnumerable<ActiveAlarmSnapshot> QueryActiveAlarmsAsync(
QueryActiveAlarmsRequest request,
CancellationToken cancellationToken);
}