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:
@@ -20,8 +20,10 @@ public sealed class MxAccessGatewayService(
|
||||
MxAccessGrpcMapper mapper,
|
||||
IEventStreamService eventStreamService,
|
||||
GatewayMetrics metrics,
|
||||
ILogger<MxAccessGatewayService> logger) : MxAccessGateway.MxAccessGatewayBase
|
||||
ILogger<MxAccessGatewayService> logger,
|
||||
IAlarmRpcDispatcher? alarmRpcDispatcher = null) : MxAccessGateway.MxAccessGatewayBase
|
||||
{
|
||||
private readonly IAlarmRpcDispatcher alarmRpcDispatcher = alarmRpcDispatcher ?? new NotWiredAlarmRpcDispatcher();
|
||||
/// <inheritdoc />
|
||||
public override async Task<OpenSessionReply> OpenSession(
|
||||
OpenSessionRequest request,
|
||||
@@ -167,7 +169,7 @@ public sealed class MxAccessGatewayService(
|
||||
/// UNIMPLEMENTED, so the .NET / Python / Go / Java / Rust SDK call sites land
|
||||
/// on a stable surface.
|
||||
/// </remarks>
|
||||
public override Task<AcknowledgeAlarmReply> AcknowledgeAlarm(
|
||||
public override async Task<AcknowledgeAlarmReply> AcknowledgeAlarm(
|
||||
AcknowledgeAlarmRequest request,
|
||||
ServerCallContext context)
|
||||
{
|
||||
@@ -187,13 +189,13 @@ public sealed class MxAccessGatewayService(
|
||||
// gRPC NotFound by the caller's MapException.
|
||||
_ = ResolveSession(request.SessionId);
|
||||
|
||||
return Task.FromResult(new AcknowledgeAlarmReply
|
||||
{
|
||||
SessionId = request.SessionId,
|
||||
CorrelationId = request.ClientCorrelationId,
|
||||
ProtocolStatus = MxAccessGrpcMapper.Ok("AcknowledgeAlarm accepted; worker dispatch pending PR A.2."),
|
||||
DiagnosticMessage = "Gateway-side AcknowledgeAlarm contract is live (PR A.3); worker-side MxAccess Acknowledge call ships in PR A.2.",
|
||||
});
|
||||
// PR A.6 — delegate to the alarm dispatcher. NotWiredAlarmRpcDispatcher
|
||||
// (default) returns OK + a worker-pending diagnostic. Production
|
||||
// WorkerAlarmRpcDispatcher (dev-rig follow-up) routes through the
|
||||
// worker IPC to AlarmClient.AlarmAckByGUID with full operator-identity
|
||||
// fidelity.
|
||||
return await alarmRpcDispatcher.AcknowledgeAsync(request, context.CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception exception) when (exception is not RpcException)
|
||||
{
|
||||
@@ -210,7 +212,7 @@ public sealed class MxAccessGatewayService(
|
||||
/// handler will translate the request into a WorkerCommand and stream the
|
||||
/// resulting snapshots.
|
||||
/// </remarks>
|
||||
public override Task QueryActiveAlarms(
|
||||
public override async Task QueryActiveAlarms(
|
||||
QueryActiveAlarmsRequest request,
|
||||
IServerStreamWriter<ActiveAlarmSnapshot> responseStream,
|
||||
ServerCallContext context)
|
||||
@@ -224,9 +226,18 @@ public sealed class MxAccessGatewayService(
|
||||
}
|
||||
_ = ResolveSession(request.SessionId);
|
||||
|
||||
// Empty stream — PR A.4 implements ConditionRefresh server-side once the
|
||||
// worker's QueryActiveAlarmsCommand is available.
|
||||
return Task.CompletedTask;
|
||||
// PR A.7 — delegate to the alarm dispatcher. NotWiredAlarmRpcDispatcher
|
||||
// (default) yields an empty stream. Production WorkerAlarmRpcDispatcher
|
||||
// (dev-rig follow-up) walks the worker's IMxAccessAlarmConsumer
|
||||
// SnapshotActiveAlarms output and translates each AlarmRecord into an
|
||||
// ActiveAlarmSnapshot.
|
||||
await foreach (ActiveAlarmSnapshot snapshot in alarmRpcDispatcher
|
||||
.QueryActiveAlarmsAsync(request, context.CancellationToken)
|
||||
.WithCancellation(context.CancellationToken)
|
||||
.ConfigureAwait(false))
|
||||
{
|
||||
await responseStream.WriteAsync(snapshot).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception exception) when (exception is not RpcException)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user