19 KiB
Cluster 07 — Contracts/gRPC
Audit of docs/Contracts.md, docs/Grpc.md, and docs/ClientProtoGeneration.md
verified against:
src/ZB.MOM.WW.MxGateway.Contracts/Protos/mxaccess_gateway.protosrc/ZB.MOM.WW.MxGateway.Contracts/Protos/mxaccess_worker.protosrc/ZB.MOM.WW.MxGateway.Contracts/Protos/galaxy_repository.protosrc/ZB.MOM.WW.MxGateway.Server/Grpc/**clients/proto/proto-inputs.jsonsrc/ZB.MOM.WW.MxGateway.Contracts/ZB.MOM.WW.MxGateway.Contracts.csproj
DOC / LINES / CLAIM / CLAIM_TYPE / VERDICT / EVIDENCE / CODE_AREA / SEVERITY / PROPOSED_FIX
DOC: docs/Grpc.md
LINES: 13, 32
CLAIM: "MxAccessGatewayService implements the six MxAccessGateway RPCs — OpenSession, CloseSession, Invoke, StreamEvents, AcknowledgeAlarm, and StreamAlarms."
CLAIM_TYPE: rpc/proto
VERDICT: wrong
EVIDENCE: mxaccess_gateway.proto:17-38 defines seven RPCs — the six listed plus QueryActiveAlarms(QueryActiveAlarmsRequest) returns (stream ActiveAlarmSnapshot). MxAccessGatewayService.cs:233 implements QueryActiveAlarms. The table at line 13 also says "six" and the table and prose at line 32 both omit QueryActiveAlarms.
CODE_AREA: proto.QueryActiveAlarms
SEVERITY: high
PROPOSED_FIX: Change "six" to "seven" in the table and prose. Add QueryActiveAlarms to the RPC list at line 32. Add a ### QueryActiveAlarms handler section describing the server-streaming, session-less snapshot behavior (iterates alarmService.CurrentAlarms, respects alarm_filter_prefix, completes without emitting transitions).
DOC: docs/Grpc.md
LINES: 148
CLAIM: "The mapper exposes static factory methods for every ProtocolStatusCode (Ok, InvalidRequest, SessionNotFound, SessionNotReady, WorkerUnavailable, Timeout, Canceled, ProtocolViolation)."
CLAIM_TYPE: rpc/proto
VERDICT: wrong
EVIDENCE: mxaccess_gateway.proto:1025 defines PROTOCOL_STATUS_CODE_MXACCESS_FAILURE = 9. MxAccessGrpcMapper.cs:76-174 lists eight factory methods — none for MxAccessFailure. The claim "every ProtocolStatusCode" is false because MxAccessFailure has no corresponding factory method.
CODE_AREA: proto.ProtocolStatusCode
SEVERITY: medium
PROPOSED_FIX: Either add "except MxAccessFailure, which is produced only by the worker" to the sentence, or add the missing factory method and update the list. Do not silently elide the gap.
DOC: docs/ClientProtoGeneration.md
LINES: 80, 145
CLAIM: "Python generated-code output directory is clients/python/src/mxgateway/generated."
CLAIM_TYPE: path
VERDICT: wrong
EVIDENCE: clients/proto/proto-inputs.json:28 declares "python": "clients/python/src/zb_mom_ww_mxgateway/generated". The actual directory on disk is clients/python/src/zb_mom_ww_mxgateway/generated/ (confirmed by ls). The doc path clients/python/src/mxgateway/generated does not exist.
CODE_AREA: proto.gen
SEVERITY: high
PROPOSED_FIX: Replace both occurrences of clients/python/src/mxgateway/generated with clients/python/src/zb_mom_ww_mxgateway/generated to match proto-inputs.json and the actual filesystem.
DOC: docs/Grpc.md
LINES: 227
CLAIM: "Under the default policy only the stream is dropped and the session continues to accept commands."
CLAIM_TYPE: behavior-rule
VERDICT: wrong
EVIDENCE: appsettings.json:53 sets "BackpressurePolicy": "FailFast". EventOptions.cs:13 confirms EventBackpressurePolicy.FailFast as the default. EventBackpressurePolicy.cs names the two values FailFast and DisconnectSubscriber. The non-FailFast (stream-drop-only) behaviour belongs to DisconnectSubscriber, not "the default policy". Under the actual default (FailFast) the session is faulted.
CODE_AREA: proto.gen
SEVERITY: medium
PROPOSED_FIX: Rewrite as: "Under DisconnectSubscriber only the stream is dropped … Under FailFast (the default configured in appsettings.json) the session is faulted …"
DOC: docs/Contracts.md
LINES: 94, 107
CLAIM: "Full solution build: dotnet build src/ZB.MOM.WW.MxGateway.slnx"
CLAIM_TYPE: command
VERDICT: accurate
EVIDENCE: src/ZB.MOM.WW.MxGateway.slnx exists on disk.
CODE_AREA: proto.gen
SEVERITY: low
PROPOSED_FIX: None.
DOC: docs/Contracts.md
LINES: 94
CLAIM: "Run the contracts build to regenerate C# protobuf and gRPC code: dotnet build src/ZB.MOM.WW.MxGateway.Contracts/ZB.MOM.WW.MxGateway.Contracts.csproj"
CLAIM_TYPE: command
VERDICT: accurate
EVIDENCE: ZB.MOM.WW.MxGateway.Contracts.csproj:27-29 includes all three .proto files with GrpcServices="Both" or "None" and OutputDir="Generated". Building the project triggers protoc via Grpc.Tools.
CODE_AREA: proto.gen
SEVERITY: low
PROPOSED_FIX: None.
DOC: docs/Contracts.md
LINES: 4-5
CLAIM: "The contracts project multi-targets net10.0;net48 and owns the .proto files."
CLAIM_TYPE: config-key
VERDICT: accurate
EVIDENCE: ZB.MOM.WW.MxGateway.Contracts.csproj:4 — <TargetFrameworks>net10.0;net48</TargetFrameworks>.
CODE_AREA: proto.gen
SEVERITY: low
PROPOSED_FIX: None.
DOC: docs/Contracts.md
LINES: 80-81
CLAIM: "Generated C# output is written to src/ZB.MOM.WW.MxGateway.Contracts/Generated/."
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: ZB.MOM.WW.MxGateway.Contracts.csproj:27 — OutputDir="Generated". Directory src/ZB.MOM.WW.MxGateway.Contracts/Generated/ contains five generated .cs files confirmed by ls.
CODE_AREA: proto.gen
SEVERITY: low
PROPOSED_FIX: None.
DOC: docs/Contracts.md
LINES: 9-19
CLAIM: "The public command model includes bulk subscription command kinds for AddItemBulk, AdviseItemBulk, RemoveItemBulk, UnAdviseItemBulk, SubscribeBulk, and UnsubscribeBulk. They return a BulkSubscribeReply containing per-item SubscribeResult records with ServerHandle, TagAddress, ItemHandle, WasSuccessful, and ErrorMessage."
CLAIM_TYPE: rpc/proto
VERDICT: accurate
EVIDENCE: mxaccess_gateway.proto:117-122 defines all six payloads. proto:562-568 defines SubscribeResult with fields server_handle, tag_address, item_handle, was_successful, error_message. proto:570-572 defines BulkSubscribeReply.
CODE_AREA: proto.SubscribeResult
SEVERITY: low
PROPOSED_FIX: None.
DOC: docs/Contracts.md
LINES: 32-45
CLAIM: "WriteBulkCommand / Write2BulkCommand / WriteSecuredBulkCommand / WriteSecured2BulkCommand each carry server_handle and a repeated list of entries. Each entry mirrors the single-item command shape — item_handle + value (+ timestamp_value on the *2 variants, + current_user_id / verifier_user_id on the secured variants). All four replies use BulkWriteReply with repeated BulkWriteResult. A BulkWriteResult has server_handle, item_handle, was_successful, optional int32 hresult, repeated MxStatusProxy statuses, and error_message."
CLAIM_TYPE: rpc/proto
VERDICT: accurate
EVIDENCE: mxaccess_gateway.proto:384-441 defines all four commands with matching fields. proto:581-588 defines BulkWriteResult with exactly those six fields. proto:590-592 defines BulkWriteReply.
CODE_AREA: proto.BulkWriteResult
SEVERITY: low
PROPOSED_FIX: None.
DOC: docs/Contracts.md
LINES: 46-61
CLAIM: "ReadBulkCommand carries server_handle, repeated string tag_addresses, and uint32 timeout_ms. The reply is BulkReadReply carrying repeated BulkReadResult. A BulkReadResult has server_handle, tag_address, item_handle, was_successful, was_cached, value, quality, source_timestamp, repeated MxStatusProxy statuses, and error_message. BulkReadResult has no hresult field."
CLAIM_TYPE: rpc/proto
VERDICT: accurate
EVIDENCE: mxaccess_gateway.proto:456-460 defines ReadBulkCommand with those three fields. proto:612-623 defines BulkReadResult with exactly those ten fields, no hresult. proto:625-627 defines BulkReadReply.
CODE_AREA: proto.BulkReadResult
SEVERITY: low
PROPOSED_FIX: None.
DOC: docs/Contracts.md
LINES: 68-71
CLAIM: "mxaccess_worker.proto defines the named-pipe worker IPC envelope and control messages. It imports mxaccess_gateway.proto so the worker and gateway use the same command, reply, event, value, and status shapes."
CLAIM_TYPE: rpc/proto
VERDICT: accurate
EVIDENCE: mxaccess_worker.proto:9 — import "mxaccess_gateway.proto";. The WorkerCommand, WorkerCommandReply, WorkerEvent messages wrap mxaccess_gateway.v1 types directly.
CODE_AREA: proto.WorkerEnvelope
SEVERITY: low
PROPOSED_FIX: None.
DOC: docs/Contracts.md
LINES: 73-78
CLAIM: "galaxy_repository.proto defines the GalaxyRepository service. The service is metadata-only and does not share types with mxaccess_gateway.proto."
CLAIM_TYPE: rpc/proto
VERDICT: accurate
EVIDENCE: galaxy_repository.proto:7-8 imports only google/protobuf/timestamp.proto and google/protobuf/wrappers.proto — no import of mxaccess_gateway.proto. The comment at galaxy_repository.proto:130 states the type enumeration is distinct from MxDataType.
CODE_AREA: proto.GalaxyRepository
SEVERITY: low
PROPOSED_FIX: None.
DOC: docs/Grpc.md
LINES: 9-16
CLAIM: "Four collaborators: MxAccessGatewayService (scoped/gRPC), MxAccessGrpcRequestValidator (singleton), MxAccessGrpcMapper (singleton), IEventStreamService/EventStreamService (singleton)."
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: GatewayApplication.cs:88-90 registers mapper, validator, and event stream service as singletons. MxAccessGatewayService is not explicitly registered (gRPC services resolved per-request by ASP.NET Core are transient/scoped — "scoped (gRPC)" is accurate per ASP.NET Core DI conventions). GatewayApplication.cs:195 maps it as a gRPC endpoint.
CODE_AREA: proto.gen
SEVERITY: low
PROPOSED_FIX: None.
DOC: docs/Grpc.md
LINES: 20-26
CLAIM: "Registration: builder.Services.AddSingleton<MxAccessGrpcMapper>(); builder.Services.AddSingleton<MxAccessGrpcRequestValidator>(); builder.Services.AddSingleton<IEventStreamService, EventStreamService>();"
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: GatewayApplication.cs:88-90 matches exactly.
CODE_AREA: proto.gen
SEVERITY: low
PROPOSED_FIX: None.
DOC: docs/Grpc.md
LINES: 237-243
CLAIM: "Authorization interceptor registration: services.AddSingleton<GatewayGrpcAuthorizationInterceptor>(); services.AddGrpc(options => options.Interceptors.Add<GatewayGrpcAuthorizationInterceptor>());"
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: GrpcAuthorizationServiceCollectionExtensions.cs:21,31 contains both lines verbatim.
CODE_AREA: proto.gen
SEVERITY: low
PROPOSED_FIX: None.
DOC: docs/Grpc.md
LINES: 100-108
CLAIM: "Validation table — OpenSession: command_timeout when set must be > 0; CloseSession: session_id non-empty; StreamEvents: session_id non-empty; Invoke: session_id non-empty, command present, kind not Unspecified, payload oneof matches kind; AcknowledgeAlarm: alarm_full_reference non-empty, validated inline not by MxAccessGrpcRequestValidator; StreamAlarms: no required fields."
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: MxAccessGrpcRequestValidator.cs:10-53 confirms all four validator methods. MxAccessGatewayService.cs:181-183 confirms the inline alarm reference check. StreamAlarms handler at line 204 has no field validation.
CODE_AREA: proto.gen
SEVERITY: low
PROPOSED_FIX: None.
DOC: docs/Grpc.md
LINES: 141-147
CLAIM: "When the worker reply or event payload is missing, the mapper returns a synthetic public message with ProtocolStatusCode.ProtocolViolation (for replies) or a sentinel MxEvent with MxEventFamily.Unspecified (for events)."
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: MxAccessGrpcMapper.cs:46-54 returns ProtocolViolation(...) when reply.Reply is null. MxAccessGrpcMapper.cs:65-69 returns sentinel MxEvent { Family = MxEventFamily.Unspecified } when event is null.
CODE_AREA: proto.gen
SEVERITY: low
PROPOSED_FIX: None.
DOC: docs/Grpc.md
LINES: 159-174
CLAIM: "Exception mapping: OperationCanceledException → Cancelled; SessionManagerException → mapped by ErrorCode; WorkerClientException → mapped by ErrorCode. WorkerClientException: CommandTimeout → DeadlineExceeded, GatewayShutdown → Cancelled, InvalidState → FailedPrecondition, ProtocolViolation → Internal, others → Unavailable."
CLAIM_TYPE: behavior-rule
VERDICT: accurate
EVIDENCE: MxAccessGatewayService.cs:902-950 matches exactly. WorkerClientErrorCode.cs:5-12 confirms the four enum values.
CODE_AREA: proto.gen
SEVERITY: low
PROPOSED_FIX: None.
DOC: docs/Grpc.md
LINES: 184-196
CLAIM: "The channel is bounded by Events:QueueCapacity and configured for a single reader and writer with FullMode = BoundedChannelFullMode.Wait and AllowSynchronousContinuations = false."
CLAIM_TYPE: config-key
VERDICT: accurate
EVIDENCE: EventStreamService.cs:44-51 matches the code snippet in the doc verbatim.
CODE_AREA: proto.gen
SEVERITY: low
PROPOSED_FIX: None.
DOC: docs/ClientProtoGeneration.md
LINES: 39-45
CLAIM: "GatewayContractInfo.GatewayProtocolVersion is the public gateway protocol version. OpenSessionReply.gateway_protocol_version returns the same value."
CLAIM_TYPE: rpc/proto
VERDICT: accurate
EVIDENCE: GatewayContractInfo.cs:12 — public const uint GatewayProtocolVersion = 3;. mxaccess_gateway.proto:71 — uint32 gateway_protocol_version = 8;. MxAccessGatewayService.cs:49 copies GatewayContractInfo.GatewayProtocolVersion into the reply field.
CODE_AREA: proto.OpenSessionReply
SEVERITY: low
PROPOSED_FIX: None.
DOC: docs/ClientProtoGeneration.md
LINES: 55-61
CLAIM: "The script writes clients/proto/descriptors/mxaccessgw-client-v1.protoset."
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: clients/proto/descriptors/mxaccessgw-client-v1.protoset exists on disk. proto-inputs.json:21 references the same path.
CODE_AREA: proto.gen
SEVERITY: low
PROPOSED_FIX: None.
DOC: docs/ClientProtoGeneration.md
LINES: 74-81
CLAIM: "Generated-code directories table: .NET → clients/dotnet/generated, Go → clients/go/internal/generated, Rust → clients/rust/src/generated, Python → clients/python/src/mxgateway/generated, Java → clients/java/src/main/generated."
CLAIM_TYPE: path
VERDICT: wrong
EVIDENCE: clients/proto/proto-inputs.json:26-30 lists "python": "clients/python/src/zb_mom_ww_mxgateway/generated". The actual directory is clients/python/src/zb_mom_ww_mxgateway/generated/ (confirmed by filesystem). The table row for Python says clients/python/src/mxgateway/generated which does not exist. All other rows match proto-inputs.json and the filesystem.
CODE_AREA: proto.gen
SEVERITY: high
PROPOSED_FIX: Change the Python row from clients/python/src/mxgateway/generated to clients/python/src/zb_mom_ww_mxgateway/generated in the table and also fix line 145 which contains the same wrong path.
DOC: docs/ClientProtoGeneration.md
LINES: 89-101 (generation commands table)
CLAIM: ".NET generation: dotnet build src/ZB.MOM.WW.MxGateway.Contracts/ZB.MOM.WW.MxGateway.Contracts.csproj; Go: Push-Location clients/go; ./generate-proto.ps1; Pop-Location; Rust: Push-Location clients/rust; cargo check --workspace; Pop-Location; Python: Push-Location clients/python; ./generate-proto.ps1; Pop-Location; Java: Push-Location clients/java; gradle :mxgateway-client:generateProto; Pop-Location."
CLAIM_TYPE: command
VERDICT: accurate
EVIDENCE: Scripts clients/go/generate-proto.ps1 and clients/python/generate-proto.ps1 exist. generate-proto.ps1 for Go uses $modulePath = 'gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go/internal/generated' matching the stated package. Contracts csproj exists. All scripts confirmed present.
CODE_AREA: proto.gen
SEVERITY: low
PROPOSED_FIX: None.
DOC: docs/ClientProtoGeneration.md
LINES: 119-125
CLAIM: "The Go scaffold maps both proto files into the internal Go package gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go/internal/generated."
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: clients/go/generate-proto.ps1:7 — $modulePath = 'gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go/internal/generated'.
CODE_AREA: proto.gen
SEVERITY: low
PROPOSED_FIX: None.
DOC: docs/ClientProtoGeneration.md
LINES: 170-176
CLAIM: "Golden fixtures: open-session-reply.ok.json, register-command-request.json, on-data-change-event.json."
CLAIM_TYPE: path
VERDICT: accurate
EVIDENCE: All three files exist at clients/proto/fixtures/golden/.
CODE_AREA: proto.gen
SEVERITY: low
PROPOSED_FIX: None.
DOC: docs/Contracts.md / docs/ClientProtoGeneration.md
LINES: (gap — not documented)
CLAIM: gap — QueryActiveAlarms RPC in mxaccess_gateway.proto service definition (line 37), QueryActiveAlarmsRequest message (line 44), and ActiveAlarmSnapshot message (line 783) are not mentioned in Contracts.md or ClientProtoGeneration.md.
CLAIM_TYPE: rpc/proto
VERDICT: gap
EVIDENCE: mxaccess_gateway.proto:37 — rpc QueryActiveAlarms(QueryActiveAlarmsRequest) returns (stream ActiveAlarmSnapshot);. Contracts.md describes every other public RPC but never mentions QueryActiveAlarms.
CODE_AREA: proto.QueryActiveAlarms
SEVERITY: medium
PROPOSED_FIX: Add a paragraph to Contracts.md describing QueryActiveAlarms — session-less, server-streaming, returns point-in-time snapshot of active alarms from the gateway's always-on alarm monitor cache, optionally filtered by alarm_filter_prefix. Cross-reference the StreamAlarms section.
DOC: docs/Contracts.md / docs/ClientProtoGeneration.md
LINES: (gap — not documented)
CLAIM: gap — AlarmFeedMessage oneof message and the StreamAlarms protocol (snapshot → snapshot_complete → transitions) are described in Grpc.md but not in Contracts.md which should be the shape-level reference.
CLAIM_TYPE: rpc/proto
VERDICT: gap
EVIDENCE: mxaccess_gateway.proto:860-870 defines AlarmFeedMessage { oneof payload { ActiveAlarmSnapshot active_alarm = 1; bool snapshot_complete = 2; OnAlarmTransitionEvent transition = 3; } }. Contracts.md does not describe this message or its stream protocol.
CODE_AREA: proto.AlarmFeedMessage
SEVERITY: low
PROPOSED_FIX: Add a brief entry in Contracts.md describing AlarmFeedMessage and the three-phase stream sequence for StreamAlarms.
DOC: docs/Contracts.md / docs/Grpc.md
LINES: (gap — not documented)
CLAIM: gap — AcknowledgeAlarmRequest has a reserved field 1 (session_id) and the acknowledgement is session-less. AcknowledgeAlarmReply also has a reserved field 1 and an intentionally-unset status field (field 5). This wire-compatibility detail is not captured in Contracts.md.
CLAIM_TYPE: rpc/proto
VERDICT: gap
EVIDENCE: mxaccess_gateway.proto:812-847 — AcknowledgeAlarmRequest has reserved 1; reserved "session_id";. AcknowledgeAlarmReply likewise has reserved 1; reserved "session_id"; and inline comment that status (field 5) is intentionally unset.
CODE_AREA: proto.AcknowledgeAlarm
SEVERITY: low
PROPOSED_FIX: Add a note in Contracts.md about the reserved session_id fields and the intentionally-empty status field so integrators using older generated code do not misinterpret wire defaults.