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:
Joseph Doherty
2026-05-28 02:55:47 -04:00
parent 1eb6e972b0
commit f93b7b99bb
25 changed files with 8793 additions and 115 deletions
+233 -3
View File
@@ -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._