docs(code-review): re-review 17 changed modules at 1f9de8a2 — 8 new findings

Re-reviewed the modules whose source changed since the last review baseline
(full-review remediation fd618cf1 + InboundAPI Database-helper fixes b3c90143),
focused on whether the fixes are sound and regression-free. 9 of 17 modules
clean; 8 new findings (0 Critical, 0 High, 4 Medium, 4 Low), all code-verified
by the orchestrator before recording:

- DataConnectionLayer-029 (Med): DCL-023's unsubscribe-clears-in-flight reopens a
  double-subscribe window that leaks an orphaned alarm feed; the alarm completion
  handler overwrites the subscription id without the tag-path guard at line 908.
- InboundAPI-031 (Med): WaitForAttribute's 5s grace backstop is tighter than the
  CommunicationService Ask's timeout+IntegrationTimeout (30s) round-trip slack, so
  a slow-but-valid timed-out 'false' arriving in the 5-30s window is cancelled into
  an unhandled OperationCanceledException/500 (contradicts spec 6 + its own comment).
- SiteRuntime-032 (Med): SiteRuntime-029's wasPresent guard skips the deployed-count
  decrement when deleting a DISABLED instance (absent from both maps), drifting the
  health-dashboard tally; self-heals on singleton restart (observational, hence Med).
- StoreAndForward-028 (Med): StoreAndForward-025 resets the register-guard but not
  _bufferedCount, so a same-instance Stop->Start re-seeds the depth gauge to ~2N.
- AuditLog-017, CentralUI-037, ScriptAnalysis-009, SiteRuntime-033 (Low): a
  test-coverage gap plus stale doc-comments/spec following the remediation.

Header commit/date bumped to 1f9de8a2 / 2026-06-24 on all 17 modules; README
regenerated (8 pending / 576 total).
This commit is contained in:
Joseph Doherty
2026-06-24 09:20:03 -04:00
parent 1f9de8a2b5
commit c42bb48585
18 changed files with 635 additions and 66 deletions
+26 -3
View File
@@ -5,10 +5,10 @@
| Module | `src/ZB.MOM.WW.ScadaBridge.Commons` |
| Design doc | `docs/requirements/Component-Commons.md` |
| Status | Reviewed |
| Last reviewed | 2026-06-19 |
| Last reviewed | 2026-06-24 |
| Reviewer | claude-agent |
| Commit reviewed | `d6ead8ae` |
| Open findings | 2 |
| Commit reviewed | `1f9de8a2` |
| Open findings | 0 |
## Summary
@@ -1239,3 +1239,26 @@ Optionally default `RecipientEmails` to an empty list, or normalize null→empty
Won't Fix (2026-06-19): not a correctness defect — the handler already ignores `RecipientEmails`
for SMS lists. Changing a required positional parameter to defaulted/nullable is a breaking
contract change for existing callers for a pure-ergonomics gain; deferred indefinitely.
## Re-review — 2026-06-24 (commit `1f9de8a2`)
Focused re-review of the changes since the prior review — verifying the code-review remediation + feature fixes are sound and regression-free. Reviewed by a per-module workflow agent; findings code-verified by the orchestrator.
**Changes reviewed:** Two changes since d6ead8ae: (1) ScadaBridgeTelemetry gained a new ClearQueueDepthProvider(Func<long>) method that does an identity-checked Interlocked.CompareExchange compare-and-clear of the process-global queue-depth provider slot, so a StoreAndForwardService can deregister its own provider on StopAsync without clobbering a newer instance that already re-registered. (2) KpiSeriesBucketer received a documentation-only change: the XML doc and an inline comment now state that input MUST be sorted ascending by BucketStartUtc and clarify that for unsorted input the method selects the last point in iteration order (not the largest-timestamp point). No executable logic in the bucketer changed.
**Verdict:** The delta is small, well-targeted, and clean. ClearQueueDepthProvider is a correct fix for a real concern (a stopped service otherwise pins a dead closure and reports a frozen gauge depth forever); the compare-and-clear is sound under the actual memory model, and the StoreAndForwardService caller correctly retains the exact delegate reference (_queueDepthProvider) and passes it back, so the identity check works. The convergence behaviour between a new instance's Volatile.Write register and an old instance's CAS clear is correct in both interleavings (newer provider always wins). The KpiSeriesBucketer change is documentation only and accurately tightens an already-satisfied precondition: the sole production caller (KpiHistoryRepository.GetRawSeriesAsync) returns rows OrderBy(CapturedAtUtc) ascending with BucketStartUtc populated from CapturedAtUtc, so the new sorted-input requirement holds in production. Both changes are pinned by new regression tests (QueueDepthGaugeTests: StopAsync clears + does-not-clobber-newer; KpiSeriesBucketerTests: unsorted-last-in-iteration + short-series-raw-timestamps). No new issues found.
| # | Category | Examined | Notes |
|---|----------|----------|-------|
| 1 | Correctness & logic bugs | ☑ | CompareExchange compare-and-clear is correct; only nulls slot when it still holds the caller's delegate. Bucketer doc matches actual last-in-iteration behaviour; for the sorted production input last-in-iteration equals largest-timestamp. No issues found. |
| 2 | Akka.NET conventions | ☑ | Not actor code — static telemetry helper and a pure downsampling function. No supervision/Tell-vs-Ask/correlation-id surface touched. Not applicable to the delta. |
| 3 | Concurrency & thread safety | ☑ | Interlocked.CompareExchange (full fence) interoperates correctly with Set's Volatile.Write and the gauge's Volatile.Read on the same atomic reference field; reference assignment is atomic, no torn reads. New-vs-old register/clear interleavings both converge to the newer provider. No issues found. |
| 4 | Error handling & resilience | ☑ | ClearQueueDepthProvider null-guards and returns early; gauge falls back to 0 after clear (matches existing null-handling in Set). No exception paths introduced. No issues found. |
| 5 | Security | ☑ | No secrets, no SQL/LDAP, no external input. Queue depth is a numeric count; no sensitive data in telemetry. No issues found. |
| 6 | Performance & resource management | ☑ | Fixes a real resource concern: clearing the provider lets the dead StoreAndForwardService instance's closure be collected instead of being pinned for process lifetime. Bucketer unchanged at runtime. No issues found. |
| 7 | Design-document adherence | ☑ | Bucketer last-value-per-bucket semantics still match Component-KpiHistory.md; the tightened sort precondition is met by GetRawSeriesAsync's OrderBy. Telemetry gauge is an internal observability detail not specified by design docs — no drift. No issues found. |
| 8 | Code organization & conventions | ☑ | ClearQueueDepthProvider mirrors SetQueueDepthProvider's signature and access style and lives in the right file. Minor pre-existing asymmetry (Set uses Volatile.Write, Clear uses Interlocked) is intentional and harmless. No issues found. |
| 9 | Testing coverage | ☑ | Both changes have dedicated regression tests: QueueDepthGaugeTests covers clear-on-stop and the no-clobber-newer-instance race; KpiSeriesBucketerTests pins unsorted last-in-iteration and short-series raw-timestamp paths. Coverage is adequate. No issues found. |
| 10 | Documentation & comments | ☑ | XML docs are thorough and accurate; the bucketer comment's 'essentially any in-bucket point' hedge correctly accounts for the exact-bucket-start equality edge. No misleading or stale comments. No issues found. |
_No new findings — the changes in this module are clean._