diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/HeidenhainDriverEndToEndTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/HeidenhainDriverEndToEndTests.cs
new file mode 100644
index 000000000..4afb9da24
--- /dev/null
+++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/HeidenhainDriverEndToEndTests.cs
@@ -0,0 +1,84 @@
+using System.Text.Json;
+using Akka.Actor;
+using Shouldly;
+using Xunit;
+using ZB.MOM.WW.OtOpcUa.Commons.OpcUa;
+using ZB.MOM.WW.OtOpcUa.Driver.Heidenhain;
+using ZB.MOM.WW.OtOpcUa.Driver.Heidenhain.Tests.Simulator;
+using ZB.MOM.WW.OtOpcUa.Runtime.Drivers;
+using ZB.MOM.WW.OtOpcUa.Runtime.Tests.Harness;
+
+namespace ZB.MOM.WW.OtOpcUa.Runtime.Tests.Drivers;
+
+///
+/// lmxopcua#803 end-to-end through the REAL : the real Heidenhain driver
+/// (built by its factory from a merged config) against the in-process LSV2 simulator — subscribe, OPC write
+/// refusal, controller-side outbound restriction, outage invalidation and recovery.
+///
+[Trait("Category", "Unit")]
+public sealed class HeidenhainDriverEndToEndTests : RuntimeActorTestBase
+{
+ private const string Exec = "Plant/H/dev1/Status/ExecutionState";
+ private const string Model = "Plant/H/dev1/Controller/Model";
+
+ private static string Config(int port) => JsonSerializer.Serialize(new
+ {
+ Devices = new[] { new { Host = "127.0.0.1", Port = port, DeviceName = "dev1" } },
+ RawTags = new[]
+ {
+ new { RawPath = Exec, TagConfig = """{"address":"Status/ExecutionState"}""", WriteIdempotent = false, DeviceName = "dev1" },
+ new { RawPath = Model, TagConfig = """{"address":"Controller/Model"}""", WriteIdempotent = false, DeviceName = "dev1" },
+ },
+ TimeoutMs = 500,
+ Polling = new { StatusMs = 100, CustomMs = 100, SlowMs = 200 },
+ Reconnect = new { InitialDelayMs = 100, MaxDelayMs = 200 },
+ });
+
+ private DriverInstanceActor.AttributeValueItem? AwaitItem(Akka.TestKit.TestProbe parent, string reference, Func match)
+ {
+ DriverInstanceActor.AttributeValueItem? found = null;
+ parent.FishForMessage(b =>
+ {
+ foreach (var i in b.Items)
+ if (i.FullReference == reference && match(i)) { found = i; return true; }
+ return false;
+ }, TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken);
+ return found;
+ }
+
+ [Fact]
+ public async Task Subscribe_write_refusal_outage_and_recovery_through_the_real_actor()
+ {
+ var control = new SimulatedControl();
+ control.Runtime[23] = () => SimulatedControl.U16(4);
+ await using var sim = Lsv2Simulator.WithControl(control);
+ var driver = HeidenhainDriverFactoryExtensions.CreateInstance("hh-e2e", Config(sim.Port));
+ var parent = CreateTestProbe();
+ parent.IgnoreMessages(m => m is not DriverInstanceActor.AttributeValueBatchPublished);
+ var actor = parent.ChildActorOf(
+ DriverInstanceActor.Props(driver, reconnectInterval: TimeSpan.FromMilliseconds(100),
+ healthPollInterval: TimeSpan.FromMilliseconds(100), commsLossTimeout: TimeSpan.FromSeconds(1)),
+ TestContext.Current.CancellationToken);
+
+ actor.Tell(new DriverInstanceActor.SetDesiredSubscriptions([Exec, Model], TimeSpan.FromMilliseconds(100)));
+ actor.Tell(new DriverInstanceActor.InitializeRequested(Config(sim.Port)));
+
+ AwaitItem(parent, Exec, i => i.StatusCode == OpcUaStatus.Good)!.Value.Value.ShouldBe(4);
+
+ // OPC write: refused by the runtime (the driver implements no IWritable) — nothing reaches the control.
+ var write = await actor.Ask(
+ new DriverInstanceActor.WriteAttribute(Exec, 9), TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken);
+ write.Success.ShouldBeFalse();
+
+ // Outage: every remote value goes BadNoCommunication.
+ sim.GoOffline();
+ AwaitItem(parent, Exec, i => i.StatusCode == HeidenhainStatus.BadNoCommunication).ShouldNotBeNull();
+
+ // Recovery: the control comes back holding the SAME value, and it re-publishes Good.
+ sim.GoOnline();
+ AwaitItem(parent, Exec, i => i.StatusCode == OpcUaStatus.Good)!.Value.Value.ShouldBe(4);
+
+ control.Violations.ShouldBeEmpty();
+ sim.Requests.Select(r => r.Command).Distinct().ShouldBeSubsetOf(new[] { "A_LG", "R_VR", "R_RI" });
+ }
+}
diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/ZB.MOM.WW.OtOpcUa.Runtime.Tests.csproj b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/ZB.MOM.WW.OtOpcUa.Runtime.Tests.csproj
index 276983f61..93ea8b0a9 100644
--- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/ZB.MOM.WW.OtOpcUa.Runtime.Tests.csproj
+++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/ZB.MOM.WW.OtOpcUa.Runtime.Tests.csproj
@@ -40,11 +40,15 @@
+
+
+