diff --git a/src/Client/ZB.MOM.WW.OtOpcUa.Client.CLI/Commands/ReadCommand.cs b/src/Client/ZB.MOM.WW.OtOpcUa.Client.CLI/Commands/ReadCommand.cs index 4f42413b..f9678a2a 100644 --- a/src/Client/ZB.MOM.WW.OtOpcUa.Client.CLI/Commands/ReadCommand.cs +++ b/src/Client/ZB.MOM.WW.OtOpcUa.Client.CLI/Commands/ReadCommand.cs @@ -56,7 +56,7 @@ public class ReadCommand : CommandBase { // The SDK's Session.ReadValue surfaces a Bad node status (BadNodeIdUnknown, // BadUserAccessDenied, ...) as an exception. A read command's job is to REPORT that status, - // not crash with a stack trace (#577) — session-level failures still propagate. + // not crash with a stack trace — session-level failures still propagate. await console.Output.WriteLineAsync($"Node: {NodeId}"); await console.Output.WriteLineAsync($"Value: "); await console.Output.WriteLineAsync($"Status: {(StatusCode)ex.StatusCode} ({StatusCodes.GetBrowseName(ex.StatusCode)})"); diff --git a/src/Client/ZB.MOM.WW.OtOpcUa.Client.Shared/Adapters/ClientTelemetry.cs b/src/Client/ZB.MOM.WW.OtOpcUa.Client.Shared/Adapters/ClientTelemetry.cs index 3f4fbccf..0e50aa4f 100644 --- a/src/Client/ZB.MOM.WW.OtOpcUa.Client.Shared/Adapters/ClientTelemetry.cs +++ b/src/Client/ZB.MOM.WW.OtOpcUa.Client.Shared/Adapters/ClientTelemetry.cs @@ -24,6 +24,7 @@ internal static class DisposeBridge private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(5); /// Runs to completion (or the timeout), swallowing every failure. + /// The asynchronous work to run; it receives a token that is cancelled when the timeout elapses. public static void Run(Func work) { using var cts = new CancellationTokenSource(Timeout); diff --git a/src/Client/ZB.MOM.WW.OtOpcUa.Client.Shared/OpcUaClientService.cs b/src/Client/ZB.MOM.WW.OtOpcUa.Client.Shared/OpcUaClientService.cs index a2350a89..c1d87129 100644 --- a/src/Client/ZB.MOM.WW.OtOpcUa.Client.Shared/OpcUaClientService.cs +++ b/src/Client/ZB.MOM.WW.OtOpcUa.Client.Shared/OpcUaClientService.cs @@ -462,7 +462,7 @@ public sealed class OpcUaClientService : IOpcUaClientService ThrowIfDisposed(); ThrowIfNotConnected(); - // The server's ShelvingState child is SDK-created with a numeric namespace-0 id, so a synthesized + // The server's ShelvingState child is SDK-created with a numeric id in namespace 0, so a synthesized // ".ShelvingState" node id never exists. The shelve methods are called on the condition // object itself with the AlarmConditionType_ShelvingState_* method ids, which the server dispatches // to the condition's ShelvingState methods. @@ -884,6 +884,8 @@ public sealed class OpcUaClientService : IOpcUaClientService catch { return false; } } + /// Builds the event filter (select clauses for the alarm and condition fields) used by alarm subscriptions. + /// The populated . internal static EventFilter CreateAlarmEventFilter() { var filter = new EventFilter(); diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/AkkaClusterOptions.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/AkkaClusterOptions.cs index d1dc6cb5..2aa53426 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/AkkaClusterOptions.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/AkkaClusterOptions.cs @@ -58,7 +58,7 @@ public sealed class AkkaClusterOptions /// . That fallback was /// described here — and relied upon in the field — for a year before it was implemented; a /// node configured only through the env var joined the cluster carrying no roles at all. - /// See Gitea #532 and docs/deployments/wonder-app-vd03-makino-z-34184.md. + /// See docs/deployments/wonder-app-vd03-makino-z-34184.md. /// /// /// The two sources normalize differently: values bound from Cluster:Roles are taken diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/AkkaClusterOptionsValidator.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/AkkaClusterOptionsValidator.cs index f68302eb..f622d9b1 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/AkkaClusterOptionsValidator.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/AkkaClusterOptionsValidator.cs @@ -44,7 +44,7 @@ public sealed class AkkaClusterOptionsValidator : OptionsValidatorBaseAddValidatedOptions (a plain AddSingleton), so constructor /// dependencies are safe here. Both and /// are optional (default ) so a caller - /// with neither to hand — including every test in this file that pre-dates R3-04 — keeps + /// with neither to hand — including every test in this file that pre-dates the post-overlay guard — keeps /// constructing this validator parameterlessly; the post-overlay guard below (choice 4) is /// simply skipped when either is null. /// @@ -116,7 +116,7 @@ public sealed class AkkaClusterOptionsValidator : OptionsValidatorBase - /// R3-04 (choice 4, the residual divergence surface): Program.cs resolves + /// Post-overlay guard (choice 4, the residual divergence surface): Program.cs resolves /// BEFORE the per-role appsettings overlay loads (the overlay's /// filename depends on the answer), so if a role overlay — or a ${secret:} expansion — /// ever set Cluster:Roles itself, that value would silently disagree with the diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/ClusterBootstrapCoordinator.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/ClusterBootstrapCoordinator.cs index cfd0754d..9df34be7 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/ClusterBootstrapCoordinator.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/ClusterBootstrapCoordinator.cs @@ -46,7 +46,9 @@ public sealed class ClusterBootstrapCoordinator : IHostedService _log = log ?? throw new ArgumentNullException(nameof(log)); } - /// + /// Starts the bootstrap join in the background when the bootstrap guard is enabled. + /// Not used; the join runs under the coordinator's own cancellation source. + /// A completed task; the join itself continues asynchronously. public Task StartAsync(CancellationToken cancellationToken) { if (!_options.BootstrapGuard.Enabled) @@ -59,7 +61,9 @@ public sealed class ClusterBootstrapCoordinator : IHostedService return Task.CompletedTask; } - /// + /// Cancels the background join and waits for it to finish. + /// Not used; shutdown is driven by the coordinator's own cancellation source. + /// A task that represents the asynchronous operation. public async Task StopAsync(CancellationToken cancellationToken) { await _cts.CancelAsync().ConfigureAwait(false); diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/ClusterBootstrapGuard.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/ClusterBootstrapGuard.cs index 38a69ac1..b84b2afa 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/ClusterBootstrapGuard.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/ClusterBootstrapGuard.cs @@ -63,7 +63,7 @@ public static class ClusterBootstrapGuard /// /// Analyzes the configured seed list to decide this node's bootstrap role. The guard engages only - /// for the Phase-6 pair shape (exactly two seeds, one of them this node); any other shape + /// for the pair shape (exactly two seeds, one of them this node); any other shape /// (single-seed legacy site node, three+ seeds, self absent) returns /// = false and the caller joins the configured seeds unchanged. /// diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/ClusterRoleInfo.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/ClusterRoleInfo.cs index 44833637..9992061a 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/ClusterRoleInfo.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/ClusterRoleInfo.cs @@ -23,7 +23,7 @@ public sealed class ClusterRoleInfo : IClusterRoleInfo, IDisposable private readonly string? _clusterRole; private readonly string? _clusterId; private readonly object _lock = new(); - // R3-04 (01/C-9, census row 17) — the two dictionaries below have DIFFERENT correct comparers, + // The two dictionaries below have DIFFERENT correct comparers, // and that split is deliberate, not an oversight (review finding, 2026-08-20). // // _membersByRole answers "every member carrying (a spelling of) this role" — OrdinalIgnoreCase, @@ -53,7 +53,7 @@ public sealed class ClusterRoleInfo : IClusterRoleInfo, IDisposable // sharing a bind IP) stay distinct. Production hosts have unique DNS names so the port // suffix is harmless redundancy. _localNode = CommonsNodeId.Parse($"{options.Value.PublicHostname}:{options.Value.Port}"); - // OrdinalIgnoreCase (Gitea #532): Cluster:Roles binds verbatim, so a node authored with 'Driver' + // OrdinalIgnoreCase: Cluster:Roles binds verbatim, so a node authored with 'Driver' // used to answer HasRole("driver") false while gossiping a role Akka would still match against // its own ordinal comparison — the node was a driver to the cluster and not to itself. _localRoles = new HashSet(options.Value.Roles, StringComparer.OrdinalIgnoreCase); @@ -128,7 +128,7 @@ public sealed class ClusterRoleInfo : IClusterRoleInfo, IDisposable { lock (_lock) { - // Cluster-002 (review R1 m6): seed only USABLE members. A member already Leaving / Exiting / Down + // Seed only USABLE members. A member already Leaving / Exiting / Down // (or still Joining) at seed time is not one, and a departing member never fires a later // MemberLeft / MemberExited / MemberDowned that HandleMemberEvent would drop it on — it would // linger until MemberRemoved, the very delay the member-event handling exists to avoid. A @@ -165,7 +165,7 @@ public sealed class ClusterRoleInfo : IClusterRoleInfo, IDisposable set.Add(up.Member); } break; - // Cluster-002: a leaving / exiting / downed member is no longer a usable member, so + // A leaving / exiting / downed member is no longer a usable member, so // drop it as soon as that transition lands rather than waiting for MemberRemoved // (several seconds later). MembersWithRole now has a production caller (the // historization scope-peer check), which must not defer to a departing partner. @@ -210,8 +210,8 @@ public sealed class ClusterRoleInfo : IClusterRoleInfo, IDisposable // Record the event's own address. The leader used to be resolved by looking the address up // in Cluster.State.Members, but that snapshot is published asynchronously and can lag the - // event: a miss stored null and nothing re-ran, so RoleLeader(role) stayed null for good - // (Gitea #590). Only the address is ever read, so no Member lookup is needed. + // event: a miss stored null and nothing re-ran, so RoleLeader(role) stayed null for good. + // Only the address is ever read, so no Member lookup is needed. _roleLeaders[evt.Role] = evt.Leader; if (evt.Leader is not null) next = ToNodeId(evt.Leader); diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/ConfigSourceOptionsValidator.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/ConfigSourceOptionsValidator.cs index 165703c1..42294ea9 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/ConfigSourceOptionsValidator.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/ConfigSourceOptionsValidator.cs @@ -21,14 +21,18 @@ public sealed class ConfigSourceOptionsValidator : IValidateOptionsAddValidatedOptions (a plain AddSingleton), so a /// constructor dependency is safe here. This validator's only use of configuration is this /// node's roles, so it takes — the single role authority - /// (archreview R3-04) — directly rather than . + /// — directly rather than . /// + /// The node's effective roles, used to decide which fetch settings are required. public ConfigSourceOptionsValidator(EffectiveRoles effectiveRoles) { _effectiveRoles = effectiveRoles; } - /// + /// Validates the for this node's roles. + /// The options instance name (unused). + /// The options to validate. + /// A success result, or a failure listing every violated rule. public ValidateOptionsResult Validate(string? name, ConfigSourceOptions options) { ArgumentNullException.ThrowIfNull(options); @@ -92,8 +96,8 @@ public sealed class ConfigSourceOptionsValidator : IValidateOptionsTrue when is among , compared case-insensitively. /// The role to test for. + /// when the role is present; otherwise . public bool Has(string role) => Gossiped.Any(r => string.Equals(r, role, StringComparison.OrdinalIgnoreCase)); diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/MeshTransportOptionsValidator.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/MeshTransportOptionsValidator.cs index d631c216..4eca6d84 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/MeshTransportOptionsValidator.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/MeshTransportOptionsValidator.cs @@ -23,7 +23,7 @@ public sealed class MeshTransportOptionsValidator : IValidateOptionsAddValidatedOptions (a plain AddSingleton), so constructor /// dependencies are safe here. is still needed for the /// ConfigServe:GrpcListenPort cross-read (the admin-side obligation below) — - /// is the single role authority (archreview R3-04) this rule reads + /// is the single role authority this rule reads /// roles through instead of re-deriving them. /// /// The application configuration, read for ConfigServe:GrpcListenPort. @@ -34,7 +34,10 @@ public sealed class MeshTransportOptionsValidator : IValidateOptions + /// Validates the for this node's roles. + /// The options instance name (unused). + /// The options to validate. + /// A success result, or a failure listing every violated rule. public ValidateOptionsResult Validate(string? name, MeshTransportOptions options) { ArgumentNullException.ThrowIfNull(options); @@ -71,7 +74,7 @@ public sealed class MeshTransportOptionsValidator : IValidateOptionsReturns true when is a cluster-scoped role — starts with /// and carries a non-empty ClusterId suffix. /// - /// The prefix test is case-insensitive (Gitea #532). It was ordinal, which meant a + /// The prefix test is case-insensitive. It was ordinal, which meant a /// Cluster:Roles entry spelled Cluster-SITE-A — that path binds verbatim, unlike /// the path, which lower-cases — classified as an ordinary role. The node /// then resolved a ClusterId while still gossiping a cluster-scoped role, @@ -33,6 +33,7 @@ public static class RoleParser /// ClusterRoleInfo did not, and got the null. /// /// The role name to test. + /// if the role is a cluster-scoped role. public static bool IsClusterRole(string role) => role is not null && role.StartsWith(ClusterRolePrefix, StringComparison.OrdinalIgnoreCase) @@ -47,9 +48,10 @@ public static class RoleParser /// OTOPCUA_ROLES path (⇒ site-a) — and normalizing one side would silently rename /// the Akka role of a running deployment (docker-dev's rig configures cluster-MAIN / /// cluster-SITE-A verbatim today). Every consumer of a ClusterId therefore compares it - /// case-insensitively; see the identity rule on AclScopeRef (Gitea #533). + /// case-insensitively; see the identity rule on AclScopeRef. /// /// The cluster-scoped role name. + /// The ClusterId portion of the role, with the role prefix removed. public static string ClusterIdFromRole(string role) => role[ClusterRolePrefix.Length..]; /// @@ -72,7 +74,7 @@ public static class RoleParser /// cluster-scoped role (a legacy / single-mesh node). /// /// - /// The single home for a derivation that was open-coded at four call sites before Gitea #532 + /// The single home for a derivation that was open-coded at four call sites before /// (ClusterRoleInfo, the ControlPlane singleton scope, SplitTopologyTransportValidator /// and ConfigDbApiKeyStore) — each with its own view of whether the classification was /// case-sensitive, which is how the mixed-case gap above survived in three of the four. @@ -91,7 +93,7 @@ public static class RoleParser /// /// /// - /// ⚠️ This fallback was documented for a year before it existed (Gitea #532). + /// ⚠️ This fallback was documented for a year before it existed. /// AkkaClusterOptions.Roles' own XML doc and /// both described it; only the validator implemented it, privately. A node configured solely /// through OTOPCUA_ROLES — which drives every bit of Program.cs's conditional @@ -114,7 +116,7 @@ public static class RoleParser /// Either source names an unknown role. /// /// Thin delegation to — - /// the single role authority (archreview R3-04). is the type to + /// the single role authority. is the type to /// reach for directly; this overload is retained so existing callers of the array-returning /// shape keep compiling. /// diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/ServiceCollectionExtensions.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/ServiceCollectionExtensions.cs index 16f9e466..fda0c131 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/ServiceCollectionExtensions.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/ServiceCollectionExtensions.cs @@ -35,7 +35,7 @@ public static class ServiceCollectionExtensions services.AddValidatedOptions( configuration, AkkaClusterOptions.SectionName); - // R3-04 (structural identity, choice 3): EffectiveRoles is resolved ONCE and registered as a + // Structural identity: EffectiveRoles is resolved ONCE and registered as a // singleton, off the SAME IConfiguration this call received — not off a DI-resolved // IConfiguration, which a plain ServiceCollection (as opposed to a WebApplicationBuilder) never // registers. Every consumer (Program.cs's hasAdmin/hasDriver, the four role-scoped validators, @@ -54,7 +54,7 @@ public static class ServiceCollectionExtensions // otherwise; TryAddSingleton is a no-op on a real host, where it is already registered. services.TryAddSingleton(configuration); - // Gitea #532 (superseded by R3-04): apply the OTOPCUA_ROLES fallback the Roles property has + // Apply the OTOPCUA_ROLES fallback the Roles property has // always DOCUMENTED, in the one place every consumer reads through. Before this, a node // configured only through the env var — the knob that drives all of Program.cs's role wiring — // bound an EMPTY Roles array and so joined the cluster role-less: invisible to diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/SplitTopologyTransportValidator.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/SplitTopologyTransportValidator.cs index 4429786b..a37de7be 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/SplitTopologyTransportValidator.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/SplitTopologyTransportValidator.cs @@ -14,7 +14,7 @@ namespace ZB.MOM.WW.OtOpcUa.Cluster; /// Phase 2/3/5 dark switches keep their DPS branches alive, but DPS is mesh-wide — it delivers /// nothing between a central node and a site node that no longer share a gossip ring. The /// marker of the split topology is a cluster-{ClusterId} role -/// (); those roles exist only post-Phase-6. A node that +/// (); those roles exist only in the per-cluster mesh topology. A node that /// carries one must use the mesh-crossing transports, and this validator refuses to /// boot it otherwise — the alternative is a node that runs, listens, and silently delivers no /// commands or telemetry, which has no stack trace and no failing node to point at. @@ -44,16 +44,21 @@ public sealed class SplitTopologyTransportValidator : IValidateOptionsMeshTransport:Mode, Telemetry:Mode, /// TelemetryDial:Mode) — not IClusterRoleInfo, whose implementation needs the /// ActorSystem, which does not exist yet at ValidateOnStart time. - /// is the single role authority (archreview R3-04) — this rule no + /// is the single role authority — this rule no /// longer re-derives the effective, normalized role list itself. /// + /// The host configuration used to cross-read the transport-mode settings. + /// The node's effective, normalized role list. public SplitTopologyTransportValidator(IConfiguration configuration, EffectiveRoles effectiveRoles) { _configuration = configuration; _effectiveRoles = effectiveRoles; } - /// + /// Fails startup when a split-topology node is left on a transport mode that cannot work there. + /// The options instance name; unused. + /// The mesh transport options; unused because every input is cross-read from configuration. + /// A success result, or a failure naming the offending transport setting. public ValidateOptionsResult Validate(string? name, MeshTransportOptions options) { // The options instance is intentionally unused: every value this rule depends on is cross-read diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/TelemetryOptions.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/TelemetryOptions.cs index fef3c386..779228b9 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/TelemetryOptions.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Cluster/TelemetryOptions.cs @@ -93,14 +93,18 @@ public sealed class TelemetryOptionsValidator : IValidateOptionsAddValidatedOptions (a plain AddSingleton), so a /// constructor dependency is safe here. This validator's only use of configuration is this /// node's roles, so it takes — the single role authority - /// (archreview R3-04) — directly rather than . + /// — directly rather than . /// + /// The node's resolved roles, the single role authority. public TelemetryOptionsValidator(EffectiveRoles effectiveRoles) { _effectiveRoles = effectiveRoles; } - /// + /// Validates the telemetry options against this node's roles. + /// The named options instance, or for the default. + /// The options to validate. + /// Success, or a failure listing every invalid setting. public ValidateOptionsResult Validate(string? name, TelemetryOptions options) { ArgumentNullException.ThrowIfNull(options); @@ -117,8 +121,8 @@ public sealed class TelemetryOptionsValidator : IValidateOptions public sealed class TelemetryDialOptionsValidator : IValidateOptions { - /// + /// Validates the telemetry dial options. + /// The named options instance, or for the default. + /// The options to validate. + /// Success, or a failure listing every invalid setting. public ValidateOptionsResult Validate(string? name, TelemetryDialOptions options) { ArgumentNullException.ThrowIfNull(options); diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Browsing/CapturedTreeBrowseSession.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Browsing/CapturedTreeBrowseSession.cs index 0ace043a..248d9ee1 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Browsing/CapturedTreeBrowseSession.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Browsing/CapturedTreeBrowseSession.cs @@ -56,7 +56,8 @@ public sealed class CapturedTreeBrowseSession(CapturedTree tree) : IBrowseSessio return Task.FromResult>(attrs); } - /// + /// Drops this session's reference to the captured tree; the tree itself may be shared. + /// A completed . public ValueTask DisposeAsync() { _tree = null; // drop this session's reference only — tree may be shared (coalescing) @@ -68,7 +69,7 @@ public sealed class CapturedTreeBrowseSession(CapturedTree tree) : IBrowseSessio /// /// Surfaces a leaf's driver-native type name as the dataType address field, so a /// browse-commit can stamp the driver-typed TagConfig with the type the controller - /// actually reported (Gitea #536). + /// actually reported. /// /// The captured attribute. /// The address fields, or null when the driver reported no native type. @@ -80,7 +81,7 @@ public sealed class CapturedTreeBrowseSession(CapturedTree tree) : IBrowseSessio /// reverse map would bind a 16-bit INT as a 32-bit DINT and read four bytes for two. /// /// Null when the driver stamped nothing — the mapper then leaves the model's own default in - /// place, which is exactly the pre-#536 behaviour rather than a guess. + /// place, which is exactly the previous behaviour rather than a guess. /// /// private static IReadOnlyDictionary? NativeTypeFields(DriverAttributeInfo a) diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Browsing/CapturingAddressSpaceBuilder.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Browsing/CapturingAddressSpaceBuilder.cs index ceae69df..ff88850e 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Browsing/CapturingAddressSpaceBuilder.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Browsing/CapturingAddressSpaceBuilder.cs @@ -17,6 +17,8 @@ public sealed class CapturingAddressSpaceBuilder : IAddressSpaceBuilder public bool Truncated; public readonly int NodeCap = nodeCap; public readonly Dictionary ById = new(StringComparer.Ordinal); + /// Reserves a slot for one more node, marking the capture truncated when the cap is reached. + /// when the node may be recorded; when the cap is reached. public bool TryReserve() { if (Count >= NodeCap) { Truncated = true; return false; } @@ -96,7 +98,10 @@ public sealed class CapturingAddressSpaceBuilder : IAddressSpaceBuilder private sealed class CapturedVariableHandle(CapturedNode node) : IVariableHandle { + /// public string FullReference => node.Id; + + /// public IAlarmConditionSink MarkAsAlarmCondition(AlarmConditionInfo info) { node.IsAlarmMarked = true; @@ -107,6 +112,7 @@ public sealed class CapturingAddressSpaceBuilder : IAddressSpaceBuilder private sealed class NoopAlarmSink : IAlarmConditionSink { public static readonly NoopAlarmSink Instance = new(); + /// public void OnTransition(AlarmEventArgs args) { } // browse never delivers live transitions } } diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Browsing/DiscoveryDriverBrowser.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Browsing/DiscoveryDriverBrowser.cs index 6c12e808..acf34e10 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Browsing/DiscoveryDriverBrowser.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Browsing/DiscoveryDriverBrowser.cs @@ -12,7 +12,7 @@ public sealed class DiscoveryDriverBrowser : IUniversalDriverBrowser /// 20 s per-call browse timeout, which only covers serving from memory. public static readonly TimeSpan OpenTimeout = TimeSpan.FromSeconds(60); - /// Bound on the cleanup ShutdownAsync (R2-01: no unbounded waits — a driver wedged + /// Bound on the cleanup ShutdownAsync (no unbounded waits — a driver wedged /// in discovery may be equally wedged in shutdown). public static readonly TimeSpan ShutdownTimeout = TimeSpan.FromSeconds(10); @@ -46,7 +46,11 @@ public sealed class DiscoveryDriverBrowser : IUniversalDriverBrowser public DiscoveryDriverBrowser(IDriverFactory driverFactory, ILogger logger) : this(driverFactory, logger, OpenTimeout, ShutdownTimeout) { } - // Test seam: short timeouts. + /// Test seam: constructor with explicit open and shutdown bounds. + /// Factory used to construct throwaway/capture driver instances. + /// Logger for shutdown-timeout warnings. + /// Upper bound on opening a capture driver and running discovery. + /// Upper bound on the cleanup shutdown of a capture driver. internal DiscoveryDriverBrowser(IDriverFactory driverFactory, ILogger logger, TimeSpan openTimeout, TimeSpan shutdownTimeout) { @@ -111,6 +115,10 @@ public sealed class DiscoveryDriverBrowser : IUniversalDriverBrowser } } + /// Builds the in-flight de-duplication key for a capture request. + /// The driver type name. + /// The driver configuration JSON. + /// A key combining the lower-cased driver type and a hash of the configuration. internal static string CaptureKey(string driverType, string configJson) => driverType.ToLowerInvariant() + ":" + Convert.ToHexString(System.Security.Cryptography.SHA256.HashData( @@ -195,6 +203,10 @@ public sealed class DiscoveryDriverBrowser : IUniversalDriverBrowser } } + /// Applies the driver-specific browse-time patch (if any) to the configuration JSON. + /// The driver type name. + /// The driver configuration JSON. + /// The patched configuration JSON, or the input unchanged when the driver type has no patch. internal static string PatchForBrowse(string driverType, string configJson) { if (!BrowsePatches.TryGetValue(driverType, out var patch)) return configJson; diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Messages/Deploy/ApplyAck.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Messages/Deploy/ApplyAck.cs index 2431bd41..b032d4ea 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Messages/Deploy/ApplyAck.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Messages/Deploy/ApplyAck.cs @@ -13,7 +13,7 @@ public enum ApplyAckOutcome { Applied, Failed } /// Why the apply failed; null on . /// The dispatch's correlation id. /// -/// Gitea #601: when the node applied the deployment (the revision advanced) but +/// when the node applied the deployment (the revision advanced) but /// its OPC UA address-space apply was DEGRADED — swallowed per-node sink faults, a failed rebuild, or no /// outcome reported in time — so the served address space may be partial. Only meaningful with /// . Trailing and defaulted so an ack from a node built before @@ -30,7 +30,7 @@ public sealed record ApplyAck( string? DegradedReason = null) { /// - /// Gitea #601 — prefix of NodeDeploymentState.FailureReason on an Applied row whose + /// The prefix of NodeDeploymentState.FailureReason on an Applied row whose /// address-space apply was degraded. Shared by central (which writes it from the ack) and a node with /// a ConfigDb (which writes its own row), so both spell the row identically. /// diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Messages/Deploy/DeployTimings.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Messages/Deploy/DeployTimings.cs index 1f0866de..fc245f93 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Messages/Deploy/DeployTimings.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Messages/Deploy/DeployTimings.cs @@ -11,11 +11,11 @@ public static class DeployTimings /// (ConfigPublishCoordinator.DefaultApplyDeadline). public static readonly TimeSpan ApplyDeadline = TimeSpan.FromMinutes(2); - /// Gitea #601 — how far inside a node's held Applied ack is released + /// How far inside a node's held Applied ack is released /// when the address-space outcome has not arrived, leaving room for transport latency. public static readonly TimeSpan ApplyOutcomeSafetyMargin = TimeSpan.FromSeconds(15); - /// Gitea #601 — the node's default apply-outcome timeout: − + /// The node's default apply-outcome timeout: − /// (105 s). Long enough that a slow-but-clean rebuild of a large /// address space is not labelled degraded, short enough to ack before central gives up. /// The timer is armed after the artifact read and driver reconcile, so if those alone took diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Messages/Drivers/DriverHealthChanged.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Messages/Drivers/DriverHealthChanged.cs index 7aaea711..c22b012b 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Messages/Drivers/DriverHealthChanged.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Messages/Drivers/DriverHealthChanged.cs @@ -38,7 +38,7 @@ namespace ZB.MOM.WW.OtOpcUa.Commons.Messages.Drivers; /// so a driver-only node has no ConfigDb connection to write rows to. This channel already reaches /// /hosts, already survives the mesh split via the Phase 5 gRPC telemetry stream, and already /// replays a last-value snapshot on every (re)subscribe — so per-host state re-primes after a -/// reconnect without a durable store. The table was dropped; see Gitea #521. +/// reconnect without a durable store. The table was dropped. /// Value-equality caveat. A record's generated Equals compares this list by /// REFERENCE, so two DriverHealthChanged carrying equal-but-distinct lists are not equal. /// Nothing dedups on record equality — DriverInstanceActor keeps its own flattened fingerprint @@ -48,13 +48,13 @@ namespace ZB.MOM.WW.OtOpcUa.Commons.Messages.Drivers; /// What this driver skipped when it last built its tag table, from /// ISkippedTagReporter.GetSkippedTags(); null when the driver does not implement that /// capability. All twelve production drivers do. -/// Why this is on the health snapshot (Gitea #541). A skip happens at init, is not an +/// Why this is on the health snapshot. A skip happens at init, is not an /// error, and does not stop the driver's OTHER tags from reading — so State stays Healthy, /// ErrorCount5Min stays 0, LastError stays null and LastSuccessfulReadUtc keeps -/// advancing while the driver ignores part or all of its configuration. On the Makino in Gitea #540 +/// advancing while the driver ignores part or all of its configuration. On the Makino /// that combination held while 20 of 22 tags were ignored. This field is what contradicts it. /// Additive, never a state change. See ISkippedTagReporter for why a skipped tag -/// must not latch the driver non-Healthy — it would satisfy both halves of the #527 comms-loss +/// must not latch the driver non-Healthy — it would satisfy both halves of the comms-loss /// conjunction and reconnect-loop a healthy device. /// Value-equality caveat — the same one carries: the /// record's generated Equals compares the sample list by REFERENCE. @@ -62,7 +62,7 @@ namespace ZB.MOM.WW.OtOpcUa.Commons.Messages.Drivers; /// /// Operator-facing driver diagnostics from IDriverDiagnostics.GetDiagnostics() (e.g. S7's /// negotiated PDU size), bounded and in ordinal key order; null when the driver does not implement the -/// capability (Gitea #640). Additive, never a state change. Same by-reference value-equality caveat as +/// capability. Additive, never a state change. Same by-reference value-equality caveat as /// . /// public sealed record DriverHealthChanged( diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Messages/Mesh/MeshTransportDtoMapper.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Messages/Mesh/MeshTransportDtoMapper.cs index 4fe43816..dd3f2d6b 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Messages/Mesh/MeshTransportDtoMapper.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Messages/Mesh/MeshTransportDtoMapper.cs @@ -216,7 +216,7 @@ public static class MeshTransportDtoMapper if (message.FailureReason is not null) proto.FailureReason = message.FailureReason; - // #601 — additive fields; a clean ack leaves both unset, byte-identical to a pre-#601 encode. + // Additive fields; a clean ack leaves both unset, byte-identical to an encode that predates these fields. proto.Degraded = message.Degraded; if (message.DegradedReason is not null) proto.DegradedReason = message.DegradedReason; @@ -255,7 +255,7 @@ public static class MeshTransportDtoMapper outcome, proto.HasFailureReason ? proto.FailureReason : null, CorrelationId.Parse(proto.CorrelationId), - // #601 — absent on an old node's ack, which decodes as false/null: today's clean ack. + // Absent on an old node's ack, which decodes as false/null: today's clean ack. Degraded: proto.Degraded, DegradedReason: proto.HasDegradedReason ? proto.DegradedReason : null); } diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Observability/OtOpcUaTelemetry.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Observability/OtOpcUaTelemetry.cs index 2d8f4dc8..2d31f973 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Observability/OtOpcUaTelemetry.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Observability/OtOpcUaTelemetry.cs @@ -77,7 +77,7 @@ public static class OtOpcUaTelemetry public const string PollOverrunDriverInstanceTag = "driver_instance"; /// - /// Session/publish-level events of an OPC UA Client driver instance (#276): the operator's view of a + /// Session/publish-level events of an OPC UA Client driver instance: the operator's view of a /// flapping upstream. Tagged (driver_instance) + /// event = publish_response|notification|data_change|bad_status_value|publish_error| /// keepalive_miss|keepalive_recovered|stale_notification_dropped. diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Commons/OpcUa/AlarmConditionSnapshot.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/OpcUa/AlarmConditionSnapshot.cs index 1330ce2c..e1813824 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Commons/OpcUa/AlarmConditionSnapshot.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/OpcUa/AlarmConditionSnapshot.cs @@ -19,15 +19,15 @@ namespace ZB.MOM.WW.OtOpcUa.Commons.OpcUa; /// /// Status code of the condition's source data (Part 9 ConditionType.Quality). Carried so a /// comms-lost native source can report a non-Good condition instead of the accidentally-Good -/// default (issue #477). It is a pure annotation — it never alters Active/Acked/Retain. Defaults to +/// default. It is a pure annotation — it never alters Active/Acked/Retain. Defaults to /// so scripted-alarm callers (which stay Good in v1) need not supply it. -/// Carries the full sub-code since #519, so a comms-lost condition reports +/// Carries the full sub-code, so a comms-lost condition reports /// rather than a bare . /// /// /// For , the UTC instant the engine expires the shelve; otherwise /// null. Carried so the node's Part 9 ShelvingState.UnshelveTime reports the real remaining -/// time instead of the SDK's ~24.8-day ceiling (Gitea #597). A timed snapshot without it (a caller that +/// time instead of the SDK's ~24.8-day ceiling. A timed snapshot without it (a caller that /// predates the field) falls back to that ceiling — the engine still owns the expiry either way. /// public sealed record AlarmConditionSnapshot( diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Commons/OpcUa/DeferredAddressSpaceSink.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/OpcUa/DeferredAddressSpaceSink.cs index 0c592bb1..4ee8e917 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Commons/OpcUa/DeferredAddressSpaceSink.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/OpcUa/DeferredAddressSpaceSink.cs @@ -44,6 +44,7 @@ public sealed class DeferredAddressSpaceSink : IOpcUaAddressSpaceSink, ISurgical public void WriteAlarmCondition(string alarmNodeId, AlarmConditionSnapshot state, DateTime sourceTimestampUtc, AddressSpaceRealm realm) => _inner.WriteAlarmCondition(alarmNodeId, state, sourceTimestampUtc, realm); + /// public void WriteAlarmQuality(string alarmNodeId, uint statusCode, DateTime sourceTimestampUtc, AddressSpaceRealm realm) => _inner.WriteAlarmQuality(alarmNodeId, statusCode, sourceTimestampUtc, realm); @@ -52,7 +53,7 @@ public sealed class DeferredAddressSpaceSink : IOpcUaAddressSpaceSink, ISurgical => _inner.MaterialiseAlarmCondition(alarmNodeId, equipmentNodeId, displayName, alarmType, severity, realm, isNative); /// - // Forward the WP4 multi-notifier wiring to the inner sink. Without this the native-alarm fan-out to the + // Forward the multi-notifier wiring to the inner sink. Without this the native-alarm fan-out to the // referencing equipment folders ships INERT on every driver-role host (actors inject THIS wrapper, not the // inner SdkAddressSpaceSink) — the F10b / PR#423 forwarding trap the reflection guard exists to catch. public void WireAlarmNotifiers(string alarmNodeId, AddressSpaceRealm alarmRealm, IReadOnlyList notifierFolderNodeIds, AddressSpaceRealm notifierFolderRealm) @@ -100,7 +101,7 @@ public sealed class DeferredAddressSpaceSink : IOpcUaAddressSpaceSink, ISurgical => _inner is ISurgicalAddressSpaceSink surgical && surgical.UpdateFolderDisplayName(folderNodeId, displayName, realm); - // R2-07 T7 — surgical remove forwards. Same capability-sniffing pattern as UpdateTagAttributes / + // Surgical remove forwards. Same capability-sniffing pattern as UpdateTagAttributes / // UpdateFolderDisplayName: forward when the inner sink supports the surgical capability; return false // otherwise (before the real SdkAddressSpaceSink is swapped in, or any non-surgical inner) so the caller // (AddressSpaceApplier) falls back to a full rebuild. Without these forwards the surgical remove path is diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Commons/OpcUa/IOpcUaAddressSpaceSink.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/OpcUa/IOpcUaAddressSpaceSink.cs index 498cd59e..7c69c314 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Commons/OpcUa/IOpcUaAddressSpaceSink.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/OpcUa/IOpcUaAddressSpaceSink.cs @@ -11,14 +11,14 @@ public interface IOpcUaAddressSpaceSink /// Write a Variable node's current value + status code + source timestamp. /// The OPC UA node ID of the variable. /// The value to write. - /// The full OPC UA status code, sub-code included. Passed to the SDK verbatim - /// (#519): a driver's reaches the client as + /// The full OPC UA status code, sub-code included. Passed to the SDK verbatim, so + /// a driver's reaches the client as /// BadNoCommunication, not as a generic . Use /// ' named constants rather than a literal. /// The source timestamp in UTC. /// Which of the two v3 namespaces ( device tree or /// equipment tree) the lives in — the sink - /// resolves the namespace index from the realm rather than parsing it out of the id string. WP3's fan-out + /// resolves the namespace index from the realm rather than parsing it out of the id string. The fan-out /// calls this once per NodeId (raw + every referencing UNS node) with the matching realm. void WriteValue(string nodeId, object? value, uint statusCode, DateTime sourceTimestampUtc, AddressSpaceRealm realm); @@ -81,11 +81,11 @@ public interface IOpcUaAddressSpaceSink /// the condition was materialised under). void WriteAlarmCondition(string alarmNodeId, AlarmConditionSnapshot state, DateTime sourceTimestampUtc, AddressSpaceRealm realm); - /// #477 — annotate a materialised condition's source-data quality OUT OF BAND from any alarm + /// Annotate a materialised condition's source-data quality OUT OF BAND from any alarm /// transition (used by the driver-connectivity path: comms lost → , /// restored → ). Sets ONLY the condition's Quality — never /// Active/Acked/Severity/Retain (a comms-lost active alarm stays active) — and fires one Part 9 event - /// only on a change to the status code itself — since #519 that includes a move between two Bad + /// only on a change to the status code itself — that now includes a move between two Bad /// sub-codes, which genuinely changes what a subscriber reads. A no-op for an unmaterialised / /// non-condition node. /// The condition node id (RawPath for a native alarm). @@ -172,9 +172,9 @@ public interface IOpcUaAddressSpaceSink /// The namespace realm the variable (and its ) live /// in. A historized UNS reference node registers the SAME as its backing /// raw node — both NodeIds map to one tagname — so this call carries the shared tagname per realm. - /// Gitea #610 review (R6): when true and the node is SCALAR, a client write + /// When true and the node is SCALAR, a client write /// of an array value is refused inline (BadTypeMismatch, nothing dispatched) because the owning - /// driver's scalar encoder cannot take one (Modbus / S7). False (default) keeps the pre-#610 behaviour — the + /// driver's scalar encoder cannot take one (Modbus / S7). False (default) keeps the earlier behaviour — the /// write is dispatched and the driver decides (OpcUaClient forwards it to the upstream server, Galaxy encodes /// it). Fixed at creation; an existing node keeps the value it was created with. void EnsureVariable(string variableNodeId, string? parentFolderNodeId, string displayName, string dataType, bool writable, AddressSpaceRealm realm, string? historianTagname = null, bool isArray = false, uint? arrayLength = null, bool refuseArrayOnScalar = false); @@ -186,7 +186,7 @@ public interface IOpcUaAddressSpaceSink void RebuildAddressSpace(); /// - /// Gitea #641 — mark the END of one address-space apply pass (after , if + /// Mark the END of one address-space apply pass (after , if /// one ran, and every materialise / notifier-wiring call that followed it). An SDK-backed sink carries each /// native condition's live Part 9 state across a rebuild and restores it when the same condition is /// re-materialised; this is where it reports the ONE condition event per restored condition (now that @@ -229,7 +229,7 @@ public interface IOpcUaAddressSpaceSink } /// One value write within a batch. -/// Same semantics per field as (#519: status verbatim). +/// Same semantics per field as (status verbatim). public readonly record struct ValueWrite( string NodeId, object? Value, uint StatusCode, DateTime SourceTimestampUtc, AddressSpaceRealm Realm); @@ -237,7 +237,7 @@ public readonly record struct ValueWrite( /// node's current VALUE is retained and only + /// change. /// The OPC UA node id of the variable, within . -/// The full status code, sub-code included, passed to the SDK verbatim (#519). +/// The full status code, sub-code included, passed to the SDK verbatim. /// The source timestamp in UTC. /// Which of the two v3 namespaces lives in. public readonly record struct StatusWrite( @@ -279,6 +279,7 @@ public sealed class NullOpcUaAddressSpaceSink : IOpcUaAddressSpaceSink /// public void WriteAlarmCondition(string alarmNodeId, AlarmConditionSnapshot state, DateTime sourceTimestampUtc, AddressSpaceRealm realm) { } + /// public void WriteAlarmQuality(string alarmNodeId, uint statusCode, DateTime sourceTimestampUtc, AddressSpaceRealm realm) { } /// diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Commons/OpcUa/OpcUaStatus.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/OpcUa/OpcUaStatus.cs index f2797331..5e7352b6 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Commons/OpcUa/OpcUaStatus.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/OpcUa/OpcUaStatus.cs @@ -2,7 +2,7 @@ namespace ZB.MOM.WW.OtOpcUa.Commons.OpcUa; /// /// The handful of OPC UA status codes the engine layer needs to spell, plus the Part 4 severity -/// classification helpers that replaced the retired OpcUaQuality 3-state enum (Gitea #519). +/// classification helpers that replaced the retired OpcUaQuality 3-state enum. /// /// Why these are bare uint literals. Commons and Runtime deliberately do not /// reference the OPC UA SDK — exists precisely so the Runtime @@ -10,11 +10,11 @@ namespace ZB.MOM.WW.OtOpcUa.Commons.OpcUa; /// decision the driver assemblies make, and it carries the same single failure mode: nothing checks /// the literal against the name. StatusCodeParityTests closes it by reflection, and it can /// only see a named constant — so add codes here rather than writing a literal at a call -/// site, exactly as Gitea #497 concluded for the drivers. +/// site, exactly as for the drivers. /// /// Severity, not equality, is what callers should branch on. A status code's top two /// bits carry its Part 4 severity (00 = Good, 01 = Uncertain, 10/11 = Bad); everything below is the -/// sub-code. Before #519 the engine collapsed every value onto a 3-state enum here and the node +/// sub-code. Previously the engine collapsed every value onto a 3-state enum here and the node /// manager re-expanded Bad to a bare 0x80000000, so no driver's sub-code ever reached /// a client. The sub-code now rides through to the SDK verbatim, and any code that only needs the /// bucket uses / / rather than diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/ApiKey.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/ApiKey.cs index b7c32a2f..90b33a12 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/ApiKey.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/ApiKey.cs @@ -1,7 +1,7 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities; /// -/// One OPC UA data-plane API key. The OPC UA data plane is machine-to-machine (#520), so a +/// One OPC UA data-plane API key. The OPC UA data plane is machine-to-machine, so a /// consuming system authenticates with a key rather than an LDAP user, and the key resolves to a /// that rows grant permissions to. /// @@ -22,7 +22,7 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities; /// ⚠️ Revocation requires a deploy. Phase 4 leaves driver-only nodes with no ConfigDb, so /// keys reach them inside the deployment artifact exactly as rows do. /// Setting to false takes effect on a node only once a new artifact reaches -/// it. This is an operational sharp edge, not an oversight — see the #520 design doc. +/// it. This is an operational sharp edge, not an oversight. /// /// public sealed class ApiKey diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/OtOpcUaConfigDbContext.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/OtOpcUaConfigDbContext.cs index 166345fb..c45161e4 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/OtOpcUaConfigDbContext.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/OtOpcUaConfigDbContext.cs @@ -41,7 +41,7 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptionsGets the DbSet of node ACLs. public DbSet NodeAcls => Set(); - /// OPC UA data-plane API keys (#520). One principal per key; secret stored as a peppered hash. + /// OPC UA data-plane API keys. One principal per key; secret stored as a peppered hash. public DbSet ApiKeys => Set(); /// Gets the DbSet of configuration audit logs. public DbSet ConfigAuditLogs => Set(); @@ -563,14 +563,14 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions ScriptedAlarms { get; init; } = []; /// User-authored scripts (shared by VirtualTags, ScriptedAlarms, and Calculation tags). Drives - /// the WP7 Calculation gates: scriptId existence + the calc→calc dependency-cycle check. + /// the Calculation gates: scriptId existence + the calc→calc dependency-cycle check. public IReadOnlyList