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
+34 -34
View File
@@ -2,7 +2,7 @@
| Field | Value |
|-------|-------|
| Module | `src/ScadaLink.HealthMonitoring` |
| Module | `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring` |
| Design doc | `docs/requirements/Component-HealthMonitoring.md` |
| Status | Reviewed |
| Last reviewed | 2026-05-28 |
@@ -119,7 +119,7 @@ _Re-review (2026-05-28, `1eb6e97`):_
| Severity | High |
| Category | Design-document adherence |
| Status | Resolved |
| Location | `src/ScadaLink.HealthMonitoring/SiteHealthCollector.cs:104`, `src/ScadaLink.HealthMonitoring/HealthReportSender.cs:79` |
| Location | `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/SiteHealthCollector.cs:104`, `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/HealthReportSender.cs:79` |
**Description**
@@ -161,7 +161,7 @@ test. No StoreAndForward source was modified (existing public API only).
| Severity | High |
| Category | Concurrency & thread safety |
| Status | Resolved |
| Location | `src/ScadaLink.HealthMonitoring/SiteHealthState.cs:11`, `src/ScadaLink.HealthMonitoring/CentralHealthAggregator.cs:86`, `src/ScadaLink.HealthMonitoring/CentralHealthAggregator.cs:137` |
| Location | `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/SiteHealthState.cs:11`, `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/CentralHealthAggregator.cs:86`, `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/CentralHealthAggregator.cs:137` |
**Description**
@@ -209,7 +209,7 @@ and no lost updates.
| Severity | Medium — re-triaged: already resolved as a side-effect of HealthMonitoring-002. |
| Category | Concurrency & thread safety |
| Status | Resolved |
| Location | `src/ScadaLink.HealthMonitoring/CentralHealthAggregator.cs:45-103` |
| Location | `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/CentralHealthAggregator.cs:45-103` |
**Description**
@@ -251,7 +251,7 @@ updates and no torn snapshots. No further code change was required for this find
| Severity | Low |
| Category | Documentation & comments |
| Status | Resolved |
| Location | `src/ScadaLink.HealthMonitoring/CentralHealthAggregator.cs:146-148`, `src/ScadaLink.HealthMonitoring/SiteHealthState.cs:21`, `src/ScadaLink.HealthMonitoring/ICentralHealthAggregator.cs:16` |
| Location | `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/CentralHealthAggregator.cs:146-148`, `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/SiteHealthState.cs:21`, `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/ICentralHealthAggregator.cs:16` |
**Description**
@@ -289,7 +289,7 @@ number, so readers can reason about the 60s offline grace.
| Severity | Medium |
| Category | Correctness & logic bugs |
| Status | Resolved |
| Location | `src/ScadaLink.HealthMonitoring/CentralHealthReportLoop.cs:48-81`, `src/ScadaLink.HealthMonitoring/CentralHealthAggregator.cs:149` |
| Location | `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/CentralHealthReportLoop.cs:48-81`, `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/CentralHealthAggregator.cs:149` |
**Description**
@@ -333,7 +333,7 @@ offline after 10 minutes) verify the behaviour.
| Severity | Low |
| Category | Design-document adherence |
| Status | Resolved |
| Location | `src/ScadaLink.HealthMonitoring/HealthReportSender.cs:28`, `src/ScadaLink.HealthMonitoring/CentralHealthReportLoop.cs:32` |
| Location | `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/HealthReportSender.cs:28`, `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/CentralHealthReportLoop.cs:32` |
**Description**
@@ -378,7 +378,7 @@ the pre-fix code, which had no `TimeProvider` parameter).
| Severity | Medium |
| Category | Correctness & logic bugs |
| Status | Resolved |
| Location | `src/ScadaLink.HealthMonitoring/CentralHealthAggregator.cs:86-99` |
| Location | `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/CentralHealthAggregator.cs:86-99` |
**Description**
@@ -422,7 +422,7 @@ verifies the registration; `MarkHeartbeat_KeepsSiteOnline_BetweenReports` and
| Severity | Medium — re-triaged: already resolved as a side-effect of HealthMonitoring-002. |
| Category | Concurrency & thread safety |
| Status | Resolved |
| Location | `src/ScadaLink.HealthMonitoring/CentralHealthAggregator.cs:146-158` |
| Location | `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/CentralHealthAggregator.cs:146-158` |
**Description**
@@ -460,7 +460,7 @@ what the HealthMonitoring-002 change did, so no further code change was required
| Severity | Medium |
| Category | Testing coverage |
| Status | Resolved |
| Location | `tests/ScadaLink.HealthMonitoring.Tests/` |
| Location | `tests/ZB.MOM.WW.ScadaBridge.HealthMonitoring.Tests/` |
**Description**
@@ -502,8 +502,8 @@ Resolved 2026-05-16 (commit `pending`). Added the missing coverage:
`SetStoreAndForwardDepths`.
The `SiteHealthReportReplica` idempotency item is **out of scope** for this module:
`SiteHealthReportReplica` is declared in `ScadaLink.Commons` and published/consumed by
`CentralCommunicationActor` in the `ScadaLink.Communication` module — the
`SiteHealthReportReplica` is declared in `ZB.MOM.WW.ScadaBridge.Commons` and published/consumed by
`CentralCommunicationActor` in the `ZB.MOM.WW.ScadaBridge.Communication` module — the
HealthMonitoring module itself has no replication code. Replica double-delivery
idempotency is already covered by `ProcessReport`'s sequence-number guard
(`ProcessReport_RejectsEqualSequence`, `ProcessReport_RejectsStaleReport_WhenSequenceNotGreater`);
@@ -517,7 +517,7 @@ The HealthMonitoring test suite now stands at 47 passing tests (was 30).
| Severity | Low |
| Category | Error handling & resilience |
| Status | Resolved |
| Location | `src/ScadaLink.HealthMonitoring/HealthReportSender.cs:70-87` |
| Location | `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/HealthReportSender.cs:70-87` |
**Description**
@@ -560,7 +560,7 @@ the pre-fix bare `catch { }` (logged-entry collection was empty).
| Severity | Low |
| Category | Code organization & conventions |
| Status | Resolved |
| Location | `src/ScadaLink.HealthMonitoring/ServiceCollectionExtensions.cs:42-46` |
| Location | `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/ServiceCollectionExtensions.cs:42-46` |
**Description**
@@ -593,7 +593,7 @@ build and all 50 tests passing.
| Severity | Low — re-triaged: already resolved as a side-effect of HealthMonitoring-002. |
| Category | Documentation & comments |
| Status | Resolved |
| Location | `src/ScadaLink.HealthMonitoring/SiteHealthState.cs:11` |
| Location | `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/SiteHealthState.cs:11` |
**Description**
@@ -620,7 +620,7 @@ HealthMonitoring-007 fixes. `SiteHealthState.LatestReport` is already declared
`SiteHealthReport? LatestReport { get; init; }` (the recommendation's "make it properly
nullable" option) with an XML doc explaining the `null` case ("known only via heartbeats,
has not yet sent a report"). A codebase-wide search confirms no `null!` suppression
remains anywhere in `src/ScadaLink.HealthMonitoring`. This is exactly the change
remains anywhere in `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring`. This is exactly the change
HealthMonitoring-002 made when converting `SiteHealthState` to an immutable record, so
the contract is now honest and no further code change was required.
@@ -631,7 +631,7 @@ the contract is now honest and no further code change was required.
| Severity | Low |
| Category | Documentation & comments |
| Status | Resolved |
| Location | `src/ScadaLink.HealthMonitoring/CentralHealthAggregator.cs:194-196` |
| Location | `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/CentralHealthAggregator.cs:194-196` |
**Description**
@@ -680,11 +680,11 @@ would have returned 30s against the pre-fix code.
| Severity | Low |
| Category | Error handling & resilience |
| Status | Resolved |
| Location | `src/ScadaLink.HealthMonitoring/HealthMonitoringOptions.cs:3-20`, `src/ScadaLink.HealthMonitoring/CentralHealthAggregator.cs:196`, `src/ScadaLink.HealthMonitoring/HealthReportSender.cs:67`, `src/ScadaLink.HealthMonitoring/CentralHealthReportLoop.cs:63` |
| Location | `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/HealthMonitoringOptions.cs:3-20`, `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/CentralHealthAggregator.cs:196`, `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/HealthReportSender.cs:67`, `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/CentralHealthReportLoop.cs:63` |
**Description**
`HealthMonitoringOptions` is bound from the `ScadaLink:HealthMonitoring` config
`HealthMonitoringOptions` is bound from the `ScadaBridge:HealthMonitoring` config
section (`SiteServiceRegistration.BindSharedOptions`) with no validation —
no `IValidateOptions<HealthMonitoringOptions>`, no `ValidateDataAnnotations`, no
`ValidateOnStart`. `ReportInterval`, `OfflineTimeout`, and `CentralOfflineTimeout`
@@ -713,7 +713,7 @@ the hosted service with an opaque `ArgumentOutOfRangeException`. Added
`HealthMonitoringOptionsValidator : IValidateOptions<HealthMonitoringOptions>` that
rejects non-positive `ReportInterval`/`OfflineTimeout`/`CentralOfflineTimeout` and a
`CentralOfflineTimeout` shorter than `OfflineTimeout`, each failure naming the
`ScadaLink:HealthMonitoring` config key. It is registered (idempotently, via
`ScadaBridge:HealthMonitoring` config key. It is registered (idempotently, via
`TryAddEnumerable`) by all three `ServiceCollectionExtensions` registration methods,
so it fires when the hosted services resolve `IOptions.Value` at startup — failing
fast with a clear message. (`ValidateOnStart()` lives in the Host module's binding
@@ -730,7 +730,7 @@ intervals and the `CentralOfflineTimeout < OfflineTimeout` case.
| Severity | Medium |
| Category | Correctness & logic bugs |
| Status | Resolved |
| Location | `src/ScadaLink.HealthMonitoring/CentralHealthAggregator.cs:122-130`, `src/ScadaLink.HealthMonitoring/SiteHealthState.cs:27` |
| Location | `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/CentralHealthAggregator.cs:122-130`, `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/SiteHealthState.cs:27` |
**Description**
@@ -784,7 +784,7 @@ the first would not compile against the pre-fix non-nullable field), and
| Severity | Low |
| Category | Code organization & conventions |
| Status | Resolved |
| Location | `src/ScadaLink.HealthMonitoring/SiteHealthCollector.cs:151` |
| Location | `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/SiteHealthCollector.cs:151` |
**Description**
@@ -828,7 +828,7 @@ constructor.
| Severity | Medium |
| Category | Correctness & logic bugs |
| Status | Resolved |
| Location | `src/ScadaLink.HealthMonitoring/HealthReportSender.cs:140-154`, `src/ScadaLink.HealthMonitoring/SiteHealthCollector.cs:146-153` |
| Location | `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/HealthReportSender.cs:140-154`, `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/SiteHealthCollector.cs:146-153` |
**Resolution (2026-05-28):** Wrapped `_transport.Send(reportWithSeq)` in an inner try/catch that, on failure, atomically restores the captured per-interval counts via a new `ISiteHealthCollector.AddIntervalCounters(scriptErrors, alarmErrors, deadLetters, siteAuditWriteFailures, auditRedactionFailures)` API backed by `Interlocked.Add`. Concurrent increments arriving during the Send accumulate against the zero left by `CollectReport`'s `Exchange`; the restore Add sums correctly with them. The new interface method ships with a default no-op so existing test fakes (`CountCapturingHealthCollector` etc.) keep compiling without per-fake updates. Regression test `HealthReportSenderTests.SendFailure_PreservesIntervalCountersForNextReport` pre-populates all five counters, makes the first Send throw, and asserts the next successful report carries the original counts (2 / 1 / 3 / 1 / 2).
@@ -882,7 +882,7 @@ report includes the previously-failed interval's `ScriptErrorCount`.
| Severity | Low |
| Category | Correctness & logic bugs |
| Status | Resolved |
| Location | `src/ScadaLink.HealthMonitoring/CentralHealthReportLoop.cs:87-98` |
| Location | `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/CentralHealthReportLoop.cs:87-98` |
**Resolution (2026-05-28):** Same shape of fix as HealthMonitoring-017 — `_aggregator.ProcessReport(reportWithSeq)` now sits inside an inner try/catch that, on failure, calls `_collector.AddIntervalCounters(...)` with the captured report's counts. Reuses the same `ISiteHealthCollector.AddIntervalCounters` API; no extra collector surface. Regression test `CentralHealthReportLoopTests.ProcessReportFailure_PreservesIntervalCountersForNextReport` pre-populates all five counters, makes the first `ProcessReport` throw, and asserts the next successful report carries the original counts.
@@ -919,7 +919,7 @@ collector API once it lands.
| Severity | Medium |
| Category | Design-document adherence |
| Status | Resolved |
| Location | `docs/requirements/Component-HealthMonitoring.md:39,40`, `src/ScadaLink.HealthMonitoring/ICentralHealthAggregator.cs`, `src/ScadaLink.AuditLog/Central/AuditCentralHealthSnapshot.cs:39-58` |
| Location | `docs/requirements/Component-HealthMonitoring.md:39,40`, `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/ICentralHealthAggregator.cs`, `src/ZB.MOM.WW.ScadaBridge.AuditLog/Central/AuditCentralHealthSnapshot.cs:39-58` |
**Resolution (2026-05-28):** Took the simpler doc-removal path rather than surfacing the metrics through a new HealthMonitoring API. Removed `SiteAuditTelemetryStalled` and `CentralAuditWriteFailures` from the Monitored Metrics table, the Audit Log KPIs section, and the Audit Log Dependencies entry in `Component-HealthMonitoring.md`. The two metrics remain internal to `AuditLog`'s `AuditCentralHealthSnapshot` — promoting them to dashboard tiles is a separate feature, out of scope here.
@@ -937,11 +937,11 @@ Tracing the code:
- `SiteAuditTelemetryStalled` is published by `SiteAuditReconciliationActor`,
picked up by `SiteAuditTelemetryStalledTracker`, and latched into
`AuditCentralHealthSnapshot._stalled` (a `ConcurrentDictionary<string, bool>`
in the `ScadaLink.AuditLog` assembly).
in the `ZB.MOM.WW.ScadaBridge.AuditLog` assembly).
- `CentralAuditWriteFailures` is incremented inside `AuditCentralHealthSnapshot`
via `ICentralAuditWriteFailureCounter.Increment()` (also in `ScadaLink.AuditLog`).
via `ICentralAuditWriteFailureCounter.Increment()` (also in `ZB.MOM.WW.ScadaBridge.AuditLog`).
Neither metric is referenced anywhere in `src/ScadaLink.HealthMonitoring/`:
Neither metric is referenced anywhere in `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/`:
- `ICentralHealthAggregator` does not expose them.
- `SiteHealthCollector` has no central counterpart (it is site-only).
- `SiteHealthReport` has no `SiteAuditTelemetryStalled` / `CentralAuditWriteFailures`
@@ -955,7 +955,7 @@ design doc places these metrics under HealthMonitoring's responsibility
Dependencies section's claim that Health Monitoring provides "the
central-computed `CentralAuditWriteFailures` / `AuditRedactionFailure` metrics"
is false for `CentralAuditWriteFailures`: nothing under
`src/ScadaLink.HealthMonitoring/` knows about it.
`src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/` knows about it.
**Recommendation**
@@ -980,7 +980,7 @@ counter and per-site stalled state.
| Severity | Low |
| Category | Correctness & logic bugs |
| Status | Resolved |
| Location | `src/ScadaLink.HealthMonitoring/CentralHealthAggregator.cs:128-147` |
| Location | `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/CentralHealthAggregator.cs:128-147` |
**Resolution (2026-05-28):** `MarkHeartbeat` now branches on
`existing.IsOnline`: when transitioning offline-to-online it anchors
@@ -1035,7 +1035,7 @@ online.
| Severity | Low |
| Category | Correctness & logic bugs |
| Status | Resolved |
| Location | `src/ScadaLink.HealthMonitoring/CentralHealthReportLoop.cs:22`, `src/ScadaLink.HealthMonitoring/CentralHealthAggregator.cs:224-226` |
| Location | `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/CentralHealthReportLoop.cs:22`, `src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/CentralHealthAggregator.cs:224-226` |
**Resolution (2026-05-28):** `CentralHealthReportLoop.CentralSiteId` is now `"$central"` instead of `"central"`. The leading `$` is forbidden in operator-set `Site.SiteIdentifier` values (which are plain identifiers), so the synthetic central self-report cannot collide with a real site whose identifier happens to be the bare word `"central"`. The collision case the finding called out — two reports clobbering each other in the aggregator keyspace via the sequence-number guard and a real site inheriting `CentralOfflineTimeout` and staying falsely-online for an extra two minutes — is now impossible. The aggregator (`CentralHealthAggregator.CheckForOfflineSites`), the Central UI health dashboard (`Monitoring/Health.razor`), and every test reference the constant rather than the literal string, so the value change is local — no consumer code needed updating. Existing `CentralHealthAggregatorTests` and `CentralHealthReportLoopTests` already use the constant, so they continue to pin the central-self-report identity through the new sentinel.
@@ -1088,9 +1088,9 @@ preserved.
| Severity | Low |
| Category | Testing coverage |
| Status | Resolved |
| Location | `tests/ScadaLink.HealthMonitoring.Tests/CentralHealthReportLoopTests.cs:32-42` |
| Location | `tests/ZB.MOM.WW.ScadaBridge.HealthMonitoring.Tests/CentralHealthReportLoopTests.cs:32-42` |
**Resolution (2026-05-28):** Picked the less-invasive recommended option (a) — kept the real-time `PeriodicTimer` but replaced the fixed-budget `Task.Delay` with a generous poll-until-condition helper. `RunLoopUntil(loop, condition, maxWait = 5s)` starts the hosted service, polls `condition` every 25 ms with a 5 s outer cap, and stops cleanly when met; `GeneratesCentralReports_WhenSelfIsPrimary`, `AssignsMonotonicSequenceNumbers`, and `ProcessReportFailure_PreservesIntervalCountersForNextReport` now use it. The legacy `RunLoopBriefly` retains a fixed wait (≥ 1 s) for the two tests that assert *absence* of reports (`GeneratesNoReports_WhenNotPrimary`, `SetsActiveNodeFlag_EvenWhenNotPrimary`) since there is no condition to poll for. Refactoring the production loop to consume `TimeProvider.CreateTimer` (option b) was rejected for batch scope — it would require a production change for what is currently a low-severity test-hygiene gap. All 73 `ScadaLink.HealthMonitoring.Tests` pass; the new generous budget tolerates slow CI runners.
**Resolution (2026-05-28):** Picked the less-invasive recommended option (a) — kept the real-time `PeriodicTimer` but replaced the fixed-budget `Task.Delay` with a generous poll-until-condition helper. `RunLoopUntil(loop, condition, maxWait = 5s)` starts the hosted service, polls `condition` every 25 ms with a 5 s outer cap, and stops cleanly when met; `GeneratesCentralReports_WhenSelfIsPrimary`, `AssignsMonotonicSequenceNumbers`, and `ProcessReportFailure_PreservesIntervalCountersForNextReport` now use it. The legacy `RunLoopBriefly` retains a fixed wait (≥ 1 s) for the two tests that assert *absence* of reports (`GeneratesNoReports_WhenNotPrimary`, `SetsActiveNodeFlag_EvenWhenNotPrimary`) since there is no condition to poll for. Refactoring the production loop to consume `TimeProvider.CreateTimer` (option b) was rejected for batch scope — it would require a production change for what is currently a low-severity test-hygiene gap. All 73 `ZB.MOM.WW.ScadaBridge.HealthMonitoring.Tests` pass; the new generous budget tolerates slow CI runners.
**Description**
@@ -1125,7 +1125,7 @@ module's tests.
| Severity | Low |
| Category | Documentation & comments |
| Status | Resolved |
| Location | `tests/ScadaLink.HealthMonitoring.Tests/SiteHealthCollectorTests.cs:117-122` |
| Location | `tests/ZB.MOM.WW.ScadaBridge.HealthMonitoring.Tests/SiteHealthCollectorTests.cs:117-122` |
**Resolution (2026-05-28):** Renamed the test method from `StoreAndForwardBufferDepths_IsEmptyPlaceholder` to `StoreAndForwardBufferDepths_DefaultsToEmpty_WhenSetterNotCalled` so the name describes what it actually asserts — the default-state contract of a fresh collector. No behaviour change; the body still constructs a collector without calling `SetStoreAndForwardDepths` and asserts `Empty(report.StoreAndForwardBufferDepths)`.