diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions/Historian/IHistorianValueWriter.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions/Historian/IHistorianValueWriter.cs
index 50dd3ed5..91e04b5f 100644
--- a/src/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions/Historian/IHistorianValueWriter.cs
+++ b/src/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions/Historian/IHistorianValueWriter.cs
@@ -1,13 +1,13 @@
namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
///
-/// One numeric sample the continuous-historization recorder drains to the historian's SQL
-/// live-value write path: an optional UTC timestamp, the typed value, and the OPC UA status code
-/// the sample was recorded with.
+/// One sample the continuous-historization recorder drains to the backend's live-value write path: an
+/// optional UTC timestamp, the typed value (any the backend supports — the
+/// Gateway backend takes numeric kinds only, SqlServer takes all of them), and the OPC UA status code the
+/// sample was recorded with.
///
///
-/// UTC source timestamp of the sample, or null to defer to the historian's server-stamped
-/// current-time path (the SQL writer uses SYSDATETIME() when the timestamp is absent).
+/// UTC source timestamp of the sample, or null to have the backend stamp the current time.
///
/// The typed sample value.
/// OPC UA status code of the sample (0 = Good).
@@ -16,20 +16,20 @@ public readonly record struct HistorizationValue(DateTime? TimestampUtc, Histori
///
/// Seam over the historian's live-value write path used by the continuous-historization recorder.
/// Lives in the abstraction layer so the Runtime recorder depends on it without taking a hard
-/// reference on the gRPC gateway driver; the gateway driver supplies the concrete adapter
-/// (GatewayHistorianValueWriter).
+/// reference on a backend driver; the selected backend provider supplies the concrete writer
+/// (GatewayHistorianValueWriter or SqlServerHistorianValueWriter).
///
public interface IHistorianValueWriter
{
///
- /// Writes a batch of live values for a single tag through the historian's SQL live-write path.
+ /// Writes a batch of live values for a single tag through the backend's live-write path.
/// Implementations are expected to be non-throwing: a transport/gateway error is surfaced as a
/// false result so the recorder retains the entries and retries, rather than as an
/// exception.
///
/// Fully-qualified historian tag the values are recorded against.
- /// The numeric samples to write, in append order.
+ /// The samples to write, in append order.
/// Cancellation token.
- /// true on a successful (or durably-queued) gateway ack; false on a retryable failure.
+ /// true when the backend stored (or durably queued) the batch; false on a retryable failure.
Task WriteLiveValuesAsync(string tag, IReadOnlyList values, CancellationToken ct);
}
diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests/Recorder/GatewayHistorianValueWriterTests.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests/Recorder/GatewayHistorianValueWriterTests.cs
index 091fb40b..6029cb96 100644
--- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests/Recorder/GatewayHistorianValueWriterTests.cs
+++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests/Recorder/GatewayHistorianValueWriterTests.cs
@@ -73,6 +73,24 @@ public sealed class GatewayHistorianValueWriterTests
Assert.False(ok);
}
+ [Fact]
+ public async Task All_non_numeric_batch_is_acked_without_calling_the_gateway()
+ {
+ // A soft-failure ack: had the writer called the gateway, the result would be false.
+ var fake = new FakeHistorianGatewayClient { WriteLiveValuesResult = new WriteAck { Success = false, Queued = false } };
+ var values = new[]
+ {
+ new HistorizationValue(DateTime.UtcNow, HistorianValue.FromString("text"), 0u),
+ new HistorizationValue(DateTime.UtcNow, HistorianValue.FromDateTime(DateTime.UtcNow), 0u),
+ new HistorizationValue(DateTime.UtcNow, HistorianValue.Empty, 0x80310000u),
+ };
+
+ var ok = await Writer(fake).WriteLiveValuesAsync("T", values, TestContext.Current.CancellationToken);
+
+ Assert.True(ok);
+ Assert.Equal(0, fake.WriteLiveValuesCallCount);
+ }
+
[Fact]
public async Task Empty_batch_is_trivially_acked_without_calling_the_gateway()
{