Replaces the inline diagnostic strings in PR A.3's AcknowledgeAlarm
+ QueryActiveAlarms handlers with an IAlarmRpcDispatcher seam.
- IAlarmRpcDispatcher (new) — gateway-side abstraction over the
worker-RPC path that fronts AlarmClient.AlarmAckByGUID and the
active-alarm walk. AcknowledgeAsync returns the
AcknowledgeAlarmReply directly; QueryActiveAlarmsAsync yields an
IAsyncEnumerable<ActiveAlarmSnapshot>.
- NotWiredAlarmRpcDispatcher (new, default impl) — returns
PROTOCOL_STATUS_OK with a structured worker-pending diagnostic
on Acknowledge, yields an empty stream on QueryActiveAlarms.
Same observable shape as PR A.3, but the integration seam is
now in code instead of hardcoded inside the handler.
- MxAccessGatewayService — handlers delegate to the dispatcher.
Constructor accepts an optional IAlarmRpcDispatcher (default
NotWiredAlarmRpcDispatcher); a future WorkerAlarmRpcDispatcher
registration in DI swaps in the live worker-IPC routing without
changing the public RPC surface.
- 2 new dispatcher tests pin the not-wired contract; 279 → 281
total tests, all green.
Worker-side dispatch (translating Acknowledge / QueryActiveAlarms
to the IPC method that calls IMxAccessAlarmConsumer from PR A.5)
is the dev-rig follow-up — it depends on validating the AVEVA
GetAlarmChangesCompleted event subscription against a live alarm
provider before pinning a wire format.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires the worker-side consumer for AVEVA alarm transitions over the
aaAlarmManagedClient API discovered in the prior foundation PR.
- IAlarmMgrDataProvider.dll referenced — exposes AlarmRecord +
eAlmTransitions / eQueryType / eSortFlags / eAlarmFilterState.
Both DLLs (aaAlarmManagedClient + IAlarmMgrDataProvider) load in
the worker's existing net48 x86 process; no new bitness boundary.
- IMxAccessAlarmConsumer abstraction — Subscribe / AcknowledgeByGuid
/ SnapshotActiveAlarms / AlarmRecordReceived event. Test seam.
- AlarmClientConsumer production wrapper — RegisterConsumer +
Subscribe + AlarmAckByGUID + GetStatistics-based active-alarm
walk, all delegated to AlarmClient. Uses AVEVA's managed event
surface (GetAlarmChangesCompleted on IAlarmMgrDataProvider) so
no Windows message pump is required — plain .NET events arrive
on the alarm-client's internal callback thread.
- AlarmRecordTransitionMapper — pure-function helpers:
MapTransitionKind(eAlmTransitions): ALM→Raise, ACK→Acknowledge,
RTN→Clear, others (SUB/ENB/DIS/SUP/REL/REMOVE)→Unspecified
so EventPump's decoding-failure counter records them.
ComposeFullReference(provider, group, name): Provider!Group.Name
format matching AVEVA's standard alarm-reference syntax.
Pinned during dev-rig validation (subsequent commits):
1. Confirm RegisterConsumer accepts hWnd=0 — if it requires a real
hwnd, the worker creates a hidden message-only window and
passes that handle. The managed event surface should make
this irrelevant but the AVEVA API is older than its managed
wrapper.
2. Wire AlarmClientConsumer.AlarmRecordReceived: the AVEVA
IAlarmMgrDataProvider.GetAlarmChangesCompleted event needs to
be hooked from inside the AlarmClient — find the proper
accessor (likely a property exposing the inner provider).
3. AlarmRecord field-by-field translation into the proto event
uses MxAccessAlarmEventSink.EnqueueTransition (existing
plumbing). The AlarmRecord field names (ar_OrigTime,
AlarmName, AckOperatorFullName, AckComment, etc.) are
pinned in the discovery dump preserved in
AlarmClientDiscoveryTests.
Tests: 127 pass (4 new ComposeFullReference cases + 1 Skip-gated
discovery probe). Transition-kind enum mapping is dev-rig-validated
rather than unit-tested because the AVEVA assembly is Private=false
on the reference and isn't copied to the test bin directory.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Discovers the surface of aaAlarmManagedClient.dll and stages the worker
csproj reference so subsequent PRs can wire native MxAccess alarm
subscription. Replaces the speculative "operator decision needed
between path 1 and path 2" framing in MxAccessAlarmEventSink with the
validated architecture.
Key findings from the discovery probe:
1. aaAlarmManagedClient.dll is x86 + .NET Framework (mixed-mode
C++/CLI; PE Machine = i386, NativeEntryPoint flag set). The
"x64-only" framing in the prior follow-up was wrong — confused
by the file path under Wonderware\Historian\x64\.
The assembly is bitness- and runtime-compatible with the
worker (net48 x86), so it loads in the existing process. No
sub-process needed.
2. AlarmClient is the public class. Its model mirrors MxAccess:
RegisterConsumer takes a Windows hWnd and the AVEVA alarm
service WM_APP-pokes that hwnd when alarms change. The worker's
existing STA + WM_APP pump can drive both the data-change COM
subscriber and the alarm-client consumer.
3. AlarmAckByGUID(alarmGuid, ackComment, oprName, oprNode,
oprDomain, oprFullName) — the native ack carries the operator's
full identity atomically with the comment. Closes the v1
operator-comment fidelity gap completely.
This PR:
- Adds the aaAlarmManagedClient.dll reference to MxGateway.Worker.
csproj. Worker still builds clean.
- Adds AlarmClientDiscoveryTests as a Skip-gated reflection probe;
flip the Skip parameter to dump the public type surface for
reference. Captured the dump into MxAccessAlarmEventSink
documentation so it doesn't have to be re-run.
- Replaces MxAccessAlarmEventSink's "two paths forward" doc with
the actual wiring plan against AlarmClient's RegisterConsumer +
Subscribe + AlarmAckByGUID surface.
Subsequent PRs (gated on STA + WM_APP integration testing on the
dev rig):
- Wire RegisterConsumer + Subscribe at session-startup; route
WM_APP messages through GetStatistics + GetAlarmExtendedRec into
EnqueueTransition.
- Translate gateway-side AcknowledgeAlarm RPC to a worker command
that calls AlarmAckByGUID with the OPC UA operator's identity;
replaces the worker-pending diagnostic from PR A.3.
- Translate gateway-side QueryActiveAlarms to a worker command
that walks GetStatistics's reported handles via GetAlarmExtendedRec.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR A.2 ship-pin discovery: the MXAccess COM Toolkit installed at
C:\Program Files (x86)\ArchestrA\Framework\Bin\ArchestrA.MXAccess.dll
does not expose any alarm-event family. Reflection enumeration of
the assembly confirms ILMXProxyServerEvents and
ILMXProxyServerEvents2 only carry OnDataChange, OnWriteComplete,
OperationComplete, and OnBufferedDataChange — no IAlarmEventSink,
no Alarms collection, no OnAlarmTransition.
AVEVA's separate alarm-subscription managed assemblies
(aaAlarmManagedClient.dll under InTouch\ViewAppFramework\Content\MA\
and ArchestrAAlarmsAndEvents.SDK.Common.dll under
Wonderware\Historian\x64\) exist on this box but are x64-only —
incompatible with the worker's x86 bitness, which is the bitness
constraint the mxaccessgw architecture exists to isolate in the
first place.
This commit replaces the speculative "TBD pin during dev-rig
validation" comment in MxAccessAlarmEventSink with the actual
finding plus the two operator-facing paths forward:
1. Stay on the value-driven sub-attribute path (current production
behaviour). lmxopcua's AlarmConditionService already synthesizes
Part 9 transitions from the four MXAccess sub-attributes.
Operator-comment fidelity is the only v1 regression.
2. Add an x64 alarm-helper sub-process alongside the worker that
loads aaAlarmManagedClient and forwards transitions to the
worker over a small named-pipe IPC. Recovers full v1 fidelity
but adds operational complexity.
Until that decision resolves, the sink's Attach is a no-op, the
worker continues to function for data subscriptions, and
lmxopcua-side AlarmConditionService keeps the sub-attribute
synthesis active.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Nineteenth (final) PR of the alarms-over-gateway epic. Pins the
public RPC handler contract added in PR A.3:
- AcknowledgeAlarm rejects empty session_id and empty
alarm_full_reference with InvalidArgument.
- AcknowledgeAlarm with valid input returns OK and a
worker-pending diagnostic so clients see a successful round-trip
even before A.2's worker dispatch lands.
- QueryActiveAlarms rejects empty session_id with InvalidArgument.
- QueryActiveAlarms with valid input streams zero snapshots until
PR A.2 wires the worker-side QueryActiveAlarmsCommand
(filter-prefix passthrough verified at the proto layer).
- OpenSession advertises both new RPC capability strings
(unary-acknowledge-alarm, server-stream-active-alarms) so client
capability negotiation lights up against the contract surface.
Closes Track A's gateway-side surface. The remaining worker
ConditionRefresh walk + integration parity-rig validation lands
during dev-rig hardware validation alongside PR A.2's COM-side
alarm subscription pin.
Tests: 279 passed (was 273; 6 new). Per-handler integration tests
land alongside the dev-rig validation when the worker walks the
real MxAccess active-alarm collection.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Eighteenth PR of the alarms-over-gateway epic
(docs/plans/alarms-over-gateway.md). Lands the proto-build path that
the worker uses to create OnAlarmTransition events. The COM-side
subscription that registers an alarm event sink against the MXAccess
Toolkit is pinned during dev-rig validation — the exact API differs
across AVEVA versions and needs hardware to verify.
Lands today (unit-testable, no hardware needed):
- MxAccessEventMapper.CreateOnAlarmTransition — mechanical proto
builder. Takes decoded alarm fields (full reference, source
object, alarm type, transition kind, severity, timestamps,
operator user/comment, category, description) and produces an
MxEvent with the OnAlarmTransition body populated. Mirrors the
pattern of CreateOnDataChange / CreateOnWriteComplete / etc.
- MxAccessAlarmEventSink — scaffolded class with documented
Attach / Detach + an internal EnqueueTransition entry point.
When dev-rig validation pins the MXAccess Toolkit alarm
subscription API, the only edit needed is to wire the COM
delegate inside Attach to call EnqueueTransition. The mapper
bridge is already done.
Pending dev-rig validation:
- Pin the MXAccess Toolkit alarm event source COM API (likely one
of IAlarmEventSink, IAlarmEventSubscription, or a method on
LMXProxyServerClass — verify against the worker host's installed
version).
- Add cancellation/cleanup tests once the COM hook is wired.
- Integration test against the parity rig that fires a real Galaxy
alarm and asserts the gateway emits OnAlarmTransition.
Tests:
- 2 new mapper tests pin the full-payload Acknowledge case and
the bare-bones Raise case.
- Full Worker.Tests suite green: 123 passed (was 121; 2 new).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Twelfth PR of the alarms-over-gateway epic
(docs/plans/alarms-over-gateway.md). Lands the public RPC handler
surface that PR A.1's proto introduced. The actual worker-side
ack call + active-alarm walk depend on PR A.2 (worker MxAccess
subscription); this PR ensures clients can call the RPCs and
receive a meaningful response without UNIMPLEMENTED at the gRPC
layer.
- AcknowledgeAlarm — validates session_id + alarm_full_reference,
resolves the session (NotFound on miss), returns a successful
reply with a structured DiagnosticMessage indicating worker
dispatch is pending PR A.2. Once A.2 ships, the body translates
the request into a WorkerCommand and forwards through
SessionManager.InvokeAsync.
- QueryActiveAlarms — validates session_id, returns an empty
stream. PR A.4 layers the actual ConditionRefresh implementation
once the worker's QueryActiveAlarmsCommand is available.
- OpenSessionReply.Capabilities advertises both new RPCs
(unary-acknowledge-alarm, server-stream-active-alarms) so
clients can negotiate against the contract surface.
OnAlarmTransition events flow through the existing StreamEvents
path automatically — EventStreamService and MxAccessGrpcMapper
forward whatever family the worker emits without filtering, so
no changes are needed there for A.3.
Tests: full 273-test suite still green. Per-handler unit tests
ship with PR A.4's expanded surface; A.3's stub handlers are
narrow enough that the existing parity-fixture tests cover the
contract round-trip.
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>