docs: add XML doc comments across src + Sister Projects section in CLAUDE.md

Bulk CommentChecker pass: fills in <param>/<inheritdoc> 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.
This commit is contained in:
Joseph Doherty
2026-05-28 01:55:24 -04:00
parent 6731845473
commit 1eb6e972b0
381 changed files with 5788 additions and 532 deletions
@@ -58,6 +58,7 @@ public sealed class NotificationForwarder
/// Returns <c>true</c> when central accepts the notification; throws on a
/// non-accepted ack or an Ask timeout/failure so the engine retries.
/// </summary>
/// <param name="message">The buffered store-and-forward message to deliver to central.</param>
public async Task<bool> DeliverAsync(StoreAndForwardMessage message)
{
// An unreadable payload cannot be fixed by retrying — park it (return false),
@@ -148,6 +149,10 @@ public sealed class NotificationForwarder
/// </summary>
public sealed class NotificationForwardException : Exception
{
/// <summary>
/// Initializes a new exception with the specified message.
/// </summary>
/// <param name="message">Message describing the forward failure.</param>
public NotificationForwardException(string message) : base(message)
{
}
@@ -16,6 +16,11 @@ public class ParkedMessageHandlerActor : ReceiveActor
private readonly StoreAndForwardService _service;
private readonly string _siteId;
/// <summary>
/// Initializes the actor and registers message handlers for query, retry, and discard operations.
/// </summary>
/// <param name="service">The store-and-forward service used to execute parked-message operations.</param>
/// <param name="siteId">The site identifier this actor manages parked messages for.</param>
public ParkedMessageHandlerActor(StoreAndForwardService service, string siteId)
{
_service = service;
@@ -17,6 +17,9 @@ public class ReplicationService
private readonly ILogger<ReplicationService> _logger;
private Func<ReplicationOperation, Task>? _replicationHandler;
/// <summary>Initializes a new instance of <see cref="ReplicationService"/>.</summary>
/// <param name="options">Store-and-forward configuration options.</param>
/// <param name="logger">Logger instance.</param>
public ReplicationService(
StoreAndForwardOptions options,
ILogger<ReplicationService> logger)
@@ -29,6 +32,7 @@ public class ReplicationService
/// Sets the handler for forwarding replication operations to the standby node.
/// Typically wraps Akka Tell to the standby's replication actor.
/// </summary>
/// <param name="handler">The async delegate that forwards each replication operation to the standby.</param>
public void SetReplicationHandler(Func<ReplicationOperation, Task> handler)
{
_replicationHandler = handler;
@@ -37,6 +41,7 @@ public class ReplicationService
/// <summary>
/// WP-11: Replicates an enqueue operation to standby (fire-and-forget).
/// </summary>
/// <param name="message">The message that was enqueued on the active node.</param>
public void ReplicateEnqueue(StoreAndForwardMessage message)
{
if (!_options.ReplicationEnabled || _replicationHandler == null) return;
@@ -50,6 +55,7 @@ public class ReplicationService
/// <summary>
/// WP-11: Replicates a remove operation to standby (fire-and-forget).
/// </summary>
/// <param name="messageId">The identifier of the message to remove from the standby buffer.</param>
public void ReplicateRemove(string messageId)
{
if (!_options.ReplicationEnabled || _replicationHandler == null) return;
@@ -63,6 +69,7 @@ public class ReplicationService
/// <summary>
/// WP-11: Replicates a park operation to standby (fire-and-forget).
/// </summary>
/// <param name="message">The message that was parked on the active node.</param>
public void ReplicatePark(StoreAndForwardMessage message)
{
if (!_options.ReplicationEnabled || _replicationHandler == null) return;
@@ -79,6 +86,7 @@ public class ReplicationService
/// carried message reflects the active node's post-requeue state (Pending,
/// retry_count = 0) so the standby's copy can be brought into sync.
/// </summary>
/// <param name="message">The message in its post-requeue (Pending, retry_count=0) state.</param>
public void ReplicateRequeue(StoreAndForwardMessage message)
{
if (!_options.ReplicationEnabled || _replicationHandler == null) return;
@@ -93,6 +101,8 @@ public class ReplicationService
/// WP-11: Applies a replicated operation received from the active node.
/// Used by the standby node to keep its SQLite in sync.
/// </summary>
/// <param name="operation">The replication operation to apply.</param>
/// <param name="storage">The standby node's store-and-forward storage to update.</param>
public async Task ApplyReplicatedOperationAsync(
ReplicationOperation operation,
StoreAndForwardStorage storage)
@@ -7,6 +7,10 @@ namespace ScadaLink.StoreAndForward;
public static class ServiceCollectionExtensions
{
/// <summary>
/// Registers Store-and-Forward services including storage, the delivery service, and the replication service.
/// </summary>
/// <param name="services">The service collection to register into.</param>
public static IServiceCollection AddStoreAndForward(this IServiceCollection services)
{
services.AddSingleton<StoreAndForwardStorage>(sp =>
@@ -58,6 +62,10 @@ public static class ServiceCollectionExtensions
return services;
}
/// <summary>
/// Registers Store-and-Forward Akka actor bindings. Actor creation is handled by the Host during actor system startup.
/// </summary>
/// <param name="services">The service collection to register into.</param>
public static IServiceCollection AddStoreAndForwardActors(this IServiceCollection services)
{
// Akka actor registration handled by Host component during actor system startup
@@ -74,6 +74,15 @@ public class StoreAndForwardService
/// </summary>
public event Action<string, StoreAndForwardCategory, string>? OnActivity;
/// <summary>
/// Initializes a new instance of the StoreAndForwardService.
/// </summary>
/// <param name="storage">The storage backend for buffered messages.</param>
/// <param name="options">Configuration options.</param>
/// <param name="logger">Logger instance.</param>
/// <param name="replication">Optional replication service for standby synchronization.</param>
/// <param name="cachedCallObserver">Optional observer for cached call lifecycle events.</param>
/// <param name="siteId">The site identifier this service belongs to.</param>
public StoreAndForwardService(
StoreAndForwardStorage storage,
StoreAndForwardOptions options,
@@ -95,6 +104,8 @@ public class StoreAndForwardService
/// <c>_deliveryHandlers</c> field documentation for the true/false/throws contract,
/// which applies identically on the immediate and retry paths.
/// </summary>
/// <param name="category">The message category to handle.</param>
/// <param name="handler">The delivery handler function.</param>
public void RegisterDeliveryHandler(
StoreAndForwardCategory category,
Func<StoreAndForwardMessage, Task<bool>> handler)
@@ -562,6 +573,10 @@ public class StoreAndForwardService
/// <summary>
/// WP-12: Gets parked messages for central query (Pattern 8).
/// </summary>
/// <param name="category">Optional category filter, or null for all categories.</param>
/// <param name="pageNumber">The page number (1-based).</param>
/// <param name="pageSize">The page size.</param>
/// <returns>A tuple of parked messages and the total count.</returns>
public async Task<(List<StoreAndForwardMessage> Messages, int TotalCount)> GetParkedMessagesAsync(
StoreAndForwardCategory? category = null,
int pageNumber = 1,
@@ -579,6 +594,8 @@ public class StoreAndForwardService
/// StoreAndForward-017: the activity-log entry carries the message's true
/// category rather than a hard-coded one.
/// </summary>
/// <param name="messageId">The identifier of the message to retry.</param>
/// <returns>True if successfully retried, false otherwise.</returns>
public async Task<bool> RetryParkedMessageAsync(string messageId)
{
var success = await _storage.RetryParkedMessageAsync(messageId);
@@ -607,6 +624,8 @@ public class StoreAndForwardService
/// StoreAndForward-017: the activity-log entry carries the message's true
/// category rather than a hard-coded one.
/// </summary>
/// <param name="messageId">The identifier of the message to discard.</param>
/// <returns>True if successfully discarded, false otherwise.</returns>
public async Task<bool> DiscardParkedMessageAsync(string messageId)
{
// Capture the category before the row is deleted so the activity log is
@@ -625,6 +644,7 @@ public class StoreAndForwardService
/// <summary>
/// WP-14: Gets buffer depth by category for health reporting.
/// </summary>
/// <returns>A dictionary of buffer depths by category.</returns>
public async Task<Dictionary<StoreAndForwardCategory, int>> GetBufferDepthAsync()
{
return await _storage.GetBufferDepthByCategoryAsync();
@@ -633,6 +653,8 @@ public class StoreAndForwardService
/// <summary>
/// WP-13: Gets count of S&amp;F messages for a given instance (for verifying survival on deletion).
/// </summary>
/// <param name="instanceName">The instance name to query.</param>
/// <returns>The number of messages originating from the instance.</returns>
public async Task<int> GetMessageCountForInstanceAsync(string instanceName)
{
return await _storage.GetMessageCountByOriginInstanceAsync(instanceName);
@@ -644,6 +666,8 @@ public class StoreAndForwardService
/// notification still in transit at the site — central reports it not-found while
/// the S&amp;F buffer still holds it, which is the site-local <c>Forwarding</c> state.
/// </summary>
/// <param name="messageId">The message identifier.</param>
/// <returns>The message, or null if not found.</returns>
public async Task<StoreAndForwardMessage?> GetMessageByIdAsync(string messageId)
{
return await _storage.GetMessageByIdAsync(messageId);
@@ -27,6 +27,11 @@ public class StoreAndForwardStorage
private readonly string _connectionString;
private readonly ILogger<StoreAndForwardStorage> _logger;
/// <summary>
/// Initializes a new instance of <see cref="StoreAndForwardStorage"/> with the given SQLite connection string.
/// </summary>
/// <param name="connectionString">SQLite connection string for the store-and-forward database.</param>
/// <param name="logger">Logger for diagnostics.</param>
public StoreAndForwardStorage(string connectionString, ILogger<StoreAndForwardStorage> logger)
{
_connectionString = connectionString;
@@ -139,6 +144,7 @@ public class StoreAndForwardStorage
/// <summary>
/// WP-9: Enqueues a new message with Pending status.
/// </summary>
/// <param name="message">The message to enqueue.</param>
public async Task EnqueueAsync(StoreAndForwardMessage message)
{
await using var connection = new SqliteConnection(_connectionString);
@@ -209,6 +215,7 @@ public class StoreAndForwardStorage
/// <summary>
/// WP-10: Updates a message after a delivery attempt.
/// </summary>
/// <param name="message">The message with updated retry count, status, and last error.</param>
public async Task UpdateMessageAsync(StoreAndForwardMessage message)
{
await using var connection = new SqliteConnection(_connectionString);
@@ -246,6 +253,8 @@ public class StoreAndForwardStorage
/// the status the sweep observed closes the sweep-vs-management race rather than
/// relying only on the in-process overlapping-sweep guard.
/// </summary>
/// <param name="message">The message with the updated values to persist.</param>
/// <param name="expectedStatus">The status the row must currently have for the update to proceed.</param>
public async Task<bool> UpdateMessageIfStatusAsync(
StoreAndForwardMessage message,
StoreAndForwardMessageStatus expectedStatus)
@@ -277,6 +286,7 @@ public class StoreAndForwardStorage
/// <summary>
/// WP-10: Removes a successfully delivered message.
/// </summary>
/// <param name="messageId">The id of the message to remove.</param>
public async Task RemoveMessageAsync(string messageId)
{
await using var connection = new SqliteConnection(_connectionString);
@@ -298,6 +308,9 @@ public class StoreAndForwardStorage
/// inconsistent with the returned page (flickering totals / off-by-one page math
/// in the paginated UI).
/// </summary>
/// <param name="category">Optional category filter; null returns parked messages from all categories.</param>
/// <param name="pageNumber">1-based page number.</param>
/// <param name="pageSize">Maximum number of messages to return per page.</param>
public async Task<(List<StoreAndForwardMessage> Messages, int TotalCount)> GetParkedMessagesAsync(
StoreAndForwardCategory? category = null,
int pageNumber = 1,
@@ -352,6 +365,7 @@ public class StoreAndForwardStorage
/// interval relative to the original (pre-park) attempt — "try immediately" only
/// by accident, and a long interval would instead delay the operator's retry.
/// </summary>
/// <param name="messageId">The id of the parked message to move back to Pending.</param>
public async Task<bool> RetryParkedMessageAsync(string messageId)
{
await using var connection = new SqliteConnection(_connectionString);
@@ -374,6 +388,7 @@ public class StoreAndForwardStorage
/// <summary>
/// WP-12: Permanently discards a parked message.
/// </summary>
/// <param name="messageId">The id of the parked message to discard.</param>
public async Task<bool> DiscardParkedMessageAsync(string messageId)
{
await using var connection = new SqliteConnection(_connectionString);
@@ -420,6 +435,7 @@ public class StoreAndForwardStorage
/// WP-13: Verifies messages are NOT deleted when an instance is deleted.
/// Returns the count of messages for a given origin instance.
/// </summary>
/// <param name="instanceName">The origin instance name to count messages for.</param>
public async Task<int> GetMessageCountByOriginInstanceAsync(string instanceName)
{
await using var connection = new SqliteConnection(_connectionString);
@@ -438,6 +454,7 @@ public class StoreAndForwardStorage
/// <summary>
/// Gets a message by ID.
/// </summary>
/// <param name="messageId">The id of the message to retrieve.</param>
public async Task<StoreAndForwardMessage?> GetMessageByIdAsync(string messageId)
{
await using var connection = new SqliteConnection(_connectionString);
@@ -473,6 +490,7 @@ public class StoreAndForwardStorage
/// <summary>
/// Gets total message count by status.
/// </summary>
/// <param name="status">The status to filter by.</param>
public async Task<int> GetMessageCountByStatusAsync(StoreAndForwardMessageStatus status)
{
await using var connection = new SqliteConnection(_connectionString);