code-review: 2026-05-28 baseline re-review of all 23 modules at 1eb6e97
Re-applies the full 10-category checklist to every src/ project — including
first-time reviews of the four newer components (AuditLog, NotificationOutbox,
SiteCallAudit, Transport) — so the code-reviews/ index reflects today's
codebase rather than the 2026-05-16 baseline. 172 new Open findings (0
Critical, 18 High, 62 Medium, 92 Low); 481 findings total across 23 modules.
regen-readme.py now derives each module's Last reviewed + Commit from its
findings.md header instead of hard-coding 2026-05-16 / 9c60592, so future
single-module re-reviews show their own date in the Module Status table.
This commit is contained in:
@@ -5,10 +5,10 @@
|
||||
| Module | `src/ScadaLink.ClusterInfrastructure` |
|
||||
| Design doc | `docs/requirements/Component-ClusterInfrastructure.md` |
|
||||
| Status | Reviewed |
|
||||
| Last reviewed | 2026-05-17 |
|
||||
| Last reviewed | 2026-05-28 |
|
||||
| Reviewer | claude-agent |
|
||||
| Commit reviewed | `39d737e` |
|
||||
| Open findings | 0 |
|
||||
| Commit reviewed | `1eb6e97` |
|
||||
| Open findings | 4 |
|
||||
|
||||
## Summary
|
||||
|
||||
@@ -45,6 +45,43 @@ part of the configuration contract but is never consumed — `ScadaLink.Host`'s
|
||||
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).
|
||||
|
||||
#### Re-review 2026-05-28 (commit `1eb6e97`)
|
||||
|
||||
The only change to this module between `39d737e` and `1eb6e97` is the
|
||||
documentation-only commit `1eb6e97` itself, which added a handful of `<param>`
|
||||
XML doc tags to `ClusterOptionsValidator.Validate` and to
|
||||
`AddClusterInfrastructureActors` — no source-of-truth changes. Walked all three
|
||||
source files and all three test files against the full 10-category checklist
|
||||
again. Found **four new issues**, all Low severity, that the prior re-review
|
||||
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
|
||||
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
|
||||
nodes" (a properly-configured deployment lists 2). `Host.StartupValidator:45`
|
||||
already enforces `>= 2`, so this module's own contract validator is the
|
||||
weaker of the two. Inconsistent enforcement across the two projects that
|
||||
share ownership of the cluster contract.
|
||||
- **CI-013 (Low, Documentation & comments)** — `ClusterOptionsTests
|
||||
.Properties_CanBeSetToCustomValues` deliberately sets
|
||||
`SplitBrainResolverStrategy = "keep-majority"` and `MinNrOfMembers = 2` — the
|
||||
exact values the design doc warns are catastrophic. The CI-006 resolution
|
||||
acknowledged this is intentional (testing the POCO accepts any value; the
|
||||
validator does the rejecting) but the test has no inline comment saying so,
|
||||
and a future reader could easily misinterpret it as endorsing those values.
|
||||
- **CI-014 (Low, Code organization)** — `AddClusterInfrastructureActors` is
|
||||
dead surface: no caller exists anywhere in the solution (verified via
|
||||
`grep -rn`), its XML doc instructs callers "do not call", and its body
|
||||
unconditionally throws. CI-002's resolution chose "fail loudly" over "delete"
|
||||
but the method now offers nothing — keeping it is API-surface noise that an
|
||||
IDE will still suggest via auto-complete.
|
||||
|
||||
## Checklist coverage
|
||||
|
||||
Original review (2026-05-16, `9c60592`) below; the re-review notes (2026-05-17,
|
||||
@@ -63,6 +100,21 @@ Original review (2026-05-16, `9c60592`) below; the re-review notes (2026-05-17,
|
||||
| 9 | Testing coverage | ✓ | `ClusterOptionsTests` covers defaults and setters. No tests for any cluster behaviour because none exists; the test project references nothing else (CI-006). **Re-review:** CI-006 resolved — 16 tests across three classes covering options, validator, and DI registration. No `DownIfAlone`-wiring test exists, but that wiring lives in the Host (CI-009). No new issue here. |
|
||||
| 10 | Documentation & comments | ✓ | `ClusterOptions` has no XML doc comments unlike peer options classes (CI-007). The "Phase 0 skeleton" placeholders are undocumented at the module level — no README or tracking note (CI-008). **Re-review:** CI-007/CI-008 resolved — full XML docs on all members; skeleton comments gone. Note: the `DownIfAlone` XML doc calls `true` "the design-doc requirement" yet the value is inert (CI-009) and unenforced (CI-010). |
|
||||
|
||||
_Re-review (2026-05-28, `1eb6e97`):_
|
||||
|
||||
| # | Category | Examined | Notes |
|
||||
|---|----------|----------|-------|
|
||||
| 1 | Correctness & logic bugs | ✓ | Validator logic and DI registration are correct. No new defects. |
|
||||
| 2 | Akka.NET conventions | ✓ | No actors in this module (legitimate, per CI-001 resolution). Nothing actor-shaped to evaluate. |
|
||||
| 3 | Concurrency & thread safety | ✓ | Validator and DI extensions remain stateless. No issues. |
|
||||
| 4 | Error handling & resilience | ✓ | Validator now rejects every catastrophic value the design doc enumerates. New — it accepts `SeedNodes.Count == 1` even though the design doc requires both nodes as seeds, and `Host.StartupValidator` enforces `>= 2`, so the module's own validator is the weaker check (CI-012). |
|
||||
| 5 | Security | ✓ | No authn/authz surface, no secret handling, no remoting transport configured here. No issues. |
|
||||
| 6 | Performance & resource management | ✓ | No resources held; validator allocates a small failure list per call only. No issues. |
|
||||
| 7 | Design-document adherence | ✓ | `ClusterOptions` contract complete and validated. New — validator's seed-node count check is weaker than the design (CI-012). |
|
||||
| 8 | Code organization & conventions | ✓ | Options/validator placement and Options pattern correct. New — `SectionName` constant documented as "single source of truth" but never read by any binding site (CI-011); `AddClusterInfrastructureActors` is dead surface that no caller invokes (CI-014). |
|
||||
| 9 | Testing coverage | ✓ | 16 tests across three classes. New — `ClusterOptionsTests.Properties_CanBeSetToCustomValues` sets the exact catastrophic values the design doc forbids without an inline comment explaining why (CI-013). |
|
||||
| 10 | Documentation & comments | ✓ | XML docs accurate across all source files (commit `1eb6e97` filled in the remaining `<param>` tags). New — CI-013 (test lacks intent comment); CI-011 (XML doc for `SectionName` claims a property the code does not deliver). |
|
||||
|
||||
## Findings
|
||||
|
||||
### ClusterInfrastructure-001 — Module implements none of its documented responsibilities
|
||||
@@ -628,3 +680,181 @@ message explaining the isolated-single-node-cluster hazard, consistent with how
|
||||
validator already rejects quorum split-brain strategies. Developed test-first:
|
||||
`ClusterOptionsValidatorTests.DownIfAloneFalse_FailsValidation` was written first,
|
||||
confirmed failing, then passing after the fix. Module test suite green (18 passed).
|
||||
|
||||
### ClusterInfrastructure-011 — `SectionName` constant is decorative — no binding site references it
|
||||
|
||||
| | |
|
||||
|--|--|
|
||||
| Severity | Low |
|
||||
| Category | Code organization & conventions |
|
||||
| Status | Open |
|
||||
| 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:45`, `src/ScadaLink.Host/StartupValidator.cs:75` |
|
||||
|
||||
**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
|
||||
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.
|
||||
|
||||
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
|
||||
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
|
||||
configuration knob" CI-009 flagged for `DownIfAlone`, just with the harm being
|
||||
configuration drift rather than runtime behaviour.
|
||||
|
||||
**Recommendation**
|
||||
|
||||
Either (a) replace the hard-coded `"ScadaLink: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
|
||||
claim to be the source of truth. Do not leave a public constant whose stated
|
||||
guarantee the code does not deliver.
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Open — needs a one-line Host-side change to reference the constant, plus a test
|
||||
that proves the section name flows from this module to the Host._
|
||||
|
||||
### ClusterInfrastructure-012 — Validator accepts `SeedNodes.Count == 1` despite design requiring both nodes as seeds
|
||||
|
||||
| | |
|
||||
|--|--|
|
||||
| Severity | Low |
|
||||
| Category | Design-document adherence |
|
||||
| Status | Open |
|
||||
| Location | `src/ScadaLink.ClusterInfrastructure/ClusterOptionsValidator.cs:30-33` |
|
||||
|
||||
**Description**
|
||||
|
||||
`Component-ClusterInfrastructure.md` (Node Configuration) states:
|
||||
|
||||
> Cluster seed nodes: **Both nodes** are seed nodes — each node lists both itself and
|
||||
> 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.
|
||||
`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:
|
||||
|
||||
```csharp
|
||||
var seedNodes = configuration.GetSection("ScadaLink:Cluster:SeedNodes").Get<List<string>>();
|
||||
if (seedNodes is null || seedNodes.Count < 2)
|
||||
errors.Add("ScadaLink:Cluster:SeedNodes must have at least 2 entries");
|
||||
```
|
||||
|
||||
So the rule is enforced — but by the **other** project, after the
|
||||
`ClusterOptionsValidator` (the contract owner) already accepted the value. This is
|
||||
both inconsistent (two validators with different rules for the same field) and the
|
||||
weaker check is the contract-owner's. The pre-existing test
|
||||
`ServiceCollectionExtensionsTests.AddClusterInfrastructure_ValidatorRejectsBadOptionsAtResolution`
|
||||
even constructs a `SeedNodes` list with one entry and expects validation to succeed
|
||||
on that count — locking in the gap.
|
||||
|
||||
**Recommendation**
|
||||
|
||||
Tighten the validator: require `SeedNodes.Count >= 2` with a message that references
|
||||
the "both nodes are seed nodes" design rule. Update
|
||||
`AddClusterInfrastructure_ValidatorRejectsBadOptionsAtResolution` to use a two-entry
|
||||
list, and add a test case for `SeedNodes.Count == 1` failing validation. Once this
|
||||
module's validator enforces the rule, `Host.StartupValidator`'s duplicate check
|
||||
becomes redundant and can be removed in the Host's review.
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Open._
|
||||
|
||||
### ClusterInfrastructure-013 — Test uses catastrophic config values without an inline-intent comment
|
||||
|
||||
| | |
|
||||
|--|--|
|
||||
| Severity | Low |
|
||||
| Category | Documentation & comments |
|
||||
| Status | Open |
|
||||
| Location | `tests/ScadaLink.ClusterInfrastructure.Tests/ClusterOptionsTests.cs:47-67` |
|
||||
|
||||
**Description**
|
||||
|
||||
`ClusterOptionsTests.Properties_CanBeSetToCustomValues` deliberately sets two values
|
||||
the design doc explicitly warns are catastrophic:
|
||||
|
||||
```csharp
|
||||
SplitBrainResolverStrategy = "keep-majority", // design doc: total cluster shutdown on partition
|
||||
...
|
||||
MinNrOfMembers = 2 // design doc: blocks singleton, halts data collection
|
||||
```
|
||||
|
||||
The CI-006 resolution acknowledged this is intentional — the test exercises the POCO
|
||||
property setter (which by design accepts any string/int because the validator does
|
||||
the rejecting), and `ClusterOptionsValidatorTests.UnsupportedSplitBrainStrategy_FailsValidation`
|
||||
+ `MinNrOfMembers_NotOne_FailsValidation` prove the validator rejects them. But this
|
||||
reasoning is recorded **only** in the CI-006 resolution text in this findings file,
|
||||
not in the test itself. A reader landing on the test cold has no signal that these
|
||||
values are forbidden in production; they could reasonably infer the test endorses
|
||||
them.
|
||||
|
||||
**Recommendation**
|
||||
|
||||
Add a brief XML-doc / inline comment to `Properties_CanBeSetToCustomValues` stating
|
||||
that it exercises only the POCO's setter — these values intentionally do **not**
|
||||
represent a valid runtime configuration, and `ClusterOptionsValidator` rejects them
|
||||
(with a cross-reference to the relevant validator tests). Two lines is enough; the
|
||||
goal is to make the test's intent self-documenting.
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Open._
|
||||
|
||||
### ClusterInfrastructure-014 — `AddClusterInfrastructureActors` is dead surface — no caller, no behaviour
|
||||
|
||||
| | |
|
||||
|--|--|
|
||||
| Severity | Low |
|
||||
| Category | Code organization & conventions |
|
||||
| Status | Open |
|
||||
| Location | `src/ScadaLink.ClusterInfrastructure/ServiceCollectionExtensions.cs:42-48` |
|
||||
|
||||
**Description**
|
||||
|
||||
`AddClusterInfrastructureActors` has now reached a curious state: it is a public
|
||||
extension method with an XML doc that ends "Do not call AddClusterInfrastructureActors()"
|
||||
and a body that unconditionally throws `NotImplementedException`. CI-002's resolution
|
||||
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`.
|
||||
|
||||
A `grep -rn "AddClusterInfrastructureActors" src/ tests/` confirms there is no caller
|
||||
anywhere in the solution. The method's only consumer is its own test
|
||||
(`AddClusterInfrastructureActors_ThrowsRatherThanSilentlySucceeding`), which asserts
|
||||
that the method throws when called. Keeping it costs API surface (IDE auto-complete
|
||||
suggests it, the docs render it, and a future contributor might re-introduce a call
|
||||
expecting it to register something), and gives nothing in return.
|
||||
|
||||
**Recommendation**
|
||||
|
||||
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
|
||||
"fail-fast" trap, mark the method `[Obsolete(true, error: true)]` so the compiler —
|
||||
not the runtime — rejects the call.
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Open._
|
||||
|
||||
Reference in New Issue
Block a user