diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway/GatewayTagProvisioner.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway/GatewayTagProvisioner.cs
index 08e87f28..986f82ab 100644
--- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway/GatewayTagProvisioner.cs
+++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway/GatewayTagProvisioner.cs
@@ -19,6 +19,16 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway;
///
public sealed class GatewayTagProvisioner : IHistorianProvisioning
{
+ ///
+ /// Storage rate (ms) stamped on every provisioned analog tag. The gateway's EnsureTags path
+ /// maps this straight to the AVEVA SDK's storageRateMs, which requires a positive, quantized
+ /// value (1000/5000/10000/…); the proto's uint32 default of 0 makes the SDK throw
+ /// ("Storage rate must be > 0 ms"), failing the whole
+ /// provision. 1000 ms (1 s) matches the gateway's own live provisioner default and suits
+ /// continuous-historization analog tags.
+ ///
+ private const uint DefaultStorageRateMs = 1000;
+
private readonly IHistorianGatewayClient _client;
private readonly ILogger _logger;
@@ -58,6 +68,8 @@ public sealed class GatewayTagProvisioner : IHistorianProvisioning
// Proto string fields are non-nullable — coalesce absent metadata to empty.
EngineeringUnit = request.EngineeringUnit ?? string.Empty,
Description = request.Description ?? string.Empty,
+ // Required: a 0 storage rate makes the gateway's EnsureTags throw (see the constant).
+ StorageRateMs = DefaultStorageRateMs,
});
}
diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests/GatewayTagProvisionerTests.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests/GatewayTagProvisionerTests.cs
index a98dad05..698d766c 100644
--- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests/GatewayTagProvisionerTests.cs
+++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests/GatewayTagProvisionerTests.cs
@@ -60,6 +60,25 @@ public sealed class GatewayTagProvisionerTests
Assert.True(defs[0].HasDataType);
}
+ [Fact]
+ public async Task Definition_carries_a_positive_storage_rate()
+ {
+ // Regression guard (R2-06 live gate): the gateway maps StorageRateMs straight to the AVEVA SDK's
+ // storageRateMs, which requires a positive value — the proto uint32 default of 0 makes EnsureTags
+ // throw ArgumentOutOfRangeException ("Storage rate must be > 0 ms"), silently failing EVERY
+ // historized-tag provision. A non-zero rate must always be stamped.
+ var fake = new FakeHistorianGatewayClient { EnsureTagsResult = new TagOperationResults() };
+ var p = Provisioner(fake);
+
+ await p.EnsureTagsAsync(
+ new[] { new HistorianTagProvisionRequest("Pump1.Temp", DriverDataType.Float32, null, null) },
+ TestContext.Current.CancellationToken);
+
+ var defs = fake.LastEnsureDefinitions!;
+ Assert.Single(defs);
+ Assert.True(defs[0].StorageRateMs > 0, "provisioned tags must carry a positive storage rate (0 fails EnsureTags).");
+ }
+
[Fact]
public async Task Maps_metadata_and_coalesces_null_metadata_to_empty()
{
diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests/Live/GatewayLiveIntegrationTests.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests/Live/GatewayLiveIntegrationTests.cs
index 1453d34a..484687d2 100644
--- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests/Live/GatewayLiveIntegrationTests.cs
+++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests/Live/GatewayLiveIntegrationTests.cs
@@ -103,6 +103,7 @@ public sealed class GatewayLiveIntegrationTests(GatewayLiveFixture fixture) : IC
DataType = HistorianDataType.Float,
EngineeringUnit = string.Empty,
Description = "OtOpcUa live validation sandbox",
+ StorageRateMs = 1000, // 0 makes the gateway's EnsureTags throw (storageRateMs must be > 0).
},
},
ct);
@@ -345,6 +346,7 @@ public sealed class GatewayLiveIntegrationTests(GatewayLiveFixture fixture) : IC
DataType = HistorianDataType.Float,
EngineeringUnit = string.Empty,
Description = "OtOpcUa live validation retype probe (stage 1: Float)",
+ StorageRateMs = 1000, // 0 makes the gateway's EnsureTags throw (storageRateMs must be > 0).
},
},
ct);
@@ -358,6 +360,7 @@ public sealed class GatewayLiveIntegrationTests(GatewayLiveFixture fixture) : IC
DataType = HistorianDataType.Int1,
EngineeringUnit = string.Empty,
Description = "OtOpcUa live validation retype probe (stage 2: Int1)",
+ StorageRateMs = 1000, // 0 makes the gateway's EnsureTags throw (storageRateMs must be > 0).
},
},
ct);