Files
mxaccessgw/docs/Contracts.md
T
Joseph Doherty a0203503a7 Code-review 2026-05-20 sweep: re-review at 1cd51bb, resolve 72 findings across all 11 modules
Re-reviewed every module/client against the 10-category checklist
(REVIEW-PROCESS.md) at commit 1cd51bb, filed 72 new findings, and
fixed them in three priority waves (3 High, 17 Medium, 52 Low).

Highs
- Server-017: enumerate AcknowledgeAlarm / QueryActiveAlarms in
  GatewayGrpcScopeResolver so non-admin keys can use them; document
  the mapping in docs/Authorization.md; add interceptor tests.
- Client.Java-013: add the five missing bulk-method stubs to the
  CLI FakeSession so the test module compiles on a clean tree.
- Client.Rust-013: fix the clippy::doc_lazy_continuation regression
  in generated tonic code by reformatting the ReadBulkCommand proto
  comment and scoping a #![allow(...)] to the generated submodules.

Mediums (highlights)
- Server: unify GatewaySession state-lock discipline (-015) and
  make DisposeAsync race-safe against in-flight CloseAsync (-016);
  add constraint-enforcement test coverage for the bulk-plan path
  (-021).
- Worker: introduce StaRuntimeShutdownException so RunAlarmPollLoop
  can distinguish graceful shutdown from a real STA-affinity
  violation (-016); have the watchdog skip StaHung while
  CurrentCommandCorrelationId is non-empty so a legitimate slow
  ReadBulk no longer self-faults (-017).
- Tests: add per-method round-trip + cancellation coverage for the
  11 GatewaySession bulk methods (-013); replace the real TCP probe
  in GalaxyHierarchyCacheTests with an IGalaxyRepository fake
  (-016).
- IntegrationTests: drive the StreamEvents writer in the live Write
  test and assert OnWriteComplete (-012); add live tests for
  Unadvise/RemoveItem/Unregister ordering, WriteSecured, and
  abnormal worker exit (-014).
- Worker.Tests: replace MxAccessSession reflection with an internal
  CreateForTesting factory (-016); cover WorkerCancel and
  unexpected-body envelope branches (-017).
- Client.Java: cancel MxEventStream when close() races
  beforeStart() (-014); return a CancellingCompletableFuture that
  actually forwards cancellation through .thenApply chains (-015).
- Client.Python: drop the silent localhost-plaintext downgrade in
  the CLI; require explicit --plaintext (-013).
- Client.Rust: stop bench-read-bulk from polluting success-latency
  histograms with failed-call durations (-015); add coverage for
  the five MalformedReply paths, the bulk-write helpers, the
  Error::Unavailable mapping, and the unary-fault path (-016).
- Contracts: extend docs/Contracts.md with the bulk read/write
  command family (-009).

Lows (highlights)
- Server: cap GalaxyGlobMatcher.RegexCache; align
  WorkerAlarmRpcDispatcher missing-session handling; drop the
  duplicate dashboard @page routes; refresh IAlarmRpcDispatcher
  XML doc.
- Worker: surface SetXmlAlarmQuery COM failures; remove dead
  subscriptionExpression / ExecutingCommand arms; preserve
  factory-supplied runtime sessions; split MxAlarmSnapshot.cs into
  three files.
- Tests: dispose the WebApplication in seven test classes; rebuild
  FakeWorkerProcess.WaitForExitAsync against a real TaskCompletion
  source; switch the heartbeat-expires test to ManualTimeProvider;
  add InvariantCulture to the remaining DateTimeOffset.Parse sites;
  document GalaxyFilterInputSafetyTests in GatewayTesting.md.
- IntegrationTests: comment fixes, RecordingServerStreamWriter
  IDisposable, class-level [Trait], single-source ZB default
  connection string.
- Worker.Tests: replace silent-return gating with LiveMxAccessFact
  so absent env vars SKIP not pass; PascalCase rename of probe
  [Fact]s; deterministic deadline test; new frame-protocol error
  tests; ComputeTransitions diff-coverage; relocate dev-rig probes
  to Probes/.
- Contracts: add round-trip coverage and per-field redaction /
  Galaxy-identifier comments to the protos.
- Client.Dotnet: introduce clients/dotnet/Directory.Build.props so
  TreatWarningsAsErrors / analysers apply; document
  DiscoverHierarchyOptions and IMxGatewayCliClient; require typed
  bulk-read handles in CLI; surface AcknowledgeAlarm transport
  faults through Translate().
- Client.Go: kill dead code in alarms_test / fakeGalaxyServer /
  runWriteBulkVariant; document the six new subcommands in
  writeUsage; drain galaxy-watch events on limit; switch io.EOF
  comparisons to errors.Is.
- Client.Java: shared shutdown helpers + new shutdownTimeout
  option; regex-based credential redaction; Long.toUnsignedString
  for uint64 sequence; doc fixes.
