docs(xmldoc): fill missing XML docs + strip tracking-ID comments across src
v2-ci / build (push) Failing after 41s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
v2-ci / build (push) Failing after 41s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
Adds <summary>/<param>/<returns>/<inheritdoc> where missing and removes project bookkeeping IDs (task/tracking refs) from shipped code comments, so the docs read cleanly and CommentChecker is quiet except for known false positives (PLC/protocol terms, event/IEqualityComparer inheritdoc). Doc/comment-only; no logic changed; solution builds clean.
This commit is contained in:
@@ -6,10 +6,6 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
/// shape so the node-manager can pass through quality, source timestamp, and
|
||||
/// server timestamp without translation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Per <c>docs/v2/plan.md</c> decision #13 — every driver maps to the same
|
||||
/// OPC UA StatusCode space; this DTO is the universal carrier.
|
||||
/// </remarks>
|
||||
/// <param name="Value">The raw value; null when <see cref="StatusCode"/> indicates Bad.</param>
|
||||
/// <param name="StatusCode">OPC UA status code (numeric value matches the OPC UA spec).</param>
|
||||
/// <param name="SourceTimestampUtc">Driver-side timestamp when the value was sampled at the source. Null if unavailable.</param>
|
||||
|
||||
@@ -27,14 +27,14 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
/// </param>
|
||||
/// <param name="WriteIdempotent">
|
||||
/// True when a timed-out or failed write to this attribute is safe to replay. Per
|
||||
/// <c>docs/v2/plan.md</c> decisions #44, #45, #143 — writes are NOT auto-retried by default
|
||||
/// <c>docs/v2/plan.md</c>, writes are NOT auto-retried by default
|
||||
/// because replaying a pulse / alarm-ack / counter-increment / recipe-step advance can
|
||||
/// duplicate field actions. Drivers flag only tags whose semantics make retry safe
|
||||
/// (holding registers with level-set values, set-point writes to analog tags) — the
|
||||
/// capability invoker respects this flag when deciding whether to apply Polly retry.
|
||||
/// </param>
|
||||
/// <param name="Source">
|
||||
/// Per ADR-002 — discriminates which runtime subsystem owns this node's dispatch.
|
||||
/// Discriminates which runtime subsystem owns this node's dispatch.
|
||||
/// Defaults to <see cref="NodeSourceKind.Driver"/> so existing callers are unchanged.
|
||||
/// </param>
|
||||
/// <param name="VirtualTagId">
|
||||
@@ -59,7 +59,7 @@ public sealed record DriverAttributeInfo(
|
||||
string? ScriptedAlarmId = null);
|
||||
|
||||
/// <summary>
|
||||
/// Per ADR-002 — discriminates which runtime subsystem owns this node's Read/Write/
|
||||
/// Discriminates which runtime subsystem owns this node's Read/Write/
|
||||
/// Subscribe dispatch. <c>Driver</c> = a real IDriver capability surface;
|
||||
/// <c>Virtual</c> = a Phase 7 <see cref="DriverAttributeInfo"/>.VirtualTagId'd tag
|
||||
/// computed by the VirtualTagEngine; <c>ScriptedAlarm</c> = a scripted Part 9 alarm
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
/// <see cref="IAlarmSource"/>, <see cref="IHistoryProvider"/>).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Per <c>docs/v2/plan.md</c> decision #143 (per-capability retry policy): Read / HistoryRead /
|
||||
/// Per the per-capability retry policy in <c>docs/v2/plan.md</c>: Read / HistoryRead /
|
||||
/// Discover / Probe / AlarmSubscribe auto-retry; <see cref="Write"/> does NOT retry unless the
|
||||
/// tag-definition carries <see cref="WriteIdempotentAttribute"/>. Alarm-acknowledge is treated
|
||||
/// as a write for retry semantics (an alarm-ack is not idempotent at the plant-floor acknowledgement
|
||||
@@ -34,7 +34,7 @@ public enum DriverCapability
|
||||
/// <summary><see cref="IAlarmSource.SubscribeAlarmsAsync"/>. Retries by default.</summary>
|
||||
AlarmSubscribe,
|
||||
|
||||
/// <summary><see cref="IAlarmSource.AcknowledgeAsync"/>. Does NOT retry — ack is a write-shaped operation (decision #143).</summary>
|
||||
/// <summary><see cref="IAlarmSource.AcknowledgeAsync"/>. Does NOT retry — ack is a write-shaped operation.</summary>
|
||||
AlarmAcknowledge,
|
||||
|
||||
/// <summary><see cref="IHistoryProvider"/> reads (Raw/Processed/AtTime/Events). Retries by default.</summary>
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
/// failure (useful for diagnostics), and <see cref="DriverState.Degraded"/> /
|
||||
/// <see cref="DriverState.Reconnecting"/> / <see cref="DriverState.Faulted"/> states may all
|
||||
/// carry a non-null message. Callers must not key behaviour on the LastError-null ↔ Healthy
|
||||
/// pairing (Core.Abstractions-008).
|
||||
/// pairing.
|
||||
/// </param>
|
||||
public sealed record DriverHealth(
|
||||
DriverState State,
|
||||
|
||||
@@ -6,8 +6,6 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
/// supervision with process-level recycle is in play.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Per <c>docs/v2/driver-stability.md</c> §2-4 and <c>docs/v2/plan.md</c> decisions #63-74.
|
||||
///
|
||||
/// <list type="bullet">
|
||||
/// <item><b>A</b> — managed, known-good SDK; low blast radius. In-process. Fast retries.
|
||||
/// Examples: OPC UA Client (OPCFoundation stack), S7 (S7NetPlus).</item>
|
||||
@@ -18,7 +16,7 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
/// </list>
|
||||
///
|
||||
/// <para>Process-kill protections (<c>MemoryRecycle</c>, <c>ScheduledRecycleScheduler</c>) are
|
||||
/// Tier C only per decisions #73-74 and #145 — killing an in-process Tier A/B driver also kills
|
||||
/// Tier C only — killing an in-process Tier A/B driver also kills
|
||||
/// every OPC UA session and every co-hosted driver, blast-radius worse than the leak.</para>
|
||||
/// </remarks>
|
||||
public enum DriverTier
|
||||
|
||||
@@ -6,9 +6,9 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
/// the registry to validate <c>DriverInstance.DriverType</c> values from the central config DB.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Per <c>docs/v2/plan.md</c> decisions #91 (JSON content validation in Admin app, not SQL CLR)
|
||||
/// and #111 (driver type → namespace kind mapping enforced by sp_ValidateDraft).
|
||||
/// The registry is the source of truth for both checks.
|
||||
/// Per <c>docs/v2/plan.md</c> decisions on JSON content validation happening in the Admin app
|
||||
/// (not SQL CLR), and on driver type → namespace kind mapping being enforced by
|
||||
/// <c>sp_ValidateDraft</c>. The registry is the source of truth for both checks.
|
||||
///
|
||||
/// Thread-safety: registration is typically single-threaded at startup; lookups happen on
|
||||
/// every config-apply (multi-threaded). The check-then-act inside <see cref="Register"/> is
|
||||
@@ -28,7 +28,7 @@ public sealed class DriverTypeRegistry
|
||||
/// <remarks>
|
||||
/// The check-then-act (duplicate check → copy-on-write rebuild → swap) is performed under
|
||||
/// <see cref="_writeLock"/> so concurrent <see cref="Register"/> calls cannot silently
|
||||
/// discard each other's registrations — see Core.Abstractions-004.
|
||||
/// discard each other's registrations.
|
||||
/// </remarks>
|
||||
/// <param name="metadata">The driver type metadata to register.</param>
|
||||
public void Register(DriverTypeMetadata metadata)
|
||||
@@ -55,6 +55,7 @@ public sealed class DriverTypeRegistry
|
||||
|
||||
/// <summary>Look up a driver type by name. Throws if unknown.</summary>
|
||||
/// <param name="driverType">The driver type name to look up.</param>
|
||||
/// <returns>The registered metadata for the driver type.</returns>
|
||||
public DriverTypeMetadata Get(string driverType)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(driverType);
|
||||
@@ -69,6 +70,7 @@ public sealed class DriverTypeRegistry
|
||||
|
||||
/// <summary>Try to look up a driver type by name. Returns null if unknown (no exception).</summary>
|
||||
/// <param name="driverType">The driver type name to look up.</param>
|
||||
/// <returns>The registered metadata, or <see langword="null"/> if the type is not registered.</returns>
|
||||
public DriverTypeMetadata? TryGet(string driverType)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(driverType);
|
||||
@@ -76,6 +78,7 @@ public sealed class DriverTypeRegistry
|
||||
}
|
||||
|
||||
/// <summary>Snapshot of all registered driver types.</summary>
|
||||
/// <returns>A snapshot collection of all registered driver type metadata.</returns>
|
||||
public IReadOnlyCollection<DriverTypeMetadata> All() => _types.Values.ToList();
|
||||
}
|
||||
|
||||
@@ -86,8 +89,8 @@ public sealed class DriverTypeRegistry
|
||||
/// <param name="DeviceConfigJsonSchema">JSON Schema for <c>DeviceConfig</c> (multi-device drivers); null if the driver has no device layer.</param>
|
||||
/// <param name="TagConfigJsonSchema">JSON Schema for <c>TagConfig</c>; required for every driver since every driver has tags.</param>
|
||||
/// <param name="Tier">
|
||||
/// Stability tier per <c>docs/v2/driver-stability.md</c> §2-4 and <c>docs/v2/plan.md</c>
|
||||
/// decisions #63-74. Drives the shared resilience pipeline defaults
|
||||
/// Stability tier per <c>docs/v2/driver-stability.md</c> §2-4 and the tiering decisions in
|
||||
/// <c>docs/v2/plan.md</c>. Drives the shared resilience pipeline defaults
|
||||
/// (<see cref="Tier"/> × capability → <c>CapabilityPolicy</c>), the <c>MemoryTracking</c>
|
||||
/// hybrid-formula constants, and whether process-level <c>MemoryRecycle</c> / scheduled-
|
||||
/// recycle protections apply (Tier C only). Every registered driver type must declare one.
|
||||
@@ -100,7 +103,7 @@ public sealed record DriverTypeMetadata(
|
||||
string TagConfigJsonSchema,
|
||||
DriverTier Tier);
|
||||
|
||||
/// <summary>Bitmask of namespace kinds a driver type may populate. Per decision #111.</summary>
|
||||
/// <summary>Bitmask of namespace kinds a driver type may populate.</summary>
|
||||
[Flags]
|
||||
public enum NamespaceKindCompatibility
|
||||
{
|
||||
|
||||
@@ -16,6 +16,7 @@ public sealed class EquipmentTagRefResolver<TDef> where TDef : class
|
||||
private readonly Func<string, TDef?> _parseRef;
|
||||
private readonly ConcurrentDictionary<string, TDef?> _cache = new(StringComparer.Ordinal);
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="EquipmentTagRefResolver{TDef}"/> class.</summary>
|
||||
/// <param name="byName">Authored tag-table lookup (returns null on miss).</param>
|
||||
/// <param name="parseRef">Parses an equipment-tag reference (TagConfig JSON) into a transient def, or null.</param>
|
||||
public EquipmentTagRefResolver(Func<string, TDef?> byName, Func<string, TDef?> parseRef)
|
||||
|
||||
@@ -28,6 +28,7 @@ public interface IHistorianDataSource : IDisposable
|
||||
/// <param name="endUtc">The end of the time range in UTC.</param>
|
||||
/// <param name="maxValuesPerNode">The maximum number of values to return per node.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>The raw historical samples for the tag over the requested range.</returns>
|
||||
Task<HistoryReadResult> ReadRawAsync(
|
||||
string fullReference,
|
||||
DateTime startUtc,
|
||||
@@ -46,6 +47,7 @@ public interface IHistorianDataSource : IDisposable
|
||||
/// <param name="interval">The interval for bucketing samples.</param>
|
||||
/// <param name="aggregate">The aggregation function to apply to each bucket.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>One aggregated sample per interval bucket over the requested range.</returns>
|
||||
Task<HistoryReadResult> ReadProcessedAsync(
|
||||
string fullReference,
|
||||
DateTime startUtc,
|
||||
@@ -63,6 +65,7 @@ public interface IHistorianDataSource : IDisposable
|
||||
/// <param name="fullReference">The full reference of the tag to read.</param>
|
||||
/// <param name="timestampsUtc">The list of timestamps to read values at.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>One sample per requested timestamp, in the same order as <paramref name="timestampsUtc"/>.</returns>
|
||||
Task<HistoryReadResult> ReadAtTimeAsync(
|
||||
string fullReference,
|
||||
IReadOnlyList<DateTime> timestampsUtc,
|
||||
@@ -77,7 +80,7 @@ public interface IHistorianDataSource : IDisposable
|
||||
/// Note on parameter types — <paramref name="maxEvents"/> is <see cref="int"/> (not
|
||||
/// <see cref="uint"/>) so callers can pass <c>0</c> or a negative value as a "use the
|
||||
/// backend's default cap" sentinel; see <c>WonderwareHistorianClient</c> /
|
||||
/// <c>HistorianDataSource</c> and Core.Abstractions-006 for the rationale. The sibling
|
||||
/// <c>HistorianDataSource</c> for the rationale. The sibling
|
||||
/// <see cref="ReadRawAsync"/> / <see cref="ReadProcessedAsync"/> use
|
||||
/// <c>uint maxValuesPerNode</c> because their OPC UA HistoryRead surface has no
|
||||
/// equivalent "use default" sentinel.
|
||||
@@ -85,8 +88,7 @@ public interface IHistorianDataSource : IDisposable
|
||||
/// This surface declares <see cref="ReadAtTimeAsync"/> and <see cref="ReadEventsAsync"/>
|
||||
/// as required members — a server-side historian owns the full read surface, unlike
|
||||
/// <see cref="IHistoryProvider"/> where the same two methods are optional default-impl
|
||||
/// methods so legacy drivers can stay raw-only. The asymmetry is intentional
|
||||
/// (Core.Abstractions-008).
|
||||
/// methods so legacy drivers can stay raw-only. The asymmetry is intentional.
|
||||
/// </remarks>
|
||||
/// <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>
|
||||
@@ -96,9 +98,10 @@ public interface IHistorianDataSource : IDisposable
|
||||
/// 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.)
|
||||
/// matching events were returned.
|
||||
/// </param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>The matching historical event records, with a continuation point if the result was truncated.</returns>
|
||||
Task<HistoricalEventsResult> ReadEventsAsync(
|
||||
string? sourceName,
|
||||
DateTime startUtc,
|
||||
@@ -110,5 +113,6 @@ public interface IHistorianDataSource : IDisposable
|
||||
/// Point-in-time health snapshot for diagnostics and dashboards. Pure
|
||||
/// observation; never blocks on backend I/O.
|
||||
/// </summary>
|
||||
/// <returns>The current health snapshot for the historian backend.</returns>
|
||||
HistorianHealthSnapshot GetHealthSnapshot();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ public interface IHistorizationOutbox : IDisposable
|
||||
/// <summary>Appends <paramref name="entry"/> to the tail of the durable buffer.</summary>
|
||||
/// <param name="entry">The value record to buffer.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
ValueTask AppendAsync(HistorizationOutboxEntry entry, CancellationToken ct);
|
||||
|
||||
/// <summary>
|
||||
@@ -24,6 +25,7 @@ public interface IHistorizationOutbox : IDisposable
|
||||
/// </summary>
|
||||
/// <param name="max">Maximum number of entries to return; must be positive.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Up to <paramref name="max"/> oldest un-acked entries in FIFO order.</returns>
|
||||
ValueTask<IReadOnlyList<HistorizationOutboxEntry>> PeekBatchAsync(int max, CancellationToken ct);
|
||||
|
||||
/// <summary>
|
||||
@@ -32,9 +34,11 @@ public interface IHistorizationOutbox : IDisposable
|
||||
/// </summary>
|
||||
/// <param name="id">The <see cref="HistorizationOutboxEntry.Id"/> to ack.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
ValueTask RemoveAsync(Guid id, CancellationToken ct);
|
||||
|
||||
/// <summary>Current number of un-acked entries held in the buffer.</summary>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>The current number of un-acked entries in the buffer.</returns>
|
||||
ValueTask<int> CountAsync(CancellationToken ct);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
/// <para>
|
||||
/// A process-wide singleton via <see cref="Instance"/> (private ctor): it carries no state
|
||||
/// and is immutable, so one shared instance is safe to assign as the node-manager's
|
||||
/// <c>HistorianDataSource</c> default until the Host wires a real source post-start (Task 5).
|
||||
/// <c>HistorianDataSource</c> default until the Host wires a real source post-start.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public sealed class NullHistorianDataSource : IHistorianDataSource
|
||||
@@ -55,10 +55,7 @@ public sealed class NullHistorianDataSource : IHistorianDataSource
|
||||
int maxEvents,
|
||||
CancellationToken cancellationToken) => Task.FromResult(EmptyEvents);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a fully-disabled snapshot — no connections open, every counter zero, every nullable
|
||||
/// field null, no cluster nodes. Pure; never blocks.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public HistorianHealthSnapshot GetHealthSnapshot() => new(
|
||||
TotalQueries: 0,
|
||||
TotalSuccesses: 0,
|
||||
|
||||
@@ -6,9 +6,9 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
/// as it discovers nodes — no buffering of the whole tree.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Per <c>docs/v2/plan.md</c> decision #52 — drivers register nodes via this builder
|
||||
/// rather than returning a tree object. Supports incremental / large address spaces
|
||||
/// without forcing the driver to buffer the whole tree.
|
||||
/// Drivers register nodes via this builder rather than returning a tree object,
|
||||
/// supporting incremental / large address spaces without forcing the driver to
|
||||
/// buffer the whole tree.
|
||||
/// </remarks>
|
||||
public interface IAddressSpaceBuilder
|
||||
{
|
||||
@@ -18,6 +18,7 @@ public interface IAddressSpaceBuilder
|
||||
/// </summary>
|
||||
/// <param name="browseName">OPC UA browse name (the segment of the path under the parent).</param>
|
||||
/// <param name="displayName">Human-readable display name. May equal <paramref name="browseName"/>.</param>
|
||||
/// <returns>A child builder scoped to inside the new folder.</returns>
|
||||
IAddressSpaceBuilder Folder(string browseName, string displayName);
|
||||
|
||||
/// <summary>
|
||||
@@ -27,6 +28,7 @@ public interface IAddressSpaceBuilder
|
||||
/// <param name="browseName">OPC UA browse name (the segment of the path under the parent folder).</param>
|
||||
/// <param name="displayName">Human-readable display name. May equal <paramref name="browseName"/>.</param>
|
||||
/// <param name="attributeInfo">Driver-side metadata for the variable.</param>
|
||||
/// <returns>A handle for the registered variable, used by Core for subscription routing.</returns>
|
||||
IVariableHandle Variable(string browseName, string displayName, DriverAttributeInfo attributeInfo);
|
||||
|
||||
/// <summary>
|
||||
@@ -56,6 +58,7 @@ public interface IVariableHandle
|
||||
/// <c>Acknowledge</c>, <c>Deactivate</c>).
|
||||
/// </summary>
|
||||
/// <param name="info">The alarm condition information.</param>
|
||||
/// <returns>The sink that receives lifecycle transitions for this alarm condition.</returns>
|
||||
IAlarmConditionSink MarkAsAlarmCondition(AlarmConditionInfo info);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ public interface IAlarmSource
|
||||
/// </summary>
|
||||
/// <param name="sourceNodeIds">The driver node IDs to subscribe to.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>A handle identifying the created subscription.</returns>
|
||||
Task<IAlarmSubscriptionHandle> SubscribeAlarmsAsync(
|
||||
IReadOnlyList<string> sourceNodeIds,
|
||||
CancellationToken cancellationToken);
|
||||
@@ -20,11 +21,13 @@ public interface IAlarmSource
|
||||
/// <summary>Cancel an alarm subscription returned by <see cref="SubscribeAlarmsAsync"/>.</summary>
|
||||
/// <param name="handle">The subscription handle returned from <see cref="SubscribeAlarmsAsync"/>.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
Task UnsubscribeAlarmsAsync(IAlarmSubscriptionHandle handle, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>Acknowledge one or more active alarms by source node ID + condition ID.</summary>
|
||||
/// <param name="acknowledgements">The batch of alarm acknowledgement requests.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
Task AcknowledgeAsync(
|
||||
IReadOnlyList<AlarmAcknowledgeRequest> acknowledgements,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
@@ -8,10 +8,6 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
/// <see cref="IHostConnectivityProbe"/>) are composable — a driver implements only what its
|
||||
/// backend actually supports.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Per <c>docs/v2/plan.md</c> decisions #4 (composable capability interfaces) and #53
|
||||
/// (capability discovery via <c>is</c> checks — no redundant flag enum).
|
||||
/// </remarks>
|
||||
public interface IDriver
|
||||
{
|
||||
/// <summary>Stable logical ID of this driver instance, sourced from the central config DB.</summary>
|
||||
@@ -23,6 +19,7 @@ public interface IDriver
|
||||
/// <summary>Initialize the driver from its <c>DriverConfig</c> JSON; open connections; prepare for first use.</summary>
|
||||
/// <param name="driverConfigJson">The driver configuration as JSON.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
Task InitializeAsync(string driverConfigJson, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
@@ -37,13 +34,16 @@ public interface IDriver
|
||||
/// </remarks>
|
||||
/// <param name="driverConfigJson">The driver configuration as JSON.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
Task ReinitializeAsync(string driverConfigJson, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>Stop the driver, close connections, release resources. Called on shutdown or driver removal.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
Task ShutdownAsync(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>Current health snapshot, polled by Core for the status dashboard and ServiceLevel.</summary>
|
||||
/// <returns>The driver's current health snapshot.</returns>
|
||||
DriverHealth GetHealth();
|
||||
|
||||
/// <summary>
|
||||
@@ -56,6 +56,7 @@ public interface IDriver
|
||||
/// allocation tracking". Tier C drivers (process-isolated) report through the same
|
||||
/// interface but the cache-flush is internal to their host.
|
||||
/// </remarks>
|
||||
/// <returns>The approximate memory footprint in bytes.</returns>
|
||||
long GetMemoryFootprint();
|
||||
|
||||
/// <summary>
|
||||
@@ -63,5 +64,6 @@ public interface IDriver
|
||||
/// Required-for-correctness state must NOT be flushed.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
Task FlushOptionalCachesAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
/// JSON editor with schema-driven validation against the registered JSON schema.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Per <c>docs/v2/plan.md</c> decision #27 — driver-specific config editors are deferred
|
||||
/// Driver-specific config editors are deferred
|
||||
/// to each driver's implementation phase; v2.0 ships with the generic JSON editor as the
|
||||
/// default. This interface is the future plug-point so phase-specific editors can land
|
||||
/// incrementally.
|
||||
|
||||
@@ -34,12 +34,8 @@ public sealed class NullDriverFactory : IDriverFactory
|
||||
public static readonly NullDriverFactory Instance = new();
|
||||
private NullDriverFactory() { }
|
||||
|
||||
/// <summary>Creates a driver (always returns null in this null implementation).</summary>
|
||||
/// <param name="driverType">The driver type name.</param>
|
||||
/// <param name="driverInstanceId">The driver instance identifier.</param>
|
||||
/// <param name="driverConfigJson">The driver configuration as a JSON string.</param>
|
||||
/// <returns>Always returns null.</returns>
|
||||
/// <inheritdoc />
|
||||
public IDriver? TryCreate(string driverType, string driverInstanceId, string driverConfigJson) => null;
|
||||
/// <summary>Gets the collection of supported driver types (empty in this null implementation).</summary>
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyCollection<string> SupportedTypes { get; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@ public interface IDriverHealthPublisher
|
||||
/// Publishes a health snapshot for one driver instance. Implementations must be
|
||||
/// non-blocking and tolerant of being called from any thread.
|
||||
/// </summary>
|
||||
/// <param name="clusterId">The cluster the driver instance belongs to.</param>
|
||||
/// <param name="driverInstanceId">The driver instance the snapshot describes.</param>
|
||||
/// <param name="health">The current health snapshot.</param>
|
||||
/// <param name="errorCount5Min">The number of errors observed in the trailing 5-minute window.</param>
|
||||
void Publish(
|
||||
string clusterId,
|
||||
string driverInstanceId,
|
||||
|
||||
@@ -17,6 +17,10 @@ public interface IDriverProbe
|
||||
/// timeout cancellation. Never throw on connection failure; instead return a result
|
||||
/// with <c>Ok = false</c> + a message.
|
||||
/// </summary>
|
||||
/// <param name="configJson">The driver-specific configuration JSON to probe with.</param>
|
||||
/// <param name="timeout">The maximum time to allow the probe to run.</param>
|
||||
/// <param name="ct">Cancellation token for the operation.</param>
|
||||
/// <returns>The probe outcome, including success/failure and latency.</returns>
|
||||
Task<DriverProbeResult> ProbeAsync(string configJson, TimeSpan timeout, CancellationToken ct);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
/// hard fault is detected (memory breach, wedge, scheduled recycle window).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Per <c>docs/v2/plan.md</c> decisions #68, #73-74, and #145. Tier A/B drivers do NOT have
|
||||
/// a supervisor because they run in-process — recycling would kill every OPC UA session and
|
||||
/// every co-hosted driver. The Core.Stability layer only invokes this interface for Tier C
|
||||
/// instances after asserting the tier via <see cref="DriverTypeMetadata.Tier"/>.
|
||||
/// Tier A/B drivers do NOT have a supervisor because they run in-process — recycling would
|
||||
/// kill every OPC UA session and every co-hosted driver. The Core.Stability layer only invokes
|
||||
/// this interface for Tier C instances after asserting the tier via
|
||||
/// <see cref="DriverTypeMetadata.Tier"/>.
|
||||
/// </remarks>
|
||||
public interface IDriverSupervisor
|
||||
{
|
||||
@@ -22,5 +22,6 @@ public interface IDriverSupervisor
|
||||
/// </summary>
|
||||
/// <param name="reason">Human-readable reason — flows into the supervisor's logs.</param>
|
||||
/// <param name="cancellationToken">Cancels the recycle request; an in-flight restart is not interrupted.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
Task RecycleAsync(string reason, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
/// a raw-only driver compiles without forcing it to provide at-time / event surfaces it
|
||||
/// has no backend for. The sibling server-side surface, <see cref="IHistorianDataSource"/>,
|
||||
/// declares both methods as required because a registered historian owns the full read
|
||||
/// surface; the asymmetry is intentional (Core.Abstractions-008).
|
||||
/// surface; the asymmetry is intentional.
|
||||
/// </remarks>
|
||||
public interface IHistoryProvider
|
||||
{
|
||||
@@ -91,7 +91,7 @@ public interface IHistoryProvider
|
||||
/// reads use for <c>maxValuesPerNode</c>) because callers and downstream historian
|
||||
/// adapters historically treat <c>maxEvents <= 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.
|
||||
/// <c>HistorianDataSource</c>). The asymmetry is intentional.
|
||||
///
|
||||
/// <b>Continuation contract when using the sentinel:</b> When <c>maxEvents <= 0</c>
|
||||
/// the backend applies its own cap. If the backend's cap truncates the result
|
||||
@@ -99,9 +99,9 @@ public interface IHistoryProvider
|
||||
/// <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>
|
||||
/// <returns>A task that returns the historical events result containing matching event records and optional continuation point.</returns>
|
||||
/// <remarks>
|
||||
/// Default implementation throws. Drivers whose backend can serve historical events
|
||||
/// override: Galaxy (Wonderware Alarm & Events log) and the OPC UA Client driver
|
||||
|
||||
@@ -16,6 +16,7 @@ public interface IHostConnectivityProbe
|
||||
/// Snapshot of host-level connectivity. The Core uses this to drive Bad-quality
|
||||
/// fan-out scoped to the affected host's subtree (not the whole driver namespace).
|
||||
/// </summary>
|
||||
/// <returns>The current connectivity status of each known host.</returns>
|
||||
IReadOnlyList<HostConnectivityStatus> GetHostStatuses();
|
||||
|
||||
/// <summary>Fired when a host transitions Running ↔ Stopped (or similar lifecycle change).</summary>
|
||||
|
||||
@@ -9,8 +9,8 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Multi-host drivers (Modbus with N PLCs, hypothetical AB CIP across a rack, etc.)
|
||||
/// implement this so the Phase 6.1 resilience pipeline can be keyed on
|
||||
/// <c>(DriverInstanceId, ResolvedHostName, DriverCapability)</c> per decision #144. One
|
||||
/// implement this so the resilience pipeline can be keyed on
|
||||
/// <c>(DriverInstanceId, ResolvedHostName, DriverCapability)</c>. One
|
||||
/// dead PLC behind a multi-device Modbus driver then trips only its own breaker; healthy
|
||||
/// siblings keep serving.</para>
|
||||
///
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Reads are idempotent — Polly retry pipelines can safely retry on transient failures
|
||||
/// (per <c>docs/v2/plan.md</c> decisions #34 and #44).
|
||||
/// (per <c>docs/v2/plan.md</c>).
|
||||
/// </remarks>
|
||||
public interface IReadable
|
||||
{
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
/// symbol-version-changed) implement this to tell Core when to re-run discovery.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Per <c>docs/v2/plan.md</c> decision #54 — static drivers (Modbus, S7, etc. whose tags
|
||||
/// Per <c>docs/v2/plan.md</c> — static drivers (Modbus, S7, etc. whose tags
|
||||
/// only change via a published config generation) don't implement <c>IRediscoverable</c>.
|
||||
/// The Core just sees absence of the interface and skips change-detection wiring for that driver.
|
||||
/// </remarks>
|
||||
|
||||
@@ -15,7 +15,7 @@ public enum DiscoveryRediscoverPolicy
|
||||
/// <summary>
|
||||
/// Driver capability for discovering tags and hierarchy from the backend.
|
||||
/// Streams discovered nodes into <see cref="IAddressSpaceBuilder"/> rather than
|
||||
/// buffering the entire tree (decision #52 — supports incremental / large address spaces).
|
||||
/// buffering the entire tree (supports incremental / large address spaces).
|
||||
/// </summary>
|
||||
public interface ITagDiscovery
|
||||
{
|
||||
@@ -25,6 +25,7 @@ public interface ITagDiscovery
|
||||
/// </summary>
|
||||
/// <param name="builder">The address space builder to stream discovered nodes into.</param>
|
||||
/// <param name="cancellationToken">A cancellation token for the discovery operation.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
Task DiscoverAsync(IAddressSpaceBuilder builder, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>Post-connect re-discovery policy. Default preserves the original retry-until-stable behavior.</summary>
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
/// historian-only adapter, for example) can omit this.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Per <c>docs/v2/plan.md</c> decisions #44 + #45 — <b>writes are NOT auto-retried by default</b>.
|
||||
/// Per <c>docs/v2/plan.md</c>, <b>writes are NOT auto-retried by default</b>.
|
||||
/// A timeout may fire after the device already accepted the command; replaying non-idempotent
|
||||
/// field actions (pulses, alarm acks, recipe steps, counter increments) can cause duplicate
|
||||
/// operations. Per-tag opt-in via <c>Tag.WriteIdempotent = true</c> in the central config DB
|
||||
@@ -19,6 +19,7 @@ public interface IWritable
|
||||
/// </summary>
|
||||
/// <param name="writes">Pairs of full reference + value to write.</param>
|
||||
/// <param name="cancellationToken">Cancellation token; the driver should abort the batch if cancelled.</param>
|
||||
/// <returns>One <see cref="WriteResult"/> per requested write, in the same order.</returns>
|
||||
Task<IReadOnlyList<WriteResult>> WriteAsync(
|
||||
IReadOnlyList<WriteRequest> writes,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
@@ -2,8 +2,8 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Every OPC UA operation surface the Phase 6.2 authorization evaluator gates, per
|
||||
/// <c>docs/v2/implementation/phase-6-2-authorization-runtime.md</c> §Stream C and
|
||||
/// decision #143. The evaluator maps each operation onto the corresponding
|
||||
/// <c>docs/v2/implementation/phase-6-2-authorization-runtime.md</c> §Stream C.
|
||||
/// The evaluator maps each operation onto the corresponding
|
||||
/// <c>NodePermissions</c> bit(s) to decide whether the calling session is allowed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
|
||||
@@ -41,6 +41,7 @@ public sealed class PollGroupEngine : IAsyncDisposable
|
||||
/// <summary>Default floor for publishing intervals — matches the Modbus 100 ms cap.</summary>
|
||||
public static readonly TimeSpan DefaultMinInterval = TimeSpan.FromMilliseconds(100);
|
||||
|
||||
/// <summary>Constructs the engine with the driver-supplied reader, change callback, and optional interval floor / error sink.</summary>
|
||||
/// <param name="reader">Driver-supplied batch reader; snapshots MUST be returned in the same
|
||||
/// order as the input references.</param>
|
||||
/// <param name="onChange">Callback invoked per changed tag — the driver forwards to its own
|
||||
@@ -49,7 +50,7 @@ public sealed class PollGroupEngine : IAsyncDisposable
|
||||
/// per <see cref="DefaultMinInterval"/>.</param>
|
||||
/// <param name="onError">Optional error sink — invoked once per caught reader exception (or
|
||||
/// internal contract-violation throw) so the owning driver can route the failure to its
|
||||
/// health surface (Core.Abstractions-005). Defensive: an <c>onError</c> handler that
|
||||
/// health surface. Defensive: an <c>onError</c> handler that
|
||||
/// itself throws is silently absorbed so a buggy forwarder cannot crash the poll loop.</param>
|
||||
public PollGroupEngine(
|
||||
Func<IReadOnlyList<string>, CancellationToken, Task<IReadOnlyList<DataValueSnapshot>>> reader,
|
||||
@@ -68,6 +69,7 @@ public sealed class PollGroupEngine : IAsyncDisposable
|
||||
/// <summary>Register a new polled subscription and start its background loop.</summary>
|
||||
/// <param name="fullReferences">The list of tag references to poll.</param>
|
||||
/// <param name="publishingInterval">The desired polling interval; will be clamped to the configured minimum.</param>
|
||||
/// <returns>A handle identifying the new subscription, for later use with <see cref="Unsubscribe"/>.</returns>
|
||||
public ISubscriptionHandle Subscribe(IReadOnlyList<string> fullReferences, TimeSpan publishingInterval)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(fullReferences);
|
||||
@@ -138,7 +140,7 @@ public sealed class PollGroupEngine : IAsyncDisposable
|
||||
catch (Exception ex) when (!IsFatal(ex))
|
||||
{
|
||||
// transient poll error — loop continues, driver health surface logs it
|
||||
// via the supplied onError callback (Core.Abstractions-005).
|
||||
// via the supplied onError callback.
|
||||
ReportError(ex);
|
||||
}
|
||||
}
|
||||
@@ -170,7 +172,7 @@ public sealed class PollGroupEngine : IAsyncDisposable
|
||||
{
|
||||
var snapshots = await _reader(state.TagReferences, ct).ConfigureAwait(false);
|
||||
|
||||
// Core.Abstractions-002: validate the reader contract before indexing. A reader that
|
||||
// Validate the reader contract before indexing. A reader that
|
||||
// returns fewer snapshots than references would silently stall the subscription; surface
|
||||
// the violation immediately with a descriptive exception instead.
|
||||
if (snapshots.Count != state.TagReferences.Count)
|
||||
@@ -207,6 +209,7 @@ public sealed class PollGroupEngine : IAsyncDisposable
|
||||
}
|
||||
|
||||
/// <summary>Cancel every active subscription and await all loop tasks. Idempotent.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
// Cancel all loops first so they can all start winding down in parallel.
|
||||
@@ -253,7 +256,7 @@ public sealed class PollGroupEngine : IAsyncDisposable
|
||||
|
||||
private sealed record PollSubscriptionHandle(long Id) : ISubscriptionHandle
|
||||
{
|
||||
/// <summary>Gets a diagnostic identifier for this subscription.</summary>
|
||||
/// <inheritdoc />
|
||||
public string DiagnosticId => $"poll-sub-{Id}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
/// recipe-step advances can duplicate irreversible field actions.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Per <c>docs/v2/plan.md</c> decisions #44, #45, and #143. Applied to tag-definition POCOs
|
||||
/// Applied to tag-definition POCOs
|
||||
/// (e.g. <c>ModbusTagDefinition</c>, <c>S7TagDefinition</c>, OPC UA client tag rows) at the
|
||||
/// property or record level. The <c>CapabilityInvoker</c> in <c>ZB.MOM.WW.OtOpcUa.Core.Resilience</c>
|
||||
/// reads this attribute via reflection once at driver-init time and caches the result; no
|
||||
|
||||
Reference in New Issue
Block a user