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:
@@ -2,7 +2,7 @@
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Module | `src/ScadaLink.ConfigurationDatabase` |
|
||||
| Module | `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase` |
|
||||
| Design doc | `docs/requirements/Component-ConfigurationDatabase.md` |
|
||||
| Status | Reviewed |
|
||||
| Last reviewed | 2026-05-28 |
|
||||
@@ -13,7 +13,7 @@
|
||||
## Summary
|
||||
|
||||
The ConfigurationDatabase module is a focused, conventional EF Core data-access layer:
|
||||
a single `ScadaLinkDbContext`, Fluent API entity configurations, eight repository
|
||||
a single `ScadaBridgeDbContext`, Fluent API entity configurations, eight repository
|
||||
implementations of Commons-defined interfaces, an `IAuditService` implementation, an
|
||||
`IInstanceLocator`, environment-aware migration handling, and design-time tooling
|
||||
support. Overall structure adheres well to the design doc and the CLAUDE.md "Code
|
||||
@@ -151,7 +151,7 @@ _Re-review (2026-05-28, `1eb6e97`):_
|
||||
| Severity | High |
|
||||
| Category | Correctness & logic bugs |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/Repositories/TemplateEngineRepository.cs:30-41` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/TemplateEngineRepository.cs:30-41` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -185,7 +185,7 @@ none consume derived/sub-templates; they all need the template's *member* collec
|
||||
(Attributes/Alarms/Scripts/Compositions), which `GetTemplateByIdAsync` already
|
||||
eager-loads. The `Template` entity has no child-templates navigation collection, and
|
||||
adding one (plus changing the interface signature) would require editing
|
||||
`ScadaLink.Commons`, which is outside this module's scope.
|
||||
`ZB.MOM.WW.ScadaBridge.Commons`, which is outside this module's scope.
|
||||
|
||||
Fix applied the recommendation's secondary option: removed the dead query so the
|
||||
method no longer misleads or wastes a round-trip, and added an XML doc comment
|
||||
@@ -204,12 +204,12 @@ template-aggregate contract the callers depend on.
|
||||
| Severity | Medium |
|
||||
| Category | Security |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/DesignTimeDbContextFactory.cs:21-22` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/DesignTimeDbContextFactory.cs:21-22` |
|
||||
|
||||
**Description**
|
||||
|
||||
`DesignTimeDbContextFactory` falls back to a literal connection string
|
||||
`"Server=localhost,1433;Database=ScadaLink_Config;User Id=sa;Password=YourPassword;TrustServerCertificate=True"`
|
||||
`"Server=localhost,1433;Database=ScadaBridge_Config;User Id=sa;Password=YourPassword;TrustServerCertificate=True"`
|
||||
when no configured connection string is found. Embedding a credential literal (even a
|
||||
placeholder) in source code is a poor pattern: it is committed to version control,
|
||||
encourages copy-paste of `sa`/`TrustServerCertificate=True` into real environments, and
|
||||
@@ -220,7 +220,7 @@ silently pointing tooling at an unintended database.
|
||||
|
||||
Remove the hardcoded fallback. If no connection string is resolved from configuration
|
||||
or environment, throw a clear `InvalidOperationException` instructing the developer to
|
||||
set `ScadaLink:Database:ConfigurationDb` (or an environment variable). At minimum, read
|
||||
set `ScadaBridge:Database:ConfigurationDb` (or an environment variable). At minimum, read
|
||||
the design-time connection string from an environment variable rather than a literal,
|
||||
and never use `sa`.
|
||||
|
||||
@@ -233,7 +233,7 @@ resolves the connection string from the Host's appsettings files or, when those
|
||||
present, from the `SCADALINK_DESIGNTIME_CONNECTIONSTRING` environment variable, and
|
||||
throws a clear `InvalidOperationException` (naming both the config key and the env var)
|
||||
when neither yields a value. Also hardened `SetBasePath` to be applied only when the
|
||||
`ScadaLink.Host` directory exists, so the factory degrades cleanly instead of throwing
|
||||
`ZB.MOM.WW.ScadaBridge.Host` directory exists, so the factory degrades cleanly instead of throwing
|
||||
`DirectoryNotFoundException` when run from a context without a sibling Host folder.
|
||||
Regression tests added in `DesignTimeDbContextFactoryTests.cs`:
|
||||
`CreateDbContext_NoConnectionStringConfigured_ThrowsClearException`,
|
||||
@@ -247,13 +247,13 @@ Regression tests added in `DesignTimeDbContextFactoryTests.cs`:
|
||||
| Severity | Medium |
|
||||
| Category | Error handling & resilience |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/ServiceCollectionExtensions.cs:44-49` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/ServiceCollectionExtensions.cs:44-49` |
|
||||
|
||||
**Description**
|
||||
|
||||
The parameterless `AddConfigurationDatabase()` overload is a deliberate no-op "retained
|
||||
for backward compatibility during migration." If a central node is wired up with this
|
||||
overload by mistake, no `ScadaLinkDbContext`, repositories, `IAuditService`, or
|
||||
overload by mistake, no `ScadaBridgeDbContext`, repositories, `IAuditService`, or
|
||||
`IInstanceLocator` are registered. The failure does not surface at startup; it surfaces
|
||||
much later as opaque DI resolution exceptions the first time any consumer requests a
|
||||
repository — far from the actual misconfiguration. The XML comment also refers to
|
||||
@@ -291,7 +291,7 @@ New regression tests added in `ServiceCollectionExtensionsTests.cs`:
|
||||
| Severity | Medium |
|
||||
| Category | Security |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/Configurations/NotificationConfiguration.cs:56-57`, `src/ScadaLink.ConfigurationDatabase/Configurations/ExternalSystemConfiguration.cs:25-26,75-77` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Configurations/NotificationConfiguration.cs:56-57`, `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Configurations/ExternalSystemConfiguration.cs:25-26,75-77` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -325,7 +325,7 @@ backed by ASP.NET Data Protection, which the module already uses
|
||||
(`IDataProtectionKeyContext`, `AddDataProtection().PersistKeysToDbContext`). Added
|
||||
`EncryptedStringConverter` (purpose-scoped `IDataProtector`; `Protect` on write,
|
||||
`Unprotect` on read; null-safe; surfaces a clear message on a `CryptographicException`).
|
||||
`ScadaLinkDbContext` gained an `(options, IDataProtectionProvider)` constructor and
|
||||
`ScadaBridgeDbContext` gained an `(options, IDataProtectionProvider)` constructor and
|
||||
applies the converter to the three secret columns in `OnModelCreating`; the DI
|
||||
registration in `ServiceCollectionExtensions` now constructs the context with the
|
||||
registered provider. The secret columns were widened to `HasMaxLength(8000)` (EF maps
|
||||
@@ -338,7 +338,7 @@ columns plus a null round-trip.
|
||||
The encryption scheme itself is fully in-module; the only remaining cross-cutting item
|
||||
is a documentation gap — the design doc does not yet state encryption-at-rest for these
|
||||
fields. That doc update is outside this module's editable scope (constraint: edit only
|
||||
`src/ScadaLink.ConfigurationDatabase`, the tests, and this file) and is surfaced here
|
||||
`src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase`, the tests, and this file) and is surfaced here
|
||||
for a follow-up to `docs/requirements/Component-ConfigurationDatabase.md`. The audit
|
||||
secret-leak concern is mitigated separately by CD-007's serializer hardening; whether
|
||||
callers should additionally redact secret-bearing entities before passing them to
|
||||
@@ -352,7 +352,7 @@ follow-up. The code fix in this module is complete.
|
||||
| Severity | Low |
|
||||
| Category | Design-document adherence |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/Configurations/AuditConfiguration.cs:11` (entity `src/ScadaLink.Commons/Entities/Audit/AuditLogEntry.cs`) |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Configurations/AuditConfiguration.cs:11` (entity `src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Audit/AuditLogEntry.cs`) |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -374,8 +374,8 @@ Resolve the discrepancy in one direction.
|
||||
|
||||
Resolved 2026-05-16 (commit pending). Root cause confirmed against source: the
|
||||
`AuditLogEntry` entity declares `int Id`, while the design doc's Audit Entry Schema
|
||||
table said `Long / GUID`. The entity lives in `ScadaLink.Commons`
|
||||
(`src/ScadaLink.Commons/Entities/Audit/AuditLogEntry.cs`), which is outside this
|
||||
table said `Long / GUID`. The entity lives in `ZB.MOM.WW.ScadaBridge.Commons`
|
||||
(`src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Audit/AuditLogEntry.cs`), which is outside this
|
||||
module's editable scope, so the discrepancy was resolved by aligning the design doc to
|
||||
the code — the recommendation's second option. The schema table now records `Id` as
|
||||
`int (identity)` with an explicit justification: a 32-bit identity matches the key type
|
||||
@@ -394,7 +394,7 @@ already exercise the `int` key end to end.
|
||||
| Severity | Low |
|
||||
| Category | Code organization & conventions |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/Configurations/SiteConfiguration.cs:24-25` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Configurations/SiteConfiguration.cs:24-25` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -434,7 +434,7 @@ the bound of the `NodeAAddress`/`NodeBAddress` siblings).
|
||||
| Severity | Medium |
|
||||
| Category | Error handling & resilience |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/Services/AuditService.cs:28-30` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Services/AuditService.cs:28-30` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -483,7 +483,7 @@ added in `AuditServiceTests.cs`:
|
||||
| Severity | Low |
|
||||
| Category | Correctness & logic bugs |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/Repositories/InboundApiRepository.cs:46-58` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/InboundApiRepository.cs:46-58` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -532,7 +532,7 @@ capturing `ILogger` to assert the warning is emitted only on malformed input).
|
||||
| Severity | Low |
|
||||
| Category | Performance & resource management |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/Repositories/TemplateEngineRepository.cs:43-51,53-61`, `src/ScadaLink.ConfigurationDatabase/Repositories/CentralUiRepository.cs:45-55` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/TemplateEngineRepository.cs:43-51,53-61`, `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/CentralUiRepository.cs:45-55` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -580,7 +580,7 @@ not a 24-row cartesian product) and
|
||||
| Severity | Low |
|
||||
| Category | Testing coverage |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/Repositories/TemplateEngineRepository.cs`, `Repositories/DeploymentManagerRepository.cs`, `Repositories/ExternalSystemRepository.cs`, `Repositories/InboundApiRepository.cs`, `Repositories/NotificationRepository.cs`, `Repositories/SiteRepository.cs`, `Services/InstanceLocator.cs` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/TemplateEngineRepository.cs`, `Repositories/DeploymentManagerRepository.cs`, `Repositories/ExternalSystemRepository.cs`, `Repositories/InboundApiRepository.cs`, `Repositories/NotificationRepository.cs`, `Repositories/SiteRepository.cs`, `Services/InstanceLocator.cs` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -629,13 +629,13 @@ as the CD-011 regression guard. The full module suite is green.
|
||||
| Severity | Low |
|
||||
| Category | Code organization & conventions |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/Repositories/ExternalSystemRepository.cs:11-14`, `Repositories/InboundApiRepository.cs:11-14`, `Repositories/NotificationRepository.cs:11-14`, `Services/InstanceLocator.cs:13-16` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/ExternalSystemRepository.cs:11-14`, `Repositories/InboundApiRepository.cs:11-14`, `Repositories/NotificationRepository.cs:11-14`, `Services/InstanceLocator.cs:13-16` |
|
||||
|
||||
**Description**
|
||||
|
||||
`SecurityRepository`, `CentralUiRepository`, `TemplateEngineRepository`,
|
||||
`DeploymentManagerRepository`, `SiteRepository`, and `AuditService` all guard their
|
||||
injected `ScadaLinkDbContext` with `?? throw new ArgumentNullException(...)`.
|
||||
injected `ScadaBridgeDbContext` with `?? throw new ArgumentNullException(...)`.
|
||||
`ExternalSystemRepository`, `InboundApiRepository`, `NotificationRepository`, and
|
||||
`InstanceLocator` assign the constructor argument directly with no guard. This is a
|
||||
minor consistency/maintainability issue: although the DI container will not normally
|
||||
@@ -651,7 +651,7 @@ inconsistent constructors so all data-access types behave uniformly.
|
||||
|
||||
Resolved 2026-05-16 (commit pending). Root cause confirmed against source:
|
||||
`ExternalSystemRepository`, `InboundApiRepository`, `NotificationRepository`, and
|
||||
`InstanceLocator` assigned the injected `ScadaLinkDbContext` directly with no null
|
||||
`InstanceLocator` assigned the injected `ScadaBridgeDbContext` directly with no null
|
||||
guard, diverging from `SecurityRepository`/`CentralUiRepository`/`TemplateEngineRepository`/
|
||||
`DeploymentManagerRepository`/`SiteRepository`/`AuditService`. Applied the recommendation:
|
||||
all four constructors now use `context ?? throw new ArgumentNullException(nameof(context))`
|
||||
@@ -668,7 +668,7 @@ Regression: `Constructor_NullContext_Throws` tests were added for all four affec
|
||||
| Severity | Medium |
|
||||
| Category | Security |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/Configurations/InboundApiConfiguration.cs:17-19` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Configurations/InboundApiConfiguration.cs:17-19` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -720,10 +720,10 @@ Design implemented — **deterministic keyed hash** (the recommendation's first
|
||||
is already a high-entropy random token, and a random salt would break the
|
||||
deterministic by-value lookup the authentication path relies on. The pepper instead
|
||||
binds every hash to the deployment. Implemented as `IApiKeyHasher` / `ApiKeyHasher`
|
||||
in `ScadaLink.Commons` (`Types/InboundApi/ApiKeyHasher.cs`); the constructor
|
||||
in `ZB.MOM.WW.ScadaBridge.Commons` (`Types/InboundApi/ApiKeyHasher.cs`); the constructor
|
||||
rejects a missing or weak (`< 16`-char) pepper with `ArgumentException` — fail-fast.
|
||||
- **Where the pepper lives.** `InboundApiOptions.ApiKeyPepper`, a component-owned
|
||||
Options class already bound from the `ScadaLink:InboundApi` configuration section
|
||||
Options class already bound from the `ScadaBridge:InboundApi` configuration section
|
||||
(Options pattern); it is never hard-coded. `AddInboundAPI` registers `IApiKeyHasher`
|
||||
via a factory that reads the bound options, so a missing/weak pepper fails the
|
||||
deployment fast rather than degrading silently. (Operators must supply the pepper
|
||||
@@ -772,14 +772,14 @@ doc, and update the Central UI API-keys page, which previously displayed a maske
|
||||
| Severity | Medium |
|
||||
| Category | Error handling & resilience |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/ScadaLinkDbContext.cs:107-124` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/ScadaBridgeDbContext.cs:107-124` |
|
||||
|
||||
**Description**
|
||||
|
||||
`ApplySecretColumnEncryption` resolves the Data Protection provider as
|
||||
`_dataProtectionProvider ?? new EphemeralDataProtectionProvider()`. The `??` fallback
|
||||
is reached whenever the context is constructed via the single-argument
|
||||
`ScadaLinkDbContext(DbContextOptions)` constructor — i.e. whenever no provider was
|
||||
`ScadaBridgeDbContext(DbContextOptions)` constructor — i.e. whenever no provider was
|
||||
injected. An `EphemeralDataProtectionProvider` generates a key ring that lives only in
|
||||
process memory and is discarded at process exit.
|
||||
|
||||
@@ -788,8 +788,8 @@ it only emits schema). The risk is on a *runtime write path*. The runtime curren
|
||||
gets the provider-bearing context only because `AddConfigurationDatabase` adds an
|
||||
`AddScoped` factory registration that overrides EF's activator-based registration.
|
||||
That override is the single thing standing between correct behaviour and silent data
|
||||
corruption: any future change that resolves a `ScadaLinkDbContext` through a path the
|
||||
override does not cover — an `AddPooledDbContextFactory`/`IDbContextFactory<ScadaLinkDbContext>`
|
||||
corruption: any future change that resolves a `ScadaBridgeDbContext` through a path the
|
||||
override does not cover — an `AddPooledDbContextFactory`/`IDbContextFactory<ScadaBridgeDbContext>`
|
||||
registration, a second `AddDbContext` call, a hand-constructed context in server code —
|
||||
would construct the context with the single-arg constructor, encrypt secret columns
|
||||
with a throwaway key, and persist ciphertext that becomes **permanently undecryptable
|
||||
@@ -806,7 +806,7 @@ single-arg constructor but mark contexts built without a real provider as
|
||||
schema-only — e.g. record a flag and have the encrypting converter throw a clear
|
||||
`InvalidOperationException` ("secret columns cannot be written without a configured
|
||||
Data Protection key ring") on the first `Protect`, instead of producing throwaway
|
||||
ciphertext. Also harden the DI wiring so a `ScadaLinkDbContext` cannot be resolved
|
||||
ciphertext. Also harden the DI wiring so a `ScadaBridgeDbContext` cannot be resolved
|
||||
through the EF-activator registration at all (e.g. register only the factory, or use
|
||||
`AddDbContextFactory` with the explicit constructor).
|
||||
|
||||
@@ -847,7 +847,7 @@ fail-fast guard now closes the residual gap for any other resolution path.
|
||||
| Severity | Low |
|
||||
| Category | Code organization & conventions |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/ScadaLinkDbContext.cs:121-123` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/ScadaBridgeDbContext.cs:121-123` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -892,7 +892,7 @@ columns) — asserting each column keeps an `EncryptedStringConverter`.
|
||||
| Severity | High |
|
||||
| Category | Concurrency & thread safety |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/Repositories/NotificationOutboxRepository.cs:33-45` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/NotificationOutboxRepository.cs:33-45` |
|
||||
|
||||
**Resolution** — rewrote `InsertIfNotExistsAsync` as a single raw-SQL
|
||||
`IF NOT EXISTS (...) INSERT` matching the
|
||||
@@ -903,7 +903,7 @@ catch on numbers 2601 (unique-index violation) and 2627
|
||||
losers are logged at Debug and treated as no-ops, eliminating the
|
||||
site-retry livelock. Two SQLite-targeted assertions in
|
||||
`RepositoryCoverageTests` were migrated to a new MS SQL-fixture file
|
||||
`tests/ScadaLink.ConfigurationDatabase.Tests/Repositories/NotificationOutboxRepositoryIntegrationTests.cs`,
|
||||
`tests/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests/Repositories/NotificationOutboxRepositoryIntegrationTests.cs`,
|
||||
which also adds a 50-way parallel race test verifying exactly one row
|
||||
lands and no exception bubbles.
|
||||
|
||||
@@ -947,7 +947,7 @@ throws and exactly one row lands.
|
||||
| Severity | Medium |
|
||||
| Category | Security |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/Repositories/InboundApiRepository.cs:35-39` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/InboundApiRepository.cs:35-39` |
|
||||
|
||||
**Resolution (2026-05-28):** Took option (a) — `InboundApiRepository` ctor now
|
||||
accepts `Func<IApiKeyHasher>? hasherAccessor = null` (deferred resolution to
|
||||
@@ -986,7 +986,7 @@ as "key not found".
|
||||
**Recommendation**
|
||||
|
||||
Either (a) take `IApiKeyHasher` via constructor injection — alongside the existing
|
||||
`ScadaLinkDbContext` and optional `ILogger` — and use it here so the repository
|
||||
`ScadaBridgeDbContext` and optional `ILogger` — and use it here so the repository
|
||||
participates in the same peppered scheme as the rest of the system; or (b) delete
|
||||
the method from both the implementation and `IInboundApiRepository` (Commons) on the
|
||||
grounds that the production authentication path correctly avoids it for timing
|
||||
@@ -1003,7 +1003,7 @@ longer exists.
|
||||
| Severity | Medium |
|
||||
| Category | Concurrency & thread safety |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/Repositories/DeploymentManagerRepository.cs:83-97` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/DeploymentManagerRepository.cs:83-97` |
|
||||
|
||||
**Resolution (2026-05-28):**
|
||||
`IDeploymentManagerRepository.DeleteDeploymentRecordAsync` now requires a `byte[] expectedRowVersion`
|
||||
@@ -1062,9 +1062,9 @@ when the real RowVersion is supplied.
|
||||
| Severity | Medium |
|
||||
| Category | Code organization & conventions |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/Configurations/AuditLogEntityTypeConfiguration.cs`, `Configurations/SiteCallEntityTypeConfiguration.cs` (mappings for `OccurredAtUtc`, `IngestedAtUtc`, `CreatedAtUtc`, `UpdatedAtUtc`, `TerminalAtUtc`) |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Configurations/AuditLogEntityTypeConfiguration.cs`, `Configurations/SiteCallEntityTypeConfiguration.cs` (mappings for `OccurredAtUtc`, `IngestedAtUtc`, `CreatedAtUtc`, `UpdatedAtUtc`, `TerminalAtUtc`) |
|
||||
|
||||
**Resolution (2026-05-28):** Added two private static `ValueConverter<DateTime, DateTime>` / `ValueConverter<DateTime?, DateTime?>` UTC-enforcing converters to `AuditLogEntityTypeConfiguration` and applied them to `AuditEvent.OccurredAtUtc` and `AuditEvent.IngestedAtUtc` via `HasConversion(...)`. The converter re-tags `DateTimeKind.Utc` on hydrate (where SQL Server's `datetime2` provider strips the Kind flag) and on write (so a producer-supplied `Kind=Unspecified` literal still lands as UTC in the model cache). Coordinates with the sibling `Commons-019` resolution (init-setter on `AuditEvent` re-tags Kind=Utc at construction). Regression test in `tests/ScadaLink.ConfigurationDatabase.Tests/Configurations/AuditLogEntityTypeConfigurationTests.cs::Configure_UtcConverter_HydratesOccurredAtUtcAsKindUtc` inserts an Unspecified-Kind value, re-reads through a cleared change-tracker, and asserts `Kind == Utc` on both columns. The `SiteCall` mapping is out of scope for this close (sibling component task).
|
||||
**Resolution (2026-05-28):** Added two private static `ValueConverter<DateTime, DateTime>` / `ValueConverter<DateTime?, DateTime?>` UTC-enforcing converters to `AuditLogEntityTypeConfiguration` and applied them to `AuditEvent.OccurredAtUtc` and `AuditEvent.IngestedAtUtc` via `HasConversion(...)`. The converter re-tags `DateTimeKind.Utc` on hydrate (where SQL Server's `datetime2` provider strips the Kind flag) and on write (so a producer-supplied `Kind=Unspecified` literal still lands as UTC in the model cache). Coordinates with the sibling `Commons-019` resolution (init-setter on `AuditEvent` re-tags Kind=Utc at construction). Regression test in `tests/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests/Configurations/AuditLogEntityTypeConfigurationTests.cs::Configure_UtcConverter_HydratesOccurredAtUtcAsKindUtc` inserts an Unspecified-Kind value, re-reads through a cleared change-tracker, and asserts `Kind == Utc` on both columns. The `SiteCall` mapping is out of scope for this close (sibling component task).
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -1115,7 +1115,7 @@ modules.
|
||||
| Severity | Medium |
|
||||
| Category | Error handling & resilience |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/Maintenance/AuditLogPartitionMaintenance.cs:181-199` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Maintenance/AuditLogPartitionMaintenance.cs:181-199` |
|
||||
|
||||
**Resolution (2026-05-28):** Took option (a) — dropped the `try/catch (SqlException)`
|
||||
around the per-month SPLIT loop entirely (and the now-unused
|
||||
@@ -1142,7 +1142,7 @@ by either path."
|
||||
|
||||
That rationale is correct only for an "already-exists" error — which the pre-check
|
||||
makes impossible. Any *other* `SqlException` — a permissions failure (the
|
||||
`scadalink_audit_purger` role's `ALTER ON SCHEMA::dbo` revoked or not granted), a
|
||||
`scadabridge_audit_purger` role's `ALTER ON SCHEMA::dbo` revoked or not granted), a
|
||||
deadlock victim, a transient connection drop, a transaction log full, an underlying
|
||||
filegroup full — leaves the boundary genuinely **not** created, logs a Warning
|
||||
(quiet by default in most appenders), and the next iteration tries to SPLIT the
|
||||
@@ -1174,7 +1174,7 @@ aborts after the first failure with no further SPLITs.
|
||||
| Severity | Low |
|
||||
| Category | Correctness & logic bugs |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/Repositories/AuditLogRepository.cs:378-387` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/AuditLogRepository.cs:378-387` |
|
||||
|
||||
**Resolution (2026-05-28):** Wrapped the `reader.GetDateTime(0)` read with `DateTime.SpecifyKind(..., DateTimeKind.Utc)` so each returned boundary now carries `Kind=Utc`, matching the explicit defensive pattern already in `AuditLogPartitionMaintenance.GetMaxBoundaryAsync`. Added an inline comment explaining the rationale (SQL Server `datetime2` strips Kind through ADO.NET; boundary values are stored UTC). With sibling CD-018 also closed, the EF read path now enforces UTC at the column level — the raw-ADO defence here is belt-and-braces for this method, which bypasses EF entirely.
|
||||
|
||||
@@ -1205,7 +1205,7 @@ converter on the column) so the defence at the read site is no longer required.
|
||||
| Severity | Low |
|
||||
| Category | Security |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/Repositories/AuditLogRepository.cs:192-338` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/AuditLogRepository.cs:192-338` |
|
||||
|
||||
**Resolution (2026-05-28):** Took the targeted (1) part of the recommendation —
|
||||
the `monthBoundary` format string is now `"yyyy-MM-dd HH:mm:ss.fffffff"`
|
||||
@@ -1263,7 +1263,7 @@ boundary lookup resolves to the expected partition.
|
||||
| Severity | Low |
|
||||
| Category | Documentation & comments |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/Repositories/DeploymentManagerRepository.cs:8-14` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/DeploymentManagerRepository.cs:8-14` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -1301,7 +1301,7 @@ No behaviour change.
|
||||
| Severity | Low |
|
||||
| Category | Design-document adherence |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/Configurations/AuditLogEntityTypeConfiguration.cs:99-101`, `Migrations/20260520142214_AddAuditLogTable.cs:103-107` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Configurations/AuditLogEntityTypeConfiguration.cs:99-101`, `Migrations/20260520142214_AddAuditLogTable.cs:103-107` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -1341,7 +1341,7 @@ index name remains `IX_AuditLog_CorrelationId`.
|
||||
| Severity | Low |
|
||||
| Category | Testing coverage |
|
||||
| Status | Resolved |
|
||||
| Location | `tests/ScadaLink.ConfigurationDatabase.Tests/Maintenance/AuditLogPartitionMaintenanceTests.cs`, `tests/.../RepositoryCoverageTests.cs:855-869` |
|
||||
| Location | `tests/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests/Maintenance/AuditLogPartitionMaintenanceTests.cs`, `tests/.../RepositoryCoverageTests.cs:855-869` |
|
||||
|
||||
**Resolution (2026-05-28):** (1) Added `AuditLogPartitionMaintenanceTests.EnsureLookahead_SecondSplitThrows_LoopAborts_FirstBoundaryStillCommitted` (Skippable, MS SQL fixture) — installs a `DbCommandInterceptor` that lets the 1st `ALTER PARTITION FUNCTION pf_AuditLog_Month() SPLIT RANGE` through and throws on the 2nd, asserts the exception propagates (CD-019's no-try/catch behaviour), counts exactly one successful split, and verifies the first boundary IS now persisted in `pf_AuditLog_Month` so the next tick resumes from N+1 with no holes. (2) Added `DeploymentManagerRepositoryTests.DeleteDeploymentRecord_CurrentRowVersion_StubAttachPath_DeleteSucceeds` — production-shape happy path: caller holds the current RowVersion, change-tracker cleared, delete completes without throwing `DbUpdateConcurrencyException` and the row is gone (1 row affected).
|
||||
|
||||
|
||||
Reference in New Issue
Block a user