Resolve Contracts-016..017

Contracts-016 (Conventions): QueryActiveAlarmsRequest.session_id header
replaced with the unambiguous "Clients may leave session_id empty; the
gateway currently ignores it and serves the session-less central-monitor
cache. A future version may use it to scope the snapshot to one
session." Removes the ambiguity that the prior "reserved for future
use" wording introduced.

Contracts-017 (Documentation): The rpc QueryActiveAlarms comment now
includes the alarm_filter_prefix description: "QueryActiveAlarmsRequest.alarm_filter_prefix
optionally narrows the snapshot to alarms whose alarm_full_reference
starts with the given prefix; an empty prefix returns the full set."

Both are proto-comment-only changes — no wire-format impact, no field
renumbering, and the regenerated MxaccessGateway.cs / MxaccessGatewayGrpc.cs
carry only the doc-comment delta. Added the additive-only regression
guard QueryActiveAlarmsRequest_PinsFieldNumbersAndRoundTripsPrefixFilter
to ProtobufContractRoundTripTests — pins
session_id=1 / client_correlation_id=2 / alarm_filter_prefix=3 by
descriptor lookup and round-trips the message with and without the
filter populated.

Verification: dotnet build src/ZB.MOM.WW.MxGateway.slnx clean;
ProtobufContractRoundTripTests 40/40 passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-24 03:20:13 -04:00
parent 327e9c5f94
commit bd1d1f1c0e
5 changed files with 65 additions and 11 deletions
@@ -735,9 +735,10 @@ namespace ZB.MOM.WW.MxGateway.Contracts.Proto {
#region Messages
/// <summary>
/// Public request shape for QueryActiveAlarms. session_id is currently unused
/// (the snapshot is session-less) but reserved so a future per-session view
/// can be added without a wire break.
/// Public request shape for QueryActiveAlarms.
/// Clients may leave `session_id` empty; the gateway currently ignores it and
/// serves the session-less central-monitor cache. A future version may use it
/// to scope the snapshot to one session.
/// </summary>
[global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
public sealed partial class QueryActiveAlarmsRequest : pb::IMessage<QueryActiveAlarmsRequest>
@@ -196,6 +196,9 @@ namespace ZB.MOM.WW.MxGateway.Contracts.Proto {
/// reconnect to seed Part 9 client state, or to reconcile alarms that may
/// have been missed during a transport blip. Streamed so callers can
/// begin processing without buffering the full set.
/// `QueryActiveAlarmsRequest.alarm_filter_prefix` optionally narrows the
/// snapshot to alarms whose `alarm_full_reference` starts with the given
/// prefix; an empty prefix returns the full set.
/// </summary>
/// <param name="request">The request received from the client.</param>
/// <param name="responseStream">Used for sending responses back to the client.</param>
@@ -364,6 +367,9 @@ namespace ZB.MOM.WW.MxGateway.Contracts.Proto {
/// reconnect to seed Part 9 client state, or to reconcile alarms that may
/// have been missed during a transport blip. Streamed so callers can
/// begin processing without buffering the full set.
/// `QueryActiveAlarmsRequest.alarm_filter_prefix` optionally narrows the
/// snapshot to alarms whose `alarm_full_reference` starts with the given
/// prefix; an empty prefix returns the full set.
/// </summary>
/// <param name="request">The request to send to the server.</param>
/// <param name="headers">The initial metadata to send with the call. This parameter is optional.</param>
@@ -381,6 +387,9 @@ namespace ZB.MOM.WW.MxGateway.Contracts.Proto {
/// reconnect to seed Part 9 client state, or to reconcile alarms that may
/// have been missed during a transport blip. Streamed so callers can
/// begin processing without buffering the full set.
/// `QueryActiveAlarmsRequest.alarm_filter_prefix` optionally narrows the
/// snapshot to alarms whose `alarm_full_reference` starts with the given
/// prefix; an empty prefix returns the full set.
/// </summary>
/// <param name="request">The request to send to the server.</param>
/// <param name="options">The options for the call.</param>
@@ -31,12 +31,16 @@ service MxAccessGateway {
// reconnect to seed Part 9 client state, or to reconcile alarms that may
// have been missed during a transport blip. Streamed so callers can
// begin processing without buffering the full set.
// `QueryActiveAlarmsRequest.alarm_filter_prefix` optionally narrows the
// snapshot to alarms whose `alarm_full_reference` starts with the given
// prefix; an empty prefix returns the full set.
rpc QueryActiveAlarms(QueryActiveAlarmsRequest) returns (stream ActiveAlarmSnapshot);
}
// Public request shape for QueryActiveAlarms. session_id is currently unused
// (the snapshot is session-less) but reserved so a future per-session view
// can be added without a wire break.
// Public request shape for QueryActiveAlarms.
// Clients may leave `session_id` empty; the gateway currently ignores it and
// serves the session-less central-monitor cache. A future version may use it
// to scope the snapshot to one session.
message QueryActiveAlarmsRequest {
string session_id = 1;
string client_correlation_id = 2;
@@ -437,6 +437,46 @@ public sealed class ProtobufContractRoundTripTests
Assert.Equal(withFilter, StreamAlarmsRequest.Parser.ParseFrom(withFilter.ToByteArray()));
}
/// <summary>
/// Verifies that <c>QueryActiveAlarmsRequest</c> pins the additive-only field numbering
/// (<c>session_id = 1</c>, <c>client_correlation_id = 2</c>, <c>alarm_filter_prefix = 3</c>)
/// advertised in its proto comment, that the message round-trips with the optional
/// <c>alarm_filter_prefix</c> populated (the filter semantic the public RPC comment
/// documents), and that <c>QueryActiveAlarms</c> remains on the public service surface.
/// </summary>
[Fact]
public void QueryActiveAlarmsRequest_PinsFieldNumbersAndRoundTripsPrefixFilter()
{
var service = Assert.Single(
MxaccessGatewayReflection.Descriptor.Services,
descriptor => descriptor.Name == "MxAccessGateway");
Assert.Contains(service.Methods, method => method.Name == "QueryActiveAlarms");
var fields = QueryActiveAlarmsRequest.Descriptor.Fields;
Assert.Equal(1, fields[QueryActiveAlarmsRequest.SessionIdFieldNumber].FieldNumber);
Assert.Equal(2, fields[QueryActiveAlarmsRequest.ClientCorrelationIdFieldNumber].FieldNumber);
Assert.Equal(3, fields[QueryActiveAlarmsRequest.AlarmFilterPrefixFieldNumber].FieldNumber);
Assert.Equal("session_id", fields[QueryActiveAlarmsRequest.SessionIdFieldNumber].Name);
Assert.Equal("client_correlation_id", fields[QueryActiveAlarmsRequest.ClientCorrelationIdFieldNumber].Name);
Assert.Equal("alarm_filter_prefix", fields[QueryActiveAlarmsRequest.AlarmFilterPrefixFieldNumber].Name);
var withoutFilter = new QueryActiveAlarmsRequest
{
ClientCorrelationId = "client-correlation-10",
};
var withFilter = new QueryActiveAlarmsRequest
{
ClientCorrelationId = "client-correlation-11",
AlarmFilterPrefix = "Tank01.",
};
Assert.Equal(withoutFilter, QueryActiveAlarmsRequest.Parser.ParseFrom(withoutFilter.ToByteArray()));
var parsedWithFilter = QueryActiveAlarmsRequest.Parser.ParseFrom(withFilter.ToByteArray());
Assert.Equal(withFilter, parsedWithFilter);
Assert.Equal("Tank01.", parsedWithFilter.AlarmFilterPrefix);
}
/// <summary>Verifies that an MxValue carrying a raw_value bytes payload round-trips.</summary>
[Fact]
public void MxValue_RoundTripsRawValueBytesPayload()