diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/OtOpcUaNodeManager.cs b/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/OtOpcUaNodeManager.cs
index 9e6091e9..6f536cb0 100644
--- a/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/OtOpcUaNodeManager.cs
+++ b/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/OtOpcUaNodeManager.cs
@@ -458,6 +458,15 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
advertised.Count, source.GetType().Name,
string.Join(", ", advertised.Select(id => AggregateFunctionMap.Map(id)?.ToString() ?? id.ToString())));
}
+
+ // #736: HistoryServerCapabilities/MaxReturnDataValues (i=11273) carries the source's raw-page cap
+ // (0 = unlimited), re-synced on every source assignment like the aggregate set above.
+ if (!HistoryPaging.AdvertiseMaxReturnDataValues(Server, source.MaxRawValuesPerRead))
+ {
+ _logger.LogWarning(
+ "OtOpcUaNodeManager: could not advertise HistoryServerCapabilities/MaxReturnDataValues ({Cap}) for historian {Source}; the cap is still enforced by HistoryRead",
+ source.MaxRawValuesPerRead, source.GetType().Name);
+ }
}
catch (Exception ex)
{
diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests/NodeManagerMaxReturnDataValuesSyncTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests/NodeManagerMaxReturnDataValuesSyncTests.cs
new file mode 100644
index 00000000..ea47eedd
--- /dev/null
+++ b/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests/NodeManagerMaxReturnDataValuesSyncTests.cs
@@ -0,0 +1,67 @@
+using Opc.Ua;
+using Opc.Ua.Server;
+using Shouldly;
+using Xunit;
+using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
+using HistorianRead = ZB.MOM.WW.OtOpcUa.Core.Abstractions.HistoryReadResult;
+
+namespace ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests;
+
+///
+/// #736 — assigning re-syncs
+/// HistoryServerCapabilities/MaxReturnDataValues (i=11273) to the source's
+/// , read back the way a client would. The Null
+/// source (the historian-disabled default) advertises Part 11's "no limit".
+///
+public sealed class NodeManagerMaxReturnDataValuesSyncTests(BoundsServerFixture fx) : IClassFixture
+{
+ [Fact]
+ public async Task Setting_the_historian_source_advertises_its_raw_cap()
+ {
+ await fx.Gate.WaitAsync(TestContext.Current.CancellationToken);
+ try
+ {
+ fx.Nm.HistorianDataSource = new CappedSource(7_000);
+ (await ReadAdvertisedAsync()).ShouldBe(7_000u);
+
+ fx.Nm.HistorianDataSource = new CappedSource(250);
+ (await ReadAdvertisedAsync()).ShouldBe(250u);
+
+ fx.Nm.HistorianDataSource = null!; // the Null default: no cap
+ (await ReadAdvertisedAsync()).ShouldBe(0u);
+ }
+ finally
+ {
+ fx.Nm.HistorianDataSource = null!;
+ fx.Gate.Release();
+ }
+ }
+
+ private async Task ReadAdvertisedAsync()
+ {
+ var context = new OperationContext(new RequestHeader(), secureChannelContext: null, RequestType.Read, identity: null);
+ var nodesToRead = new ReadValueIdCollection
+ {
+ new ReadValueId { NodeId = VariableIds.HistoryServerCapabilities_MaxReturnDataValues, AttributeId = Attributes.Value },
+ };
+ var (values, _) = await fx.Nm.Server.NodeManager.ReadAsync(context, 0, TimestampsToReturn.Neither, nodesToRead, TestContext.Current.CancellationToken);
+ values.Count.ShouldBe(1);
+ StatusCode.IsGood(values[0].StatusCode).ShouldBeTrue(values[0].StatusCode.ToString());
+ return (uint)values[0].Value;
+ }
+
+ private sealed class CappedSource(int cap) : IHistorianDataSource
+ {
+ public int MaxRawValuesPerRead => cap;
+ public Task ReadRawAsync(string r, DateTime s, DateTime e, uint m, CancellationToken ct) =>
+ NullHistorianDataSource.Instance.ReadRawAsync(r, s, e, m, ct);
+ public Task ReadProcessedAsync(string r, DateTime s, DateTime e, TimeSpan i, HistoryAggregateType a, CancellationToken ct) =>
+ NullHistorianDataSource.Instance.ReadProcessedAsync(r, s, e, i, a, ct);
+ public Task ReadAtTimeAsync(string r, IReadOnlyList t, CancellationToken ct) =>
+ NullHistorianDataSource.Instance.ReadAtTimeAsync(r, t, ct);
+ public Task ReadEventsAsync(string? n, DateTime s, DateTime e, int m, CancellationToken ct) =>
+ NullHistorianDataSource.Instance.ReadEventsAsync(n, s, e, m, ct);
+ public HistorianHealthSnapshot GetHealthSnapshot() => NullHistorianDataSource.Instance.GetHealthSnapshot();
+ public void Dispose() { }
+ }
+}