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>
Go Client
The Go client module contains the generated MXAccess Gateway protobuf bindings,
a small handwritten mxgateway package, and the mxgw-go test CLI scaffold.
The module uses the shared proto inputs documented in
../../docs/ClientProtoGeneration.md so gateway and client contracts stay in
sync.
Layout
clients/go/
go.mod
generate-proto.ps1
internal/generated/
mxgateway/
cmd/mxgw-go/
internal/generated contains code produced by protoc, protoc-gen-go, and
protoc-gen-go-grpc. Do not edit generated files by hand.
Regenerating Protobuf Bindings
Run generation after the shared .proto files or the Go output path changes:
./generate-proto.ps1
The script uses the tool paths recorded in ../../docs/ToolchainLinks.md.
Build And Test
Run the Go module checks from clients/go:
go test ./...
go build ./...
go vet ./...
The tests parse the shared JSON fixtures, exercise value and status conversion,
use bufconn for fake gateway auth and streaming behavior, and cover CLI JSON
redaction.
Packaging
Build a local CLI executable from clients/go:
New-Item -ItemType Directory -Force ../../artifacts/clients/go | Out-Null
go build -o ../../artifacts/clients/go/mxgw-go.exe ./cmd/mxgw-go
Install the CLI into the active GOBIN or GOPATH/bin:
go install ./cmd/mxgw-go
Other Go modules can consume the library package with the module path
gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go/mxgateway.
Client API
Use mxgateway.Dial with mxgateway.Options to configure plaintext or TLS
transport, API-key metadata, dial timeout, and per-call timeout:
client, err := mxgateway.Dial(ctx, mxgateway.Options{
Endpoint: "localhost:5000",
APIKey: os.Getenv("MXGATEWAY_API_KEY"),
Plaintext: true,
})
Client.OpenSession returns a Session with helpers for Register,
AddItem, AddItem2, Advise, Write, Events, and Close. Prefer
SubscribeEvents or SubscribeEventsAfter for long-running streams because the
returned subscription owns cancellation and exposes Close for deterministic
goroutine cleanup. Raw protobuf messages remain available through the
mxgateway package aliases and the Raw helper methods. Typed errors support
errors.As for GatewayError, CommandError, and MxAccessError; command
errors preserve the raw reply.
Galaxy Repository browse
The GalaxyRepository service (proto package galaxy_repository.v1) is a
read-only metadata-only browse over the AVEVA System Platform Galaxy
Repository. It uses the same API-key authentication as the MXAccess Gateway
and requires the metadata:read scope. Use mxgateway.DialGalaxy to obtain a
*GalaxyClient that mirrors the connection-management conventions of
Client:
galaxy, err := mxgateway.DialGalaxy(ctx, mxgateway.Options{
Endpoint: "localhost:5000",
APIKey: os.Getenv("MXGATEWAY_API_KEY"),
Plaintext: true,
})
if err != nil {
return err
}
defer galaxy.Close()
ok, err := galaxy.TestConnection(ctx)
deployTime, present, err := galaxy.GetLastDeployTime(ctx)
objects, err := galaxy.DiscoverHierarchy(ctx)
GetLastDeployTime returns (time.Time{}, false, nil) when the server
reports present=false (no deploy recorded). DiscoverHierarchy returns
the generated *GalaxyObject slice with each object's dynamic attributes
populated for direct contract access.
Watching deploy events
WatchDeployEvents opens a server-streaming subscription. The server emits a
bootstrap event with the current Galaxy state immediately on subscribe, then
one DeployEvent per new deploy. Sequence is monotonic per server start;
gaps signal dropped events. Pass a non-nil lastSeenDeployTime to suppress the
bootstrap event when resuming from a known checkpoint:
streamCtx, cancel := context.WithCancel(ctx)
defer cancel()
events, errs, err := galaxy.WatchDeployEvents(streamCtx, nil)
if err != nil {
return err
}
for {
select {
case ev, ok := <-events:
if !ok {
return nil // stream completed (server EOF or ctx cancelled)
}
log.Printf("seq=%d objects=%d attrs=%d",
ev.GetSequence(), ev.GetObjectCount(), ev.GetAttributeCount())
case streamErr := <-errs:
if streamErr != nil {
return streamErr // *GatewayError
}
case <-ctx.Done():
return ctx.Err()
}
}
Cancel the supplied context to tear down the stream cleanly. Both channels
close after EOF, cancellation, or a terminal error; surfaced errors are wrapped
in *GatewayError.
The CLI exposes the same RPC via galaxy-watch:
go run ./cmd/mxgw-go galaxy-watch -plaintext
go run ./cmd/mxgw-go galaxy-watch -plaintext -json
go run ./cmd/mxgw-go galaxy-watch -plaintext -last-seen-deploy-time 2026-04-28T10:00:00Z
go run ./cmd/mxgw-go galaxy-watch -plaintext -limit 5
The command runs until Ctrl+C (or the optional -limit is reached) and prints
one line per event in text mode or one JSON object per event with -json.
CLI
The mxgw-go CLI emits JSON with redacted API keys for commands that connect to
the gateway:
go run ./cmd/mxgw-go version -json
go run ./cmd/mxgw-go open-session -endpoint localhost:5000 -plaintext -json
go run ./cmd/mxgw-go register -session-id <id> -client-name mxgw-go -plaintext -json
go run ./cmd/mxgw-go add-item -session-id <id> -server-handle 1 -item Area001.Tag.Value -plaintext -json
go run ./cmd/mxgw-go advise -session-id <id> -server-handle 1 -item-handle 1 -plaintext -json
go run ./cmd/mxgw-go write -session-id <id> -server-handle 1 -item-handle 1 -type int32 -value 123 -plaintext -json
go run ./cmd/mxgw-go stream-events -session-id <id> -plaintext -json
go run ./cmd/mxgw-go smoke -item Area001.Tag.Value -plaintext -json
go run ./cmd/mxgw-go galaxy-test-connection -plaintext -json
go run ./cmd/mxgw-go galaxy-last-deploy -plaintext -json
go run ./cmd/mxgw-go galaxy-discover -plaintext -json
go run ./cmd/mxgw-go galaxy-watch -plaintext -json
Use -api-key-env MXGATEWAY_API_KEY or -api-key <key> when authentication is
enabled. CLI output redacts the key value and never writes the raw secret.
Use TLS options for a secured gateway:
go run ./cmd/mxgw-go smoke -endpoint mxgateway.example.local:5001 -ca-cert C:\certs\mxgateway-ca.pem -server-name-override mxgateway.example.local -api-key-env MXGATEWAY_API_KEY -item Area001.Tag.Value -json
Integration Checks
Run live checks only when a gateway and MXAccess-backed worker are available:
$env:MXGATEWAY_INTEGRATION = '1'
$env:MXGATEWAY_ENDPOINT = 'localhost:5000'
$env:MXGATEWAY_API_KEY = '<gateway-api-key>'
$env:MXGATEWAY_TEST_ITEM = 'Area001.Tag.Value'
go run ./cmd/mxgw-go smoke -endpoint $env:MXGATEWAY_ENDPOINT -plaintext -api-key-env MXGATEWAY_API_KEY -item $env:MXGATEWAY_TEST_ITEM -json