scripts/run-client-e2e-tests.ps1 expects each language CLI to expose a
`batch` subcommand that reads command lines from stdin, runs each
through the normal subcommand dispatch, writes the JSON result, then
a sentinel line `__MXGW_BATCH_EOR__`. The implementation lived on a
divergent branch (commit 6126099) that was never merged into main —
this commit ports the same protocol to HEAD's renamed CLIs so the
existing matrix script runs end-to-end.
The protocol:
- one line of stdin = one full CLI invocation
- successful output → stdout, then __MXGW_BATCH_EOR__
- failure → {"error":"...","type":"error"} JSON on stdout, then
__MXGW_BATCH_EOR__ (errors do NOT exit the loop)
- empty line or EOF terminates the loop
Per-CLI additions:
.NET: RunBatchAsync + per-line StringWriter capture, JSON error
envelope when forceJsonErrors is true. Two new tests in
MxGatewayClientCliTests covering the success and error paths.
Go: runBatch with bufio.Scanner, runs each line through the
existing runWithIO switch with a buffered stdout writer. One new
test pinning the EOR sentinel.
Rust: new `Batch` variant on the clap Command enum, run_batch
re-parses each line via Cli::try_parse_from. Two new tests in the
inline mod tests block.
Python: new `batch` click command in commands.py that uses
CliRunner to dispatch each line; synthesises {"error",..."type"}
JSON from click error messages when the captured output isn't
already JSON-shaped. Three new tests in test_cli.py.
Java: BatchCommand inner @Command with BufferedReader stdin loop,
fresh commandLine() per dispatch with captured stdout/stderr
PrintWriters; non-zero exit codes and uncaught exceptions both
surface as JSON-error blocks. Two new tests.
Also fixes scripts/run-client-e2e-tests.ps1 line 705: the Python
invocation was still passing the old module name `mxgateway_cli` to
`python -m`; the client SDK rename in 397d3c5 moved it to
`zb_mom_ww_mxgateway_cli`. Without the fix the Python leg fails
with "No module named mxgateway_cli" before reaching open-session.
Verification: full matrix at the redeployed gateway (localhost:5120,
running ZB.MOM.WW.MxGateway.Server.exe / ZB.MOM.WW.MxGateway.Worker.exe)
with -SkipBulk -SkipReadWriteBulk -SkipParity -SkipAuth (those phases
exercise bulk read/write CLI subcommands that also live on the
divergent branch — porting those is a follow-up). All five clients
report `closed=true, addedItems=120, eventCount=5` and overall
`success=true`. Per-language unit tests pass:
- dotnet: 59/59
- go: all packages clean
- rust: cargo test --workspace clean
- python: 42/42
- java: gradle build SUCCESSFUL
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Client.Rust-021 (Design adherence): RustClientDesign.md "Crate layout"
section now describes the actual flat workspace structure instead of
the aspirational nested form. The replacement text states that the
workspace root is clients/rust/, the top-level crate
zb-mom-ww-mxgateway-client is declared in clients/rust/Cargo.toml
directly, and crates/mxgw-cli/ is the sole [workspace.members] entry.
The accompanying tree lists the real files on disk (Cargo.toml,
Cargo.lock, build.rs, README.md, RustClientDesign.md,
src/{lib,client,session,galaxy,options,auth,error,value,version,generated}.rs
plus the src/generated/ tonic-build output dir, tests/, and
crates/mxgw-cli/).
Doc-only change. cargo build --workspace + cargo test --workspace clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Client.Java-027 (Documentation): Updated 17 Gradle task references in
clients/java/README.md (lines 37, 108-110, 160-161, 169-176, 186, 206,
221) and 3 in clients/java/JavaClientDesign.md from the retired short
subproject names to the canonical zb-mom-ww-mxgateway-client /
zb-mom-ww-mxgateway-cli names. Copy-pasting any documented command now
matches the subproject names declared in settings.gradle.
Client.Java-028 (Design adherence): Build-layout block in
JavaClientDesign.md lines 23-27 updated to show the actual package
paths com/zb/mom/ww/mxgateway/{client,cli}/ instead of the retired
com/dohertylan/mxgateway/{client,cli}/ paths.
Client.Java-029 (Documentation): README.md line 210 corrected from
"zb-mom-ww-mxgateway-cli/build/install/mxgateway-cli" to
"zb-mom-ww-mxgateway-cli/build/install/zb-mom-ww-mxgateway-cli" — Gradle
installDist produces a directory whose name matches the project name,
not the short suffix. The e2e script already used the correct path.
Client.Java-030 (Testing coverage): Added
queryActiveAlarmsForwardsRequestAndStreamsSnapshots to
MxGatewayClientSessionTests. The test pushes a QueryActiveAlarmsRequest
carrying session_id / client_correlation_id / alarm_filter_prefix
through an InProcessGateway + TestGatewayService and asserts the server
observed all three request fields, two ActiveAlarmSnapshots stream in
order, and onError is never called. TDD red→green confirmed via a
deliberately-wrong session_id assertion. The re-triage note in
Client.Java-030's resolution clarifies that the finding's reference to
"the existing acknowledgeAlarm test" was aspirational — the alarm RPC
surface had zero coverage before this commit.
Client.Java-031 (Conventions): README.md prose lines 17, 22, 26 updated
to use the canonical zb-mom-ww-mxgateway-client / zb-mom-ww-mxgateway-cli
names so the layout description matches Gradle / IDE project names.
Verification: gradle build BUILD SUCCESSFUL; all Java unit tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rename across every client surface using each language's idiomatic convention:
* .NET clients/dotnet/MxGateway.Client[.Cli|.Tests]/
-> clients/dotnet/ZB.MOM.WW.MxGateway.Client[.Cli|.Tests]/
namespaces -> ZB.MOM.WW.MxGateway.Client[.Cli|.Tests]
contracts ProjectReference repointed to ZB.MOM.WW.MxGateway.Contracts
sln migrated to slnx (dotnet sln migrate)
* Python src/mxgateway -> src/zb_mom_ww_mxgateway
src/mxgateway_cli -> src/zb_mom_ww_mxgateway_cli
distribution: mxaccess-gateway-client -> zb-mom-ww-mxaccess-gateway-client
* Rust crate: mxgateway-client -> zb-mom-ww-mxgateway-client
build.rs proto path repointed
* Java subprojects: mxgateway-{client,cli} -> zb-mom-ww-mxgateway-{client,cli}
packages com.dohertylan.mxgateway -> com.zb.mom.ww.mxgateway
group com.dohertylan.mxgateway -> com.zb.mom.ww.mxgateway
rootProject mxaccessgw-java -> zb-mom-ww-mxaccessgw-java
* Go generate-proto.ps1 proto path repointed; module path and
package mxgateway kept (Go convention).
* proto-inputs.json: generatedOutputs.python updated to new package path.
* scripts/run-client-e2e-tests.ps1: Java CLI install path + gradle task
updated to zb-mom-ww-mxgateway-cli.
CLI binary names (mxgw, mxgw-py, mxgw-go, mxgateway-cli) and wire-level
identifiers (MXGATEWAY_* env vars, the mxgw_<id>_<secret> API key
prefix, protobuf package names like mxaccess_gateway.v1, all MXAccess
references) intentionally NOT renamed.
Fix pre-existing alarms-over-gateway breaks unblocked by the rename:
* mxaccess_gateway.proto: add missing public message QueryActiveAlarmsRequest
{session_id, client_correlation_id, alarm_filter_prefix} and missing
rpc QueryActiveAlarms(QueryActiveAlarmsRequest) returns
(stream ActiveAlarmSnapshot). All four typed clients referenced
these but they were absent from the proto.
* MxAccessGatewayService.QueryActiveAlarms: implement the new RPC on
the server, streaming from IGatewayAlarmService.CurrentAlarms with
optional alarm_filter_prefix filter.
* clients/dotnet/.../DiscoverHierarchyOptions.cs: add the hand-written
.NET POCO that wraps DiscoverHierarchyRequest (referenced by
GalaxyRepositoryClient.DiscoverHierarchyAsync but never authored).
* Drop retired session_id field references from
AcknowledgeAlarmRequest/AcknowledgeAlarmReply test fixtures across
.NET, Rust, Go, and Python clients.
* Rust integration test: add the missing stream_alarms impl on the
fake MxAccessGateway server (the trait gained the method, fake
didn't).
* Rust CLI test: bump expected gatewayProtocolVersion 2 -> 3.
Regenerated artifacts updated in this commit:
* src/ZB.MOM.WW.MxGateway.Contracts/Generated/{MxaccessGateway,MxaccessGatewayGrpc}.cs
* clients/python/src/zb_mom_ww_mxgateway/generated/*_pb2{,_grpc}.py
* clients/go/internal/generated/*.pb.go
(C# regenerated by Grpc.Tools on contracts build; Python and Go via
their generate-proto.ps1 scripts; Rust regenerates from .proto via
tonic-build at compile time so no checked-in artefact.)
Verification: 472 server tests, 275 worker tests (9 dev-rig skipped),
18 integration tests (live MxAccess + LDAP + Galaxy), 57 .NET client
tests, 32 Rust workspace tests, 39 Python tests, all Go packages, and
gradle build for Java all pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Apply the ZB.MOM.WW. prefix to all gateway-side projects, folders,
.csproj/.sln contents, C# namespaces, using directives, generated proto
C# (csharp_namespace + checked-in generated files), InternalsVisibleTo
attributes, project-name string literals (LoadProject, .sln lookups,
worker exe paths, staticwebassets manifest), and the install/script/doc
references that point at any of the above. Migrate the solution from
.sln to .slnx via `dotnet sln migrate` and delete the old file.
External-runtime identifiers are intentionally NOT prefixed so external
configuration keeps working:
- GatewayMetrics.cs MeterName ("MxGateway.Server")
- DashboardAuthenticationDefaults Scheme/Policy ("MxGateway.Dashboard")
- GatewayRequestLoggingMiddleware logger category ("MxGateway.Request")
- StaRuntime thread name ("MxGateway.Worker.STA")
- appsettings.json root section "MxGateway" + env-var prefix
MxGateway__... and secret-name MxGateway:ApiKeyPepper
- C:\ProgramData\MxGateway\ data dir paths
Also fixes two tests that were not rename-related but became visible
while validating the rename:
- WorkerLiveMxAccessSmokeTests.ShutDownAsync: cancellation that the
gateway service correctly maps to RpcException(Cancelled) per gRPC
convention was being misclassified as a stream fault. Added a sibling
catch on RpcException with StatusCode.Cancelled.
- IntegrationTestEnvironment.ResolveRepositoryRoot: extracted IsRepositoryRoot
and made it accept either a .git marker OR a .sln/.slnx next to src/
so the worker-exe walker works in non-git working copies.
clients/proto/proto-inputs.json's protoRoot updated to point at
src/ZB.MOM.WW.MxGateway.Contracts/Protos.
Verified by `dotnet build` and a full `dotnet test` of the .slnx with
MXGATEWAY_RUN_LIVE_{MXACCESS,LDAP,GALAXY}_TESTS=1:
Tests: 472/472 pass
Worker.Tests: 280/280 pass (4 dev-rig [Fact(Skip=...)] skipped)
IntegrationTests: 18/18 pass
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Eleventh PR of the alarms-over-gateway epic
(docs/plans/alarms-over-gateway.md). Mirrors PR E.2's .NET surface
on the Rust async SDK. Depends on PR E.1 (regen, merged).
- GatewayClient::acknowledge_alarm — async unary call. Uses the
existing unary_request helper (call timeout) and routes failures
through Error mapping; non-OK protocol status promotes to
Error::ProtocolStatus via ensure_protocol_success.
- GatewayClient::query_active_alarms — async server-streaming call
returning a new ActiveAlarmStream type alias (parallel to
EventStream). Errors are pre-mapped from tonic::Status; dropping
the stream cancels the call cooperatively.
- GATEWAY_PROTOCOL_VERSION bumped 2 → 3 to match the .NET contract.
- FakeGateway test impl extends to satisfy the new trait methods so
client_behavior.rs builds. Two new integration tests cover the
new SDK methods.
Tests:
- 12 unit + 10 client_behavior + 4 proto_fixtures = 26 tests, all
pass under cargo test (Rust 1.x via existing toolchain).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tenth PR of the alarms-over-gateway epic
(docs/plans/alarms-over-gateway.md). Mirrors PR E.2's .NET surface
on the Java SDK. Depends on PR E.1 (regen, merged).
- MxGatewayClient.acknowledgeAlarm — blocking unary call, validates
protocol status via the existing MxGatewayErrors helper. Wraps
RuntimeException through MxGatewayErrors.fromGrpc for typed
failure mapping.
- MxGatewayClient.acknowledgeAlarmAsync — CompletableFuture variant
using the future stub.
- MxGatewayClient.queryActiveAlarms — async server-streaming RPC
observed via a new MxGatewayActiveAlarmsSubscription handle
(parallel to MxGatewayEventSubscription; the existing
subscription class is hard-typed to MxEvent so a parallel type
was simpler than retrofitting generics).
- MxGatewayClientVersion bumps GATEWAY_PROTOCOL_VERSION 2 → 3 to
match the .NET contract; CLI version-string assertions updated
to match.
Java SDK build green via Gradle 9.4.1 (mxgateway-client + mxgateway-cli).
17 tasks, all tests passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ninth PR of the alarms-over-gateway epic
(docs/plans/alarms-over-gateway.md). Mirrors PR E.2's .NET surface
on the Go SDK. Depends on PR E.1 (regen, merged).
- Client.AcknowledgeAlarm — context-aware unary call routed through
the existing callContext helper (default 30s timeout). Failures
wrap into *GatewayError; protocol-status non-OK promotes to typed
protocol errors via EnsureProtocolSuccess.
- Client.QueryActiveAlarms — context-streaming wrapper around the
generated MxAccessGateway_QueryActiveAlarmsClient. Caller drives
the stream via Recv(); cancelling ctx releases it.
- types.go re-exports the four new generated types
(AcknowledgeAlarmRequest/Reply, QueryActiveAlarmsRequest,
ActiveAlarmSnapshot) plus the AlarmTransitionKind /
AlarmConditionState enums and the
QueryActiveAlarmsClient stream alias.
- version.go bumps GatewayProtocolVersion 1 → 3 to match the .NET
contract; the const was previously stale and the bump fixes the
pre-existing TestOpenSessionFixtureProtocolVersions failure that
was masked because the fixture had not been regenerated until A.1.
Tests:
- 4 new tests in alarms_test.go — request shape + auth metadata,
nil-request rejection, Unauthenticated mapping, snapshot
streaming over bufconn, filter-prefix passthrough.
- All Go test suites green: cmd/mxgw-go + mxgateway.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Seventh PR of the alarms-over-gateway epic
(docs/plans/alarms-over-gateway.md). Depends on PR A.1 (proto, merged)
and E.1 (regen, merged).
Hand-written .NET SDK methods on top of the regenerated proto types:
- MxGatewayClient.AcknowledgeAlarmAsync — routes through the existing
safe-unary retry pipeline (Acks are idempotent at MxAccess), maps
Unauthenticated/PermissionDenied RpcExceptions to typed
MxGatewayAuthenticationException / MxGatewayAuthorizationException
via GrpcMxGatewayClientTransport.MapRpcException.
- MxGatewayClient.QueryActiveAlarmsAsync — server-streaming
IAsyncEnumerable<ActiveAlarmSnapshot> mirroring the StreamEvents
pattern.
- IMxGatewayClientTransport extended; GrpcMxGatewayClientTransport
implements both methods using the regenerated grpc client.
- FakeGatewayTransport extended with capture lists, exception queue,
and reply / snapshot enqueue helpers.
CLI version-string assertions updated for the GatewayProtocolVersion
2 → 3 bump from A.1.
The CLI alarms verb (subscribe / acknowledge / query-active) is
deferred to a follow-up — keeping this PR focused on the SDK surface
that lmxopcua's GalaxyDriver consumes in PR B.2. The other-language
SDKs (E.3-E.6) layer the same shape on the regen.
Tests:
- 6 new MxGatewayClientAlarmsTests — request shape, cancellation
honor (linked-token via retry pipeline), Unauthenticated mapping,
streaming snapshot enumeration, filter prefix passthrough,
cancellation during enumeration.
- Full client test suite: 57 passed (was 51; 6 new).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pure mechanical regen following PR A.1 (alarm-transition event family
+ AcknowledgeAlarm / QueryActiveAlarms public RPCs). Ran:
- clients/python/generate-proto.ps1 → mxaccess_gateway_pb2.py +
mxaccess_gateway_pb2_grpc.py.
- clients/go/generate-proto.ps1 → mxaccess_gateway.pb.go +
mxaccess_gateway_grpc.pb.go + galaxy_repository.pb.go (whitespace
diff from upstream protoc minor version).
The .NET binding regenerates on csproj rebuild via Grpc.Tools — its
artifact (Generated/MxaccessGateway*.cs) was already updated as part
of A.1's commit. Java + Rust regen happens at build time via the
gradle plugin / build.rs respectively, with no committed output to
update.
Smoke-imported the regenerated Python descriptors:
OnAlarmTransitionEvent.DESCRIPTOR.fields → alarm_full_reference,
alarm_type_name, category, current_value, description, ...
AcknowledgeAlarmRequest.DESCRIPTOR.fields → alarm_full_reference,
client_correlation_id, comment, operator_user, session_id
ActiveAlarmSnapshot.DESCRIPTOR.fields → alarm_full_reference,
alarm_type_name, category, current_state, current_value, ...
PRs E.2 - E.6 layer hand-written SDK methods on top of the regenerated
types — those land per-language as separate PRs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First PR of the alarms-over-gateway epic
(docs/plans/alarms-over-gateway.md in lmxopcua). Pure contract-surface
change — no functional wiring yet. Worker-side subscription (A.2),
gateway-side dispatch + ack handler (A.3), and ConditionRefresh
(A.4) follow.
mxaccess_gateway.proto:
- Extend MxEventFamily with MX_EVENT_FAMILY_ON_ALARM_TRANSITION = 5.
- Extend MxEvent.body oneof with OnAlarmTransitionEvent on_alarm_transition = 24.
- Add OnAlarmTransitionEvent message carrying the full MxAccess alarm
payload (full reference, source object, alarm-type-name, transition
kind, raw severity, original raise timestamp, transition timestamp,
operator user/comment, category, description, current/limit value).
Mapping to OPC UA 0-1000 severity ladder happens server-side in
lmxopcua's MxAccessSeverityMapper (B.1) — gateway preserves the
native MxAccess scale.
- Add AlarmTransitionKind enum (Raise / Acknowledge / Clear / Retrigger).
- Add ActiveAlarmSnapshot + AlarmConditionState for the
ConditionRefresh stream.
- Add public RPCs AcknowledgeAlarm (unary) and QueryActiveAlarms
(server-streaming) on MxAccessGateway service.
- Add AcknowledgeAlarmRequest/Reply + QueryActiveAlarmsRequest.
GatewayContractInfo.GatewayProtocolVersion bumps 2 -> 3. Fixture
manifests (proto-inputs, behavior, parity, golden OpenSessionReply)
and protoset descriptor regenerated.
Tests: round-trip serialization for the new messages with
all-fields-populated and empty-optional-fields cases; oneof
last-write-wins guard between OnDataChange and OnAlarmTransition;
descriptor service-method enumeration includes the two new RPCs.
All 273 existing tests still pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Resolve 14 conflicts from popping local stash on top of origin's
eed1e88 + 8d3352f doc-comment additions (11 mechanical, plus
version.rs, DashboardAuthenticatorTests.cs, DashboardGalaxyProjector.cs)
- Fix 4 test files that used AGENTS.md as the repo-root sentinel
(now use CLAUDE.md, since AGENTS.md was removed in 4731ab5)
- Redirect 10 doc citations from AGENTS.md to the matching gateway.md
sections (Value Model, Status Model, Security, STA Worker Thread
Model, gRPC Layer rule, cancellation rule)
Verified: solution build clean, x86 worker build clean, 266/266
gateway tests passing, 121/121 worker tests passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Rename 16 kebab-case docs to PascalCase per StyleGuide
- Move per-language client design docs from docs/ to clients/<lang>/
alongside their READMEs
- Add ## Related Documentation sections to 15 docs that lacked one
- Fix sentence-case violations in H3 headings (StyleGuide rule)
- Update cross-references in gateway.md, client READMEs, scripts,
and generate-proto.ps1 helpers to follow the new paths
- Add CLAUDE.md with build/test commands, the source-update
verification matrix, the parity-first contract, and pointers
to MXAccess and Galaxy Repository analysis sources
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>