- Client.Python: combine duplicate imports; add coverage for
  _percentile / bench-read-bulk / MAX_AGGREGATE_EVENTS /
  _api_key_from_env; populate pyproject metadata and ship py.typed.
- Client.Rust: expose next_correlation_id() so CLI ping/close
  stop hard-coding correlation IDs; resync RustClientDesign.md
  with the current Session / Error surface and CLI subcommand set.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 09:46:47 -04:00

5.6 KiB

Protobuf Contracts

The contracts project contains the public gRPC API and the gateway-to-worker IPC messages. The .proto files are the source of truth; generated C# files are recreated by the contracts project build.

Files

src/MxGateway.Contracts/Protos/mxaccess_gateway.proto defines the public MxAccessGateway gRPC service, command payloads, command replies, event DTOs, MxValue, MxArray, and MxStatusProxy.

The public command model includes bulk subscription command kinds for AddItemBulk, AdviseItemBulk, RemoveItemBulk, UnAdviseItemBulk, SubscribeBulk, and UnsubscribeBulk. These commands are normal unary Invoke payloads. They do not add separate gRPC methods, and they return a BulkSubscribeReply containing per-item SubscribeResult records with ServerHandle, TagAddress, ItemHandle, WasSuccessful, and ErrorMessage.

The gateway forwards each bulk command as one worker command. The worker runs the corresponding MXAccess AddItem, Advise, UnAdvise, and RemoveItem calls sequentially on the session STA and preserves input order in the result list.

The command model also includes bulk write/read command kinds: WriteBulk, Write2Bulk, WriteSecuredBulk, WriteSecured2Bulk, and ReadBulk. They are unary Invoke payloads on the same MxAccessGateway surface (not separate gRPC methods) and exist so a caller can submit one list of items per round trip while preserving MXAccess parity per entry.

  • WriteBulkCommand / Write2BulkCommand / WriteSecuredBulkCommand / WriteSecured2BulkCommand each carry a server_handle and a repeated list of entries (WriteBulkEntry, Write2BulkEntry, WriteSecuredBulkEntry, WriteSecured2BulkEntry). 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, which carries repeated BulkWriteResult. A BulkWriteResult has server_handle, item_handle, was_successful, optional int32 hresult, repeated MxStatusProxy statuses, and error_message. Per-entry failures populate error_message + hresult and never raise — callers iterate and inspect each entry. The credential-sensitive redaction rules for WriteSecured / WriteSecured2 apply to every value inside WriteSecuredBulkEntry and WriteSecured2BulkEntry.

  • ReadBulkCommand carries server_handle, repeated string tag_addresses, and uint32 timeout_ms (0 means use the gateway-configured default). 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. MXAccess has no synchronous Read, so ReadBulk is dual-mode per entry: when a tag is already advised in the session the worker returns the cached OnDataChange payload without touching the subscription (was_cached = true); otherwise the worker takes a full AddItem + Advise + wait-for-first-OnDataChange + UnAdvise + RemoveItem snapshot lifecycle and returns the result (was_cached = false). The asymmetry that BulkReadResult has no hresult field is intentional — ReadBulk outcomes are timeout / cache / lifecycle states rather than MXAccess COM return codes.

See gateway.md for the full cached-vs-snapshot ReadBulk lifecycle and the per-command scope requirements, and docs/DesignDecisions.md "Bulk Command Family" for the rationale behind the per-entry result shape (independent success tracking, input-order preservation, no partial-failure exceptions).

src/MxGateway.Contracts/Protos/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.

src/MxGateway.Contracts/Protos/galaxy_repository.proto defines the GalaxyRepository service used by clients to browse the Galaxy Repository (deployed object hierarchy and dynamic attributes). The service is metadata- only and does not share types with mxaccess_gateway.proto. See Galaxy Repository Browse for the RPC catalog and behavior.

Generated C# output is written to src/MxGateway.Contracts/Generated/. Do not hand-edit generated files.

Client generation inputs are published through clients/proto/proto-inputs.json and the descriptor set under clients/proto/descriptors/. See Client Proto Generation for language-specific generation inputs, output directories, and golden protobuf JSON fixtures.

Generation

Run the contracts build to regenerate C# protobuf and gRPC code:

dotnet build src/MxGateway.Contracts/MxGateway.Contracts.csproj

Run the focused contract tests after changing either .proto file:

dotnet test src/MxGateway.Tests/MxGateway.Tests.csproj --filter ProtobufContractRoundTripTests

The full solution build also regenerates the C# contracts before compiling gateway and test projects:

dotnet build src/MxGateway.sln

Regenerate the client descriptor after changing either .proto file:

powershell -ExecutionPolicy Bypass -File scripts/publish-client-proto-inputs.ps1