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
+48 -48
View File
@@ -2,7 +2,7 @@
| Field | Value |
|-------|-------|
| Module | `src/ScadaLink.CentralUI` |
| Module | `src/ZB.MOM.WW.ScadaBridge.CentralUI` |
| Design doc | `docs/requirements/Component-CentralUI.md` |
| Status | Reviewed |
| Last reviewed | 2026-05-28 |
@@ -131,7 +131,7 @@ a UX/design adherence gap), and the un-tested `TransportImport` /
| Severity | Critical |
| Category | Security |
| Status | Resolved |
| Location | `src/ScadaLink.CentralUI/ScriptAnalysis/ScriptAnalysisService.cs:171-424` |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/ScriptAnalysis/ScriptAnalysisService.cs:171-424` |
**Description**
@@ -184,7 +184,7 @@ the commit whose message references `CentralUI-001`.
| Severity | High |
| Category | Security |
| Status | Resolved |
| Location | `src/ScadaLink.CentralUI/Auth/AuthEndpoints.cs:63-69`; `src/ScadaLink.CentralUI/Components/Pages/Deployment/*.razor` |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Auth/AuthEndpoints.cs:63-69`; `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/*.razor` |
**Description**
@@ -231,7 +231,7 @@ message references `CentralUI-002`.
| Severity | High |
| Category | Concurrency & thread safety |
| Status | Resolved |
| Location | `src/ScadaLink.CentralUI/ScriptAnalysis/ScriptAnalysisService.cs:359-423` |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/ScriptAnalysis/ScriptAnalysisService.cs:359-423` |
**Description**
@@ -276,7 +276,7 @@ pre-fix code and pass after. Fixed by the commit whose message references
| Severity | High |
| Category | Security |
| Status | Resolved |
| Location | `src/ScadaLink.CentralUI/Auth/CookieAuthenticationStateProvider.cs:22-28` |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Auth/CookieAuthenticationStateProvider.cs:22-28` |
**Description**
@@ -324,7 +324,7 @@ Fixed by the commit whose message references `CentralUI-004`.
| Severity | Medium |
| Category | Design-document adherence |
| Status | Resolved |
| Location | `src/ScadaLink.CentralUI/Auth/AuthEndpoints.cs:47-81`; `src/ScadaLink.CentralUI/Components/Shared/SessionExpiry.razor:18-30` |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Auth/AuthEndpoints.cs:47-81`; `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/SessionExpiry.razor:18-30` |
**Description**
@@ -346,7 +346,7 @@ fixed 30-minute model. The code and the documented decision must agree.
Resolved 2026-05-16 (commit `<pending>`) — cross-module fix (CentralUI +
Security), explicitly authorized. Root cause confirmed against the source:
`AddCookie` (`ScadaLink.Security/ServiceCollectionExtensions.cs`) set neither
`AddCookie` (`ZB.MOM.WW.ScadaBridge.Security/ServiceCollectionExtensions.cs`) set neither
`ExpireTimeSpan` nor `SlidingExpiration`; `AuthEndpoints` stamped a fixed
`expires_at = UtcNow + 30 min` claim and a 30-minute absolute cookie
`ExpiresUtc`; `SessionExpiry.razor` scheduled one hard redirect at that fixed
@@ -383,8 +383,8 @@ Regression tests fail against the pre-fix code and pass after. Security:
`AddSecurity_AuthCookie_ExpireTimeSpanIsConfigurable` (pins the options-pattern
binding). CentralUI: `SessionExpiryPolicyTests.BuildSignInProperties_DoesNotSetFixedAbsoluteExpiry`,
`..._IsPersistent`, `..._AllowsSlidingRefresh` pin that the login sign-in no
longer imposes a fixed absolute cap. `dotnet build ScadaLink.slnx` clean;
`tests/ScadaLink.Security.Tests` 57 passed, `tests/ScadaLink.CentralUI.Tests`
longer imposes a fixed absolute cap. `dotnet build ZB.MOM.WW.ScadaBridge.slnx` clean;
`tests/ZB.MOM.WW.ScadaBridge.Security.Tests` 57 passed, `tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests`
254 passed.
### CentralUI-006 — Deployment status page polls every 10s despite the documented SignalR-push design
@@ -394,7 +394,7 @@ longer imposes a fixed absolute cap. `dotnet build ScadaLink.slnx` clean;
| Severity | Medium |
| Category | Design-document adherence |
| Status | Resolved |
| Location | `src/ScadaLink.CentralUI/Components/Pages/Deployment/Deployments.razor:196-216` |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/Deployments.razor:196-216` |
**Description**
@@ -424,7 +424,7 @@ deployment records (`GetAllDeploymentRecordsAsync`) and the full instance map
(`GetAllInstancesAsync`) — contradicting Component-CentralUI "Real-Time Updates"
("transitions push to the UI immediately via SignalR … no polling required").
**Process/DI topology confirmed.** `ScadaLink.Host/Program.cs` calls both
**Process/DI topology confirmed.** `ZB.MOM.WW.ScadaBridge.Host/Program.cs` calls both
`AddDeploymentManager()` (line 75) and `AddCentralUI()` (line 77) on the same
`builder.Services` — DeploymentManager and the Central UI run **in the same
central Host process**, so a DI singleton is genuinely shared between the
@@ -432,7 +432,7 @@ DeploymentManager services and the Blazor circuit's scoped components. The
shared-singleton seam is real; no out-of-process fallback was needed.
**What was implemented — push-based updates.** A new
`IDeploymentStatusNotifier` (`ScadaLink.DeploymentManager/IDeploymentStatusNotifier.cs`)
`IDeploymentStatusNotifier` (`ZB.MOM.WW.ScadaBridge.DeploymentManager/IDeploymentStatusNotifier.cs`)
with a C# `event Action<DeploymentStatusChange>` and a small payload
(`DeploymentStatusChange` = deployment id + instance id + new status). Its
implementation `DeploymentStatusNotifier` invokes each subscriber in isolation
@@ -464,9 +464,9 @@ Regression tests fail against the pre-fix code and pass after. DeploymentManager
pins the shared-singleton seam. CentralUI (`DeploymentsPushUpdateTests`):
`Deployments_DoesNotPoll_HasNoRefreshTimer` (pre-fix: the `_refreshTimer` field
existed — confirmed failing), `Deployments_StatusChange_TriggersReload`, and
`Deployments_Dispose_UnsubscribesFromNotifier`. `dotnet build ScadaLink.slnx`
clean (0 warnings); `tests/ScadaLink.DeploymentManager.Tests` 76 passed,
`tests/ScadaLink.CentralUI.Tests` 257 passed. (`TopologyPageTests`' DI fixture
`Deployments_Dispose_UnsubscribesFromNotifier`. `dotnet build ZB.MOM.WW.ScadaBridge.slnx`
clean (0 warnings); `tests/ZB.MOM.WW.ScadaBridge.DeploymentManager.Tests` 76 passed,
`tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests` 257 passed. (`TopologyPageTests`' DI fixture
was also updated to register the new notifier, since it constructs the real
`DeploymentService`.)
@@ -477,7 +477,7 @@ was also updated to register the new notifier, since it constructs the real
| Severity | Medium |
| Category | Correctness & logic bugs |
| Status | Resolved |
| Location | `src/ScadaLink.CentralUI/Components/Layout/NavMenu.razor:69-78`; `src/ScadaLink.CentralUI/Components/Pages/Monitoring/EventLogs.razor:2`; `src/ScadaLink.CentralUI/Components/Pages/Monitoring/ParkedMessages.razor:2` |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Layout/NavMenu.razor:69-78`; `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Monitoring/EventLogs.razor:2`; `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Monitoring/ParkedMessages.razor:2` |
**Description**
@@ -520,7 +520,7 @@ fail against the pre-fix code and pass after;
| Severity | Medium |
| Category | Correctness & logic bugs |
| Status | Resolved |
| Location | `src/ScadaLink.CentralUI/Components/Pages/Monitoring/AuditLog.razor:242-243` |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Monitoring/AuditLog.razor:242-243` |
**Description**
@@ -563,7 +563,7 @@ time-range filters, so it is unaffected.
| Severity | Medium |
| Category | Concurrency & thread safety |
| Status | Resolved |
| Location | `src/ScadaLink.CentralUI/Components/Pages/Deployment/DebugView.razor:400-409,538-544` |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/DebugView.razor:400-409,538-544` |
**Description**
@@ -608,7 +608,7 @@ mechanism rather than the race window.
| Severity | Medium |
| Category | Error handling & resilience |
| Status | Resolved |
| Location | `src/ScadaLink.CentralUI/Components/Shared/ToastNotification.razor:62-71,90` |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/ToastNotification.razor:62-71,90` |
**Description**
@@ -651,7 +651,7 @@ still-works behaviours.
| Severity | Medium |
| Category | Error handling & resilience |
| Status | Resolved |
| Location | `src/ScadaLink.CentralUI/Components/Shared/DiffDialog.razor:89-95,151-157` |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/DiffDialog.razor:89-95,151-157` |
**Description**
@@ -687,7 +687,7 @@ path.
| Severity | Medium |
| Category | Performance & resource management |
| Status | Resolved |
| Location | `src/ScadaLink.CentralUI/Components/Pages/Admin/Sites.razor:196-205` |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Admin/Sites.razor:196-205` |
**Description**
@@ -722,7 +722,7 @@ LoadData_GroupsConnectionsBySite_AndRendersThem}` fail against the pre-fix code
| Severity | Medium |
| Category | Concurrency & thread safety |
| Status | Resolved |
| Location | `src/ScadaLink.CentralUI/ScriptAnalysis/ScriptAnalysisService.cs:951-952` (actual call at `:975`) |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/ScriptAnalysis/ScriptAnalysisService.cs:951-952` (actual call at `:975`) |
**Description**
@@ -764,7 +764,7 @@ source). The five existing `Hover`/`SignatureHelp` tests in
| Severity | Low (re-triaged from Medium 2026-05-16 — see Resolution) |
| Category | Error handling & resilience |
| Status | Resolved |
| Location | `src/ScadaLink.CentralUI/ScriptAnalysis/ScriptAnalysisService.cs:254-259`; `src/ScadaLink.CentralUI/ScriptAnalysis/SandboxHostHelpers.cs:26-117` |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/ScriptAnalysis/ScriptAnalysisService.cs:254-259`; `src/ZB.MOM.WW.ScadaBridge.CentralUI/ScriptAnalysis/SandboxHostHelpers.cs:26-117` |
**Description**
@@ -814,7 +814,7 @@ cannot silently regress.
| Severity | Low |
| Category | Concurrency & thread safety |
| Status | Won't Fix |
| Location | `src/ScadaLink.CentralUI/ServiceCollectionExtensions.cs:24`; `src/ScadaLink.CentralUI/Components/Shared/DialogService.cs:18-69` |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/ServiceCollectionExtensions.cs:24`; `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/DialogService.cs:18-69` |
**Description**
@@ -860,7 +860,7 @@ service cannot silently regress.
| Severity | Low |
| Category | Performance & resource management |
| Status | Resolved |
| Location | `src/ScadaLink.CentralUI/Components/Shared/DataTable.razor:62-68`; `src/ScadaLink.CentralUI/Components/Pages/Deployment/Deployments.razor:167-173` |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/DataTable.razor:62-68`; `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/Deployments.razor:167-173` |
**Description**
@@ -898,7 +898,7 @@ includes first/last) and `PagerWindowTests` (6 tests pinning the helper logic).
| Severity | Low |
| Category | Security |
| Status | Resolved |
| Location | `src/ScadaLink.CentralUI/Auth/AuthEndpoints.cs:127-138` |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Auth/AuthEndpoints.cs:127-138` |
**Description**
@@ -939,7 +939,7 @@ the pre-auth login exemption was not over-corrected.
| Severity | Low |
| Category | Error handling & resilience |
| Status | Resolved |
| Location | `src/ScadaLink.CentralUI/Components/Shared/MonacoEditor.razor:116-118,123,142,164,170,176,182,189`; `src/ScadaLink.CentralUI/Components/Shared/TreeView.razor:129,139`; `src/ScadaLink.CentralUI/Components/Pages/Admin/Sites.razor:316-319` |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/MonacoEditor.razor:116-118,123,142,164,170,176,182,189`; `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/TreeView.razor:129,139`; `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Admin/Sites.razor:316-319` |
**Description**
@@ -991,7 +991,7 @@ not logged).
| Severity | Low |
| Category | Testing coverage |
| Status | Resolved |
| Location | `tests/ScadaLink.CentralUI.Tests/` |
| Location | `tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/` |
**Description**
@@ -1028,7 +1028,7 @@ Toast/timer disposal: `ToastNotificationTests` (from CentralUI-010).
This batch also added `BrowserTimeTests`, `MonitoringAuthorizationTests`,
`SitesPageTests`, `DataTablePagerTests` + `PagerWindowTests`,
`TreeViewStorageResilienceTests`, and `MonacoEditorLoggingTests`. The
`tests/ScadaLink.CentralUI.Tests` suite is green at 251 tests. Remaining
`tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests` suite is green at 251 tests. Remaining
untested paths are low-risk render-only pages; the Critical/High/Medium paths
the finding prioritised are all now covered, so the finding is considered
resolved. (Note: `TopologyPageTests`'s DI setup was also updated this session —
@@ -1044,7 +1044,7 @@ in the fixture.)
| Severity | High |
| Category | Concurrency & thread safety |
| Status | Resolved |
| Location | `src/ScadaLink.CentralUI/Components/Shared/SessionExpiry.razor:39-62`; `src/ScadaLink.CentralUI/Auth/CookieAuthenticationStateProvider.cs:29-43` |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/SessionExpiry.razor:39-62`; `src/ZB.MOM.WW.ScadaBridge.CentralUI/Auth/CookieAuthenticationStateProvider.cs:29-43` |
**Description**
@@ -1094,7 +1094,7 @@ expired session (see CentralUI-025).
| Severity | Medium |
| Category | Concurrency & thread safety |
| Status | Resolved |
| Location | `src/ScadaLink.CentralUI/Components/Pages/Deployment/DebugView.razor:404-419,511-519,275-289` |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/DebugView.razor:404-419,511-519,275-289` |
**Description**
@@ -1133,7 +1133,7 @@ critical section as the upsert.
| Severity | Medium |
| Category | Error handling & resilience |
| Status | Resolved |
| Location | `src/ScadaLink.CentralUI/Components/Pages/Deployment/Deployments.razor:221-229,317-322` |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/Deployments.razor:221-229,317-322` |
**Description**
@@ -1172,7 +1172,7 @@ rather than the whole table on each event.
| Severity | Low |
| Category | Error handling & resilience |
| Status | Resolved |
| Location | `src/ScadaLink.CentralUI/Components/Pages/Monitoring/ParkedMessages.razor:690-698`; `src/ScadaLink.CentralUI/Components/Shared/DiffDialog.razor:107-116,118-130,104` |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Monitoring/ParkedMessages.razor:690-698`; `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/DiffDialog.razor:107-116,118-130,104` |
**Description**
@@ -1205,11 +1205,11 @@ call, consistent with the CentralUI-018 fixes in the same module.
| Severity | Low |
| Category | Code organization & conventions |
| Status | Resolved |
| Location | `src/ScadaLink.CentralUI/Components/Layout/NavMenu.razor:102`; `src/ScadaLink.CentralUI/Components/Pages/Dashboard.razor:14`; `GetCurrentUserAsync` in `Templates.razor`, `TemplateEdit.razor`, `TemplateCreate.razor`, `SharedScripts.razor`, `SharedScriptForm.razor`, `Sites.razor`, `Topology.razor`, `InstanceCreate.razor`, `InstanceConfigure.razor` |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Layout/NavMenu.razor:102`; `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Dashboard.razor:14`; `GetCurrentUserAsync` in `Templates.razor`, `TemplateEdit.razor`, `TemplateCreate.razor`, `SharedScripts.razor`, `SharedScriptForm.razor`, `Sites.razor`, `Topology.razor`, `InstanceCreate.razor`, `InstanceConfigure.razor` |
**Description**
`ScadaLink.Security.JwtTokenService` exposes the canonical claim-type constants
`ZB.MOM.WW.ScadaBridge.Security.JwtTokenService` exposes the canonical claim-type constants
(`UsernameClaimType = "Username"`, `DisplayNameClaimType = "DisplayName"`,
`RoleClaimType`, `SiteIdClaimType`). `SiteScopeService` correctly uses
`JwtTokenService.SiteIdClaimType`, but every `GetCurrentUserAsync` helper across
@@ -1239,7 +1239,7 @@ or a small scoped service) so the claim lookup lives in exactly one place.
| Severity | Low |
| Category | Testing coverage |
| Status | Resolved |
| Location | `tests/ScadaLink.CentralUI.Tests/Auth/SessionExpiryPolicyTests.cs`; `src/ScadaLink.CentralUI/Components/Shared/SessionExpiry.razor` |
| Location | `tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Auth/SessionExpiryPolicyTests.cs`; `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/SessionExpiry.razor` |
**Description**
@@ -1273,7 +1273,7 @@ also forces the CentralUI-020 fix.
| Severity | Medium |
| Category | Correctness & logic bugs |
| Status | Resolved |
| Location | `src/ScadaLink.CentralUI/Components/Audit/AuditFilterBar.razor:97-104`; `src/ScadaLink.CentralUI/Components/Audit/AuditQueryModel.cs:56-58,150-178,203-213` |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Audit/AuditFilterBar.razor:97-104`; `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Audit/AuditQueryModel.cs:56-58,150-178,203-213` |
**Description**
@@ -1317,7 +1317,7 @@ in the same time zone in every documented deployment.
| Severity | Medium |
| Category | Correctness & logic bugs |
| Status | Resolved |
| Location | `src/ScadaLink.CentralUI/Components/Pages/SiteCalls/SiteCallsReport.razor:74-80`; `src/ScadaLink.CentralUI/Components/Pages/SiteCalls/SiteCallsReport.razor.cs:421-425`; `src/ScadaLink.CentralUI/Components/Pages/Notifications/NotificationReport.razor:75-81,639-640`; `src/ScadaLink.CentralUI/Components/Pages/Monitoring/EventLogs.razor:62-73,261-262` |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/SiteCalls/SiteCallsReport.razor:74-80`; `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/SiteCalls/SiteCallsReport.razor.cs:421-425`; `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Notifications/NotificationReport.razor:75-81,639-640`; `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Monitoring/EventLogs.razor:62-73,261-262` |
**Description**
@@ -1364,7 +1364,7 @@ changes.
| Severity | High |
| Category | Security |
| Status | Resolved |
| Location | `src/ScadaLink.CentralUI/Components/Pages/Notifications/NotificationReport.razor:2,434,472,502`; `src/ScadaLink.CentralUI/Components/Pages/SiteCalls/SiteCallsReport.razor:2,52-59`; `src/ScadaLink.CentralUI/Components/Pages/SiteCalls/SiteCallsReport.razor.cs:97-110,201,250-251,278-279` |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Notifications/NotificationReport.razor:2,434,472,502`; `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/SiteCalls/SiteCallsReport.razor:2,52-59`; `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/SiteCalls/SiteCallsReport.razor.cs:97-110,201,250-251,278-279` |
**Description**
@@ -1420,7 +1420,7 @@ Regression test `SiteCallsReportPageTests.SiteScoping_ScopedDeploymentUser_Hides
seeds a Deployment user with a single `SiteId=1` claim, asserts only the Plant-A row
renders, and verifies the Plant-B row is dropped (the page's row count drops from 2 to
1). All three existing report-page test fixtures register `SiteScopeService` so the
default system-wide path is unaffected — the full `ScadaLink.CentralUI.Tests` suite
default system-wide path is unaffected — the full `ZB.MOM.WW.ScadaBridge.CentralUI.Tests` suite
still passes (568 / 568).
### CentralUI-029 — `ConfigurationAuditLog` uses `JS.InvokeAsync<int>("eval", ...)` instead of a dedicated JS module
@@ -1430,9 +1430,9 @@ still passes (568 / 568).
| Severity | Low |
| Category | Code organization & conventions |
| Status | Resolved |
| Location | `src/ScadaLink.CentralUI/Components/Pages/Audit/ConfigurationAuditLog.razor:248-263` |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Audit/ConfigurationAuditLog.razor:248-263` |
**Resolution (2026-05-28):** Added a small 5-line `wwwroot/js/browser-time.js` ES module exporting `getTimezoneOffsetMinutes()`, and replaced the `JS.InvokeAsync<int>("eval", "new Date().getTimezoneOffset()")` call in `ConfigurationAuditLog.OnAfterRenderAsync` with a lazy `IJSObjectReference` import (`./_content/ScadaLink.CentralUI/js/browser-time.js`) + `module.InvokeAsync<int>("getTimezoneOffsetMinutes")`, matching the `session-expiry.js` / `audit-grid.js` / `nav-state.js` / `transport.js` module-import pattern. The residual `eval` JS-interop surface is gone and the page is now CSP-compatible with `unsafe-eval` forbidden.
**Resolution (2026-05-28):** Added a small 5-line `wwwroot/js/browser-time.js` ES module exporting `getTimezoneOffsetMinutes()`, and replaced the `JS.InvokeAsync<int>("eval", "new Date().getTimezoneOffset()")` call in `ConfigurationAuditLog.OnAfterRenderAsync` with a lazy `IJSObjectReference` import (`./_content/ZB.MOM.WW.ScadaBridge.CentralUI/js/browser-time.js`) + `module.InvokeAsync<int>("getTimezoneOffsetMinutes")`, matching the `session-expiry.js` / `audit-grid.js` / `nav-state.js` / `transport.js` module-import pattern. The residual `eval` JS-interop surface is gone and the page is now CSP-compatible with `unsafe-eval` forbidden.
**Description**
@@ -1464,7 +1464,7 @@ plumbing CentralUI-027 will need.
| Severity | Low |
| Category | Concurrency & thread safety |
| Status | Resolved |
| Location | `src/ScadaLink.CentralUI/ScriptAnalysis/SandboxConsoleCapture.cs:31-118`; `src/ScadaLink.CentralUI/ScriptAnalysis/ScriptAnalysisService.cs:401-404` |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/ScriptAnalysis/SandboxConsoleCapture.cs:31-118`; `src/ZB.MOM.WW.ScadaBridge.CentralUI/ScriptAnalysis/ScriptAnalysisService.cs:401-404` |
**Resolution (2026-05-28):** Wrapped every `Write`/`WriteLine` override in `SandboxConsoleCapture` through a `WriteSynchronized` helper that takes a `lock` on the current `AsyncLocal` capture buffer before writing — concurrent `Console.WriteLine` calls from a script's `Task.WhenAll`/`Task.Run` fan-out now serialise on the buffer instance, so the `StringBuilder` underneath can no longer be corrupted. The fall-through to the unwrapped `_fallback` writer is unlocked because the BCL's process-wide `Console.Out` is already synchronised. Different capture scopes have different lock targets, so two unrelated sandbox runs never block each other. New regression test `SandboxConsoleCaptureTests.BeginCapture_ConcurrentWritesFromTasks_DoNotCorruptBuffer` drives 32 tasks × 50 lines each through one capture scope and asserts every line is intact in the buffer.
@@ -1501,9 +1501,9 @@ the expected line count regardless of thread interleaving.
| Severity | Low |
| Category | Performance & resource management |
| Status | Resolved |
| Location | `src/ScadaLink.CentralUI/Components/Pages/Design/TransportImport.razor.cs:72,104-142,160-161` |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TransportImport.razor.cs:72,104-142,160-161` |
**Resolution (2026-05-28):** Replaced the `private byte[]? _bundleBytes` field with `private string? _bundleTempPath`. `OnFileSelectedAsync` now creates `Path.GetTempPath()/scadalink-transport-staging/` (created on first use) and streams the upload via `InputFile.OpenReadStream(maxBytes).CopyToAsync(FileStream)` straight to a `Guid.NewGuid():N + .scadabundle` temp file; `TryLoadAsync` opens the same path as a fresh `FileStream` for each `IBundleImporter.LoadAsync` call. The component now implements `IDisposable` and a `DeleteBundleTempFile()` helper that runs on `ResetSessionState`, `OnFileSelectedAsync` (before a new upload), and `Dispose` (circuit teardown); IO failures during cleanup are swallowed so audit-failure-style defensive semantics hold. Per-circuit working set drops from up to `MaxBundleSizeMb` (default 100 MB) per open wizard to the 80 KB FileStream buffer. The existing reflection-based test helper `SeedAtPassphraseStep` was migrated to write bytes to a real temp file and set `_bundleTempPath`, so the 7 existing TransportImport bUnit tests still pass against the new staging model.
**Resolution (2026-05-28):** Replaced the `private byte[]? _bundleBytes` field with `private string? _bundleTempPath`. `OnFileSelectedAsync` now creates `Path.GetTempPath()/scadabridge-transport-staging/` (created on first use) and streams the upload via `InputFile.OpenReadStream(maxBytes).CopyToAsync(FileStream)` straight to a `Guid.NewGuid():N + .scadabundle` temp file; `TryLoadAsync` opens the same path as a fresh `FileStream` for each `IBundleImporter.LoadAsync` call. The component now implements `IDisposable` and a `DeleteBundleTempFile()` helper that runs on `ResetSessionState`, `OnFileSelectedAsync` (before a new upload), and `Dispose` (circuit teardown); IO failures during cleanup are swallowed so audit-failure-style defensive semantics hold. Per-circuit working set drops from up to `MaxBundleSizeMb` (default 100 MB) per open wizard to the 80 KB FileStream buffer. The existing reflection-based test helper `SeedAtPassphraseStep` was migrated to write bytes to a real temp file and set `_bundleTempPath`, so the 7 existing TransportImport bUnit tests still pass against the new staging model.
**Description**
@@ -1536,7 +1536,7 @@ docs to call out the in-memory cost per concurrent import session.
| Severity | Low |
| Category | Design-document adherence |
| Status | Resolved |
| Location | `src/ScadaLink.CentralUI/Components/Audit/AuditResultsGrid.razor:76-82`; `src/ScadaLink.CentralUI/Components/Audit/AuditResultsGrid.razor.cs:65,196-197,219-220` |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Audit/AuditResultsGrid.razor:76-82`; `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Audit/AuditResultsGrid.razor.cs:65,196-197,219-220` |
**Resolution (2026-05-28):** Added a `Stack<AuditLogPaging?> _cursorStack` and `AuditLogPaging? _currentPaging` field to `AuditResultsGrid.razor.cs`. `NextPage` now pushes the current cursor before advancing; a new `PrevPage` method pops the prior cursor, reloads at that position, and decrements `_pageNumber` only if the reload succeeds (a failed fetch leaves the user on the current page rather than stranding them between pages). The filter-change reset clears the stack alongside `_rows`. The razor template now renders a `btn-group` with a Previous button (gated on `CanGoBack`) alongside the existing Next button; both buttons get the standard `disabled` treatment during loads.
@@ -1568,9 +1568,9 @@ forward-only paging on the Audit Log grid.
| Severity | Low |
| Category | Testing coverage |
| Status | Resolved |
| Location | `src/ScadaLink.CentralUI/Components/Pages/Design/TransportImport.razor.cs:97-238,267-319`; `src/ScadaLink.CentralUI/Components/Pages/SiteCalls/SiteCallsReport.razor.cs:107-148`; `tests/ScadaLink.CentralUI.Tests/Pages/Design/TransportImportPageTests.cs`; `tests/ScadaLink.CentralUI.Tests/Pages/SiteCallsReportPageTests.cs` |
| Location | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TransportImport.razor.cs:97-238,267-319`; `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/SiteCalls/SiteCallsReport.razor.cs:107-148`; `tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Pages/Design/TransportImportPageTests.cs`; `tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Pages/SiteCallsReportPageTests.cs` |
**Resolution (2026-05-28):** Added `tests/ScadaLink.CentralUI.Tests/Pages/QueryStringDrillInTests.cs` (4 bUnit tests). For `SiteCallsReport` it pins the case-insensitive `?status=parked` → canonical "Parked" normalisation, the unrecognised-status silent drop, and the non-boolean `?stuck=yes` silent drop — gaps the existing `SiteCallsReportPageTests` (which covered the Parked / stuck=true / no-params happy paths) did not exercise. For `TransportImport` it asserts that the wizard has no `[Parameter]`-bound query keys: an unrecognised drill-in URL (`?bundleImportId=…&foo=bar`) leaves `_step` at `Upload` and the Step-1 InputFile control renders cleanly.
**Resolution (2026-05-28):** Added `tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Pages/QueryStringDrillInTests.cs` (4 bUnit tests). For `SiteCallsReport` it pins the case-insensitive `?status=parked` → canonical "Parked" normalisation, the unrecognised-status silent drop, and the non-boolean `?stuck=yes` silent drop — gaps the existing `SiteCallsReportPageTests` (which covered the Parked / stuck=true / no-params happy paths) did not exercise. For `TransportImport` it asserts that the wizard has no `[Parameter]`-bound query keys: an unrecognised drill-in URL (`?bundleImportId=…&foo=bar`) leaves `_step` at `Upload` and the Step-1 InputFile control renders cleanly.
**Description**