Point the .NET client at the StreamAlarms alarm feed

Replace the client's QueryActiveAlarmsAsync with StreamAlarmsAsync —
a session-less subscription to the gateway's central alarm feed that
yields the active-alarm snapshot followed by live transitions.
AcknowledgeAlarm is session-less (AcknowledgeAlarmRequest no longer
carries a session id). Updates the transport interface, the gRPC
transport, the test fake, and the alarm tests; the .NET client
solution builds and its alarm tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-21 16:30:50 -04:00
parent 40ca4b6908
commit ac12c150c3
5 changed files with 58 additions and 63 deletions
@@ -134,8 +134,8 @@ internal sealed class GrpcMxGatewayClientTransport(
}
/// <inheritdoc />
public async IAsyncEnumerable<ActiveAlarmSnapshot> QueryActiveAlarmsAsync(
QueryActiveAlarmsRequest request,
public async IAsyncEnumerable<AlarmFeedMessage> StreamAlarmsAsync(
StreamAlarmsRequest request,
CallOptions callOptions,
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default)
{
@@ -143,12 +143,12 @@ internal sealed class GrpcMxGatewayClientTransport(
? cancellationToken
: callOptions.CancellationToken;
using AsyncServerStreamingCall<ActiveAlarmSnapshot> call = RawClient.QueryActiveAlarms(request, callOptions);
using AsyncServerStreamingCall<AlarmFeedMessage> call = RawClient.StreamAlarms(request, callOptions);
IAsyncStreamReader<ActiveAlarmSnapshot> responseStream = call.ResponseStream;
IAsyncStreamReader<AlarmFeedMessage> responseStream = call.ResponseStream;
while (true)
{
ActiveAlarmSnapshot? snapshot;
AlarmFeedMessage? message;
try
{
if (!await responseStream.MoveNext(effectiveCancellationToken).ConfigureAwait(false))
@@ -156,22 +156,22 @@ internal sealed class GrpcMxGatewayClientTransport(
break;
}
snapshot = responseStream.Current;
message = responseStream.Current;
}
catch (RpcException exception)
{
throw RpcExceptionMapper.Map(exception, effectiveCancellationToken);
}
yield return snapshot;
yield return message;
}
}
/// <inheritdoc />
IAsyncEnumerable<ActiveAlarmSnapshot> IMxGatewayClientTransport.QueryActiveAlarmsAsync(
QueryActiveAlarmsRequest request,
IAsyncEnumerable<AlarmFeedMessage> IMxGatewayClientTransport.StreamAlarmsAsync(
StreamAlarmsRequest request,
CallOptions callOptions)
{
return QueryActiveAlarmsAsync(request, callOptions);
return StreamAlarmsAsync(request, callOptions);
}
}
@@ -66,13 +66,13 @@ internal interface IMxGatewayClientTransport
CallOptions callOptions);
/// <summary>
/// Streams a snapshot of all alarms currently in Active or ActiveAcked state — the
/// ConditionRefresh equivalent for the gateway.
/// Attaches to the gateway's central alarm feed — the current active-alarm
/// snapshot followed by live transitions.
/// </summary>
/// <param name="request">The query request, optionally scoped by alarm-reference prefix.</param>
/// <param name="request">The stream request, optionally scoped by alarm-reference prefix.</param>
/// <param name="callOptions">gRPC call options.</param>
/// <returns>An async enumerable of active-alarm snapshots.</returns>
IAsyncEnumerable<ActiveAlarmSnapshot> QueryActiveAlarmsAsync(
QueryActiveAlarmsRequest request,
/// <returns>An async enumerable of alarm feed messages.</returns>
IAsyncEnumerable<AlarmFeedMessage> StreamAlarmsAsync(
StreamAlarmsRequest request,
CallOptions callOptions);
}
@@ -205,24 +205,25 @@ public sealed class MxGatewayClient : IAsyncDisposable
}
/// <summary>
/// Streams a snapshot of all alarms currently Active or ActiveAcked — the gateway's
/// ConditionRefresh equivalent. Used after reconnect to seed the local Part 9 state
/// machine, or to reconcile alarms that may have been missed during a transport
/// blip. Optionally scoped by alarm-reference prefix
/// (<see cref="QueryActiveAlarmsRequest.AlarmFilterPrefix"/>) so a partial refresh
/// can target an equipment sub-tree.
/// Attaches to the gateway's central alarm feed. The stream opens with one
/// <see cref="AlarmFeedMessage"/> per currently-active alarm (the
/// ConditionRefresh snapshot), then a single <c>snapshot_complete</c>, then a
/// <c>transition</c> for every subsequent raise / acknowledge / clear. Served
/// by the gateway's always-on alarm monitor — no worker session is opened, so
/// any number of clients may attach. Optionally scoped by alarm-reference
/// prefix (<see cref="StreamAlarmsRequest.AlarmFilterPrefix"/>).
/// </summary>
/// <param name="request">The query request, optionally scoped by alarm-reference prefix.</param>
/// <param name="request">The stream request, optionally scoped by alarm-reference prefix.</param>
/// <param name="cancellationToken">Cancellation token for the stream.</param>
/// <returns>An async enumerable of active-alarm snapshots.</returns>
public IAsyncEnumerable<ActiveAlarmSnapshot> QueryActiveAlarmsAsync(
QueryActiveAlarmsRequest request,
/// <returns>An async enumerable of alarm feed messages.</returns>
public IAsyncEnumerable<AlarmFeedMessage> StreamAlarmsAsync(
StreamAlarmsRequest request,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(request);
ThrowIfDisposed();
return _transport.QueryActiveAlarmsAsync(request, CreateStreamCallOptions(cancellationToken));
return _transport.StreamAlarmsAsync(request, CreateStreamCallOptions(cancellationToken));
}
/// <summary>