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.Commons` |
|
||||
| Module | `src/ZB.MOM.WW.ScadaBridge.Commons` |
|
||||
| Design doc | `docs/requirements/Component-Commons.md` |
|
||||
| Status | Reviewed |
|
||||
| Last reviewed | 2026-05-28 |
|
||||
@@ -79,8 +79,8 @@ coverage for the new types is uneven — `TrackedOperationId`, `SiteCallOperatio
|
||||
`Notification`, and `SiteCall` are all directly tested; the Transport types
|
||||
(`BundleManifest`, `EncryptionMetadata`, `BundleSession`, `BundleSummary`, `ExportSelection`,
|
||||
`ImportPreview`, `ImportResolution`, `ImportResult`, `ManifestContentEntry`) have only
|
||||
integration-level coverage in `tests/ScadaLink.Transport.IntegrationTests/`, with no
|
||||
shape/serialization tests in `ScadaLink.Commons.Tests`.
|
||||
integration-level coverage in `tests/ZB.MOM.WW.ScadaBridge.Transport.IntegrationTests/`, with no
|
||||
shape/serialization tests in `ZB.MOM.WW.ScadaBridge.Commons.Tests`.
|
||||
|
||||
## Checklist coverage
|
||||
|
||||
@@ -109,7 +109,7 @@ shape/serialization tests in `ScadaLink.Commons.Tests`.
|
||||
| 6 | Performance & resource management | ✓ | `IBundleSessionStore.EvictExpired` exists for sessions — good. `BundleSession` carries `DecryptedContent` plus `Manifest` per session; the size is bounded by the configured bundle cap but no explicit per-session size accounting. `ExternalCallResult.Response` lazy parse not thread-safe (Commons-021). |
|
||||
| 7 | Design-document adherence | ✓ | `Component-Commons.md` is now significantly stale relative to the actual file set: stale enum values for `AuditKind`/`AuditStatus`, missing `AuditEvent`/`SiteCall` entities, missing `IAuditLogRepository`, missing six service interfaces and `Interfaces/Transport/`, missing four `Types/*` folders and `Messages/Audit/` (Commons-017). |
|
||||
| 8 | Code organization & conventions | ✓ | `IOperationTrackingStore` and `IPartitionMaintenance` live at the root of `Interfaces/` rather than under `Interfaces/Services/` (Commons-018). `BundleSession.Locked` uses a magic `3` rather than a named constant (Commons-016). Message contracts and entities otherwise follow the additive-evolution / POCO / `record` conventions. |
|
||||
| 9 | Testing coverage | ✓ | Transport types (`BundleManifest`, `EncryptionMetadata`, `BundleSession`, `BundleSummary`, `ExportSelection`, `ImportPreview`, `ImportResolution`, `ImportResult`, `ManifestContentEntry`) have no unit tests in `tests/ScadaLink.Commons.Tests/`; only `tests/ScadaLink.Transport.IntegrationTests/` exercises them (Commons-020). `IngestAuditEventsCommand` / `IngestCachedTelemetryCommand` / `UpsertSiteCallCommand` / `PullAuditEventsRequest` / `PullAuditEventsResponse` / `AuditTelemetryEnvelope` shape tests also absent. |
|
||||
| 9 | Testing coverage | ✓ | Transport types (`BundleManifest`, `EncryptionMetadata`, `BundleSession`, `BundleSummary`, `ExportSelection`, `ImportPreview`, `ImportResolution`, `ImportResult`, `ManifestContentEntry`) have no unit tests in `tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/`; only `tests/ZB.MOM.WW.ScadaBridge.Transport.IntegrationTests/` exercises them (Commons-020). `IngestAuditEventsCommand` / `IngestCachedTelemetryCommand` / `UpsertSiteCallCommand` / `PullAuditEventsRequest` / `PullAuditEventsResponse` / `AuditTelemetryEnvelope` shape tests also absent. |
|
||||
| 10 | Documentation & comments | ✓ | `IAuditCorrelationContext` references `BundleImporter.ApplyAsync` — an implementation type Commons does not see, so the `<see cref>` is unresolvable (Commons-022b, folded into Commons-022). `ImportPreviewItem.FieldDiffJson` and `Notification.ResolvedTargets` are JSON-string columns with no documented shape contract (Commons-022). |
|
||||
|
||||
## Findings
|
||||
@@ -121,7 +121,7 @@ shape/serialization tests in `ScadaLink.Commons.Tests`.
|
||||
| Severity | Medium |
|
||||
| Category | Concurrency & thread safety |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.Commons/Types/StaleTagMonitor.cs:42-46`, `:62-67` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.Commons/Types/StaleTagMonitor.cs:42-46`, `:62-67` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -162,7 +162,7 @@ internal `CallbackEnteredHook` test seam).
|
||||
| Severity | Medium |
|
||||
| Category | Performance & resource management |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.Commons/Types/DynamicJsonElement.cs:10-17` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.Commons/Types/DynamicJsonElement.cs:10-17` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -200,7 +200,7 @@ remarks block documenting the lifetime contract. Regression tests added in
|
||||
| Severity | Medium |
|
||||
| Category | Error handling & resilience |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.Commons/Types/ScriptParameters.cs:72-86` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.Commons/Types/ScriptParameters.cs:72-86` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -241,11 +241,11 @@ contract is unchanged. Regression tests added in `ScriptParametersTests`
|
||||
| Severity | Medium |
|
||||
| Category | Code organization & conventions |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.Commons/Messages/Management/ManagementCommandRegistry.cs:14-35` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Management/ManagementCommandRegistry.cs:14-35` |
|
||||
|
||||
**Description**
|
||||
|
||||
`BuildRegistry` registers only types in the exact `ScadaLink.Commons.Messages.Management`
|
||||
`BuildRegistry` registers only types in the exact `ZB.MOM.WW.ScadaBridge.Commons.Messages.Management`
|
||||
namespace whose names end in `Command`. `GetCommandName(Type)`, however, strips a
|
||||
`Command` suffix from *any* type passed to it. The two halves disagree:
|
||||
|
||||
@@ -295,7 +295,7 @@ SiteRuntime all build clean against the change. Regression tests added in
|
||||
| Severity | Low |
|
||||
| Category | Error handling & resilience |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.Commons/Serialization/OpcUaEndpointConfigSerializer.cs:25-51` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.Commons/Serialization/OpcUaEndpointConfigSerializer.cs:25-51` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -338,7 +338,7 @@ out-of-scope to change. Regression tests added in `OpcUaEndpointConfigSerializer
|
||||
| Severity | Low |
|
||||
| Category | Correctness & logic bugs |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.Commons/Types/DynamicJsonElement.cs:47-51`, `:66-76` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.Commons/Types/DynamicJsonElement.cs:47-51`, `:66-76` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -377,7 +377,7 @@ Regression tests added in `DynamicJsonElementTests` (`TryConvert_ObjectTarget_On
|
||||
| Severity | Low |
|
||||
| Category | Design-document adherence |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.Commons/Types/ScriptParameters.cs`, `src/ScadaLink.Commons/Serialization/OpcUaEndpointConfigSerializer.cs`, `src/ScadaLink.Commons/Validators/OpcUaEndpointConfigValidator.cs`, `src/ScadaLink.Commons/Types/StaleTagMonitor.cs`, `src/ScadaLink.Commons/Types/ScriptArgs.cs` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.Commons/Types/ScriptParameters.cs`, `src/ZB.MOM.WW.ScadaBridge.Commons/Serialization/OpcUaEndpointConfigSerializer.cs`, `src/ZB.MOM.WW.ScadaBridge.Commons/Validators/OpcUaEndpointConfigValidator.cs`, `src/ZB.MOM.WW.ScadaBridge.Commons/Types/StaleTagMonitor.cs`, `src/ZB.MOM.WW.ScadaBridge.Commons/Types/ScriptArgs.cs` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -423,7 +423,7 @@ carve-out makes these types compliant by design.
|
||||
| Severity | Low |
|
||||
| Category | Akka.NET conventions |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.Commons/Messages/Management/InstanceCommands.cs:10` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Management/InstanceCommands.cs:10` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -450,12 +450,12 @@ as `Item1`/`Item2`, unfriendly to REQ-COM-5a additive evolution. Introduced a na
|
||||
`ConnectionBinding(string AttributeName, int DataConnectionId)` in
|
||||
`Messages/Management/InstanceCommands.cs` (alongside the command, matching the existing
|
||||
record-per-file convention) and changed `SetConnectionBindingsCommand.Bindings` to
|
||||
`IReadOnlyList<ConnectionBinding>`. All consumers were updated in lock-step: `ScadaLink.CLI`
|
||||
`IReadOnlyList<ConnectionBinding>`. All consumers were updated in lock-step: `ZB.MOM.WW.ScadaBridge.CLI`
|
||||
(`InstanceCommands.TryParseBindings` now builds a `List<ConnectionBinding>`),
|
||||
`ScadaLink.TemplateEngine` (`InstanceService.SetConnectionBindingsAsync` parameter),
|
||||
`ScadaLink.ManagementService` (`ManagementActor` forwards `cmd.Bindings` unchanged — the
|
||||
`ZB.MOM.WW.ScadaBridge.TemplateEngine` (`InstanceService.SetConnectionBindingsAsync` parameter),
|
||||
`ZB.MOM.WW.ScadaBridge.ManagementService` (`ManagementActor` forwards `cmd.Bindings` unchanged — the
|
||||
new element type flows through), and one consumer the finding did not list,
|
||||
`ScadaLink.CentralUI` (`InstanceConfigure.razor`'s `SaveBindings` built a `List<(string,int)>`).
|
||||
`ZB.MOM.WW.ScadaBridge.CentralUI` (`InstanceConfigure.razor`'s `SaveBindings` built a `List<(string,int)>`).
|
||||
A repo-wide `src/` scan confirms no other references remain. Regression tests added in
|
||||
`ConnectionBindingSerializationTests` (`ConnectionBinding_SerializesWithNamedProperties`
|
||||
asserts named `AttributeName`/`DataConnectionId` JSON properties and the absence of
|
||||
@@ -463,7 +463,7 @@ asserts named `AttributeName`/`DataConnectionId` JSON properties and the absence
|
||||
JSON round-trip); existing parse/forward tests (`ParseBindings_ValidJson_ReturnsPairs`,
|
||||
`SetConnectionBindings_BulkAssignment_Success`) were updated to the new type. Affected
|
||||
suites are green (Commons 226, CLI 77, ManagementService 55, TemplateEngine 287) and
|
||||
`dotnet build ScadaLink.slnx` is clean.
|
||||
`dotnet build ZB.MOM.WW.ScadaBridge.slnx` is clean.
|
||||
|
||||
### Commons-009 — `Component-Commons.md` is stale relative to the actual file set
|
||||
|
||||
@@ -521,11 +521,11 @@ regression test.
|
||||
| Severity | Low |
|
||||
| Category | Testing coverage |
|
||||
| Status | Resolved |
|
||||
| Location | `tests/ScadaLink.Commons.Tests/` |
|
||||
| Location | `tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/` |
|
||||
|
||||
**Description**
|
||||
|
||||
`ScadaLink.Commons.Tests` covers `Result`, `RetryPolicy`, `ScriptParameters`,
|
||||
`ZB.MOM.WW.ScadaBridge.Commons.Tests` covers `Result`, `RetryPolicy`, `ScriptParameters`,
|
||||
`StaleTagMonitor`, the OPC UA validator, enums, message conventions, compatibility, and
|
||||
entity conventions. It does not cover several types that contain exactly the kind of
|
||||
edge-case logic that warrants tests:
|
||||
@@ -567,7 +567,7 @@ tests (up from 196).
|
||||
| Severity | Low |
|
||||
| Category | Correctness & logic bugs |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.Commons/Types/Result.cs:15-20`, `:30-32`, `:36` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.Commons/Types/Result.cs:15-20`, `:30-32`, `:36` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -600,7 +600,7 @@ interpolated string, so no consumer is broken. Regression tests added in `Result
|
||||
| Severity | Low |
|
||||
| Category | Documentation & comments |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.Commons/Types/ValueFormatter.cs:20-27` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.Commons/Types/ValueFormatter.cs:20-27` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -639,7 +639,7 @@ each pinned under `de-DE`).
|
||||
| Severity | Low |
|
||||
| Category | Correctness & logic bugs |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.Commons/Types/DynamicJsonElement.cs:40-54` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.Commons/Types/DynamicJsonElement.cs:40-54` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -682,7 +682,7 @@ tests added in `DynamicJsonElementTests` (`IndexAccess_WithLongIndex_Works`,
|
||||
| Severity | Low |
|
||||
| Category | Error handling & resilience |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.Commons/Serialization/OpcUaEndpointConfigSerializer.cs:107-131` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.Commons/Serialization/OpcUaEndpointConfigSerializer.cs:107-131` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -733,7 +733,7 @@ describe the corrupt-typed-row branch. Regression tests added in
|
||||
| Severity | Medium |
|
||||
| Category | Correctness & logic bugs |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.Commons/Types/Transport/EncryptionMetadata.cs:3-8` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.Commons/Types/Transport/EncryptionMetadata.cs:3-8` |
|
||||
|
||||
**Resolution (2026-05-28):** Converted `EncryptionMetadata` to a non-positional `record` with an explicit constructor that enforces invariants at the type boundary: `Algorithm` must equal `"AES-256-GCM"`, `Kdf` must equal `"PBKDF2-SHA256"`, `Iterations` must lie in `[MinPbkdf2Iterations=100_000, MaxPbkdf2Iterations=10_000_000]`, and `SaltB64`/`IvB64` must be non-null (empty permitted for the BundleSerializer.Pack seed pattern). Invalid values throw `ArgumentException` (or `ArgumentNullException`) naming the offending field. Added `EncryptionMetadataTests` covering valid construction, unknown algorithm/KDF (including case sensitivity), out-of-range iteration counts (including both boundaries), and null salt/IV. Updated `BundleSecretEncryptorTests` / `BundleSerializerTests` to use `MinPbkdf2Iterations` instead of the prior 10_000 placeholder.
|
||||
|
||||
@@ -785,7 +785,7 @@ accepted values on the record.
|
||||
| Severity | Low |
|
||||
| Category | Code organization & conventions |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.Commons/Types/Transport/BundleSession.cs:13-16` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.Commons/Types/Transport/BundleSession.cs:13-16` |
|
||||
|
||||
**Resolution (2026-05-28):** Added `public const int MaxUnlockAttempts = 3;` to `BundleSession` with an XML doc cross-referencing the authoritative `TransportOptions.MaxUnlockAttemptsPerSession`. The `Locked` getter now reads `FailedUnlockAttempts >= MaxUnlockAttempts` instead of comparing against the literal `3`, and the property's XML doc names the constant. No call-site update required — the existing Transport-component `TransportOptions.MaxUnlockAttemptsPerSession` (also `3`) remains the operator-facing dial; this constant is the shim's own threshold, now searchable for a security review.
|
||||
|
||||
@@ -883,17 +883,17 @@ needed again now.
|
||||
| Severity | Low |
|
||||
| Category | Code organization & conventions |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.Commons/Interfaces/IOperationTrackingStore.cs`, `src/ScadaLink.Commons/Interfaces/IPartitionMaintenance.cs` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/IOperationTrackingStore.cs`, `src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/IPartitionMaintenance.cs` |
|
||||
|
||||
**Resolution (2026-05-28):** Moved both files into `src/ScadaLink.Commons/Interfaces/Services/`, matching the REQ-COM-5b sub-folder convention alongside the other service interfaces (`ISiteAuditQueue`, `INodeIdentityProvider`, `ICachedCallLifecycleObserver`, etc.). The 9 consumer files across `ScadaLink.SiteRuntime`, `ScadaLink.AuditLog`, `ScadaLink.ConfigurationDatabase`, and `ScadaLink.Host` exceed the in-instructions 8-file STOP threshold for namespace rewrites, so the namespace was deliberately kept as `ScadaLink.Commons.Interfaces` (not `.Services`) — no consumer change required, build remains green. A comment in each moved file records the rationale and notes that adopting the canonical `.Services` namespace can be picked up alongside any future Commons-wide namespace tidy-up.
|
||||
**Resolution (2026-05-28):** Moved both files into `src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Services/`, matching the REQ-COM-5b sub-folder convention alongside the other service interfaces (`ISiteAuditQueue`, `INodeIdentityProvider`, `ICachedCallLifecycleObserver`, etc.). The 9 consumer files across `ZB.MOM.WW.ScadaBridge.SiteRuntime`, `ZB.MOM.WW.ScadaBridge.AuditLog`, `ZB.MOM.WW.ScadaBridge.ConfigurationDatabase`, and `ZB.MOM.WW.ScadaBridge.Host` exceed the in-instructions 8-file STOP threshold for namespace rewrites, so the namespace was deliberately kept as `ZB.MOM.WW.ScadaBridge.Commons.Interfaces` (not `.Services`) — no consumer change required, build remains green. A comment in each moved file records the rationale and notes that adopting the canonical `.Services` namespace can be picked up alongside any future Commons-wide namespace tidy-up.
|
||||
|
||||
**Description**
|
||||
|
||||
REQ-COM-5b documents the `Interfaces/` folder as having exactly three sub-folders:
|
||||
`Protocol/` (REQ-COM-2), `Repositories/` (REQ-COM-4), and `Services/` (REQ-COM-4a). Two
|
||||
new interfaces — `IOperationTrackingStore` and `IPartitionMaintenance` — are filed at
|
||||
the root of `Interfaces/` (namespace `ScadaLink.Commons.Interfaces`) rather than under
|
||||
`Interfaces/Services/` (namespace `ScadaLink.Commons.Interfaces.Services`). They are
|
||||
the root of `Interfaces/` (namespace `ZB.MOM.WW.ScadaBridge.Commons.Interfaces`) rather than under
|
||||
`Interfaces/Services/` (namespace `ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services`). They are
|
||||
straightforward cross-cutting service interfaces consumed by the Audit Log component (a
|
||||
site-local SQLite tracking store; a central partition-maintenance hosted-service helper)
|
||||
and conceptually belong alongside `ISiteAuditQueue`, `ICachedCallLifecycleObserver`, etc.
|
||||
@@ -904,8 +904,8 @@ other recently-added service interface uses `Interfaces.Services`.
|
||||
**Recommendation**
|
||||
|
||||
Move both files into `Interfaces/Services/` and adjust the namespace to
|
||||
`ScadaLink.Commons.Interfaces.Services`. Update consumers in `ScadaLink.AuditLog`,
|
||||
`ScadaLink.SiteRuntime`, and `ScadaLink.ConfigurationDatabase`. Add them to the
|
||||
`ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services`. Update consumers in `ZB.MOM.WW.ScadaBridge.AuditLog`,
|
||||
`ZB.MOM.WW.ScadaBridge.SiteRuntime`, and `ZB.MOM.WW.ScadaBridge.ConfigurationDatabase`. Add them to the
|
||||
REQ-COM-4a list (see Commons-017).
|
||||
|
||||
### Commons-019 — New `*Utc`-suffixed `DateTime` columns on `AuditEvent` / `SiteCall` are not enforced as UTC; inconsistent with `Notification`'s `DateTimeOffset`
|
||||
@@ -915,9 +915,9 @@ REQ-COM-4a list (see Commons-017).
|
||||
| Severity | Medium |
|
||||
| Category | Correctness & logic bugs |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.Commons/Entities/Audit/AuditEvent.cs:15-18`, `src/ScadaLink.Commons/Entities/Audit/SiteCall.cs:59-68`, `tests/ScadaLink.Commons.Tests/Entities/EntityConventionTests.cs:49-69` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Audit/AuditEvent.cs:15-18`, `src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Audit/SiteCall.cs:59-68`, `tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Entities/EntityConventionTests.cs:49-69` |
|
||||
|
||||
**Resolution (2026-05-28):** Kept the `DateTime` type on `AuditEvent` (a `DateTimeOffset` migration is a data-shape change beyond this finding's scope) and instead enforced the UTC invariant at the assignment boundary. `AuditEvent.OccurredAtUtc` / `IngestedAtUtc` now have init-setters that call `DateTime.SpecifyKind(value, DateTimeKind.Utc)` via private backing fields, so any value supplied with `Kind=Unspecified` (`DateTime` literal, JSON deserialise, EF hydrate that bypassed the converter) is re-tagged as UTC on assignment. The record-level XML doc gained a remarks block stating the invariant and contrasting with `Notification`'s `DateTimeOffset` shape. Sibling `ConfigurationDatabase-018` adds the matching EF value converter so the read path also enforces `Kind=Utc`; the two changes travelled together. Regression coverage in `tests/ScadaLink.ConfigurationDatabase.Tests/Configurations/AuditLogEntityTypeConfigurationTests.cs::Configure_UtcConverter_HydratesOccurredAtUtcAsKindUtc`. The `SiteCall` and `EntityConventionTests` sub-points named in the location list are out of scope for this close (they fall under sibling code-review tasks).
|
||||
**Resolution (2026-05-28):** Kept the `DateTime` type on `AuditEvent` (a `DateTimeOffset` migration is a data-shape change beyond this finding's scope) and instead enforced the UTC invariant at the assignment boundary. `AuditEvent.OccurredAtUtc` / `IngestedAtUtc` now have init-setters that call `DateTime.SpecifyKind(value, DateTimeKind.Utc)` via private backing fields, so any value supplied with `Kind=Unspecified` (`DateTime` literal, JSON deserialise, EF hydrate that bypassed the converter) is re-tagged as UTC on assignment. The record-level XML doc gained a remarks block stating the invariant and contrasting with `Notification`'s `DateTimeOffset` shape. Sibling `ConfigurationDatabase-018` adds the matching EF value converter so the read path also enforces `Kind=Utc`; the two changes travelled together. Regression coverage in `tests/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests/Configurations/AuditLogEntityTypeConfigurationTests.cs::Configure_UtcConverter_HydratesOccurredAtUtcAsKindUtc`. The `SiteCall` and `EntityConventionTests` sub-points named in the location list are out of scope for this close (they fall under sibling code-review tasks).
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -967,16 +967,16 @@ Option 2 is the smaller change and is consistent with how `AuditLog` rows are st
|
||||
SQL Server (`datetime2`, no offset). Either way the inconsistency with `Notification`
|
||||
should be documented in REQ-COM-1 as a deliberate choice.
|
||||
|
||||
### Commons-020 — Transport types and new Audit-message types have no unit tests in `ScadaLink.Commons.Tests`
|
||||
### Commons-020 — Transport types and new Audit-message types have no unit tests in `ZB.MOM.WW.ScadaBridge.Commons.Tests`
|
||||
|
||||
| | |
|
||||
|--|--|
|
||||
| Severity | Low |
|
||||
| Category | Testing coverage |
|
||||
| Status | Resolved |
|
||||
| Location | `tests/ScadaLink.Commons.Tests/` |
|
||||
| Location | `tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/` |
|
||||
|
||||
**Resolution (2026-05-28):** Added `tests/ScadaLink.Commons.Tests/Types/Transport/TransportRecordsTests.cs` (14 tests) covering ctor and System.Text.Json round-trip for `BundleManifest`, `ExportSelection`, `ImportPreview` + `ImportPreviewItem`, `ImportResolution` (all four `ResolutionAction`s), and `ImportResult`, plus a record-equality sanity check that catches a positional/tuple slip. `EncryptionMetadata` (Commons-015), `AuditEvent` (init-setter / `SourceNode`), `CachedCallTelemetry`, and `AuditTelemetryEnvelope` were verified already covered by their existing focused test files; no new tests were added for those to avoid duplication.
|
||||
**Resolution (2026-05-28):** Added `tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Types/Transport/TransportRecordsTests.cs` (14 tests) covering ctor and System.Text.Json round-trip for `BundleManifest`, `ExportSelection`, `ImportPreview` + `ImportPreviewItem`, `ImportResolution` (all four `ResolutionAction`s), and `ImportResult`, plus a record-equality sanity check that catches a positional/tuple slip. `EncryptionMetadata` (Commons-015), `AuditEvent` (init-setter / `SourceNode`), `CachedCallTelemetry`, and `AuditTelemetryEnvelope` were verified already covered by their existing focused test files; no new tests were added for those to avoid duplication.
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -984,8 +984,8 @@ The Transport (#24) work adds nine records under `Types/Transport/` (`BundleMani
|
||||
`EncryptionMetadata`, `BundleSession`, `BundleSummary`, `ExportSelection`,
|
||||
`ImportPreview` + `ImportPreviewItem`, `ImportResolution`, `ImportResult`,
|
||||
`ManifestContentEntry`) and four interfaces under `Interfaces/Transport/`. None of them
|
||||
have a focused test file in `tests/ScadaLink.Commons.Tests/` — coverage is entirely
|
||||
inside `tests/ScadaLink.Transport.IntegrationTests/`, which exercises the
|
||||
have a focused test file in `tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/` — coverage is entirely
|
||||
inside `tests/ZB.MOM.WW.ScadaBridge.Transport.IntegrationTests/`, which exercises the
|
||||
end-to-end exporter/importer flow but does not pin the Commons-level wire contracts.
|
||||
|
||||
Similarly, the new `Messages/Audit/` folder (`IngestAuditEventsCommand`/`Reply`,
|
||||
@@ -1005,11 +1005,11 @@ regression.
|
||||
|
||||
**Recommendation**
|
||||
|
||||
Add focused tests in `tests/ScadaLink.Commons.Tests/Types/Transport/` (round-trip
|
||||
Add focused tests in `tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Types/Transport/` (round-trip
|
||||
serialization for each Transport record, named JSON property assertions for
|
||||
`EncryptionMetadata` / `BundleManifest`, the `BundleSession.Locked` threshold —
|
||||
see Commons-016, the `ConflictKind`/`ResolutionAction` enum coverage), and in
|
||||
`tests/ScadaLink.Commons.Tests/Messages/Audit/` (round-trip + named-property assertions
|
||||
`tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Messages/Audit/` (round-trip + named-property assertions
|
||||
for the seven new message files). Prioritise the contracts that cross the site→central
|
||||
boundary (`AuditTelemetryEnvelope`, `PullAuditEventsRequest`/`Response`,
|
||||
`IngestCachedTelemetryCommand`).
|
||||
@@ -1021,7 +1021,7 @@ boundary (`AuditTelemetryEnvelope`, `PullAuditEventsRequest`/`Response`,
|
||||
| Severity | Low |
|
||||
| Category | Concurrency & thread safety |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.Commons/Interfaces/Services/IExternalSystemClient.cs:91-104` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Services/IExternalSystemClient.cs:91-104` |
|
||||
|
||||
**Resolution (2026-05-28):** Replaced the two mutable backing fields (`_response`/`_responseParsed`) with a single `private readonly Lazy<dynamic?> _response` initialised in the field initializer — `LazyThreadSafetyMode.ExecutionAndPublication` (the default) guarantees the parse runs at most once and every concurrent reader observes the same published `DynamicJsonElement`. `Response` is now a one-line `_response.Value` expression-bodied property. Regression test `ExternalCallResultTests.Response_ConcurrentReads_ReturnSameInstance` fires 64 concurrent readers through a `Barrier` and asserts `Assert.Same` across all observed values.
|
||||
|
||||
@@ -1074,7 +1074,7 @@ behavior.
|
||||
| Severity | Low |
|
||||
| Category | Documentation & comments |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.Commons/Interfaces/Transport/IAuditCorrelationContext.cs:11`, `src/ScadaLink.Commons/Types/Transport/ImportPreview.cs:11`, `src/ScadaLink.Commons/Entities/Notifications/Notification.cs:33` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Transport/IAuditCorrelationContext.cs:11`, `src/ZB.MOM.WW.ScadaBridge.Commons/Types/Transport/ImportPreview.cs:11`, `src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Notifications/Notification.cs:33` |
|
||||
|
||||
**Resolution (2026-05-28):** Replaced the unresolvable `<see cref="BundleImporter.ApplyAsync"/>` in `IAuditCorrelationContext` with a plain-text `BundleImporter.ApplyAsync` reference (qualified inline as "in the Transport component") so the XML doc no longer emits a CS1574 warning from Commons, which cannot see the implementation type. The `ImportPreviewItem.FieldDiffJson` / `Notification.ResolvedTargets` JSON-shape sub-point is tracked separately and not in scope for this close — the XML doc on `IAuditCorrelationContext` does not name those columns.
|
||||
|
||||
@@ -1084,7 +1084,7 @@ Two related XML-doc weaknesses, both around the new Transport / Audit surface:
|
||||
|
||||
1. `IAuditCorrelationContext`'s remarks say
|
||||
`<see cref="BundleImporter.ApplyAsync"/>`. `BundleImporter` is the concrete
|
||||
implementation in `ScadaLink.Transport.Import`, which Commons does not (and must
|
||||
implementation in `ZB.MOM.WW.ScadaBridge.Transport.Import`, which Commons does not (and must
|
||||
not) reference. The cref is unresolvable from Commons and will surface as a
|
||||
build-time XML doc warning. The correct reference is the interface method
|
||||
`IBundleImporter.ApplyAsync`.
|
||||
@@ -1120,7 +1120,7 @@ Two related XML-doc weaknesses, both around the new Transport / Audit surface:
|
||||
| Severity | Low |
|
||||
| Category | Akka.NET conventions |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.Commons/Messages/Audit/SiteCallQueries.cs:53-66`, `:110-123`, `src/ScadaLink.Commons/Messages/Notification/NotificationOutboxQueries.cs:26-39`, `:104-123`, `src/ScadaLink.Commons/Types/SiteCallOperational.cs:42-54`, `src/ScadaLink.Commons/Types/TrackingStatusSnapshot.cs:33-46` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Audit/SiteCallQueries.cs:53-66`, `:110-123`, `src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Notification/NotificationOutboxQueries.cs:26-39`, `:104-123`, `src/ZB.MOM.WW.ScadaBridge.Commons/Types/SiteCallOperational.cs:42-54`, `src/ZB.MOM.WW.ScadaBridge.Commons/Types/TrackingStatusSnapshot.cs:33-46` |
|
||||
|
||||
**Resolution (2026-05-28):** Read all six locations and confirmed the dominant pattern is "trailing-optional with `= null` default" (`SiteCallSummary`, `SiteCallDetail`, `NotificationSummary`, `NotificationDetail`, `NotificationOutboxQueryRequest.SourceNodeFilter`, `SiteCallQueryRequest.SourceNodeFilter` all already use this form). The single odd-one-out was `TrackingStatusSnapshot.SourceNode`, declared as `string? SourceNode` with no default — added the `= null` default to unify it with the rest. Verified both existing callers (`OperationTrackingStore.cs` and `TrackingApiTests.cs`) use named arguments, so the change is purely additive. `SiteCallOperational.SourceNode` sits in the middle of its positional parameter list rather than the trailing slot — that's a separate positional-record concern outside the "trailing-optional" pattern the finding called out, and moving it would touch many telemetry/proto consumers, so it was deliberately not touched here.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user