refactor: rename ScadaLink → ZB.MOM.WW.ScadaBridge (code + projects + namespaces)

Solution + 23 src projects + 26 test projects renamed; folders, csproj,
namespaces, and ScadaLinkDbContext/ScadaBridgeDbContext class updated.
ActorSystem "scadalink" → "scadabridge", Akka seed-node URLs migrated.
SQL roles/logins, LDAP domains, CLI command name, and CLI config dir
(~/.scadalink → ~/.scadabridge) also renamed.

Build green; 5 Host.Tests fail awaiting SQL login rename in next commit.
Pre-existing StaleTagMonitor timing flakes unchanged.

Rename script committed at tools/rename-to-scadabridge.sh.
This commit is contained in:
Joseph Doherty
2026-05-28 09:37:45 -04:00
parent 6d87ee3c3b
commit 7b0b9c7365
1531 changed files with 11180 additions and 11054 deletions
+15 -15
View File
@@ -2,7 +2,7 @@
| Field | Value |
|-------|-------|
| Module | `src/ScadaLink.NotificationOutbox` |
| Module | `src/ZB.MOM.WW.ScadaBridge.NotificationOutbox` |
| Design doc | `docs/requirements/Component-NotificationOutbox.md` |
| Status | Reviewed |
| Last reviewed | 2026-05-28 |
@@ -28,7 +28,7 @@ ConfigurationDatabase**. The outbox inherits two known defects from its sibling
that are reachable through `EmailNotificationDeliveryAdapter`: the OAuth2 SASL empty-user
bug (NS-021) ships every M365 send with `user=""`, and the
`InsertIfNotExistsAsync` check-then-act race (CD-015) lives on the outbox's ack-after-persist
hot path. Neither is a defect of code under `src/ScadaLink.NotificationOutbox/`, but both
hot path. Neither is a defect of code under `src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/`, but both
are surfaced here because production dispatch and ingest go through these exact lines.
A secondary theme is **dispatcher-fire-and-forget audit writes** (`_ = _auditWriter.WriteAsync(...)`)
that can race the per-sweep scope dispose under the wrong DI graph, and a few smaller
@@ -63,7 +63,7 @@ from `Component-NotificationOutbox.md`. No Critical findings; two High, six Medi
| Severity | High |
| Category | Correctness & logic bugs |
| Status | Resolved |
| Location | `src/ScadaLink.NotificationOutbox/Delivery/EmailNotificationDeliveryAdapter.cs:185-191` (calls `smtp.AuthenticateAsync("oauth2", token)`); root cause in `src/ScadaLink.NotificationService/MailKitSmtpClientWrapper.cs:76-79` |
| Location | `src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/Delivery/EmailNotificationDeliveryAdapter.cs:185-191` (calls `smtp.AuthenticateAsync("oauth2", token)`); root cause in `src/ZB.MOM.WW.ScadaBridge.NotificationService/MailKitSmtpClientWrapper.cs:76-79` |
**Description**
@@ -106,7 +106,7 @@ _Unresolved._
| Severity | High |
| Category | Correctness & logic bugs |
| Status | Resolved |
| Location | `src/ScadaLink.NotificationOutbox/NotificationOutboxActor.cs:348-360` |
| Location | `src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/NotificationOutboxActor.cs:348-360` |
**Description**
@@ -146,7 +146,7 @@ _Unresolved._
| Severity | Medium |
| Category | Error handling & resilience |
| Status | Resolved |
| Location | `src/ScadaLink.NotificationOutbox/NotificationOutboxActor.cs:334`, `src/ScadaLink.NotificationOutbox/Delivery/INotificationDeliveryAdapter.cs:22` |
| Location | `src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/NotificationOutboxActor.cs:334`, `src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/Delivery/INotificationDeliveryAdapter.cs:22` |
**Description**
@@ -186,14 +186,14 @@ _Unresolved._
| Severity | Medium |
| Category | Akka.NET conventions |
| Status | Resolved |
| Location | `src/ScadaLink.NotificationOutbox/NotificationOutboxActor.cs:425-435`, `463-485` |
| Location | `src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/NotificationOutboxActor.cs:425-435`, `463-485` |
**Description**
Both emission helpers issue `_ = _auditWriter.WriteAsync(evt);` — discarding the
returned task. `CentralAuditWriter.WriteAsync` opens its own `await using var scope =
_services.CreateAsyncScope();` and resolves a scoped `IAuditLogRepository` (verified
at `src/ScadaLink.AuditLog/Central/CentralAuditWriter.cs:118-121`), so the writer is
at `src/ZB.MOM.WW.ScadaBridge.AuditLog/Central/CentralAuditWriter.cs:118-121`), so the writer is
defensively scope-independent. However the dispatcher already holds a per-sweep
`using var scope = _serviceProvider.CreateScope();` and the per-notification
`UpdateAsync` runs in that scope. The fire-and-forget pattern means:
@@ -238,15 +238,15 @@ _Unresolved._
| Severity | Medium |
| Category | Concurrency & thread safety |
| Status | Resolved |
| Location | `src/ScadaLink.NotificationOutbox/NotificationOutboxActor.cs:127-132` (caller); root cause in `src/ScadaLink.ConfigurationDatabase/Repositories/NotificationOutboxRepository.cs:33-45` |
| Location | `src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/NotificationOutboxActor.cs:127-132` (caller); root cause in `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/NotificationOutboxRepository.cs:33-45` |
**Resolution (2026-05-28):** Closed by CD-015 — `NotificationOutboxRepository.InsertIfNotExistsAsync` (commit `ac96b83`) is now a single-statement `IF NOT EXISTS ... INSERT` via `ExecuteSqlInterpolatedAsync` with a `SqlException` filter swallowing duplicate-key violations (`2601`/`2627`) as a no-op (`return false`). The check-then-act window is eliminated; the at-least-once handoff contract holds and the actor's `PipeTo` success/failure projection no longer surfaces a permanent PK-violation back to the site. Verified in `src/ScadaLink.ConfigurationDatabase/Repositories/NotificationOutboxRepository.cs:51-103`.
**Resolution (2026-05-28):** Closed by CD-015 — `NotificationOutboxRepository.InsertIfNotExistsAsync` (commit `ac96b83`) is now a single-statement `IF NOT EXISTS ... INSERT` via `ExecuteSqlInterpolatedAsync` with a `SqlException` filter swallowing duplicate-key violations (`2601`/`2627`) as a no-op (`return false`). The check-then-act window is eliminated; the at-least-once handoff contract holds and the actor's `PipeTo` success/failure projection no longer surfaces a permanent PK-violation back to the site. Verified in `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/NotificationOutboxRepository.cs:51-103`.
**Description**
`HandleSubmit``PersistAsync` calls `repository.InsertIfNotExistsAsync(notification)`
on `INotificationOutboxRepository`. The current implementation
(`src/ScadaLink.ConfigurationDatabase/Repositories/NotificationOutboxRepository.cs`)
(`src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/NotificationOutboxRepository.cs`)
does a check-then-act with no duplicate-key catch — documented as CD-015 (High,
Open). The Notification Outbox's documented contract is "at-least-once handoff with
ack-after-persist plus insert-if-not-exists on `NotificationId`" (CLAUDE.md,
@@ -280,7 +280,7 @@ remains the CD-015 raw-SQL `IF NOT EXISTS … INSERT` with `2601/2627` catch in
| Severity | Low |
| Category | Performance & resource management |
| Status | Resolved |
| Location | `src/ScadaLink.NotificationOutbox/NotificationOutboxActor.cs:267-277` |
| Location | `src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/NotificationOutboxActor.cs:267-277` |
**Description**
@@ -319,7 +319,7 @@ stateful intent (timeouts, circuit breakers) cannot accidentally lose state.
| Severity | Medium |
| Category | Design-document adherence |
| Status | Resolved |
| Location | `src/ScadaLink.NotificationOutbox/NotificationOutboxOptions.cs:13`, `:22`, `:25`; `docs/requirements/Component-NotificationOutbox.md:152-160` |
| Location | `src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/NotificationOutboxOptions.cs:13`, `:22`, `:25`; `docs/requirements/Component-NotificationOutbox.md:152-160` |
**Description**
@@ -363,7 +363,7 @@ remains tracked separately.
| Severity | Low |
| Category | Testing coverage |
| Status | Resolved |
| Location | `src/ScadaLink.NotificationOutbox/NotificationOutboxActor.cs:29-31`, `:251-259`; tests in `tests/ScadaLink.NotificationOutbox.Tests/NotificationOutboxActorDispatchTests.cs` |
| Location | `src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/NotificationOutboxActor.cs:29-31`, `:251-259`; tests in `tests/ZB.MOM.WW.ScadaBridge.NotificationOutbox.Tests/NotificationOutboxActorDispatchTests.cs` |
**Resolution (2026-05-28):** The `FallbackMaxRetries` / `FallbackRetryDelay` constants are documented for forward-compat with a deferred secondary-adapter path — when no SMTP configuration row exists, `EmailNotificationDeliveryAdapter` returns `Permanent("No SMTP configuration available")` before the retry-policy values are ever consulted, so the path is effectively unreachable from any current production caller. The reachable use of the constants — clamping a non-positive `SmtpConfiguration.MaxRetries` / `RetryDelay` (per NO-002) — is already covered by `TransientFailure_WithZeroMaxRetries_RetriesUsingFallback_DoesNotParkImmediately`, `TransientFailure_WithNegativeMaxRetries_RetriesUsingFallback_DoesNotParkImmediately`, and `TransientFailure_WithNonPositiveRetryDelay_UsesFallbackDelay_NotZero` in `NotificationOutboxActorDispatchTests.cs`. Mark untestable today and re-visit when a non-Email adapter (Teams etc.) makes the empty-SMTP-config branch genuinely deliverable.
@@ -401,7 +401,7 @@ the choice in the actor XML so a maintainer does not "fix" the unreachable code.
| Severity | Low |
| Category | Documentation & comments |
| Status | Resolved |
| Location | `src/ScadaLink.NotificationOutbox/NotificationOutboxOptions.cs:15-16` |
| Location | `src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/NotificationOutboxOptions.cs:15-16` |
**Description**
@@ -440,7 +440,7 @@ requeue, or escalation. Matches `Component-NotificationOutbox.md §Monitoring`.
| Severity | Medium |
| Category | Documentation & comments |
| Status | Resolved |
| Location | `src/ScadaLink.NotificationOutbox/NotificationOutboxActor.cs:469-477` |
| Location | `src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/NotificationOutboxActor.cs:469-477` |
**Description**