docs(code-reviews): re-review batch 1 at 39d737e — CentralUI, CLI, ClusterInfrastructure, Commons, Communication
17 new findings: CentralUI-020..025, CLI-014..016, ClusterInfrastructure-009..010, Commons-013..014, Communication-012..015.
This commit is contained in:
@@ -5,10 +5,10 @@
|
||||
| Module | `src/ScadaLink.Commons` |
|
||||
| Design doc | `docs/requirements/Component-Commons.md` |
|
||||
| Status | Reviewed |
|
||||
| Last reviewed | 2026-05-16 |
|
||||
| Last reviewed | 2026-05-17 |
|
||||
| Reviewer | claude-agent |
|
||||
| Commit reviewed | `9c60592` |
|
||||
| Open findings | 0 |
|
||||
| Commit reviewed | `39d737e` |
|
||||
| Open findings | 2 |
|
||||
|
||||
## Summary
|
||||
|
||||
@@ -32,14 +32,28 @@ kind of edge-case logic that warrants them. Entity and message contracts otherwi
|
||||
clean and additive-evolution-friendly, with the exception of one `ValueTuple` use in a
|
||||
wire command.
|
||||
|
||||
**Re-review 2026-05-17 (commit `39d737e`).** All twelve prior findings (Commons-001
|
||||
through Commons-012) are confirmed `Resolved` — the fixes are sound, well-targeted, and
|
||||
backed by focused regression tests (`StaleTagMonitorRaceTests`, `DynamicJsonElementTests`,
|
||||
`ScriptParametersTests`, `ManagementCommandRegistryTests`, `OpcUaEndpointConfigSerializerTests`,
|
||||
`ResultTests`, `ValueFormatterTests`, `ConnectionBindingSerializationTests`,
|
||||
`FlatteningAndScriptScopeTests`). The new files introduced since `9c60592`
|
||||
(`TemplateAlarm` lock/inherit fields, `IExternalSystemRepository` name-keyed lookups,
|
||||
`DeploymentStateQueryRequest`/`Response`, `ParameterDefinition`) follow the established
|
||||
POCO / record / additive-evolution conventions and carry round-trip compatibility tests.
|
||||
Two new Low-severity findings were recorded this pass: a `DynamicJsonElement` array
|
||||
indexer that rejects `long` indices (Commons-013) and an `OpcUaEndpointConfigSerializer`
|
||||
legacy-fallback path that can mislabel a corrupt new-shape row as `Legacy` (Commons-014).
|
||||
No Critical, High, or Medium issues were found.
|
||||
|
||||
## Checklist coverage
|
||||
|
||||
| # | Category | Examined | Notes |
|
||||
|---|----------|----------|-------|
|
||||
| 1 | Correctness & logic bugs | ✓ | `DynamicJsonElement.TryConvert` returns success for non-convertible types; `Result<T>` allows null error; legacy-config fallback loses data. |
|
||||
| 1 | Correctness & logic bugs | ✓ | `DynamicJsonElement.TryConvert` returns success for non-convertible types; `Result<T>` allows null error; legacy-config fallback loses data. Re-review: `DynamicJsonElement.TryGetIndex` rejects non-`int` indices (Commons-013). |
|
||||
| 2 | Akka.NET conventions | ✓ | Commons has no actors (correct). Message contracts are records and immutable. One wire message uses `ValueTuple` (Commons-008). Correlation IDs present on request/response messages. |
|
||||
| 3 | Concurrency & thread safety | ✓ | `StaleTagMonitor` has a check-then-act race between the timer callback and `OnValueReceived` (Commons-001). |
|
||||
| 4 | Error handling & resilience | ✓ | `ScriptParameters.GetNullable` silently swallows conversion failures (Commons-003); OPC UA legacy deserialize discards malformed input (Commons-005). |
|
||||
| 4 | Error handling & resilience | ✓ | `ScriptParameters.GetNullable` silently swallows conversion failures (Commons-003); OPC UA legacy deserialize discards malformed input (Commons-005). Re-review: corrupt typed OPC UA rows can fall through to the legacy path and be mislabelled `Legacy` (Commons-014). |
|
||||
| 5 | Security | ✓ | No auth logic here. `SmtpConfiguration.Credentials` / OPC UA passwords are plain-string fields (storage/encryption is a consumer concern) — noted, not a finding. No script-trust violations: Commons defines no forbidden-API surface. |
|
||||
| 6 | Performance & resource management | ✓ | `StaleTagMonitor` disposes its `Timer` correctly. `DynamicJsonElement` references a `JsonElement` whose backing document lifetime is not owned (Commons-002). |
|
||||
| 7 | Design-document adherence | ✓ | Several behavior-bearing helper/validator/serializer classes push against REQ-COM-6 "no business logic" (Commons-007). Folder layout matches REQ-COM-5b. |
|
||||
@@ -566,3 +580,75 @@ the parameterless `ToString()`). The XML doc gained a remarks block stating the
|
||||
culture-invariant contract and why. Regression tests added in `ValueFormatterTests`
|
||||
(`FormatDisplayValue_Double_UsesInvariantCulture_*`, `_DateTime_*`, `_CollectionOfDoubles_*`,
|
||||
each pinned under `de-DE`).
|
||||
|
||||
### Commons-013 — `DynamicJsonElement.TryGetIndex` rejects non-`int` index values
|
||||
|
||||
| | |
|
||||
|--|--|
|
||||
| Severity | Low |
|
||||
| Category | Correctness & logic bugs |
|
||||
| Status | Open |
|
||||
| Location | `src/ScadaLink.Commons/Types/DynamicJsonElement.cs:40-54` |
|
||||
|
||||
**Description**
|
||||
|
||||
`TryGetIndex` accepts an index only when `indexes[0] is int index`. `DynamicJsonElement`
|
||||
is designed for dynamic access from scripts (`obj.items[0]`). In a `dynamic` expression the
|
||||
index operand's runtime type follows the script's variable type — a script that computes
|
||||
an index in a loop counter or reads it from another `DynamicJsonElement` (whose numbers
|
||||
are unwrapped as `long` by `Wrap`, see `:105`) will pass a `long`, not an `int`. The
|
||||
pattern match then fails, `TryGetIndex` returns `false`, and the dynamic binder throws a
|
||||
`RuntimeBinderException` for what is a perfectly valid in-range index. Because the wrapper
|
||||
itself surfaces JSON numbers as `long`, `obj.items[obj.count - 1]` — count being a wrapped
|
||||
JSON number — is the exact failing case. The `int`-only guard also silently rejects
|
||||
`byte`/`short` indices that would widen to a valid array position.
|
||||
|
||||
**Recommendation**
|
||||
|
||||
Accept any integral index by converting through `Convert.ToInt64` (guarded for
|
||||
`OverflowException`) or by matching `int`, `long`, `short`, `byte` and normalizing to a
|
||||
single integer before the bounds check. Add a regression test indexing with a `long`.
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
|
||||
### Commons-014 — `OpcUaEndpointConfigSerializer.Deserialize` can mislabel a corrupt typed row as `Legacy`
|
||||
|
||||
| | |
|
||||
|--|--|
|
||||
| Severity | Low |
|
||||
| Category | Error handling & resilience |
|
||||
| Status | Open |
|
||||
| Location | `src/ScadaLink.Commons/Serialization/OpcUaEndpointConfigSerializer.cs:107-131` |
|
||||
|
||||
**Description**
|
||||
|
||||
`Deserialize` tries the typed path first: it parses the document, checks for an
|
||||
`endpointUrl` property, then calls `JsonSerializer.Deserialize<OpcUaEndpointConfig>`.
|
||||
The whole block is wrapped in `catch (JsonException) { /* fall through to legacy */ }`.
|
||||
If a row *is* the current typed shape (it has `endpointUrl`) but is corrupt in a way that
|
||||
makes `JsonSerializer.Deserialize` throw a `JsonException` — e.g. an enum-valued field
|
||||
holding an unrecognised string, or a numeric field holding a non-numeric token — the
|
||||
exception is swallowed and control falls through to `LoadLegacy`. `LoadLegacy` only
|
||||
requires the root to be a JSON object, so it will usually succeed against the same input
|
||||
and the result is reported as `OpcUaConfigParseStatus.Legacy`. The Commons-005 fix added
|
||||
the `Malformed` status precisely so a caller can tell a recoverable legacy row from
|
||||
unparseable data; this path re-introduces a softer version of the same confusion — a
|
||||
genuinely broken current-shape row is presented to the user as a benign "please re-save"
|
||||
legacy row, and the offending field is silently dropped by `FromFlatDict` (which ignores
|
||||
keys it cannot parse) rather than surfaced. The XML doc describes the legacy fallback as
|
||||
being for "pre-refactor rows" only and does not mention this branch.
|
||||
|
||||
**Recommendation**
|
||||
|
||||
Only fall through to `LoadLegacy` when the typed shape is genuinely *not present* — i.e.
|
||||
the `endpointUrl` property is absent. When `endpointUrl` *is* present but typed
|
||||
deserialization throws, classify the outcome as `Malformed` (or a distinct status) so the
|
||||
caller can surface a real error instead of an empty/partial config. Tighten the XML doc
|
||||
to describe this branch, and add a regression test for a typed row with an invalid enum
|
||||
field.
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
|
||||
Reference in New Issue
Block a user