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.ClusterInfrastructure` |
|
||||
| Module | `src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure` |
|
||||
| Design doc | `docs/requirements/Component-ClusterInfrastructure.md` |
|
||||
| Status | Reviewed |
|
||||
| Last reviewed | 2026-05-28 |
|
||||
@@ -40,7 +40,7 @@ well-documented, and well-tested. This re-review examined all three source files
|
||||
all three test files against the full 10-category checklist and found **two new
|
||||
issues**, both stemming from work the prior review explicitly deferred to a "Host
|
||||
review" that has not happened: the `DownIfAlone` property is exposed and validated as
|
||||
part of the configuration contract but is never consumed — `ScadaLink.Host`'s
|
||||
part of the configuration contract but is never consumed — `ZB.MOM.WW.ScadaBridge.Host`'s
|
||||
`BuildHocon` still hard-codes `down-if-alone = on` (CI-009, Medium) — and the validator
|
||||
does not enforce the design doc's requirement that `down-if-alone` be `on` for the
|
||||
keep-oldest resolver, so `DownIfAlone = false` is silently accepted (CI-010, Low).
|
||||
@@ -58,9 +58,9 @@ either did not surface or that have aged into the file:
|
||||
- **CI-011 (Low, Code organization)** — `ClusterOptions.SectionName` is
|
||||
documented as "the single source of truth so binding sites do not hard-code
|
||||
the magic string" (the very justification CI-005's resolution offered), but
|
||||
`ScadaLink.Host.SiteServiceRegistration.BindSharedOptions:100` and three
|
||||
references in `ScadaLink.Host.StartupValidator` all hard-code
|
||||
`"ScadaLink:Cluster"` literals. The constant is decorative — a "single source
|
||||
`ZB.MOM.WW.ScadaBridge.Host.SiteServiceRegistration.BindSharedOptions:100` and three
|
||||
references in `ZB.MOM.WW.ScadaBridge.Host.StartupValidator` all hard-code
|
||||
`"ScadaBridge:Cluster"` literals. The constant is decorative — a "single source
|
||||
of truth" that nothing reads. Same pattern as CI-009 (inert configuration knob).
|
||||
- **CI-012 (Low, Design-document adherence)** — the validator accepts
|
||||
`SeedNodes.Count == 1` even though the design doc states "both nodes are seed
|
||||
@@ -90,7 +90,7 @@ Original review (2026-05-16, `9c60592`) below; the re-review notes (2026-05-17,
|
||||
| # | Category | Examined | Notes |
|
||||
|---|----------|----------|-------|
|
||||
| 1 | Correctness & logic bugs | ✓ | No executable logic exists beyond an options POCO; no logic bugs, but `ServiceCollectionExtensions` returns success while doing nothing (CI-002). **Re-review:** CI-002 resolved. New — `DownIfAlone` is a settable property that controls nothing because the HOCON builder hard-codes the value (CI-009). |
|
||||
| 2 | Akka.NET conventions | ✓ | No actors, no `ActorSystem` bootstrap, no supervision, no cluster/singleton wiring exist despite the design doc requiring all of them (CI-001). Nothing to assess against `Tell`/`Ask`, immutability, or `PipeTo`. **Re-review:** confirmed the Akka bootstrap legitimately lives in `ScadaLink.Host` (CI-001 resolution); still nothing actor-related in this module. No issues. |
|
||||
| 2 | Akka.NET conventions | ✓ | No actors, no `ActorSystem` bootstrap, no supervision, no cluster/singleton wiring exist despite the design doc requiring all of them (CI-001). Nothing to assess against `Tell`/`Ask`, immutability, or `PipeTo`. **Re-review:** confirmed the Akka bootstrap legitimately lives in `ZB.MOM.WW.ScadaBridge.Host` (CI-001 resolution); still nothing actor-related in this module. No issues. |
|
||||
| 3 | Concurrency & thread safety | ✓ | No shared mutable state, no actors, no async code. No issues found in current code. **Re-review:** validator and DI extensions are stateless; no issues. |
|
||||
| 4 | Error handling & resilience | ✓ | Failover, split-brain, dual-node recovery, and graceful-shutdown logic are entirely absent (CI-001). No exception paths to review in current code. **Re-review:** the validator now fails fast on misconfiguration. New — it does not enforce the design doc's `down-if-alone = on` requirement (CI-010). |
|
||||
| 5 | Security | ✓ | No authn/authz surface in this module. Akka remoting is unconfigured, so transport security cannot be assessed; flagged as part of the missing implementation (CI-001). No secret handling present. **Re-review:** still no authn/authz surface, no secret handling. No issues. |
|
||||
@@ -124,7 +124,7 @@ _Re-review (2026-05-28, `1eb6e97`):_
|
||||
| Severity | High |
|
||||
| Category | Design-document adherence |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ClusterInfrastructure/ServiceCollectionExtensions.cs:9`, `src/ScadaLink.ClusterInfrastructure/ServiceCollectionExtensions.cs:16` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure/ServiceCollectionExtensions.cs:9`, `src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure/ServiceCollectionExtensions.cs:16` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -138,8 +138,8 @@ and a `ServiceCollectionExtensions` whose methods are explicitly commented
|
||||
and simply return the unmodified `IServiceCollection`. There is no `Akka.Cluster`,
|
||||
`Akka.Cluster.Tools`, `Akka.Remote`, or split-brain-resolver dependency in the
|
||||
`.csproj` at all (it references only `Microsoft.Extensions.DependencyInjection.Abstractions`,
|
||||
`Microsoft.Extensions.Options`, and `ScadaLink.Commons`). Because every other
|
||||
ScadaLink component runs inside the actor system this module is responsible for
|
||||
`Microsoft.Extensions.Options`, and `ZB.MOM.WW.ScadaBridge.Commons`). Because every other
|
||||
ScadaBridge component runs inside the actor system this module is responsible for
|
||||
creating, the absence of any implementation blocks the foundational layer of the
|
||||
system.
|
||||
|
||||
@@ -157,19 +157,19 @@ should clearly state it is unimplemented so callers do not assume otherwise.
|
||||
_Re-triaged 2026-05-16 — remains Open, needs a design decision from the user._
|
||||
|
||||
Verified against the source at the reviewed commit: the finding's factual claims hold.
|
||||
`src/ScadaLink.ClusterInfrastructure` still contains only `ClusterOptions.cs` and a
|
||||
`src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure` still contains only `ClusterOptions.cs` and a
|
||||
no-op `ServiceCollectionExtensions.cs`, and the `.csproj` references no Akka packages.
|
||||
|
||||
However, the documented cluster behaviour is **not actually absent from the system** —
|
||||
it has been implemented in the **Host** project rather than in this module:
|
||||
|
||||
- `src/ScadaLink.Host/Actors/AkkaHostedService.cs` bootstraps the `ActorSystem`,
|
||||
generates the HOCON from `ClusterOptions` (it imports `ScadaLink.ClusterInfrastructure`
|
||||
- `src/ZB.MOM.WW.ScadaBridge.Host/Actors/AkkaHostedService.cs` bootstraps the `ActorSystem`,
|
||||
generates the HOCON from `ClusterOptions` (it imports `ZB.MOM.WW.ScadaBridge.ClusterInfrastructure`
|
||||
and injects `IOptions<ClusterOptions>`), and configures the `keep-oldest` split-brain
|
||||
resolver with `down-if-alone = on` (see `AkkaHostedService.cs:95-96`).
|
||||
- `src/ScadaLink.Host/Health/AkkaClusterHealthCheck.cs`, `AkkaClusterNodeProvider.cs`,
|
||||
- `src/ZB.MOM.WW.ScadaBridge.Host/Health/AkkaClusterHealthCheck.cs`, `AkkaClusterNodeProvider.cs`,
|
||||
and `Health/ActiveNodeHealthCheck.cs` cover cluster membership / active-node detection.
|
||||
- Akka cluster/remote package references live in `ScadaLink.Host.csproj` and the
|
||||
- Akka cluster/remote package references live in `ZB.MOM.WW.ScadaBridge.Host.csproj` and the
|
||||
per-component projects (`SiteRuntime`, `Communication`, etc.).
|
||||
|
||||
So the real situation is an **ownership / design-doc drift**, not missing behaviour:
|
||||
@@ -183,11 +183,11 @@ of two substantial decisions, both requiring the user:
|
||||
|
||||
1. **Move the bootstrap into this module** — relocate the HOCON generation, split-brain
|
||||
config, cluster-singleton helpers and `CoordinatedShutdown` wiring out of
|
||||
`ScadaLink.Host` into `ScadaLink.ClusterInfrastructure`, add the Akka package
|
||||
`ZB.MOM.WW.ScadaBridge.Host` into `ZB.MOM.WW.ScadaBridge.ClusterInfrastructure`, add the Akka package
|
||||
references, and re-wire the Host to call into it. This is a cross-module refactor
|
||||
touching `src/ScadaLink.Host/*` and several other projects — outside the edit scope
|
||||
permitted for this finding (only `src/ScadaLink.ClusterInfrastructure/`,
|
||||
`tests/ScadaLink.ClusterInfrastructure.Tests/`, and this file may be edited).
|
||||
touching `src/ZB.MOM.WW.ScadaBridge.Host/*` and several other projects — outside the edit scope
|
||||
permitted for this finding (only `src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure/`,
|
||||
`tests/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure.Tests/`, and this file may be edited).
|
||||
2. **Accept the current placement** — keep the bootstrap in the Host and update
|
||||
`Component-ClusterInfrastructure.md` (and the README component table) to record that
|
||||
the Host owns the actor-system/cluster bootstrap and that this module's role is the
|
||||
@@ -202,8 +202,8 @@ bring-up), and the design docs are corrected to record the true ownership.
|
||||
**Resolved** — fixing commit `<pending>`, date 2026-05-16. The finding was a design-doc
|
||||
drift, not missing behaviour. `docs/requirements/Component-ClusterInfrastructure.md` now
|
||||
carries an "Implementation Note — Code Placement" section stating that the
|
||||
`ScadaLink.ClusterInfrastructure` project owns the `ClusterOptions` configuration model
|
||||
while `ScadaLink.Host` owns the Akka bootstrap, HOCON generation, split-brain-resolver
|
||||
`ZB.MOM.WW.ScadaBridge.ClusterInfrastructure` project owns the `ClusterOptions` configuration model
|
||||
while `ZB.MOM.WW.ScadaBridge.Host` owns the Akka bootstrap, HOCON generation, split-brain-resolver
|
||||
wiring, `CoordinatedShutdown` integration, and active-node health checks. The README
|
||||
component table (row 13) was updated to match. No code change was required — the
|
||||
documented cluster behaviour already exists and is exercised; only the doc's
|
||||
@@ -216,7 +216,7 @@ module-ownership claim was wrong. Module test suite green (3 passed).
|
||||
| Severity | Medium |
|
||||
| Category | Correctness & logic bugs |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ClusterInfrastructure/ServiceCollectionExtensions.cs:7-17` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure/ServiceCollectionExtensions.cs:7-17` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -239,17 +239,17 @@ with the genuine registration when CI-001 is addressed.
|
||||
**Resolution**
|
||||
|
||||
Confirmed against the source: both methods returned the `IServiceCollection`
|
||||
unchanged. Verified the consumers — `ScadaLink.Host` calls `AddClusterInfrastructure()`
|
||||
unchanged. Verified the consumers — `ZB.MOM.WW.ScadaBridge.Host` calls `AddClusterInfrastructure()`
|
||||
(`Program.cs:68`, `SiteServiceRegistration.cs:24`); `AddClusterInfrastructureActors`
|
||||
is dead — it is called nowhere in the solution.
|
||||
|
||||
**Resolved** — fixing commit `commit pending`, date 2026-05-16.
|
||||
`AddClusterInfrastructure` now does real work: it registers the
|
||||
`ClusterOptionsValidator` (CI-004) via `TryAddEnumerable`, so the method is no longer a
|
||||
no-op and a misconfigured `ScadaLink:Cluster` section fails fast on the first
|
||||
no-op and a misconfigured `ScadaBridge:Cluster` section fails fast on the first
|
||||
`IOptions<ClusterOptions>` resolution. `AddClusterInfrastructureActors` — which this
|
||||
component never had any actors to register, as CI-001 established the Akka bootstrap
|
||||
lives in `ScadaLink.Host` — now throws `NotImplementedException` with a message
|
||||
lives in `ZB.MOM.WW.ScadaBridge.Host` — now throws `NotImplementedException` with a message
|
||||
pointing the caller to the Host, rather than masquerading as a completed registration.
|
||||
Covered by `ServiceCollectionExtensionsTests`
|
||||
(`AddClusterInfrastructure_RegistersOptionsValidator`,
|
||||
@@ -263,7 +263,7 @@ Covered by `ServiceCollectionExtensionsTests`
|
||||
| Severity | Medium |
|
||||
| Category | Design-document adherence |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ClusterInfrastructure/ClusterOptions.cs:3-11` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure/ClusterOptions.cs:3-11` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -290,7 +290,7 @@ agree on where each value lives.
|
||||
**Resolution**
|
||||
|
||||
Partially re-triaged. Verified against the source: most of the "missing" settings are
|
||||
**deliberately owned by `ScadaLink.Host.NodeOptions`** — `NodeOptions` already carries
|
||||
**deliberately owned by `ZB.MOM.WW.ScadaBridge.Host.NodeOptions`** — `NodeOptions` already carries
|
||||
`Role`, `NodeHostname`, `SiteId`, `RemotingPort` and `GrpcPort`, and `AkkaHostedService`
|
||||
builds the HOCON from `NodeOptions` for exactly those values. Local SQLite storage paths
|
||||
live in the database / store-and-forward options. This is the ownership split CI-001
|
||||
@@ -307,7 +307,7 @@ deliberate ownership split — node identity/remoting/gRPC in `Host.NodeOptions`
|
||||
paths in the database options, cluster-formation settings here — so the design doc and
|
||||
the options classes now agree on where each value lives. (`AkkaHostedService` currently
|
||||
hard-codes `down-if-alone = on` in HOCON; wiring it to read `DownIfAlone` is a one-line
|
||||
`ScadaLink.Host` change, outside this module's permitted edit scope, and is noted for
|
||||
`ZB.MOM.WW.ScadaBridge.Host` change, outside this module's permitted edit scope, and is noted for
|
||||
the Host's review.) Covered by `ClusterOptionsTests.DefaultValues_AreCorrect` and
|
||||
`ClusterOptionsTests.DownIfAlone_CanBeSet`.
|
||||
|
||||
@@ -318,7 +318,7 @@ the Host's review.) Covered by `ClusterOptionsTests.DefaultValues_AreCorrect` an
|
||||
| Severity | Medium |
|
||||
| Category | Code organization & conventions |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ClusterInfrastructure/ClusterOptions.cs:3-11` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure/ClusterOptions.cs:3-11` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -374,7 +374,7 @@ attributes cannot. Covered by `ClusterOptionsValidatorTests` (8 cases) and
|
||||
| Severity | Low |
|
||||
| Category | Code organization & conventions |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ClusterInfrastructure/ClusterOptions.cs:3` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure/ClusterOptions.cs:3` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -398,9 +398,9 @@ Confirmed against the source: `ClusterOptions` previously exposed no section-nam
|
||||
constant, leaving binding sites to hard-code the magic string.
|
||||
|
||||
**Resolved** — fixing commit `commit pending`, date 2026-05-16. `ClusterOptions` now
|
||||
exposes `public const string SectionName = "ScadaLink:Cluster";` as the single source
|
||||
exposes `public const string SectionName = "ScadaBridge:Cluster";` as the single source
|
||||
of truth for the `appsettings.json` section name, with an XML doc explaining its
|
||||
purpose. The chosen value matches the `ScadaLink:`-prefixed section convention used by
|
||||
purpose. The chosen value matches the `ScadaBridge:`-prefixed section convention used by
|
||||
peer option classes and referenced by `ClusterOptionsValidator` / the design doc.
|
||||
Covered by `ClusterOptionsTests.SectionName_IsTheExpectedAppSettingsSection`, which
|
||||
both pins the value and — by referencing the constant — guards against its removal
|
||||
@@ -413,7 +413,7 @@ both pins the value and — by referencing the constant — guards against its r
|
||||
| Severity | Medium |
|
||||
| Category | Testing coverage |
|
||||
| Status | Resolved |
|
||||
| Location | `tests/ScadaLink.ClusterInfrastructure.Tests/ClusterOptionsTests.cs:1-51` |
|
||||
| Location | `tests/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure.Tests/ClusterOptionsTests.cs:1-51` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -438,14 +438,14 @@ from `ClusterOptions` and for the options validation from CI-004.
|
||||
**Resolution**
|
||||
|
||||
Re-triaged in light of CI-001's resolution. The Akka bootstrap, HOCON generation,
|
||||
cluster formation, failover and singleton handover are owned by `ScadaLink.Host`, not
|
||||
cluster formation, failover and singleton handover are owned by `ZB.MOM.WW.ScadaBridge.Host`, not
|
||||
this project — multi-node `Akka.Cluster.TestKit` tests for that behaviour belong in the
|
||||
Host's test suite, outside this module's scope. What this module legitimately owns is
|
||||
`ClusterOptions`, its validator, and the DI registration, and the testing gap there is
|
||||
now closed.
|
||||
|
||||
**Resolved** — fixing commit `commit pending`, date 2026-05-16. Added two test classes
|
||||
to `tests/ScadaLink.ClusterInfrastructure.Tests`: `ClusterOptionsValidatorTests`
|
||||
to `tests/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure.Tests`: `ClusterOptionsValidatorTests`
|
||||
(8 cases — valid defaults pass; `MinNrOfMembers != 1`, unsupported split-brain
|
||||
strategies, empty seed nodes, heartbeat not below the failure threshold, non-positive
|
||||
`StableAfter` all fail; and a multi-failure accumulation case) and
|
||||
@@ -467,7 +467,7 @@ design); `ClusterOptionsValidator` is the layer that now rejects `keep-majority`
|
||||
| Severity | Low |
|
||||
| Category | Documentation & comments |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ClusterInfrastructure/ClusterOptions.cs:3-11` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure/ClusterOptions.cs:3-11` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -510,7 +510,7 @@ inspection of `ClusterOptions.cs:3-74`. Module test suite green (17 passed).
|
||||
| Severity | Low |
|
||||
| Category | Documentation & comments |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ClusterInfrastructure/ServiceCollectionExtensions.cs:9`, `src/ScadaLink.ClusterInfrastructure/ServiceCollectionExtensions.cs:16` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure/ServiceCollectionExtensions.cs:9`, `src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure/ServiceCollectionExtensions.cs:16` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -540,17 +540,17 @@ complete-looking design doc with no caveat. That premise has been overtaken by t
|
||||
CI-001/CI-002 work:
|
||||
|
||||
- The "Phase 0 skeleton" comments no longer exist anywhere in
|
||||
`src/ScadaLink.ClusterInfrastructure` (verified by `grep`). `ServiceCollectionExtensions`
|
||||
`src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure` (verified by `grep`). `ServiceCollectionExtensions`
|
||||
now does real work (registers `ClusterOptionsValidator`) and `AddClusterInfrastructureActors`
|
||||
throws explicitly — both with accurate XML docs explaining the ownership split.
|
||||
- The module is no longer an unimplemented skeleton. CI-001 established that the Akka
|
||||
bootstrap legitimately lives in `ScadaLink.Host`, and this project's true scope —
|
||||
bootstrap legitimately lives in `ZB.MOM.WW.ScadaBridge.Host`, and this project's true scope —
|
||||
the `ClusterOptions` configuration contract, its validator, and DI registration — is
|
||||
fully implemented and tested.
|
||||
- The design doc `Component-ClusterInfrastructure.md` now opens with an
|
||||
"Implementation Note — Code Placement" section (added by CI-001) that explicitly
|
||||
states the component is a *design responsibility* realised across
|
||||
`ScadaLink.ClusterInfrastructure` (configuration model) and `ScadaLink.Host`
|
||||
`ZB.MOM.WW.ScadaBridge.ClusterInfrastructure` (configuration model) and `ZB.MOM.WW.ScadaBridge.Host`
|
||||
(bootstrap/runtime wiring), and the README component table (row 13) was updated to
|
||||
match. A reader of the design doc no longer assumes a single fully-built project.
|
||||
|
||||
@@ -569,7 +569,7 @@ inspection of `ServiceCollectionExtensions.cs` and
|
||||
| Severity | Medium |
|
||||
| Category | Design-document adherence |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ClusterInfrastructure/ClusterOptions.cs:74` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure/ClusterOptions.cs:74` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -577,7 +577,7 @@ The `DownIfAlone` property was added to `ClusterOptions` by CI-003's resolution
|
||||
part of "the split-brain configuration contract". It is public, defaults to `true`,
|
||||
carries an XML doc presenting it as "the design-doc requirement", and is exercised by
|
||||
`ClusterOptionsTests.DownIfAlone_CanBeSet`. However, nothing in the system reads it.
|
||||
The Akka.NET HOCON is generated by `ScadaLink.Host.Actors.AkkaHostedService.BuildHocon`,
|
||||
The Akka.NET HOCON is generated by `ZB.MOM.WW.ScadaBridge.Host.Actors.AkkaHostedService.BuildHocon`,
|
||||
which **hard-codes** the resolver setting:
|
||||
|
||||
```
|
||||
@@ -596,7 +596,7 @@ field (`SeedNodes`, `MinNrOfMembers`, `SplitBrainResolverStrategy`, `StableAfter
|
||||
The result is a configuration property that an operator can set in `appsettings.json`,
|
||||
that passes validation, and that has **zero runtime effect** — setting
|
||||
`DownIfAlone: false` does not turn the flag off. CI-003's resolution explicitly
|
||||
acknowledged this gap ("wiring it to read `DownIfAlone` is a one-line `ScadaLink.Host`
|
||||
acknowledged this gap ("wiring it to read `DownIfAlone` is a one-line `ZB.MOM.WW.ScadaBridge.Host`
|
||||
change ... noted for the Host's review") but the wiring was never done and no tracked
|
||||
finding carried it, so the gap has silently persisted to commit `39d737e`. An inert,
|
||||
misleadingly-documented configuration knob is a correctness and design-adherence
|
||||
@@ -616,13 +616,13 @@ controls nothing.
|
||||
**Resolution**
|
||||
|
||||
Root cause verified against the source at commit `39d737e`:
|
||||
`src/ScadaLink.Host/Actors/AkkaHostedService.cs:147` hard-codes `down-if-alone = on`
|
||||
`src/ZB.MOM.WW.ScadaBridge.Host/Actors/AkkaHostedService.cs:147` hard-codes `down-if-alone = on`
|
||||
inside the `keep-oldest` block, and `BuildHocon` consumes every other `ClusterOptions`
|
||||
field but never reads `clusterOptions.DownIfAlone`. The finding's facts hold. The fix
|
||||
is correctly scoped to the **Host** module — the configuration property and its
|
||||
validation legitimately live in `ScadaLink.ClusterInfrastructure` (this module),
|
||||
validation legitimately live in `ZB.MOM.WW.ScadaBridge.ClusterInfrastructure` (this module),
|
||||
and the per-CLAUDE.md ownership split (CI-001) places HOCON generation in
|
||||
`ScadaLink.Host`.
|
||||
`ZB.MOM.WW.ScadaBridge.Host`.
|
||||
|
||||
**Resolved** — by cross-reference, date 2026-05-17. No code change in this module:
|
||||
`ClusterOptions.DownIfAlone` and its validation (CI-010) are correct and complete here.
|
||||
@@ -638,7 +638,7 @@ green (18 passed).
|
||||
| Severity | Low |
|
||||
| Category | Error handling & resilience |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ClusterInfrastructure/ClusterOptionsValidator.cs:21-71` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure/ClusterOptionsValidator.cs:21-71` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -688,27 +688,27 @@ confirmed failing, then passing after the fix. Module test suite green (18 passe
|
||||
| Severity | Low |
|
||||
| Category | Code organization & conventions |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ClusterInfrastructure/ClusterOptions.cs:24-27`, `src/ScadaLink.Host/SiteServiceRegistration.cs:100`, `src/ScadaLink.Host/StartupValidator.cs:43`, `src/ScadaLink.Host/StartupValidator.cs:75` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure/ClusterOptions.cs:24-27`, `src/ZB.MOM.WW.ScadaBridge.Host/SiteServiceRegistration.cs:100`, `src/ZB.MOM.WW.ScadaBridge.Host/StartupValidator.cs:43`, `src/ZB.MOM.WW.ScadaBridge.Host/StartupValidator.cs:75` |
|
||||
|
||||
**Resolution (2026-05-28):** Took option (b) since wiring the constant into the Host's `SiteServiceRegistration.BindSharedOptions` / `StartupValidator` is outside this module's editable surface — deleted the `SectionName` constant from `ClusterOptions.cs` and the companion `SectionName_IsTheExpectedAppSettingsSection` test from `ClusterOptionsTests.cs`. The Host's `"ScadaLink:Cluster"` literals now stand alone (consistent with the implementation rather than the broken "single source of truth" claim). A code-comment placeholder records the rationale so a future Host-side change can reinstate the constant alongside the binding-site updates.
|
||||
**Resolution (2026-05-28):** Took option (b) since wiring the constant into the Host's `SiteServiceRegistration.BindSharedOptions` / `StartupValidator` is outside this module's editable surface — deleted the `SectionName` constant from `ClusterOptions.cs` and the companion `SectionName_IsTheExpectedAppSettingsSection` test from `ClusterOptionsTests.cs`. The Host's `"ScadaBridge:Cluster"` literals now stand alone (consistent with the implementation rather than the broken "single source of truth" claim). A code-comment placeholder records the rationale so a future Host-side change can reinstate the constant alongside the binding-site updates.
|
||||
|
||||
**Description**
|
||||
|
||||
`ClusterOptions.SectionName` was added by CI-005 as `public const string SectionName =
|
||||
"ScadaLink:Cluster";`, with an XML doc declaring it "the single source of truth so
|
||||
"ScadaBridge:Cluster";`, with an XML doc declaring it "the single source of truth so
|
||||
binding sites do not hard-code the magic string". CI-005's resolution likewise framed
|
||||
the constant as the canonical reference value. In practice, **no caller in the
|
||||
solution reads it**. `grep -rn "ClusterOptions.SectionName" src/` returns zero hits.
|
||||
Every site that needs the section name hard-codes the literal:
|
||||
|
||||
- `ScadaLink.Host.SiteServiceRegistration.BindSharedOptions:100` —
|
||||
`services.Configure<ClusterOptions>(config.GetSection("ScadaLink:Cluster"));`
|
||||
- `ScadaLink.Host.StartupValidator:43,45,75` — three `"ScadaLink:Cluster"` /
|
||||
`"ScadaLink:Cluster:SeedNodes"` literals.
|
||||
- `ZB.MOM.WW.ScadaBridge.Host.SiteServiceRegistration.BindSharedOptions:100` —
|
||||
`services.Configure<ClusterOptions>(config.GetSection("ScadaBridge:Cluster"));`
|
||||
- `ZB.MOM.WW.ScadaBridge.Host.StartupValidator:43,45,75` — three `"ScadaBridge:Cluster"` /
|
||||
`"ScadaBridge:Cluster:SeedNodes"` literals.
|
||||
|
||||
The `SectionName_IsTheExpectedAppSettingsSection` test pins the constant's value but
|
||||
does not protect against the underlying drift hazard: if someone changes
|
||||
`SectionName` to `"ScadaLink:Akka:Cluster"`, the test still passes (because it tests
|
||||
`SectionName` to `"ScadaBridge:Akka:Cluster"`, the test still passes (because it tests
|
||||
the constant against the same literal), the validator still registers, and binding
|
||||
silently goes to whichever string the Host hard-codes. The constant currently
|
||||
provides none of the safety its XML doc claims. This is the same pattern of "inert
|
||||
@@ -717,7 +717,7 @@ configuration drift rather than runtime behaviour.
|
||||
|
||||
**Recommendation**
|
||||
|
||||
Either (a) replace the hard-coded `"ScadaLink:Cluster"` literals in
|
||||
Either (a) replace the hard-coded `"ScadaBridge:Cluster"` literals in
|
||||
`SiteServiceRegistration.cs:100` and `StartupValidator.cs:43,45,75` with
|
||||
`ClusterOptions.SectionName` (a small Host-module change, to be tracked there), or
|
||||
(b) if the constant is intentionally decorative, soften the XML doc so it does not
|
||||
@@ -731,7 +731,7 @@ guarantee the code does not deliver.
|
||||
| Severity | Low |
|
||||
| Category | Design-document adherence |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ClusterInfrastructure/ClusterOptionsValidator.cs:30-43` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure/ClusterOptionsValidator.cs:30-43` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -741,18 +741,18 @@ guarantee the code does not deliver.
|
||||
> its partner. Either node can start first and form the cluster; the other joins when
|
||||
> it starts. No startup ordering dependency.
|
||||
|
||||
A correctly-configured ScadaLink deployment therefore lists **two** seed nodes.
|
||||
A correctly-configured ScadaBridge deployment therefore lists **two** seed nodes.
|
||||
`ClusterOptionsValidator.Validate` only checks that `SeedNodes` is non-null and
|
||||
non-empty (`Count == 0`). A configuration with a single seed node passes validation
|
||||
silently — but that defeats the "no startup ordering dependency" guarantee the
|
||||
design doc explicitly calls out.
|
||||
|
||||
`ScadaLink.Host.StartupValidator:43-46` does enforce the rule:
|
||||
`ZB.MOM.WW.ScadaBridge.Host.StartupValidator:43-46` does enforce the rule:
|
||||
|
||||
```csharp
|
||||
var seedNodes = configuration.GetSection("ScadaLink:Cluster:SeedNodes").Get<List<string>>();
|
||||
var seedNodes = configuration.GetSection("ScadaBridge:Cluster:SeedNodes").Get<List<string>>();
|
||||
if (seedNodes is null || seedNodes.Count < 2)
|
||||
errors.Add("ScadaLink:Cluster:SeedNodes must have at least 2 entries");
|
||||
errors.Add("ScadaBridge:Cluster:SeedNodes must have at least 2 entries");
|
||||
```
|
||||
|
||||
So the rule is enforced — but by the **other** project, after the
|
||||
@@ -789,7 +789,7 @@ ClusterInfrastructure.Tests).
|
||||
| Severity | Low |
|
||||
| Category | Documentation & comments |
|
||||
| Status | Resolved |
|
||||
| Location | `tests/ScadaLink.ClusterInfrastructure.Tests/ClusterOptionsTests.cs:47-67` |
|
||||
| Location | `tests/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure.Tests/ClusterOptionsTests.cs:47-67` |
|
||||
|
||||
**Resolution (2026-05-28):** Added a 10-line inline `// ClusterInfra-013: ...` block at the top of `Properties_CanBeSetToCustomValues` explicitly recording that this test exercises the POCO property setters only — the `keep-majority` strategy and `MinNrOfMembers = 2` values are explicitly forbidden in production by `ClusterOptionsValidator`, and the comment cross-references `UnsupportedSplitBrainStrategy_FailsValidation` and `MinNrOfMembers_NotOne_FailsValidation` so a future reader cannot misread the test as endorsing those values.
|
||||
|
||||
@@ -828,7 +828,7 @@ goal is to make the test's intent self-documenting.
|
||||
| Severity | Low |
|
||||
| Category | Code organization & conventions |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ClusterInfrastructure/ServiceCollectionExtensions.cs:42-48` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure/ServiceCollectionExtensions.cs:42-48` |
|
||||
|
||||
**Resolution (2026-05-28):** Deleted the `AddClusterInfrastructureActors` extension method from `ServiceCollectionExtensions.cs` and its companion `AddClusterInfrastructureActors_ThrowsRatherThanSilentlySucceeding` test from `ServiceCollectionExtensionsTests.cs`. Verified no production caller existed before deletion via `grep -rn`. A code comment records the rationale (CI-001 ownership question now permanently settled; method served only to throw and was IDE-auto-complete noise). The class-level XML doc on the test file was updated to drop the stale reference to the removed test.
|
||||
|
||||
@@ -840,7 +840,7 @@ and a body that unconditionally throws `NotImplementedException`. CI-002's resol
|
||||
chose "throw loudly" over "delete" specifically because CI-001 was still resolving the
|
||||
ownership-split question. That question is settled — the design doc, the README
|
||||
component table, and `Component-ClusterInfrastructure.md`'s "Implementation Note — Code
|
||||
Placement" all permanently locate the Akka actor bootstrap in `ScadaLink.Host`.
|
||||
Placement" all permanently locate the Akka actor bootstrap in `ZB.MOM.WW.ScadaBridge.Host`.
|
||||
|
||||
A `grep -rn "AddClusterInfrastructureActors" src/ tests/` confirms there is no caller
|
||||
anywhere in the solution. The method's only consumer is its own test
|
||||
@@ -854,6 +854,6 @@ expecting it to register something), and gives nothing in return.
|
||||
Delete `AddClusterInfrastructureActors`, delete its test, and add a one-line note to
|
||||
`docs/requirements/Component-ClusterInfrastructure.md`'s code-placement section
|
||||
explicitly stating that this project exposes no actor-registration extension
|
||||
(actor wiring lives in `ScadaLink.Host`). If the user prefers to keep the
|
||||
(actor wiring lives in `ZB.MOM.WW.ScadaBridge.Host`). If the user prefers to keep the
|
||||
"fail-fast" trap, mark the method `[Obsolete(true, error: true)]` so the compiler —
|
||||
not the runtime — rejects the call.
|
||||
|
||||
Reference in New Issue
Block a user