fix(opcuaclient): review fixes - drop driver-side OperationLimits chunking (SDK batches), diagnostics as OTel counter, safe discovery selection, options validation at load (#273 #275 #276 #277 #278)

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-08 14:55:53 -04:00
parent 935ba8d1d4
commit 1219b7b13e
13 changed files with 406 additions and 652 deletions
@@ -203,33 +203,20 @@ public sealed record OpcUaClientDriverOptions
/// <summary>
/// Publish cycles with no data before the server sends a keep-alive (default 10). Must be &gt;= 1.
/// </summary>
[Range(1, int.MaxValue)]
public uint SubscriptionKeepAliveCount { get; init; } = 10;
/// <summary>
/// Publish cycles with no Publish request before the server drops the subscription (default 1000).
/// Must be at least 3x <see cref="SubscriptionKeepAliveCount"/> (OPC UA Part 4 §5.13.2).
/// </summary>
[Range(3, int.MaxValue)]
public uint SubscriptionLifetimeCount { get; init; } = 1000;
/// <summary>Max notifications per Publish response; <c>0</c> (default) = server decides.</summary>
public uint SubscriptionMaxNotificationsPerPublish { get; init; }
/// <summary>Relative subscription priority 0-255 (default 0).</summary>
[Range(0, 255)]
public byte SubscriptionPriority { get; init; }
// ---- #275 ----
/// <summary>
/// When <c>true</c> (default) the driver reads the remote server's
/// <c>ServerCapabilities/OperationLimits</c> after connect and splits Read / Write / CreateMonitoredItems
/// requests that exceed them (a declared limit of 0 = unlimited). Set <c>false</c> to send every request
/// whole and let the server answer <c>BadTooManyOperations</c> itself.
/// </summary>
public bool HonorServerOperationLimits { get; init; } = true;
// ---- #277 ----
/// <summary>Certificate-revocation / issuer-store settings. Defaults preserve the SDK behaviour.</summary>
@@ -241,11 +228,19 @@ public sealed record OpcUaClientDriverOptions
/// Optional discovery endpoint (a Local Discovery Server or the server itself), e.g.
/// <c>opc.tcp://lds.internal:4840</c>. When set, <c>InitializeAsync</c> calls <c>FindServers</c> and
/// <c>GetEndpoints</c> on it and tries the discovered URLs that match <see cref="SecurityPolicy"/> +
/// <see cref="SecurityMode"/> <i>before</i> the statically configured <see cref="EndpointUrls"/> /
/// <see cref="EndpointUrl"/>. A discovery failure is non-fatal: the static list is used. Discovery
/// <see cref="SecurityMode"/> <i>after</i> the statically configured <see cref="EndpointUrls"/> /
/// <see cref="EndpointUrl"/> (pin the server with <see cref="DiscoveryServerUri"/>). A discovery failure is non-fatal: the static list is used. Discovery
/// itself always runs over an unsecured channel, as the spec requires. <c>null</c> (default) = off.
/// </summary>
public string? DiscoveryUrl { get; init; }
/// <summary>
/// ApplicationUri of the one server discovery may resolve to. An LDS usually registers several servers;
/// with this set only that server's endpoints are used. When unset and discovery finds more than one
/// server, connect fails with an error listing their ApplicationUris. Discovered endpoints are always
/// tried AFTER the configured <see cref="EndpointUrls"/>.
/// </summary>
public string? DiscoveryServerUri { get; init; }
}
/// <summary>