# 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` — `net10.0;net48`. 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(); builder.Services.AddSingleton(); builder.Services.AddSingleton();`" 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(); services.AddGrpc(options => options.Interceptors.Add());`" 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.