review(Core.Abstractions): document ReadEventsAsync continuation contract (OpcUaServer-002 root)

Re-review at 7286d320. Core.Abstractions-009: ReadEventsAsync maxEvents<=0 sentinel now
documents the implementer's continuation-point obligation when a backend cap truncates
(the root of OpcUaServer-002). -010: PollGroupEngineTests pass CancellationToken. Plus
EquipmentTagRefResolver.TryResolve [MaybeNullWhen(false)] NRT cleanup + test.
This commit is contained in:
Joseph Doherty
2026-06-19 11:06:56 -04:00
parent 354b0e7613
commit 65e6af6001
7 changed files with 135 additions and 12 deletions
@@ -1,4 +1,5 @@
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
@@ -27,9 +28,14 @@ public sealed class EquipmentTagRefResolver<TDef> where TDef : class
/// <summary>True when <paramref name="fullReference"/> resolves to a def (authored or equipment).</summary>
/// <param name="fullReference">The wire reference handed to the driver.</param>
/// <param name="def">The resolved tag-definition when this returns true.</param>
/// <param name="def">
/// The resolved tag-definition when this returns <see langword="true"/>. When this returns
/// <see langword="false"/> the value is undefined — callers must not use it.
/// <c>[MaybeNullWhen(false)]</c> informs the nullable-reference-types (NRT) analyser so
/// callers in NRT-enabled contexts do not need to suppress warnings.
/// </param>
/// <returns><see langword="true"/> when a definition was found.</returns>
public bool TryResolve(string fullReference, out TDef def)
public bool TryResolve(string fullReference, [MaybeNullWhen(false)] out TDef def)
{
var authored = _byName(fullReference);
if (authored is not null) { def = authored; return true; }
@@ -37,7 +43,7 @@ public sealed class EquipmentTagRefResolver<TDef> where TDef : class
var resolved = _cache.GetOrAdd(fullReference, _parseRef);
if (resolved is not null) { def = resolved; return true; }
def = null!;
def = default;
return false;
}
@@ -91,7 +91,13 @@ public interface IHistorianDataSource : IDisposable
/// <param name="sourceName">The source name to filter events, or null to return events from all sources.</param>
/// <param name="startUtc">The start of the time range in UTC.</param>
/// <param name="endUtc">The end of the time range in UTC.</param>
/// <param name="maxEvents">The maximum number of events to return, or a non-positive value to use the default backend cap.</param>
/// <param name="maxEvents">
/// The maximum number of events to return. A non-positive value uses the backend's
/// default cap. When the backend cap truncates the result, implementations MUST set
/// <see cref="HistoricalEventsResult.ContinuationPoint"/> to a non-null token so
/// callers can detect truncation and page — a null continuation point means all
/// matching events were returned. (Core.Abstractions-009.)
/// </param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
Task<HistoricalEventsResult> ReadEventsAsync(
string? sourceName,
@@ -92,6 +92,14 @@ public interface IHistoryProvider
/// adapters historically treat <c>maxEvents &lt;= 0</c> as a sentinel meaning
/// "use the backend's default cap" (see <c>WonderwareHistorianClient</c> /
/// <c>HistorianDataSource</c>). The asymmetry is intentional — Core.Abstractions-006.
///
/// <b>Continuation contract when using the sentinel:</b> When <c>maxEvents &lt;= 0</c>
/// the backend applies its own cap. If the backend's cap truncates the result
/// (i.e. more matching events exist beyond the returned set), implementations MUST set
/// <see cref="HistoricalEventsResult.ContinuationPoint"/> to a non-null token so
/// callers can detect truncation and page. A null <c>ContinuationPoint</c> means all
/// matching events were returned — callers rely on this to decide whether to page.
/// (Core.Abstractions-009.)
/// </param>
/// <param name="cancellationToken">Request cancellation.</param>
/// <remarks>