From 1eb6e972b0c127beb9fcedf9516d5fee127d4bea Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 28 May 2026 01:55:24 -0400 Subject: [PATCH] docs: add XML doc comments across src + Sister Projects section in CLAUDE.md Bulk CommentChecker pass: fills in / tags on public APIs across all 23 src/ projects so the doc-coverage gate is green. Also adds a Sister Projects section to CLAUDE.md pointing at the MxAccess Gateway and OtOpcUa sibling repos, and gitignores local credential captures (*login*.txt) and the wonder-app-vd03 deploy/ artifacts. --- .gitignore | 6 + CLAUDE.md | 7 + .../Central/AuditCentralHealthSnapshot.cs | 1 + .../Central/AuditLogIngestActor.cs | 11 +- .../AuditLogPartitionMaintenanceService.cs | 6 + .../Central/AuditLogPurgeActor.cs | 13 +- .../CentralAuditRedactionFailureCounter.cs | 4 + .../Central/CentralAuditWriter.cs | 12 +- .../Central/IPullAuditEventsClient.cs | 4 + .../Central/ISiteEnumerator.cs | 1 + .../Central/SiteAuditReconciliationActor.cs | 16 +- .../SiteAuditTelemetryStalledTracker.cs | 15 ++ .../Payload/DefaultAuditPayloadFilter.cs | 7 + .../Payload/IAuditPayloadFilter.cs | 1 + .../ServiceCollectionExtensions.cs | 8 + .../Site/FallbackAuditWriter.cs | 6 + ...althMetricsAuditRedactionFailureCounter.cs | 2 + .../HealthMetricsAuditWriteFailureCounter.cs | 4 + .../Site/RingBufferFallback.cs | 7 + .../Site/SiteAuditBacklogReporter.cs | 5 + .../Site/SqliteAuditWriter.cs | 70 ++----- .../Telemetry/CachedCallLifecycleBridge.cs | 4 + .../Telemetry/CachedCallTelemetryForwarder.cs | 12 +- .../Site/Telemetry/ISiteStreamAuditClient.cs | 4 + .../Site/Telemetry/SiteAuditTelemetryActor.cs | 7 + src/ScadaLink.CLI/CliConfig.cs | 11 + .../Commands/ApiMethodCommands.cs | 7 + .../Commands/AuditCommandHelpers.cs | 39 ++++ src/ScadaLink.CLI/Commands/AuditCommands.cs | 7 + .../Commands/AuditExportHelpers.cs | 36 ++++ src/ScadaLink.CLI/Commands/AuditFormatter.cs | 8 + .../Commands/AuditLogCommands.cs | 9 + .../Commands/AuditQueryHelpers.cs | 25 +++ .../Commands/AuditVerifyChainHelpers.cs | 1 + src/ScadaLink.CLI/Commands/BundleCommands.cs | 6 + src/ScadaLink.CLI/Commands/CommandHelpers.cs | 21 ++ .../Commands/DataConnectionCommands.cs | 7 + .../Commands/DbConnectionCommands.cs | 9 + src/ScadaLink.CLI/Commands/DebugCommands.cs | 5 + .../Commands/DebugStreamHelpers.cs | 3 + src/ScadaLink.CLI/Commands/DeployCommands.cs | 7 + .../Commands/ExternalSystemCommands.cs | 7 + src/ScadaLink.CLI/Commands/HealthCommands.cs | 7 + .../Commands/InstanceCommands.cs | 16 ++ .../Commands/NotificationCommands.cs | 8 + .../Commands/SecurityCommands.cs | 7 + .../Commands/SharedScriptCommands.cs | 6 + src/ScadaLink.CLI/Commands/SiteCommands.cs | 7 + .../Commands/TableAuditFormatter.cs | 1 + .../Commands/TemplateCommands.cs | 5 + src/ScadaLink.CLI/ManagementHttpClient.cs | 25 +++ src/ScadaLink.CLI/OutputFormatter.cs | 8 + .../Audit/AuditExportEndpoints.cs | 6 + src/ScadaLink.CentralUI/Auth/AuthEndpoints.cs | 3 + .../Auth/ClaimsPrincipalExtensions.cs | 3 + .../Auth/CookieAuthenticationStateProvider.cs | 5 + .../Auth/SiteScopeService.cs | 4 + .../Components/Audit/AuditFilterBar.razor.cs | 1 + .../Components/Audit/AuditQueryModel.cs | 14 ++ .../Audit/AuditResultsGrid.razor.cs | 9 + .../Audit/ExecutionDetailModal.razor.cs | 1 + .../Components/Audit/ExecutionTree.razor.cs | 1 + .../Pages/Audit/AuditLogPage.razor.cs | 6 + .../Pages/Audit/ExecutionTreePage.razor.cs | 1 + .../Pages/Design/TransportExport.razor.cs | 9 + .../Pages/Design/TransportImport.razor.cs | 2 + .../Pages/SiteCalls/SiteCallsReport.razor.cs | 1 + .../Shared/AlarmTriggerConfigCodec.cs | 86 ++++++++ .../Components/Shared/DialogService.cs | 3 + .../Components/Shared/DurationInput.cs | 3 + .../Components/Shared/PagerWindow.cs | 4 + .../Components/Shared/SchemaBuilderModel.cs | 9 + .../Components/Shared/ScriptParameterNames.cs | 8 + .../Shared/ScriptTriggerConfigCodec.cs | 8 + .../Components/Shared/TemplateTreeNode.cs | 4 + .../Shared/TriggerAttributeMapper.cs | 2 + src/ScadaLink.CentralUI/EndpointExtensions.cs | 2 + .../ScriptAnalysis/ISharedScriptCatalog.cs | 9 + .../ScriptAnalysis/InboundScriptHost.cs | 40 ++++ .../ScriptAnalysis/JsonSchemaShapeParser.cs | 4 + .../ScriptAnalysis/SandboxConsoleCapture.cs | 13 ++ .../ScriptAnalysis/SandboxHostHelpers.cs | 31 +++ .../SandboxInboundScriptHost.cs | 38 ++++ .../ScriptAnalysis/SandboxInstanceGateway.cs | 10 + .../ScriptAnalysis/SandboxScriptHost.cs | 177 ++++++++++++++++ .../ScriptAnalysis/ScriptAnalysisEndpoints.cs | 6 + .../ScriptAnalysis/ScriptAnalysisService.cs | 24 +++ .../ScriptAnalysis/ScriptShapeParser.cs | 4 + .../ServiceCollectionExtensions.cs | 4 + .../Services/AuditLogExportService.cs | 5 + .../Services/AuditLogQueryService.cs | 6 + .../Services/IAuditLogQueryService.cs | 7 + .../ClusterOptionsValidator.cs | 5 + .../ServiceCollectionExtensions.cs | 2 + .../Entities/Audit/AuditLogEntry.cs | 17 ++ .../Deployment/DeployedConfigSnapshot.cs | 9 + .../Entities/Deployment/DeploymentRecord.cs | 40 ++++ .../SystemArtifactDeploymentRecord.cs | 11 + .../DatabaseConnectionDefinition.cs | 8 + .../ExternalSystemDefinition.cs | 13 ++ .../ExternalSystems/ExternalSystemMethod.cs | 14 ++ .../Entities/InboundApi/ApiKey.cs | 7 + .../Entities/InboundApi/ApiMethod.cs | 10 + .../Entities/Instances/Area.cs | 9 + .../Entities/Instances/Instance.cs | 13 ++ .../Instances/InstanceAlarmOverride.cs | 6 + .../Instances/InstanceAttributeOverride.cs | 6 + .../Instances/InstanceConnectionBinding.cs | 8 + .../Entities/Notifications/Notification.cs | 23 +++ .../Notifications/NotificationList.cs | 6 + .../Notifications/NotificationRecipient.cs | 9 + .../Notifications/SmtpConfiguration.cs | 17 ++ .../Entities/Scripts/SharedScript.cs | 10 + .../Entities/Security/LdapGroupMapping.cs | 6 + .../Entities/Security/SiteScopeRule.cs | 3 + .../Entities/Sites/DataConnection.cs | 13 ++ src/ScadaLink.Commons/Entities/Sites/Site.cs | 13 ++ .../Entities/Templates/Template.cs | 31 +++ .../Entities/Templates/TemplateAlarm.cs | 13 ++ .../Entities/Templates/TemplateAttribute.cs | 28 +++ .../Entities/Templates/TemplateComposition.cs | 8 + .../Entities/Templates/TemplateFolder.cs | 8 + .../Entities/Templates/TemplateScript.cs | 46 ++++- .../Interfaces/IOperationTrackingStore.cs | 23 +++ .../Interfaces/IPartitionMaintenance.cs | 3 + .../Interfaces/Protocol/IDataConnection.cs | 43 ++++ .../Repositories/IAuditLogRepository.cs | 12 ++ .../Repositories/ICentralUiRepository.cs | 36 ++++ .../IDeploymentManagerRepository.cs | 126 ++++++++++++ .../Repositories/IExternalSystemRepository.cs | 103 ++++++++++ .../Repositories/IInboundApiRepository.cs | 39 ++++ .../INotificationOutboxRepository.cs | 30 +++ .../Repositories/INotificationRepository.cs | 69 +++++++ .../Repositories/ISecurityRepository.cs | 70 +++++++ .../Repositories/ISiteCallAuditRepository.cs | 15 ++ .../Repositories/ISiteRepository.cs | 39 ++++ .../Repositories/ITemplateEngineRepository.cs | 190 ++++++++++++++++++ .../Interfaces/Services/IAuditService.cs | 10 + .../Interfaces/Services/IAuditWriter.cs | 2 + .../Services/ICachedCallLifecycleObserver.cs | 2 + .../Services/ICachedCallTelemetryForwarder.cs | 2 + .../Services/ICentralAuditWriter.cs | 2 + .../Interfaces/Services/IDatabaseGateway.cs | 7 + .../Services/IExternalSystemClient.cs | 11 + .../Interfaces/Services/IInstanceLocator.cs | 2 + .../Services/INotificationDeliveryService.cs | 5 + .../Interfaces/Services/ISiteAuditQueue.cs | 10 + .../Transport/IAuditCorrelationContext.cs | 1 + .../Interfaces/Transport/IBundleExporter.cs | 8 + .../Interfaces/Transport/IBundleImporter.cs | 20 ++ .../Transport/IBundleSessionStore.cs | 7 + .../Management/ManagementCommandRegistry.cs | 5 + .../Messages/Streaming/ISiteStreamEvent.cs | 1 + .../OpcUaEndpointConfigSerializer.cs | 19 ++ .../Types/Audit/AuditQueryParamParsers.cs | 5 + .../DataConnections/OpcUaDeadbandConfig.cs | 2 + .../DataConnections/OpcUaEndpointConfig.cs | 57 ++++++ .../DataConnections/OpcUaHeartbeatConfig.cs | 2 + .../OpcUaUserIdentityConfig.cs | 8 + .../Types/DynamicJsonElement.cs | 6 + .../Types/Flattening/ConfigurationDiff.cs | 9 + .../Flattening/FlattenedConfiguration.cs | 29 +++ .../Types/Flattening/ValidationResult.cs | 18 ++ .../Types/InboundApi/ApiKeyHasher.cs | 3 + .../Types/InboundApi/ParameterDefinition.cs | 3 + src/ScadaLink.Commons/Types/Result.cs | 13 ++ src/ScadaLink.Commons/Types/ScriptArgs.cs | 3 + .../Types/ScriptParameters.cs | 49 +++++ .../Types/Scripts/AlarmContext.cs | 3 + .../Types/Scripts/ScriptScope.cs | 1 + .../Types/StaleTagMonitor.cs | 4 + .../Types/TrackedOperationId.cs | 3 + .../Types/Transport/BundleSession.cs | 6 + src/ScadaLink.Commons/Types/ValueFormatter.cs | 1 + .../OpcUaEndpointConfigValidator.cs | 5 + .../Actors/CentralCommunicationActor.cs | 19 +- .../Actors/DebugStreamBridgeActor.cs | 16 ++ .../Actors/SiteCommunicationActor.cs | 13 +- .../Actors/StreamRelayActor.cs | 5 + .../CommunicationService.cs | 188 +++++++++++++++++ .../DebugStreamService.cs | 14 ++ .../Grpc/AuditEventDtoMapper.cs | 2 + .../Grpc/ISiteStreamSubscriber.cs | 3 + .../Grpc/SiteCallDtoMapper.cs | 1 + .../Grpc/SiteStreamGrpcClient.cs | 32 +++ .../Grpc/SiteStreamGrpcClientFactory.cs | 13 ++ .../Grpc/SiteStreamGrpcServer.cs | 19 ++ .../ServiceCollectionExtensions.cs | 4 + .../Configurations/AuditConfiguration.cs | 4 + .../AuditLogEntityTypeConfiguration.cs | 2 + .../Configurations/DeploymentConfiguration.cs | 6 + .../ExternalSystemConfiguration.cs | 6 + .../Configurations/InboundApiConfiguration.cs | 4 + .../Configurations/InstanceConfiguration.cs | 10 + .../NotificationConfiguration.cs | 6 + .../NotificationOutboxConfiguration.cs | 2 + .../Configurations/ScriptConfiguration.cs | 2 + .../Configurations/SecurityConfiguration.cs | 8 + .../SiteCallEntityTypeConfiguration.cs | 4 + .../Configurations/SiteConfiguration.cs | 4 + .../Configurations/TemplateConfiguration.cs | 12 ++ .../DesignTimeDbContextFactory.cs | 4 + .../EncryptedStringConverter.cs | 4 + .../AuditLogPartitionMaintenance.cs | 3 + .../Repositories/AuditLogRepository.cs | 135 +------------ .../Repositories/CentralUiRepository.cs | 13 ++ .../DeploymentManagerRepository.cs | 35 +++- .../Repositories/ExternalSystemRepository.cs | 23 +++ .../Repositories/InboundApiRepository.cs | 28 ++- .../NotificationOutboxRepository.cs | 11 + .../Repositories/NotificationRepository.cs | 19 ++ .../Repositories/SecurityRepository.cs | 16 ++ .../Repositories/SiteCallAuditRepository.cs | 56 +----- .../Repositories/SiteRepository.cs | 18 ++ .../Repositories/TemplateEngineRepository.cs | 75 ++++++- .../ScadaLinkDbContext.cs | 68 +++++++ .../ServiceCollectionExtensions.cs | 3 + .../Services/AuditCorrelationContext.cs | 1 + .../Services/AuditService.cs | 6 + .../Services/InstanceLocator.cs | 3 + .../Actors/DataConnectionActor.cs | 18 ++ .../Actors/DataConnectionManagerActor.cs | 24 +-- .../Adapters/IOpcUaClient.cs | 52 +++++ .../Adapters/OpcUaDataConnection.cs | 19 ++ .../Adapters/RealOpcUaClient.cs | 31 +++ .../DataConnectionFactory.cs | 12 ++ .../OpcUaGlobalOptions.cs | 4 + .../ServiceCollectionExtensions.cs | 9 + .../ArtifactDeploymentService.cs | 22 ++ .../DeploymentService.cs | 29 +++ .../DeploymentStatusNotifier.cs | 2 + .../FlatteningPipeline.cs | 7 + .../IDeploymentStatusNotifier.cs | 1 + .../IFlatteningPipeline.cs | 2 + .../OperationLockManager.cs | 11 + .../ServiceCollectionExtensions.cs | 5 + .../StateTransitionValidator.cs | 10 + .../DatabaseGateway.cs | 24 +-- .../ErrorClassifier.cs | 12 ++ .../ExternalSystemClient.cs | 31 +-- .../ServiceCollectionExtensions.cs | 17 ++ .../CentralHealthAggregator.cs | 43 +--- .../CentralHealthReportLoop.cs | 10 + .../HealthMonitoringOptions.cs | 2 + .../HealthMonitoringOptionsValidator.cs | 5 + .../HealthReportSender.cs | 10 + .../ICentralHealthAggregator.cs | 9 + .../IClusterNodeProvider.cs | 1 + .../IHealthReportTransport.cs | 4 + .../ISiteHealthCollector.cs | 89 ++++++++ .../ISiteIdentityProvider.cs | 1 + .../ServiceCollectionExtensions.cs | 3 + .../SiteHealthCollector.cs | 77 ++----- .../SiteHealthState.cs | 3 + .../Actors/AkkaHostedService.cs | 24 +++ .../Actors/DeadLetterMonitorActor.cs | 7 + .../AkkaHealthReportTransport.cs | 5 + src/ScadaLink.Host/DatabaseOptions.cs | 5 + .../Health/ActiveNodeHealthCheck.cs | 5 + .../Health/AkkaClusterHealthCheck.cs | 9 + .../Health/AkkaClusterNodeProvider.cs | 7 + .../Health/DatabaseHealthCheck.cs | 9 + .../LoggerConfigurationFactory.cs | 6 + src/ScadaLink.Host/LoggingOptions.cs | 1 + src/ScadaLink.Host/NodeIdentityProvider.cs | 5 + src/ScadaLink.Host/NodeOptions.cs | 5 + src/ScadaLink.Host/SiteIdentityProvider.cs | 5 + src/ScadaLink.Host/SiteServiceRegistration.cs | 6 + src/ScadaLink.Host/StartupRetry.cs | 11 + src/ScadaLink.Host/StartupValidator.cs | 2 + .../StoreAndForwardSiteContext.cs | 3 + src/ScadaLink.InboundAPI/ApiKeyValidator.cs | 31 +++ .../CommunicationServiceInstanceRouter.cs | 7 + .../EndpointExtensions.cs | 2 + .../ForbiddenApiChecker.cs | 6 + src/ScadaLink.InboundAPI/IInstanceRouter.cs | 12 ++ .../InboundApiEndpointFilter.cs | 6 + src/ScadaLink.InboundAPI/InboundApiOptions.cs | 1 + .../InboundScriptExecutor.cs | 33 +++ .../Middleware/AuditWriteMiddleware.cs | 31 +++ .../AuditWriteMiddlewareExtensions.cs | 1 + .../ParameterValidator.cs | 13 ++ .../ReturnValueValidator.cs | 9 + src/ScadaLink.InboundAPI/RouteHelper.cs | 28 +++ .../ServiceCollectionExtensions.cs | 4 + .../AuditEndpoints.cs | 18 ++ .../DebugStreamHub.cs | 16 +- .../ManagementActor.cs | 9 + .../ManagementActorHolder.cs | 1 + .../ManagementEndpoints.cs | 8 + .../ManagementServiceOptions.cs | 1 + .../ServiceCollectionExtensions.cs | 4 + .../Delivery/DeliveryOutcome.cs | 3 + .../EmailNotificationDeliveryAdapter.cs | 6 + .../NotificationOutboxActor.cs | 13 +- .../ServiceCollectionExtensions.cs | 1 + .../EmailAddressValidator.cs | 2 + .../ISmtpClientWrapper.cs | 15 ++ .../MailKitSmtpClientWrapper.cs | 5 + .../NotificationDeliveryService.cs | 20 +- .../OAuth2TokenService.cs | 6 + .../ServiceCollectionExtensions.cs | 8 + .../SmtpErrorClassifier.cs | 2 + .../SmtpPermanentException.cs | 5 + .../SmtpTlsMode.cs | 1 + .../AuthorizationPolicies.cs | 4 + src/ScadaLink.Security/JwtTokenService.cs | 25 +++ src/ScadaLink.Security/LdapAuthService.cs | 17 ++ src/ScadaLink.Security/RoleMapper.cs | 6 + src/ScadaLink.Security/SecurityOptions.cs | 4 + .../ServiceCollectionExtensions.cs | 8 + .../SiteScopeAuthorizationHandler.cs | 6 + .../ServiceCollectionExtensions.cs | 1 + .../SiteCallAuditActor.cs | 13 +- .../EventLogHandlerActor.cs | 2 + .../EventLogPurgeService.cs | 6 + .../EventLogQueryService.cs | 5 + .../IEventLogQueryService.cs | 4 + .../ServiceCollectionExtensions.cs | 1 + .../SiteEventLogOptions.cs | 5 + .../SiteEventLogger.cs | 14 ++ .../Actors/AlarmActor.cs | 17 +- .../Actors/AlarmExecutionActor.cs | 11 + .../Actors/DeploymentManagerActor.cs | 22 +- .../Actors/InstanceActor.cs | 19 +- .../Actors/ScriptActor.cs | 22 +- .../Actors/ScriptExecutionActor.cs | 18 ++ .../Actors/SiteReplicationActor.cs | 10 + .../Persistence/SiteStorageInitializer.cs | 12 ++ .../Persistence/SiteStorageService.cs | 91 +++++++++ .../SiteExternalSystemRepository.cs | 61 ++++-- .../SiteNotificationRepository.cs | 21 ++ .../Repositories/SyntheticId.cs | 1 + .../Scripts/AuditingDbCommand.cs | 30 +++ .../Scripts/AuditingDbConnection.cs | 31 +++ .../Scripts/AuditingDbDataReader.cs | 43 ++++ .../Scripts/ScopeAccessors.cs | 63 ++++++ .../Scripts/ScriptCompilationService.cs | 17 ++ .../Scripts/ScriptExecutionScheduler.cs | 8 +- .../Scripts/ScriptRuntimeContext.cs | 140 +++++++++++++ .../Scripts/SharedScriptLibrary.cs | 13 ++ .../Scripts/TriggerExpressionGlobals.cs | 24 +++ .../ServiceCollectionExtensions.cs | 5 + .../Streaming/SiteStreamManager.cs | 19 +- .../Tracking/OperationTrackingStore.cs | 7 + .../NotificationForwarder.cs | 5 + .../ParkedMessageHandlerActor.cs | 5 + .../ReplicationService.cs | 10 + .../ServiceCollectionExtensions.cs | 8 + .../StoreAndForwardService.cs | 24 +++ .../StoreAndForwardStorage.cs | 18 ++ src/ScadaLink.TemplateEngine/CycleDetector.cs | 11 + .../Flattening/FlatteningService.cs | 4 + .../Flattening/RevisionHashService.cs | 82 ++++++++ src/ScadaLink.TemplateEngine/LockEnforcer.cs | 9 + .../ServiceCollectionExtensions.cs | 8 + .../Services/AreaService.cs | 25 +++ .../Services/InstanceService.cs | 58 ++++++ .../Services/SiteService.cs | 52 +++++ .../Services/TemplateDeletionService.cs | 8 + .../Services/TemplateFolderService.cs | 32 +++ .../SharedScriptService.cs | 45 +++++ .../TemplateNaming.cs | 4 + .../TemplateResolver.cs | 39 +++- .../TemplateService.cs | 159 +++++++++++++++ .../Validation/CSharpDelimiterScanner.cs | 3 + .../Validation/SemanticValidator.cs | 8 + .../Validation/ValidationService.cs | 22 ++ .../Encryption/BundleSecretEncryptor.cs | 14 ++ .../Export/BundleExporter.cs | 10 + .../Export/DependencyResolver.cs | 12 ++ .../Import/ArtifactDiff.cs | 56 ++++++ .../Import/BundleImporter.cs | 76 +++---- .../Import/BundleSessionStore.cs | 9 + .../Import/SemanticValidationException.cs | 5 + .../Serialization/BundleSerializer.cs | 21 ++ .../Serialization/EntitySerializer.cs | 6 + .../Serialization/ManifestBuilder.cs | 12 ++ .../Serialization/ManifestValidator.cs | 4 + .../ServiceCollectionExtensions.cs | 4 + src/ScadaLink.Transport/TransportOptions.cs | 6 + 381 files changed, 5788 insertions(+), 532 deletions(-) diff --git a/.gitignore b/.gitignore index d6410ba9..aec5aeb6 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,9 @@ data/ # Docker env2 runtime data docker-env2/*/logs/ docker-env2/*/data/ + +# Local credentials / login captures — never commit +*login*.txt + +# Sister-project deployment artifacts (not part of this solution) +/deploy/ diff --git a/CLAUDE.md b/CLAUDE.md index 89403b0d..3f1ab46b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,6 +18,13 @@ When a change is requested, the default assumption is: update the design doc *an - `docs/plans/` — Design decision and implementation-plan documents from refinement sessions. - `AkkaDotNet/` — Akka.NET reference documentation and best practices notes. +## Sister Projects + +Related repos cloned as sibling directories under `~/Desktop/` — referenced for context, not part of this solution: + +- `~/Desktop/MxAccessGateway` — MxAccess Gateway (`https://gitea.dohertylan.com/dohertj2/mxaccessgw`). +- `~/Desktop/OtOpcUa` — OtOpcUa (`https://gitea.dohertylan.com/dohertj2/lmxopcua`). + ## Document Conventions - Requirements documents (high-level and component-level) live in `docs/requirements/`. diff --git a/src/ScadaLink.AuditLog/Central/AuditCentralHealthSnapshot.cs b/src/ScadaLink.AuditLog/Central/AuditCentralHealthSnapshot.cs index e728c51c..699d508a 100644 --- a/src/ScadaLink.AuditLog/Central/AuditCentralHealthSnapshot.cs +++ b/src/ScadaLink.AuditLog/Central/AuditCentralHealthSnapshot.cs @@ -64,6 +64,7 @@ public sealed class AuditCentralHealthSnapshot /// later from the Akka host) can push without a friend reference; /// readers should call . /// + /// The event carrying the site ID and new stalled state. public void ApplyStalled(SiteAuditTelemetryStalledChanged evt) { if (evt is null) return; diff --git a/src/ScadaLink.AuditLog/Central/AuditLogIngestActor.cs b/src/ScadaLink.AuditLog/Central/AuditLogIngestActor.cs index 61a6dafc..ae5e7ed9 100644 --- a/src/ScadaLink.AuditLog/Central/AuditLogIngestActor.cs +++ b/src/ScadaLink.AuditLog/Central/AuditLogIngestActor.cs @@ -52,6 +52,8 @@ public class AuditLogIngestActor : ReceiveActor /// lifetime exceeds the test, so the actor reuses the same instance across /// every message. Used by Bundle D's MSSQL-backed TestKit fixture. /// + /// Audit log repository instance shared across all messages. + /// Logger for ingest diagnostics. public AuditLogIngestActor( IAuditLogRepository repository, ILogger logger) @@ -77,6 +79,8 @@ public class AuditLogIngestActor : ReceiveActor /// is a long-lived cluster singleton, so it cannot hold a scope across /// messages. /// + /// Root service provider used to open a fresh scope per message. + /// Logger for ingest diagnostics. public AuditLogIngestActor( IServiceProvider serviceProvider, ILogger logger) @@ -91,12 +95,7 @@ public class AuditLogIngestActor : ReceiveActor ReceiveAsync(OnCachedTelemetryAsync); } - /// - /// Audit-write failures are best-effort by design (see alog.md §13): a - /// thrown exception in the ingest pipeline must not crash the actor. - /// Resume keeps the actor's state intact so the next batch is processed - /// against the same repository instance. - /// + /// protected override SupervisorStrategy SupervisorStrategy() { return new OneForOneStrategy(maxNrOfRetries: 0, withinTimeRange: TimeSpan.Zero, decider: diff --git a/src/ScadaLink.AuditLog/Central/AuditLogPartitionMaintenanceService.cs b/src/ScadaLink.AuditLog/Central/AuditLogPartitionMaintenanceService.cs index 2aa02f81..39d99d8c 100644 --- a/src/ScadaLink.AuditLog/Central/AuditLogPartitionMaintenanceService.cs +++ b/src/ScadaLink.AuditLog/Central/AuditLogPartitionMaintenanceService.cs @@ -60,6 +60,12 @@ public sealed class AuditLogPartitionMaintenanceService : IHostedService, IDispo private CancellationTokenSource? _cts; private Task? _loop; + /// + /// Initializes the maintenance service with its required dependencies. + /// + /// Scope factory used to open DI scopes for each maintenance run. + /// Partition maintenance options (retention period, purge interval, etc.). + /// Logger for this service. public AuditLogPartitionMaintenanceService( IServiceScopeFactory scopeFactory, IOptions options, diff --git a/src/ScadaLink.AuditLog/Central/AuditLogPurgeActor.cs b/src/ScadaLink.AuditLog/Central/AuditLogPurgeActor.cs index 153e2382..a0768513 100644 --- a/src/ScadaLink.AuditLog/Central/AuditLogPurgeActor.cs +++ b/src/ScadaLink.AuditLog/Central/AuditLogPurgeActor.cs @@ -61,6 +61,11 @@ public class AuditLogPurgeActor : ReceiveActor private readonly ILogger _logger; private ICancelable? _timer; + /// Initializes a new instance of and registers the tick handler. + /// DI service provider used to create scoped repository instances per tick. + /// Options controlling the purge interval. + /// Options controlling retention policy (RetentionDays). + /// Logger instance. public AuditLogPurgeActor( IServiceProvider services, IOptions purgeOptions, @@ -80,6 +85,7 @@ public class AuditLogPurgeActor : ReceiveActor ReceiveAsync(_ => OnTickAsync()); } + /// protected override void PreStart() { base.PreStart(); @@ -92,17 +98,14 @@ public class AuditLogPurgeActor : ReceiveActor sender: Self); } + /// protected override void PostStop() { _timer?.Cancel(); base.PostStop(); } - /// - /// Resume keeps the singleton alive across any leaked exception. Restart - /// would re-run PreStart and reschedule the timer (harmless but wasteful); - /// Stop is wrong because the singleton must keep ticking until shutdown. - /// + /// protected override SupervisorStrategy SupervisorStrategy() { return new OneForOneStrategy( diff --git a/src/ScadaLink.AuditLog/Central/CentralAuditRedactionFailureCounter.cs b/src/ScadaLink.AuditLog/Central/CentralAuditRedactionFailureCounter.cs index 102b6d93..a60ffe66 100644 --- a/src/ScadaLink.AuditLog/Central/CentralAuditRedactionFailureCounter.cs +++ b/src/ScadaLink.AuditLog/Central/CentralAuditRedactionFailureCounter.cs @@ -47,6 +47,10 @@ public sealed class CentralAuditRedactionFailureCounter : IAuditRedactionFailure { private readonly AuditCentralHealthSnapshot _snapshot; + /// + /// Initializes a new backed by the supplied snapshot. + /// + /// The central health snapshot that accumulates the redaction failure count. public CentralAuditRedactionFailureCounter(AuditCentralHealthSnapshot snapshot) { _snapshot = snapshot ?? throw new ArgumentNullException(nameof(snapshot)); diff --git a/src/ScadaLink.AuditLog/Central/CentralAuditWriter.cs b/src/ScadaLink.AuditLog/Central/CentralAuditWriter.cs index 9f079681..7dec9b38 100644 --- a/src/ScadaLink.AuditLog/Central/CentralAuditWriter.cs +++ b/src/ScadaLink.AuditLog/Central/CentralAuditWriter.cs @@ -66,6 +66,11 @@ public sealed class CentralAuditWriter : ICentralAuditWriter /// provider simply leaves SourceNode at whatever the caller set (often /// null, which is the legacy behaviour). /// + /// Service provider used to open a per-call scope for the scoped repository. + /// Logger for swallowed write-failure diagnostics. + /// Optional payload filter for truncation and redaction; defaults to a pass-through. + /// Optional counter incremented on swallowed repository failures; defaults to a no-op. + /// Optional node identity provider for stamping SourceNode on central-origin rows. public CentralAuditWriter( IServiceProvider services, ILogger logger, @@ -80,12 +85,7 @@ public sealed class CentralAuditWriter : ICentralAuditWriter _nodeIdentity = nodeIdentity; } - /// - /// Persists into the central AuditLog table - /// idempotently on . Stamps - /// from the central-side clock. - /// Internal failures are logged and swallowed — never thrown. - /// + /// public async Task WriteAsync(AuditEvent evt, CancellationToken ct = default) { if (evt is null) diff --git a/src/ScadaLink.AuditLog/Central/IPullAuditEventsClient.cs b/src/ScadaLink.AuditLog/Central/IPullAuditEventsClient.cs index e094e48c..172174c1 100644 --- a/src/ScadaLink.AuditLog/Central/IPullAuditEventsClient.cs +++ b/src/ScadaLink.AuditLog/Central/IPullAuditEventsClient.cs @@ -37,6 +37,10 @@ public interface IPullAuditEventsClient /// rows ordered oldest-first AND a MoreAvailable flag the actor /// uses to decide whether to fire another pull immediately. /// + /// The identifier of the site to pull audit events from. + /// Only events with an OccurredAtUtc at or after this cursor time are returned. + /// Maximum number of events to return per call. + /// Cancellation token. Task PullAsync( string siteId, DateTime sinceUtc, diff --git a/src/ScadaLink.AuditLog/Central/ISiteEnumerator.cs b/src/ScadaLink.AuditLog/Central/ISiteEnumerator.cs index 9e9607cd..357ebae7 100644 --- a/src/ScadaLink.AuditLog/Central/ISiteEnumerator.cs +++ b/src/ScadaLink.AuditLog/Central/ISiteEnumerator.cs @@ -22,6 +22,7 @@ public interface ISiteEnumerator /// on the next tick. Implementations should reflect adds/removes promptly /// — the actor calls this once per tick. /// + /// Cancellation token for the async enumeration. Task> EnumerateAsync(CancellationToken ct = default); } diff --git a/src/ScadaLink.AuditLog/Central/SiteAuditReconciliationActor.cs b/src/ScadaLink.AuditLog/Central/SiteAuditReconciliationActor.cs index e38e6d2c..737be9fb 100644 --- a/src/ScadaLink.AuditLog/Central/SiteAuditReconciliationActor.cs +++ b/src/ScadaLink.AuditLog/Central/SiteAuditReconciliationActor.cs @@ -95,6 +95,14 @@ public class SiteAuditReconciliationActor : ReceiveActor private ICancelable? _timer; + /// + /// Initializes the reconciliation actor with its dependencies and registers the tick handler. + /// + /// Enumerates the known sites to reconcile. + /// Client used to pull audit events from individual sites. + /// Root service provider for opening a per-tick DI scope. + /// Reconciliation configuration (interval, page size). + /// Logger for reconciliation diagnostics. public SiteAuditReconciliationActor( ISiteEnumerator sites, IPullAuditEventsClient client, @@ -117,6 +125,7 @@ public class SiteAuditReconciliationActor : ReceiveActor ReceiveAsync(_ => OnTickAsync()); } + /// protected override void PreStart() { base.PreStart(); @@ -129,6 +138,7 @@ public class SiteAuditReconciliationActor : ReceiveActor sender: Self); } + /// protected override void PostStop() { _timer?.Cancel(); @@ -301,11 +311,7 @@ public class SiteAuditReconciliationActor : ReceiveActor } } - /// - /// Resume on any unhandled exception inside the receive — the singleton - /// MUST stay alive even if the per-tick try/catch leaks. Restart would - /// reset the cursors (safe but wasteful); Resume preserves them. - /// + /// protected override SupervisorStrategy SupervisorStrategy() { return new OneForOneStrategy( diff --git a/src/ScadaLink.AuditLog/Central/SiteAuditTelemetryStalledTracker.cs b/src/ScadaLink.AuditLog/Central/SiteAuditTelemetryStalledTracker.cs index e1ed0fd2..849a1647 100644 --- a/src/ScadaLink.AuditLog/Central/SiteAuditTelemetryStalledTracker.cs +++ b/src/ScadaLink.AuditLog/Central/SiteAuditTelemetryStalledTracker.cs @@ -67,6 +67,7 @@ public sealed class SiteAuditTelemetryStalledTracker : IDisposable /// SiteAuditTelemetryStalledTrackerTests use the ActorSystem ctor /// via Akka.TestKit so they exercise the production subscribe path. /// + /// The actor system event stream to observe. public SiteAuditTelemetryStalledTracker(EventStream eventStream) : this(eventStream, snapshot: null) { @@ -80,6 +81,8 @@ public sealed class SiteAuditTelemetryStalledTracker : IDisposable /// subscribe (no actor system), but tests that drive the tracker via /// get the snapshot push for free. /// + /// The actor system event stream to observe. + /// Optional central health snapshot to mirror stalled-state changes into. public SiteAuditTelemetryStalledTracker(EventStream eventStream, AuditCentralHealthSnapshot? snapshot) { _eventStream = eventStream ?? throw new ArgumentNullException(nameof(eventStream)); @@ -94,6 +97,7 @@ public sealed class SiteAuditTelemetryStalledTracker : IDisposable /// updates the latched /// per-site map. tears the subscriber down. /// + /// The actor system whose EventStream will be subscribed. public SiteAuditTelemetryStalledTracker(ActorSystem actorSystem) : this(actorSystem, snapshot: null) { @@ -105,6 +109,8 @@ public sealed class SiteAuditTelemetryStalledTracker : IDisposable /// shared so the central health /// surface sees per-site stalled state without re-reading the tracker. /// + /// The actor system whose EventStream will be subscribed. + /// Optional central health snapshot to mirror stalled-state changes into. public SiteAuditTelemetryStalledTracker(ActorSystem actorSystem, AuditCentralHealthSnapshot? snapshot) { ArgumentNullException.ThrowIfNull(actorSystem); @@ -136,6 +142,7 @@ public sealed class SiteAuditTelemetryStalledTracker : IDisposable /// internally so tests against the bare-stream ctor can still drive the /// tracker, but the production path always goes through the actor. /// + /// The stalled-state change event to apply. internal void Apply(SiteAuditTelemetryStalledChanged evt) { if (evt is null) return; @@ -147,6 +154,9 @@ public sealed class SiteAuditTelemetryStalledTracker : IDisposable _snapshot?.ApplyStalled(evt); } + /// + /// Disposes the tracker and tears down the internal subscriber actor. + /// public void Dispose() { if (_disposed) return; @@ -173,12 +183,17 @@ public sealed class SiteAuditTelemetryStalledTracker : IDisposable { private readonly SiteAuditTelemetryStalledTracker _parent; + /// + /// Initializes a new subscriber actor that forwards events to the given tracker. + /// + /// The parent tracker whose method will be called for each event. public StalledChangedSubscriber(SiteAuditTelemetryStalledTracker parent) { _parent = parent; Receive(evt => _parent.Apply(evt)); } + /// protected override void PostStop() { Context.System.EventStream.Unsubscribe(Self, typeof(SiteAuditTelemetryStalledChanged)); diff --git a/src/ScadaLink.AuditLog/Payload/DefaultAuditPayloadFilter.cs b/src/ScadaLink.AuditLog/Payload/DefaultAuditPayloadFilter.cs index 0d22447f..dbf8bd8d 100644 --- a/src/ScadaLink.AuditLog/Payload/DefaultAuditPayloadFilter.cs +++ b/src/ScadaLink.AuditLog/Payload/DefaultAuditPayloadFilter.cs @@ -103,6 +103,9 @@ public sealed class DefaultAuditPayloadFilter : IAuditPayloadFilter /// counter from the container; a NoOp default is registered in /// . /// + /// Live-reloadable audit log options. + /// Logger for redaction diagnostics. + /// Optional counter incremented when a redaction operation fails; defaults to a no-op. public DefaultAuditPayloadFilter( IOptionsMonitor options, ILogger logger, @@ -113,6 +116,7 @@ public sealed class DefaultAuditPayloadFilter : IAuditPayloadFilter _failureCounter = failureCounter ?? new NoOpAuditRedactionFailureCounter(); } + /// public AuditEvent Apply(AuditEvent rawEvent) { try @@ -573,8 +577,11 @@ public sealed class DefaultAuditPayloadFilter : IAuditPayloadFilter { public static readonly CompiledRegex Invalid = new(null); + /// Gets the compiled , or null when the pattern was invalid. public Regex? Regex { get; } + /// Initializes a new wrapping the given compiled regex instance. + /// The pre-compiled regex, or null to represent an invalid pattern. public CompiledRegex(Regex? regex) => Regex = regex; } } diff --git a/src/ScadaLink.AuditLog/Payload/IAuditPayloadFilter.cs b/src/ScadaLink.AuditLog/Payload/IAuditPayloadFilter.cs index 45b7ee21..d9d410af 100644 --- a/src/ScadaLink.AuditLog/Payload/IAuditPayloadFilter.cs +++ b/src/ScadaLink.AuditLog/Payload/IAuditPayloadFilter.cs @@ -26,5 +26,6 @@ public interface IAuditPayloadFilter /// and return a filtered copy. MUST NOT throw — on internal failure, over-redact /// and surface the failure via the audit-redaction-failure health metric. /// + /// The unfiltered audit event to process. AuditEvent Apply(AuditEvent rawEvent); } diff --git a/src/ScadaLink.AuditLog/ServiceCollectionExtensions.cs b/src/ScadaLink.AuditLog/ServiceCollectionExtensions.cs index d97917fe..05aafce1 100644 --- a/src/ScadaLink.AuditLog/ServiceCollectionExtensions.cs +++ b/src/ScadaLink.AuditLog/ServiceCollectionExtensions.cs @@ -53,6 +53,9 @@ public static class ServiceCollectionExtensions /// and the site-→central telemetry collaborators. Idempotent re-registration /// is not supported; call this exactly once per . /// + /// The service collection to register into. + /// Application configuration used to bind and related options sections. + /// The same for chaining. public static IServiceCollection AddAuditLog(this IServiceCollection services, IConfiguration config) { ArgumentNullException.ThrowIfNull(services); @@ -252,6 +255,8 @@ public static class ServiceCollectionExtensions /// ships in M6. /// /// + /// The service collection to register into. + /// The same for chaining. public static IServiceCollection AddAuditLogHealthMetricsBridge(this IServiceCollection services) { ArgumentNullException.ThrowIfNull(services); @@ -290,6 +295,9 @@ public static class ServiceCollectionExtensions /// from any composition root" invariant. /// /// + /// The service collection to register into. + /// Application configuration used to bind partition maintenance options. + /// The same for chaining. public static IServiceCollection AddAuditLogCentralMaintenance( this IServiceCollection services, IConfiguration config) diff --git a/src/ScadaLink.AuditLog/Site/FallbackAuditWriter.cs b/src/ScadaLink.AuditLog/Site/FallbackAuditWriter.cs index 18511f12..321d6374 100644 --- a/src/ScadaLink.AuditLog/Site/FallbackAuditWriter.cs +++ b/src/ScadaLink.AuditLog/Site/FallbackAuditWriter.cs @@ -44,6 +44,11 @@ public sealed class FallbackAuditWriter : IAuditWriter /// registration /// always passes the real filter through. /// + /// The primary audit writer (typically the SQLite writer). + /// Drop-oldest ring buffer used to stash events when the primary fails. + /// Counter incremented on each primary failure for health reporting. + /// Logger for diagnostics. + /// Optional payload filter applied before writing; null means no filtering. public FallbackAuditWriter( IAuditWriter primary, RingBufferFallback ring, @@ -58,6 +63,7 @@ public sealed class FallbackAuditWriter : IAuditWriter _filter = filter; // null = no-op pass-through; see WriteAsync. } + /// public async Task WriteAsync(AuditEvent evt, CancellationToken ct = default) { ArgumentNullException.ThrowIfNull(evt); diff --git a/src/ScadaLink.AuditLog/Site/HealthMetricsAuditRedactionFailureCounter.cs b/src/ScadaLink.AuditLog/Site/HealthMetricsAuditRedactionFailureCounter.cs index 78454e3a..5365f56f 100644 --- a/src/ScadaLink.AuditLog/Site/HealthMetricsAuditRedactionFailureCounter.cs +++ b/src/ScadaLink.AuditLog/Site/HealthMetricsAuditRedactionFailureCounter.cs @@ -38,6 +38,8 @@ public sealed class HealthMetricsAuditRedactionFailureCounter : IAuditRedactionF { private readonly ISiteHealthCollector _collector; + /// Initializes the counter with the site health collector it bridges into. + /// The site health collector that receives the incremented redaction-failure count. public HealthMetricsAuditRedactionFailureCounter(ISiteHealthCollector collector) { _collector = collector ?? throw new ArgumentNullException(nameof(collector)); diff --git a/src/ScadaLink.AuditLog/Site/HealthMetricsAuditWriteFailureCounter.cs b/src/ScadaLink.AuditLog/Site/HealthMetricsAuditWriteFailureCounter.cs index 7284727b..9d4cf666 100644 --- a/src/ScadaLink.AuditLog/Site/HealthMetricsAuditWriteFailureCounter.cs +++ b/src/ScadaLink.AuditLog/Site/HealthMetricsAuditWriteFailureCounter.cs @@ -23,6 +23,10 @@ public sealed class HealthMetricsAuditWriteFailureCounter : IAuditWriteFailureCo { private readonly ISiteHealthCollector _collector; + /// + /// Initializes a new backed by the given health collector. + /// + /// The site health collector to increment on each audit write failure. public HealthMetricsAuditWriteFailureCounter(ISiteHealthCollector collector) { _collector = collector ?? throw new ArgumentNullException(nameof(collector)); diff --git a/src/ScadaLink.AuditLog/Site/RingBufferFallback.cs b/src/ScadaLink.AuditLog/Site/RingBufferFallback.cs index cf38dcdd..ac33e5e0 100644 --- a/src/ScadaLink.AuditLog/Site/RingBufferFallback.cs +++ b/src/ScadaLink.AuditLog/Site/RingBufferFallback.cs @@ -37,6 +37,8 @@ public sealed class RingBufferFallback /// public event Action? RingBufferOverflowed; + /// Initializes the ring buffer with the specified fixed capacity. + /// Maximum number of events to buffer; must be greater than zero. Default is 1024. public RingBufferFallback(int capacity = 1024) { if (capacity <= 0) @@ -62,6 +64,8 @@ public sealed class RingBufferFallback /// only when the ring has been /// -d. /// + /// The audit event to enqueue. + /// if enqueued (or enqueued with overflow); when the channel is completed. public bool TryEnqueue(AuditEvent evt) { ArgumentNullException.ThrowIfNull(evt); @@ -91,6 +95,7 @@ public sealed class RingBufferFallback /// been called. Callers that only want to drain what's currently buffered /// must call first. /// + /// Cancellation token to abort the async enumeration. public async IAsyncEnumerable DrainAsync( [EnumeratorCancellation] CancellationToken cancellationToken) { @@ -105,6 +110,8 @@ public sealed class RingBufferFallback /// recovery path. Returns /// when the ring is empty. /// + /// When this returns , contains the dequeued event. + /// if an event was dequeued; if the ring is empty. public bool TryDequeue(out AuditEvent evt) => _channel.Reader.TryRead(out evt!); /// diff --git a/src/ScadaLink.AuditLog/Site/SiteAuditBacklogReporter.cs b/src/ScadaLink.AuditLog/Site/SiteAuditBacklogReporter.cs index 955832a9..95823b01 100644 --- a/src/ScadaLink.AuditLog/Site/SiteAuditBacklogReporter.cs +++ b/src/ScadaLink.AuditLog/Site/SiteAuditBacklogReporter.cs @@ -52,6 +52,11 @@ public sealed class SiteAuditBacklogReporter : IHostedService, IDisposable private CancellationTokenSource? _cts; private Task? _loop; + /// Initializes a new instance of . + /// The site audit queue used to probe the backlog count. + /// The site health collector that receives the backlog snapshot. + /// Logger instance. + /// Poll interval override; defaults to (30 s). public SiteAuditBacklogReporter( ISiteAuditQueue queue, ISiteHealthCollector collector, diff --git a/src/ScadaLink.AuditLog/Site/SqliteAuditWriter.cs b/src/ScadaLink.AuditLog/Site/SqliteAuditWriter.cs index 3e3ed440..9173af6c 100644 --- a/src/ScadaLink.AuditLog/Site/SqliteAuditWriter.cs +++ b/src/ScadaLink.AuditLog/Site/SqliteAuditWriter.cs @@ -48,6 +48,11 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable private readonly Task _writerLoop; private bool _disposed; + /// Initializes a new instance of the SqliteAuditWriter class. + /// Configuration options for the audit writer. + /// Logger instance. + /// Node identity provider. + /// Optional connection string override. public SqliteAuditWriter( IOptions options, ILogger logger, @@ -186,14 +191,7 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable alter.ExecuteNonQuery(); } - /// - /// Enqueue an event for durable persistence. The returned - /// completes once the event has been INSERTed (or, in the duplicate-EventId - /// case, recognised as already present); it faults only if the writer loop - /// itself collapses. The enqueue side never blocks on disk I/O — it only - /// awaits the bounded channel's back-pressure when the writer is briefly - /// behind. - /// + /// public Task WriteAsync(AuditEvent evt, CancellationToken ct = default) { ArgumentNullException.ThrowIfNull(evt); @@ -386,12 +384,7 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable } } - /// - /// Returns up to rows in , - /// oldest first, with - /// as the deterministic tiebreaker. Called by Bundle D's site telemetry - /// actor to build a batch for the gRPC push. - /// + /// public Task> ReadPendingAsync(int limit, CancellationToken ct = default) { if (limit <= 0) @@ -443,6 +436,8 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable /// , which also returns /// rows). /// + /// Maximum number of rows to return. + /// Cancellation token. public Task> ReadForwardedAsync(int limit, CancellationToken ct = default) { if (limit <= 0) @@ -481,11 +476,7 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable } } - /// - /// Flips the supplied EventIds from to - /// in a single UPDATE. Non-existent - /// or already-forwarded ids are no-ops. - /// + /// public Task MarkForwardedAsync(IReadOnlyList eventIds, CancellationToken ct = default) { ArgumentNullException.ThrowIfNull(eventIds); @@ -520,15 +511,7 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable } } - /// - /// M6 reconciliation-pull read: returns up to rows - /// whose OccurredAtUtc >= sinceUtc and whose - /// is still or - /// . Forwarded rows are included so the - /// brief race window between a site-Forwarded ack and central ingest cannot - /// silently drop rows; central dedups on . - /// Ordered oldest first, EventId tiebreaker. - /// + /// public Task> ReadPendingSinceAsync( DateTime sinceUtc, int batchSize, CancellationToken ct = default) { @@ -575,13 +558,7 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable } } - /// - /// M6 reconciliation-pull commit: flips the supplied EventIds to - /// , but ONLY for rows currently in - /// or . - /// Rows already in are left untouched - /// (idempotent re-call). Non-existent ids are silent no-ops. - /// + /// public Task MarkReconciledAsync(IReadOnlyList eventIds, CancellationToken ct = default) { ArgumentNullException.ThrowIfNull(eventIds); @@ -616,22 +593,7 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable } } - /// - /// M6 Bundle E (T6) health-metric surface: returns a point-in-time snapshot - /// of the site queue's pending count, the oldest pending row's - /// , and the on-disk file size. Called - /// by the site-side SiteAuditBacklogReporter hosted service on its - /// 30 s tick to refresh the SiteHealthReport.SiteAuditBacklog field. - /// - /// - /// The pending-count + oldest-row queries run inside the same write lock as - /// the hot-path INSERT batch so the snapshot is consistent against the - /// connection's view (no torn read of an in-flight transaction). The on-disk - /// size lookup happens OUTSIDE the lock — it's a stat() call on the file - /// path and doesn't touch the connection. In-memory and missing files - /// return 0 bytes (the snapshot is for ops dashboards, not a correctness - /// invariant). - /// + /// public Task GetBacklogStatsAsync(CancellationToken ct = default) { int pendingCount; @@ -731,11 +693,13 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable }; } + /// Disposes the audit writer and releases resources. public void Dispose() { DisposeAsync().AsTask().GetAwaiter().GetResult(); } + /// Asynchronously disposes the audit writer and releases resources. public async ValueTask DisposeAsync() { Task? writerLoop; @@ -779,13 +743,17 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable /// An audit event awaiting persistence by the background writer. private sealed class PendingAuditEvent { + /// Initializes a new instance of the PendingAuditEvent class. + /// The audit event to persist. public PendingAuditEvent(AuditEvent evt) { Event = evt; Completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); } + /// The audit event to persist. public AuditEvent Event { get; } + /// Task completion source for write completion signaling. public TaskCompletionSource Completion { get; } } } diff --git a/src/ScadaLink.AuditLog/Site/Telemetry/CachedCallLifecycleBridge.cs b/src/ScadaLink.AuditLog/Site/Telemetry/CachedCallLifecycleBridge.cs index 299f7ce1..09be7ab0 100644 --- a/src/ScadaLink.AuditLog/Site/Telemetry/CachedCallLifecycleBridge.cs +++ b/src/ScadaLink.AuditLog/Site/Telemetry/CachedCallLifecycleBridge.cs @@ -48,6 +48,10 @@ public sealed class CachedCallLifecycleBridge : ICachedCallLifecycleObserver /// private readonly INodeIdentityProvider? _nodeIdentity; + /// Initializes a new with the given telemetry forwarder, logger, and optional node identity provider. + /// The telemetry forwarder used to ship cached-call lifecycle events to central. + /// Logger for bridge diagnostics. + /// Optional node identity provider used to stamp SourceNode on emitted telemetry rows. public CachedCallLifecycleBridge( ICachedCallTelemetryForwarder forwarder, ILogger logger, diff --git a/src/ScadaLink.AuditLog/Site/Telemetry/CachedCallTelemetryForwarder.cs b/src/ScadaLink.AuditLog/Site/Telemetry/CachedCallTelemetryForwarder.cs index 897f047d..dd7f8812 100644 --- a/src/ScadaLink.AuditLog/Site/Telemetry/CachedCallTelemetryForwarder.cs +++ b/src/ScadaLink.AuditLog/Site/Telemetry/CachedCallTelemetryForwarder.cs @@ -70,6 +70,10 @@ public sealed class CachedCallTelemetryForwarder : ICachedCallTelemetryForwarder /// registration. Production site nodes wire both — the central lazy /// resolution is a no-op path kept symmetric with the M2 writer chain. /// + /// Writer used to persist audit events from the telemetry packet. + /// Optional store for updating operation tracking state; null on central nodes. + /// Logger for this forwarder. + /// Optional provider of the current node name stamped on emitted rows. public CachedCallTelemetryForwarder( IAuditWriter auditWriter, IOperationTrackingStore? trackingStore, @@ -82,13 +86,7 @@ public sealed class CachedCallTelemetryForwarder : ICachedCallTelemetryForwarder _nodeIdentity = nodeIdentity; } - /// - /// Fan out one combined-telemetry packet to the audit writer and the - /// tracking store. Returns once both halves have been attempted (success - /// OR logged failure). NEVER throws — exceptions are caught per-half and - /// logged at warning level so the calling script's outbound action is not - /// disturbed. - /// + /// public async Task ForwardAsync(CachedCallTelemetry telemetry, CancellationToken ct = default) { ArgumentNullException.ThrowIfNull(telemetry); diff --git a/src/ScadaLink.AuditLog/Site/Telemetry/ISiteStreamAuditClient.cs b/src/ScadaLink.AuditLog/Site/Telemetry/ISiteStreamAuditClient.cs index b6b27f54..07e58e5d 100644 --- a/src/ScadaLink.AuditLog/Site/Telemetry/ISiteStreamAuditClient.cs +++ b/src/ScadaLink.AuditLog/Site/Telemetry/ISiteStreamAuditClient.cs @@ -20,6 +20,8 @@ public interface ISiteStreamAuditClient /// /// in the site SQLite queue. /// + /// The batch of audit events to forward. + /// Cancellation token for the operation. Task IngestAuditEventsAsync(AuditEventBatch batch, CancellationToken ct); /// @@ -38,5 +40,7 @@ public interface ISiteStreamAuditClient /// DI default (used by central and test composition roots) returns an empty /// ack so no rows are flipped. /// + /// The batch of cached-call telemetry packets to forward. + /// Cancellation token for the operation. Task IngestCachedTelemetryAsync(CachedTelemetryBatch batch, CancellationToken ct); } diff --git a/src/ScadaLink.AuditLog/Site/Telemetry/SiteAuditTelemetryActor.cs b/src/ScadaLink.AuditLog/Site/Telemetry/SiteAuditTelemetryActor.cs index e903e0a4..68cc3cd7 100644 --- a/src/ScadaLink.AuditLog/Site/Telemetry/SiteAuditTelemetryActor.cs +++ b/src/ScadaLink.AuditLog/Site/Telemetry/SiteAuditTelemetryActor.cs @@ -43,6 +43,11 @@ public class SiteAuditTelemetryActor : ReceiveActor private readonly ILogger _logger; private ICancelable? _pendingTick; + /// Initializes the actor with its drain queue, gRPC client, options, and logger. + /// The site-local SQLite audit queue to drain. + /// The gRPC client used to push audit events to central. + /// Telemetry options controlling drain intervals and batch size. + /// Logger instance. public SiteAuditTelemetryActor( ISiteAuditQueue queue, ISiteStreamAuditClient client, @@ -62,6 +67,7 @@ public class SiteAuditTelemetryActor : ReceiveActor ReceiveAsync(_ => OnDrainAsync()); } + /// protected override void PreStart() { base.PreStart(); @@ -71,6 +77,7 @@ public class SiteAuditTelemetryActor : ReceiveActor ScheduleNext(TimeSpan.FromSeconds(_options.BusyIntervalSeconds)); } + /// protected override void PostStop() { _pendingTick?.Cancel(); diff --git a/src/ScadaLink.CLI/CliConfig.cs b/src/ScadaLink.CLI/CliConfig.cs index 91fff5c0..dd59e585 100644 --- a/src/ScadaLink.CLI/CliConfig.cs +++ b/src/ScadaLink.CLI/CliConfig.cs @@ -2,9 +2,14 @@ using System.Text.Json; namespace ScadaLink.CLI; +/// +/// Resolved CLI configuration combining config file values, environment variable overrides, and per-invocation credentials. +/// public class CliConfig { + /// Base URL of the ScadaLink Management API (e.g. http://localhost:9000). public string? ManagementUrl { get; set; } + /// Default output format for CLI commands; defaults to "json". public string DefaultFormat { get; set; } = "json"; /// @@ -21,6 +26,10 @@ public class CliConfig /// public string? Password { get; set; } + /// + /// Loads CLI configuration by merging the config file, environment variables, and credential env vars. + /// + /// A populated instance. public static CliConfig Load() { var config = new CliConfig(); @@ -66,7 +75,9 @@ public class CliConfig private class CliConfigFile { + /// Management API URL from the config file. public string? ManagementUrl { get; set; } + /// Default output format from the config file. public string? DefaultFormat { get; set; } } } diff --git a/src/ScadaLink.CLI/Commands/ApiMethodCommands.cs b/src/ScadaLink.CLI/Commands/ApiMethodCommands.cs index cde69a64..eb79980a 100644 --- a/src/ScadaLink.CLI/Commands/ApiMethodCommands.cs +++ b/src/ScadaLink.CLI/Commands/ApiMethodCommands.cs @@ -6,6 +6,13 @@ namespace ScadaLink.CLI.Commands; public static class ApiMethodCommands { + /// + /// Builds the api-method CLI command group with subcommands for managing inbound API methods. + /// + /// Global option for the management URL. + /// Global option for the output format. + /// Global option for the authentication username. + /// Global option for the authentication password. public static Command Build(Option urlOption, Option formatOption, Option usernameOption, Option passwordOption) { var command = new Command("api-method") { Description = "Manage inbound API methods" }; diff --git a/src/ScadaLink.CLI/Commands/AuditCommandHelpers.cs b/src/ScadaLink.CLI/Commands/AuditCommandHelpers.cs index 876db62b..706ad97a 100644 --- a/src/ScadaLink.CLI/Commands/AuditCommandHelpers.cs +++ b/src/ScadaLink.CLI/Commands/AuditCommandHelpers.cs @@ -9,12 +9,37 @@ namespace ScadaLink.CLI.Commands; /// public sealed class AuditConnection { + /// + /// The management URL, or null if resolution failed. + /// public string? Url { get; init; } + + /// + /// The username for authentication, or null if resolution failed. + /// public string? Username { get; init; } + + /// + /// The password for authentication, or null if resolution failed. + /// public string? Password { get; init; } + + /// + /// Error message if resolution failed, or null. + /// public string? Error { get; init; } + + /// + /// Error code if resolution failed, or null. + /// public string? ErrorCode { get; init; } + /// + /// Creates a failed connection with an error message and code. + /// + /// The error message. + /// The error code. + /// A failed AuditConnection. public static AuditConnection Fail(string error, string code) => new() { Error = error, ErrorCode = code }; } @@ -28,6 +53,14 @@ public sealed class AuditConnection /// public static class AuditCommandHelpers { + /// + /// Resolves management API connection details from command line arguments, config file, or environment variables. + /// + /// The parsed command line arguments. + /// The URL option. + /// The username option. + /// The password option. + /// The resolved connection details, or a failure result. public static AuditConnection ResolveConnection( ParseResult result, Option urlOption, @@ -67,6 +100,12 @@ public static class AuditCommandHelpers return new AuditConnection { Url = url, Username = username, Password = password }; } + /// + /// Resolves the output format from command line arguments, config file, or defaults to "table". + /// + /// The parsed command line arguments. + /// The format option. + /// The resolved format string. public static string ResolveFormat(ParseResult result, Option formatOption) => CommandHelpers.ResolveFormat(result, formatOption, CliConfig.Load()); } diff --git a/src/ScadaLink.CLI/Commands/AuditCommands.cs b/src/ScadaLink.CLI/Commands/AuditCommands.cs index 2951e59e..1c7b0d13 100644 --- a/src/ScadaLink.CLI/Commands/AuditCommands.cs +++ b/src/ScadaLink.CLI/Commands/AuditCommands.cs @@ -11,6 +11,13 @@ namespace ScadaLink.CLI.Commands; /// public static class AuditCommands { + /// + /// Builds the audit command group with query, export, and verify-chain sub-commands. + /// + /// Global --url option for the management API endpoint. + /// Global --format option for output format. + /// Global --username option for authentication. + /// Global --password option for authentication. public static Command Build(Option urlOption, Option formatOption, Option usernameOption, Option passwordOption) { var command = new Command("audit") { Description = "Query and export the centralized audit log" }; diff --git a/src/ScadaLink.CLI/Commands/AuditExportHelpers.cs b/src/ScadaLink.CLI/Commands/AuditExportHelpers.cs index 4a36fc53..b693d4aa 100644 --- a/src/ScadaLink.CLI/Commands/AuditExportHelpers.cs +++ b/src/ScadaLink.CLI/Commands/AuditExportHelpers.cs @@ -13,15 +13,45 @@ namespace ScadaLink.CLI.Commands; /// public sealed class AuditExportArgs { + /// + /// Start timestamp for the export time window. + /// public string Since { get; set; } = string.Empty; + /// + /// End timestamp for the export time window. + /// public string Until { get; set; } = string.Empty; + /// + /// Export format (e.g., 'json', 'csv', 'parquet'). + /// public string Format { get; set; } = string.Empty; + /// + /// Output file path for the exported audit log. + /// public string Output { get; set; } = string.Empty; + /// + /// Channel filter values (repeated query parameter). + /// public string[] Channel { get; set; } = Array.Empty(); + /// + /// Kind filter values (repeated query parameter). + /// public string[] Kind { get; set; } = Array.Empty(); + /// + /// Status filter values (repeated query parameter). + /// public string[] Status { get; set; } = Array.Empty(); + /// + /// Site identifier filter values (repeated query parameter). + /// public string[] Site { get; set; } = Array.Empty(); + /// + /// Optional target system filter. + /// public string? Target { get; set; } + /// + /// Optional actor/user filter. + /// public string? Actor { get; set; } } @@ -41,6 +71,8 @@ public static class AuditExportHelpers /// server's multi-value IN (…) filter receives the full set — mirroring /// . /// + /// The export arguments containing filters and format. + /// The current time for resolving relative time specifications. public static string BuildQueryString(AuditExportArgs args, DateTimeOffset now) { var parts = new List(); @@ -79,6 +111,10 @@ public static class AuditExportHelpers /// A 501 Not Implemented (parquet not yet supported server-side) prints the /// server message and returns a non-zero exit code. /// + /// The management HTTP client for API communication. + /// The export arguments containing filters and output file path. + /// Text writer for command output messages. + /// The current time for resolving relative time specifications. public static async Task RunExportAsync( ManagementHttpClient client, AuditExportArgs args, TextWriter output, DateTimeOffset now) { diff --git a/src/ScadaLink.CLI/Commands/AuditFormatter.cs b/src/ScadaLink.CLI/Commands/AuditFormatter.cs index ebfb600c..9cb053eb 100644 --- a/src/ScadaLink.CLI/Commands/AuditFormatter.cs +++ b/src/ScadaLink.CLI/Commands/AuditFormatter.cs @@ -10,6 +10,8 @@ namespace ScadaLink.CLI.Commands; public interface IAuditFormatter { /// Renders one page of events. Called once per fetched page. + /// The audit events on this page. + /// Writer to render the formatted output to. void WritePage(IReadOnlyList events, TextWriter output); } @@ -21,6 +23,7 @@ public sealed class JsonLinesAuditFormatter : IAuditFormatter { private static readonly JsonSerializerOptions Compact = new() { WriteIndented = false }; + /// public void WritePage(IReadOnlyList events, TextWriter output) { foreach (var evt in events) @@ -35,6 +38,11 @@ public sealed class JsonLinesAuditFormatter : IAuditFormatter /// public static class AuditFormatterFactory { + /// + /// Returns an for the given format name. + /// + /// Format name; table selects the table formatter, any other value selects JSONL. + /// Writer for notice messages emitted during formatting. public static IAuditFormatter Create(string format, TextWriter notices) { if (string.Equals(format, "table", StringComparison.OrdinalIgnoreCase)) diff --git a/src/ScadaLink.CLI/Commands/AuditLogCommands.cs b/src/ScadaLink.CLI/Commands/AuditLogCommands.cs index eee61435..72551dab 100644 --- a/src/ScadaLink.CLI/Commands/AuditLogCommands.cs +++ b/src/ScadaLink.CLI/Commands/AuditLogCommands.cs @@ -32,6 +32,8 @@ public static class AuditLogCommands /// an alias of audit-config — so this only adds the migration warning. /// Factored out of Program.cs so it is unit-testable without spawning a process. /// + /// The raw command-line arguments passed to the CLI. + /// The text writer to emit the deprecation warning to. public static void WriteDeprecationWarningIfNeeded(string[] args, TextWriter stderr) { if (args.Length > 0 @@ -41,6 +43,13 @@ public static class AuditLogCommands } } + /// + /// Builds the audit-config command (with the deprecated audit-log alias) and its subcommands. + /// + /// Global management URL option. + /// Global output format option. + /// Global username option. + /// Global password option. public static Command Build(Option urlOption, Option formatOption, Option usernameOption, Option passwordOption) { var command = new Command("audit-config") { Description = "Query the configuration-change audit log" }; diff --git a/src/ScadaLink.CLI/Commands/AuditQueryHelpers.cs b/src/ScadaLink.CLI/Commands/AuditQueryHelpers.cs index f8640ebc..173cce1e 100644 --- a/src/ScadaLink.CLI/Commands/AuditQueryHelpers.cs +++ b/src/ScadaLink.CLI/Commands/AuditQueryHelpers.cs @@ -15,18 +15,31 @@ namespace ScadaLink.CLI.Commands; /// public sealed class AuditQueryArgs { + /// Start time spec (relative like 1h, or absolute ISO-8601). public string? Since { get; set; } + /// End time spec (relative like 7d, or absolute ISO-8601). public string? Until { get; set; } + /// Multi-valued channel filter. public string[] Channel { get; set; } = Array.Empty(); + /// Multi-valued audit event kind filter. public string[] Kind { get; set; } = Array.Empty(); + /// Multi-valued status filter. public string[] Status { get; set; } = Array.Empty(); + /// Multi-valued site ID filter. public string[] Site { get; set; } = Array.Empty(); + /// Target system or service filter. public string? Target { get; set; } + /// Actor (user or system) filter. public string? Actor { get; set; } + /// Operation correlation ID filter. public string? CorrelationId { get; set; } + /// Script execution ID filter. public string? ExecutionId { get; set; } + /// Parent execution ID filter. public string? ParentExecutionId { get; set; } + /// Filter for errors only (status=Failed). public bool ErrorsOnly { get; set; } + /// Page size for pagination. public int PageSize { get; set; } = 100; } @@ -45,6 +58,8 @@ public static class AuditQueryHelpers /// relative offset (30s, 15m, 1h, 7d) interpreted as /// minus the offset, or an absolute ISO-8601 timestamp. /// + /// The time specification string. + /// The current time used as reference for relative specs. /// The spec is neither a known relative form nor a parseable ISO-8601 timestamp. public static DateTimeOffset ResolveTimeSpec(string spec, DateTimeOffset now) { @@ -84,6 +99,10 @@ public static class AuditQueryHelpers /// server's multi-value IN (…) filter receives the full set. --errors-only /// maps to a single status=Failed and overrides any explicit --status. /// + /// The audit query arguments. + /// The current time for resolving relative time specs. + /// Optional keyset cursor timestamp. + /// Optional keyset cursor event ID. public static string BuildQueryString( AuditQueryArgs args, DateTimeOffset now, DateTimeOffset? afterOccurredAtUtc, string? afterEventId) { @@ -144,6 +163,12 @@ public static class AuditQueryHelpers /// follows nextCursor until the server returns a null cursor. Returns the /// process exit code (0 success, non-zero on HTTP/transport error). /// + /// The management HTTP client. + /// The audit query arguments. + /// Whether to follow pagination cursors. + /// The audit result formatter. + /// The output writer for results. + /// The current time for resolving relative time specs. public static async Task RunQueryAsync( ManagementHttpClient client, AuditQueryArgs args, diff --git a/src/ScadaLink.CLI/Commands/AuditVerifyChainHelpers.cs b/src/ScadaLink.CLI/Commands/AuditVerifyChainHelpers.cs index 45aa9642..12cf41b5 100644 --- a/src/ScadaLink.CLI/Commands/AuditVerifyChainHelpers.cs +++ b/src/ScadaLink.CLI/Commands/AuditVerifyChainHelpers.cs @@ -13,6 +13,7 @@ public static class AuditVerifyChainHelpers /// Returns true if is a well-formed YYYY-MM value /// with a real month (01-12). A malformed month (e.g. 2026-13) is rejected. /// + /// The month string to validate in YYYY-MM format. public static bool IsValidMonth(string? month) => !string.IsNullOrWhiteSpace(month) && DateTime.TryParseExact(month, "yyyy-MM", CultureInfo.InvariantCulture, diff --git a/src/ScadaLink.CLI/Commands/BundleCommands.cs b/src/ScadaLink.CLI/Commands/BundleCommands.cs index 0eb595ac..eb2002e8 100644 --- a/src/ScadaLink.CLI/Commands/BundleCommands.cs +++ b/src/ScadaLink.CLI/Commands/BundleCommands.cs @@ -15,6 +15,12 @@ public static class BundleCommands { private static readonly TimeSpan BundleCommandTimeout = TimeSpan.FromMinutes(5); + /// Builds the bundle command group with export, preview, and import sub-commands. + /// Shared management URL option. + /// Shared output format option. + /// Shared username option. + /// Shared password option. + /// The configured for the bundle group. public static Command Build( Option urlOption, Option formatOption, Option usernameOption, Option passwordOption) diff --git a/src/ScadaLink.CLI/Commands/CommandHelpers.cs b/src/ScadaLink.CLI/Commands/CommandHelpers.cs index ccb73ed1..f1689fa8 100644 --- a/src/ScadaLink.CLI/Commands/CommandHelpers.cs +++ b/src/ScadaLink.CLI/Commands/CommandHelpers.cs @@ -7,6 +7,16 @@ namespace ScadaLink.CLI.Commands; internal static class CommandHelpers { + /// + /// Resolves the management URL, credentials, and output format, then sends + /// to the management API and returns the process exit code. + /// + /// Parsed command-line result from which option values are read. + /// Option that supplies the management URL override. + /// Option that supplies the output format override. + /// Option that supplies the username override. + /// Option that supplies the password override. + /// The management command object to send. internal static async Task ExecuteCommandAsync( ParseResult result, Option urlOption, @@ -69,6 +79,9 @@ internal static class CommandHelpers /// is used, otherwise json. The --format option must not declare a /// DefaultValueFactory — that would mask whether the flag was supplied. /// + /// Parsed command-line result. + /// The --format option definition. + /// Loaded CLI configuration providing the default format fallback. internal static string ResolveFormat(ParseResult result, Option formatOption, CliConfig config) { // GetResult returns non-null only when the option was actually present on the @@ -87,6 +100,8 @@ internal static class CommandHelpers /// Resolves a single credential: an explicit command-line value wins, otherwise the /// environment-variable fallback (from ) is used. /// + /// Value supplied on the command line, or null if absent. + /// Fallback value from the config file or environment variable. internal static string? ResolveCredential(string? commandLineValue, string? envValue) => string.IsNullOrWhiteSpace(commandLineValue) ? envValue : commandLineValue; @@ -96,6 +111,7 @@ internal static class CommandHelpers /// new Uri(...) in the constructor and throw /// an unhandled . /// + /// URL string to validate. internal static bool IsValidManagementUrl(string? url) { if (string.IsNullOrWhiteSpace(url)) @@ -105,6 +121,11 @@ internal static class CommandHelpers && (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps); } + /// + /// Writes the management response to stdout and returns the appropriate process exit code. + /// + /// Response received from the management API. + /// Output format (json or table). internal static int HandleResponse(ManagementResponse response, string format) { if (response.JsonData != null) diff --git a/src/ScadaLink.CLI/Commands/DataConnectionCommands.cs b/src/ScadaLink.CLI/Commands/DataConnectionCommands.cs index e9e66846..059bed60 100644 --- a/src/ScadaLink.CLI/Commands/DataConnectionCommands.cs +++ b/src/ScadaLink.CLI/Commands/DataConnectionCommands.cs @@ -6,6 +6,13 @@ namespace ScadaLink.CLI.Commands; public static class DataConnectionCommands { + /// + /// Builds the data-connection command group and all its subcommands. + /// + /// Global management URL option. + /// Global output format option. + /// Global username option. + /// Global password option. public static Command Build(Option urlOption, Option formatOption, Option usernameOption, Option passwordOption) { var command = new Command("data-connection") { Description = "Manage data connections" }; diff --git a/src/ScadaLink.CLI/Commands/DbConnectionCommands.cs b/src/ScadaLink.CLI/Commands/DbConnectionCommands.cs index 4bbe2342..af77f32b 100644 --- a/src/ScadaLink.CLI/Commands/DbConnectionCommands.cs +++ b/src/ScadaLink.CLI/Commands/DbConnectionCommands.cs @@ -4,8 +4,17 @@ using ScadaLink.Commons.Messages.Management; namespace ScadaLink.CLI.Commands; +/// +/// CLI commands for managing database connection definitions. +/// public static class DbConnectionCommands { + /// Builds the db-connection command with list, get, create, update, and delete sub-commands. + /// Global URL option. + /// Global output format option. + /// Global username option. + /// Global password option. + /// The configured db-connection command. public static Command Build(Option urlOption, Option formatOption, Option usernameOption, Option passwordOption) { var command = new Command("db-connection") { Description = "Manage database connections" }; diff --git a/src/ScadaLink.CLI/Commands/DebugCommands.cs b/src/ScadaLink.CLI/Commands/DebugCommands.cs index e080df7b..ba6b55b1 100644 --- a/src/ScadaLink.CLI/Commands/DebugCommands.cs +++ b/src/ScadaLink.CLI/Commands/DebugCommands.cs @@ -10,6 +10,11 @@ namespace ScadaLink.CLI.Commands; public static class DebugCommands { + /// Builds the debug command with its subcommands using the given shared CLI options. + /// Shared management URL option. + /// Shared output format option. + /// Shared username option for authentication. + /// Shared password option for authentication. public static Command Build(Option urlOption, Option formatOption, Option usernameOption, Option passwordOption) { var command = new Command("debug") { Description = "Runtime debugging" }; diff --git a/src/ScadaLink.CLI/Commands/DebugStreamHelpers.cs b/src/ScadaLink.CLI/Commands/DebugStreamHelpers.cs index 845e428c..1a6bac1d 100644 --- a/src/ScadaLink.CLI/Commands/DebugStreamHelpers.cs +++ b/src/ScadaLink.CLI/Commands/DebugStreamHelpers.cs @@ -25,6 +25,8 @@ internal static class DebugStreamHelpers /// (Ctrl+C during connect) is a graceful shutdown — exit 0, no error printed. /// Anything else is a genuine connection failure — exit 1. /// + /// The exception thrown by HubConnection.StartAsync. + /// True when the user requested cancellation (Ctrl+C) before the exception was thrown. internal static ConnectFailure ClassifyConnectFailure(Exception ex, bool cancellationRequested) { if (cancellationRequested && ex is OperationCanceledException) @@ -40,6 +42,7 @@ internal static class DebugStreamHelpers /// a brief grace period covers a termination that races with cancellation. If no /// result is ever produced (pure Ctrl+C), the stream ended gracefully — exit 0. /// + /// The task whose result is the intended exit code, set by OnStreamTerminated or the Closed handler. internal static async Task ResolveStreamExitCodeAsync(Task exitTask) { if (exitTask.IsCompletedSuccessfully) diff --git a/src/ScadaLink.CLI/Commands/DeployCommands.cs b/src/ScadaLink.CLI/Commands/DeployCommands.cs index 5957aac0..6e587e88 100644 --- a/src/ScadaLink.CLI/Commands/DeployCommands.cs +++ b/src/ScadaLink.CLI/Commands/DeployCommands.cs @@ -6,6 +6,13 @@ namespace ScadaLink.CLI.Commands; public static class DeployCommands { + /// + /// Builds the deploy command group with all sub-commands. + /// + /// Global management URL option. + /// Global output format option. + /// Global username option. + /// Global password option. public static Command Build(Option urlOption, Option formatOption, Option usernameOption, Option passwordOption) { var command = new Command("deploy") { Description = "Deployment operations" }; diff --git a/src/ScadaLink.CLI/Commands/ExternalSystemCommands.cs b/src/ScadaLink.CLI/Commands/ExternalSystemCommands.cs index 30a58956..4a32a3bb 100644 --- a/src/ScadaLink.CLI/Commands/ExternalSystemCommands.cs +++ b/src/ScadaLink.CLI/Commands/ExternalSystemCommands.cs @@ -6,6 +6,13 @@ namespace ScadaLink.CLI.Commands; public static class ExternalSystemCommands { + /// + /// Builds the external-system CLI command group with subcommands for managing external systems. + /// + /// Global option for the management URL. + /// Global option for the output format. + /// Global option for the authentication username. + /// Global option for the authentication password. public static Command Build(Option urlOption, Option formatOption, Option usernameOption, Option passwordOption) { var command = new Command("external-system") { Description = "Manage external systems" }; diff --git a/src/ScadaLink.CLI/Commands/HealthCommands.cs b/src/ScadaLink.CLI/Commands/HealthCommands.cs index f3ac6e45..c387345a 100644 --- a/src/ScadaLink.CLI/Commands/HealthCommands.cs +++ b/src/ScadaLink.CLI/Commands/HealthCommands.cs @@ -6,6 +6,13 @@ namespace ScadaLink.CLI.Commands; public static class HealthCommands { + /// + /// Builds the health command group with summary, site, event-log, and parked-message sub-commands. + /// + /// Global --url option for the management API endpoint. + /// Global --format option for output format. + /// Global --username option for authentication. + /// Global --password option for authentication. public static Command Build(Option urlOption, Option formatOption, Option usernameOption, Option passwordOption) { var command = new Command("health") { Description = "Health monitoring" }; diff --git a/src/ScadaLink.CLI/Commands/InstanceCommands.cs b/src/ScadaLink.CLI/Commands/InstanceCommands.cs index fd77b261..955b256e 100644 --- a/src/ScadaLink.CLI/Commands/InstanceCommands.cs +++ b/src/ScadaLink.CLI/Commands/InstanceCommands.cs @@ -6,6 +6,14 @@ namespace ScadaLink.CLI.Commands; public static class InstanceCommands { + /// + /// Builds the instance command and its subcommands. + /// + /// The URL option. + /// The format option. + /// The username option. + /// The password option. + /// The instance command with all subcommands. public static Command Build(Option urlOption, Option formatOption, Option usernameOption, Option passwordOption) { var command = new Command("instance") { Description = "Manage instances" }; @@ -71,6 +79,10 @@ public static class InstanceCommands /// throwing when the JSON is malformed, a pair has the wrong arity, or an element /// has the wrong type. /// + /// The JSON string to parse. + /// The parsed bindings list, or null if parsing fails. + /// The error message if parsing fails, or null on success. + /// True if parsing succeeded; false otherwise. internal static bool TryParseBindings( string json, out List? bindings, @@ -126,6 +138,10 @@ public static class InstanceCommands /// false with a descriptive instead of throwing /// when the JSON is malformed or null. /// + /// The JSON string to parse. + /// The parsed overrides dictionary, or null if parsing fails. + /// The error message if parsing fails, or null on success. + /// True if parsing succeeded; false otherwise. internal static bool TryParseOverrides( string json, out Dictionary? overrides, diff --git a/src/ScadaLink.CLI/Commands/NotificationCommands.cs b/src/ScadaLink.CLI/Commands/NotificationCommands.cs index 685599b3..7cddf94b 100644 --- a/src/ScadaLink.CLI/Commands/NotificationCommands.cs +++ b/src/ScadaLink.CLI/Commands/NotificationCommands.cs @@ -6,6 +6,13 @@ namespace ScadaLink.CLI.Commands; public static class NotificationCommands { + /// + /// Builds the notification command group with sub-commands for managing notification lists and SMTP configuration. + /// + /// Global --url option for the management API endpoint. + /// Global --format option for output format. + /// Global --username option for authentication. + /// Global --password option for authentication. public static Command Build(Option urlOption, Option formatOption, Option usernameOption, Option passwordOption) { var command = new Command("notification") { Description = "Manage notification lists" }; @@ -123,6 +130,7 @@ public static class NotificationCommands /// invocation. The optional --tls-mode / --credentials flags map to /// null when omitted so the server-side handler preserves the existing values. /// + /// The parsed command-line result from the smtp update invocation. internal static UpdateSmtpConfigCommand BuildUpdateSmtpConfigCommand(ParseResult result) { var id = result.GetValue(SmtpIdOption); diff --git a/src/ScadaLink.CLI/Commands/SecurityCommands.cs b/src/ScadaLink.CLI/Commands/SecurityCommands.cs index deea7e65..68b2b729 100644 --- a/src/ScadaLink.CLI/Commands/SecurityCommands.cs +++ b/src/ScadaLink.CLI/Commands/SecurityCommands.cs @@ -6,6 +6,13 @@ namespace ScadaLink.CLI.Commands; public static class SecurityCommands { + /// + /// Builds the security command group with API key, role mapping, and scope rule subcommands. + /// + /// Shared management URL option. + /// Shared output format option. + /// Shared username option for authentication. + /// Shared password option for authentication. public static Command Build(Option urlOption, Option formatOption, Option usernameOption, Option passwordOption) { var command = new Command("security") { Description = "Manage security settings" }; diff --git a/src/ScadaLink.CLI/Commands/SharedScriptCommands.cs b/src/ScadaLink.CLI/Commands/SharedScriptCommands.cs index e64767af..c64577c3 100644 --- a/src/ScadaLink.CLI/Commands/SharedScriptCommands.cs +++ b/src/ScadaLink.CLI/Commands/SharedScriptCommands.cs @@ -6,6 +6,12 @@ namespace ScadaLink.CLI.Commands; public static class SharedScriptCommands { + /// Builds the shared-script command group with list, get, create, update, and delete sub-commands. + /// Shared management URL option. + /// Shared output format option. + /// Shared username option. + /// Shared password option. + /// The configured for the shared-script group. public static Command Build(Option urlOption, Option formatOption, Option usernameOption, Option passwordOption) { var command = new Command("shared-script") { Description = "Manage shared scripts" }; diff --git a/src/ScadaLink.CLI/Commands/SiteCommands.cs b/src/ScadaLink.CLI/Commands/SiteCommands.cs index ff43eff4..a8cf32e4 100644 --- a/src/ScadaLink.CLI/Commands/SiteCommands.cs +++ b/src/ScadaLink.CLI/Commands/SiteCommands.cs @@ -6,6 +6,13 @@ namespace ScadaLink.CLI.Commands; public static class SiteCommands { + /// + /// Builds the site command group and all its subcommands. + /// + /// Global management URL option. + /// Global output format option. + /// Global username option. + /// Global password option. public static Command Build(Option urlOption, Option formatOption, Option usernameOption, Option passwordOption) { var command = new Command("site") { Description = "Manage sites" }; diff --git a/src/ScadaLink.CLI/Commands/TableAuditFormatter.cs b/src/ScadaLink.CLI/Commands/TableAuditFormatter.cs index 98c9b6c5..dbf85b15 100644 --- a/src/ScadaLink.CLI/Commands/TableAuditFormatter.cs +++ b/src/ScadaLink.CLI/Commands/TableAuditFormatter.cs @@ -28,6 +28,7 @@ public sealed class TableAuditFormatter : IAuditFormatter ("httpStatus", "HttpStatus", 10), }; + /// public void WritePage(IReadOnlyList events, TextWriter output) { // Build every cell first so column widths account for the actual data. diff --git a/src/ScadaLink.CLI/Commands/TemplateCommands.cs b/src/ScadaLink.CLI/Commands/TemplateCommands.cs index dd9a4cd6..69fd92c8 100644 --- a/src/ScadaLink.CLI/Commands/TemplateCommands.cs +++ b/src/ScadaLink.CLI/Commands/TemplateCommands.cs @@ -6,6 +6,11 @@ namespace ScadaLink.CLI.Commands; public static class TemplateCommands { + /// Builds the template command with its subcommands using the given shared CLI options. + /// Shared management URL option. + /// Shared output format option. + /// Shared username option for authentication. + /// Shared password option for authentication. public static Command Build(Option urlOption, Option formatOption, Option usernameOption, Option passwordOption) { var command = new Command("template") { Description = "Manage templates" }; diff --git a/src/ScadaLink.CLI/ManagementHttpClient.cs b/src/ScadaLink.CLI/ManagementHttpClient.cs index ce8e02bd..6f8882f2 100644 --- a/src/ScadaLink.CLI/ManagementHttpClient.cs +++ b/src/ScadaLink.CLI/ManagementHttpClient.cs @@ -8,6 +8,12 @@ public class ManagementHttpClient : IDisposable { private readonly HttpClient _httpClient; + /// + /// Initializes a new instance of the class. + /// + /// The base URL for the management API. + /// The username for HTTP Basic authentication. + /// The password for HTTP Basic authentication. public ManagementHttpClient(string baseUrl, string username, string password) : this(new HttpClient(), baseUrl, username, password) { @@ -18,6 +24,10 @@ public class ManagementHttpClient : IDisposable /// over a stub ) so the request/response handling can /// be exercised without a live server. /// + /// The HTTP client to use for requests. + /// The base URL for the management API. + /// The username for HTTP Basic authentication. + /// The password for HTTP Basic authentication. internal ManagementHttpClient(HttpClient httpClient, string baseUrl, string username, string password) { _httpClient = httpClient; @@ -27,6 +37,13 @@ public class ManagementHttpClient : IDisposable new AuthenticationHeaderValue("Basic", credentials); } + /// + /// Sends a management command to the management API. + /// + /// The command name to execute. + /// The command payload. + /// The request timeout. + /// A management response containing status and data. public async Task SendCommandAsync(string commandName, object payload, TimeSpan timeout) { using var cts = new CancellationTokenSource(timeout); @@ -82,6 +99,8 @@ public class ManagementHttpClient : IDisposable /// REST resources. Authentication (HTTP Basic) and the base address are shared. /// /// Path relative to the base URL, with query string. + /// The request timeout. + /// A management response containing status and data. public async Task SendGetAsync(string relativePath, TimeSpan timeout) { using var cts = new CancellationTokenSource(timeout); @@ -130,9 +149,15 @@ public class ManagementHttpClient : IDisposable /// disposing the returned message. The /// option ensures the body is not pre-buffered. /// + /// Path relative to the base URL, with query string. + /// A cancellation token that can be used to cancel the operation. + /// The raw HTTP response message for streaming. public async Task SendGetStreamAsync(string relativePath, CancellationToken cancellationToken) => await _httpClient.GetAsync(relativePath, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + /// + /// Disposes the underlying HTTP client. + /// public void Dispose() => _httpClient.Dispose(); } diff --git a/src/ScadaLink.CLI/OutputFormatter.cs b/src/ScadaLink.CLI/OutputFormatter.cs index 46342895..220e24c6 100644 --- a/src/ScadaLink.CLI/OutputFormatter.cs +++ b/src/ScadaLink.CLI/OutputFormatter.cs @@ -12,16 +12,24 @@ public static class OutputFormatter PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + /// Serializes to indented JSON and writes it to standard output. + /// The object to serialize; null is serialized as JSON null. public static void WriteJson(object? data) { Console.WriteLine(JsonSerializer.Serialize(data, JsonOptions)); } + /// Writes a JSON error envelope with the given message and code to standard error. + /// Human-readable error description. + /// Machine-readable error code. public static void WriteError(string message, string code) { Console.Error.WriteLine(JsonSerializer.Serialize(new { error = message, code }, JsonOptions)); } + /// Writes a plain-text padded table to standard output with the given column headers and data rows. + /// Data rows; each inner array corresponds to a column in the same order as . + /// Column header labels. public static void WriteTable(IEnumerable rows, string[] headers) { var allRows = new List { headers }; diff --git a/src/ScadaLink.CentralUI/Audit/AuditExportEndpoints.cs b/src/ScadaLink.CentralUI/Audit/AuditExportEndpoints.cs index a497dcb9..14206cc9 100644 --- a/src/ScadaLink.CentralUI/Audit/AuditExportEndpoints.cs +++ b/src/ScadaLink.CentralUI/Audit/AuditExportEndpoints.cs @@ -43,6 +43,9 @@ public static class AuditExportEndpoints /// public const int DefaultMaxRows = 100_000; + /// Registers the audit log CSV export endpoint on the given route builder. + /// The endpoint route builder to register against. + /// The same instance for chaining. public static IEndpointRouteBuilder MapAuditExportEndpoints(this IEndpointRouteBuilder endpoints) { endpoints.MapGet("/api/centralui/audit/export", HandleExportAsync) @@ -56,6 +59,8 @@ public static class AuditExportEndpoints /// tests can call it directly when desirable; the live wire-up goes /// through the minimal-API map above. /// + /// The HTTP context for the current request. + /// The export service used to stream audit rows as CSV. internal static async Task HandleExportAsync(HttpContext context, IAuditLogExportService exportService) { var filter = ParseFilter(context.Request.Query); @@ -88,6 +93,7 @@ public static class AuditExportEndpoints /// sourceSiteId. The divergence is deliberate — each endpoint matches /// its own CLI / UI URL builder — so do NOT "fix" the two to one key name. /// + /// The query string parameters from the HTTP request. internal static AuditLogQueryFilter ParseFilter(IQueryCollection query) { var channels = AuditQueryParamParsers.ParseEnumList(query["channel"]); diff --git a/src/ScadaLink.CentralUI/Auth/AuthEndpoints.cs b/src/ScadaLink.CentralUI/Auth/AuthEndpoints.cs index db1f09be..2990fa3b 100644 --- a/src/ScadaLink.CentralUI/Auth/AuthEndpoints.cs +++ b/src/ScadaLink.CentralUI/Auth/AuthEndpoints.cs @@ -15,6 +15,8 @@ namespace ScadaLink.CentralUI.Auth; /// public static class AuthEndpoints { + /// Registers the /auth/login, /auth/logout, and /auth/ping endpoints on the given route builder. + /// The route builder to add the endpoints to. public static IEndpointRouteBuilder MapAuthEndpoints(this IEndpointRouteBuilder endpoints) { endpoints.MapPost("/auth/login", async (HttpContext context) => @@ -155,6 +157,7 @@ public static class AuthEndpoints /// cookie session is still valid and 401 once it has lapsed /// server-side. See CentralUI-020. /// + /// The current HTTP context used to check authentication state and write the response. public static Task HandlePing(HttpContext context) { context.Response.StatusCode = context.User.Identity?.IsAuthenticated == true diff --git a/src/ScadaLink.CentralUI/Auth/ClaimsPrincipalExtensions.cs b/src/ScadaLink.CentralUI/Auth/ClaimsPrincipalExtensions.cs index 4cd3ec58..e4c0b5c7 100644 --- a/src/ScadaLink.CentralUI/Auth/ClaimsPrincipalExtensions.cs +++ b/src/ScadaLink.CentralUI/Auth/ClaimsPrincipalExtensions.cs @@ -19,6 +19,7 @@ public static class ClaimsPrincipalExtensions /// The audit username for , or /// when the claim is absent. /// + /// The claims principal to read the username from. public static string GetUsername(this ClaimsPrincipal principal) => principal.FindFirst(JwtTokenService.UsernameClaimType)?.Value ?? UnknownUser; @@ -26,6 +27,7 @@ public static class ClaimsPrincipalExtensions /// The display name for , or null when /// the claim is absent. /// + /// The claims principal to read the display name from. public static string? GetDisplayName(this ClaimsPrincipal principal) => principal.FindFirst(JwtTokenService.DisplayNameClaimType)?.Value; @@ -34,6 +36,7 @@ public static class ClaimsPrincipalExtensions /// Replaces the GetCurrentUserAsync helper that was copy-pasted into /// ten components (CentralUI-024). /// + /// The Blazor authentication state provider to read from. public static async Task GetCurrentUsernameAsync( this AuthenticationStateProvider authStateProvider) { diff --git a/src/ScadaLink.CentralUI/Auth/CookieAuthenticationStateProvider.cs b/src/ScadaLink.CentralUI/Auth/CookieAuthenticationStateProvider.cs index 361cd09d..9c3d5ac4 100644 --- a/src/ScadaLink.CentralUI/Auth/CookieAuthenticationStateProvider.cs +++ b/src/ScadaLink.CentralUI/Auth/CookieAuthenticationStateProvider.cs @@ -28,6 +28,10 @@ public class CookieAuthenticationStateProvider : ServerAuthenticationStateProvid { private readonly Task _circuitAuthState; + /// + /// Snapshots the authenticated principal from the current HTTP context for use throughout the circuit lifetime. + /// + /// Accessor used to read the initial HTTP context principal. public CookieAuthenticationStateProvider(IHttpContextAccessor httpContextAccessor) { // Snapshot the principal at circuit-construction time. HttpContext is @@ -38,6 +42,7 @@ public class CookieAuthenticationStateProvider : ServerAuthenticationStateProvid _circuitAuthState = Task.FromResult(new AuthenticationState(user)); } + /// public override Task GetAuthenticationStateAsync() => _circuitAuthState; } diff --git a/src/ScadaLink.CentralUI/Auth/SiteScopeService.cs b/src/ScadaLink.CentralUI/Auth/SiteScopeService.cs index 44ba11d0..d3ab48c5 100644 --- a/src/ScadaLink.CentralUI/Auth/SiteScopeService.cs +++ b/src/ScadaLink.CentralUI/Auth/SiteScopeService.cs @@ -27,6 +27,8 @@ public sealed class SiteScopeService private readonly AuthenticationStateProvider _authStateProvider; private (bool IsSystemWide, IReadOnlySet Sites)? _cached; + /// Initializes a new instance of . + /// The Blazor authentication state provider used to read the current user's claims. public SiteScopeService(AuthenticationStateProvider authStateProvider) { _authStateProvider = authStateProvider; @@ -51,6 +53,7 @@ public sealed class SiteScopeService /// Returns the subset of the user is permitted to /// see. A system-wide user gets the full list back unchanged. /// + /// The full set of sites to filter. public async Task> FilterSitesAsync(IEnumerable sites) { var (isSystemWide, allowed) = await ResolveAsync(); @@ -63,6 +66,7 @@ public sealed class SiteScopeService /// True when the user may operate on the site with the given Site.Id. /// Must be re-checked server-side before any mutating cross-site command. /// + /// The Site.Id to check. public async Task IsSiteAllowedAsync(int siteId) { var (isSystemWide, allowed) = await ResolveAsync(); diff --git a/src/ScadaLink.CentralUI/Components/Audit/AuditFilterBar.razor.cs b/src/ScadaLink.CentralUI/Components/Audit/AuditFilterBar.razor.cs index 451bccf1..c4a15522 100644 --- a/src/ScadaLink.CentralUI/Components/Audit/AuditFilterBar.razor.cs +++ b/src/ScadaLink.CentralUI/Components/Audit/AuditFilterBar.razor.cs @@ -63,6 +63,7 @@ public partial class AuditFilterBar /// [Parameter] public string? InitialInstanceSearch { get; set; } + /// protected override async Task OnInitializedAsync() { // One-shot prefill from a drill-in deep link. Subsequent parameter changes diff --git a/src/ScadaLink.CentralUI/Components/Audit/AuditQueryModel.cs b/src/ScadaLink.CentralUI/Components/Audit/AuditQueryModel.cs index 3864e5b1..84509e29 100644 --- a/src/ScadaLink.CentralUI/Components/Audit/AuditQueryModel.cs +++ b/src/ScadaLink.CentralUI/Components/Audit/AuditQueryModel.cs @@ -33,9 +33,13 @@ namespace ScadaLink.CentralUI.Components.Audit; /// public sealed class AuditQueryModel { + /// Selected channel filter chips; empty means all channels. public HashSet Channels { get; } = new(); + /// Selected kind filter chips; empty means all kinds. public HashSet Kinds { get; } = new(); + /// Selected status filter chips; empty means all statuses. public HashSet Statuses { get; } = new(); + /// Selected source-site identifier chips; empty means all sites. public HashSet SiteIdentifiers { get; } = new(StringComparer.OrdinalIgnoreCase); /// @@ -46,13 +50,20 @@ public sealed class AuditQueryModel /// public HashSet SourceNodes { get; } = new(StringComparer.OrdinalIgnoreCase); + /// Selected time-range preset controlling which historical window is queried. public AuditTimeRangePreset TimeRange { get; set; } = AuditTimeRangePreset.LastHour; + /// Custom start of the time window; used only when is . public DateTime? CustomFromUtc { get; set; } + /// Custom end of the time window; used only when is . public DateTime? CustomToUtc { get; set; } + /// Free-text filter applied to instance names (UI-only; dropped when converting to ). public string InstanceSearch { get; set; } = string.Empty; + /// Free-text filter applied to script names (UI-only; dropped when converting to ). public string ScriptSearch { get; set; } = string.Empty; + /// Free-text filter applied to the target field (external system / DB name / notification list). public string TargetSearch { get; set; } = string.Empty; + /// Free-text filter applied to the actor field (instance or inbound API key name). public string ActorSearch { get; set; } = string.Empty; /// @@ -72,6 +83,7 @@ public sealed class AuditQueryModel /// public string ParentExecutionId { get; set; } = string.Empty; + /// When true and no explicit status chips are selected, the filter targets the full non-success status set. public bool ErrorsOnly { get; set; } /// @@ -133,6 +145,8 @@ public sealed class AuditQueryModel /// multi-select maps straight through to its filter list (an empty set yields /// null — "do not constrain"). See class doc for the Errors-only rule. /// + /// The current UTC timestamp used to compute relative time-range windows. + /// A populated ready for the repository. public AuditLogQueryFilter ToFilter(DateTime utcNow) { var statuses = ResolveStatuses(); diff --git a/src/ScadaLink.CentralUI/Components/Audit/AuditResultsGrid.razor.cs b/src/ScadaLink.CentralUI/Components/Audit/AuditResultsGrid.razor.cs index 981b714b..cb965eba 100644 --- a/src/ScadaLink.CentralUI/Components/Audit/AuditResultsGrid.razor.cs +++ b/src/ScadaLink.CentralUI/Components/Audit/AuditResultsGrid.razor.cs @@ -183,6 +183,7 @@ public partial class AuditResultsGrid : IAsyncDisposable ? $"--audit-col-width: {width}px;" : string.Empty; + /// protected override async Task OnParametersSetAsync() { // Reset & reload whenever the filter reference changes. AuditLogQueryFilter @@ -255,6 +256,7 @@ public partial class AuditResultsGrid : IAsyncDisposable } } + /// protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) @@ -366,6 +368,8 @@ public partial class AuditResultsGrid : IAsyncDisposable /// JS callback: the user finished resizing a column. Persists the new /// per-column width and re-renders so the body cells track the header. /// + /// The stable key of the resized column. + /// The new column width in pixels. [JSInvokable] public async Task OnColumnResized(string columnKey, int widthPx) { @@ -384,6 +388,8 @@ public partial class AuditResultsGrid : IAsyncDisposable /// header of . Moves the dragged column into the /// target's slot, persists the resulting order, and re-renders. /// + /// The stable key of the column being dragged. + /// The stable key of the target column drop slot. [JSInvokable] public async Task OnColumnReordered(string fromKey, string toKey) { @@ -422,6 +428,9 @@ public partial class AuditResultsGrid : IAsyncDisposable } } + /// + /// Releases the .NET object reference held for JS interop callbacks. + /// public ValueTask DisposeAsync() { _selfRef?.Dispose(); diff --git a/src/ScadaLink.CentralUI/Components/Audit/ExecutionDetailModal.razor.cs b/src/ScadaLink.CentralUI/Components/Audit/ExecutionDetailModal.razor.cs index 43d10946..1eebad72 100644 --- a/src/ScadaLink.CentralUI/Components/Audit/ExecutionDetailModal.razor.cs +++ b/src/ScadaLink.CentralUI/Components/Audit/ExecutionDetailModal.razor.cs @@ -80,6 +80,7 @@ public partial class ExecutionDetailModal /// private const int RowPageSize = 100; + /// protected override async Task OnParametersSetAsync() { // Load only on the closed → open transition. A re-render while already diff --git a/src/ScadaLink.CentralUI/Components/Audit/ExecutionTree.razor.cs b/src/ScadaLink.CentralUI/Components/Audit/ExecutionTree.razor.cs index fc773f79..fe3a9c73 100644 --- a/src/ScadaLink.CentralUI/Components/Audit/ExecutionTree.razor.cs +++ b/src/ScadaLink.CentralUI/Components/Audit/ExecutionTree.razor.cs @@ -100,6 +100,7 @@ public partial class ExecutionTree // the whole chain is shown on arrival so the user sees the full picture. private readonly HashSet _collapsed = new(); + /// protected override void OnParametersSet() { // Nested instance: the parent already assembled our subtrees. diff --git a/src/ScadaLink.CentralUI/Components/Pages/Audit/AuditLogPage.razor.cs b/src/ScadaLink.CentralUI/Components/Pages/Audit/AuditLogPage.razor.cs index b7c7a5c2..4a0363db 100644 --- a/src/ScadaLink.CentralUI/Components/Pages/Audit/AuditLogPage.razor.cs +++ b/src/ScadaLink.CentralUI/Components/Pages/Audit/AuditLogPage.razor.cs @@ -54,6 +54,7 @@ public partial class AuditLogPage : IDisposable private bool _drawerOpen; private string? _initialInstanceSearch; + /// protected override void OnInitialized() { ApplyQueryStringFilters(); @@ -81,6 +82,7 @@ public partial class AuditLogPage : IDisposable }); } + /// Unsubscribes from navigation events to prevent memory leaks when the component is removed. public void Dispose() { Navigation.LocationChanged -= HandleLocationChanged; @@ -248,6 +250,10 @@ public partial class AuditLogPage : IDisposable /// internal string ExportUrl => BuildExportUrl(_currentFilter); + /// + /// Builds the CSV export URL for the given filter, encoding all active filter dimensions as query parameters. + /// + /// Currently applied filter; null returns the bare export endpoint. internal static string BuildExportUrl(AuditLogQueryFilter? filter) { const string basePath = "/api/centralui/audit/export"; diff --git a/src/ScadaLink.CentralUI/Components/Pages/Audit/ExecutionTreePage.razor.cs b/src/ScadaLink.CentralUI/Components/Pages/Audit/ExecutionTreePage.razor.cs index b760939a..2d3211a8 100644 --- a/src/ScadaLink.CentralUI/Components/Pages/Audit/ExecutionTreePage.razor.cs +++ b/src/ScadaLink.CentralUI/Components/Pages/Audit/ExecutionTreePage.razor.cs @@ -49,6 +49,7 @@ public partial class ExecutionTreePage private Guid? _modalExecutionId; private bool _modalOpen; + /// protected override async Task OnInitializedAsync() { _executionId = ParseExecutionId(); diff --git a/src/ScadaLink.CentralUI/Components/Pages/Design/TransportExport.razor.cs b/src/ScadaLink.CentralUI/Components/Pages/Design/TransportExport.razor.cs index c968e474..fbefbbf3 100644 --- a/src/ScadaLink.CentralUI/Components/Pages/Design/TransportExport.razor.cs +++ b/src/ScadaLink.CentralUI/Components/Pages/Design/TransportExport.razor.cs @@ -105,6 +105,7 @@ public partial class TransportExport : ComponentBase private bool _downloadInProgress; private string? _downloadError; + /// protected override async Task OnInitializedAsync() { await LoadAllAsync(); @@ -181,6 +182,7 @@ public partial class TransportExport : ComponentBase /// to colour an inline strength meter; never used to gate the export — the /// importer enforces its own strength + lockout policies. /// + /// The passphrase string to score. internal static int PassphraseStrength(string s) { if (string.IsNullOrEmpty(s)) return 0; @@ -259,6 +261,7 @@ public partial class TransportExport : ComponentBase /// envelope-encrypt. Surfaces in the Step 3 warning banner so the user /// knows exactly what an unencrypted export would leak. /// + /// The resolved export closure whose secret fields are counted. internal static int CountSecrets(ResolvedExport resolved) { var count = 0; @@ -363,6 +366,8 @@ public partial class TransportExport : ComponentBase /// odd chars in TransportOptions.SourceEnvironment don't produce /// browser-rejected filenames. /// + /// The environment label to embed in the filename (sanitised to filename-safe characters). + /// Timestamp to use for the datetime segment; defaults to when null. internal static string BuildFilename(string sourceEnvironment, DateTimeOffset? nowUtc = null) { var safe = SanitizeForFilename(sourceEnvironment); @@ -420,6 +425,10 @@ public partial class TransportExport : ComponentBase /// Items that are in but NOT in — /// the auto-included dependencies the resolver pulled in for the user. /// + /// The element type of the artifact list. + /// The full resolved list including both seed and auto-included items. + /// The set of explicitly selected item ids. + /// Function that extracts the integer id from an item. internal static IReadOnlyList AutoIncluded(IReadOnlyList all, IReadOnlyCollection seed, Func idOf) { return all.Where(x => !seed.Contains(idOf(x))).ToList(); diff --git a/src/ScadaLink.CentralUI/Components/Pages/Design/TransportImport.razor.cs b/src/ScadaLink.CentralUI/Components/Pages/Design/TransportImport.razor.cs index b6078b37..f18394c6 100644 --- a/src/ScadaLink.CentralUI/Components/Pages/Design/TransportImport.razor.cs +++ b/src/ScadaLink.CentralUI/Components/Pages/Design/TransportImport.razor.cs @@ -353,6 +353,8 @@ public partial class TransportImport : ComponentBase /// /// Visible to tests via internal so the default-mapping contract is unit-pinned. /// + /// The import preview containing all conflict items to map. + /// A dictionary keyed by (EntityType, Name) with default resolution actions populated. internal static Dictionary<(string EntityType, string Name), ImportResolution> BuildDefaultResolutions( ImportPreview preview) { diff --git a/src/ScadaLink.CentralUI/Components/Pages/SiteCalls/SiteCallsReport.razor.cs b/src/ScadaLink.CentralUI/Components/Pages/SiteCalls/SiteCallsReport.razor.cs index 726a0252..9d0cf91d 100644 --- a/src/ScadaLink.CentralUI/Components/Pages/SiteCalls/SiteCallsReport.razor.cs +++ b/src/ScadaLink.CentralUI/Components/Pages/SiteCalls/SiteCallsReport.razor.cs @@ -89,6 +89,7 @@ public partial class SiteCallsReport private bool HasNextPage => _nextCursor is not null; + /// protected override async Task OnInitializedAsync() { try diff --git a/src/ScadaLink.CentralUI/Components/Shared/AlarmTriggerConfigCodec.cs b/src/ScadaLink.CentralUI/Components/Shared/AlarmTriggerConfigCodec.cs index 457d0bb2..bf514b88 100644 --- a/src/ScadaLink.CentralUI/Components/Shared/AlarmTriggerConfigCodec.cs +++ b/src/ScadaLink.CentralUI/Components/Shared/AlarmTriggerConfigCodec.cs @@ -31,6 +31,9 @@ internal static class AlarmTriggerConfigCodec /// type. Returns a model with default values on null/empty/malformed input /// or for missing keys — never throws. /// + /// The trigger configuration JSON string, or null/empty for defaults. + /// The alarm trigger type that determines which properties to extract. + /// A populated AlarmTriggerModel with default values for missing fields. internal static AlarmTriggerModel Parse(string? json, AlarmTriggerType type) { var model = new AlarmTriggerModel(); @@ -115,6 +118,9 @@ internal static class AlarmTriggerConfigCodec /// current trigger type. Expression is not bound to a single /// attribute, so attributeName is omitted for it. /// + /// The AlarmTriggerModel to serialize. + /// The alarm trigger type determining which properties to serialize. + /// The serialized JSON representation of the model. internal static string Serialize(AlarmTriggerModel model, AlarmTriggerType type) { using var stream = new MemoryStream(); @@ -174,6 +180,11 @@ internal static class AlarmTriggerConfigCodec return Encoding.UTF8.GetString(stream.ToArray()); } + /// + /// Normalizes a direction string to one of: rising, falling, or either. + /// + /// The raw direction string to normalize. + /// Normalized direction: "rising", "falling", or "either". internal static string NormalizeDirection(string? raw) => raw?.ToLowerInvariant() switch { "rising" or "up" or "positive" => "rising", @@ -213,47 +224,122 @@ internal static class AlarmTriggerConfigCodec internal sealed class AlarmTriggerModel { + /// + /// The attribute name bound to this trigger. + /// public string? AttributeName { get; set; } // ValueMatch + /// + /// The value to match against the attribute for ValueMatch triggers. + /// public string? MatchValue { get; set; } + /// + /// Indicates whether the match should be inverted (not equal) for ValueMatch triggers. + /// public bool NotEquals { get; set; } // RangeViolation + /// + /// The minimum threshold for RangeViolation triggers. + /// public double? Min { get; set; } + /// + /// The maximum threshold for RangeViolation triggers. + /// public double? Max { get; set; } // RateOfChange + /// + /// The threshold per second for RateOfChange triggers. + /// public double? ThresholdPerSecond { get; set; } + /// + /// The time window in seconds for RateOfChange rate calculation. + /// public double? WindowSeconds { get; set; } + /// + /// The direction of change: "rising", "falling", or "either" for RateOfChange triggers. + /// public string Direction { get; set; } = "either"; // HiLo — any subset of setpoints may be set; per-setpoint priorities // override the alarm-level priority for that band. + /// + /// The low-low setpoint for HiLo triggers. + /// public double? LoLo { get; set; } + /// + /// The low setpoint for HiLo triggers. + /// public double? Lo { get; set; } + /// + /// The high setpoint for HiLo triggers. + /// public double? Hi { get; set; } + /// + /// The high-high setpoint for HiLo triggers. + /// public double? HiHi { get; set; } + /// + /// The priority for low-low alarm state. + /// public int? LoLoPriority { get; set; } + /// + /// The priority for low alarm state. + /// public int? LoPriority { get; set; } + /// + /// The priority for high alarm state. + /// public int? HiPriority { get; set; } + /// + /// The priority for high-high alarm state. + /// public int? HiHiPriority { get; set; } // Hysteresis: optional deactivation deadband per setpoint. Once at the // band, the setpoint threshold is relaxed by this amount before the alarm // de-escalates. Prevents flapping when the value hovers at the boundary. + /// + /// The deadband for low-low alarm de-escalation. + /// public double? LoLoDeadband { get; set; } + /// + /// The deadband for low alarm de-escalation. + /// public double? LoDeadband { get; set; } + /// + /// The deadband for high alarm de-escalation. + /// public double? HiDeadband { get; set; } + /// + /// The deadband for high-high alarm de-escalation. + /// public double? HiHiDeadband { get; set; } // Per-band operator message. Optional; surfaces on AlarmStateChanged.Message // and may be used by notification routing or operator displays. + /// + /// The operator message for low-low alarm state. + /// public string? LoLoMessage { get; set; } + /// + /// The operator message for low alarm state. + /// public string? LoMessage { get; set; } + /// + /// The operator message for high alarm state. + /// public string? HiMessage { get; set; } + /// + /// The operator message for high-high alarm state. + /// public string? HiHiMessage { get; set; } // Expression — boolean C# expression evaluated on attribute updates. + /// + /// The boolean C# expression to evaluate for Expression triggers. + /// public string? Expression { get; set; } } diff --git a/src/ScadaLink.CentralUI/Components/Shared/DialogService.cs b/src/ScadaLink.CentralUI/Components/Shared/DialogService.cs index ae38c94c..c2f28abe 100644 --- a/src/ScadaLink.CentralUI/Components/Shared/DialogService.cs +++ b/src/ScadaLink.CentralUI/Components/Shared/DialogService.cs @@ -32,6 +32,7 @@ public class DialogService : IDialogService // (the Blazor renderer's, for an event-handler caller). private TaskCompletionSource? _tcs; + /// public Task ConfirmAsync(string title, string message, bool danger = false) { EnsureNoActiveDialog(); @@ -42,6 +43,7 @@ public class DialogService : IDialogService return Project(tcs.Task, static r => r is bool b && b); } + /// public Task PromptAsync(string title, string label, string initialValue = "", string? placeholder = null) { EnsureNoActiveDialog(); @@ -68,6 +70,7 @@ public class DialogService : IDialogService /// dialog. must be a bool for confirms /// and a string? for prompts (null = cancel). /// + /// The user's response: a bool for confirms or a string? for prompts. internal void Resolve(object? result) { var tcs = _tcs; diff --git a/src/ScadaLink.CentralUI/Components/Shared/DurationInput.cs b/src/ScadaLink.CentralUI/Components/Shared/DurationInput.cs index 68158eaa..293d1d81 100644 --- a/src/ScadaLink.CentralUI/Components/Shared/DurationInput.cs +++ b/src/ScadaLink.CentralUI/Components/Shared/DurationInput.cs @@ -17,6 +17,7 @@ internal static class DurationInput /// A null or non-positive duration yields a blank value and the default /// sec unit. /// + /// The duration to split, or null for unset. internal static (string? Value, string Unit) Split(TimeSpan? duration) { if (duration is not { } d || d <= TimeSpan.Zero) return (null, "sec"); @@ -31,6 +32,8 @@ internal static class DurationInput /// Composes a number+unit pair into a duration. A blank, unparseable, or /// non-positive value yields null (unset). /// + /// The numeric string entered by the user. + /// The selected unit token (ms, sec, or min). internal static TimeSpan? Compose(string? value, string unit) { if (!long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var n) diff --git a/src/ScadaLink.CentralUI/Components/Shared/PagerWindow.cs b/src/ScadaLink.CentralUI/Components/Shared/PagerWindow.cs index 8351b679..3974b4c6 100644 --- a/src/ScadaLink.CentralUI/Components/Shared/PagerWindow.cs +++ b/src/ScadaLink.CentralUI/Components/Shared/PagerWindow.cs @@ -15,6 +15,10 @@ public static class PagerWindow /// is how many pages to show on each side of the /// current page. /// + /// The currently active page (1-based). + /// The total number of pages. + /// Number of page buttons to show on each side of the current page; default 2. + /// An ordered list of page numbers and 0 ellipsis placeholders. public static IReadOnlyList Build(int currentPage, int totalPages, int radius = 2) { if (totalPages <= 1) diff --git a/src/ScadaLink.CentralUI/Components/Shared/SchemaBuilderModel.cs b/src/ScadaLink.CentralUI/Components/Shared/SchemaBuilderModel.cs index 7bb32347..a276eb16 100644 --- a/src/ScadaLink.CentralUI/Components/Shared/SchemaBuilderModel.cs +++ b/src/ScadaLink.CentralUI/Components/Shared/SchemaBuilderModel.cs @@ -24,8 +24,11 @@ internal sealed class SchemaProperty { /// Stable identity for Blazor @key across renames. public Guid Id { get; } = Guid.NewGuid(); + /// Property name as it appears in the JSON Schema properties map. public string Name { get; set; } = string.Empty; + /// When true, the property is listed in the JSON Schema required array. public bool Required { get; set; } = true; + /// The JSON Schema node describing this property's type and structure. public SchemaNode Schema { get; set; } = new(); } @@ -41,6 +44,8 @@ internal static class SchemaBuilderModel /// shape ([{name,type,required,itemType?}]) for safety during the /// transition window — translates it into an equivalent object schema. /// + /// JSON Schema string to parse, or null/empty to return the fallback. + /// The to return when the input cannot be parsed. public static SchemaNode Parse(string? json, SchemaNode fallback) { if (string.IsNullOrWhiteSpace(json)) return fallback; @@ -66,6 +71,10 @@ internal static class SchemaBuilderModel /// Default scalar schema (return mode default). public static SchemaNode NewValue() => new() { Type = "string" }; + /// + /// Serializes a tree to its canonical JSON Schema string. + /// + /// The schema node to serialize. public static string Serialize(SchemaNode node) { using var stream = new System.IO.MemoryStream(); diff --git a/src/ScadaLink.CentralUI/Components/Shared/ScriptParameterNames.cs b/src/ScadaLink.CentralUI/Components/Shared/ScriptParameterNames.cs index 4618f3a3..8e8aca60 100644 --- a/src/ScadaLink.CentralUI/Components/Shared/ScriptParameterNames.cs +++ b/src/ScadaLink.CentralUI/Components/Shared/ScriptParameterNames.cs @@ -9,12 +9,20 @@ namespace ScadaLink.CentralUI.Components.Shared; /// public static class ScriptParameterNames { + /// + /// Parses a parameter definitions JSON Schema and returns the declared parameter names. + /// + /// JSON Schema or legacy flat-array string; null/empty returns an empty list. public static IReadOnlyList Parse(string? json) => JsonSchemaShapeParser.ParseParameters(json) .Select(p => p.Name) .Where(s => !string.IsNullOrEmpty(s)) .ToList(); + /// + /// Parses a parameter definitions JSON Schema and returns the full parameter shape objects. + /// + /// JSON Schema or legacy flat-array string; null/empty returns an empty list. public static IReadOnlyList ParseShapes(string? json) => JsonSchemaShapeParser.ParseParameters(json); } diff --git a/src/ScadaLink.CentralUI/Components/Shared/ScriptTriggerConfigCodec.cs b/src/ScadaLink.CentralUI/Components/Shared/ScriptTriggerConfigCodec.cs index 142c0d63..d45ecb63 100644 --- a/src/ScadaLink.CentralUI/Components/Shared/ScriptTriggerConfigCodec.cs +++ b/src/ScadaLink.CentralUI/Components/Shared/ScriptTriggerConfigCodec.cs @@ -67,6 +67,7 @@ internal static class ScriptTriggerConfigCodec internal static readonly string[] Operators = { ">", ">=", "<", "<=", "==", "!=" }; /// Classifies a raw TriggerType string (case-insensitive). + /// The raw trigger type string from the template script entity. internal static ScriptTriggerKind ParseKind(string? triggerType) { if (string.IsNullOrWhiteSpace(triggerType)) return ScriptTriggerKind.None; @@ -87,12 +88,14 @@ internal static class ScriptTriggerConfigCodec /// Expression. False for Interval (its own period is the cadence), Call /// (invoked explicitly, never throttled), and None/Unknown. /// + /// The raw trigger type string to classify. internal static bool SupportsMinTimeBetweenRuns(string? triggerType) => ParseKind(triggerType) is ScriptTriggerKind.ValueChange or ScriptTriggerKind.Conditional or ScriptTriggerKind.Expression; /// Canonical TriggerType string for a kind; null for None/Unknown. + /// The trigger kind to convert. internal static string? KindToString(ScriptTriggerKind kind) => kind switch { ScriptTriggerKind.Interval => "Interval", @@ -108,6 +111,8 @@ internal static class ScriptTriggerConfigCodec /// Returns a model with default values on null/empty/malformed input or for /// missing keys — never throws. /// + /// The raw JSON trigger configuration string. + /// The trigger kind, used to determine which fields to parse. internal static ScriptTriggerModel Parse(string? json, ScriptTriggerKind kind) { var model = new ScriptTriggerModel(); @@ -154,6 +159,8 @@ internal static class ScriptTriggerConfigCodec /// Serializes the model to the JSON shape ScriptActor.ParseTriggerConfig /// expects. Returns null for None/Unknown (no structured config to emit). /// + /// The trigger model to serialize. + /// The trigger kind, used to determine which fields to emit. internal static string? Serialize(ScriptTriggerModel model, ScriptTriggerKind kind) { if (kind is ScriptTriggerKind.None or ScriptTriggerKind.Unknown) return null; @@ -206,6 +213,7 @@ internal static class ScriptTriggerConfigCodec } /// Returns if it is a recognized operator, else ">". + /// The raw operator string to normalize. internal static string NormalizeOperator(string? raw) { var op = raw?.Trim(); diff --git a/src/ScadaLink.CentralUI/Components/Shared/TemplateTreeNode.cs b/src/ScadaLink.CentralUI/Components/Shared/TemplateTreeNode.cs index 2da38fdc..188ed5f6 100644 --- a/src/ScadaLink.CentralUI/Components/Shared/TemplateTreeNode.cs +++ b/src/ScadaLink.CentralUI/Components/Shared/TemplateTreeNode.cs @@ -22,9 +22,13 @@ public enum TemplateTreeNodeKind /// public sealed class TemplateTreeNode { + /// Discriminator indicating whether this node represents a folder, template, or composition slot. public required TemplateTreeNodeKind Kind { get; init; } + /// Database id of the underlying folder, template, or composition record. public required int Id { get; init; } + /// Display name of the node. public required string Name { get; init; } + /// Child nodes (sub-folders, templates, or composition slots). public List Children { get; } = new(); /// Stable key for TreeView selection / expansion tracking. diff --git a/src/ScadaLink.CentralUI/Components/Shared/TriggerAttributeMapper.cs b/src/ScadaLink.CentralUI/Components/Shared/TriggerAttributeMapper.cs index 71874f69..64ac68c0 100644 --- a/src/ScadaLink.CentralUI/Components/Shared/TriggerAttributeMapper.cs +++ b/src/ScadaLink.CentralUI/Components/Shared/TriggerAttributeMapper.cs @@ -18,6 +18,7 @@ namespace ScadaLink.CentralUI.Components.Shared; public static class TriggerAttributeMapper { /// Direct and inherited attributes, exposed as Attributes["..."]. + /// The full flattened attribute choice list from the trigger editor. public static IReadOnlyList SelfAttributes( IReadOnlyList choices) => choices @@ -30,6 +31,7 @@ public static class TriggerAttributeMapper /// Children["X"].Attributes["Y"]. Entries without a dotted prefix /// are skipped (no child scope to attach them to). /// + /// The full flattened attribute choice list from the trigger editor. public static IReadOnlyList Children( IReadOnlyList choices) => choices diff --git a/src/ScadaLink.CentralUI/EndpointExtensions.cs b/src/ScadaLink.CentralUI/EndpointExtensions.cs index 608e62aa..17425630 100644 --- a/src/ScadaLink.CentralUI/EndpointExtensions.cs +++ b/src/ScadaLink.CentralUI/EndpointExtensions.cs @@ -13,6 +13,8 @@ public static class EndpointExtensions /// Maps the Central UI endpoints. The caller must provide the root App component type /// from the Host assembly so that blazor.web.js is served correctly. /// + /// The root Blazor App component type, supplied by the Host assembly. + /// The endpoint route builder to register routes on. public static IEndpointRouteBuilder MapCentralUI(this IEndpointRouteBuilder endpoints) where TApp : Microsoft.AspNetCore.Components.IComponent { diff --git a/src/ScadaLink.CentralUI/ScriptAnalysis/ISharedScriptCatalog.cs b/src/ScadaLink.CentralUI/ScriptAnalysis/ISharedScriptCatalog.cs index 5e6afb87..82fa0b29 100644 --- a/src/ScadaLink.CentralUI/ScriptAnalysis/ISharedScriptCatalog.cs +++ b/src/ScadaLink.CentralUI/ScriptAnalysis/ISharedScriptCatalog.cs @@ -8,6 +8,7 @@ namespace ScadaLink.CentralUI.ScriptAnalysis; /// public interface ISharedScriptCatalog { + /// Returns the parameter and return shapes for all registered shared scripts. Task> GetShapesAsync(); /// @@ -15,6 +16,8 @@ public interface ISharedScriptCatalog /// null if no shared script with that name exists. Used by Test Run to /// compile and execute nested CallShared invocations. /// + /// Name of the shared script to retrieve. + /// Cancellation token for the async lookup. Task GetByNameAsync(string name, CancellationToken cancellationToken = default); } @@ -24,8 +27,13 @@ public class SharedScriptCatalog : ISharedScriptCatalog { private readonly SharedScriptService _service; + /// + /// Initializes a new backed by the given service. + /// + /// Service providing access to shared script definitions. public SharedScriptCatalog(SharedScriptService service) => _service = service; + /// public async Task> GetShapesAsync() { var scripts = await _service.GetAllSharedScriptsAsync(); @@ -34,6 +42,7 @@ public class SharedScriptCatalog : ISharedScriptCatalog .ToList(); } + /// public async Task GetByNameAsync(string name, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(name)) return null; diff --git a/src/ScadaLink.CentralUI/ScriptAnalysis/InboundScriptHost.cs b/src/ScadaLink.CentralUI/ScriptAnalysis/InboundScriptHost.cs index 1e1ecbd2..a79b4e08 100644 --- a/src/ScadaLink.CentralUI/ScriptAnalysis/InboundScriptHost.cs +++ b/src/ScadaLink.CentralUI/ScriptAnalysis/InboundScriptHost.cs @@ -10,44 +10,84 @@ namespace ScadaLink.CentralUI.ScriptAnalysis; /// public class InboundScriptHost { + /// + /// The request parameters passed to the inbound API method. + /// public ScriptParameters Parameters { get; init; } = new(); + /// + /// The route helper for accessing target instances. + /// public RouteHelper Route { get; } = new(); + /// + /// The cancellation token for the operation. + /// public System.Threading.CancellationToken CancellationToken { get; } /// Editor mirror of ScadaLink.InboundAPI.RouteHelper. public class RouteHelper { + /// + /// Targets a specific instance for method invocation. + /// + /// The instance code to target. public RouteTarget To(string instanceCode) => new(); } /// Editor mirror of ScadaLink.InboundAPI.RouteTarget. public class RouteTarget { + /// + /// Calls a script on the target instance. + /// + /// The name of the script to call. + /// Optional parameters to pass to the script. + /// Cancellation token. public System.Threading.Tasks.Task Call( string scriptName, object? parameters = null, System.Threading.CancellationToken cancellationToken = default) => System.Threading.Tasks.Task.FromResult(null); + /// + /// Gets an attribute value from the target instance. + /// + /// The name of the attribute to retrieve. + /// Cancellation token. public System.Threading.Tasks.Task GetAttribute( string attributeName, System.Threading.CancellationToken cancellationToken = default) => System.Threading.Tasks.Task.FromResult(null); + /// + /// Gets multiple attribute values from the target instance. + /// + /// The names of the attributes to retrieve. + /// Cancellation token. public System.Threading.Tasks.Task> GetAttributes( IEnumerable attributeNames, System.Threading.CancellationToken cancellationToken = default) => System.Threading.Tasks.Task.FromResult>( new Dictionary()); + /// + /// Sets a single attribute value on the target instance. + /// + /// The name of the attribute to set. + /// The value to set. + /// Cancellation token. public System.Threading.Tasks.Task SetAttribute( string attributeName, string value, System.Threading.CancellationToken cancellationToken = default) => System.Threading.Tasks.Task.CompletedTask; + /// + /// Sets multiple attribute values on the target instance. + /// + /// Dictionary of attribute names to values. + /// Cancellation token. public System.Threading.Tasks.Task SetAttributes( IReadOnlyDictionary attributeValues, System.Threading.CancellationToken cancellationToken = default) => diff --git a/src/ScadaLink.CentralUI/ScriptAnalysis/JsonSchemaShapeParser.cs b/src/ScadaLink.CentralUI/ScriptAnalysis/JsonSchemaShapeParser.cs index ba6e2da2..592a9fb4 100644 --- a/src/ScadaLink.CentralUI/ScriptAnalysis/JsonSchemaShapeParser.cs +++ b/src/ScadaLink.CentralUI/ScriptAnalysis/JsonSchemaShapeParser.cs @@ -18,6 +18,8 @@ namespace ScadaLink.CentralUI.ScriptAnalysis; /// public static class JsonSchemaShapeParser { + /// Parses a JSON Schema or legacy flat-array parameters definition and returns the resulting parameter shapes. + /// The JSON string to parse; null or whitespace returns an empty list. public static IReadOnlyList ParseParameters(string? json) { if (string.IsNullOrWhiteSpace(json)) return Array.Empty(); @@ -37,6 +39,8 @@ public static class JsonSchemaShapeParser } } + /// Parses a JSON Schema or legacy return-type definition and returns the normalised type name, or null if absent or unrecognised. + /// The JSON string to parse; null or whitespace returns null. public static string? ParseReturnType(string? json) { if (string.IsNullOrWhiteSpace(json)) return null; diff --git a/src/ScadaLink.CentralUI/ScriptAnalysis/SandboxConsoleCapture.cs b/src/ScadaLink.CentralUI/ScriptAnalysis/SandboxConsoleCapture.cs index 97e4d1c3..d783d252 100644 --- a/src/ScadaLink.CentralUI/ScriptAnalysis/SandboxConsoleCapture.cs +++ b/src/ScadaLink.CentralUI/ScriptAnalysis/SandboxConsoleCapture.cs @@ -33,6 +33,7 @@ internal sealed class SandboxConsoleCapture : TextWriter private SandboxConsoleCapture(TextWriter fallback) => _fallback = fallback; + /// public override Encoding Encoding => _fallback.Encoding; /// @@ -70,6 +71,7 @@ internal sealed class SandboxConsoleCapture : TextWriter /// The scope is restored on dispose, so nesting and concurrent scopes on /// other call-trees are unaffected. /// + /// The writer that receives console output for this scope. public CaptureScope BeginCapture(StringWriter buffer) { var previous = _current.Value; @@ -77,15 +79,20 @@ internal sealed class SandboxConsoleCapture : TextWriter return new CaptureScope(this, previous); } + /// public override void Write(char value) => Target.Write(value); + /// public override void Write(string? value) => Target.Write(value); + /// public override void Write(char[] buffer, int index, int count) => Target.Write(buffer, index, count); + /// public override void WriteLine() => Target.WriteLine(); + /// public override void WriteLine(string? value) => Target.WriteLine(value); private TextWriter Target => _current.Value ?? _fallback; @@ -95,12 +102,18 @@ internal sealed class SandboxConsoleCapture : TextWriter private readonly SandboxConsoleCapture _owner; private readonly StringWriter? _previous; + /// + /// Initializes a capture scope that restores the previous writer on dispose. + /// + /// The owning instance. + /// The writer that was active before this scope was opened. internal CaptureScope(SandboxConsoleCapture owner, StringWriter? previous) { _owner = owner; _previous = previous; } + /// Restores the previous capture scope. public void Dispose() => _owner._current.Value = _previous; } } diff --git a/src/ScadaLink.CentralUI/ScriptAnalysis/SandboxHostHelpers.cs b/src/ScadaLink.CentralUI/ScriptAnalysis/SandboxHostHelpers.cs index 7e9231a9..d0f59a48 100644 --- a/src/ScadaLink.CentralUI/ScriptAnalysis/SandboxHostHelpers.cs +++ b/src/ScadaLink.CentralUI/ScriptAnalysis/SandboxHostHelpers.cs @@ -18,12 +18,20 @@ public class SandboxExternalHelper private readonly IExternalSystemClient? _client; private readonly string _instanceName; + /// Initializes a new instance of the SandboxExternalHelper class. + /// Optional external system client for test runs. + /// The instance name context. public SandboxExternalHelper(IExternalSystemClient? client, string instanceName) { _client = client; _instanceName = instanceName; } + /// Invokes a synchronous external system call. + /// The external system name. + /// The method name to invoke. + /// Optional method parameters. + /// Cancellation token. public Task Call( string systemName, string methodName, @@ -36,6 +44,11 @@ public class SandboxExternalHelper return _client.CallAsync(systemName, methodName, parameters, cancellationToken); } + /// Invokes a cached external system call. + /// The external system name. + /// The method name to invoke. + /// Optional method parameters. + /// Cancellation token. public Task CachedCall( string systemName, string methodName, @@ -49,17 +62,24 @@ public class SandboxExternalHelper } } +/// Sandbox database helper for script analysis. public class SandboxDatabaseHelper { private readonly IDatabaseGateway? _gateway; private readonly string _instanceName; + /// Initializes a new instance of the SandboxDatabaseHelper class. + /// Optional database gateway for test runs. + /// The instance name context. public SandboxDatabaseHelper(IDatabaseGateway? gateway, string instanceName) { _gateway = gateway; _instanceName = instanceName; } + /// Gets a database connection by name. + /// The database connection name. + /// Cancellation token. public Task Connection(string name, CancellationToken cancellationToken = default) { if (_gateway == null) @@ -68,6 +88,11 @@ public class SandboxDatabaseHelper return _gateway.GetConnectionAsync(name, cancellationToken); } + /// Executes a cached database write operation. + /// The database connection name. + /// The SQL statement to execute. + /// Optional SQL parameters. + /// Cancellation token. public Task CachedWrite( string name, string sql, @@ -97,6 +122,7 @@ public class SandboxDatabaseHelper public class SandboxNotifyHelper { /// Selects the notification list to send to. + /// The notification list name. public SandboxNotifyTarget To(string listName) => new(); @@ -106,6 +132,7 @@ public class SandboxNotifyHelper /// Unknown status — it exists for signature fidelity with /// NotifyHelper.Status. /// + /// The notification ID to check status for. public Task Status(string notificationId) => Task.FromResult(new NotificationDeliveryStatus("Unknown", 0, null, null)); } @@ -116,6 +143,7 @@ public class SandboxNotifyHelper /// public class SandboxNotifyTarget { + /// Initializes a new instance of the SandboxNotifyTarget class. internal SandboxNotifyTarget() { } @@ -125,6 +153,9 @@ public class SandboxNotifyTarget /// the sandbox nothing is enqueued or delivered; a fake id is returned so /// the call type-checks identically to production. /// + /// The notification subject. + /// The notification message. + /// Cancellation token. public Task Send(string subject, string message, CancellationToken cancellationToken = default) => Task.FromResult(Guid.NewGuid().ToString("N")); } diff --git a/src/ScadaLink.CentralUI/ScriptAnalysis/SandboxInboundScriptHost.cs b/src/ScadaLink.CentralUI/ScriptAnalysis/SandboxInboundScriptHost.cs index 3c14e47c..4f6aedef 100644 --- a/src/ScadaLink.CentralUI/ScriptAnalysis/SandboxInboundScriptHost.cs +++ b/src/ScadaLink.CentralUI/ScriptAnalysis/SandboxInboundScriptHost.cs @@ -14,15 +14,22 @@ namespace ScadaLink.CentralUI.ScriptAnalysis; /// public class SandboxInboundScriptHost { + /// Gets or initializes the script input parameters. public ScriptParameters Parameters { get; init; } = new(); + /// Gets or initializes the cancellation token for the test run. public CancellationToken CancellationToken { get; init; } + /// Gets the route accessor; every call throws in a test run. public RouteAccessor Route { get; } = new(); /// Mirror of ScadaLink.InboundAPI.RouteHelper. public class RouteAccessor { + /// + /// Creates a sandbox route target that throws on every operation. + /// + /// The instance code (used only in the exception message). public RouteTarget To(string instanceCode) => new(instanceCode); } @@ -31,30 +38,61 @@ public class SandboxInboundScriptHost { private readonly string _instanceCode; + /// + /// Initializes the sandbox route target for the given instance code. + /// + /// The instance code referenced by the routing expression (used in the exception message). internal RouteTarget(string instanceCode) => _instanceCode = instanceCode; + /// + /// Always throws ; cross-site routing is unavailable in a Test Run. + /// + /// Script name (included in the exception message). + /// Unused parameters. + /// Unused token. public Task Call( string scriptName, object? parameters = null, CancellationToken cancellationToken = default) => throw Unavailable($"Call(\"{scriptName}\")"); + /// + /// Always throws ; cross-site routing is unavailable in a Test Run. + /// + /// Attribute name (included in the exception message). + /// Unused token. public Task GetAttribute( string attributeName, CancellationToken cancellationToken = default) => throw Unavailable($"GetAttribute(\"{attributeName}\")"); + /// + /// Always throws ; cross-site routing is unavailable in a Test Run. + /// + /// Attribute names (unused). + /// Unused token. public Task> GetAttributes( IEnumerable attributeNames, CancellationToken cancellationToken = default) => throw Unavailable("GetAttributes(...)"); + /// + /// Always throws ; cross-site routing is unavailable in a Test Run. + /// + /// Attribute name (included in the exception message). + /// Unused value. + /// Unused token. public Task SetAttribute( string attributeName, string value, CancellationToken cancellationToken = default) => throw Unavailable($"SetAttribute(\"{attributeName}\")"); + /// + /// Always throws ; cross-site routing is unavailable in a Test Run. + /// + /// Unused attribute values. + /// Unused token. public Task SetAttributes( IReadOnlyDictionary attributeValues, CancellationToken cancellationToken = default) => diff --git a/src/ScadaLink.CentralUI/ScriptAnalysis/SandboxInstanceGateway.cs b/src/ScadaLink.CentralUI/ScriptAnalysis/SandboxInstanceGateway.cs index b55339f0..dfccaca0 100644 --- a/src/ScadaLink.CentralUI/ScriptAnalysis/SandboxInstanceGateway.cs +++ b/src/ScadaLink.CentralUI/ScriptAnalysis/SandboxInstanceGateway.cs @@ -17,6 +17,13 @@ public sealed class SandboxInstanceGateway : ISandboxInstanceGateway private readonly string _instanceUniqueName; private readonly CancellationToken _runToken; + /// + /// Initializes a new bound to a specific deployed instance. + /// + /// Communication service used to route requests to the site. + /// String identifier of the site hosting the bound instance. + /// Unique name of the instance to route calls to. + /// Cancellation token for the test run; applied to all cross-site calls. public SandboxInstanceGateway( CommunicationService comms, string siteId, @@ -29,6 +36,7 @@ public sealed class SandboxInstanceGateway : ISandboxInstanceGateway _runToken = runToken; } + /// public async Task GetAttributeAsync(string canonicalName, CancellationToken ct) { var request = new RouteToGetAttributesRequest( @@ -41,6 +49,7 @@ public sealed class SandboxInstanceGateway : ISandboxInstanceGateway return response.Values.TryGetValue(canonicalName, out var value) ? value : null; } + /// public async Task SetAttributeAsync(string canonicalName, string value, CancellationToken ct) { var request = new RouteToSetAttributesRequest( @@ -52,6 +61,7 @@ public sealed class SandboxInstanceGateway : ISandboxInstanceGateway $"SetAttribute(\"{canonicalName}\") on bound instance '{_instanceUniqueName}' failed: {response.ErrorMessage}"); } + /// public async Task CallScriptAsync( string canonicalScriptName, IReadOnlyDictionary? parameters, CancellationToken ct) { diff --git a/src/ScadaLink.CentralUI/ScriptAnalysis/SandboxScriptHost.cs b/src/ScadaLink.CentralUI/ScriptAnalysis/SandboxScriptHost.cs index a4a77108..d1803c62 100644 --- a/src/ScadaLink.CentralUI/ScriptAnalysis/SandboxScriptHost.cs +++ b/src/ScadaLink.CentralUI/ScriptAnalysis/SandboxScriptHost.cs @@ -19,23 +19,64 @@ namespace ScadaLink.CentralUI.ScriptAnalysis; /// public class SandboxScriptHost { + /// + /// Script parameters passed to the sandbox. + /// public ScriptParameters Parameters { get; init; } = new(); + /// + /// Cancellation token for the sandbox execution. + /// public CancellationToken CancellationToken { get; init; } + /// + /// Alarm context for the sandbox. + /// public AlarmContext? Alarm { get; init; } + /// + /// Script scope defining the execution context. + /// public ScriptScope Scope { get; init; } = ScriptScope.Root; + /// + /// Instance context providing access to deployed instance data. + /// public SandboxInstanceContext Instance { get; init; } = new(); + /// + /// Helper for external system calls. + /// public SandboxExternalHelper ExternalSystem => Instance.ExternalSystem; + + /// + /// Helper for database operations. + /// public SandboxDatabaseHelper Database => Instance.Database; + + /// + /// Helper for sending notifications. + /// public SandboxNotifyHelper Notify => Instance.Notify; + + /// + /// Helper for calling scripts. + /// public SandboxScriptCallHelper Scripts => Instance.Scripts; + /// + /// Accessor for attributes scoped to the current instance. + /// public SandboxAttributeAccessor Attributes => new(Instance, Scope.SelfPath); + + /// + /// Accessor for child compositions. + /// public SandboxChildrenAccessor Children => new(Instance, Scope.SelfPath); + + /// + /// Accessor for the parent composition, or null if at root. + /// public SandboxCompositionAccessor? Parent => Scope.ParentPath == null ? null : new SandboxCompositionAccessor(Instance, Scope.ParentPath); } @@ -47,8 +88,29 @@ public class SandboxScriptHost /// public interface ISandboxInstanceGateway { + /// + /// Gets the value of an attribute with the specified canonical name. + /// + /// The canonical name of the attribute. + /// Cancellation token. + /// The attribute value, or null if not found. Task GetAttributeAsync(string canonicalName, CancellationToken ct); + + /// + /// Sets the value of an attribute with the specified canonical name. + /// + /// The canonical name of the attribute. + /// The value to set. + /// Cancellation token. Task SetAttributeAsync(string canonicalName, string value, CancellationToken ct); + + /// + /// Calls a script with the specified canonical name. + /// + /// The canonical name of the script. + /// Script parameters, or null if none. + /// Cancellation token. + /// The script result, or null if none. Task CallScriptAsync( string canonicalScriptName, IReadOnlyDictionary? parameters, CancellationToken ct); } @@ -65,11 +127,34 @@ public class SandboxInstanceContext { private readonly ISandboxInstanceGateway? _gateway; + /// + /// Helper for external system calls. + /// public SandboxExternalHelper ExternalSystem { get; } + + /// + /// Helper for database operations. + /// public SandboxDatabaseHelper Database { get; } + + /// + /// Helper for sending notifications. + /// public SandboxNotifyHelper Notify { get; } + + /// + /// Helper for calling scripts. + /// public SandboxScriptCallHelper Scripts { get; } + /// + /// Initializes a new instance of the SandboxInstanceContext. + /// + /// Gateway for accessing deployed instance data, or null if unbound. + /// External system helper, or null to create a default. + /// Database helper, or null to create a default. + /// Notification helper, or null to create a default. + /// Script call helper, or null to create a default. public SandboxInstanceContext( ISandboxInstanceGateway? gateway = null, SandboxExternalHelper? external = null, @@ -84,6 +169,11 @@ public class SandboxInstanceContext Scripts = scripts ?? new SandboxScriptCallHelper(null); } + /// + /// Gets the value of an attribute. + /// + /// The name of the attribute. + /// The attribute value, or null if not found. public Task GetAttribute(string attributeName) { if (_gateway == null) @@ -93,6 +183,11 @@ public class SandboxInstanceContext return _gateway.GetAttributeAsync(attributeName, CancellationToken.None); } + /// + /// Sets the value of an attribute. + /// + /// The name of the attribute. + /// The value to set. public void SetAttribute(string attributeName, string value) { if (_gateway == null) @@ -102,6 +197,12 @@ public class SandboxInstanceContext _gateway.SetAttributeAsync(attributeName, value, CancellationToken.None).GetAwaiter().GetResult(); } + /// + /// Calls a sibling script. + /// + /// The name of the script. + /// Script parameters, or null if none. + /// The script result, or null if none. public Task CallScript(string scriptName, object? parameters = null) { if (_gateway == null) @@ -121,12 +222,23 @@ public class SandboxScriptCallHelper { private readonly Func?, CancellationToken, Task>? _callShared; + /// + /// Initializes a new instance of the SandboxScriptCallHelper. + /// + /// Delegate for calling shared scripts, or null if not available. public SandboxScriptCallHelper( Func?, CancellationToken, Task>? callShared) { _callShared = callShared; } + /// + /// Calls a shared script. + /// + /// The name of the shared script. + /// Script parameters, or null if none. + /// Cancellation token. + /// The script result, or null if none. public Task CallShared( string scriptName, object? parameters = null, @@ -147,25 +259,54 @@ public class SandboxAttributeAccessor { private readonly SandboxInstanceContext _ctx; + /// + /// The scope prefix for attribute resolution. + /// public string ScopePrefix { get; } + /// + /// Initializes a new instance of the SandboxAttributeAccessor. + /// + /// The sandbox instance context. + /// The scope prefix for attribute names. public SandboxAttributeAccessor(SandboxInstanceContext ctx, string prefix) { _ctx = ctx; ScopePrefix = prefix; } + /// + /// Resolves a key to its fully qualified name within the current scope. + /// + /// The attribute key. + /// The fully qualified attribute name. public string Resolve(string key) => ScopePrefix.Length == 0 ? key : ScopePrefix + "." + key; + /// + /// Gets or sets an attribute value by key. + /// + /// The attribute key. + /// The attribute value, or null if not found. public object? this[string key] { get => _ctx.GetAttribute(Resolve(key)).GetAwaiter().GetResult(); set => _ctx.SetAttribute(Resolve(key), value?.ToString() ?? string.Empty); } + /// + /// Gets an attribute value asynchronously. + /// + /// The attribute key. + /// The attribute value, or null if not found. public Task GetAsync(string key) => _ctx.GetAttribute(Resolve(key)); + /// + /// Sets an attribute value asynchronously. + /// + /// The attribute key. + /// The value to set, or null. + /// A task representing the operation. public Task SetAsync(string key, object? value) { _ctx.SetAttribute(Resolve(key), value?.ToString() ?? string.Empty); @@ -181,10 +322,21 @@ public class SandboxCompositionAccessor { private readonly SandboxInstanceContext _ctx; + /// + /// The path to the composition within the instance hierarchy. + /// public string Path { get; } + /// + /// Accessor for attributes within the composition. + /// public SandboxAttributeAccessor Attributes { get; } + /// + /// Initializes a new instance of the SandboxCompositionAccessor. + /// + /// The sandbox instance context. + /// The path to the composition within the instance hierarchy. public SandboxCompositionAccessor(SandboxInstanceContext ctx, string path) { _ctx = ctx; @@ -192,9 +344,20 @@ public class SandboxCompositionAccessor Attributes = new SandboxAttributeAccessor(ctx, path); } + /// + /// Resolves a script name to its fully qualified name within the composition. + /// + /// The script name. + /// The fully qualified script name. public string ResolveScript(string scriptName) => Path.Length == 0 ? scriptName : Path + "." + scriptName; + /// + /// Calls a script within the composition. + /// + /// The name of the script. + /// Script parameters, or null if none. + /// The script result, or null if none. public Task CallScript(string scriptName, object? parameters = null) => _ctx.CallScript(ResolveScript(scriptName), parameters); } @@ -208,12 +371,22 @@ public class SandboxChildrenAccessor private readonly SandboxInstanceContext _ctx; private readonly string _selfPath; + /// + /// Initializes a new instance of the SandboxChildrenAccessor. + /// + /// The sandbox instance context. + /// The path to the parent composition. public SandboxChildrenAccessor(SandboxInstanceContext ctx, string selfPath) { _ctx = ctx; _selfPath = selfPath; } + /// + /// Gets a child composition by name. + /// + /// The name of the child composition. + /// An accessor for the child composition. public SandboxCompositionAccessor this[string compositionName] { get @@ -232,5 +405,9 @@ public class SandboxChildrenAccessor /// public class ScriptSandboxException : Exception { + /// + /// Initializes a new instance of the ScriptSandboxException. + /// + /// The exception message. public ScriptSandboxException(string message) : base(message) { } } diff --git a/src/ScadaLink.CentralUI/ScriptAnalysis/ScriptAnalysisEndpoints.cs b/src/ScadaLink.CentralUI/ScriptAnalysis/ScriptAnalysisEndpoints.cs index 7e317555..935617f9 100644 --- a/src/ScadaLink.CentralUI/ScriptAnalysis/ScriptAnalysisEndpoints.cs +++ b/src/ScadaLink.CentralUI/ScriptAnalysis/ScriptAnalysisEndpoints.cs @@ -6,8 +6,14 @@ using ScadaLink.Security; namespace ScadaLink.CentralUI.ScriptAnalysis; +/// +/// Minimal-API endpoint group for Roslyn-backed script analysis (diagnostics, completions, hover, etc.). +/// public static class ScriptAnalysisEndpoints { + /// Registers all script analysis endpoints under /api/script-analysis. + /// The endpoint route builder to register against. + /// The same instance for chaining. public static IEndpointRouteBuilder MapScriptAnalysisEndpoints(this IEndpointRouteBuilder endpoints) { var group = endpoints.MapGroup("/api/script-analysis") diff --git a/src/ScadaLink.CentralUI/ScriptAnalysis/ScriptAnalysisService.cs b/src/ScadaLink.CentralUI/ScriptAnalysis/ScriptAnalysisService.cs index cd7dd74b..191ebb0c 100644 --- a/src/ScadaLink.CentralUI/ScriptAnalysis/ScriptAnalysisService.cs +++ b/src/ScadaLink.CentralUI/ScriptAnalysis/ScriptAnalysisService.cs @@ -72,6 +72,10 @@ public class ScriptAnalysisService private readonly IMemoryCache _cache; private readonly IServiceProvider _services; + /// Initializes the service with its collaborators. + /// Catalog of registered shared scripts for name completion and sandboxed execution. + /// Memory cache used to short-circuit repeated diagnostics for identical code. + /// Service provider for resolving optional runtime collaborators (locator, comms, etc.). public ScriptAnalysisService( ISharedScriptCatalog sharedScripts, IMemoryCache cache, @@ -97,6 +101,9 @@ public class ScriptAnalysisService ? cs.WithOptions(cs.Options.WithNullableContextOptions(NullableContextOptions.Annotations)) : compilation; + /// Compiles the script and returns editor diagnostic markers, including forbidden-API and SCADA-specific diagnostics. + /// The diagnose request containing the script code and context metadata. + /// A containing zero or more diagnostic markers. public DiagnoseResponse Diagnose(DiagnoseRequest request) { if (string.IsNullOrEmpty(request.Code)) @@ -171,6 +178,9 @@ public class ScriptAnalysisService /// land in the result without mutating process-global Console state — two /// concurrent Test Runs do not interfere with each other. /// + /// The sandbox run request including code, kind, parameters, and optional instance binding. + /// Cancellation token for the caller; combined with an internal timeout CTS. + /// A describing the outcome, return value, console output, and any error. public async Task RunInSandboxAsync(SandboxRunRequest request, CancellationToken ct) { if (string.IsNullOrWhiteSpace(request.Code)) @@ -519,6 +529,9 @@ public class ScriptAnalysisService return Convert.ToHexString(bytes); } + /// Returns Roslyn-backed IntelliSense completions augmented with SCADA-specific string-literal suggestions. + /// The completions request containing code text, cursor position, and context metadata. + /// A with the list of completion items. public async Task CompleteAsync(CompletionsRequest request) { if (string.IsNullOrEmpty(request.CodeText)) @@ -696,6 +709,9 @@ public class ScriptAnalysisService InsertTextRules: insertAsSnippet); } + /// Formats the script code using Roslyn's NormalizeWhitespace. + /// The format request containing the script source. + /// A with the formatted code, or the original code if parsing fails. public FormatResponse Format(FormatRequest request) { if (string.IsNullOrEmpty(request.Code)) @@ -723,9 +739,14 @@ public class ScriptAnalysisService /// IReadOnlyDictionary literal ({ ["p"] = … }), which is /// already self-labelling — there are no positional arguments to annotate. /// + /// The inlay hints request (unused; always returns an empty set). + /// An empty . public InlayHintsResponse InlayHints(InlayHintsRequest request) => new(Array.Empty()); + /// Returns hover documentation for the symbol under the cursor, with SCADA-specific script-shape detail. + /// The hover request containing code text and cursor position. + /// A with Markdown documentation, or a null-content response when no hover applies. public async Task Hover(HoverRequest request) { var script = TryParse(request.CodeText); @@ -772,6 +793,9 @@ public class ScriptAnalysisService return new HoverResponse(FormatHover(shape, call)); } + /// Returns signature-help information for the script call at the cursor, resolving parameter shapes from the script catalog. + /// The signature-help request containing code text and cursor position. + /// A with the active signature and parameter, or an empty response when none applies. public async Task SignatureHelp(SignatureHelpRequest request) { var empty = new SignatureHelpResponse(null, null, 0); diff --git a/src/ScadaLink.CentralUI/ScriptAnalysis/ScriptShapeParser.cs b/src/ScadaLink.CentralUI/ScriptAnalysis/ScriptShapeParser.cs index 9ddefb92..c206c018 100644 --- a/src/ScadaLink.CentralUI/ScriptAnalysis/ScriptShapeParser.cs +++ b/src/ScadaLink.CentralUI/ScriptAnalysis/ScriptShapeParser.cs @@ -8,6 +8,10 @@ namespace ScadaLink.CentralUI.ScriptAnalysis; /// public static class ScriptShapeParser { + /// Parses the parameter and return-type JSON schemas for a script and returns a describing its signature. + /// The canonical script name. + /// The JSON Schema or legacy flat-array parameters definition, or null for parameterless scripts. + /// The JSON Schema or legacy return-type definition, or null for void scripts. public static ScriptShape Parse(string name, string? parametersJson, string? returnJson) { var parameters = JsonSchemaShapeParser.ParseParameters(parametersJson); diff --git a/src/ScadaLink.CentralUI/ServiceCollectionExtensions.cs b/src/ScadaLink.CentralUI/ServiceCollectionExtensions.cs index acec819a..3f9e9fc0 100644 --- a/src/ScadaLink.CentralUI/ServiceCollectionExtensions.cs +++ b/src/ScadaLink.CentralUI/ServiceCollectionExtensions.cs @@ -10,6 +10,10 @@ namespace ScadaLink.CentralUI; public static class ServiceCollectionExtensions { + /// + /// Registers all Central UI services including Blazor, auth state, dialogs, audit query, and script analysis. + /// + /// The service collection to configure. public static IServiceCollection AddCentralUI(this IServiceCollection services) { services.AddRazorComponents() diff --git a/src/ScadaLink.CentralUI/Services/AuditLogExportService.cs b/src/ScadaLink.CentralUI/Services/AuditLogExportService.cs index dbbbd257..9d30da46 100644 --- a/src/ScadaLink.CentralUI/Services/AuditLogExportService.cs +++ b/src/ScadaLink.CentralUI/Services/AuditLogExportService.cs @@ -59,6 +59,10 @@ public sealed class AuditLogExportService : IAuditLogExportService private readonly IAuditLogRepository _repository; + /// + /// Initializes a new . + /// + /// Audit log repository used to page through entries for export. public AuditLogExportService(IAuditLogRepository repository) { _repository = repository ?? throw new ArgumentNullException(nameof(repository)); @@ -170,6 +174,7 @@ public sealed class AuditLogExportService : IAuditLogExportService /// scalars use invariant culture so an export taken on one locale parses /// cleanly on another. /// + /// The audit event to format as a CSV row. internal static string FormatCsvRow(AuditEvent evt) { var sb = new StringBuilder(256); diff --git a/src/ScadaLink.CentralUI/Services/AuditLogQueryService.cs b/src/ScadaLink.CentralUI/Services/AuditLogQueryService.cs index 69970609..d41d18f5 100644 --- a/src/ScadaLink.CentralUI/Services/AuditLogQueryService.cs +++ b/src/ScadaLink.CentralUI/Services/AuditLogQueryService.cs @@ -65,6 +65,8 @@ public sealed class AuditLogQueryService : IAuditLogQueryService /// ScadaLinkDbContext and never contends with the circuit-scoped /// context the filter bar uses. /// + /// Factory used to open a fresh DI scope per query. + /// Central health aggregator for KPI backlog data. public AuditLogQueryService( IServiceScopeFactory scopeFactory, ICentralHealthAggregator healthAggregator) @@ -77,6 +79,8 @@ public sealed class AuditLogQueryService : IAuditLogQueryService /// Test-seam constructor — injects a repository instance whose lifetime the /// caller owns. Used by unit tests that substitute a stub repository. /// + /// The audit log repository instance to use directly. + /// Central health aggregator for KPI backlog data. public AuditLogQueryService( IAuditLogRepository repository, ICentralHealthAggregator healthAggregator) @@ -85,8 +89,10 @@ public sealed class AuditLogQueryService : IAuditLogQueryService _healthAggregator = healthAggregator ?? throw new ArgumentNullException(nameof(healthAggregator)); } + /// public int DefaultPageSize => 100; + /// public async Task> QueryAsync( AuditLogQueryFilter filter, AuditLogPaging? paging = null, diff --git a/src/ScadaLink.CentralUI/Services/IAuditLogQueryService.cs b/src/ScadaLink.CentralUI/Services/IAuditLogQueryService.cs index 9cd01255..7d523db4 100644 --- a/src/ScadaLink.CentralUI/Services/IAuditLogQueryService.cs +++ b/src/ScadaLink.CentralUI/Services/IAuditLogQueryService.cs @@ -21,6 +21,9 @@ public interface IAuditLogQueryService /// + /// back as the cursor for the next page. /// + /// Filter criteria applied to the audit log query. + /// Optional paging cursor; defaults to first page when null. + /// Cancellation token. Task> QueryAsync( AuditLogQueryFilter filter, AuditLogPaging? paging = null, @@ -49,6 +52,7 @@ public interface IAuditLogQueryService /// outage degrades the tile group to "unavailable" rather than killing the /// dashboard. /// + /// Cancellation token. Task GetKpiSnapshotAsync(CancellationToken ct = default); /// @@ -66,6 +70,8 @@ public interface IAuditLogQueryService /// implementation opens its own DI scope per call so the tree page's /// auto-load never contends with the circuit-scoped ScadaLinkDbContext. /// + /// Any execution id in the chain to look up. + /// Cancellation token. Task> GetExecutionTreeAsync( Guid executionId, CancellationToken ct = default); @@ -79,5 +85,6 @@ public interface IAuditLogQueryService /// (failover, scaling) surface within a minute, which is acceptable for a /// filter affordance. /// + /// Cancellation token. Task> GetDistinctSourceNodesAsync(CancellationToken ct = default); } diff --git a/src/ScadaLink.ClusterInfrastructure/ClusterOptionsValidator.cs b/src/ScadaLink.ClusterInfrastructure/ClusterOptionsValidator.cs index 59b0c0a4..61e96a72 100644 --- a/src/ScadaLink.ClusterInfrastructure/ClusterOptionsValidator.cs +++ b/src/ScadaLink.ClusterInfrastructure/ClusterOptionsValidator.cs @@ -18,6 +18,11 @@ public sealed class ClusterOptionsValidator : IValidateOptions "keep-oldest" }; + /// + /// Validates the cluster options, returning a failure result if any critical settings are misconfigured. + /// + /// Named options instance name (unused; all instances are validated identically). + /// The cluster options to validate. public ValidateOptionsResult Validate(string? name, ClusterOptions options) { var failures = new List(); diff --git a/src/ScadaLink.ClusterInfrastructure/ServiceCollectionExtensions.cs b/src/ScadaLink.ClusterInfrastructure/ServiceCollectionExtensions.cs index 4b4dfd6a..0c046e02 100644 --- a/src/ScadaLink.ClusterInfrastructure/ServiceCollectionExtensions.cs +++ b/src/ScadaLink.ClusterInfrastructure/ServiceCollectionExtensions.cs @@ -22,6 +22,7 @@ public static class ServiceCollectionExtensions /// into a broken cluster. /// /// + /// The service collection to register into. public static IServiceCollection AddClusterInfrastructure(this IServiceCollection services) { services.TryAddEnumerable( @@ -37,6 +38,7 @@ public static class ServiceCollectionExtensions /// fast with a clear cause instead of failing later, far from here. /// /// Always thrown. + /// The service collection (unused; method always throws). public static IServiceCollection AddClusterInfrastructureActors(this IServiceCollection services) { throw new NotImplementedException( diff --git a/src/ScadaLink.Commons/Entities/Audit/AuditLogEntry.cs b/src/ScadaLink.Commons/Entities/Audit/AuditLogEntry.cs index 1a4cc675..2ae5fe26 100644 --- a/src/ScadaLink.Commons/Entities/Audit/AuditLogEntry.cs +++ b/src/ScadaLink.Commons/Entities/Audit/AuditLogEntry.cs @@ -2,16 +2,33 @@ namespace ScadaLink.Commons.Entities.Audit; public class AuditLogEntry { + /// Auto-incremented primary key. public int Id { get; set; } + /// Username of the actor who performed the action. public string User { get; set; } + /// Action performed (e.g. Created, Updated, Deleted). public string Action { get; set; } + /// Entity type name (e.g. Template, ExternalSystem). public string EntityType { get; set; } + /// String representation of the entity's primary key. public string EntityId { get; set; } + /// Human-readable name of the affected entity. public string EntityName { get; set; } + /// JSON snapshot of the entity's state after the action; null for deletes. public string? AfterStateJson { get; set; } + /// UTC timestamp when the audit entry was recorded. public DateTimeOffset Timestamp { get; set; } + /// Bundle import session id when this entry was created during a bundle import; otherwise null. public Guid? BundleImportId { get; set; } + /// + /// Creates an audit log entry for the specified user action on a named entity. + /// + /// Username of the actor performing the action. + /// Action name (e.g. Created, Updated, Deleted). + /// Entity type name. + /// String primary key of the affected entity. + /// Human-readable name of the affected entity. public AuditLogEntry(string user, string action, string entityType, string entityId, string entityName) { User = user ?? throw new ArgumentNullException(nameof(user)); diff --git a/src/ScadaLink.Commons/Entities/Deployment/DeployedConfigSnapshot.cs b/src/ScadaLink.Commons/Entities/Deployment/DeployedConfigSnapshot.cs index a16a1989..28c241bb 100644 --- a/src/ScadaLink.Commons/Entities/Deployment/DeployedConfigSnapshot.cs +++ b/src/ScadaLink.Commons/Entities/Deployment/DeployedConfigSnapshot.cs @@ -6,9 +6,13 @@ namespace ScadaLink.Commons.Entities.Deployment; /// public class DeployedConfigSnapshot { + /// Primary key. public int Id { get; set; } + /// Foreign key to the owning Instance entity. public int InstanceId { get; set; } + /// Unique deployment identifier assigned at deploy time for idempotency. public string DeploymentId { get; set; } + /// Revision hash of the flattened configuration at deploy time, used for staleness detection. public string RevisionHash { get; set; } /// @@ -16,8 +20,13 @@ public class DeployedConfigSnapshot /// public string ConfigurationJson { get; set; } + /// UTC timestamp when this snapshot was persisted. public DateTimeOffset DeployedAt { get; set; } + /// Initializes a new snapshot with the deployment identity, revision hash, and serialized configuration. + /// Unique deployment identifier. + /// Revision hash of the flattened configuration. + /// JSON-serialized flattened configuration. public DeployedConfigSnapshot(string deploymentId, string revisionHash, string configurationJson) { DeploymentId = deploymentId ?? throw new ArgumentNullException(nameof(deploymentId)); diff --git a/src/ScadaLink.Commons/Entities/Deployment/DeploymentRecord.cs b/src/ScadaLink.Commons/Entities/Deployment/DeploymentRecord.cs index d7d221b1..b4c200be 100644 --- a/src/ScadaLink.Commons/Entities/Deployment/DeploymentRecord.cs +++ b/src/ScadaLink.Commons/Entities/Deployment/DeploymentRecord.cs @@ -4,14 +4,49 @@ namespace ScadaLink.Commons.Entities.Deployment; public class DeploymentRecord { + /// + /// The deployment record identifier. + /// public int Id { get; set; } + + /// + /// The instance identifier being deployed. + /// public int InstanceId { get; set; } + + /// + /// The current deployment status. + /// public DeploymentStatus Status { get; set; } + + /// + /// The deployment identifier. + /// public string DeploymentId { get; set; } + + /// + /// The revision hash of the deployed configuration, or null. + /// public string? RevisionHash { get; set; } + + /// + /// The user who initiated the deployment. + /// public string DeployedBy { get; set; } + + /// + /// The time when the deployment was initiated. + /// public DateTimeOffset DeployedAt { get; set; } + + /// + /// The time when the deployment completed, or null if still in progress. + /// public DateTimeOffset? CompletedAt { get; set; } + + /// + /// Error message if the deployment failed, or null. + /// public string? ErrorMessage { get; set; } /// @@ -19,6 +54,11 @@ public class DeploymentRecord /// public byte[] RowVersion { get; set; } = []; + /// + /// Initializes a new instance of the DeploymentRecord. + /// + /// The deployment identifier. + /// The user initiating the deployment. public DeploymentRecord(string deploymentId, string deployedBy) { DeploymentId = deploymentId ?? throw new ArgumentNullException(nameof(deploymentId)); diff --git a/src/ScadaLink.Commons/Entities/Deployment/SystemArtifactDeploymentRecord.cs b/src/ScadaLink.Commons/Entities/Deployment/SystemArtifactDeploymentRecord.cs index c73fdf19..b3d45028 100644 --- a/src/ScadaLink.Commons/Entities/Deployment/SystemArtifactDeploymentRecord.cs +++ b/src/ScadaLink.Commons/Entities/Deployment/SystemArtifactDeploymentRecord.cs @@ -1,13 +1,24 @@ namespace ScadaLink.Commons.Entities.Deployment; +/// +/// Records a system-wide artifact deployment operation, tracking status per site. +/// public class SystemArtifactDeploymentRecord { + /// Primary key. public int Id { get; set; } + /// Type identifier for the deployed artifact (e.g. the artifact category name). public string ArtifactType { get; set; } + /// Username of the operator who initiated the deployment. public string DeployedBy { get; set; } + /// UTC timestamp when the deployment was initiated. public DateTimeOffset DeployedAt { get; set; } + /// JSON-serialized per-site deployment status map, or null if not yet computed. public string? PerSiteStatus { get; set; } + /// Initializes a new with required fields. + /// The artifact type being deployed. + /// The username of the initiating operator. public SystemArtifactDeploymentRecord(string artifactType, string deployedBy) { ArtifactType = artifactType ?? throw new ArgumentNullException(nameof(artifactType)); diff --git a/src/ScadaLink.Commons/Entities/ExternalSystems/DatabaseConnectionDefinition.cs b/src/ScadaLink.Commons/Entities/ExternalSystems/DatabaseConnectionDefinition.cs index c6f58de3..afdf3483 100644 --- a/src/ScadaLink.Commons/Entities/ExternalSystems/DatabaseConnectionDefinition.cs +++ b/src/ScadaLink.Commons/Entities/ExternalSystems/DatabaseConnectionDefinition.cs @@ -2,12 +2,20 @@ namespace ScadaLink.Commons.Entities.ExternalSystems; public class DatabaseConnectionDefinition { + /// Gets or sets the primary key. public int Id { get; set; } + /// Gets or sets the human-readable connection name. public string Name { get; set; } + /// Gets or sets the ADO.NET connection string for this database. public string ConnectionString { get; set; } + /// Gets or sets the maximum number of retry attempts for transient failures. public int MaxRetries { get; set; } + /// Gets or sets the delay between retry attempts. public TimeSpan RetryDelay { get; set; } + /// Initializes a new with the required name and connection string. + /// The human-readable connection name. + /// The ADO.NET connection string. public DatabaseConnectionDefinition(string name, string connectionString) { Name = name ?? throw new ArgumentNullException(nameof(name)); diff --git a/src/ScadaLink.Commons/Entities/ExternalSystems/ExternalSystemDefinition.cs b/src/ScadaLink.Commons/Entities/ExternalSystems/ExternalSystemDefinition.cs index 49c46b54..04f053ee 100644 --- a/src/ScadaLink.Commons/Entities/ExternalSystems/ExternalSystemDefinition.cs +++ b/src/ScadaLink.Commons/Entities/ExternalSystems/ExternalSystemDefinition.cs @@ -2,14 +2,27 @@ namespace ScadaLink.Commons.Entities.ExternalSystems; public class ExternalSystemDefinition { + /// Database primary key. public int Id { get; set; } + /// Display name for the external system. public string Name { get; set; } + /// Base URL of the external system's HTTP endpoint. public string EndpointUrl { get; set; } + /// Authentication type identifier (e.g., "ApiKey", "Basic"). public string AuthType { get; set; } + /// JSON-serialized authentication configuration for the selected . public string? AuthConfiguration { get; set; } + /// Maximum number of retry attempts for transient failures. public int MaxRetries { get; set; } + /// Fixed delay between retry attempts. public TimeSpan RetryDelay { get; set; } + /// + /// Initializes a new . + /// + /// Display name for the external system. + /// Base URL of the external system's HTTP endpoint. + /// Authentication type identifier. public ExternalSystemDefinition(string name, string endpointUrl, string authType) { Name = name ?? throw new ArgumentNullException(nameof(name)); diff --git a/src/ScadaLink.Commons/Entities/ExternalSystems/ExternalSystemMethod.cs b/src/ScadaLink.Commons/Entities/ExternalSystems/ExternalSystemMethod.cs index fb84a825..b12fb3b2 100644 --- a/src/ScadaLink.Commons/Entities/ExternalSystems/ExternalSystemMethod.cs +++ b/src/ScadaLink.Commons/Entities/ExternalSystems/ExternalSystemMethod.cs @@ -1,15 +1,29 @@ namespace ScadaLink.Commons.Entities.ExternalSystems; +/// +/// Defines a callable HTTP method on an external system definition. +/// public class ExternalSystemMethod { + /// Primary key. public int Id { get; set; } + /// Foreign key referencing the owning ExternalSystemDefinition. public int ExternalSystemDefinitionId { get; set; } + /// Name of the method as referenced in scripts. public string Name { get; set; } + /// HTTP method (GET, POST, PUT, DELETE, etc.). public string HttpMethod { get; set; } + /// URL path relative to the external system's base URL. public string Path { get; set; } + /// JSON-serialized parameter definitions for this method, or null if there are none. public string? ParameterDefinitions { get; set; } + /// JSON-serialized return type definition for this method, or null if void. public string? ReturnDefinition { get; set; } + /// Initializes a new instance of with the required fields. + /// The method name. + /// The HTTP method verb. + /// The URL path. public ExternalSystemMethod(string name, string httpMethod, string path) { Name = name ?? throw new ArgumentNullException(nameof(name)); diff --git a/src/ScadaLink.Commons/Entities/InboundApi/ApiKey.cs b/src/ScadaLink.Commons/Entities/InboundApi/ApiKey.cs index 3c395b4c..387df895 100644 --- a/src/ScadaLink.Commons/Entities/InboundApi/ApiKey.cs +++ b/src/ScadaLink.Commons/Entities/InboundApi/ApiKey.cs @@ -10,7 +10,9 @@ namespace ScadaLink.Commons.Entities.InboundApi; /// public class ApiKey { + /// Database primary key. public int Id { get; set; } + /// Display name for the API key. public string Name { get; set; } /// @@ -20,6 +22,7 @@ public class ApiKey /// public string KeyHash { get; set; } + /// When false, the key is rejected even if the hash matches. public bool IsEnabled { get; set; } /// @@ -28,6 +31,8 @@ public class ApiKey /// never holds the plaintext. Production code paths that have a configured pepper /// should use with a peppered hash instead. /// + /// Display name for the API key. + /// Plaintext key value; hashed immediately and never stored. public ApiKey(string name, string keyValue) { Name = name ?? throw new ArgumentNullException(nameof(name)); @@ -50,6 +55,8 @@ public class ApiKey /// path, which generates a random key, hashes it with the configured (peppered) /// , and stores only the resulting hash. /// + /// Display name for the API key. + /// Pre-computed keyed hash of the API key value. public static ApiKey FromHash(string name, string keyHash) { return new ApiKey diff --git a/src/ScadaLink.Commons/Entities/InboundApi/ApiMethod.cs b/src/ScadaLink.Commons/Entities/InboundApi/ApiMethod.cs index 9058b675..4b3b65e5 100644 --- a/src/ScadaLink.Commons/Entities/InboundApi/ApiMethod.cs +++ b/src/ScadaLink.Commons/Entities/InboundApi/ApiMethod.cs @@ -2,14 +2,24 @@ namespace ScadaLink.Commons.Entities.InboundApi; public class ApiMethod { + /// Gets or sets the primary key. public int Id { get; set; } + /// Gets or sets the method name used in the route (/api/{Name}). public string Name { get; set; } + /// Gets or sets the C# script body executed when the method is invoked. public string Script { get; set; } + /// Gets or sets the JSON-serialised list of API key IDs approved for this method, or null for unrestricted. public string? ApprovedApiKeyIds { get; set; } + /// Gets or sets the JSON Schema describing the accepted parameters, or null if the method takes no parameters. public string? ParameterDefinitions { get; set; } + /// Gets or sets the JSON Schema describing the return type, or null if the method returns nothing. public string? ReturnDefinition { get; set; } + /// Gets or sets the script execution timeout in seconds. public int TimeoutSeconds { get; set; } + /// Initializes a new with the required name and script. + /// The method name (used as the route segment). + /// The C# script body to execute. public ApiMethod(string name, string script) { Name = name ?? throw new ArgumentNullException(nameof(name)); diff --git a/src/ScadaLink.Commons/Entities/Instances/Area.cs b/src/ScadaLink.Commons/Entities/Instances/Area.cs index bcd14e8d..2f5aa973 100644 --- a/src/ScadaLink.Commons/Entities/Instances/Area.cs +++ b/src/ScadaLink.Commons/Entities/Instances/Area.cs @@ -2,12 +2,21 @@ namespace ScadaLink.Commons.Entities.Instances; public class Area { + /// Gets or sets the database primary key. public int Id { get; set; } + /// Gets or sets the id of the site this area belongs to. public int SiteId { get; set; } + /// Gets or sets the display name of the area. public string Name { get; set; } + /// Gets or sets the id of the parent area, or null for a root area. public int? ParentAreaId { get; set; } + /// Gets or sets the child areas nested under this area. public ICollection Children { get; set; } = new List(); + /// + /// Initializes a new with the given name. + /// + /// Display name for the area. public Area(string name) { Name = name ?? throw new ArgumentNullException(nameof(name)); diff --git a/src/ScadaLink.Commons/Entities/Instances/Instance.cs b/src/ScadaLink.Commons/Entities/Instances/Instance.cs index 5ece8191..b6f1da5a 100644 --- a/src/ScadaLink.Commons/Entities/Instances/Instance.cs +++ b/src/ScadaLink.Commons/Entities/Instances/Instance.cs @@ -4,16 +4,29 @@ namespace ScadaLink.Commons.Entities.Instances; public class Instance { + /// Primary key. public int Id { get; set; } + /// Foreign key to the template this instance is based on. public int TemplateId { get; set; } + /// Foreign key to the site where this instance is deployed. public int SiteId { get; set; } + /// Optional foreign key to the organisational area this instance belongs to. public int? AreaId { get; set; } + /// System-wide unique name that identifies this instance. public string UniqueName { get; set; } + /// Current lifecycle state of the instance. public InstanceState State { get; set; } + /// Per-attribute value overrides applied on top of the template defaults. public ICollection AttributeOverrides { get; set; } = new List(); + /// Per-alarm configuration overrides applied on top of the template defaults. public ICollection AlarmOverrides { get; set; } = new List(); + /// Data-connection bindings that map template tags to site data sources. public ICollection ConnectionBindings { get; set; } = new List(); + /// + /// Initializes a new instance with the required unique name. + /// + /// System-wide unique name for this instance. public Instance(string uniqueName) { UniqueName = uniqueName ?? throw new ArgumentNullException(nameof(uniqueName)); diff --git a/src/ScadaLink.Commons/Entities/Instances/InstanceAlarmOverride.cs b/src/ScadaLink.Commons/Entities/Instances/InstanceAlarmOverride.cs index 15ccc031..c214ff05 100644 --- a/src/ScadaLink.Commons/Entities/Instances/InstanceAlarmOverride.cs +++ b/src/ScadaLink.Commons/Entities/Instances/InstanceAlarmOverride.cs @@ -19,7 +19,9 @@ namespace ScadaLink.Commons.Entities.Instances; /// public class InstanceAlarmOverride { + /// Primary key. public int Id { get; set; } + /// Foreign key to the instance this override belongs to. public int InstanceId { get; set; } /// @@ -41,6 +43,10 @@ public class InstanceAlarmOverride /// public int? PriorityLevelOverride { get; set; } + /// + /// Initializes a new alarm override for the specified alarm. + /// + /// Canonical name of the alarm to override. public InstanceAlarmOverride(string alarmCanonicalName) { AlarmCanonicalName = alarmCanonicalName ?? throw new ArgumentNullException(nameof(alarmCanonicalName)); diff --git a/src/ScadaLink.Commons/Entities/Instances/InstanceAttributeOverride.cs b/src/ScadaLink.Commons/Entities/Instances/InstanceAttributeOverride.cs index bd594288..2abc986d 100644 --- a/src/ScadaLink.Commons/Entities/Instances/InstanceAttributeOverride.cs +++ b/src/ScadaLink.Commons/Entities/Instances/InstanceAttributeOverride.cs @@ -2,11 +2,17 @@ namespace ScadaLink.Commons.Entities.Instances; public class InstanceAttributeOverride { + /// Gets or sets the primary key. public int Id { get; set; } + /// Gets or sets the foreign key of the owning instance. public int InstanceId { get; set; } + /// Gets or sets the attribute name this override targets. public string AttributeName { get; set; } + /// Gets or sets the override value, or null to clear a previous override. public string? OverrideValue { get; set; } + /// Initializes a new for the given attribute name. + /// The name of the attribute to override. public InstanceAttributeOverride(string attributeName) { AttributeName = attributeName ?? throw new ArgumentNullException(nameof(attributeName)); diff --git a/src/ScadaLink.Commons/Entities/Instances/InstanceConnectionBinding.cs b/src/ScadaLink.Commons/Entities/Instances/InstanceConnectionBinding.cs index 6dc027b7..614dbd78 100644 --- a/src/ScadaLink.Commons/Entities/Instances/InstanceConnectionBinding.cs +++ b/src/ScadaLink.Commons/Entities/Instances/InstanceConnectionBinding.cs @@ -2,11 +2,19 @@ namespace ScadaLink.Commons.Entities.Instances; public class InstanceConnectionBinding { + /// Auto-incremented primary key. public int Id { get; set; } + /// Foreign key to the owning instance. public int InstanceId { get; set; } + /// Name of the attribute on the instance that this binding maps to a data connection tag. public string AttributeName { get; set; } + /// Foreign key to the data connection that provides values for this attribute. public int DataConnectionId { get; set; } + /// + /// Creates a binding for the specified attribute name. + /// + /// Name of the attribute being bound to a data connection. public InstanceConnectionBinding(string attributeName) { AttributeName = attributeName ?? throw new ArgumentNullException(nameof(attributeName)); diff --git a/src/ScadaLink.Commons/Entities/Notifications/Notification.cs b/src/ScadaLink.Commons/Entities/Notifications/Notification.cs index 7113008e..ea0d3ae9 100644 --- a/src/ScadaLink.Commons/Entities/Notifications/Notification.cs +++ b/src/ScadaLink.Commons/Entities/Notifications/Notification.cs @@ -11,19 +11,27 @@ public class Notification { /// GUID primary key, generated at the originating site. public string NotificationId { get; set; } + /// Gets or sets the notification type. public NotificationType Type { get; set; } + /// Gets or sets the notification list name. public string ListName { get; set; } + /// Gets or sets the notification subject. public string Subject { get; set; } + /// Gets or sets the notification body. public string Body { get; set; } /// JSON extensibility hook for channel-specific payload data. public string? TypeData { get; set; } + /// Gets or sets the notification delivery status. public NotificationStatus Status { get; set; } = NotificationStatus.Pending; + /// Gets or sets the delivery retry count. public int RetryCount { get; set; } + /// Gets or sets the last error message, if any. public string? LastError { get; set; } /// Resolved delivery targets snapshotted at delivery time, for audit. public string? ResolvedTargets { get; set; } + /// Gets or sets the originating site ID. public string SourceSiteId { get; set; } /// @@ -34,7 +42,9 @@ public class Notification /// central; nullable so rows submitted before the column existed don't block ingest. /// public string? SourceNode { get; set; } + /// Gets or sets the originating instance ID, if any. public string? SourceInstanceId { get; set; } + /// Gets or sets the originating script name, if any. public string? SourceScript { get; set; } /// @@ -54,14 +64,27 @@ public class Notification /// non-routed runs, or for notifications submitted before the column existed. /// public Guid? OriginParentExecutionId { get; set; } + /// Gets or sets the time when the notification was enqueued at the site. public DateTimeOffset SiteEnqueuedAt { get; set; } /// Central ingest time. public DateTimeOffset CreatedAt { get; set; } + /// Gets or sets the time of the last delivery attempt, if any. public DateTimeOffset? LastAttemptAt { get; set; } + /// Gets or sets the time of the next scheduled delivery attempt, if any. public DateTimeOffset? NextAttemptAt { get; set; } + /// Gets or sets the time the notification was delivered, if any. public DateTimeOffset? DeliveredAt { get; set; } + /// + /// Initializes a new instance of the Notification class. + /// + /// The notification ID (GUID). + /// The notification type. + /// The notification list name. + /// The notification subject. + /// The notification body text. + /// The originating site ID. public Notification(string notificationId, NotificationType type, string listName, string subject, string body, string sourceSiteId) { diff --git a/src/ScadaLink.Commons/Entities/Notifications/NotificationList.cs b/src/ScadaLink.Commons/Entities/Notifications/NotificationList.cs index b02db24b..d8dcc993 100644 --- a/src/ScadaLink.Commons/Entities/Notifications/NotificationList.cs +++ b/src/ScadaLink.Commons/Entities/Notifications/NotificationList.cs @@ -4,11 +4,17 @@ namespace ScadaLink.Commons.Entities.Notifications; public class NotificationList { + /// Primary key. public int Id { get; set; } + /// Display name of the notification list. public string Name { get; set; } + /// Delivery type discriminator (e.g., Email). public NotificationType Type { get; set; } = NotificationType.Email; + /// Recipients belonging to this list. public ICollection Recipients { get; set; } = new List(); + /// Initializes the notification list with the given name. + /// Display name of the notification list. public NotificationList(string name) { Name = name ?? throw new ArgumentNullException(nameof(name)); diff --git a/src/ScadaLink.Commons/Entities/Notifications/NotificationRecipient.cs b/src/ScadaLink.Commons/Entities/Notifications/NotificationRecipient.cs index 706d0473..d0d1ca57 100644 --- a/src/ScadaLink.Commons/Entities/Notifications/NotificationRecipient.cs +++ b/src/ScadaLink.Commons/Entities/Notifications/NotificationRecipient.cs @@ -2,11 +2,20 @@ namespace ScadaLink.Commons.Entities.Notifications; public class NotificationRecipient { + /// Gets or sets the database primary key. public int Id { get; set; } + /// Gets or sets the id of the parent notification list. public int NotificationListId { get; set; } + /// Gets or sets the display name of the recipient. public string Name { get; set; } + /// Gets or sets the recipient's email address. public string EmailAddress { get; set; } + /// + /// Initializes a new with the required fields. + /// + /// Display name of the recipient. + /// Email address of the recipient. public NotificationRecipient(string name, string emailAddress) { Name = name ?? throw new ArgumentNullException(nameof(name)); diff --git a/src/ScadaLink.Commons/Entities/Notifications/SmtpConfiguration.cs b/src/ScadaLink.Commons/Entities/Notifications/SmtpConfiguration.cs index 0481ef87..3b237efb 100644 --- a/src/ScadaLink.Commons/Entities/Notifications/SmtpConfiguration.cs +++ b/src/ScadaLink.Commons/Entities/Notifications/SmtpConfiguration.cs @@ -2,18 +2,35 @@ namespace ScadaLink.Commons.Entities.Notifications; public class SmtpConfiguration { + /// Gets or sets the primary key. public int Id { get; set; } + /// Gets or sets the SMTP server hostname or IP address. public string Host { get; set; } + /// Gets or sets the SMTP server port number. public int Port { get; set; } + /// Gets or sets the authentication type (e.g. Basic, OAuth2ClientCredentials). public string AuthType { get; set; } + /// Gets or sets the serialized credentials (password or OAuth2 client secret), or null when not applicable. public string? Credentials { get; set; } + /// Gets or sets the TLS mode (None, StartTLS, or SSL), or null to use the provider default. public string? TlsMode { get; set; } + /// Gets or sets the sender address placed in the From header. public string FromAddress { get; set; } + /// Gets or sets the connection timeout in seconds. public int ConnectionTimeoutSeconds { get; set; } + /// Gets or sets the maximum number of concurrent SMTP connections. public int MaxConcurrentConnections { get; set; } + /// Gets or sets the maximum number of delivery retries before parking. public int MaxRetries { get; set; } + /// Gets or sets the delay between retry attempts. public TimeSpan RetryDelay { get; set; } + /// + /// Initializes a new with required fields. + /// + /// SMTP server hostname or IP address. + /// Authentication type string (e.g. Basic, OAuth2ClientCredentials). + /// Sender address for the From header. public SmtpConfiguration(string host, string authType, string fromAddress) { Host = host ?? throw new ArgumentNullException(nameof(host)); diff --git a/src/ScadaLink.Commons/Entities/Scripts/SharedScript.cs b/src/ScadaLink.Commons/Entities/Scripts/SharedScript.cs index ede07e58..1b94f344 100644 --- a/src/ScadaLink.Commons/Entities/Scripts/SharedScript.cs +++ b/src/ScadaLink.Commons/Entities/Scripts/SharedScript.cs @@ -2,12 +2,22 @@ namespace ScadaLink.Commons.Entities.Scripts; public class SharedScript { + /// Primary key. public int Id { get; set; } + /// Unique script name used to reference this script from templates. public string Name { get; set; } + /// C# script source code. public string Code { get; set; } + /// JSON-serialized parameter definitions, or null when the script takes no parameters. public string? ParameterDefinitions { get; set; } + /// JSON-serialized return type definition, or null when the script has no return value. public string? ReturnDefinition { get; set; } + /// + /// Initializes a new shared script with the required name and code. + /// + /// Unique script name. + /// C# script source code. public SharedScript(string name, string code) { Name = name ?? throw new ArgumentNullException(nameof(name)); diff --git a/src/ScadaLink.Commons/Entities/Security/LdapGroupMapping.cs b/src/ScadaLink.Commons/Entities/Security/LdapGroupMapping.cs index 3f3bd2c2..c5f1e49e 100644 --- a/src/ScadaLink.Commons/Entities/Security/LdapGroupMapping.cs +++ b/src/ScadaLink.Commons/Entities/Security/LdapGroupMapping.cs @@ -2,13 +2,19 @@ namespace ScadaLink.Commons.Entities.Security; public class LdapGroupMapping { + /// Gets or sets the primary key. public int Id { get; set; } + /// Gets or sets the LDAP/AD group CN that this mapping targets. public string LdapGroupName { get; set; } + /// Gets or sets the ScadaLink role name this group maps to. public string Role { get; set; } // Parameterless constructor for EF Core seed data private LdapGroupMapping() { LdapGroupName = null!; Role = null!; } + /// Initializes a new linking an LDAP group to a ScadaLink role. + /// The LDAP group name (CN). + /// The ScadaLink role name to assign. public LdapGroupMapping(string ldapGroupName, string role) { LdapGroupName = ldapGroupName ?? throw new ArgumentNullException(nameof(ldapGroupName)); diff --git a/src/ScadaLink.Commons/Entities/Security/SiteScopeRule.cs b/src/ScadaLink.Commons/Entities/Security/SiteScopeRule.cs index 06e92b9b..329428d1 100644 --- a/src/ScadaLink.Commons/Entities/Security/SiteScopeRule.cs +++ b/src/ScadaLink.Commons/Entities/Security/SiteScopeRule.cs @@ -2,7 +2,10 @@ namespace ScadaLink.Commons.Entities.Security; public class SiteScopeRule { + /// Database primary key. public int Id { get; set; } + /// Foreign key to the this rule restricts. public int LdapGroupMappingId { get; set; } + /// Foreign key to the site this rule limits the mapping to. public int SiteId { get; set; } } diff --git a/src/ScadaLink.Commons/Entities/Sites/DataConnection.cs b/src/ScadaLink.Commons/Entities/Sites/DataConnection.cs index 77214357..66e80bba 100644 --- a/src/ScadaLink.Commons/Entities/Sites/DataConnection.cs +++ b/src/ScadaLink.Commons/Entities/Sites/DataConnection.cs @@ -2,14 +2,27 @@ namespace ScadaLink.Commons.Entities.Sites; public class DataConnection { + /// Gets or sets the database primary key. public int Id { get; set; } + /// Gets or sets the owning site's id. public int SiteId { get; set; } + /// Gets or sets the unique name of this data connection within the site. public string Name { get; set; } + /// Gets or sets the protocol type string (e.g. "OpcUa"). public string Protocol { get; set; } + /// Gets or sets the primary protocol-specific configuration JSON. public string? PrimaryConfiguration { get; set; } + /// Gets or sets the backup protocol-specific configuration JSON used on failover. public string? BackupConfiguration { get; set; } + /// Gets or sets the number of failover retry attempts before the connection is marked failed. public int FailoverRetryCount { get; set; } = 3; + /// + /// Initializes a new with the required fields. + /// + /// Unique name of the connection within the site. + /// Protocol type string (e.g. "OpcUa"). + /// Id of the owning site. public DataConnection(string name, string protocol, int siteId) { Name = name ?? throw new ArgumentNullException(nameof(name)); diff --git a/src/ScadaLink.Commons/Entities/Sites/Site.cs b/src/ScadaLink.Commons/Entities/Sites/Site.cs index d7e71bee..eb4b614f 100644 --- a/src/ScadaLink.Commons/Entities/Sites/Site.cs +++ b/src/ScadaLink.Commons/Entities/Sites/Site.cs @@ -2,15 +2,28 @@ namespace ScadaLink.Commons.Entities.Sites; public class Site { + /// Primary key. public int Id { get; set; } + /// Human-readable display name for the site. public string Name { get; set; } + /// Machine-readable identifier used in Akka addresses and API routing. public string SiteIdentifier { get; set; } + /// Optional description of the site. public string? Description { get; set; } + /// Akka remote address for site node A (ClusterClient contact point). public string? NodeAAddress { get; set; } + /// Akka remote address for site node B (ClusterClient contact point). public string? NodeBAddress { get; set; } + /// gRPC endpoint for site node A used by the central SiteStreamGrpcClient. public string? GrpcNodeAAddress { get; set; } + /// gRPC endpoint for site node B used by the central SiteStreamGrpcClient. public string? GrpcNodeBAddress { get; set; } + /// + /// Initializes a new site with the required name and identifier. + /// + /// Human-readable display name. + /// Machine-readable identifier used in Akka addresses. public Site(string name, string siteIdentifier) { Name = name ?? throw new ArgumentNullException(nameof(name)); diff --git a/src/ScadaLink.Commons/Entities/Templates/Template.cs b/src/ScadaLink.Commons/Entities/Templates/Template.cs index 64df5b76..1e17d15f 100644 --- a/src/ScadaLink.Commons/Entities/Templates/Template.cs +++ b/src/ScadaLink.Commons/Entities/Templates/Template.cs @@ -2,14 +2,41 @@ namespace ScadaLink.Commons.Entities.Templates; public class Template { + /// + /// The unique identifier for the template. + /// public int Id { get; set; } + /// + /// The name of the template. + /// public string Name { get; set; } + /// + /// Optional description of the template. + /// public string? Description { get; set; } + /// + /// The identifier of the parent template, if this template inherits from another. + /// public int? ParentTemplateId { get; set; } + /// + /// The identifier of the folder containing this template. + /// public int? FolderId { get; set; } + /// + /// Collection of attributes defined in this template. + /// public ICollection Attributes { get; set; } = new List(); + /// + /// Collection of alarms defined in this template. + /// public ICollection Alarms { get; set; } = new List(); + /// + /// Collection of scripts defined in this template. + /// public ICollection Scripts { get; set; } = new List(); + /// + /// Collection of compositions defined in this template. + /// public ICollection Compositions { get; set; } = new List(); /// @@ -27,6 +54,10 @@ public class Template /// public int? OwnerCompositionId { get; set; } + /// + /// Initializes a new instance of the Template with the specified name. + /// + /// The name of the template. public Template(string name) { Name = name ?? throw new ArgumentNullException(nameof(name)); diff --git a/src/ScadaLink.Commons/Entities/Templates/TemplateAlarm.cs b/src/ScadaLink.Commons/Entities/Templates/TemplateAlarm.cs index da7d21ac..3804dcbd 100644 --- a/src/ScadaLink.Commons/Entities/Templates/TemplateAlarm.cs +++ b/src/ScadaLink.Commons/Entities/Templates/TemplateAlarm.cs @@ -4,14 +4,23 @@ namespace ScadaLink.Commons.Entities.Templates; public class TemplateAlarm { + /// Database primary key. public int Id { get; set; } + /// Foreign key to the owning . public int TemplateId { get; set; } + /// Unique alarm name within the template. public string Name { get; set; } + /// Optional human-readable description of the alarm. public string? Description { get; set; } + /// Alarm priority level; lower values indicate higher priority. public int PriorityLevel { get; set; } + /// When true, this alarm is locked and cannot be overridden in derived templates. public bool IsLocked { get; set; } + /// Type of trigger condition that activates this alarm. public AlarmTriggerType TriggerType { get; set; } + /// JSON-serialized trigger configuration specific to the . public string? TriggerConfiguration { get; set; } + /// Optional ID of the script to execute when the alarm triggers. public int? OnTriggerScriptId { get; set; } /// @@ -29,6 +38,10 @@ public class TemplateAlarm /// public bool LockedInDerived { get; set; } + /// + /// Initializes a new with the specified name. + /// + /// The unique alarm name within the template. public TemplateAlarm(string name) { Name = name ?? throw new ArgumentNullException(nameof(name)); diff --git a/src/ScadaLink.Commons/Entities/Templates/TemplateAttribute.cs b/src/ScadaLink.Commons/Entities/Templates/TemplateAttribute.cs index 8a2d236c..05573d11 100644 --- a/src/ScadaLink.Commons/Entities/Templates/TemplateAttribute.cs +++ b/src/ScadaLink.Commons/Entities/Templates/TemplateAttribute.cs @@ -4,13 +4,37 @@ namespace ScadaLink.Commons.Entities.Templates; public class TemplateAttribute { + /// + /// Gets or sets the attribute ID. + /// public int Id { get; set; } + /// + /// Gets or sets the template ID that owns this attribute. + /// public int TemplateId { get; set; } + /// + /// Gets or sets the attribute name. + /// public string Name { get; set; } + /// + /// Gets or sets the attribute value. + /// public string? Value { get; set; } + /// + /// Gets or sets the data type of the attribute. + /// public DataType DataType { get; set; } + /// + /// Gets or sets a value indicating whether the attribute is locked from override. + /// public bool IsLocked { get; set; } + /// + /// Gets or sets the attribute description. + /// public string? Description { get; set; } + /// + /// Gets or sets the data source reference for this attribute. + /// public string? DataSourceReference { get; set; } /// @@ -28,6 +52,10 @@ public class TemplateAttribute /// public bool LockedInDerived { get; set; } + /// + /// Initializes a new instance of the class with the specified name. + /// + /// The attribute name. public TemplateAttribute(string name) { Name = name ?? throw new ArgumentNullException(nameof(name)); diff --git a/src/ScadaLink.Commons/Entities/Templates/TemplateComposition.cs b/src/ScadaLink.Commons/Entities/Templates/TemplateComposition.cs index 2c6e5360..6d12a1ba 100644 --- a/src/ScadaLink.Commons/Entities/Templates/TemplateComposition.cs +++ b/src/ScadaLink.Commons/Entities/Templates/TemplateComposition.cs @@ -2,11 +2,19 @@ namespace ScadaLink.Commons.Entities.Templates; public class TemplateComposition { + /// Primary key. public int Id { get; set; } + /// Foreign key to the parent template that includes this composition. public int TemplateId { get; set; } + /// Foreign key to the template being composed into the parent. public int ComposedTemplateId { get; set; } + /// Name of the composition instance within the parent template's namespace. public string InstanceName { get; set; } + /// + /// Initializes a new template composition with the required instance name. + /// + /// Name of this composition slot within the parent template. public TemplateComposition(string instanceName) { InstanceName = instanceName ?? throw new ArgumentNullException(nameof(instanceName)); diff --git a/src/ScadaLink.Commons/Entities/Templates/TemplateFolder.cs b/src/ScadaLink.Commons/Entities/Templates/TemplateFolder.cs index fd691af2..efe424e3 100644 --- a/src/ScadaLink.Commons/Entities/Templates/TemplateFolder.cs +++ b/src/ScadaLink.Commons/Entities/Templates/TemplateFolder.cs @@ -2,11 +2,19 @@ namespace ScadaLink.Commons.Entities.Templates; public class TemplateFolder { + /// Database primary key. public int Id { get; set; } + /// Display name for the folder. public string Name { get; set; } + /// ID of the parent folder, or null for root-level folders. public int? ParentFolderId { get; set; } + /// Display ordering position within the parent folder. public int SortOrder { get; set; } + /// + /// Initializes a new with the specified name. + /// + /// The display name for the folder. public TemplateFolder(string name) { Name = name ?? throw new ArgumentNullException(nameof(name)); diff --git a/src/ScadaLink.Commons/Entities/Templates/TemplateScript.cs b/src/ScadaLink.Commons/Entities/Templates/TemplateScript.cs index 17fbc9ff..b544af4f 100644 --- a/src/ScadaLink.Commons/Entities/Templates/TemplateScript.cs +++ b/src/ScadaLink.Commons/Entities/Templates/TemplateScript.cs @@ -2,15 +2,54 @@ namespace ScadaLink.Commons.Entities.Templates; public class TemplateScript { + /// + /// The script identifier. + /// public int Id { get; set; } + + /// + /// The template identifier this script belongs to. + /// public int TemplateId { get; set; } + + /// + /// The script name. + /// public string Name { get; set; } + + /// + /// Whether the script is locked for editing. + /// public bool IsLocked { get; set; } + + /// + /// The script code. + /// public string Code { get; set; } + + /// + /// The trigger type for the script, or null. + /// public string? TriggerType { get; set; } + + /// + /// The trigger configuration, or null. + /// public string? TriggerConfiguration { get; set; } + + /// + /// The parameter definitions in JSON format, or null. + /// public string? ParameterDefinitions { get; set; } + + /// + /// The return type definition in JSON format, or null. + /// public string? ReturnDefinition { get; set; } + + /// + /// The minimum time between script runs, or null. + /// public TimeSpan? MinTimeBetweenRuns { get; set; } /// @@ -23,11 +62,16 @@ public class TemplateScript /// /// Set on a base script. When true, derived templates may not override - /// the script body — the row is rendered readonly with a 🔒 in the derived + /// the script body — the row is rendered readonly in the derived /// UI, and any attempt to update it through the API is rejected. /// public bool LockedInDerived { get; set; } + /// + /// Initializes a new instance of the TemplateScript. + /// + /// The script name. + /// The script code. public TemplateScript(string name, string code) { Name = name ?? throw new ArgumentNullException(nameof(name)); diff --git a/src/ScadaLink.Commons/Interfaces/IOperationTrackingStore.cs b/src/ScadaLink.Commons/Interfaces/IOperationTrackingStore.cs index b1924413..0f0d20f4 100644 --- a/src/ScadaLink.Commons/Interfaces/IOperationTrackingStore.cs +++ b/src/ScadaLink.Commons/Interfaces/IOperationTrackingStore.cs @@ -34,6 +34,13 @@ public interface IOperationTrackingStore /// untouched), matching the at-least-once semantics of the calling site /// store-and-forward path. /// + /// Unique operation ID (idempotency key). + /// Kind of operation (e.g., cached call type). + /// Optional summary of the operation target. + /// Optional ID of the source instance. + /// Optional name of the source script. + /// Optional source node identifier. + /// Cancellation token. Task RecordEnqueueAsync( TrackedOperationId id, string kind, @@ -49,6 +56,12 @@ public interface IOperationTrackingStore /// already applied) are NOT mutated — the operation has reached its final /// outcome and any late-arriving attempt telemetry is dropped on the floor. /// + /// Operation ID to update. + /// Current operation status. + /// Number of retry attempts. + /// Optional error message from the last attempt. + /// Optional HTTP status code from the last attempt. + /// Cancellation token. Task RecordAttemptAsync( TrackedOperationId id, string status, @@ -62,6 +75,11 @@ public interface IOperationTrackingStore /// TerminalAtUtc = now and writes the final status / error. A row /// already in terminal state is left untouched (first-write-wins). /// + /// Operation ID to mark as terminal. + /// Final operation status. + /// Optional final error message. + /// Optional final HTTP status code. + /// Cancellation token. Task RecordTerminalAsync( TrackedOperationId id, string status, @@ -73,6 +91,9 @@ public interface IOperationTrackingStore /// Return the latest snapshot for the supplied id, or null when no /// tracking row exists (purged or never recorded). /// + /// Operation ID to fetch status for. + /// Cancellation token. + /// Tracking status snapshot, or null if not found. Task GetStatusAsync( TrackedOperationId id, CancellationToken ct = default); @@ -82,6 +103,8 @@ public interface IOperationTrackingStore /// . Non-terminal rows are kept regardless /// of age (the operation is still in flight). /// + /// Cutoff timestamp; rows terminal before this are deleted. + /// Cancellation token. Task PurgeTerminalAsync( DateTime olderThanUtc, CancellationToken ct = default); diff --git a/src/ScadaLink.Commons/Interfaces/IPartitionMaintenance.cs b/src/ScadaLink.Commons/Interfaces/IPartitionMaintenance.cs index b8b3ec52..b6908310 100644 --- a/src/ScadaLink.Commons/Interfaces/IPartitionMaintenance.cs +++ b/src/ScadaLink.Commons/Interfaces/IPartitionMaintenance.cs @@ -36,6 +36,8 @@ public interface IPartitionMaintenance /// boundary that already exists is skipped rather than re-issued. /// Returns the boundaries actually added, in chronological order. /// + /// Number of future monthly boundaries to ensure exist. + /// Cancellation token for the SQL operation. Task> EnsureLookaheadAsync(int lookaheadMonths, CancellationToken ct = default); /// @@ -44,5 +46,6 @@ public interface IPartitionMaintenance /// Returns null when the partition function does not exist or /// has no boundaries. /// + /// Cancellation token for the SQL operation. Task GetMaxBoundaryAsync(CancellationToken ct = default); } diff --git a/src/ScadaLink.Commons/Interfaces/Protocol/IDataConnection.cs b/src/ScadaLink.Commons/Interfaces/Protocol/IDataConnection.cs index ba6ed7f1..3b685cde 100644 --- a/src/ScadaLink.Commons/Interfaces/Protocol/IDataConnection.cs +++ b/src/ScadaLink.Commons/Interfaces/Protocol/IDataConnection.cs @@ -8,19 +8,62 @@ public record TagValue(object? Value, QualityCode Quality, DateTimeOffset Timest public record ReadResult(bool Success, TagValue? Value, string? ErrorMessage); public record WriteResult(bool Success, string? ErrorMessage); +/// Callback invoked when a subscribed tag value changes. +/// The tag path whose value has changed. +/// The new tag value including quality and timestamp. public delegate void SubscriptionCallback(string tagPath, TagValue value); public interface IDataConnection : IAsyncDisposable { + /// Establishes the protocol connection using the provided connection details. + /// Protocol-specific key-value configuration pairs. + /// Cancellation token. Task ConnectAsync(IDictionary connectionDetails, CancellationToken cancellationToken = default); + /// Gracefully terminates the protocol connection. + /// Cancellation token. Task DisconnectAsync(CancellationToken cancellationToken = default); + /// Subscribes to value-change notifications for a tag path; returns a subscription ID. + /// The tag path to subscribe to. + /// Callback invoked on each value change. + /// Cancellation token. + /// A subscription ID that can be passed to . Task SubscribeAsync(string tagPath, SubscriptionCallback callback, CancellationToken cancellationToken = default); + /// Cancels an active subscription by its ID. + /// The subscription ID returned by . + /// Cancellation token. Task UnsubscribeAsync(string subscriptionId, CancellationToken cancellationToken = default); + /// Reads the current value of a single tag. + /// The tag path to read. + /// Cancellation token. + /// The read result containing the value or an error. Task ReadAsync(string tagPath, CancellationToken cancellationToken = default); + /// Reads the current values of multiple tags in a single round-trip. + /// The tag paths to read. + /// Cancellation token. + /// A dictionary of tag paths to their read results. Task> ReadBatchAsync(IEnumerable tagPaths, CancellationToken cancellationToken = default); + /// Writes a value to a single tag. + /// The tag path to write. + /// The value to write. + /// Cancellation token. + /// The write result indicating success or failure. Task WriteAsync(string tagPath, object? value, CancellationToken cancellationToken = default); + /// Writes values to multiple tags in a single round-trip. + /// A dictionary of tag paths to values. + /// Cancellation token. + /// A dictionary of tag paths to their write results. Task> WriteBatchAsync(IDictionary values, CancellationToken cancellationToken = default); + /// Writes a batch of values, then writes a flag and waits for a specific response value within the timeout. + /// Tag values to write before the flag. + /// Tag path of the trigger flag. + /// Value to write to the flag tag. + /// Tag path to monitor for the expected response value. + /// The response value that indicates completion. + /// Maximum time to wait for the response. + /// Cancellation token. + /// true if the response value was observed within the timeout; otherwise false. Task WriteBatchAndWaitAsync(IDictionary values, string flagPath, object? flagValue, string responsePath, object? responseValue, TimeSpan timeout, CancellationToken cancellationToken = default); + /// Current connection health status. ConnectionHealth Status { get; } /// diff --git a/src/ScadaLink.Commons/Interfaces/Repositories/IAuditLogRepository.cs b/src/ScadaLink.Commons/Interfaces/Repositories/IAuditLogRepository.cs index 159eda1f..f9e6751c 100644 --- a/src/ScadaLink.Commons/Interfaces/Repositories/IAuditLogRepository.cs +++ b/src/ScadaLink.Commons/Interfaces/Repositories/IAuditLogRepository.cs @@ -29,6 +29,8 @@ public interface IAuditLogRepository /// stored row untouched (first-write-wins). Bypasses the EF change tracker /// so the row never enters a tracked state. /// + /// The audit event to insert. + /// Cancellation token. Task InsertIfNotExistsAsync(AuditEvent evt, CancellationToken ct = default); /// @@ -39,6 +41,9 @@ public interface IAuditLogRepository /// + /// to fetch the next page. /// + /// Filter criteria to apply to the query. + /// Paging cursor and page size. + /// Cancellation token. Task> QueryAsync( AuditLogQueryFilter filter, AuditLogPaging paging, @@ -75,6 +80,8 @@ public interface IAuditLogRepository /// and the composite PK still rejects same-(EventId, OccurredAtUtc) rows. /// /// + /// Lower-bound datetime of the monthly partition to switch out. + /// Cancellation token. Task SwitchOutPartitionAsync(DateTime monthBoundary, CancellationToken ct = default); /// @@ -85,6 +92,8 @@ public interface IAuditLogRepository /// excluded (a no-op switch is wasted work). Used by the M6 purge actor /// to enumerate retention-eligible months on every tick. /// + /// Only partitions whose data is entirely older than this UTC datetime are returned. + /// Cancellation token. Task> GetPartitionBoundariesOlderThanAsync( DateTime threshold, CancellationToken ct = default); @@ -172,6 +181,8 @@ public interface IAuditLogRepository /// stub-node treatment of any other row-less execution. /// /// + /// Any execution id in the chain; the implementation walks to the root and back down. + /// Cancellation token. Task> GetExecutionTreeAsync( Guid executionId, CancellationToken ct = default); @@ -182,5 +193,6 @@ public interface IAuditLogRepository /// "Node" multi-select filter dropdown — the Central UI caches the result /// for ~60s so the repository is hit at most once per minute per circuit. /// + /// Cancellation token. Task> GetDistinctSourceNodesAsync(CancellationToken ct = default); } diff --git a/src/ScadaLink.Commons/Interfaces/Repositories/ICentralUiRepository.cs b/src/ScadaLink.Commons/Interfaces/Repositories/ICentralUiRepository.cs index 8fad6b70..8b0f7b5d 100644 --- a/src/ScadaLink.Commons/Interfaces/Repositories/ICentralUiRepository.cs +++ b/src/ScadaLink.Commons/Interfaces/Repositories/ICentralUiRepository.cs @@ -8,15 +8,49 @@ namespace ScadaLink.Commons.Interfaces.Repositories; public interface ICentralUiRepository { + /// Returns all configured sites. + /// Cancellation token. Task> GetAllSitesAsync(CancellationToken cancellationToken = default); + /// Returns all data connections for the specified site. + /// The site database ID to filter by. + /// Cancellation token. Task> GetDataConnectionsBySiteIdAsync(int siteId, CancellationToken cancellationToken = default); + /// Returns all data connections across all sites. + /// Cancellation token. Task> GetAllDataConnectionsAsync(CancellationToken cancellationToken = default); + /// Returns the full template tree including folders and templates. + /// Cancellation token. Task> GetTemplateTreeAsync(CancellationToken cancellationToken = default); + /// Returns instances filtered by optional site, template, or search term. + /// Optional site ID to filter by. + /// Optional template ID to filter by. + /// Optional keyword to filter instance names. + /// Cancellation token. Task> GetInstancesFilteredAsync(int? siteId = null, int? templateId = null, string? searchTerm = null, CancellationToken cancellationToken = default); + /// Returns the most recent deployment records up to the specified count. + /// Maximum number of records to return. + /// Cancellation token. Task> GetRecentDeploymentsAsync(int count, CancellationToken cancellationToken = default); + /// Returns the area tree for the specified site. + /// The site database ID. + /// Cancellation token. Task> GetAreaTreeBySiteIdAsync(int siteId, CancellationToken cancellationToken = default); // Audit log queries + /// + /// Queries audit log entries with optional filters, returning a page of results and the total matching count. + /// + /// Optional user filter. + /// Optional entity type filter. + /// Optional action filter. + /// Optional start of date range filter. + /// Optional end of date range filter. + /// Optional entity ID filter. + /// Optional entity name filter. + /// Optional bundle import correlation ID filter. + /// One-based page number. + /// Number of entries per page. + /// Cancellation token. Task<(IReadOnlyList Entries, int TotalCount)> GetAuditLogEntriesAsync( string? user = null, string? entityType = null, @@ -30,5 +64,7 @@ public interface ICentralUiRepository int pageSize = 50, CancellationToken cancellationToken = default); + /// Persists pending changes to the underlying store. + /// Cancellation token. Task SaveChangesAsync(CancellationToken cancellationToken = default); } diff --git a/src/ScadaLink.Commons/Interfaces/Repositories/IDeploymentManagerRepository.cs b/src/ScadaLink.Commons/Interfaces/Repositories/IDeploymentManagerRepository.cs index 4e775e43..6b4cd921 100644 --- a/src/ScadaLink.Commons/Interfaces/Repositories/IDeploymentManagerRepository.cs +++ b/src/ScadaLink.Commons/Interfaces/Repositories/IDeploymentManagerRepository.cs @@ -7,31 +7,149 @@ namespace ScadaLink.Commons.Interfaces.Repositories; public interface IDeploymentManagerRepository { // DeploymentRecord + /// + /// Gets a deployment record by its ID. + /// + /// The deployment record ID. + /// A cancellation token that can be used to cancel the operation. + /// The deployment record, or null if not found. Task GetDeploymentRecordByIdAsync(int id, CancellationToken cancellationToken = default); + /// + /// Gets all deployment records. + /// + /// A cancellation token that can be used to cancel the operation. + /// A read-only list of all deployment records. Task> GetAllDeploymentRecordsAsync(CancellationToken cancellationToken = default); + /// + /// Gets all deployment records for a specific instance. + /// + /// The instance ID. + /// A cancellation token that can be used to cancel the operation. + /// A read-only list of deployment records for the instance. Task> GetDeploymentsByInstanceIdAsync(int instanceId, CancellationToken cancellationToken = default); + /// + /// Gets the current deployment status for an instance. + /// + /// The instance ID. + /// A cancellation token that can be used to cancel the operation. + /// The current deployment record, or null if no deployment exists. Task GetCurrentDeploymentStatusAsync(int instanceId, CancellationToken cancellationToken = default); + /// + /// Gets a deployment record by deployment ID. + /// + /// The deployment ID. + /// A cancellation token that can be used to cancel the operation. + /// The deployment record, or null if not found. Task GetDeploymentByDeploymentIdAsync(string deploymentId, CancellationToken cancellationToken = default); + /// + /// Adds a new deployment record. + /// + /// The deployment record to add. + /// A cancellation token that can be used to cancel the operation. + /// A task representing the asynchronous operation. Task AddDeploymentRecordAsync(DeploymentRecord record, CancellationToken cancellationToken = default); + /// + /// Updates an existing deployment record. + /// + /// The deployment record to update. + /// A cancellation token that can be used to cancel the operation. + /// A task representing the asynchronous operation. Task UpdateDeploymentRecordAsync(DeploymentRecord record, CancellationToken cancellationToken = default); + /// + /// Deletes a deployment record by ID. + /// + /// The deployment record ID to delete. + /// A cancellation token that can be used to cancel the operation. + /// A task representing the asynchronous operation. Task DeleteDeploymentRecordAsync(int id, CancellationToken cancellationToken = default); // SystemArtifactDeploymentRecord + /// + /// Gets a system artifact deployment record by ID. + /// + /// The system artifact deployment record ID. + /// A cancellation token that can be used to cancel the operation. + /// The system artifact deployment record, or null if not found. Task GetSystemArtifactDeploymentByIdAsync(int id, CancellationToken cancellationToken = default); + /// + /// Gets all system artifact deployment records. + /// + /// A cancellation token that can be used to cancel the operation. + /// A read-only list of all system artifact deployment records. Task> GetAllSystemArtifactDeploymentsAsync(CancellationToken cancellationToken = default); + /// + /// Adds a new system artifact deployment record. + /// + /// The system artifact deployment record to add. + /// A cancellation token that can be used to cancel the operation. + /// A task representing the asynchronous operation. Task AddSystemArtifactDeploymentAsync(SystemArtifactDeploymentRecord record, CancellationToken cancellationToken = default); + /// + /// Updates an existing system artifact deployment record. + /// + /// The system artifact deployment record to update. + /// A cancellation token that can be used to cancel the operation. + /// A task representing the asynchronous operation. Task UpdateSystemArtifactDeploymentAsync(SystemArtifactDeploymentRecord record, CancellationToken cancellationToken = default); + /// + /// Deletes a system artifact deployment record by ID. + /// + /// The system artifact deployment record ID to delete. + /// A cancellation token that can be used to cancel the operation. + /// A task representing the asynchronous operation. Task DeleteSystemArtifactDeploymentAsync(int id, CancellationToken cancellationToken = default); // WP-8: DeployedConfigSnapshot + /// + /// Gets the deployed config snapshot for an instance. + /// + /// The instance ID. + /// A cancellation token that can be used to cancel the operation. + /// The deployed config snapshot, or null if not found. Task GetDeployedSnapshotByInstanceIdAsync(int instanceId, CancellationToken cancellationToken = default); + /// + /// Adds a new deployed config snapshot. + /// + /// The deployed config snapshot to add. + /// A cancellation token that can be used to cancel the operation. + /// A task representing the asynchronous operation. Task AddDeployedSnapshotAsync(DeployedConfigSnapshot snapshot, CancellationToken cancellationToken = default); + /// + /// Updates an existing deployed config snapshot. + /// + /// The deployed config snapshot to update. + /// A cancellation token that can be used to cancel the operation. + /// A task representing the asynchronous operation. Task UpdateDeployedSnapshotAsync(DeployedConfigSnapshot snapshot, CancellationToken cancellationToken = default); + /// + /// Deletes the deployed config snapshot for an instance. + /// + /// The instance ID. + /// A cancellation token that can be used to cancel the operation. + /// A task representing the asynchronous operation. Task DeleteDeployedSnapshotAsync(int instanceId, CancellationToken cancellationToken = default); // Instance lookups for deployment pipeline + /// + /// Gets an instance by ID. + /// + /// The instance ID. + /// A cancellation token that can be used to cancel the operation. + /// The instance, or null if not found. Task GetInstanceByIdAsync(int instanceId, CancellationToken cancellationToken = default); + /// + /// Gets an instance by unique name. + /// + /// The unique instance name. + /// A cancellation token that can be used to cancel the operation. + /// The instance, or null if not found. Task GetInstanceByUniqueNameAsync(string uniqueName, CancellationToken cancellationToken = default); + /// + /// Updates an instance. + /// + /// The instance to update. + /// A cancellation token that can be used to cancel the operation. + /// A task representing the asynchronous operation. Task UpdateInstanceAsync(Instance instance, CancellationToken cancellationToken = default); /// @@ -39,7 +157,15 @@ public interface IDeploymentManagerRepository /// records, deployed config snapshot, attribute/alarm overrides, and /// connection bindings. /// + /// The instance ID to delete. + /// A cancellation token that can be used to cancel the operation. + /// A task representing the asynchronous operation. Task DeleteInstanceAsync(int instanceId, CancellationToken cancellationToken = default); + /// + /// Saves all pending changes to the database. + /// + /// A cancellation token that can be used to cancel the operation. + /// A task representing the number of entities saved. Task SaveChangesAsync(CancellationToken cancellationToken = default); } diff --git a/src/ScadaLink.Commons/Interfaces/Repositories/IExternalSystemRepository.cs b/src/ScadaLink.Commons/Interfaces/Repositories/IExternalSystemRepository.cs index 6c4d6b9c..b3ffca59 100644 --- a/src/ScadaLink.Commons/Interfaces/Repositories/IExternalSystemRepository.cs +++ b/src/ScadaLink.Commons/Interfaces/Repositories/IExternalSystemRepository.cs @@ -5,6 +5,12 @@ namespace ScadaLink.Commons.Interfaces.Repositories; public interface IExternalSystemRepository { // ExternalSystemDefinition + /// + /// Gets an external system definition by ID. + /// + /// The external system ID. + /// Cancellation token. + /// The external system definition, or null if not found. Task GetExternalSystemByIdAsync(int id, CancellationToken cancellationToken = default); /// @@ -13,14 +19,46 @@ public interface IExternalSystemRepository /// ExternalSystem.Call()) does not have to fetch every system and filter /// in memory on each call (ExternalSystemGateway-011). /// + /// The external system name. + /// Cancellation token. + /// The external system definition, or null if not found. Task GetExternalSystemByNameAsync(string name, CancellationToken cancellationToken = default); + /// + /// Gets all external system definitions. + /// + /// Cancellation token. + /// A read-only list of external system definitions. Task> GetAllExternalSystemsAsync(CancellationToken cancellationToken = default); + + /// + /// Adds a new external system definition. + /// + /// The external system definition to add. + /// Cancellation token. Task AddExternalSystemAsync(ExternalSystemDefinition definition, CancellationToken cancellationToken = default); + + /// + /// Updates an existing external system definition. + /// + /// The external system definition to update. + /// Cancellation token. Task UpdateExternalSystemAsync(ExternalSystemDefinition definition, CancellationToken cancellationToken = default); + + /// + /// Deletes an external system definition by ID. + /// + /// The external system ID. + /// Cancellation token. Task DeleteExternalSystemAsync(int id, CancellationToken cancellationToken = default); // ExternalSystemMethod + /// + /// Gets an external system method by ID. + /// + /// The method ID. + /// Cancellation token. + /// The external system method, or null if not found. Task GetExternalSystemMethodByIdAsync(int id, CancellationToken cancellationToken = default); /// @@ -29,14 +67,48 @@ public interface IExternalSystemRepository /// resolution does not have to fetch every method of the system and filter in /// memory on each call (ExternalSystemGateway-011). /// + /// The external system ID. + /// The method name. + /// Cancellation token. + /// The external system method, or null if not found. Task GetMethodByNameAsync(int externalSystemId, string methodName, CancellationToken cancellationToken = default); + /// + /// Gets all methods for a given external system. + /// + /// The external system ID. + /// Cancellation token. + /// A read-only list of external system methods. Task> GetMethodsByExternalSystemIdAsync(int externalSystemId, CancellationToken cancellationToken = default); + + /// + /// Adds a new external system method. + /// + /// The external system method to add. + /// Cancellation token. Task AddExternalSystemMethodAsync(ExternalSystemMethod method, CancellationToken cancellationToken = default); + + /// + /// Updates an existing external system method. + /// + /// The external system method to update. + /// Cancellation token. Task UpdateExternalSystemMethodAsync(ExternalSystemMethod method, CancellationToken cancellationToken = default); + + /// + /// Deletes an external system method by ID. + /// + /// The method ID. + /// Cancellation token. Task DeleteExternalSystemMethodAsync(int id, CancellationToken cancellationToken = default); // DatabaseConnectionDefinition + /// + /// Gets a database connection definition by ID. + /// + /// The database connection ID. + /// Cancellation token. + /// The database connection definition, or null if not found. Task GetDatabaseConnectionByIdAsync(int id, CancellationToken cancellationToken = default); /// @@ -46,12 +118,43 @@ public interface IExternalSystemRepository /// not have to fetch every connection and filter in memory on each call /// (ExternalSystemGateway-011). /// + /// The database connection name. + /// Cancellation token. + /// The database connection definition, or null if not found. Task GetDatabaseConnectionByNameAsync(string name, CancellationToken cancellationToken = default); + /// + /// Gets all database connection definitions. + /// + /// Cancellation token. + /// A read-only list of database connection definitions. Task> GetAllDatabaseConnectionsAsync(CancellationToken cancellationToken = default); + + /// + /// Adds a new database connection definition. + /// + /// The database connection definition to add. + /// Cancellation token. Task AddDatabaseConnectionAsync(DatabaseConnectionDefinition definition, CancellationToken cancellationToken = default); + + /// + /// Updates an existing database connection definition. + /// + /// The database connection definition to update. + /// Cancellation token. Task UpdateDatabaseConnectionAsync(DatabaseConnectionDefinition definition, CancellationToken cancellationToken = default); + + /// + /// Deletes a database connection definition by ID. + /// + /// The database connection ID. + /// Cancellation token. Task DeleteDatabaseConnectionAsync(int id, CancellationToken cancellationToken = default); + /// + /// Saves pending changes to the repository. + /// + /// Cancellation token. + /// The number of entities saved. Task SaveChangesAsync(CancellationToken cancellationToken = default); } diff --git a/src/ScadaLink.Commons/Interfaces/Repositories/IInboundApiRepository.cs b/src/ScadaLink.Commons/Interfaces/Repositories/IInboundApiRepository.cs index bef4ef61..8a636492 100644 --- a/src/ScadaLink.Commons/Interfaces/Repositories/IInboundApiRepository.cs +++ b/src/ScadaLink.Commons/Interfaces/Repositories/IInboundApiRepository.cs @@ -5,21 +5,60 @@ namespace ScadaLink.Commons.Interfaces.Repositories; public interface IInboundApiRepository { // ApiKey + /// Retrieves an API key by ID. + /// The API key ID. + /// Cancellation token. Task GetApiKeyByIdAsync(int id, CancellationToken cancellationToken = default); + /// Retrieves all API keys. + /// Cancellation token. Task> GetAllApiKeysAsync(CancellationToken cancellationToken = default); + /// Retrieves an API key by value. + /// The API key value. + /// Cancellation token. Task GetApiKeyByValueAsync(string keyValue, CancellationToken cancellationToken = default); + /// Adds a new API key. + /// The API key to add. + /// Cancellation token. Task AddApiKeyAsync(ApiKey apiKey, CancellationToken cancellationToken = default); + /// Updates an existing API key. + /// The API key to update. + /// Cancellation token. Task UpdateApiKeyAsync(ApiKey apiKey, CancellationToken cancellationToken = default); + /// Deletes an API key by ID. + /// The API key ID. + /// Cancellation token. Task DeleteApiKeyAsync(int id, CancellationToken cancellationToken = default); // ApiMethod + /// Retrieves an API method by ID. + /// The API method ID. + /// Cancellation token. Task GetApiMethodByIdAsync(int id, CancellationToken cancellationToken = default); + /// Retrieves all API methods. + /// Cancellation token. Task> GetAllApiMethodsAsync(CancellationToken cancellationToken = default); + /// Retrieves an API method by name. + /// The API method name. + /// Cancellation token. Task GetMethodByNameAsync(string name, CancellationToken cancellationToken = default); + /// Retrieves API keys approved for a method. + /// The API method ID. + /// Cancellation token. Task> GetApprovedKeysForMethodAsync(int methodId, CancellationToken cancellationToken = default); + /// Adds a new API method. + /// The API method to add. + /// Cancellation token. Task AddApiMethodAsync(ApiMethod method, CancellationToken cancellationToken = default); + /// Updates an existing API method. + /// The API method to update. + /// Cancellation token. Task UpdateApiMethodAsync(ApiMethod method, CancellationToken cancellationToken = default); + /// Deletes an API method by ID. + /// The API method ID. + /// Cancellation token. Task DeleteApiMethodAsync(int id, CancellationToken cancellationToken = default); + /// Saves pending changes to the database. + /// Cancellation token. Task SaveChangesAsync(CancellationToken cancellationToken = default); } diff --git a/src/ScadaLink.Commons/Interfaces/Repositories/INotificationOutboxRepository.cs b/src/ScadaLink.Commons/Interfaces/Repositories/INotificationOutboxRepository.cs index 6cf94536..ccfb2599 100644 --- a/src/ScadaLink.Commons/Interfaces/Repositories/INotificationOutboxRepository.cs +++ b/src/ScadaLink.Commons/Interfaces/Repositories/INotificationOutboxRepository.cs @@ -22,6 +22,9 @@ public interface INotificationOutboxRepository /// row was inserted, false when an existing row was left untouched. /// Commits internally — this call is its own transaction. /// + /// The notification to insert. + /// Cancellation token. + /// True if inserted, false if already exists. Task InsertIfNotExistsAsync(Notification n, CancellationToken cancellationToken = default); /// @@ -30,21 +33,35 @@ public interface INotificationOutboxRepository /// Terminal rows are excluded. Ordered by CreatedAt ascending, capped at /// . /// + /// The current time for evaluating due retries. + /// Maximum number of rows to return. + /// Cancellation token. + /// A list of notifications ready for delivery. Task> GetDueAsync(DateTimeOffset now, int batchSize, CancellationToken cancellationToken = default); /// Returns the notification with the given id, or null. + /// The notification identifier. + /// Cancellation token. + /// The notification, or null if not found. Task GetByIdAsync(string notificationId, CancellationToken cancellationToken = default); /// /// Marks modified and persists it (status transitions). /// Commits internally — this call is its own transaction. /// + /// The notification to update. + /// Cancellation token. Task UpdateAsync(Notification n, CancellationToken cancellationToken = default); /// /// Returns a page of notifications matching , ordered by /// CreatedAt descending, together with the total matching count. /// + /// The query filter. + /// The page number (1-based). + /// The page size. + /// Cancellation token. + /// A tuple of rows and total count. Task<(IReadOnlyList Rows, int TotalCount)> QueryAsync( NotificationOutboxFilter filter, int pageNumber, int pageSize, CancellationToken cancellationToken = default); @@ -52,6 +69,9 @@ public interface INotificationOutboxRepository /// Bulk-deletes terminal rows (Delivered/Parked/Discarded) whose CreatedAt is /// older than . Returns the number of rows deleted. /// + /// The cutoff time for deletion. + /// Cancellation token. + /// The number of rows deleted. Task DeleteTerminalOlderThanAsync(DateTimeOffset cutoff, CancellationToken cancellationToken = default); /// @@ -59,6 +79,10 @@ public interface INotificationOutboxRepository /// delivered cutoffs are supplied by the caller; the current time used for /// OldestPendingAge is captured inside the method. /// + /// The time threshold for marking notifications as stuck. + /// The time threshold for counting delivered notifications. + /// Cancellation token. + /// A KPI snapshot. Task ComputeKpisAsync( DateTimeOffset stuckCutoff, DateTimeOffset deliveredSince, CancellationToken cancellationToken = default); @@ -68,6 +92,10 @@ public interface INotificationOutboxRepository /// are supplied by the caller; the current time used for OldestPendingAge is /// captured inside the method. /// + /// The time threshold for marking notifications as stuck. + /// The time threshold for counting delivered notifications. + /// Cancellation token. + /// A list of per-site KPI snapshots. Task> ComputePerSiteKpisAsync( DateTimeOffset stuckCutoff, DateTimeOffset deliveredSince, CancellationToken cancellationToken = default); @@ -76,5 +104,7 @@ public interface INotificationOutboxRepository /// multiple changes for a single commit; the individual mutating methods on this /// interface already commit on their own. /// + /// Cancellation token. + /// The number of changes persisted. Task SaveChangesAsync(CancellationToken cancellationToken = default); } diff --git a/src/ScadaLink.Commons/Interfaces/Repositories/INotificationRepository.cs b/src/ScadaLink.Commons/Interfaces/Repositories/INotificationRepository.cs index 512e85da..8cb15795 100644 --- a/src/ScadaLink.Commons/Interfaces/Repositories/INotificationRepository.cs +++ b/src/ScadaLink.Commons/Interfaces/Repositories/INotificationRepository.cs @@ -5,26 +5,95 @@ namespace ScadaLink.Commons.Interfaces.Repositories; public interface INotificationRepository { // NotificationList + /// Gets a notification list by ID. + /// The notification list ID. + /// Cancellation token. + /// The notification list, or null if not found. Task GetNotificationListByIdAsync(int id, CancellationToken cancellationToken = default); + + /// Gets all notification lists. + /// Cancellation token. + /// A read-only list of notification lists. Task> GetAllNotificationListsAsync(CancellationToken cancellationToken = default); + + /// Gets a notification list by name. + /// The notification list name. + /// Cancellation token. + /// The notification list, or null if not found. Task GetListByNameAsync(string name, CancellationToken cancellationToken = default); + + /// Adds a new notification list. + /// The notification list to add. + /// Cancellation token. Task AddNotificationListAsync(NotificationList list, CancellationToken cancellationToken = default); + + /// Updates an existing notification list. + /// The notification list to update. + /// Cancellation token. Task UpdateNotificationListAsync(NotificationList list, CancellationToken cancellationToken = default); + + /// Deletes a notification list by ID. + /// The notification list ID. + /// Cancellation token. Task DeleteNotificationListAsync(int id, CancellationToken cancellationToken = default); // NotificationRecipient + /// Gets a notification recipient by ID. + /// The recipient ID. + /// Cancellation token. + /// The notification recipient, or null if not found. Task GetRecipientByIdAsync(int id, CancellationToken cancellationToken = default); + + /// Gets all recipients in a notification list. + /// The notification list ID. + /// Cancellation token. + /// A read-only list of recipients. Task> GetRecipientsByListIdAsync(int notificationListId, CancellationToken cancellationToken = default); + + /// Adds a new notification recipient. + /// The recipient to add. + /// Cancellation token. Task AddRecipientAsync(NotificationRecipient recipient, CancellationToken cancellationToken = default); + + /// Updates an existing notification recipient. + /// The recipient to update. + /// Cancellation token. Task UpdateRecipientAsync(NotificationRecipient recipient, CancellationToken cancellationToken = default); + + /// Deletes a notification recipient by ID. + /// The recipient ID. + /// Cancellation token. Task DeleteRecipientAsync(int id, CancellationToken cancellationToken = default); // SmtpConfiguration + /// Gets an SMTP configuration by ID. + /// The SMTP configuration ID. + /// Cancellation token. + /// The SMTP configuration, or null if not found. Task GetSmtpConfigurationByIdAsync(int id, CancellationToken cancellationToken = default); + + /// Gets all SMTP configurations. + /// Cancellation token. + /// A read-only list of SMTP configurations. Task> GetAllSmtpConfigurationsAsync(CancellationToken cancellationToken = default); + + /// Adds a new SMTP configuration. + /// The SMTP configuration to add. + /// Cancellation token. Task AddSmtpConfigurationAsync(SmtpConfiguration configuration, CancellationToken cancellationToken = default); + + /// Updates an existing SMTP configuration. + /// The SMTP configuration to update. + /// Cancellation token. Task UpdateSmtpConfigurationAsync(SmtpConfiguration configuration, CancellationToken cancellationToken = default); + + /// Deletes an SMTP configuration by ID. + /// The SMTP configuration ID. + /// Cancellation token. Task DeleteSmtpConfigurationAsync(int id, CancellationToken cancellationToken = default); + /// Saves pending changes to the repository. + /// Cancellation token. + /// The number of entities saved. Task SaveChangesAsync(CancellationToken cancellationToken = default); } diff --git a/src/ScadaLink.Commons/Interfaces/Repositories/ISecurityRepository.cs b/src/ScadaLink.Commons/Interfaces/Repositories/ISecurityRepository.cs index c9e7ccbc..441e81bc 100644 --- a/src/ScadaLink.Commons/Interfaces/Repositories/ISecurityRepository.cs +++ b/src/ScadaLink.Commons/Interfaces/Repositories/ISecurityRepository.cs @@ -5,19 +5,89 @@ namespace ScadaLink.Commons.Interfaces.Repositories; public interface ISecurityRepository { // LdapGroupMapping + /// + /// Gets an LDAP group mapping by ID. + /// + /// The mapping ID. + /// A cancellation token that can be used to cancel the operation. + /// The LDAP group mapping, or null if not found. Task GetMappingByIdAsync(int id, CancellationToken cancellationToken = default); + /// + /// Gets all LDAP group mappings. + /// + /// A cancellation token that can be used to cancel the operation. + /// A read-only list of all LDAP group mappings. Task> GetAllMappingsAsync(CancellationToken cancellationToken = default); + /// + /// Gets all LDAP group mappings for a specific role. + /// + /// The role name. + /// A cancellation token that can be used to cancel the operation. + /// A read-only list of LDAP group mappings for the role. Task> GetMappingsByRoleAsync(string role, CancellationToken cancellationToken = default); + /// + /// Adds a new LDAP group mapping. + /// + /// The LDAP group mapping to add. + /// A cancellation token that can be used to cancel the operation. + /// A task representing the asynchronous operation. Task AddMappingAsync(LdapGroupMapping mapping, CancellationToken cancellationToken = default); + /// + /// Updates an existing LDAP group mapping. + /// + /// The LDAP group mapping to update. + /// A cancellation token that can be used to cancel the operation. + /// A task representing the asynchronous operation. Task UpdateMappingAsync(LdapGroupMapping mapping, CancellationToken cancellationToken = default); + /// + /// Deletes an LDAP group mapping by ID. + /// + /// The mapping ID to delete. + /// A cancellation token that can be used to cancel the operation. + /// A task representing the asynchronous operation. Task DeleteMappingAsync(int id, CancellationToken cancellationToken = default); // SiteScopeRule + /// + /// Gets a site scope rule by ID. + /// + /// The scope rule ID. + /// A cancellation token that can be used to cancel the operation. + /// The site scope rule, or null if not found. Task GetScopeRuleByIdAsync(int id, CancellationToken cancellationToken = default); + /// + /// Gets all site scope rules for an LDAP group mapping. + /// + /// The LDAP group mapping ID. + /// A cancellation token that can be used to cancel the operation. + /// A read-only list of scope rules for the mapping. Task> GetScopeRulesForMappingAsync(int ldapGroupMappingId, CancellationToken cancellationToken = default); + /// + /// Adds a new site scope rule. + /// + /// The site scope rule to add. + /// A cancellation token that can be used to cancel the operation. + /// A task representing the asynchronous operation. Task AddScopeRuleAsync(SiteScopeRule rule, CancellationToken cancellationToken = default); + /// + /// Updates an existing site scope rule. + /// + /// The site scope rule to update. + /// A cancellation token that can be used to cancel the operation. + /// A task representing the asynchronous operation. Task UpdateScopeRuleAsync(SiteScopeRule rule, CancellationToken cancellationToken = default); + /// + /// Deletes a site scope rule by ID. + /// + /// The scope rule ID to delete. + /// A cancellation token that can be used to cancel the operation. + /// A task representing the asynchronous operation. Task DeleteScopeRuleAsync(int id, CancellationToken cancellationToken = default); + /// + /// Saves all pending changes to the database. + /// + /// A cancellation token that can be used to cancel the operation. + /// A task representing the number of entities saved. Task SaveChangesAsync(CancellationToken cancellationToken = default); } diff --git a/src/ScadaLink.Commons/Interfaces/Repositories/ISiteCallAuditRepository.cs b/src/ScadaLink.Commons/Interfaces/Repositories/ISiteCallAuditRepository.cs index 81c3a482..38e64e09 100644 --- a/src/ScadaLink.Commons/Interfaces/Repositories/ISiteCallAuditRepository.cs +++ b/src/ScadaLink.Commons/Interfaces/Repositories/ISiteCallAuditRepository.cs @@ -37,11 +37,15 @@ public interface ISiteCallAuditRepository /// the stored status' rank. Out-of-order / duplicate updates are silently /// dropped (monotonic forward-only progression). /// + /// The site call row to insert or monotonically update. + /// Cancellation token. Task UpsertAsync(SiteCall siteCall, CancellationToken ct = default); /// /// Returns the row for the given id, or null if none exists. /// + /// The tracked operation id to look up. + /// Cancellation token. Task GetAsync(TrackedOperationId id, CancellationToken ct = default); /// @@ -51,6 +55,9 @@ public interface ISiteCallAuditRepository /// + /// to fetch subsequent pages. /// + /// Filter criteria for the query. + /// Keyset paging parameters. + /// Cancellation token. Task> QueryAsync( SiteCallQueryFilter filter, SiteCallPaging paging, @@ -62,6 +69,8 @@ public interface ISiteCallAuditRepository /// (TerminalAtUtc IS NULL) are NEVER purged. Returns the number of rows /// deleted. /// + /// UTC cutoff; terminal rows older than this are deleted. + /// Cancellation token. Task PurgeTerminalAsync(DateTime olderThanUtc, CancellationToken ct = default); /// @@ -72,6 +81,9 @@ public interface ISiteCallAuditRepository /// ; the current time for OldestPendingAge /// is captured inside the method. /// + /// UTC threshold for classifying a row as stuck. + /// UTC start of the delivered/failed interval window. + /// Cancellation token. Task ComputeKpisAsync( DateTime stuckCutoff, DateTime intervalSince, @@ -82,6 +94,9 @@ public interface ISiteCallAuditRepository /// site. Sites with no SiteCalls rows at all are omitted. The stuck /// cutoff and interval bounds are interpreted as in . /// + /// UTC threshold for classifying a row as stuck. + /// UTC start of the delivered/failed interval window. + /// Cancellation token. Task> ComputePerSiteKpisAsync( DateTime stuckCutoff, DateTime intervalSince, diff --git a/src/ScadaLink.Commons/Interfaces/Repositories/ISiteRepository.cs b/src/ScadaLink.Commons/Interfaces/Repositories/ISiteRepository.cs index 81be37ce..a1ba5cd4 100644 --- a/src/ScadaLink.Commons/Interfaces/Repositories/ISiteRepository.cs +++ b/src/ScadaLink.Commons/Interfaces/Repositories/ISiteRepository.cs @@ -9,23 +9,62 @@ namespace ScadaLink.Commons.Interfaces.Repositories; public interface ISiteRepository { // Sites + /// Retrieves a site by its ID. + /// The site primary key. + /// Cancellation token. Task GetSiteByIdAsync(int id, CancellationToken cancellationToken = default); + /// Retrieves a site by its identifier. + /// The unique site identifier string. + /// Cancellation token. Task GetSiteByIdentifierAsync(string siteIdentifier, CancellationToken cancellationToken = default); + /// Retrieves all sites. + /// Cancellation token. Task> GetAllSitesAsync(CancellationToken cancellationToken = default); + /// Adds a new site. + /// The site entity to add. + /// Cancellation token. Task AddSiteAsync(Site site, CancellationToken cancellationToken = default); + /// Updates an existing site. + /// The site entity with updated values. + /// Cancellation token. Task UpdateSiteAsync(Site site, CancellationToken cancellationToken = default); + /// Deletes a site. + /// The site primary key. + /// Cancellation token. Task DeleteSiteAsync(int id, CancellationToken cancellationToken = default); // Data Connections + /// Retrieves a data connection by its ID. + /// The data connection primary key. + /// Cancellation token. Task GetDataConnectionByIdAsync(int id, CancellationToken cancellationToken = default); + /// Retrieves all data connections. + /// Cancellation token. Task> GetAllDataConnectionsAsync(CancellationToken cancellationToken = default); + /// Retrieves all data connections for a site. + /// The site primary key to filter by. + /// Cancellation token. Task> GetDataConnectionsBySiteIdAsync(int siteId, CancellationToken cancellationToken = default); + /// Adds a new data connection. + /// The data connection entity to add. + /// Cancellation token. Task AddDataConnectionAsync(DataConnection connection, CancellationToken cancellationToken = default); + /// Updates an existing data connection. + /// The data connection entity with updated values. + /// Cancellation token. Task UpdateDataConnectionAsync(DataConnection connection, CancellationToken cancellationToken = default); + /// Deletes a data connection. + /// The data connection primary key. + /// Cancellation token. Task DeleteDataConnectionAsync(int id, CancellationToken cancellationToken = default); // Instances (for deletion constraint checks) + /// Retrieves all instances deployed to a site. + /// The site primary key to filter by. + /// Cancellation token. Task> GetInstancesBySiteIdAsync(int siteId, CancellationToken cancellationToken = default); + /// Saves all pending changes to the database. + /// Cancellation token. Task SaveChangesAsync(CancellationToken cancellationToken = default); } diff --git a/src/ScadaLink.Commons/Interfaces/Repositories/ITemplateEngineRepository.cs b/src/ScadaLink.Commons/Interfaces/Repositories/ITemplateEngineRepository.cs index 41461f71..36a4ff15 100644 --- a/src/ScadaLink.Commons/Interfaces/Repositories/ITemplateEngineRepository.cs +++ b/src/ScadaLink.Commons/Interfaces/Repositories/ITemplateEngineRepository.cs @@ -7,8 +7,16 @@ namespace ScadaLink.Commons.Interfaces.Repositories; public interface ITemplateEngineRepository { // Template + /// Retrieves a template by ID. + /// The template ID. + /// Cancellation token. Task GetTemplateByIdAsync(int id, CancellationToken cancellationToken = default); + /// Retrieves a template with its child entities by ID. + /// The template ID. + /// Cancellation token. Task GetTemplateWithChildrenAsync(int id, CancellationToken cancellationToken = default); + /// Retrieves all templates. + /// Cancellation token. Task> GetAllTemplatesAsync(CancellationToken cancellationToken = default); /// /// Returns every template that contains a composition referencing @@ -16,89 +24,271 @@ public interface ITemplateEngineRepository /// its Attributes / Scripts / Compositions so the caller can build a /// CompositionContext without a follow-up round-trip per parent. /// + /// The composed template ID. + /// Cancellation token. Task> GetTemplatesComposingAsync(int composedTemplateId, CancellationToken cancellationToken = default); + /// Adds a new template. + /// The template to add. + /// Cancellation token. Task AddTemplateAsync(Template template, CancellationToken cancellationToken = default); + /// Updates an existing template. + /// The template to update. + /// Cancellation token. Task UpdateTemplateAsync(Template template, CancellationToken cancellationToken = default); + /// Deletes a template by ID. + /// The template ID. + /// Cancellation token. Task DeleteTemplateAsync(int id, CancellationToken cancellationToken = default); // TemplateAttribute + /// Retrieves a template attribute by ID. + /// The attribute ID. + /// Cancellation token. Task GetTemplateAttributeByIdAsync(int id, CancellationToken cancellationToken = default); + /// Retrieves attributes for a template. + /// The template ID. + /// Cancellation token. Task> GetAttributesByTemplateIdAsync(int templateId, CancellationToken cancellationToken = default); + /// Adds a new template attribute. + /// The attribute to add. + /// Cancellation token. Task AddTemplateAttributeAsync(TemplateAttribute attribute, CancellationToken cancellationToken = default); + /// Updates an existing template attribute. + /// The attribute to update. + /// Cancellation token. Task UpdateTemplateAttributeAsync(TemplateAttribute attribute, CancellationToken cancellationToken = default); + /// Deletes a template attribute by ID. + /// The attribute ID. + /// Cancellation token. Task DeleteTemplateAttributeAsync(int id, CancellationToken cancellationToken = default); // TemplateAlarm + /// Retrieves a template alarm by ID. + /// The alarm ID. + /// Cancellation token. Task GetTemplateAlarmByIdAsync(int id, CancellationToken cancellationToken = default); + /// Retrieves alarms for a template. + /// The template ID. + /// Cancellation token. Task> GetAlarmsByTemplateIdAsync(int templateId, CancellationToken cancellationToken = default); + /// Adds a new template alarm. + /// The alarm to add. + /// Cancellation token. Task AddTemplateAlarmAsync(TemplateAlarm alarm, CancellationToken cancellationToken = default); + /// Updates an existing template alarm. + /// The alarm to update. + /// Cancellation token. Task UpdateTemplateAlarmAsync(TemplateAlarm alarm, CancellationToken cancellationToken = default); + /// Deletes a template alarm by ID. + /// The alarm ID. + /// Cancellation token. Task DeleteTemplateAlarmAsync(int id, CancellationToken cancellationToken = default); // TemplateScript + /// Retrieves a template script by ID. + /// The script ID. + /// Cancellation token. Task GetTemplateScriptByIdAsync(int id, CancellationToken cancellationToken = default); + /// Retrieves scripts for a template. + /// The template ID. + /// Cancellation token. Task> GetScriptsByTemplateIdAsync(int templateId, CancellationToken cancellationToken = default); + /// Adds a new template script. + /// The script to add. + /// Cancellation token. Task AddTemplateScriptAsync(TemplateScript script, CancellationToken cancellationToken = default); + /// Updates an existing template script. + /// The script to update. + /// Cancellation token. Task UpdateTemplateScriptAsync(TemplateScript script, CancellationToken cancellationToken = default); + /// Deletes a template script by ID. + /// The script ID. + /// Cancellation token. Task DeleteTemplateScriptAsync(int id, CancellationToken cancellationToken = default); // TemplateComposition + /// Retrieves a template composition by ID. + /// The composition ID. + /// Cancellation token. Task GetTemplateCompositionByIdAsync(int id, CancellationToken cancellationToken = default); + /// Retrieves compositions for a template. + /// The template ID. + /// Cancellation token. Task> GetCompositionsByTemplateIdAsync(int templateId, CancellationToken cancellationToken = default); + /// Adds a new template composition. + /// The composition to add. + /// Cancellation token. Task AddTemplateCompositionAsync(TemplateComposition composition, CancellationToken cancellationToken = default); + /// Updates an existing template composition. + /// The composition to update. + /// Cancellation token. Task UpdateTemplateCompositionAsync(TemplateComposition composition, CancellationToken cancellationToken = default); + /// Deletes a template composition by ID. + /// The composition ID. + /// Cancellation token. Task DeleteTemplateCompositionAsync(int id, CancellationToken cancellationToken = default); // Instance + /// Retrieves an instance by ID. + /// The instance ID. + /// Cancellation token. Task GetInstanceByIdAsync(int id, CancellationToken cancellationToken = default); + /// Retrieves all instances. + /// Cancellation token. Task> GetAllInstancesAsync(CancellationToken cancellationToken = default); + /// Retrieves instances for a template. + /// The template ID. + /// Cancellation token. Task> GetInstancesByTemplateIdAsync(int templateId, CancellationToken cancellationToken = default); + /// Retrieves instances for a site. + /// The site ID. + /// Cancellation token. Task> GetInstancesBySiteIdAsync(int siteId, CancellationToken cancellationToken = default); + /// Retrieves an instance by unique name. + /// The unique instance name. + /// Cancellation token. Task GetInstanceByUniqueNameAsync(string uniqueName, CancellationToken cancellationToken = default); + /// Adds a new instance. + /// The instance to add. + /// Cancellation token. Task AddInstanceAsync(Instance instance, CancellationToken cancellationToken = default); + /// Updates an existing instance. + /// The instance to update. + /// Cancellation token. Task UpdateInstanceAsync(Instance instance, CancellationToken cancellationToken = default); + /// Deletes an instance by ID. + /// The instance ID. + /// Cancellation token. Task DeleteInstanceAsync(int id, CancellationToken cancellationToken = default); // InstanceAttributeOverride + /// Retrieves attribute overrides for an instance. + /// The instance ID. + /// Cancellation token. Task> GetOverridesByInstanceIdAsync(int instanceId, CancellationToken cancellationToken = default); + /// Adds a new instance attribute override. + /// The override to add. + /// Cancellation token. Task AddInstanceAttributeOverrideAsync(InstanceAttributeOverride attributeOverride, CancellationToken cancellationToken = default); + /// Updates an existing instance attribute override. + /// The override to update. + /// Cancellation token. Task UpdateInstanceAttributeOverrideAsync(InstanceAttributeOverride attributeOverride, CancellationToken cancellationToken = default); + /// Deletes an instance attribute override by ID. + /// The override ID. + /// Cancellation token. Task DeleteInstanceAttributeOverrideAsync(int id, CancellationToken cancellationToken = default); // InstanceAlarmOverride + /// Retrieves alarm overrides for an instance. + /// The instance ID. + /// Cancellation token. Task> GetAlarmOverridesByInstanceIdAsync(int instanceId, CancellationToken cancellationToken = default); + /// Retrieves an alarm override by instance and alarm name. + /// The instance ID. + /// The alarm canonical name. + /// Cancellation token. Task GetAlarmOverrideAsync(int instanceId, string alarmCanonicalName, CancellationToken cancellationToken = default); + /// Adds a new instance alarm override. + /// The override to add. + /// Cancellation token. Task AddInstanceAlarmOverrideAsync(InstanceAlarmOverride alarmOverride, CancellationToken cancellationToken = default); + /// Updates an existing instance alarm override. + /// The override to update. + /// Cancellation token. Task UpdateInstanceAlarmOverrideAsync(InstanceAlarmOverride alarmOverride, CancellationToken cancellationToken = default); + /// Deletes an instance alarm override by ID. + /// The override ID. + /// Cancellation token. Task DeleteInstanceAlarmOverrideAsync(int id, CancellationToken cancellationToken = default); // InstanceConnectionBinding + /// Retrieves connection bindings for an instance. + /// The instance ID. + /// Cancellation token. Task> GetBindingsByInstanceIdAsync(int instanceId, CancellationToken cancellationToken = default); + /// Adds a new instance connection binding. + /// The binding to add. + /// Cancellation token. Task AddInstanceConnectionBindingAsync(InstanceConnectionBinding binding, CancellationToken cancellationToken = default); + /// Updates an existing instance connection binding. + /// The binding to update. + /// Cancellation token. Task UpdateInstanceConnectionBindingAsync(InstanceConnectionBinding binding, CancellationToken cancellationToken = default); + /// Deletes an instance connection binding by ID. + /// The binding ID. + /// Cancellation token. Task DeleteInstanceConnectionBindingAsync(int id, CancellationToken cancellationToken = default); // Area + /// Retrieves an area by ID. + /// The area ID. + /// Cancellation token. Task GetAreaByIdAsync(int id, CancellationToken cancellationToken = default); + /// Retrieves areas for a site. + /// The site ID. + /// Cancellation token. Task> GetAreasBySiteIdAsync(int siteId, CancellationToken cancellationToken = default); + /// Adds a new area. + /// The area to add. + /// Cancellation token. Task AddAreaAsync(Area area, CancellationToken cancellationToken = default); + /// Updates an existing area. + /// The area to update. + /// Cancellation token. Task UpdateAreaAsync(Area area, CancellationToken cancellationToken = default); + /// Deletes an area by ID. + /// The area ID. + /// Cancellation token. Task DeleteAreaAsync(int id, CancellationToken cancellationToken = default); // SharedScript + /// Retrieves a shared script by ID. + /// The script ID. + /// Cancellation token. Task GetSharedScriptByIdAsync(int id, CancellationToken cancellationToken = default); + /// Retrieves a shared script by name. + /// The script name. + /// Cancellation token. Task GetSharedScriptByNameAsync(string name, CancellationToken cancellationToken = default); + /// Retrieves all shared scripts. + /// Cancellation token. Task> GetAllSharedScriptsAsync(CancellationToken cancellationToken = default); + /// Adds a new shared script. + /// The script to add. + /// Cancellation token. Task AddSharedScriptAsync(SharedScript sharedScript, CancellationToken cancellationToken = default); + /// Updates an existing shared script. + /// The script to update. + /// Cancellation token. Task UpdateSharedScriptAsync(SharedScript sharedScript, CancellationToken cancellationToken = default); + /// Deletes a shared script by ID. + /// The script ID. + /// Cancellation token. Task DeleteSharedScriptAsync(int id, CancellationToken cancellationToken = default); // TemplateFolder + /// Retrieves a template folder by ID. + /// The folder ID. + /// Cancellation token. Task GetFolderByIdAsync(int id, CancellationToken cancellationToken = default); + /// Retrieves all template folders. + /// Cancellation token. Task> GetAllFoldersAsync(CancellationToken cancellationToken = default); + /// Adds a new template folder. + /// The folder to add. + /// Cancellation token. Task AddFolderAsync(TemplateFolder folder, CancellationToken cancellationToken = default); + /// Updates an existing template folder. + /// The folder to update. + /// Cancellation token. Task UpdateFolderAsync(TemplateFolder folder, CancellationToken cancellationToken = default); + /// Deletes a template folder by ID. + /// The folder ID. + /// Cancellation token. Task DeleteFolderAsync(int id, CancellationToken cancellationToken = default); + /// Saves pending changes to the database. + /// Cancellation token. Task SaveChangesAsync(CancellationToken cancellationToken = default); } diff --git a/src/ScadaLink.Commons/Interfaces/Services/IAuditService.cs b/src/ScadaLink.Commons/Interfaces/Services/IAuditService.cs index 984bc3fe..eb2ef72e 100644 --- a/src/ScadaLink.Commons/Interfaces/Services/IAuditService.cs +++ b/src/ScadaLink.Commons/Interfaces/Services/IAuditService.cs @@ -2,5 +2,15 @@ namespace ScadaLink.Commons.Interfaces.Services; public interface IAuditService { + /// + /// Appends an audit log entry recording a user action on an entity. + /// + /// The authenticated username performing the action. + /// The action performed (e.g., "Create", "Update", "Delete"). + /// The type name of the affected entity. + /// The string representation of the entity's primary key. + /// The display name of the affected entity. + /// The entity state after the action; may be null for deletes. + /// Cancellation token for the log write. Task LogAsync(string user, string action, string entityType, string entityId, string entityName, object? afterState, CancellationToken cancellationToken = default); } diff --git a/src/ScadaLink.Commons/Interfaces/Services/IAuditWriter.cs b/src/ScadaLink.Commons/Interfaces/Services/IAuditWriter.cs index a95bc15e..5c4a0ae1 100644 --- a/src/ScadaLink.Commons/Interfaces/Services/IAuditWriter.cs +++ b/src/ScadaLink.Commons/Interfaces/Services/IAuditWriter.cs @@ -13,5 +13,7 @@ public interface IAuditWriter /// Persist an audit event. Best-effort: implementations must swallow/log internal failures /// rather than propagating them to the calling boundary code. /// + /// The audit event to persist. + /// Cancellation token. Task WriteAsync(AuditEvent evt, CancellationToken ct = default); } diff --git a/src/ScadaLink.Commons/Interfaces/Services/ICachedCallLifecycleObserver.cs b/src/ScadaLink.Commons/Interfaces/Services/ICachedCallLifecycleObserver.cs index cbc365c6..a6f45daa 100644 --- a/src/ScadaLink.Commons/Interfaces/Services/ICachedCallLifecycleObserver.cs +++ b/src/ScadaLink.Commons/Interfaces/Services/ICachedCallLifecycleObserver.cs @@ -34,6 +34,8 @@ public interface ICachedCallLifecycleObserver /// the per-category channel discriminator, retry-count + last-error /// context, and whether the outcome reached a terminal state. /// + /// Per-attempt context including the tracking id, outcome, and audit provenance fields. + /// Cancellation token for the observation operation. Task OnAttemptCompletedAsync(CachedCallAttemptContext context, CancellationToken ct = default); } diff --git a/src/ScadaLink.Commons/Interfaces/Services/ICachedCallTelemetryForwarder.cs b/src/ScadaLink.Commons/Interfaces/Services/ICachedCallTelemetryForwarder.cs index 60ff6bbe..e4f269a6 100644 --- a/src/ScadaLink.Commons/Interfaces/Services/ICachedCallTelemetryForwarder.cs +++ b/src/ScadaLink.Commons/Interfaces/Services/ICachedCallTelemetryForwarder.cs @@ -30,5 +30,7 @@ public interface ICachedCallTelemetryForwarder /// swallowed; the returned Task completes when both halves have been /// attempted. /// + /// The combined-telemetry packet to fan out. + /// Cancellation token. Task ForwardAsync(CachedCallTelemetry telemetry, CancellationToken ct = default); } diff --git a/src/ScadaLink.Commons/Interfaces/Services/ICentralAuditWriter.cs b/src/ScadaLink.Commons/Interfaces/Services/ICentralAuditWriter.cs index d91f5340..56599d2e 100644 --- a/src/ScadaLink.Commons/Interfaces/Services/ICentralAuditWriter.cs +++ b/src/ScadaLink.Commons/Interfaces/Services/ICentralAuditWriter.cs @@ -12,5 +12,7 @@ public interface ICentralAuditWriter /// Persist an audit event into the central AuditLog table directly (bypassing site telemetry). /// Best-effort: implementations must swallow/log internal failures rather than propagating them. /// + /// The audit event to persist. + /// Cancellation token. Task WriteAsync(AuditEvent evt, CancellationToken ct = default); } diff --git a/src/ScadaLink.Commons/Interfaces/Services/IDatabaseGateway.cs b/src/ScadaLink.Commons/Interfaces/Services/IDatabaseGateway.cs index c7a89fd7..4184c439 100644 --- a/src/ScadaLink.Commons/Interfaces/Services/IDatabaseGateway.cs +++ b/src/ScadaLink.Commons/Interfaces/Services/IDatabaseGateway.cs @@ -14,6 +14,8 @@ public interface IDatabaseGateway /// Connection pooling is managed by the underlying provider. /// Caller is responsible for disposing. /// + /// Name of the configured database connection to open. + /// Cancellation token for the async open operation. Task GetConnectionAsync( string connectionName, CancellationToken cancellationToken = default); @@ -48,6 +50,11 @@ public interface IDatabaseGateway /// retry-loop cached-write audit rows carry it. null for a /// non-routed run. /// + /// Name of the configured database connection to write to. + /// SQL statement to execute as a store-and-forward write. + /// Optional SQL parameters for the statement. + /// Optional name of the instance that originated the write. + /// Cancellation token for the buffering operation. Task CachedWriteAsync( string connectionName, string sql, diff --git a/src/ScadaLink.Commons/Interfaces/Services/IExternalSystemClient.cs b/src/ScadaLink.Commons/Interfaces/Services/IExternalSystemClient.cs index 107156fd..af8f2490 100644 --- a/src/ScadaLink.Commons/Interfaces/Services/IExternalSystemClient.cs +++ b/src/ScadaLink.Commons/Interfaces/Services/IExternalSystemClient.cs @@ -11,6 +11,11 @@ public interface IExternalSystemClient /// /// Synchronous call to an external system. All failures returned to caller. /// + /// The name of the external system. + /// The name of the method to invoke. + /// Method parameters as a dictionary, or null if none. + /// Cancellation token. + /// The result of the external call. Task CallAsync( string systemName, string methodName, @@ -21,6 +26,11 @@ public interface IExternalSystemClient /// Attempt immediate delivery; on transient failure, hand to S&F engine. /// Permanent failures returned to caller. /// + /// The name of the external system. + /// The name of the method to invoke. + /// Method parameters as a dictionary, or null if none. + /// The instance name originating the call, or null. + /// Cancellation token. /// /// Audit Log #23 (M3): caller-supplied tracking id used as the /// store-and-forward message id so the S&F retry loop can read it @@ -49,6 +59,7 @@ public interface IExternalSystemClient /// retry-loop cached-call audit rows carry it. null for a non-routed /// run. /// + /// The result of the external call. Task CachedCallAsync( string systemName, string methodName, diff --git a/src/ScadaLink.Commons/Interfaces/Services/IInstanceLocator.cs b/src/ScadaLink.Commons/Interfaces/Services/IInstanceLocator.cs index 365fd913..6b49898b 100644 --- a/src/ScadaLink.Commons/Interfaces/Services/IInstanceLocator.cs +++ b/src/ScadaLink.Commons/Interfaces/Services/IInstanceLocator.cs @@ -10,6 +10,8 @@ public interface IInstanceLocator /// Resolves the site identifier for a given instance unique name. /// Returns null if the instance is not found. /// + /// System-wide unique name of the instance to look up. + /// Cancellation token. Task GetSiteIdForInstanceAsync( string instanceUniqueName, CancellationToken cancellationToken = default); diff --git a/src/ScadaLink.Commons/Interfaces/Services/INotificationDeliveryService.cs b/src/ScadaLink.Commons/Interfaces/Services/INotificationDeliveryService.cs index 9d77f1b8..863d363e 100644 --- a/src/ScadaLink.Commons/Interfaces/Services/INotificationDeliveryService.cs +++ b/src/ScadaLink.Commons/Interfaces/Services/INotificationDeliveryService.cs @@ -10,6 +10,11 @@ public interface INotificationDeliveryService /// Sends a notification to a named list. Transient failures go to S&F. /// Permanent failures returned to caller. /// + /// Name of the notification list to deliver to. + /// Subject line of the notification. + /// Plain-text body of the notification. + /// Optional name of the instance that triggered the send. + /// Cancellation token for the async operation. Task SendAsync( string listName, string subject, diff --git a/src/ScadaLink.Commons/Interfaces/Services/ISiteAuditQueue.cs b/src/ScadaLink.Commons/Interfaces/Services/ISiteAuditQueue.cs index c9e04626..b3606670 100644 --- a/src/ScadaLink.Commons/Interfaces/Services/ISiteAuditQueue.cs +++ b/src/ScadaLink.Commons/Interfaces/Services/ISiteAuditQueue.cs @@ -33,6 +33,8 @@ public interface ISiteAuditQueue /// oldest first. Idempotent — repeated calls before /// will yield the same rows again. /// + /// Maximum number of rows to return. + /// Cancellation token. Task> ReadPendingAsync(int limit, CancellationToken ct = default); /// @@ -41,6 +43,8 @@ public interface ISiteAuditQueue /// . /// Non-existent or already-forwarded ids are silent no-ops. /// + /// Event IDs to mark as forwarded. + /// Cancellation token. Task MarkForwardedAsync(IReadOnlyList eventIds, CancellationToken ct = default); /// @@ -58,6 +62,9 @@ public interface ISiteAuditQueue /// is oldest first with /// as the deterministic tiebreaker. /// + /// Lower bound timestamp (UTC). + /// Maximum number of rows to return. + /// Cancellation token. Task> ReadPendingSinceAsync( DateTime sinceUtc, int batchSize, CancellationToken ct = default); @@ -70,6 +77,8 @@ public interface ISiteAuditQueue /// Rows already in /// are left untouched (idempotent re-call). Non-existent ids are silent no-ops. /// + /// Event IDs to mark as reconciled. + /// Cancellation token. Task MarkReconciledAsync(IReadOnlyList eventIds, CancellationToken ct = default); /// @@ -83,5 +92,6 @@ public interface ISiteAuditQueue /// implementations are expected to take the same connection lock used by /// the hot-path INSERT batch and the drain queries. /// + /// Cancellation token. Task GetBacklogStatsAsync(CancellationToken ct = default); } diff --git a/src/ScadaLink.Commons/Interfaces/Transport/IAuditCorrelationContext.cs b/src/ScadaLink.Commons/Interfaces/Transport/IAuditCorrelationContext.cs index 962246a5..bc0007a2 100644 --- a/src/ScadaLink.Commons/Interfaces/Transport/IAuditCorrelationContext.cs +++ b/src/ScadaLink.Commons/Interfaces/Transport/IAuditCorrelationContext.cs @@ -18,5 +18,6 @@ namespace ScadaLink.Commons.Interfaces.Transport; /// public interface IAuditCorrelationContext { + /// Gets or sets the bundle import id used to correlate audit rows written during a bundle apply operation. Guid? BundleImportId { get; set; } } diff --git a/src/ScadaLink.Commons/Interfaces/Transport/IBundleExporter.cs b/src/ScadaLink.Commons/Interfaces/Transport/IBundleExporter.cs index e5b06840..473d5432 100644 --- a/src/ScadaLink.Commons/Interfaces/Transport/IBundleExporter.cs +++ b/src/ScadaLink.Commons/Interfaces/Transport/IBundleExporter.cs @@ -4,6 +4,14 @@ namespace ScadaLink.Commons.Interfaces.Transport; public interface IBundleExporter { + /// + /// Exports the selected artifacts as an encrypted or plain bundle stream. + /// + /// Specifies which artifact types and ids to include in the bundle. + /// Username of the operator performing the export, stamped in the manifest. + /// Environment label stamped in the bundle manifest. + /// Optional passphrase to encrypt the bundle; null produces an unencrypted bundle. + /// Cancellation token. Task ExportAsync( ExportSelection selection, string user, diff --git a/src/ScadaLink.Commons/Interfaces/Transport/IBundleImporter.cs b/src/ScadaLink.Commons/Interfaces/Transport/IBundleImporter.cs index d259d39b..fa8a35ab 100644 --- a/src/ScadaLink.Commons/Interfaces/Transport/IBundleImporter.cs +++ b/src/ScadaLink.Commons/Interfaces/Transport/IBundleImporter.cs @@ -4,8 +4,28 @@ namespace ScadaLink.Commons.Interfaces.Transport; public interface IBundleImporter { + /// + /// Validates and decrypts the bundle stream, opens a session, and returns session metadata. + /// + /// Stream containing the bundle zip archive. + /// Optional passphrase for decrypting an encrypted bundle. + /// Cancellation token. Task LoadAsync(Stream bundleStream, string? passphrase, CancellationToken ct = default); + + /// + /// Diffs the loaded bundle against the target database and returns a per-artifact preview. + /// + /// Session id returned by . + /// Cancellation token. Task PreviewAsync(Guid sessionId, CancellationToken ct = default); + + /// + /// Applies the chosen conflict resolutions and commits the import transaction. + /// + /// Session id returned by . + /// Per-artifact conflict resolutions from the preview step. + /// Username of the operator performing the import, stamped in audit rows. + /// Cancellation token. Task ApplyAsync( Guid sessionId, IReadOnlyList resolutions, diff --git a/src/ScadaLink.Commons/Interfaces/Transport/IBundleSessionStore.cs b/src/ScadaLink.Commons/Interfaces/Transport/IBundleSessionStore.cs index 0f8a1781..cea04d63 100644 --- a/src/ScadaLink.Commons/Interfaces/Transport/IBundleSessionStore.cs +++ b/src/ScadaLink.Commons/Interfaces/Transport/IBundleSessionStore.cs @@ -4,8 +4,15 @@ namespace ScadaLink.Commons.Interfaces.Transport; public interface IBundleSessionStore { + /// Stores the session and returns it; overwrites any existing session with the same id. + /// The session to store. BundleSession Open(BundleSession session); + /// Returns the session for the given id, or null if not found or expired. + /// The session identifier to look up. BundleSession? Get(Guid sessionId); + /// Removes the session for the given id, if present. + /// The session identifier to remove. void Remove(Guid sessionId); + /// Removes all sessions whose expiry has passed. void EvictExpired(); } diff --git a/src/ScadaLink.Commons/Messages/Management/ManagementCommandRegistry.cs b/src/ScadaLink.Commons/Messages/Management/ManagementCommandRegistry.cs index 7c9acb30..d7b88645 100644 --- a/src/ScadaLink.Commons/Messages/Management/ManagementCommandRegistry.cs +++ b/src/ScadaLink.Commons/Messages/Management/ManagementCommandRegistry.cs @@ -25,6 +25,10 @@ public static class ManagementCommandRegistry private static readonly FrozenDictionary NamesByType = Commands.ToFrozenDictionary(kv => kv.Value, kv => kv.Key); + /// + /// Resolves a management command wire name to its CLR type, or null if not registered. + /// + /// The wire name of the management command (without the "Command" suffix). public static Type? Resolve(string commandName) { return Commands.GetValueOrDefault(commandName); @@ -33,6 +37,7 @@ public static class ManagementCommandRegistry /// /// Returns the registered wire name for a management command type. /// + /// The CLR type of the management command to look up. /// /// Thrown when is not a registered management /// command — i.e. not a non-abstract *Command type in the diff --git a/src/ScadaLink.Commons/Messages/Streaming/ISiteStreamEvent.cs b/src/ScadaLink.Commons/Messages/Streaming/ISiteStreamEvent.cs index a83599c0..9f043fdc 100644 --- a/src/ScadaLink.Commons/Messages/Streaming/ISiteStreamEvent.cs +++ b/src/ScadaLink.Commons/Messages/Streaming/ISiteStreamEvent.cs @@ -6,5 +6,6 @@ namespace ScadaLink.Commons.Messages.Streaming; /// public interface ISiteStreamEvent { + /// The unique name of the instance that produced this event. string InstanceUniqueName { get; } } diff --git a/src/ScadaLink.Commons/Serialization/OpcUaEndpointConfigSerializer.cs b/src/ScadaLink.Commons/Serialization/OpcUaEndpointConfigSerializer.cs index b66bef0c..c0eb1dd7 100644 --- a/src/ScadaLink.Commons/Serialization/OpcUaEndpointConfigSerializer.cs +++ b/src/ScadaLink.Commons/Serialization/OpcUaEndpointConfigSerializer.cs @@ -34,6 +34,11 @@ public enum OpcUaConfigParseStatus /// public readonly record struct OpcUaConfigParseResult { + /// + /// Initializes the result with the parsed config and its classification status. + /// + /// The parsed endpoint config (or an empty default on malformed input). + /// Classification of the parse outcome. public OpcUaConfigParseResult(OpcUaEndpointConfig config, OpcUaConfigParseStatus status) { Config = config; @@ -58,6 +63,8 @@ public readonly record struct OpcUaConfigParseResult /// and ; callers that need to tell those /// apart should read directly. /// + /// Receives the parsed endpoint config. + /// Receives true when the source was the legacy flat-dict shape. public void Deconstruct(out OpcUaEndpointConfig config, out bool isLegacy) { config = Config; @@ -83,6 +90,11 @@ public static class OpcUaEndpointConfigSerializer Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) } }; + /// + /// Serializes an to the current typed JSON shape. + /// + /// The endpoint configuration to serialize. + /// A JSON string representing the configuration. public static string Serialize(OpcUaEndpointConfig config) => JsonSerializer.Serialize(config, JsonOpts); @@ -105,6 +117,7 @@ public static class OpcUaEndpointConfigSerializer /// caller should surface an error rather than treating it as the user's saved data. /// /// + /// The stored JSON string to parse; null or blank yields a default typed result. public static OpcUaConfigParseResult Deserialize(string? json) { if (string.IsNullOrWhiteSpace(json)) @@ -161,6 +174,7 @@ public static class OpcUaEndpointConfigSerializer /// IDataConnection.ConnectAsync expects. Keys match the historical convention /// used by OpcUaDataConnection so the adapter can keep that interface. /// + /// The endpoint configuration to flatten. public static IDictionary ToFlatDict(OpcUaEndpointConfig config) { var dict = new Dictionary @@ -202,6 +216,11 @@ public static class OpcUaEndpointConfigSerializer return dict; } + /// + /// Reconstructs an from the legacy flat string-dict shape. + /// + /// The flat key-value dictionary produced by the legacy shape. + /// The reconstructed endpoint configuration. public static OpcUaEndpointConfig FromFlatDict(IDictionary dict) { var c = new OpcUaEndpointConfig(); diff --git a/src/ScadaLink.Commons/Types/Audit/AuditQueryParamParsers.cs b/src/ScadaLink.Commons/Types/Audit/AuditQueryParamParsers.cs index adf1c1a0..145d5f6a 100644 --- a/src/ScadaLink.Commons/Types/Audit/AuditQueryParamParsers.cs +++ b/src/ScadaLink.Commons/Types/Audit/AuditQueryParamParsers.cs @@ -35,6 +35,9 @@ public static class AuditQueryParamParsers /// is null, empty, or yields no parseable /// value — so the filter dimension stays unconstrained. /// + /// The enum type to parse each raw value into. + /// Raw query-parameter string values to parse; may be null. + /// A non-empty list of parsed values, or null if no values could be parsed. public static IReadOnlyList? ParseEnumList(IEnumerable? rawValues) where TEnum : struct, Enum { @@ -59,6 +62,8 @@ public static class AuditQueryParamParsers /// is null, empty, or every value was /// blank. /// + /// Raw query-parameter string values to trim and filter; may be null. + /// A non-empty list of trimmed strings, or null if no non-blank values remain. public static IReadOnlyList? ParseStringList(IEnumerable? rawValues) { if (rawValues is null) diff --git a/src/ScadaLink.Commons/Types/DataConnections/OpcUaDeadbandConfig.cs b/src/ScadaLink.Commons/Types/DataConnections/OpcUaDeadbandConfig.cs index 798e8c58..bb9d4347 100644 --- a/src/ScadaLink.Commons/Types/DataConnections/OpcUaDeadbandConfig.cs +++ b/src/ScadaLink.Commons/Types/DataConnections/OpcUaDeadbandConfig.cs @@ -2,6 +2,8 @@ namespace ScadaLink.Commons.Types.DataConnections; public sealed class OpcUaDeadbandConfig { + /// Gets or sets the OPC UA deadband type (Absolute or Percent). public OpcUaDeadbandType Type { get; set; } = OpcUaDeadbandType.Absolute; + /// Gets or sets the deadband threshold value; meaning depends on . public double Value { get; set; } = 0.0; } diff --git a/src/ScadaLink.Commons/Types/DataConnections/OpcUaEndpointConfig.cs b/src/ScadaLink.Commons/Types/DataConnections/OpcUaEndpointConfig.cs index 3d872177..d790514d 100644 --- a/src/ScadaLink.Commons/Types/DataConnections/OpcUaEndpointConfig.cs +++ b/src/ScadaLink.Commons/Types/DataConnections/OpcUaEndpointConfig.cs @@ -1,34 +1,91 @@ namespace ScadaLink.Commons.Types.DataConnections; +/// +/// OPC UA endpoint configuration. +/// public sealed class OpcUaEndpointConfig { // Connection + /// + /// The OPC UA endpoint URL. + /// public string EndpointUrl { get; set; } = ""; + /// + /// The security mode for the connection. + /// public OpcUaSecurityMode SecurityMode { get; set; } = OpcUaSecurityMode.None; + /// + /// Whether to automatically accept untrusted certificates. + /// public bool AutoAcceptUntrustedCerts { get; set; } = true; // Timing + /// + /// Session timeout in milliseconds. + /// public int SessionTimeoutMs { get; set; } = 60000; + /// + /// Operation timeout in milliseconds. + /// public int OperationTimeoutMs { get; set; } = 15000; // Subscription + /// + /// Publishing interval in milliseconds. + /// public int PublishingIntervalMs { get; set; } = 1000; + /// + /// Sampling interval in milliseconds. + /// public int SamplingIntervalMs { get; set; } = 1000; + /// + /// Queue size for the subscription. + /// public int QueueSize { get; set; } = 10; + /// + /// Keep-alive count for the subscription. + /// public int KeepAliveCount { get; set; } = 10; + /// + /// Lifetime count for the subscription. + /// public int LifetimeCount { get; set; } = 30; + /// + /// Maximum notifications per publish. + /// public int MaxNotificationsPerPublish { get; set; } = 100; + /// + /// Whether to discard oldest notifications when queue is full. + /// public bool DiscardOldest { get; set; } = true; + /// + /// Subscription priority level. + /// public byte SubscriptionPriority { get; set; } = 0; + /// + /// Display name for the subscription. + /// public string SubscriptionDisplayName { get; set; } = "ScadaLink"; // Read / filter + /// + /// Timestamps to return in read operations. + /// public OpcUaTimestampsToReturn TimestampsToReturn { get; set; } = OpcUaTimestampsToReturn.Source; + /// + /// Deadband configuration for filtering notifications. + /// public OpcUaDeadbandConfig? Deadband { get; set; } // Authentication (optional; null = anonymous) + /// + /// User identity configuration for authentication. + /// public OpcUaUserIdentityConfig? UserIdentity { get; set; } // Heartbeat (optional) + /// + /// Heartbeat configuration for connection monitoring. + /// public OpcUaHeartbeatConfig? Heartbeat { get; set; } } diff --git a/src/ScadaLink.Commons/Types/DataConnections/OpcUaHeartbeatConfig.cs b/src/ScadaLink.Commons/Types/DataConnections/OpcUaHeartbeatConfig.cs index 1882a115..d2ac8dc8 100644 --- a/src/ScadaLink.Commons/Types/DataConnections/OpcUaHeartbeatConfig.cs +++ b/src/ScadaLink.Commons/Types/DataConnections/OpcUaHeartbeatConfig.cs @@ -2,6 +2,8 @@ namespace ScadaLink.Commons.Types.DataConnections; public sealed class OpcUaHeartbeatConfig { + /// OPC UA node path of the heartbeat tag to monitor for activity. public string TagPath { get; set; } = ""; + /// Maximum number of seconds without a value update before the connection is considered unhealthy. public int MaxSilenceSeconds { get; set; } = 30; } diff --git a/src/ScadaLink.Commons/Types/DataConnections/OpcUaUserIdentityConfig.cs b/src/ScadaLink.Commons/Types/DataConnections/OpcUaUserIdentityConfig.cs index a1bd3a88..b2093ee2 100644 --- a/src/ScadaLink.Commons/Types/DataConnections/OpcUaUserIdentityConfig.cs +++ b/src/ScadaLink.Commons/Types/DataConnections/OpcUaUserIdentityConfig.cs @@ -1,10 +1,18 @@ namespace ScadaLink.Commons.Types.DataConnections; +/// +/// OPC UA user identity configuration for a data connection endpoint. +/// public sealed class OpcUaUserIdentityConfig { + /// The OPC UA user token type (Anonymous, UserName, or Certificate). public OpcUaUserTokenType TokenType { get; set; } = OpcUaUserTokenType.Anonymous; + /// Username for UserName token type authentication. public string Username { get; set; } = ""; + /// Password for UserName token type authentication. public string Password { get; set; } = ""; + /// File path to the X.509 certificate for Certificate token type authentication. public string CertificatePath { get; set; } = ""; + /// Password to unlock the certificate private key. public string CertificatePassword { get; set; } = ""; } diff --git a/src/ScadaLink.Commons/Types/DynamicJsonElement.cs b/src/ScadaLink.Commons/Types/DynamicJsonElement.cs index f1b4ec2e..7fafaa53 100644 --- a/src/ScadaLink.Commons/Types/DynamicJsonElement.cs +++ b/src/ScadaLink.Commons/Types/DynamicJsonElement.cs @@ -20,6 +20,8 @@ public class DynamicJsonElement : DynamicObject { private readonly JsonElement _element; + /// Initializes a new wrapping a clone of the given . + /// The JSON element to wrap; it is cloned to decouple lifetime from the source document. public DynamicJsonElement(JsonElement element) { // Clone detaches the element from its owning JsonDocument so accessing it @@ -27,6 +29,7 @@ public class DynamicJsonElement : DynamicObject _element = element.Clone(); } + /// public override bool TryGetMember(GetMemberBinder binder, out object? result) { if (_element.ValueKind == JsonValueKind.Object && @@ -39,6 +42,7 @@ public class DynamicJsonElement : DynamicObject return false; } + /// public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object? result) { // Accept any integral index, not just int. DynamicJsonElement surfaces JSON @@ -80,6 +84,7 @@ public class DynamicJsonElement : DynamicObject } } + /// public override bool TryConvert(ConvertBinder binder, out object? result) { // Conversion to object (or dynamic): never null out a present value. Return the @@ -97,6 +102,7 @@ public class DynamicJsonElement : DynamicObject return result != null; } + /// public override string ToString() { return _element.ValueKind switch diff --git a/src/ScadaLink.Commons/Types/Flattening/ConfigurationDiff.cs b/src/ScadaLink.Commons/Types/Flattening/ConfigurationDiff.cs index af9a68d6..87b5f923 100644 --- a/src/ScadaLink.Commons/Types/Flattening/ConfigurationDiff.cs +++ b/src/ScadaLink.Commons/Types/Flattening/ConfigurationDiff.cs @@ -6,13 +6,20 @@ namespace ScadaLink.Commons.Types.Flattening; /// public sealed record ConfigurationDiff { + /// Unique name of the instance this diff applies to. public string InstanceUniqueName { get; init; } = string.Empty; + /// Revision hash of the previously deployed configuration, or null if not previously deployed. public string? OldRevisionHash { get; init; } + /// Revision hash of the new configuration being compared. public string? NewRevisionHash { get; init; } + /// True when any attribute, alarm, or script changes are present. public bool HasChanges => AttributeChanges.Count > 0 || AlarmChanges.Count > 0 || ScriptChanges.Count > 0; + /// Diff entries for resolved attributes. public IReadOnlyList> AttributeChanges { get; init; } = []; + /// Diff entries for resolved alarms. public IReadOnlyList> AlarmChanges { get; init; } = []; + /// Diff entries for resolved scripts. public IReadOnlyList> ScriptChanges { get; init; } = []; } @@ -21,7 +28,9 @@ public sealed record ConfigurationDiff /// public sealed record DiffEntry { + /// The canonical name of the changed entity. public string CanonicalName { get; init; } = string.Empty; + /// The type of change: Added, Removed, or Changed. public DiffChangeType ChangeType { get; init; } /// diff --git a/src/ScadaLink.Commons/Types/Flattening/FlattenedConfiguration.cs b/src/ScadaLink.Commons/Types/Flattening/FlattenedConfiguration.cs index 500d6d22..35905b27 100644 --- a/src/ScadaLink.Commons/Types/Flattening/FlattenedConfiguration.cs +++ b/src/ScadaLink.Commons/Types/Flattening/FlattenedConfiguration.cs @@ -9,13 +9,21 @@ namespace ScadaLink.Commons.Types.Flattening; /// public sealed record FlattenedConfiguration { + /// Gets the instance unique name. public string InstanceUniqueName { get; init; } = string.Empty; + /// Gets the template ID. public int TemplateId { get; init; } + /// Gets the site ID. public int SiteId { get; init; } + /// Gets the area ID, if any. public int? AreaId { get; init; } + /// Gets the resolved attributes. public IReadOnlyList Attributes { get; init; } = []; + /// Gets the resolved alarms. public IReadOnlyList Alarms { get; init; } = []; + /// Gets the resolved scripts. public IReadOnlyList Scripts { get; init; } = []; + /// Gets the UTC timestamp when this configuration was generated. public DateTimeOffset GeneratedAtUtc { get; init; } = DateTimeOffset.UtcNow; /// @@ -31,9 +39,13 @@ public sealed record FlattenedConfiguration /// public sealed record ConnectionConfig { + /// Gets the protocol name (e.g., "OpcUa"). public string Protocol { get; init; } = string.Empty; + /// Gets the primary configuration as JSON. public string? ConfigurationJson { get; init; } + /// Gets the backup configuration as JSON. public string? BackupConfigurationJson { get; init; } + /// Gets the number of failover retries. public int FailoverRetryCount { get; init; } = 3; } @@ -48,9 +60,13 @@ public sealed record ResolvedAttribute /// public string CanonicalName { get; init; } = string.Empty; + /// Gets the attribute value. public string? Value { get; init; } + /// Gets the data type name. public string DataType { get; init; } = string.Empty; + /// Gets whether the attribute is locked. public bool IsLocked { get; init; } + /// Gets the attribute description. public string? Description { get; init; } /// @@ -84,10 +100,15 @@ public sealed record ResolvedAttribute /// public sealed record ResolvedAlarm { + /// Gets the path-qualified canonical name. public string CanonicalName { get; init; } = string.Empty; + /// Gets the alarm description. public string? Description { get; init; } + /// Gets the priority level. public int PriorityLevel { get; init; } + /// Gets whether the alarm is locked. public bool IsLocked { get; init; } + /// Gets the trigger type. public string TriggerType { get; init; } = string.Empty; /// @@ -100,6 +121,7 @@ public sealed record ResolvedAlarm /// public string? OnTriggerScriptCanonicalName { get; init; } + /// Gets the source of this alarm value: "Template", "Inherited", "Composed", or "Override". public string Source { get; init; } = "Template"; } @@ -108,10 +130,15 @@ public sealed record ResolvedAlarm /// public sealed record ResolvedScript { + /// Gets the path-qualified canonical name. public string CanonicalName { get; init; } = string.Empty; + /// Gets the script code. public string Code { get; init; } = string.Empty; + /// Gets whether the script is locked. public bool IsLocked { get; init; } + /// Gets the trigger type. public string? TriggerType { get; init; } + /// Gets the trigger configuration. public string? TriggerConfiguration { get; init; } /// @@ -124,7 +151,9 @@ public sealed record ResolvedScript /// public string? ReturnDefinition { get; init; } + /// Gets the minimum time between script executions. public TimeSpan? MinTimeBetweenRuns { get; init; } + /// Gets the source of this script. public string Source { get; init; } = "Template"; /// diff --git a/src/ScadaLink.Commons/Types/Flattening/ValidationResult.cs b/src/ScadaLink.Commons/Types/Flattening/ValidationResult.cs index 14658018..c607c427 100644 --- a/src/ScadaLink.Commons/Types/Flattening/ValidationResult.cs +++ b/src/ScadaLink.Commons/Types/Flattening/ValidationResult.cs @@ -5,15 +5,23 @@ namespace ScadaLink.Commons.Types.Flattening; /// public sealed record ValidationResult { + /// True when there are no validation errors. public bool IsValid => Errors.Count == 0; + /// Validation errors that block the operation. public IReadOnlyList Errors { get; init; } = []; + /// Non-blocking validation warnings. public IReadOnlyList Warnings { get; init; } = []; + /// Returns a result with no errors or warnings. public static ValidationResult Success() => new(); + /// Returns a result containing the given errors. + /// The validation errors to include. public static ValidationResult FromErrors(params ValidationEntry[] errors) => new() { Errors = errors }; + /// Merges multiple validation results into a single combined result. + /// The results to merge. public static ValidationResult Merge(params ValidationResult[] results) { var errors = new List(); @@ -32,7 +40,9 @@ public sealed record ValidationResult /// public sealed record ValidationEntry { + /// The category classifying the kind of validation failure. public ValidationCategory Category { get; init; } + /// Human-readable description of the validation issue. public string Message { get; init; } = string.Empty; /// @@ -40,9 +50,17 @@ public sealed record ValidationEntry /// public string? EntityName { get; init; } + /// Creates an error entry with the given category, message, and optional entity name. + /// The validation category. + /// The error message. + /// The canonical name of the entity that caused the error, if any. public static ValidationEntry Error(ValidationCategory category, string message, string? entityName = null) => new() { Category = category, Message = message, EntityName = entityName }; + /// Creates a warning entry with the given category, message, and optional entity name. + /// The validation category. + /// The warning message. + /// The canonical name of the entity that triggered the warning, if any. public static ValidationEntry Warning(ValidationCategory category, string message, string? entityName = null) => new() { Category = category, Message = message, EntityName = entityName }; } diff --git a/src/ScadaLink.Commons/Types/InboundApi/ApiKeyHasher.cs b/src/ScadaLink.Commons/Types/InboundApi/ApiKeyHasher.cs index ae5a05ce..f4b3aae2 100644 --- a/src/ScadaLink.Commons/Types/InboundApi/ApiKeyHasher.cs +++ b/src/ScadaLink.Commons/Types/InboundApi/ApiKeyHasher.cs @@ -16,6 +16,8 @@ public interface IApiKeyHasher /// The same input always produces the same output (deterministic), which keeps /// the by-value lookup working. /// + /// The raw API key to hash. + /// A Base64-encoded HMAC-SHA256 hash of the key. string Hash(string apiKey); } @@ -54,6 +56,7 @@ public sealed class ApiKeyHasher : IApiKeyHasher /// /// Creates a hasher keyed with the given server-side pepper. /// + /// Server-side HMAC key; must be at least characters. /// /// Thrown if is null, blank, or shorter than /// — a missing or weak pepper is a deployment diff --git a/src/ScadaLink.Commons/Types/InboundApi/ParameterDefinition.cs b/src/ScadaLink.Commons/Types/InboundApi/ParameterDefinition.cs index 0353a928..e82a7426 100644 --- a/src/ScadaLink.Commons/Types/InboundApi/ParameterDefinition.cs +++ b/src/ScadaLink.Commons/Types/InboundApi/ParameterDefinition.cs @@ -9,7 +9,10 @@ namespace ScadaLink.Commons.Types.InboundApi; /// public class ParameterDefinition { + /// Gets or sets the parameter name as it must appear in the JSON request body. public string Name { get; set; } = string.Empty; + /// Gets or sets the expected type (e.g. "String", "Integer", "Float", "Boolean", "Object", "List"). public string Type { get; set; } = "String"; + /// Gets or sets whether this parameter must be present in the request body. public bool Required { get; set; } = true; } diff --git a/src/ScadaLink.Commons/Types/Result.cs b/src/ScadaLink.Commons/Types/Result.cs index 742351e7..40d26338 100644 --- a/src/ScadaLink.Commons/Types/Result.cs +++ b/src/ScadaLink.Commons/Types/Result.cs @@ -22,27 +22,40 @@ public sealed class Result IsSuccess = false; } + /// True when the result represents a successful outcome. public bool IsSuccess { get; } + /// True when the result represents a failure outcome. public bool IsFailure => !IsSuccess; + /// The success value; throws when is true. public T Value => IsSuccess ? _value! : throw new InvalidOperationException("Cannot access Value on a failed Result. Error: " + _error); + /// The error message; throws when is true. public string Error => IsFailure ? _error! : throw new InvalidOperationException("Cannot access Error on a successful Result."); + /// Creates a successful result carrying the given value. + /// The success value. + /// A successful . public static Result Success(T value) => new(value); /// /// Creates a failed result carrying the given error message. /// + /// Non-blank error message describing the failure. /// is null. /// is empty or whitespace. public static Result Failure(string error) => new(error); + /// Pattern-matches the result, invoking either the success or failure delegate. + /// The return type of both delegates. + /// Delegate invoked with the value when the result is successful. + /// Delegate invoked with the error message when the result is a failure. + /// The value returned by the invoked delegate. public TResult Match(Func onSuccess, Func onFailure) => IsSuccess ? onSuccess(_value!) : onFailure(_error!); } diff --git a/src/ScadaLink.Commons/Types/ScriptArgs.cs b/src/ScadaLink.Commons/Types/ScriptArgs.cs index 3c095a15..ae11d135 100644 --- a/src/ScadaLink.Commons/Types/ScriptArgs.cs +++ b/src/ScadaLink.Commons/Types/ScriptArgs.cs @@ -16,6 +16,9 @@ namespace ScadaLink.Commons.Types; /// public static class ScriptArgs { + /// Normalizes a loosely-typed parameters argument into a read-only string-keyed dictionary, or null if no parameters were supplied. + /// Null, an existing dictionary, or an anonymous object whose properties become parameter entries. + /// A normalized read-only dictionary, or null when is null. public static IReadOnlyDictionary? Normalize(object? parameters) { switch (parameters) diff --git a/src/ScadaLink.Commons/Types/ScriptParameters.cs b/src/ScadaLink.Commons/Types/ScriptParameters.cs index 46a1e6a2..47fa2884 100644 --- a/src/ScadaLink.Commons/Types/ScriptParameters.cs +++ b/src/ScadaLink.Commons/Types/ScriptParameters.cs @@ -13,11 +13,18 @@ public class ScriptParameters : IReadOnlyDictionary { private readonly IReadOnlyDictionary _inner; + /// + /// Initializes a new instance of the class with the specified parameters. + /// + /// The underlying parameter dictionary. public ScriptParameters(IReadOnlyDictionary parameters) { _inner = parameters ?? throw new ArgumentNullException(nameof(parameters)); } + /// + /// Initializes a new instance of the class with an empty parameter dictionary. + /// public ScriptParameters() : this(new Dictionary()) { } /// @@ -30,6 +37,9 @@ public class ScriptParameters : IReadOnlyDictionary /// Get<List<int>>("key") — converts list to typed List; throws on first bad element. /// /// + /// The target type for the parameter value. + /// The parameter key. + /// The converted parameter value. public T Get(string key) { var targetType = typeof(T); @@ -220,13 +230,43 @@ public class ScriptParameters : IReadOnlyDictionary } // IReadOnlyDictionary implementation + /// + /// Gets the value associated with the specified key. + /// + /// The parameter key. + /// The parameter value, or null if the key is not found. public object? this[string key] => _inner[key]; + /// + /// Gets the collection of parameter keys. + /// public IEnumerable Keys => _inner.Keys; + /// + /// Gets the collection of parameter values. + /// public IEnumerable Values => _inner.Values; + /// + /// Gets the number of parameters. + /// public int Count => _inner.Count; + /// + /// Determines whether the parameters contain the specified key. + /// + /// The key to locate. + /// True if the key is found; false otherwise. public bool ContainsKey(string key) => _inner.ContainsKey(key); + /// + /// Attempts to get the value associated with the specified key. + /// + /// The key to locate. + /// The value associated with the key if found; otherwise null. + /// True if the key is found; false otherwise. public bool TryGetValue(string key, out object? value) => _inner.TryGetValue(key, out value); + /// + /// Returns an enumerator that iterates through the parameters. + /// + /// An enumerator for the parameters. public IEnumerator> GetEnumerator() => _inner.GetEnumerator(); + /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } @@ -236,7 +276,16 @@ public class ScriptParameters : IReadOnlyDictionary /// public class ScriptParameterException : Exception { + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The error message that explains the reason for the exception. public ScriptParameterException(string message) : base(message) { } + /// + /// Initializes a new instance of the class with a specified error message and inner exception. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception. public ScriptParameterException(string message, Exception innerException) : base(message, innerException) { } } diff --git a/src/ScadaLink.Commons/Types/Scripts/AlarmContext.cs b/src/ScadaLink.Commons/Types/Scripts/AlarmContext.cs index 6fd505ac..b3d1c65c 100644 --- a/src/ScadaLink.Commons/Types/Scripts/AlarmContext.cs +++ b/src/ScadaLink.Commons/Types/Scripts/AlarmContext.cs @@ -11,8 +11,11 @@ namespace ScadaLink.Commons.Types.Scripts; /// public sealed class AlarmContext { + /// Name of the alarm that fired. public string Name { get; init; } = string.Empty; + /// Severity level of the alarm; for binary trigger types. public AlarmLevel Level { get; init; } = AlarmLevel.None; + /// Operator-assigned priority of the alarm. public int Priority { get; init; } /// diff --git a/src/ScadaLink.Commons/Types/Scripts/ScriptScope.cs b/src/ScadaLink.Commons/Types/Scripts/ScriptScope.cs index fd2a930a..6ad3e827 100644 --- a/src/ScadaLink.Commons/Types/Scripts/ScriptScope.cs +++ b/src/ScadaLink.Commons/Types/Scripts/ScriptScope.cs @@ -13,5 +13,6 @@ public sealed record ScriptScope(string SelfPath, string? ParentPath) /// Scope for a script directly on the root template (no compositions). public static readonly ScriptScope Root = new("", null); + /// Gets a value indicating whether this script has a parent composition path. public bool HasParent => ParentPath != null; } diff --git a/src/ScadaLink.Commons/Types/StaleTagMonitor.cs b/src/ScadaLink.Commons/Types/StaleTagMonitor.cs index d11ed1b0..4c335c0f 100644 --- a/src/ScadaLink.Commons/Types/StaleTagMonitor.cs +++ b/src/ScadaLink.Commons/Types/StaleTagMonitor.cs @@ -33,6 +33,8 @@ public sealed class StaleTagMonitor : IDisposable /// private long _generation; + /// Initializes a new that fires if no value is received within . + /// The maximum time with no received value before the event fires; must be positive. public StaleTagMonitor(TimeSpan maxSilence) { if (maxSilence <= TimeSpan.Zero) @@ -46,6 +48,7 @@ public sealed class StaleTagMonitor : IDisposable /// public event Action? Stale; + /// Gets the maximum silence interval after which the event fires. public TimeSpan MaxSilence => _maxSilence; /// @@ -105,6 +108,7 @@ public sealed class StaleTagMonitor : IDisposable } } + /// Stops monitoring and disposes the internal timer. public void Dispose() { Stop(); diff --git a/src/ScadaLink.Commons/Types/TrackedOperationId.cs b/src/ScadaLink.Commons/Types/TrackedOperationId.cs index 3a6bb224..87a5bfa7 100644 --- a/src/ScadaLink.Commons/Types/TrackedOperationId.cs +++ b/src/ScadaLink.Commons/Types/TrackedOperationId.cs @@ -29,6 +29,7 @@ public readonly record struct TrackedOperationId(Guid Value) /// is not a valid GUID — callers crossing untrusted boundaries should use /// instead. /// + /// GUID string to parse. public static TrackedOperationId Parse(string s) => new(Guid.Parse(s)); /// @@ -36,6 +37,8 @@ public readonly record struct TrackedOperationId(Guid Value) /// or non-GUID input; is default on /// failure. /// + /// GUID string to parse, or null. + /// Parsed value on success; default on failure. public static bool TryParse(string? s, out TrackedOperationId result) { if (Guid.TryParse(s, out var g)) diff --git a/src/ScadaLink.Commons/Types/Transport/BundleSession.cs b/src/ScadaLink.Commons/Types/Transport/BundleSession.cs index b495c0eb..defed650 100644 --- a/src/ScadaLink.Commons/Types/Transport/BundleSession.cs +++ b/src/ScadaLink.Commons/Types/Transport/BundleSession.cs @@ -2,10 +2,16 @@ namespace ScadaLink.Commons.Types.Transport; public sealed class BundleSession { + /// Unique identifier for this import session. public Guid SessionId { get; init; } + /// Parsed manifest from the uploaded bundle. public BundleManifest Manifest { get; init; } = null!; + /// Decrypted bundle content bytes; empty until the bundle is successfully unlocked. public byte[] DecryptedContent { get; init; } = Array.Empty(); + /// UTC timestamp after which this session is considered expired and must be re-uploaded. public DateTimeOffset ExpiresAt { get; init; } + /// Number of failed passphrase unlock attempts for this session. public int FailedUnlockAttempts { get; set; } + /// True when three or more unlock attempts have failed, locking further attempts. public bool Locked => FailedUnlockAttempts >= 3; } diff --git a/src/ScadaLink.Commons/Types/ValueFormatter.cs b/src/ScadaLink.Commons/Types/ValueFormatter.cs index 2f430c28..c6d426fa 100644 --- a/src/ScadaLink.Commons/Types/ValueFormatter.cs +++ b/src/ScadaLink.Commons/Types/ValueFormatter.cs @@ -13,6 +13,7 @@ public static class ValueFormatter /// Formats a value as a string. Returns the value's string representation for /// scalars and comma-separated elements for array/collection types. /// + /// The value to format; null returns an empty string. /// /// Formatting is culture-invariant: /// numbers and values render the same regardless of the diff --git a/src/ScadaLink.Commons/Validators/OpcUaEndpointConfigValidator.cs b/src/ScadaLink.Commons/Validators/OpcUaEndpointConfigValidator.cs index fc0e494f..06e5ed47 100644 --- a/src/ScadaLink.Commons/Validators/OpcUaEndpointConfigValidator.cs +++ b/src/ScadaLink.Commons/Validators/OpcUaEndpointConfigValidator.cs @@ -11,6 +11,11 @@ namespace ScadaLink.Commons.Validators; /// public static class OpcUaEndpointConfigValidator { + /// + /// Validates all fields of an , returning errors with optionally-prefixed field names. + /// + /// The OPC UA endpoint configuration to validate. + /// Optional prefix prepended to each field name in error entries (e.g., "Primary."). public static ValidationResult Validate(OpcUaEndpointConfig config, string fieldPrefix = "") { var errors = new List(); diff --git a/src/ScadaLink.Communication/Actors/CentralCommunicationActor.cs b/src/ScadaLink.Communication/Actors/CentralCommunicationActor.cs index 319a267b..c1bf2519 100644 --- a/src/ScadaLink.Communication/Actors/CentralCommunicationActor.cs +++ b/src/ScadaLink.Communication/Actors/CentralCommunicationActor.cs @@ -18,6 +18,11 @@ namespace ScadaLink.Communication.Actors; /// public interface ISiteClientFactory { + /// Creates a ClusterClient actor for the given site with the specified contact points. + /// The actor system in which to create the client. + /// The site identifier, used to name the actor. + /// The set of receptionist actor paths to use as initial contacts. + /// An actor reference for the new ClusterClient. IActorRef Create(ActorSystem system, string siteId, ImmutableHashSet contacts); } @@ -26,6 +31,7 @@ public interface ISiteClientFactory /// public class DefaultSiteClientFactory : ISiteClientFactory { + /// public IActorRef Create(ActorSystem system, string siteId, ImmutableHashSet contacts) { var settings = ClusterClientSettings.Create(system).WithInitialContacts(contacts); @@ -121,6 +127,9 @@ public class CentralCommunicationActor : ReceiveActor /// private const string HealthReportTopic = "site-health-replica"; + /// Initializes the and wires all message handlers. + /// DI service provider for scoped repository and aggregator access. + /// Factory used to create per-site ClusterClient actors. /// /// Optional override for the audit-ingest Ask timeout; defaults to /// (30 s). Exists only so tests can @@ -494,13 +503,7 @@ public class CentralCommunicationActor : ReceiveActor } } - /// - /// Coordinator supervision strategy (CLAUDE.md: "Resume for coordinator actors"). - /// CentralCommunicationActor is a long-lived coordinator that owns the per-site - /// ClusterClient map; a transient fault in a child (e.g. a ClusterClient child) - /// must Resume so the child's connection state is preserved rather than wiped by - /// a Restart. - /// + /// protected override SupervisorStrategy SupervisorStrategy() { return new OneForOneStrategy( @@ -513,6 +516,7 @@ public class CentralCommunicationActor : ReceiveActor })); } + /// protected override void PreStart() { _log.Info("CentralCommunicationActor started"); @@ -540,6 +544,7 @@ public class CentralCommunicationActor : ReceiveActor ActorRefs.NoSender); } + /// protected override void PostStop() { _log.Info("CentralCommunicationActor stopped. In-progress deployments treated as failed (WP-5)."); diff --git a/src/ScadaLink.Communication/Actors/DebugStreamBridgeActor.cs b/src/ScadaLink.Communication/Actors/DebugStreamBridgeActor.cs index 6c104e79..adc686bc 100644 --- a/src/ScadaLink.Communication/Actors/DebugStreamBridgeActor.cs +++ b/src/ScadaLink.Communication/Actors/DebugStreamBridgeActor.cs @@ -31,6 +31,7 @@ public class DebugStreamBridgeActor : ReceiveActor, IWithTimers private const int MaxRetries = 3; private const string ReconnectTimerKey = "grpc-reconnect"; private const string StabilityTimerKey = "grpc-stability"; + /// Delay between gRPC reconnection attempts. internal static TimeSpan ReconnectDelay { get; set; } = TimeSpan.FromSeconds(5); /// @@ -48,8 +49,21 @@ public class DebugStreamBridgeActor : ReceiveActor, IWithTimers private bool _stopped; private CancellationTokenSource? _grpcCts; + /// Timer scheduler for reconnect and stability window timers. public ITimerScheduler Timers { get; set; } = null!; + /// + /// Initializes the debug stream bridge actor and registers message handlers. + /// + /// Site identifier for targeting ClusterClient messages and logging. + /// Unique name of the instance whose debug stream is being bridged. + /// Correlation id for the debug session. + /// Actor used to forward ClusterClient messages to the site. + /// Callback invoked on each received debug event. + /// Callback invoked when the stream terminates. + /// Factory for creating gRPC streaming clients. + /// gRPC address of the site's node A. + /// gRPC address of the site's node B. public DebugStreamBridgeActor( string siteIdentifier, string instanceUniqueName, @@ -141,6 +155,7 @@ public class DebugStreamBridgeActor : ReceiveActor, IWithTimers }); } + /// protected override void PreStart() { _log.Info("Starting debug stream bridge for {0} on site {1}", _instanceUniqueName, _siteIdentifier); @@ -151,6 +166,7 @@ public class DebugStreamBridgeActor : ReceiveActor, IWithTimers _centralCommunicationActor.Tell(envelope, Self); } + /// protected override void PostStop() { _grpcCts?.Cancel(); diff --git a/src/ScadaLink.Communication/Actors/SiteCommunicationActor.cs b/src/ScadaLink.Communication/Actors/SiteCommunicationActor.cs index 42d7635e..feb88f6e 100644 --- a/src/ScadaLink.Communication/Actors/SiteCommunicationActor.cs +++ b/src/ScadaLink.Communication/Actors/SiteCommunicationActor.cs @@ -47,8 +47,13 @@ public class SiteCommunicationActor : ReceiveActor, IWithTimers private IActorRef? _integrationHandler; private IActorRef? _artifactHandler; + /// Akka timer scheduler injected by the framework via . public ITimerScheduler Timers { get; set; } = null!; + /// Initializes the actor, wires all message pattern handlers, and schedules the periodic heartbeat. + /// The site identifier included in outbound messages. + /// Communication options including heartbeat interval and transport settings. + /// Local reference to the Deployment Manager singleton proxy. public SiteCommunicationActor( string siteId, CommunicationOptions options, @@ -302,12 +307,7 @@ public class SiteCommunicationActor : ReceiveActor, IWithTimers } - /// - /// Coordinator supervision strategy (CLAUDE.md: "Resume for coordinator actors"). - /// SiteCommunicationActor is a long-lived coordinator routing all message - /// patterns to local handlers; a transient fault in a child must Resume so the - /// child's in-memory state is preserved rather than discarded by a Restart. - /// + /// protected override SupervisorStrategy SupervisorStrategy() { return new OneForOneStrategy( @@ -320,6 +320,7 @@ public class SiteCommunicationActor : ReceiveActor, IWithTimers })); } + /// protected override void PreStart() { _log.Info("SiteCommunicationActor started for site {0}", _siteId); diff --git a/src/ScadaLink.Communication/Actors/StreamRelayActor.cs b/src/ScadaLink.Communication/Actors/StreamRelayActor.cs index a26d3fa3..e0040bbd 100644 --- a/src/ScadaLink.Communication/Actors/StreamRelayActor.cs +++ b/src/ScadaLink.Communication/Actors/StreamRelayActor.cs @@ -21,6 +21,11 @@ public class StreamRelayActor : ReceiveActor private readonly string _correlationId; private readonly ChannelWriter _channelWriter; + /// + /// Initializes a new for the given gRPC stream correlation. + /// + /// Correlation id stamped on every relayed . + /// Channel writer to which converted events are written. public StreamRelayActor(string correlationId, ChannelWriter channelWriter) { _correlationId = correlationId; diff --git a/src/ScadaLink.Communication/CommunicationService.cs b/src/ScadaLink.Communication/CommunicationService.cs index 4ef51697..d9030a13 100644 --- a/src/ScadaLink.Communication/CommunicationService.cs +++ b/src/ScadaLink.Communication/CommunicationService.cs @@ -28,6 +28,11 @@ public class CommunicationService private IActorRef? _notificationOutboxProxy; private IActorRef? _siteCallAuditProxy; + /// + /// Initializes a new instance of the CommunicationService. + /// + /// Communication service configuration options. + /// Logger instance. public CommunicationService( IOptions options, ILogger logger) @@ -39,6 +44,7 @@ public class CommunicationService /// /// Sets the central communication actor reference. Called during actor system startup. /// + /// The central communication actor reference. public void SetCommunicationActor(IActorRef centralCommunicationActor) { _centralCommunicationActor = centralCommunicationActor; @@ -49,6 +55,7 @@ public class CommunicationService /// system startup. The outbox actor is central-local, so outbox calls Ask this /// proxy directly (no SiteEnvelope routing). /// + /// The notification outbox proxy reference. public void SetNotificationOutbox(IActorRef notificationOutboxProxy) { _notificationOutboxProxy = notificationOutboxProxy; @@ -60,6 +67,7 @@ public class CommunicationService /// Calls read calls Ask this proxy directly (no SiteEnvelope routing), the /// same pattern as . /// + /// The Site Call Audit proxy reference. public void SetSiteCallAudit(IActorRef siteCallAuditProxy) { _siteCallAuditProxy = siteCallAuditProxy; @@ -104,6 +112,13 @@ public class CommunicationService // ── Pattern 1: Instance Deployment ── + /// + /// Sends a deployment command for an instance to a site. + /// + /// The target site identifier. + /// The deployment command. + /// Cancellation token. + /// The deployment status response. public async Task DeployInstanceAsync( string siteId, DeployInstanceCommand command, CancellationToken cancellationToken = default) { @@ -124,6 +139,10 @@ public class CommunicationService /// central buffering) if the site is unreachable, and the caller falls /// through to a normal deploy. /// + /// The target site identifier. + /// The deployment state query request. + /// Cancellation token. + /// The deployment state query response. public async Task QueryDeploymentStateAsync( string siteId, DeploymentStateQueryRequest request, CancellationToken cancellationToken = default) { @@ -138,6 +157,13 @@ public class CommunicationService // ── Pattern 2: Lifecycle ── + /// + /// Sends a disable command for an instance to a site. + /// + /// The target site identifier. + /// The disable command. + /// Cancellation token. + /// The instance lifecycle response. public async Task DisableInstanceAsync( string siteId, DisableInstanceCommand command, CancellationToken cancellationToken = default) { @@ -146,6 +172,13 @@ public class CommunicationService envelope, _options.LifecycleTimeout, cancellationToken); } + /// + /// Sends an enable command for an instance to a site. + /// + /// The target site identifier. + /// The enable command. + /// Cancellation token. + /// The instance lifecycle response. public async Task EnableInstanceAsync( string siteId, EnableInstanceCommand command, CancellationToken cancellationToken = default) { @@ -154,6 +187,13 @@ public class CommunicationService envelope, _options.LifecycleTimeout, cancellationToken); } + /// + /// Sends a delete command for an instance to a site. + /// + /// The target site identifier. + /// The delete command. + /// Cancellation token. + /// The instance lifecycle response. public async Task DeleteInstanceAsync( string siteId, DeleteInstanceCommand command, CancellationToken cancellationToken = default) { @@ -164,6 +204,13 @@ public class CommunicationService // ── Pattern 3: Artifact Deployment ── + /// + /// Sends a system-wide artifact deployment command to a site. + /// + /// The target site identifier. + /// The artifact deployment command. + /// Cancellation token. + /// The artifact deployment response. public async Task DeployArtifactsAsync( string siteId, DeployArtifactsCommand command, CancellationToken cancellationToken = default) { @@ -174,6 +221,13 @@ public class CommunicationService // ── Pattern 4: Integration Routing ── + /// + /// Routes an integration call to a site. + /// + /// The target site identifier. + /// The integration call request. + /// Cancellation token. + /// The integration call response. public async Task RouteIntegrationCallAsync( string siteId, IntegrationCallRequest request, CancellationToken cancellationToken = default) { @@ -184,6 +238,13 @@ public class CommunicationService // ── Pattern 5: Debug View ── + /// + /// Subscribes to debug view events from a site. + /// + /// The target site identifier. + /// The debug view subscription request. + /// Cancellation token. + /// A snapshot of the debug view. public async Task SubscribeDebugViewAsync( string siteId, SubscribeDebugViewRequest request, CancellationToken cancellationToken = default) { @@ -192,6 +253,11 @@ public class CommunicationService envelope, _options.DebugViewTimeout, cancellationToken); } + /// + /// Unsubscribes from debug view events for a site. + /// + /// The target site identifier. + /// The debug view unsubscription request. public void UnsubscribeDebugView(string siteId, UnsubscribeDebugViewRequest request) { // Tell (fire-and-forget) — no response expected @@ -200,6 +266,13 @@ public class CommunicationService // ── Pattern 6a: Debug Snapshot (one-shot, request/response) ── + /// + /// Requests a snapshot of the debug view from a site. + /// + /// The target site identifier. + /// The debug snapshot request. + /// Cancellation token. + /// A snapshot of the debug view. public async Task RequestDebugSnapshotAsync( string siteId, DebugSnapshotRequest request, CancellationToken cancellationToken = default) { @@ -213,6 +286,13 @@ public class CommunicationService // ── Pattern 7: Remote Queries ── + /// + /// Queries event logs from a site. + /// + /// The target site identifier. + /// The event log query request. + /// Cancellation token. + /// The event log query response. public async Task QueryEventLogsAsync( string siteId, EventLogQueryRequest request, CancellationToken cancellationToken = default) { @@ -221,6 +301,13 @@ public class CommunicationService envelope, _options.QueryTimeout, cancellationToken); } + /// + /// Queries parked messages from a site. + /// + /// The target site identifier. + /// The parked message query request. + /// Cancellation token. + /// The parked message query response. public async Task QueryParkedMessagesAsync( string siteId, ParkedMessageQueryRequest request, CancellationToken cancellationToken = default) { @@ -229,6 +316,13 @@ public class CommunicationService envelope, _options.QueryTimeout, cancellationToken); } + /// + /// Retries a parked message at a site. + /// + /// The target site identifier. + /// The parked message retry request. + /// Cancellation token. + /// The parked message retry response. public async Task RetryParkedMessageAsync( string siteId, ParkedMessageRetryRequest request, CancellationToken cancellationToken = default) { @@ -237,6 +331,13 @@ public class CommunicationService envelope, _options.QueryTimeout, cancellationToken); } + /// + /// Discards a parked message at a site. + /// + /// The target site identifier. + /// The parked message discard request. + /// Cancellation token. + /// The parked message discard response. public async Task DiscardParkedMessageAsync( string siteId, ParkedMessageDiscardRequest request, CancellationToken cancellationToken = default) { @@ -250,6 +351,13 @@ public class CommunicationService // ── Inbound API Cross-Site Routing (WP-4) ── + /// + /// Routes an inbound API call to a site. + /// + /// The target site identifier. + /// The call route request. + /// Cancellation token. + /// The call route response. public async Task RouteToCallAsync( string siteId, RouteToCallRequest request, CancellationToken cancellationToken = default) { @@ -258,6 +366,13 @@ public class CommunicationService envelope, _options.IntegrationTimeout, cancellationToken); } + /// + /// Routes an inbound API get-attributes request to a site. + /// + /// The target site identifier. + /// The get-attributes route request. + /// Cancellation token. + /// The get-attributes route response. public async Task RouteToGetAttributesAsync( string siteId, RouteToGetAttributesRequest request, CancellationToken cancellationToken = default) { @@ -266,6 +381,13 @@ public class CommunicationService envelope, _options.IntegrationTimeout, cancellationToken); } + /// + /// Routes an inbound API set-attributes request to a site. + /// + /// The target site identifier. + /// The set-attributes route request. + /// Cancellation token. + /// The set-attributes route response. public async Task RouteToSetAttributesAsync( string siteId, RouteToSetAttributesRequest request, CancellationToken cancellationToken = default) { @@ -276,6 +398,12 @@ public class CommunicationService // ── Notification Outbox (central-local actor — Asked directly, no SiteEnvelope) ── + /// + /// Queries the notification outbox. + /// + /// The notification outbox query request. + /// Cancellation token. + /// The notification outbox query response. public async Task QueryNotificationOutboxAsync( NotificationOutboxQueryRequest request, CancellationToken cancellationToken = default) { @@ -283,6 +411,12 @@ public class CommunicationService request, _options.QueryTimeout, cancellationToken); } + /// + /// Retries a notification from the outbox. + /// + /// The retry notification request. + /// Cancellation token. + /// The retry notification response. public async Task RetryNotificationAsync( RetryNotificationRequest request, CancellationToken cancellationToken = default) { @@ -290,6 +424,12 @@ public class CommunicationService request, _options.QueryTimeout, cancellationToken); } + /// + /// Discards a notification from the outbox. + /// + /// The discard notification request. + /// Cancellation token. + /// The discard notification response. public async Task DiscardNotificationAsync( DiscardNotificationRequest request, CancellationToken cancellationToken = default) { @@ -297,6 +437,12 @@ public class CommunicationService request, _options.QueryTimeout, cancellationToken); } + /// + /// Gets details about a specific notification. + /// + /// The notification detail request. + /// Cancellation token. + /// The notification detail response. public async Task GetNotificationDetailAsync( NotificationDetailRequest request, CancellationToken cancellationToken = default) { @@ -304,6 +450,12 @@ public class CommunicationService request, _options.QueryTimeout, cancellationToken); } + /// + /// Gets KPI metrics for the notification outbox. + /// + /// The notification KPI request. + /// Cancellation token. + /// The notification KPI response. public async Task GetNotificationKpisAsync( NotificationKpiRequest request, CancellationToken cancellationToken = default) { @@ -311,6 +463,12 @@ public class CommunicationService request, _options.QueryTimeout, cancellationToken); } + /// + /// Gets per-site KPI metrics for the notification outbox. + /// + /// The per-site notification KPI request. + /// Cancellation token. + /// The per-site notification KPI response. public async Task GetPerSiteNotificationKpisAsync( PerSiteNotificationKpiRequest request, CancellationToken cancellationToken = default) { @@ -320,6 +478,12 @@ public class CommunicationService // ── Site Call Audit (central-local actor — Asked directly, no SiteEnvelope) ── + /// + /// Queries site call audit records. + /// + /// The site call query request. + /// Cancellation token. + /// The site call query response. public async Task QuerySiteCallsAsync( SiteCallQueryRequest request, CancellationToken cancellationToken = default) { @@ -327,6 +491,12 @@ public class CommunicationService request, _options.QueryTimeout, cancellationToken); } + /// + /// Gets details about a specific site call. + /// + /// The site call detail request. + /// Cancellation token. + /// The site call detail response. public async Task GetSiteCallDetailAsync( SiteCallDetailRequest request, CancellationToken cancellationToken = default) { @@ -334,6 +504,12 @@ public class CommunicationService request, _options.QueryTimeout, cancellationToken); } + /// + /// Gets KPI metrics for site calls. + /// + /// The site call KPI request. + /// Cancellation token. + /// The site call KPI response. public async Task GetSiteCallKpisAsync( SiteCallKpiRequest request, CancellationToken cancellationToken = default) { @@ -341,6 +517,12 @@ public class CommunicationService request, _options.QueryTimeout, cancellationToken); } + /// + /// Gets per-site KPI metrics for site calls. + /// + /// The per-site site call KPI request. + /// Cancellation token. + /// The per-site site call KPI response. public async Task GetPerSiteSiteCallKpisAsync( PerSiteSiteCallKpiRequest request, CancellationToken cancellationToken = default) { @@ -364,6 +546,9 @@ public class CommunicationService /// first, that outcome would be lost to a generic Ask-timeout exception. /// /// + /// The retry site call request. + /// Cancellation token. + /// The retry site call response. public async Task RetrySiteCallAsync( RetrySiteCallRequest request, CancellationToken cancellationToken = default) { @@ -376,6 +561,9 @@ public class CommunicationService /// owning site. See for the routing and /// source-of-truth rationale. /// + /// The discard site call request. + /// Cancellation token. + /// The discard site call response. public async Task DiscardSiteCallAsync( DiscardSiteCallRequest request, CancellationToken cancellationToken = default) { diff --git a/src/ScadaLink.Communication/DebugStreamService.cs b/src/ScadaLink.Communication/DebugStreamService.cs index 507e93ec..79c7f3fd 100644 --- a/src/ScadaLink.Communication/DebugStreamService.cs +++ b/src/ScadaLink.Communication/DebugStreamService.cs @@ -23,6 +23,13 @@ public class DebugStreamService private readonly ConcurrentDictionary _sessions = new(); private ActorSystem? _actorSystem; + /// + /// Initializes a new instance of the class. + /// + /// The communication service. + /// The service provider for dependency resolution. + /// The gRPC client factory for creating site stream clients. + /// The logger instance. public DebugStreamService( CommunicationService communicationService, IServiceProvider serviceProvider, @@ -38,6 +45,7 @@ public class DebugStreamService /// /// Sets the ActorSystem reference. Called during actor system startup (from AkkaHostedService). /// + /// The actor system to use for creating bridge actors. public void SetActorSystem(ActorSystem actorSystem) { _actorSystem = actorSystem; @@ -48,6 +56,11 @@ public class DebugStreamService /// Ongoing events are delivered via the onEvent callback. /// The onTerminated callback fires if the stream is killed (site disconnect, timeout). /// + /// The instance ID to stream debug information for. + /// Callback invoked for each event received from the stream. + /// Callback invoked when the stream terminates. + /// A cancellation token that can be used to cancel the operation. + /// A debug stream session with the initial snapshot. public async Task StartStreamAsync( int instanceId, Action onEvent, @@ -162,6 +175,7 @@ public class DebugStreamService /// /// Stops an active debug stream session. /// + /// The session ID of the debug stream to stop. public void StopStream(string sessionId) { if (_sessions.TryRemove(sessionId, out var bridgeActor)) diff --git a/src/ScadaLink.Communication/Grpc/AuditEventDtoMapper.cs b/src/ScadaLink.Communication/Grpc/AuditEventDtoMapper.cs index a2fe2ee3..fc14aec7 100644 --- a/src/ScadaLink.Communication/Grpc/AuditEventDtoMapper.cs +++ b/src/ScadaLink.Communication/Grpc/AuditEventDtoMapper.cs @@ -36,6 +36,7 @@ public static class AuditEventDtoMapper /// Projects an into its wire-format DTO. Null reference /// fields collapse to empty strings; null integer fields leave the wrapper unset. /// + /// The audit event to project to wire format. public static AuditEventDto ToDto(AuditEvent evt) { ArgumentNullException.ThrowIfNull(evt); @@ -83,6 +84,7 @@ public static class AuditEventDtoMapper /// and /// are intentionally left null — the central ingest actor sets the latter. /// + /// The wire-format DTO to reconstruct into an . public static AuditEvent FromDto(AuditEventDto dto) { ArgumentNullException.ThrowIfNull(dto); diff --git a/src/ScadaLink.Communication/Grpc/ISiteStreamSubscriber.cs b/src/ScadaLink.Communication/Grpc/ISiteStreamSubscriber.cs index 438131c4..868c98e9 100644 --- a/src/ScadaLink.Communication/Grpc/ISiteStreamSubscriber.cs +++ b/src/ScadaLink.Communication/Grpc/ISiteStreamSubscriber.cs @@ -12,11 +12,14 @@ public interface ISiteStreamSubscriber /// /// Subscribes an actor to receive filtered stream events for a specific instance. /// + /// The unique name of the instance whose events to subscribe to. + /// The actor reference that will receive stream event messages. /// A subscription ID that can be used for unsubscription. string Subscribe(string instanceName, IActorRef subscriber); /// /// Removes all subscriptions for the given actor. /// + /// The actor reference whose subscriptions should be removed. void RemoveSubscriber(IActorRef subscriber); } diff --git a/src/ScadaLink.Communication/Grpc/SiteCallDtoMapper.cs b/src/ScadaLink.Communication/Grpc/SiteCallDtoMapper.cs index c3e7ab34..4f29b477 100644 --- a/src/ScadaLink.Communication/Grpc/SiteCallDtoMapper.cs +++ b/src/ScadaLink.Communication/Grpc/SiteCallDtoMapper.cs @@ -45,6 +45,7 @@ public static class SiteCallDtoMapper /// inside the dual-write transaction so the AuditLog and SiteCalls rows /// share one instant. The value sent on the wire is informational only. /// + /// The wire-format site call DTO to map. public static SiteCall FromDto(SiteCallOperationalDto dto) { ArgumentNullException.ThrowIfNull(dto); diff --git a/src/ScadaLink.Communication/Grpc/SiteStreamGrpcClient.cs b/src/ScadaLink.Communication/Grpc/SiteStreamGrpcClient.cs index 08f62304..c0a51ea0 100644 --- a/src/ScadaLink.Communication/Grpc/SiteStreamGrpcClient.cs +++ b/src/ScadaLink.Communication/Grpc/SiteStreamGrpcClient.cs @@ -40,6 +40,11 @@ public class SiteStreamGrpcClient : IAsyncDisposable, IDisposable /// internal TimeSpan KeepAlivePingTimeout { get; } + /// + /// Creates a client with default communication options. + /// + /// The gRPC endpoint address for the site. + /// Logger for diagnostics and errors. public SiteStreamGrpcClient(string endpoint, ILogger logger) : this(endpoint, logger, new CommunicationOptions()) { @@ -50,6 +55,9 @@ public class SiteStreamGrpcClient : IAsyncDisposable, IDisposable /// rather than hard-coded, satisfying the design doc's "gRPC Connection Keepalive" /// section which states these values are configurable. /// + /// The gRPC endpoint address for the site. + /// Logger for diagnostics and errors. + /// Communication options including keepalive settings. public SiteStreamGrpcClient(string endpoint, ILogger logger, CommunicationOptions options) { Endpoint = endpoint; @@ -81,6 +89,7 @@ public class SiteStreamGrpcClient : IAsyncDisposable, IDisposable /// opening a real gRPC channel, so endpoint-aware factory behaviour can be /// exercised by test doubles. /// + /// The gRPC endpoint address for the site. protected SiteStreamGrpcClient(string endpoint) { Endpoint = endpoint; @@ -95,6 +104,8 @@ public class SiteStreamGrpcClient : IAsyncDisposable, IDisposable /// /// Registers a CancellationTokenSource for a correlation ID. Test-only. /// + /// Unique identifier for the subscription. + /// CancellationTokenSource for managing the subscription lifecycle. internal void AddSubscriptionForTesting(string correlationId, CancellationTokenSource cts) { _subscriptions[correlationId] = cts; @@ -106,6 +117,8 @@ public class SiteStreamGrpcClient : IAsyncDisposable, IDisposable /// calls briefly share an ID), the prior CTS is /// cancelled and disposed so it cannot leak. Internal for testability. /// + /// Unique identifier for the subscription. + /// CancellationTokenSource for managing the subscription lifecycle. internal void RegisterSubscription(string correlationId, CancellationTokenSource cts) { if (_subscriptions.TryGetValue(correlationId, out var prior) && !ReferenceEquals(prior, cts)) @@ -121,6 +134,8 @@ public class SiteStreamGrpcClient : IAsyncDisposable, IDisposable /// exactly the one supplied. A racing replacement stream may already own the slot, /// in which case this is a no-op. Internal for testability. /// + /// Unique identifier for the subscription. + /// CancellationTokenSource to match before removing. internal void RemoveSubscription(string correlationId, CancellationTokenSource cts) { _subscriptions.TryRemove(new KeyValuePair(correlationId, cts)); @@ -132,6 +147,11 @@ public class SiteStreamGrpcClient : IAsyncDisposable, IDisposable /// The callback delivers domain events, and /// lets the caller handle reconnection. /// + /// Unique identifier for this subscription. + /// Unique name of the instance to subscribe to. + /// Callback invoked for each domain event received from the stream. + /// Callback invoked when the subscription encounters an error. + /// Cancellation token to stop the subscription. public virtual async Task SubscribeAsync( string correlationId, string instanceUniqueName, @@ -180,6 +200,7 @@ public class SiteStreamGrpcClient : IAsyncDisposable, IDisposable /// /// Cancels an active subscription by correlation ID. /// + /// Unique identifier of the subscription to cancel. public virtual void Unsubscribe(string correlationId) { if (_subscriptions.TryRemove(correlationId, out var cts)) @@ -193,6 +214,8 @@ public class SiteStreamGrpcClient : IAsyncDisposable, IDisposable /// Converts a proto SiteStreamEvent to the corresponding domain message. /// Internal for testability. /// + /// The protobuf site stream event to convert. + /// The converted domain event, or null if the event type is not recognized. internal static object? ConvertToDomainEvent(SiteStreamEvent evt) => evt.EventCase switch { SiteStreamEvent.EventOneofCase.AttributeChanged => new AttributeValueChanged( @@ -218,6 +241,8 @@ public class SiteStreamGrpcClient : IAsyncDisposable, IDisposable /// /// Maps proto Quality enum to domain string. Internal for testability. /// + /// The protobuf quality value to map. + /// The mapped quality as a string ("Good", "Uncertain", "Bad", or "Unknown"). internal static string MapQuality(Quality quality) => quality switch { Quality.Good => "Good", @@ -229,6 +254,8 @@ public class SiteStreamGrpcClient : IAsyncDisposable, IDisposable /// /// Maps proto AlarmStateEnum to domain AlarmState. Internal for testability. /// + /// The protobuf alarm state to map. + /// The mapped domain alarm state. internal static AlarmState MapAlarmState(AlarmStateEnum state) => state switch { AlarmStateEnum.AlarmStateNormal => AlarmState.Normal, @@ -239,6 +266,8 @@ public class SiteStreamGrpcClient : IAsyncDisposable, IDisposable /// /// Maps proto AlarmLevelEnum to domain AlarmLevel. Internal for testability. /// + /// The protobuf alarm level to map. + /// The mapped domain alarm level. internal static AlarmLevel MapAlarmLevel(AlarmLevelEnum level) => level switch { AlarmLevelEnum.AlarmLevelLow => AlarmLevel.Low, @@ -266,6 +295,9 @@ public class SiteStreamGrpcClient : IAsyncDisposable, IDisposable _channel?.Dispose(); } + /// + /// Asynchronously disposes of the gRPC client and all subscriptions. + /// public virtual ValueTask DisposeAsync() { ReleaseResources(); diff --git a/src/ScadaLink.Communication/Grpc/SiteStreamGrpcClientFactory.cs b/src/ScadaLink.Communication/Grpc/SiteStreamGrpcClientFactory.cs index 2de8277e..da8a5690 100644 --- a/src/ScadaLink.Communication/Grpc/SiteStreamGrpcClientFactory.cs +++ b/src/ScadaLink.Communication/Grpc/SiteStreamGrpcClientFactory.cs @@ -15,6 +15,10 @@ public class SiteStreamGrpcClientFactory : IAsyncDisposable, IDisposable private readonly ILoggerFactory _loggerFactory; private readonly CommunicationOptions _options; + /// + /// Test/default constructor — uses default . + /// + /// Logger factory passed to created clients. public SiteStreamGrpcClientFactory(ILoggerFactory loggerFactory) : this(loggerFactory, Options.Create(new CommunicationOptions())) { @@ -25,6 +29,8 @@ public class SiteStreamGrpcClientFactory : IAsyncDisposable, IDisposable /// so the configured gRPC keepalive settings /// are applied rather than hard-coded defaults. /// + /// Logger factory passed to created clients. + /// Communication options applied to each created client. public SiteStreamGrpcClientFactory(ILoggerFactory loggerFactory, IOptions options) { _loggerFactory = loggerFactory; @@ -40,6 +46,8 @@ public class SiteStreamGrpcClientFactory : IAsyncDisposable, IDisposable /// endpoint on a cache hit defeated debug-stream node failover and meant a /// corrected gRPC address never took effect without a central restart. /// + /// Unique site identifier used as the cache key. + /// gRPC endpoint the returned client must be bound to. public virtual SiteStreamGrpcClient GetOrCreate(string siteIdentifier, string grpcEndpoint) { // Fast path: a client is cached and already bound to the requested endpoint. @@ -73,6 +81,7 @@ public class SiteStreamGrpcClientFactory : IAsyncDisposable, IDisposable /// can substitute a tracking client while still exercising the factory's real /// caching and disposal machinery. /// + /// gRPC endpoint the new client will connect to. protected virtual SiteStreamGrpcClient CreateClient(string grpcEndpoint) { var logger = _loggerFactory.CreateLogger(); @@ -86,6 +95,7 @@ public class SiteStreamGrpcClientFactory : IAsyncDisposable, IDisposable /// path for full site *removal* — call it when a site record is deleted so its /// cached gRPC client does not linger for the life of the process. /// + /// Unique site identifier whose client should be removed. public async Task RemoveSiteAsync(string siteIdentifier) { if (_clients.TryRemove(siteIdentifier, out var client)) @@ -94,6 +104,9 @@ public class SiteStreamGrpcClientFactory : IAsyncDisposable, IDisposable } } + /// + /// Asynchronously disposes all cached clients and clears the cache. + /// public async ValueTask DisposeAsync() { foreach (var client in _clients.Values) diff --git a/src/ScadaLink.Communication/Grpc/SiteStreamGrpcServer.cs b/src/ScadaLink.Communication/Grpc/SiteStreamGrpcServer.cs index e75db338..60b90e65 100644 --- a/src/ScadaLink.Communication/Grpc/SiteStreamGrpcServer.cs +++ b/src/ScadaLink.Communication/Grpc/SiteStreamGrpcServer.cs @@ -47,6 +47,9 @@ public class SiteStreamGrpcServer : SiteStreamService.SiteStreamServiceBase /// Test-only constructor — kept internal so the DI container sees a /// single public constructor and is not faced with an ambiguous choice. /// + /// The stream subscriber for managing subscriptions. + /// The logger instance. + /// The maximum concurrent streams (default 100). internal SiteStreamGrpcServer( ISiteStreamSubscriber streamSubscriber, ILogger logger, @@ -61,6 +64,9 @@ public class SiteStreamGrpcServer : SiteStreamService.SiteStreamServiceBase /// concurrency limit and the 4-hour zombie-stream session timeout are honoured /// rather than hard-coded. /// + /// The stream subscriber for managing subscriptions. + /// The logger instance. + /// Communication options containing stream limits and timeouts. public SiteStreamGrpcServer( ISiteStreamSubscriber streamSubscriber, ILogger logger, @@ -89,6 +95,7 @@ public class SiteStreamGrpcServer : SiteStreamService.SiteStreamServiceBase /// The ActorSystem is set here rather than via the constructor so that /// the gRPC server can be created by DI before the actor system exists. /// + /// The initialized Akka actor system. public void SetReady(ActorSystem actorSystem) { _actorSystem = actorSystem; @@ -103,6 +110,7 @@ public class SiteStreamGrpcServer : SiteStreamService.SiteStreamServiceBase /// Outbox singleton proxy. Bundle E supplies the actor after the cluster /// singleton starts. /// + /// The audit log ingest actor proxy. public void SetAuditIngestActor(IActorRef proxy) { _auditIngestActor = proxy; @@ -117,6 +125,7 @@ public class SiteStreamGrpcServer : SiteStreamService.SiteStreamServiceBase /// because the queue and the gRPC server are both DI singletons brought up /// in independent orders on site startup. /// + /// The site audit queue for serving reconciliation pulls. public void SetSiteAuditQueue(ISiteAuditQueue queue) { _siteAuditQueue = queue; @@ -133,6 +142,7 @@ public class SiteStreamGrpcServer : SiteStreamService.SiteStreamServiceBase /// Effective per-stream session lifetime. Exposed for tests. internal TimeSpan MaxStreamLifetime => _maxStreamLifetime; + /// public override async Task SubscribeInstance( InstanceStreamRequest request, IServerStreamWriter responseStream, @@ -235,6 +245,9 @@ public class SiteStreamGrpcServer : SiteStreamService.SiteStreamServiceBase /// behaviour. /// /// + /// + /// The audit event batch to ingest. + /// The server call context. public override async Task IngestAuditEvents( AuditEventBatch request, ServerCallContext context) @@ -303,6 +316,9 @@ public class SiteStreamGrpcServer : SiteStreamService.SiteStreamServiceBase /// the actor proxy has not been set the RPC replies with an empty ack so /// sites treat the outcome as transient and retry, never a hard fault. /// + /// + /// The cached telemetry batch to ingest. + /// The server call context. public override async Task IngestCachedTelemetry( CachedTelemetryBatch request, ServerCallContext context) @@ -370,6 +386,9 @@ public class SiteStreamGrpcServer : SiteStreamService.SiteStreamServiceBase /// on its next cycle, which is the same self-healing semantics as the /// SetAuditIngestActor wiring race window. /// + /// + /// The pull request with time bounds and batch size. + /// The server call context. public override async Task PullAuditEvents( PullAuditEventsRequest request, ServerCallContext context) diff --git a/src/ScadaLink.Communication/ServiceCollectionExtensions.cs b/src/ScadaLink.Communication/ServiceCollectionExtensions.cs index 330a9e02..91a197bf 100644 --- a/src/ScadaLink.Communication/ServiceCollectionExtensions.cs +++ b/src/ScadaLink.Communication/ServiceCollectionExtensions.cs @@ -5,6 +5,8 @@ namespace ScadaLink.Communication; public static class ServiceCollectionExtensions { + /// Registers communication services including options, , gRPC client factory, and debug stream. + /// The DI service collection to register services into. public static IServiceCollection AddCommunication(this IServiceCollection services) { services.AddOptions() @@ -17,6 +19,8 @@ public static class ServiceCollectionExtensions return services; } + /// Hook for registering additional DI services needed by communication actors; actor creation itself happens inside AkkaHostedService. + /// The DI service collection to register services into. public static IServiceCollection AddCommunicationActors(this IServiceCollection services) { // Actor registration happens in AkkaHostedService.RegisterCentralActors/RegisterSiteActors. diff --git a/src/ScadaLink.ConfigurationDatabase/Configurations/AuditConfiguration.cs b/src/ScadaLink.ConfigurationDatabase/Configurations/AuditConfiguration.cs index b6d0d34d..afd82c0a 100644 --- a/src/ScadaLink.ConfigurationDatabase/Configurations/AuditConfiguration.cs +++ b/src/ScadaLink.ConfigurationDatabase/Configurations/AuditConfiguration.cs @@ -6,6 +6,10 @@ namespace ScadaLink.ConfigurationDatabase.Configurations; public class AuditLogEntryConfiguration : IEntityTypeConfiguration { + /// + /// Configures the EF Core entity mapping for . + /// + /// The entity type builder for . public void Configure(EntityTypeBuilder builder) { builder.HasKey(a => a.Id); diff --git a/src/ScadaLink.ConfigurationDatabase/Configurations/AuditLogEntityTypeConfiguration.cs b/src/ScadaLink.ConfigurationDatabase/Configurations/AuditLogEntityTypeConfiguration.cs index e092b657..03df819b 100644 --- a/src/ScadaLink.ConfigurationDatabase/Configurations/AuditLogEntityTypeConfiguration.cs +++ b/src/ScadaLink.ConfigurationDatabase/Configurations/AuditLogEntityTypeConfiguration.cs @@ -11,6 +11,8 @@ namespace ScadaLink.ConfigurationDatabase.Configurations; /// public class AuditLogEntityTypeConfiguration : IEntityTypeConfiguration { + /// Applies the EF Core type configuration for to the model builder. + /// The entity type builder to configure. public void Configure(EntityTypeBuilder builder) { builder.ToTable("AuditLog"); diff --git a/src/ScadaLink.ConfigurationDatabase/Configurations/DeploymentConfiguration.cs b/src/ScadaLink.ConfigurationDatabase/Configurations/DeploymentConfiguration.cs index 4441be8e..a2ed329b 100644 --- a/src/ScadaLink.ConfigurationDatabase/Configurations/DeploymentConfiguration.cs +++ b/src/ScadaLink.ConfigurationDatabase/Configurations/DeploymentConfiguration.cs @@ -7,6 +7,8 @@ namespace ScadaLink.ConfigurationDatabase.Configurations; public class DeploymentRecordConfiguration : IEntityTypeConfiguration { + /// Configures the EF Core mapping for . + /// The entity type builder. public void Configure(EntityTypeBuilder builder) { builder.HasKey(d => d.Id); @@ -43,6 +45,8 @@ public class DeploymentRecordConfiguration : IEntityTypeConfiguration { + /// Configures the EF Core mapping for . + /// The entity type builder. public void Configure(EntityTypeBuilder builder) { builder.HasKey(s => s.Id); @@ -70,6 +74,8 @@ public class DeployedConfigSnapshotConfiguration : IEntityTypeConfiguration { + /// Configures the EF Core mapping for . + /// The entity type builder. public void Configure(EntityTypeBuilder builder) { builder.HasKey(d => d.Id); diff --git a/src/ScadaLink.ConfigurationDatabase/Configurations/ExternalSystemConfiguration.cs b/src/ScadaLink.ConfigurationDatabase/Configurations/ExternalSystemConfiguration.cs index d50c4e3d..7ddbb815 100644 --- a/src/ScadaLink.ConfigurationDatabase/Configurations/ExternalSystemConfiguration.cs +++ b/src/ScadaLink.ConfigurationDatabase/Configurations/ExternalSystemConfiguration.cs @@ -6,6 +6,8 @@ namespace ScadaLink.ConfigurationDatabase.Configurations; public class ExternalSystemDefinitionConfiguration : IEntityTypeConfiguration { + /// Applies the EF Core entity type configuration for . + /// The entity type builder to configure. public void Configure(EntityTypeBuilder builder) { builder.HasKey(e => e.Id); @@ -38,6 +40,8 @@ public class ExternalSystemDefinitionConfiguration : IEntityTypeConfiguration { + /// Applies the EF Core entity type configuration for . + /// The entity type builder to configure. public void Configure(EntityTypeBuilder builder) { builder.HasKey(m => m.Id); @@ -66,6 +70,8 @@ public class ExternalSystemMethodConfiguration : IEntityTypeConfiguration { + /// Applies the EF Core entity type configuration for . + /// The entity type builder to configure. public void Configure(EntityTypeBuilder builder) { builder.HasKey(d => d.Id); diff --git a/src/ScadaLink.ConfigurationDatabase/Configurations/InboundApiConfiguration.cs b/src/ScadaLink.ConfigurationDatabase/Configurations/InboundApiConfiguration.cs index 3e0145aa..5c6e22e1 100644 --- a/src/ScadaLink.ConfigurationDatabase/Configurations/InboundApiConfiguration.cs +++ b/src/ScadaLink.ConfigurationDatabase/Configurations/InboundApiConfiguration.cs @@ -6,6 +6,8 @@ namespace ScadaLink.ConfigurationDatabase.Configurations; public class ApiKeyConfiguration : IEntityTypeConfiguration { + /// Configures the EF Core mapping for the entity. + /// Entity type builder used to apply the configuration. public void Configure(EntityTypeBuilder builder) { builder.HasKey(k => k.Id); @@ -28,6 +30,8 @@ public class ApiKeyConfiguration : IEntityTypeConfiguration public class ApiMethodConfiguration : IEntityTypeConfiguration { + /// Configures the EF Core mapping for the entity. + /// Entity type builder used to apply the configuration. public void Configure(EntityTypeBuilder builder) { builder.HasKey(m => m.Id); diff --git a/src/ScadaLink.ConfigurationDatabase/Configurations/InstanceConfiguration.cs b/src/ScadaLink.ConfigurationDatabase/Configurations/InstanceConfiguration.cs index 1860f511..2cba9ad9 100644 --- a/src/ScadaLink.ConfigurationDatabase/Configurations/InstanceConfiguration.cs +++ b/src/ScadaLink.ConfigurationDatabase/Configurations/InstanceConfiguration.cs @@ -8,6 +8,8 @@ namespace ScadaLink.ConfigurationDatabase.Configurations; public class InstanceConfiguration : IEntityTypeConfiguration { + /// Configures the EF Core mapping for . + /// The entity type builder. public void Configure(EntityTypeBuilder builder) { builder.HasKey(i => i.Id); @@ -57,6 +59,8 @@ public class InstanceConfiguration : IEntityTypeConfiguration public class InstanceAttributeOverrideConfiguration : IEntityTypeConfiguration { + /// Configures the EF Core mapping for . + /// The entity type builder. public void Configure(EntityTypeBuilder builder) { builder.HasKey(o => o.Id); @@ -74,6 +78,8 @@ public class InstanceAttributeOverrideConfiguration : IEntityTypeConfiguration { + /// Configures the EF Core mapping for . + /// The entity type builder. public void Configure(EntityTypeBuilder builder) { builder.HasKey(o => o.Id); @@ -91,6 +97,8 @@ public class InstanceAlarmOverrideConfiguration : IEntityTypeConfiguration { + /// Configures the EF Core mapping for . + /// The entity type builder. public void Configure(EntityTypeBuilder builder) { builder.HasKey(b => b.Id); @@ -110,6 +118,8 @@ public class InstanceConnectionBindingConfiguration : IEntityTypeConfiguration { + /// Configures the EF Core mapping for . + /// The entity type builder. public void Configure(EntityTypeBuilder builder) { builder.HasKey(a => a.Id); diff --git a/src/ScadaLink.ConfigurationDatabase/Configurations/NotificationConfiguration.cs b/src/ScadaLink.ConfigurationDatabase/Configurations/NotificationConfiguration.cs index cd85e4ee..8cda69b1 100644 --- a/src/ScadaLink.ConfigurationDatabase/Configurations/NotificationConfiguration.cs +++ b/src/ScadaLink.ConfigurationDatabase/Configurations/NotificationConfiguration.cs @@ -6,6 +6,8 @@ namespace ScadaLink.ConfigurationDatabase.Configurations; public class NotificationListConfiguration : IEntityTypeConfiguration { + /// Configures the EF Core mapping for . + /// The entity type builder. public void Configure(EntityTypeBuilder builder) { builder.HasKey(n => n.Id); @@ -30,6 +32,8 @@ public class NotificationListConfiguration : IEntityTypeConfiguration { + /// Configures the EF Core mapping for . + /// The entity type builder. public void Configure(EntityTypeBuilder builder) { builder.HasKey(r => r.Id); @@ -46,6 +50,8 @@ public class NotificationRecipientConfiguration : IEntityTypeConfiguration { + /// Configures the EF Core mapping for . + /// The entity type builder. public void Configure(EntityTypeBuilder builder) { builder.HasKey(s => s.Id); diff --git a/src/ScadaLink.ConfigurationDatabase/Configurations/NotificationOutboxConfiguration.cs b/src/ScadaLink.ConfigurationDatabase/Configurations/NotificationOutboxConfiguration.cs index 6c2c693f..d6c3b2ff 100644 --- a/src/ScadaLink.ConfigurationDatabase/Configurations/NotificationOutboxConfiguration.cs +++ b/src/ScadaLink.ConfigurationDatabase/Configurations/NotificationOutboxConfiguration.cs @@ -11,6 +11,8 @@ namespace ScadaLink.ConfigurationDatabase.Configurations; /// public class NotificationOutboxConfiguration : IEntityTypeConfiguration { + /// Configures the EF Core entity type mapping for . + /// The entity type builder to configure. public void Configure(EntityTypeBuilder builder) { builder.HasKey(n => n.NotificationId); diff --git a/src/ScadaLink.ConfigurationDatabase/Configurations/ScriptConfiguration.cs b/src/ScadaLink.ConfigurationDatabase/Configurations/ScriptConfiguration.cs index aefb6fa9..058080e1 100644 --- a/src/ScadaLink.ConfigurationDatabase/Configurations/ScriptConfiguration.cs +++ b/src/ScadaLink.ConfigurationDatabase/Configurations/ScriptConfiguration.cs @@ -6,6 +6,8 @@ namespace ScadaLink.ConfigurationDatabase.Configurations; public class SharedScriptConfiguration : IEntityTypeConfiguration { + /// Configures the EF Core mapping for the entity. + /// Entity type builder used to apply the configuration. public void Configure(EntityTypeBuilder builder) { builder.HasKey(s => s.Id); diff --git a/src/ScadaLink.ConfigurationDatabase/Configurations/SecurityConfiguration.cs b/src/ScadaLink.ConfigurationDatabase/Configurations/SecurityConfiguration.cs index bc4e9683..ef890102 100644 --- a/src/ScadaLink.ConfigurationDatabase/Configurations/SecurityConfiguration.cs +++ b/src/ScadaLink.ConfigurationDatabase/Configurations/SecurityConfiguration.cs @@ -7,6 +7,10 @@ namespace ScadaLink.ConfigurationDatabase.Configurations; public class LdapGroupMappingConfiguration : IEntityTypeConfiguration { + /// + /// Configures the EF Core entity type mapping for . + /// + /// The entity type builder to configure. public void Configure(EntityTypeBuilder builder) { builder.HasKey(m => m.Id); @@ -32,6 +36,10 @@ public class LdapGroupMappingConfiguration : IEntityTypeConfiguration { + /// + /// Configures the EF Core entity type mapping for . + /// + /// The entity type builder to configure. public void Configure(EntityTypeBuilder builder) { builder.HasKey(r => r.Id); diff --git a/src/ScadaLink.ConfigurationDatabase/Configurations/SiteCallEntityTypeConfiguration.cs b/src/ScadaLink.ConfigurationDatabase/Configurations/SiteCallEntityTypeConfiguration.cs index a0203ab1..36bcd0bd 100644 --- a/src/ScadaLink.ConfigurationDatabase/Configurations/SiteCallEntityTypeConfiguration.cs +++ b/src/ScadaLink.ConfigurationDatabase/Configurations/SiteCallEntityTypeConfiguration.cs @@ -14,6 +14,10 @@ namespace ScadaLink.ConfigurationDatabase.Configurations; /// public class SiteCallEntityTypeConfiguration : IEntityTypeConfiguration { + /// + /// Configures the EF Core entity type mapping for . + /// + /// The entity type builder to configure. public void Configure(EntityTypeBuilder builder) { builder.ToTable("SiteCalls"); diff --git a/src/ScadaLink.ConfigurationDatabase/Configurations/SiteConfiguration.cs b/src/ScadaLink.ConfigurationDatabase/Configurations/SiteConfiguration.cs index 41d455f2..e6a9b20a 100644 --- a/src/ScadaLink.ConfigurationDatabase/Configurations/SiteConfiguration.cs +++ b/src/ScadaLink.ConfigurationDatabase/Configurations/SiteConfiguration.cs @@ -6,6 +6,8 @@ namespace ScadaLink.ConfigurationDatabase.Configurations; public class SiteConfiguration : IEntityTypeConfiguration { + /// Configures the EF Core mapping for . + /// The entity type builder. public void Configure(EntityTypeBuilder builder) { builder.HasKey(s => s.Id); @@ -33,6 +35,8 @@ public class SiteConfiguration : IEntityTypeConfiguration public class DataConnectionConfiguration : IEntityTypeConfiguration { + /// Configures the EF Core mapping for . + /// The entity type builder. public void Configure(EntityTypeBuilder builder) { builder.HasKey(d => d.Id); diff --git a/src/ScadaLink.ConfigurationDatabase/Configurations/TemplateConfiguration.cs b/src/ScadaLink.ConfigurationDatabase/Configurations/TemplateConfiguration.cs index a568d8f2..c0a8aea9 100644 --- a/src/ScadaLink.ConfigurationDatabase/Configurations/TemplateConfiguration.cs +++ b/src/ScadaLink.ConfigurationDatabase/Configurations/TemplateConfiguration.cs @@ -6,6 +6,8 @@ namespace ScadaLink.ConfigurationDatabase.Configurations; public class TemplateConfiguration : IEntityTypeConfiguration