Files
mxaccessgw/docs/audit/fragments/07-contracts.md
T

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.proto
  • src/ZB.MOM.WW.MxGateway.Contracts/Protos/mxaccess_worker.proto
  • src/ZB.MOM.WW.MxGateway.Contracts/Protos/galaxy_repository.proto
  • src/ZB.MOM.WW.MxGateway.Server/Grpc/**
  • clients/proto/proto-inputs.json
  • src/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:27OutputDir="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:9import "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: OperationCanceledExceptionCancelled; SessionManagerException → mapped by ErrorCode; WorkerClientException → mapped by ErrorCode. WorkerClientException: CommandTimeoutDeadlineExceeded, GatewayShutdownCancelled, InvalidStateFailedPrecondition, ProtocolViolationInternal, 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:12public const uint GatewayProtocolVersion = 3;. mxaccess_gateway.proto:71uint32 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:37rpc 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-847AcknowledgeAlarmRequest 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.