diff --git a/src/MxGateway.Worker.Tests/AlarmClientDiscoveryTests.cs b/src/MxGateway.Worker.Tests/AlarmClientDiscoveryTests.cs
new file mode 100644
index 0000000..6f4cc2d
--- /dev/null
+++ b/src/MxGateway.Worker.Tests/AlarmClientDiscoveryTests.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Linq;
+using System.Reflection;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace MxGateway.Worker.Tests;
+
+///
+/// One-shot reflection probe — discovers the public surface of
+/// aaAlarmManagedClient.dll so we can design the alarm-helper
+/// wiring in the worker. Marked Skip so it doesn't run as part of the
+/// normal suite; flip the Skip parameter to see the output.
+///
+public sealed class AlarmClientDiscoveryTests
+{
+ private readonly ITestOutputHelper output;
+
+ public AlarmClientDiscoveryTests(ITestOutputHelper output)
+ {
+ this.output = output;
+ }
+
+ [Fact(Skip = "Discovery probe — flip Skip=null to dump aaAlarmManagedClient surface")]
+ public void DumpAlarmClientPublicSurface()
+ {
+ Assembly asm = Assembly.LoadFrom(@"C:\Program Files (x86)\ArchestrA\Framework\Bin\ViewAppFramework\Content\MA\aaAlarmManagedClient.dll");
+
+ output.WriteLine($"Assembly: {asm.FullName}");
+ output.WriteLine("Public types:");
+
+ Type[] types = asm.GetExportedTypes()
+ .OrderBy(t => t.FullName, StringComparer.Ordinal)
+ .ToArray();
+ foreach (Type t in types)
+ {
+ output.WriteLine($" {t.FullName} ({(t.IsClass ? "class" : t.IsInterface ? "interface" : t.IsEnum ? "enum" : "other")})");
+ }
+
+ output.WriteLine("");
+ output.WriteLine("Public events / methods on alarm-named types:");
+ foreach (Type t in types.Where(x => x.Name.IndexOf("Alarm", StringComparison.OrdinalIgnoreCase) >= 0
+ || x.Name.IndexOf("Subscription", StringComparison.OrdinalIgnoreCase) >= 0
+ || x.Name.IndexOf("Event", StringComparison.OrdinalIgnoreCase) >= 0))
+ {
+ output.WriteLine($" {t.FullName}");
+ foreach (EventInfo e in t.GetEvents(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static))
+ {
+ output.WriteLine($" event {e.EventHandlerType?.Name} {e.Name}");
+ }
+ foreach (MethodInfo m in t.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
+ {
+ if (m.IsSpecialName) continue;
+ string parms = string.Join(", ", m.GetParameters().Select(p => $"{p.ParameterType.Name} {p.Name}"));
+ output.WriteLine($" method {m.ReturnType.Name} {m.Name}({parms})");
+ }
+ }
+ }
+}
diff --git a/src/MxGateway.Worker/MxAccess/MxAccessAlarmEventSink.cs b/src/MxGateway.Worker/MxAccess/MxAccessAlarmEventSink.cs
index ebf0f27..5b1f430 100644
--- a/src/MxGateway.Worker/MxAccess/MxAccessAlarmEventSink.cs
+++ b/src/MxGateway.Worker/MxAccess/MxAccessAlarmEventSink.cs
@@ -4,80 +4,49 @@ using MxGateway.Contracts.Proto;
namespace MxGateway.Worker.MxAccess;
///
-/// PR A.2 sink intended to register against an MXAccess alarm event source
-/// and forward each alarm transition into the worker's event queue as an
-/// . The mapper bridge is fully
-/// implemented + unit-tested via
-/// ; the
-/// path is intentionally a no-op pending the
-/// architectural decision documented below.
+/// PR A.2 sink for native MxAccess alarm transitions. Bridges the
+/// aaAlarmManagedClient.AlarmClient consumer to the worker's
+/// event queue, producing messages
+/// via .
///
///
///
-/// 2026-04-30 dev-rig finding: 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 (which exports a single COM interop
-/// module containing ILMXProxyServerEvents and
-/// ILMXProxyServerEvents2) confirms the only available events
-/// are OnDataChange, OnWriteComplete,
-/// OperationComplete, and OnBufferedDataChange. There is
-/// no OnAlarmTransition, no IAlarmEventSink, and no
-/// Alarms collection on the COM server.
+/// Architecture (pinned 2026-04-30): the worker hosts
+/// aaAlarmManagedClient.AlarmClient alongside the existing
+/// ArchestrA.MxAccess COM consumer. Both are x86 .NET Framework
+/// 4.8 — the worker's existing runtime — and both use the same Windows
+/// STA + WM_APP message pump. The MxAccess COM Toolkit at
+/// C:\Program Files (x86)\ArchestrA\Framework\Bin\ArchestrA.MXAccess.dll
+/// exposes no alarm events; the alarm provider lives in a separate
+/// AVEVA service that aaAlarmManagedClient subscribes to.
///
///
-/// AVEVA's separate alarm-subscription managed assemblies
-/// (aaAlarmManagedClient.dll under
-/// InTouch\ViewAppFramework\Content\MA\,
-/// ArchestrAAlarmsAndEvents.SDK.Common.dll under
-/// Wonderware\Historian\x64\) are present on this box but are
-/// x64-only; they cannot load into the worker process,
-/// which is x86 because of the MXAccess COM bitness constraint that
-/// the mxaccessgw architecture exists to isolate. Loading them
-/// in a separate x64 helper process would add meaningful operational
-/// complexity (a third process tier alongside worker + gateway) and is
-/// not in the current architecture.
+/// Discovered API surface (see
+/// AlarmClientDiscoveryTests.DumpAlarmClientPublicSurface in
+/// MxGateway.Worker.Tests — Skip-gated reflection probe):
///
-///
-/// Two paths forward — operator decision needed before the
-/// sink can be wired:
-///
-///
-/// -
-///
-/// Stay on the value-driven sub-attribute path
-/// (current production behaviour). The lmxopcua server's
-/// AlarmConditionService already synthesizes Part 9
-/// transitions from the four MXAccess sub-attributes
-/// (InAlarm, Acked, Priority,
-/// Description) via the data-change subscription.
-/// Operator-comment fidelity is the only regression vs. v1; if
-/// acceptable, this row stays the production path and the
-/// family stays
-/// reserved-but-dormant on the wire.
-///
-///
-/// -
-///
-/// Add an x64 alarm-helper sub-process alongside
-/// the worker that loads aaAlarmManagedClient,
-/// subscribes to alarms, and forwards transitions to the worker
-/// over a small named-pipe IPC. Then this sink's
-/// connects to that helper instead of to
-/// the COM server, and routes each transition through
-/// . Adds operational complexity
-/// but recovers full v1 fidelity (operator user, comment,
-/// original raise time, category).
-///
-///
+///
+/// - RegisterConsumer(hWnd, productName, applicationName, version, retainHidden) — registers a Windows-message-pump consumer; the AVEVA alarm service WM_APP-pokes the hWnd when alarms change.
+/// - Subscribe(provider, fromPri, toPri, queryType, sortFlags, filterMask, filterSpec) — subscribes to a Galaxy alarm provider with priority + filter scoping.
+/// - GetStatistics(out percentQuery, totalAlarms, activeAlarms, …, out int[] changeCodes, out int[] changePos, out int[] hAlarm) — called on each WM_APP poke; enumerates which alarms changed.
+/// - GetAlarmExtendedRec(index, out AlarmRecord) — pulls the full alarm record (operator, comment, original raise, category, severity).
+/// - AlarmAckByGUID(alarmGuid, ackComment, oprName, oprNode, oprDomain, oprFullName) — full-fidelity native Acknowledge: comment + four operator-identity fields are atomic with the ack transition.
///
///
-/// Until that decision is made, this sink's is a
-/// no-op. The worker continues to function for data subscriptions, and
-/// the gateway's family
-/// is reserved on the wire but never emitted. lmxopcua-side
-/// AlarmConditionService keeps the sub-attribute synthesis
-/// active and continues to surface alarms to OPC UA Part 9 clients.
+/// Wiring plan (subsequent PRs):
+///
+///
+/// - Worker session-startup wires AlarmClient.RegisterConsumer against the worker's existing STA hWnd; Subscribe with the Galaxy provider name + a permissive priority/filter range.
+/// - The STA's WM_APP handler routes alarm-changed messages into ; the message ID is established at runtime via the consumer's reported handler (verify on dev rig).
+/// - Gateway-side AcknowledgeAlarm RPC translates to a worker command that calls AlarmClient.AlarmAckByGUID with the OPC UA operator's resolved identity — replaces the worker-pending diagnostic from PR A.3.
+///
+///
+/// Until those PRs land, is a no-op. The worker
+/// continues to function for data subscriptions, and the gateway's
+/// family is reserved on
+/// the wire but never emitted. lmxopcua-side AlarmConditionService
+/// keeps the sub-attribute synthesis active and continues to surface
+/// alarms to OPC UA Part 9 clients in the meantime.
///
///
public sealed class MxAccessAlarmEventSink : IMxAccessEventSink
diff --git a/src/MxGateway.Worker/MxGateway.Worker.csproj b/src/MxGateway.Worker/MxGateway.Worker.csproj
index a23d32a..dbde52e 100644
--- a/src/MxGateway.Worker/MxGateway.Worker.csproj
+++ b/src/MxGateway.Worker/MxGateway.Worker.csproj
@@ -24,6 +24,11 @@
false
false
+
+ C:\Program Files (x86)\ArchestrA\Framework\Bin\ViewAppFramework\Content\MA\aaAlarmManagedClient.dll
+ false
+ false
+