docs(comments): strip internal task/milestone/bundle bookkeeping from code comments
Remove project bookkeeping citations from shipped code comments across the solution: hyphenated task IDs (WP-14, StoreAndForward-025), milestone/task/ issue refs (M3, Task 4, Audit Log #23, #21), Bundle X task-bundle labels, and C/D/K/S/T phase labels. Comment text only — no code logic, string/log literals, or XML-doc structure changed. Genuine descriptions are preserved (only the citation is stripped), and technical lookalikes are retained (UTF-8, SHA-256, T00:00:00, M365, UTC-5, pre-C4/pre-C5 schema versions). Flagged by the new CommentChecker TaskReferenceInComment / TrackingReferenceInComment checks plus targeted grep passes; full solution builds clean, append-only guard tests pass.
This commit is contained in:
+8
-8
@@ -8,14 +8,14 @@ using ZB.MOM.WW.ScadaBridge.NotificationService;
|
||||
namespace ZB.MOM.WW.ScadaBridge.NotificationOutbox.Delivery;
|
||||
|
||||
/// <summary>
|
||||
/// Task 12: Email channel delivery adapter for the central notification outbox.
|
||||
/// Email channel delivery adapter for the central notification outbox.
|
||||
///
|
||||
/// Reuses the <see cref="ZB.MOM.WW.ScadaBridge.NotificationService"/> SMTP primitives —
|
||||
/// <see cref="ISmtpClientWrapper"/>, <see cref="SmtpTlsModeParser"/>,
|
||||
/// <see cref="OAuth2TokenService"/> and the typed <see cref="SmtpPermanentException"/>.
|
||||
/// This adapter owns the full connect/auth/send/disconnect sequence and maps the
|
||||
/// outcome to the outbox's three-way <see cref="DeliveryOutcome"/> (Success / Permanent /
|
||||
/// Transient) — the canonical central-side email delivery path. NS-019: the prior
|
||||
/// Transient) — the canonical central-side email delivery path. The prior
|
||||
/// site-shaped <c>NotificationDeliveryService</c> was deleted with sites no longer
|
||||
/// delivering notifications.
|
||||
/// </summary>
|
||||
@@ -80,7 +80,7 @@ public sealed class EmailNotificationDeliveryAdapter : INotificationDeliveryAdap
|
||||
}
|
||||
|
||||
// An unknown TLS mode is a configuration error that retrying cannot fix —
|
||||
// surface it as a permanent failure (NS-005 SMTP TLS validation policy).
|
||||
// surface it as a permanent failure (SMTP TLS validation policy).
|
||||
SmtpTlsMode tlsMode;
|
||||
try
|
||||
{
|
||||
@@ -95,7 +95,7 @@ public sealed class EmailNotificationDeliveryAdapter : INotificationDeliveryAdap
|
||||
}
|
||||
|
||||
// A malformed sender or recipient address cannot be fixed by retrying —
|
||||
// surface it as a permanent failure (mirrors NS-008).
|
||||
// surface it as a permanent failure.
|
||||
var addressError = EmailAddressValidator.ValidateAddresses(
|
||||
smtpConfig.FromAddress, recipients);
|
||||
if (addressError != null)
|
||||
@@ -146,7 +146,7 @@ public sealed class EmailNotificationDeliveryAdapter : INotificationDeliveryAdap
|
||||
{
|
||||
// An unclassified failure — chiefly an OAuth2 token-fetch failure. The
|
||||
// outbox treats it as permanent: retrying a broken credential burns
|
||||
// token-endpoint calls. (Mirrors the NS-015 default-to-permanent stance.)
|
||||
// token-endpoint calls. (Mirrors the default-to-permanent stance.)
|
||||
var detail = CredentialRedactor.Scrub(ex.Message, smtpConfig.Credentials);
|
||||
_logger.LogError(
|
||||
"Unclassified failure delivering email to list '{List}' ({ExceptionType}): {Detail}",
|
||||
@@ -168,7 +168,7 @@ public sealed class EmailNotificationDeliveryAdapter : INotificationDeliveryAdap
|
||||
string body,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Create exactly one client and dispose the one actually used (NS-004).
|
||||
// Create exactly one client and dispose the one actually used.
|
||||
var smtp = _smtpClientFactory();
|
||||
using var disposable = smtp as IDisposable;
|
||||
|
||||
@@ -188,7 +188,7 @@ public sealed class EmailNotificationDeliveryAdapter : INotificationDeliveryAdap
|
||||
credentials = await _tokenService.GetTokenAsync(credentials, cancellationToken);
|
||||
}
|
||||
|
||||
// NO-001/NS-021: OAuth2 XOAUTH2 requires the user identity (FromAddress)
|
||||
// OAuth2 XOAUTH2 requires the user identity (FromAddress)
|
||||
// to be sent alongside the access token; an empty user is rejected by M365.
|
||||
await smtp.AuthenticateAsync(
|
||||
config.AuthType,
|
||||
@@ -212,7 +212,7 @@ public sealed class EmailNotificationDeliveryAdapter : INotificationDeliveryAdap
|
||||
// catch filters to classify.
|
||||
finally
|
||||
{
|
||||
// Always tear the connection down, regardless of outcome (NS-010).
|
||||
// Always tear the connection down, regardless of outcome.
|
||||
// Disconnect is best-effort: a disconnect failure must not mask the
|
||||
// original delivery exception.
|
||||
try
|
||||
|
||||
+4
-4
@@ -10,7 +10,7 @@ using ZB.MOM.WW.ScadaBridge.NotificationService;
|
||||
namespace ZB.MOM.WW.ScadaBridge.NotificationOutbox.Delivery;
|
||||
|
||||
/// <summary>
|
||||
/// SMS channel delivery adapter for the central notification outbox (T9 pivot).
|
||||
/// SMS channel delivery adapter for the central notification outbox.
|
||||
///
|
||||
/// Calls Twilio directly over its REST API — no SDK — honouring the project's
|
||||
/// no-new-NuGet-package rule. The adapter resolves the notification's list, keeps
|
||||
@@ -18,7 +18,7 @@ namespace ZB.MOM.WW.ScadaBridge.NotificationOutbox.Delivery;
|
||||
/// loads the central <see cref="SmsConfiguration"/>, composes a plain-text body, and
|
||||
/// issues one <c>POST .../Messages.json</c> per recipient (Twilio has no BCC). Each
|
||||
/// response is classified via <see cref="SmsErrorClassifier"/> and rolled up into the
|
||||
/// outbox's three-way <see cref="DeliveryOutcome"/> per design D6.
|
||||
/// outbox's three-way <see cref="DeliveryOutcome"/>.
|
||||
/// <para>
|
||||
/// Mirrors the Email adapter's structure (load list → recipients → config, build
|
||||
/// message, classify outcome). "Accepted by Twilio" (HTTP 2xx) is treated as
|
||||
@@ -280,8 +280,8 @@ public sealed class SmsNotificationDeliveryAdapter : INotificationDeliveryAdapte
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rolls the per-recipient outcomes up into a single notification-level outcome
|
||||
/// per design D6: any transient → Transient (whole notification retries); else any
|
||||
/// Rolls the per-recipient outcomes up into a single notification-level outcome:
|
||||
/// any transient → Transient (whole notification retries); else any
|
||||
/// accepted → Success (the permanently-failed numbers are noted, NOT parked); else
|
||||
/// (all permanent / zero accepted) → Permanent.
|
||||
/// </summary>
|
||||
|
||||
+3
-3
@@ -8,10 +8,10 @@ using ZB.MOM.WW.ScadaBridge.Commons.Types.Notifications;
|
||||
namespace ZB.MOM.WW.ScadaBridge.NotificationOutbox.Kpi;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IKpiSampleSource"/> for the Notification Outbox (#21): snapshots the same
|
||||
/// <see cref="IKpiSampleSource"/> for the Notification Outbox: snapshots the same
|
||||
/// point-in-time delivery KPIs the live Health-dashboard tiles surface — queue depth, stuck
|
||||
/// count, parked count, delivered-last-interval, and oldest-pending age — at every sampling
|
||||
/// pass of the central KPI-history recorder (M6 "KPI History & Trends").
|
||||
/// pass of the central KPI-history recorder ("KPI History & Trends").
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
@@ -35,7 +35,7 @@ namespace ZB.MOM.WW.ScadaBridge.NotificationOutbox.Kpi;
|
||||
public sealed class NotificationOutboxKpiSampleSource : IKpiSampleSource
|
||||
{
|
||||
// Charted metrics share the public Commons catalog so source + UI trend page key
|
||||
// off one symbol (#178). The uncharted internal metrics stay private here.
|
||||
// off one symbol. The uncharted internal metrics stay private here.
|
||||
private const string MetricQueueDepth = KpiMetrics.NotificationOutbox.QueueDepth;
|
||||
private const string MetricStuckCount = "stuckCount";
|
||||
private const string MetricParkedCount = KpiMetrics.NotificationOutbox.ParkedCount;
|
||||
|
||||
@@ -51,7 +51,7 @@ public class NotificationOutboxActor : ReceiveActor, IWithTimers
|
||||
private bool _dispatching;
|
||||
|
||||
/// <summary>
|
||||
/// NotificationOutbox-006: cached <see cref="NotificationType"/> → adapter lookup, built
|
||||
/// Cached <see cref="NotificationType"/> → adapter lookup, built
|
||||
/// lazily on the first dispatch sweep and reused for the lifetime of the actor. The
|
||||
/// adapter registration is decided at startup by <c>AddNotificationOutbox</c> (the set is
|
||||
/// keyed by <see cref="NotificationType"/> and is static per process lifetime), so
|
||||
@@ -70,7 +70,7 @@ public class NotificationOutboxActor : ReceiveActor, IWithTimers
|
||||
private IReadOnlyDictionary<NotificationType, INotificationDeliveryAdapter>? _adaptersCache;
|
||||
|
||||
/// <summary>
|
||||
/// NotificationOutbox-006: actor-lifetime DI scope that owns the cached
|
||||
/// Actor-lifetime DI scope that owns the cached
|
||||
/// <see cref="_adaptersCache"/> adapter instances. Created lazily on the first
|
||||
/// dispatch sweep that needs adapters; disposed in <see cref="PostStop"/> so the
|
||||
/// scoped adapter graph (and any disposable dependencies it transitively holds) is
|
||||
@@ -79,7 +79,7 @@ public class NotificationOutboxActor : ReceiveActor, IWithTimers
|
||||
private IServiceScope? _adaptersScope;
|
||||
|
||||
/// <summary>
|
||||
/// NO-003: lifecycle-scoped cancellation source, cancelled in <see cref="PostStop"/> so
|
||||
/// Lifecycle-scoped cancellation source, cancelled in <see cref="PostStop"/> so
|
||||
/// any in-flight dispatch sweep — including a long-running SMTP send via the channel
|
||||
/// adapter — observes shutdown promptly instead of blocking <c>CoordinatedShutdown</c>
|
||||
/// for the full SMTP connect/auth/send timeout per in-progress notification.
|
||||
@@ -129,7 +129,7 @@ public class NotificationOutboxActor : ReceiveActor, IWithTimers
|
||||
protected override void PreStart()
|
||||
{
|
||||
base.PreStart();
|
||||
// NO-003: shutdown token is alive for the lifetime of the actor; cancelled in PostStop
|
||||
// Shutdown token is alive for the lifetime of the actor; cancelled in PostStop
|
||||
// so dispatcher sweeps and the SMTP send beneath them observe coordinated shutdown.
|
||||
_shutdownCts = new CancellationTokenSource();
|
||||
Timers.StartPeriodicTimer(
|
||||
@@ -141,7 +141,7 @@ public class NotificationOutboxActor : ReceiveActor, IWithTimers
|
||||
/// <inheritdoc />
|
||||
protected override void PostStop()
|
||||
{
|
||||
// NO-003: cancel the shutdown token first so the in-flight sweep's adapter call
|
||||
// Cancel the shutdown token first so the in-flight sweep's adapter call
|
||||
// observes cancellation, then dispose the source. Order matters — disposing first
|
||||
// would race with an in-flight sweep registering with the token.
|
||||
try
|
||||
@@ -156,7 +156,7 @@ public class NotificationOutboxActor : ReceiveActor, IWithTimers
|
||||
_shutdownCts?.Dispose();
|
||||
_shutdownCts = null;
|
||||
|
||||
// NotificationOutbox-006: dispose the actor-lifetime adapter scope so the cached
|
||||
// Dispose the actor-lifetime adapter scope so the cached
|
||||
// scoped adapter instances and their disposable dependencies are torn down with
|
||||
// the actor (e.g. on a CoordinatedShutdown / failover that stops the singleton).
|
||||
_adaptersScope?.Dispose();
|
||||
@@ -252,9 +252,9 @@ public class NotificationOutboxActor : ReceiveActor, IWithTimers
|
||||
|
||||
_dispatching = true;
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
// NO-003: hand the lifecycle token to the sweep so cancellation reaches the
|
||||
// Hand the lifecycle token to the sweep so cancellation reaches the
|
||||
// adapter. A null token (very early start / post-stop race) is replaced with
|
||||
// None — no behaviour change vs. the pre-NO-003 dispatcher.
|
||||
// None — no behaviour change vs. the previous dispatcher.
|
||||
var cancellationToken = _shutdownCts?.Token ?? CancellationToken.None;
|
||||
|
||||
// RunDispatchPass swallows its own errors, but the failure projection is kept as a
|
||||
@@ -283,7 +283,7 @@ public class NotificationOutboxActor : ReceiveActor, IWithTimers
|
||||
/// which is correct because those services back a fresh DbContext per sweep. The
|
||||
/// channel delivery adapters, however, are cached for the actor's lifetime via
|
||||
/// <see cref="ResolveAdapters"/> — see <see cref="_adaptersCache"/> for the
|
||||
/// NotificationOutbox-006 rationale.
|
||||
/// rationale.
|
||||
/// </summary>
|
||||
private async Task RunDispatchPass(DateTimeOffset now, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -301,7 +301,7 @@ public class NotificationOutboxActor : ReceiveActor, IWithTimers
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
// NO-003: shutdown cancelled the claim; row stays Pending and the next active
|
||||
// Shutdown cancelled the claim; row stays Pending and the next active
|
||||
// node picks it up. Not a failure.
|
||||
return;
|
||||
}
|
||||
@@ -320,7 +320,7 @@ public class NotificationOutboxActor : ReceiveActor, IWithTimers
|
||||
|
||||
foreach (var notification in due)
|
||||
{
|
||||
// NO-003: between deliveries, observe shutdown so we don't kick off a fresh
|
||||
// Between deliveries, observe shutdown so we don't kick off a fresh
|
||||
// SMTP send when the actor is already tearing down.
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
@@ -334,7 +334,7 @@ public class NotificationOutboxActor : ReceiveActor, IWithTimers
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
// NO-003: in-flight delivery interrupted by shutdown. Row remains in its
|
||||
// In-flight delivery interrupted by shutdown. Row remains in its
|
||||
// pre-attempt state; next active sweep retries.
|
||||
return;
|
||||
}
|
||||
@@ -360,7 +360,7 @@ public class NotificationOutboxActor : ReceiveActor, IWithTimers
|
||||
/// permanently fail in that case, so the policy only acts as a guard.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// NO-002: a non-positive <see cref="SmtpConfiguration.MaxRetries"/> (zero or negative)
|
||||
/// A non-positive <see cref="SmtpConfiguration.MaxRetries"/> (zero or negative)
|
||||
/// would otherwise satisfy <c>RetryCount >= maxRetries</c> on the very first transient
|
||||
/// failure and park the row without a single retry — silently halving the outbox's
|
||||
/// delivery guarantees. The same applies to a non-positive <c>RetryDelay</c>, which
|
||||
@@ -410,7 +410,7 @@ public class NotificationOutboxActor : ReceiveActor, IWithTimers
|
||||
/// last-wins resolution semantics.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// NotificationOutbox-006: the lookup used to be rebuilt on every dispatch sweep
|
||||
/// The lookup used to be rebuilt on every dispatch sweep
|
||||
/// from the per-sweep DI scope. Adapter registration is static per process
|
||||
/// lifetime, so the dict is now built ONCE — on the first sweep that needs it —
|
||||
/// and reused. To respect each adapter's scoped lifetime
|
||||
@@ -444,7 +444,7 @@ public class NotificationOutboxActor : ReceiveActor, IWithTimers
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// M4 Bundle B2 + B3: a single
|
||||
/// A single
|
||||
/// <see cref="AuditChannel.Notification"/>/<see cref="AuditKind.NotifyDeliver"/>
|
||||
/// row is emitted with <see cref="AuditStatus.Attempted"/> per attempt
|
||||
/// (success, transient, permanent); when the post-outcome status is a
|
||||
@@ -492,7 +492,7 @@ public class NotificationOutboxActor : ReceiveActor, IWithTimers
|
||||
|
||||
// Measure the attempt duration around the adapter call so the
|
||||
// Attempted row carries it for KPI use.
|
||||
// NO-003: pass the lifecycle token so a coordinated shutdown promptly cancels the
|
||||
// Pass the lifecycle token so a coordinated shutdown promptly cancels the
|
||||
// in-flight SMTP send instead of waiting for the SMTP connect/auth/send timeout.
|
||||
var attemptStart = DateTimeOffset.UtcNow;
|
||||
var outcome = await adapter.DeliverAsync(notification, cancellationToken);
|
||||
@@ -579,7 +579,7 @@ public class NotificationOutboxActor : ReceiveActor, IWithTimers
|
||||
/// for the same defensive reason as <see cref="EmitAttemptAuditAsync"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// NO-004: <see cref="ICentralAuditWriter.WriteAsync"/> is awaited inside the
|
||||
/// <see cref="ICentralAuditWriter.WriteAsync"/> is awaited inside the
|
||||
/// try/catch so the catch is actually reachable for writer faults and so the
|
||||
/// audit task does not outlive the per-sweep DI scope. The audit-write-never-
|
||||
/// affects-delivery invariant is preserved by the surrounding catch.
|
||||
@@ -607,7 +607,7 @@ public class NotificationOutboxActor : ReceiveActor, IWithTimers
|
||||
/// <summary>
|
||||
/// Maps the central-outbox <see cref="NotificationStatus"/> terminal
|
||||
/// values onto the corresponding <see cref="AuditStatus"/> values used by
|
||||
/// AuditLog (#23). Non-terminal statuses throw — the caller must gate on
|
||||
/// AuditLog. Non-terminal statuses throw — the caller must gate on
|
||||
/// <see cref="IsTerminal"/>.
|
||||
/// </summary>
|
||||
private static AuditStatus MapNotificationStatusToAuditStatus(NotificationStatus status)
|
||||
@@ -630,7 +630,7 @@ public class NotificationOutboxActor : ReceiveActor, IWithTimers
|
||||
/// dispatcher loop (alog.md §13).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// NO-004: previously the writer task was discarded (<c>_ = WriteAsync(...)</c>),
|
||||
/// Previously the writer task was discarded (<c>_ = WriteAsync(...)</c>),
|
||||
/// which made the surrounding catch unreachable for any fault originating in the
|
||||
/// awaited body of <c>WriteAsync</c> and let the audit task outlive the dispatcher's
|
||||
/// per-sweep DI scope. The task is now awaited inside the try/catch: the
|
||||
@@ -669,7 +669,7 @@ public class NotificationOutboxActor : ReceiveActor, IWithTimers
|
||||
/// <see cref="AuditEvent.ExecutionId"/> is copied straight from
|
||||
/// <see cref="Notification.OriginExecutionId"/> so the dispatcher's
|
||||
/// <c>NotifyDeliver</c> rows carry the same per-run id as the site's
|
||||
/// <c>NotifySend</c> row (Audit Log #23); <see cref="AuditEvent.ParentExecutionId"/>
|
||||
/// <c>NotifySend</c> row; <see cref="AuditEvent.ParentExecutionId"/>
|
||||
/// is likewise copied from <see cref="Notification.OriginParentExecutionId"/>.
|
||||
/// </summary>
|
||||
private static AuditEvent BuildNotifyDeliverEvent(
|
||||
@@ -695,13 +695,13 @@ public class NotificationOutboxActor : ReceiveActor, IWithTimers
|
||||
actor: SystemActor,
|
||||
target: notification.ListName,
|
||||
correlationId: correlationId,
|
||||
// ExecutionId (Audit Log #23): the originating script execution's id,
|
||||
// ExecutionId: the originating script execution's id,
|
||||
// carried from the site on NotificationSubmit and persisted on the
|
||||
// Notification row. Echoing it here links the central NotifyDeliver
|
||||
// rows to the site-emitted NotifySend row for the same run. Null when
|
||||
// the notification was raised outside a script execution.
|
||||
executionId: notification.OriginExecutionId,
|
||||
// ParentExecutionId (Audit Log #23): the originating routed run's
|
||||
// ParentExecutionId: the originating routed run's
|
||||
// parent ExecutionId, carried from the site on NotificationSubmit and
|
||||
// persisted on the Notification row. Echoing it here links the central
|
||||
// NotifyDeliver rows to the routed run's parent. Null for non-routed runs.
|
||||
@@ -1013,7 +1013,7 @@ public class NotificationOutboxActor : ReceiveActor, IWithTimers
|
||||
notification.Status = NotificationStatus.Discarded;
|
||||
await repository.UpdateAsync(notification);
|
||||
|
||||
// M4 Bundle B3: a manual discard is the OTHER code path that produces
|
||||
// A manual discard is the OTHER code path that produces
|
||||
// a terminal NotificationStatus transition (alongside the dispatcher).
|
||||
// Emit a Discarded NotifyDeliver row to match the dispatcher's
|
||||
// Delivered/Parked emissions; the row carries no error message because
|
||||
@@ -1169,17 +1169,17 @@ public class NotificationOutboxActor : ReceiveActor, IWithTimers
|
||||
{
|
||||
SourceInstanceId = msg.SourceInstanceId,
|
||||
SourceScript = msg.SourceScript,
|
||||
// SourceNode (SourceNode-stamping Task 13): the cluster node on which the
|
||||
// SourceNode: the cluster node on which the
|
||||
// notification was emitted (node-a/node-b for site rows). Stamped by the
|
||||
// emitting site from INodeIdentityProvider and carried, inside the
|
||||
// serialized payload, through the S&F buffer to central. EF tracked-entity
|
||||
// insert flows it through to the Notifications.SourceNode column. Null on
|
||||
// submissions buffered before the field existed.
|
||||
SourceNode = msg.SourceNode,
|
||||
// OriginExecutionId (Audit Log #23): the originating script execution's id,
|
||||
// OriginExecutionId: the originating script execution's id,
|
||||
// carried from the site so the dispatcher can echo it onto NotifyDeliver rows.
|
||||
OriginExecutionId = msg.OriginExecutionId,
|
||||
// OriginParentExecutionId (Audit Log #23): the originating routed run's parent
|
||||
// OriginParentExecutionId: the originating routed run's parent
|
||||
// ExecutionId, carried from the site so the dispatcher can echo it onto
|
||||
// NotifyDeliver rows.
|
||||
OriginParentExecutionId = msg.OriginParentExecutionId,
|
||||
|
||||
@@ -80,10 +80,10 @@ public static class ServiceCollectionExtensions
|
||||
services.AddScoped<INotificationDeliveryAdapter>(
|
||||
sp => sp.GetRequiredService<SmsNotificationDeliveryAdapter>());
|
||||
|
||||
// KPI history (M6): the recorder singleton enumerates every IKpiSampleSource each
|
||||
// KPI history: the recorder singleton enumerates every IKpiSampleSource each
|
||||
// sampling pass to snapshot the outbox delivery KPIs into the central history store.
|
||||
// TryAddEnumerable is idempotent — no double-registration if AddNotificationOutbox
|
||||
// is ever called more than once, matching the AuditLog (K8) idiom.
|
||||
// is ever called more than once, matching the AuditLog idiom.
|
||||
services.TryAddEnumerable(ServiceDescriptor.Scoped<IKpiSampleSource, NotificationOutboxKpiSampleSource>());
|
||||
|
||||
return services;
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="ZB.MOM.WW.ScadaBridge.NotificationOutbox.Tests" />
|
||||
<!--
|
||||
Audit Log #23 (M4 Bundle E — Task E2): the cross-project
|
||||
The cross-project
|
||||
NotifyDispatcherAuditTrailTests need to drive the dispatcher loop
|
||||
deterministically via the internal InternalMessages.DispatchTick.Instance
|
||||
sentinel (same pattern the existing NotificationOutbox.Tests use).
|
||||
|
||||
Reference in New Issue
Block a user