diff --git a/archreview/plans/R2-09-driver-fleet-batch-plan.md.tasks.json b/archreview/plans/R2-09-driver-fleet-batch-plan.md.tasks.json
index 12fda5e7..e802ac57 100644
--- a/archreview/plans/R2-09-driver-fleet-batch-plan.md.tasks.json
+++ b/archreview/plans/R2-09-driver-fleet-batch-plan.md.tasks.json
@@ -95,7 +95,7 @@
{
"id": "B3.5",
"subject": "B3: failing S7PollEngineMigrationTests (array-cadence PERF-3, rapid sub/unsub STAB-6, sustained-failure health+backoff)",
- "status": "pending",
+ "status": "completed",
"blockedBy": [
"B3.2"
]
diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7.Tests/S7PollEngineMigrationTests.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7.Tests/S7PollEngineMigrationTests.cs
new file mode 100644
index 00000000..f3e6d8fc
--- /dev/null
+++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7.Tests/S7PollEngineMigrationTests.cs
@@ -0,0 +1,130 @@
+using S7.Net;
+using Shouldly;
+using Xunit;
+using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
+using S7NetDataType = global::S7.Net.DataType;
+
+namespace ZB.MOM.WW.OtOpcUa.Driver.S7.Tests;
+
+///
+/// Guards for retiring the bespoke S7 poll fork onto the shared PollGroupEngine
+/// (CONV-1). The engine brings two fixes the fork lacked — structural array diffing (PERF-3)
+/// and drain-before-CTS-dispose on unsubscribe (STAB-6-S7) — and keeps failure→health parity.
+///
+[Trait("Category", "Unit")]
+public sealed class S7PollEngineMigrationTests
+{
+ // A fake that opens cleanly and serves a CONSTANT 6-byte block for array reads (three
+ // big-endian UInt16 words) — every ReadArrayAsync decodes a fresh ushort[3] with identical
+ // contents, so a reference-equality diff (the fork) refires every tick while a structural
+ // diff (the engine) fires only once.
+ private sealed class ConstantArrayFactory : IS7PlcFactory
+ {
+ public bool ReadThrows { get; set; }
+ public IS7Plc Create(S7CpuType cpuType, string host, int port, short rack, short slot, TimeSpan timeout)
+ => new ConstantArrayPlc(this);
+ }
+
+ private sealed class ConstantArrayPlc(ConstantArrayFactory owner) : IS7Plc
+ {
+ public bool IsConnected { get; private set; }
+ public Task OpenAsync(CancellationToken ct) { IsConnected = true; return Task.CompletedTask; }
+ public void Close() => IsConnected = false;
+
+ public Task