clients/dotnet: SDK methods for AcknowledgeAlarm + QueryActiveAlarms (PR E.2)

Seventh PR of the alarms-over-gateway epic
(docs/plans/alarms-over-gateway.md). Depends on PR A.1 (proto, merged)
and E.1 (regen, merged).

Hand-written .NET SDK methods on top of the regenerated proto types:

- MxGatewayClient.AcknowledgeAlarmAsync — routes through the existing
  safe-unary retry pipeline (Acks are idempotent at MxAccess), maps
  Unauthenticated/PermissionDenied RpcExceptions to typed
  MxGatewayAuthenticationException / MxGatewayAuthorizationException
  via GrpcMxGatewayClientTransport.MapRpcException.
- MxGatewayClient.QueryActiveAlarmsAsync — server-streaming
  IAsyncEnumerable<ActiveAlarmSnapshot> mirroring the StreamEvents
  pattern.
- IMxGatewayClientTransport extended; GrpcMxGatewayClientTransport
  implements both methods using the regenerated grpc client.
- FakeGatewayTransport extended with capture lists, exception queue,
  and reply / snapshot enqueue helpers.

CLI version-string assertions updated for the GatewayProtocolVersion
2 → 3 bump from A.1.

The CLI alarms verb (subscribe / acknowledge / query-active) is
deferred to a follow-up — keeping this PR focused on the SDK surface
that lmxopcua's GalaxyDriver consumes in PR B.2. The other-language
SDKs (E.3-E.6) layer the same shape on the regen.

Tests:
- 6 new MxGatewayClientAlarmsTests — request shape, cancellation
  honor (linked-token via retry pipeline), Unauthenticated mapping,
  streaming snapshot enumeration, filter prefix passthrough,
  cancellation during enumeration.
- Full client test suite: 57 passed (was 51; 6 new).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-04-30 16:37:12 -04:00
parent 26d0e2c471
commit 0765eb4de3
6 changed files with 387 additions and 2 deletions
@@ -182,6 +182,48 @@ public sealed class MxGatewayClient : IAsyncDisposable
return _transport.StreamEventsAsync(request, CreateStreamCallOptions(cancellationToken));
}
/// <summary>
/// Acknowledges an active MXAccess alarm condition through the gateway. The
/// gateway authenticates the request against the API key's <c>invoke:alarm-ack</c>
/// scope and forwards the acknowledge to the worker's MXAccess session;
/// the resulting <see cref="MxStatusProxy"/> is returned in the reply.
/// </summary>
/// <param name="request">The acknowledge request — alarm reference, comment, operator user.</param>
/// <param name="cancellationToken">Cancellation token for the operation.</param>
/// <returns>The acknowledge reply with protocol + native MxStatus.</returns>
public Task<AcknowledgeAlarmReply> AcknowledgeAlarmAsync(
AcknowledgeAlarmRequest request,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(request);
ThrowIfDisposed();
return ExecuteSafeUnaryAsync(
token => _transport.AcknowledgeAlarmAsync(request, CreateCallOptions(token)),
cancellationToken);
}
/// <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.
/// </summary>
/// <param name="request">The query 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,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(request);
ThrowIfDisposed();
return _transport.QueryActiveAlarmsAsync(request, CreateStreamCallOptions(cancellationToken));
}
/// <summary>
/// Disposes the client and releases all resources.
/// </summary>