docs(archreview): round-2 re-review (2026-07-12) + 8 fix plans (86 tasks)
Re-ran all 8 domain reviews at HEAD8c888f13against theb910f5ebbaseline: every round-1 finding source-verified (168 fixed, 0 regressions, 0 false claims); 56 new findings (1 Critical / 4 High / 15 Medium / 36 Low), concentrated in post-baseline code (anti-entropy resync, KPI rollup backfill, live alarm stream) and seams the fixes exposed. Headliners: S&F resync predicate inversion can wipe the delivering node's buffer (02-N1 Critical); resync snapshot exceeds the Akka remoting frame size (02-N2); failover drill kills the one node keep-oldest can't survive (01-N1); unbounded rollup backfill per failover (04-R1); live production API key in untracked test.txt (08-NF1). Adds PLAN-R2-01..08 + .tasks.json manifests and the Round-2 board, P0 list, cross-plan mutexes, and wave order in 00-MASTER-TRACKER.
This commit is contained in:
@@ -1,185 +1,187 @@
|
||||
# Architecture Review 08 — Cross-Cutting Conventions, Test Posture, Underdeveloped Areas
|
||||
# Architecture Review 08 — Cross-Cutting Conventions, Test Posture, Underdeveloped Areas (Round 2)
|
||||
|
||||
Reviewer domain: cross-cutting conventions, code organization, test posture, repo-wide underdeveloped-areas sweep, docs-code drift. Individual subsystems are covered in depth by the other seven reviews; this one covers the seams and the whole.
|
||||
**Date:** 2026-07-12 (round 2; round 1 dated 2026-07-08, baseline commit `b910f5eb`)
|
||||
**Repo state reviewed:** `main` @ `8c888f13` — 268 commits past the round-1 baseline (all eight archreview fix plans, PLAN-01…PLAN-08, plus the two post-initiative features: aggregated live alarm stream and KPI hourly rollups, both merged 2026-07-10).
|
||||
|
||||
Reviewer domain: cross-cutting conventions, code organization, test posture, docs-code drift, deferred-work inventory. Individual subsystems are covered in depth by the other seven reviews; this one covers the seams and the whole.
|
||||
|
||||
## Scope
|
||||
|
||||
- `src/` — all 26 projects: Commons structure, project reference graph, options pattern, POCO/repository conventions, UTC and correlation-ID discipline, message-contract evolution.
|
||||
- `tests/` — all 30 test projects: breadth, quality sampling, integration/performance posture, slnx membership.
|
||||
- Repo-wide grep sweep for TODO / FIXME / HACK / NotImplementedException / "deferred" / "follow-up" / "not shipped", plus deferred-work inventory from `docs/plans/` and `CLAUDE.md`.
|
||||
- Docs-code sync spot checks: README component table, `docs/requirements/Component-*.md` (KpiHistory, StoreAndForward, Transport, ClusterInfrastructure), `docs/components/` reference docs.
|
||||
- Disposition audit: every round-1 finding re-verified against current source with file:line evidence (plan claims not trusted — checked directly).
|
||||
- Fresh convention sweep over `src/` (26 projects) and `tests/` (30 projects): UTC discipline, TODO/HACK/NotImplementedException surface, Commons purity, options-validation coverage, correlation IDs, csproj hygiene, message-contract enforcement.
|
||||
- Drift audit of the 268 post-baseline commits: new options classes vs validators, new wire contracts vs contract locks, CHANGELOG/README/CLAUDE.md claims vs code, register accuracy.
|
||||
- Deferred-work register (`docs/plans/2026-07-08-deferred-work-register.md`, created by PLAN-08 T11) — item-by-item accuracy check, including the two register rows that shipped post-initiative (#10 live alarm stream, #22 KPI rollups).
|
||||
|
||||
Method: `.slnx` and `.csproj` graph extraction, directory-structure listing, targeted greps over `src/`, `tests/`, `docs/`, and direct reads of the files behind every claim below. Read-only except this report.
|
||||
Method: targeted greps and direct file reads only (static review; no builds/test runs — suites verified green 2026-07-10 per the master tracker). Read-only except this report.
|
||||
|
||||
## Maturity Verdict
|
||||
## Verdict
|
||||
|
||||
This is an unusually disciplined codebase for its size (~190k src LOC, ~156k test LOC, ~5,000 test cases across 30 projects). The documented conventions are not aspirational — they are actually followed: Commons is genuinely persistence-ignorant, the Types/Interfaces/Entities/Messages hierarchy is real, UTC discipline is total (zero `DateTime.Now` in src), correlation IDs are on every cross-cluster request/response plus the `ManagementEnvelope`, and the project reference graph has no site→central contamination. The TODO surface is astonishingly small (two real TODOs in all of src) and almost every deferral is deliberately documented at the deferral site with a pointer to the design doc. The gaps that exist are narrow and specific: one genuine spec-vs-code contradiction (Transport claims Area membership travels in bundles; the exporter always emits `null`), the CLI.Tests slnx exclusion that keeps 279 tests out of every solution-level test run, options-validation coverage on only ~6 of ~20 option classes, and a thin PerformanceTests project that is a correctness-at-small-scale suite rather than a performance suite. Deferred-work inventory is sizeable (~20 tracked items) but almost entirely intentional and logged; only two or three items carry real operational risk.
|
||||
**Round 1 vs round 2 in one line:** every actionable round-1 finding was genuinely fixed (17 fixed, 3 partially, 6 consciously deferred with register rows, 0 regressed) and the conventions that were already clean stayed clean through 268 commits of fix-plan churn; the new residue is small and mostly hygiene — one real embarrassment (a live production API key sitting in an untracked `test.txt` at the repo root), a failover-perf placeholder whose stated precondition has already been satisfied, and an abandoned CHANGELOG that is now factually wrong.
|
||||
|
||||
---
|
||||
|
||||
## 1. Conventions & Organization
|
||||
## 1. Round-1 Finding Disposition
|
||||
|
||||
### 1.1 Commons hierarchy — followed, with undocumented extensions [Low]
|
||||
Every round-1 finding, verified in current source. "Fixed (verified)" means the fix was confirmed at the cited location, not taken from plan claims.
|
||||
|
||||
`src/ZB.MOM.WW.ScadaBridge.Commons/` follows the documented `Types/`, `Interfaces/`, `Entities/`, `Messages/` layout with domain-area subfolders exactly as `Component-Commons.md` describes (284 .cs files; e.g. `Entities/{Audit,Deployment,ExternalSystems,InboundApi,Instances,Kpi,Notifications,Schemas,Scripts,SecuredWrites,Security,Sites,Templates}`, `Messages/{Artifacts,Audit,Communication,DataConnection,DebugView,Deployment,Health,InboundApi,Instance,Integration,Lifecycle,Management,Notification,RemoteQuery,ScriptExecution,Streaming}`).
|
||||
|
||||
Three top-level folders exist beyond the documented four: `Observability/`, `Serialization/` (protocol endpoint-config serializers), and `Validators/`. These are reasonable homes, but the convention doc lists only the four canonical folders. Either document them in `Component-Commons.md` or fold them in (`Serialization` arguably belongs under `Types/`).
|
||||
|
||||
### 1.2 POCO persistence-ignorance — verified clean [Pass]
|
||||
|
||||
Grep for EF Core / DataAnnotations attributes across Commons returns only one hit, and it is an XML-doc `<see cref>` to `DbUpdateConcurrencyException` in `src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/IDeploymentManagerRepository.cs:64` — a documentation reference, not a dependency. The Commons csproj has zero EF package references (its only PackageReference is the shared `ZB.MOM.WW.Audit` lib). Entities are genuinely persistence-ignorant.
|
||||
|
||||
### 1.3 Repository interfaces vs implementations — 1:1, with a deliberate site-side exception [Low]
|
||||
|
||||
14 repository interfaces in `Commons/Interfaces/Repositories/`, 14 implementation files in `ConfigurationDatabase/Repositories/` — exact match. Two additional implementations live in `src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Repositories/` (`SiteExternalSystemRepository`, `SiteNotificationRepository`): read-only SQLite-backed site-side implementations that throw `NotSupportedException` on writes. This is a sensible pattern (same interface, site-local store), but it is an undocumented exception to the "implementations in Configuration Database" rule.
|
||||
|
||||
**Vestigial seam** [Low/Medium]: `SiteNotificationRepository` (registered scoped in `src/ZB.MOM.WW.ScadaBridge.SiteRuntime/ServiceCollectionExtensions.cs:65`) reads the site-local `notification_lists` table — but per the central-only notification design, `SiteStorageService.PurgeCentralOnlyNotificationConfigAsync` (`src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Persistence/SiteStorageService.cs:703`) deliberately empties that table on every artifact apply. So the registered repository can only ever return empty results, and `StoreNotificationListAsync` (line 721) still exists as a write path into a table the design says must stay empty. The purge is well-commented and correct as a security cleanup; the live read/write surface around an intentionally-empty table is dead weight and a trap for a future maintainer. Recommend removing `StoreNotificationListAsync`/list-read paths or marking them `[Obsolete]` with the design rationale.
|
||||
|
||||
### 1.4 Project reference graph — sane; no site→central contamination [Pass, one Low note]
|
||||
|
||||
Extracted from all 26 csproj files:
|
||||
|
||||
- **Commons is a leaf** (no project refs). Everything depends on it; it depends on nothing.
|
||||
- **Site-side projects never reference central-only projects.** `SiteRuntime` → Commons, Communication, DataConnectionLayer, ScriptAnalysis, HealthMonitoring, SiteEventLogging, StoreAndForward — critically, **no** reference to `ConfigurationDatabase` (EF/central SQL), `ManagementService`, `CentralUI`, or `TemplateEngine`. Same for DataConnectionLayer, StoreAndForward, SiteEventLogging.
|
||||
- **Central-only stack is correctly layered**: AuditLog/SiteCallAudit/Transport → ConfigurationDatabase; CentralUI → the central components + ManagementService; Host references everything (composition root, as designed).
|
||||
- **CLI → Commons only** — correctly thin (HTTP client to the Management API).
|
||||
- **DelmiaNotifier → nothing** — correctly standalone/BCL-only.
|
||||
|
||||
One smell [Low]: `Communication` → `HealthMonitoring` (`src/ZB.MOM.WW.ScadaBridge.Communication/Actors/CentralCommunicationActor.cs:13`). A transport layer depending on a monitoring component is a mild layering inversion; the health-aggregator abstraction it consumes could live in Commons. Cosmetic [Low]: `ManagementService.csproj` uses a Windows-style `..\` separator for its InboundAPI reference while every other reference in the repo uses `../` — harmless to MSBuild, inconsistent style.
|
||||
|
||||
### 1.5 Options pattern — ownership right, validation coverage partial [Medium]
|
||||
|
||||
Every component owns its options class in its own project (31 `*Options.cs` files across src; none in Commons) — the ownership convention is fully honored. But **eager validation is wired for only ~6 components**: `IValidateOptions<>`/`ValidateDataAnnotations`/`ValidateOnStart` appear only in AuditLog, KpiHistory, NotificationOutbox (+SmsOptions), HealthMonitoring, Security, ClusterInfrastructure, plus the Host `StartupValidator`. Components binding options without a validator include Communication, DataConnectionLayer, DeploymentManager, ExternalSystemGateway, InboundAPI, ManagementService, NotificationService, SiteCallAudit, SiteEventLogging, SiteRuntime, StoreAndForward, and Transport (e.g. `src/ZB.MOM.WW.ScadaBridge.Transport/ServiceCollectionExtensions.cs:25` — `AddOptions<TransportOptions>().BindConfiguration(...)` with no `.ValidateOnStart()`). A malformed `appsettings` section in those components surfaces at first use rather than at startup, which is exactly what the Host's readiness-gating philosophy is supposed to prevent. Recommend a sweep adding validators (the HealthMonitoring/ClusterInfrastructure `IValidateOptions` implementations are ready-made patterns to copy).
|
||||
|
||||
### 1.6 UTC timestamps — total compliance [Pass]
|
||||
|
||||
Zero occurrences of `DateTime.Now`/`DateTimeOffset.Now` (non-Utc) in all of src (grep across .cs and .razor). Every timestamp uses `UtcNow` or stores `datetime2`/ISO-8601 UTC.
|
||||
|
||||
### 1.7 Correlation IDs — convention followed [Pass]
|
||||
|
||||
96 `CorrelationId` declarations across `Commons/Messages/` cover the full cross-cluster request/response surface (RouteToInstance, Get/SetAttribute(s), Subscribe*, WriteTag*, ParkedMessage*, NotificationOutboxQueries, SiteCallQueries, EventLogQuery, DeploymentStateQuery, WaitForAttribute, etc.). Management commands themselves carry no per-command field, but they always travel inside `ManagementEnvelope(User, Command, CorrelationId)` with `ManagementSuccess`/`ManagementError`/`ManagementUnauthorized` echoing it (`Commons/Messages/Management/ManagementEnvelope.cs:7-11`) — the convention holds at the envelope level.
|
||||
|
||||
### 1.8 Message contract evolution — convention by discipline, not enforcement [Low]
|
||||
|
||||
Messages are plain C# records with defaulted trailing parameters (additive-friendly, e.g. `CreateDataConnectionCommand(..., string? BackupConfiguration = null, int FailoverRetryCount = 3)` in `Commons/Messages/Management/DataConnectionCommands.cs`). The Akka HOCON in `Host/Actors/AkkaHostedService.cs:233` configures no custom serializer, so ClusterClient traffic rides Akka.NET's default JSON serialization — tolerant of additive fields. The gRPC side does have contract-lock tests (`tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/Protos/CachedTelemetryProtoTests.cs`, `PullAuditEventsProtoTests.cs`), but there is no equivalent guard (snapshot/round-trip tests against old shapes) for the ClusterClient record contracts. Given the "system-wide artifact version skew across sites is supported" design decision, a small contract-compatibility test suite for the highest-traffic ClusterClient messages would convert the additive-only rule from convention to enforcement.
|
||||
|
||||
### 1.9 Naming/namespace consistency [Pass]
|
||||
|
||||
Consistent `ZB.MOM.WW.ScadaBridge.<Component>` namespaces mirroring folder structure in every file sampled (SiteRuntime, Transport, Commons, KpiHistory, ManagementService). Test projects mirror src names 1:1 plus IntegrationTests/PerformanceTests/PlaywrightTests/Transport.IntegrationTests.
|
||||
|
||||
---
|
||||
|
||||
## 2. Test Posture & Quality
|
||||
|
||||
### 2.1 Breadth — every component has a test project; volume is high
|
||||
|
||||
30 test projects; ~5,050 `[Fact]`/`[Theory]` methods; ~156k test LOC against ~190k src LOC (src figure inflated by ~75k in ConfigurationDatabase, mostly EF migrations). Per-project counts (files / LOC / test methods):
|
||||
|
||||
| Project | Tests | Notes |
|
||||
|---|---|---|
|
||||
| CentralUI.Tests | 829 | bUnit page/component tests — largest suite |
|
||||
| Commons.Tests | 465 | includes KpiSeriesBucketer, codecs, validators |
|
||||
| SiteRuntime.Tests | 457 | Akka TestKit behavioral (see 2.2) |
|
||||
| TemplateEngine.Tests | 433 | |
|
||||
| CLI.Tests | 279 | **excluded from slnx** (see 2.4) |
|
||||
| AuditLog.Tests | 237 | |
|
||||
| ManagementService.Tests | 234 | |
|
||||
| ConfigurationDatabase.Tests | 219 | incl. migration tests |
|
||||
| InboundAPI.Tests | 218 | |
|
||||
| Communication.Tests | 211 | incl. proto contract locks |
|
||||
| DataConnectionLayer.Tests | 190 | |
|
||||
| Host.Tests | 142 | incl. CompositionRootTests |
|
||||
| Security.Tests | 133 | |
|
||||
| NotificationOutbox.Tests / Transport.Tests | 122 / 124 | |
|
||||
| StoreAndForward.Tests | 116 | |
|
||||
| DeploymentManager.Tests | 98 | |
|
||||
| HealthMonitoring.Tests | 84 | |
|
||||
| IntegrationTests / ExternalSystemGateway.Tests | 69 / 69 | |
|
||||
| Transport.IntegrationTests | 60 | |
|
||||
| SiteEventLogging.Tests | 54 | |
|
||||
| ScriptAnalysis.Tests / NotificationService.Tests / CentralUI.PlaywrightTests | 40 / 36 / 36 | |
|
||||
| SiteCallAudit.Tests | 31 | thinnest per src LOC (1,642 loc / 31 tests) |
|
||||
| DelmiaNotifier.Tests | 23 | |
|
||||
| ClusterInfrastructure.Tests | 15 | src is only 191 LOC (wiring lives in Host) — proportionate |
|
||||
| KpiHistory.Tests | 14 | bucketer/query/chart tests live with owners (Commons.Tests, CentralUI.Tests) — coverage is distributed, not missing |
|
||||
| PerformanceTests | 10 | see 2.3 |
|
||||
|
||||
Relatively thin spots: **SiteCallAudit.Tests** (31 tests for a reconciliation/KPI/relay component) and **DeploymentManager.Tests** (98 tests for the deployment pipeline, though much of its logic is exercised via ManagementService/Host/Integration suites).
|
||||
|
||||
### 2.2 Quality sampling — real behavioral tests, not shallow
|
||||
|
||||
`tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/Actors/AlarmActorTests.cs` is representative: full Akka TestKit harness, real compiled script library, `TestProbe` as the parent InstanceActor, asserts actual `AlarmStateChanged` messages with state/name payloads — genuine behavior, not constructor smoke tests. The SiteRuntime.Tests directory also carries targeted regression suites (`InstanceActorChildAttributeRaceTests`, `DeploymentManagerRedeployTests`, `DeploymentManagerMediumFindingsTests` — named for review findings, indicating review feedback gets locked in as tests). IntegrationTests covers real cross-cutting concerns: `CentralFailoverTests`, `SiteFailoverTests`, `DualNodeRecoveryTests`, `RecoveryDrillTests`, `ReadinessTests`, `AuthFlowTests`, `SecurityHardeningTests`, `AuditTransactionTests`, `NotificationOutboxFlowTests`, gRPC tests, plus a `ScadaBridgeWebApplicationFactory`. A 36-test Playwright E2E suite exists for the Central UI.
|
||||
|
||||
### 2.3 PerformanceTests is real but minimal — a misnomer more than a stub [Medium]
|
||||
|
||||
`tests/ZB.MOM.WW.ScadaBridge.PerformanceTests/` has 3 test files / 10 tests: `HealthAggregationTests` (10 sites × 100 updates, **correctness assertions only, no timing**), `StaggeredStartupTests` (one `Stopwatch` assertion < 1000ms), and `AuditLog/HotPathLatencyTests` (the best of the three: `Stopwatch.GetTimestamp` p95-under-threshold on the site audit hot path; its own doc comment acknowledges "no BenchmarkDotNet" as a deliberate choice). Nothing here exercises the load-bearing performance claims of the design — 25s failover, gRPC streaming throughput, S&F drain rate, per-subscriber stream backpressure. Either rename the project to reflect its actual scope or grow it toward the design's stated performance envelope (failover time and stream throughput being the two highest-value additions).
|
||||
|
||||
### 2.4 CLI.Tests still excluded from the slnx [High]
|
||||
|
||||
Verified against `ZB.MOM.WW.ScadaBridge.slnx`: 29 of 30 test projects are members; `tests/ZB.MOM.WW.ScadaBridge.CLI.Tests` is absent (the src `CLI.csproj` *is* in the slnx). This is a documented gotcha in CLAUDE.md and memory, but the durable fix — adding one `<Project>` line — has not been made, so `dotnet test ZB.MOM.WW.ScadaBridge.slnx` silently skips 279 tests and a green solution run can mask CLI tests that never compiled. There is no evident reason to keep it out; add it.
|
||||
|
||||
---
|
||||
|
||||
## 3. Underdeveloped Areas & Deferred-Work Inventory
|
||||
|
||||
The in-code TODO surface is remarkably small: exactly **two real TODOs** in all of src (both the same issue — Transport area export, `EntitySerializer.cs:251/518`), zero `NotImplementedException` in production code paths (the one that existed was deliberately deleted — see the comment in `ClusterInfrastructure/ServiceCollectionExtensions.cs:34-47`), and zero FIXME/HACK. 39 "deferred/follow-up" comment mentions are almost all self-documenting pointers to logged decisions.
|
||||
|
||||
Consolidated inventory (item, where noted, risk of leaving it):
|
||||
|
||||
| # | Item | Where noted | Risk if left |
|
||||
| Round-1 finding | Sev | Disposition | Evidence (current source) |
|
||||
|---|---|---|---|
|
||||
| 1 | **Transport: Area membership doesn't travel in bundles** — exporter hardcodes `AreaName: null`; importer machinery is fully built (`BundleImporter.cs:3104-3168` resolves/creates areas) | `src/.../Transport/Serialization/EntitySerializer.cs:251,518` (TODO); contradicts `Component-Transport.md:21` + CLAUDE.md #24 | **High** — spec and CLAUDE.md claim it ships; cross-env imports silently drop area organization; half-built feature invites confusion |
|
||||
| 2 | CLI.Tests not in slnx | `ZB.MOM.WW.ScadaBridge.slnx`; CLAUDE.md gotcha | **High** — 279 tests invisible to solution-level CI/test runs |
|
||||
| 3 | AuditLog reconciliation dials site NodeA only; NodeB failover endpoint selection deferred | `src/.../AuditLog/Central/SiteEnumerator.cs:32,64`, `ISiteEnumerator.cs:16` | **Medium** — during a site NodeA outage, telemetry-loss reconciliation (the safety net) is also unavailable; audit gaps persist until NodeA returns |
|
||||
| 4 | Options validation missing on ~14 components (§1.5) | e.g. `Transport/ServiceCollectionExtensions.cs:25` | **Medium** — config typos surface at first use, not startup |
|
||||
| 5 | `SmsConfiguration.MaxRetryCount` stored but not honored at dispatch (SMTP retry policy governs SMS) | `Commons/Entities/Notifications/SmsConfiguration.cs:36` | **Medium** — admin-visible knob that does nothing; misleading UI |
|
||||
| 6 | Role-check case-sensitivity asymmetry: UI `RequireClaim` policies vs ManagementActor's case-insensitive check | `ManagementService/ManagementActor.cs:1910` | **Medium** — a role stored with non-canonical casing could pass one gate and fail the other; deliberately unaltered, separately deferred |
|
||||
| 7 | SecuredWrite audit rows leave `SourceNode` NULL | CLAUDE.md (Security section, logged follow-up) | **Low/Medium** — per-node audit forensics blind spot for two-person writes specifically |
|
||||
| 8 | Hash-chain tamper evidence (T1) | CLAUDE.md; `docs/plans/2026-05-20-audit-log-code-roadmap.md:12`; CLI `verify-chain` is a shipped no-op stub (`CLI/Commands/AuditVerifyChainHelpers.cs:7`) | **Low/Medium** — deferred to v1.x by locked decision; append-only DB roles are the current control; the no-op CLI verb slightly overstates capability |
|
||||
| 9 | Parquet audit archival/export (T2) — endpoint returns 501 | `ManagementService/AuditEndpoints.cs:204-208`; CLAUDE.md | **Low** — deferred v1.x; 501 + CLI messaging handle it honestly |
|
||||
| 10 | Aggregated **live** alarm stream for Alarm Summary (currently snapshot fan-out + poll) | `docs/plans/2026-06-18-m7-opcua-mxgateway-ux-design.md:252` | **Low/Medium** — operator page scales with instance count; fan-out Ask per refresh is the known cost |
|
||||
| 11 | Central-persisted, auditable OPC UA server-cert trust (v1 is site-local PKI only) | m7 design follow-ups; CLAUDE.md | **Low** — broadcast-to-both-nodes covers HA; no central governance/audit of trust decisions |
|
||||
| 12 | Native-alarm-source-override CSV bulk import | m7 design follow-ups; CLAUDE.md (M7 UX) | **Low** — attribute-override CSV shipped; parity gap only |
|
||||
| 13 | `WaitForAttribute` quality-gated ("Good"-only) mode | `SiteRuntime/Scripts/ScriptRuntimeContext.cs:405` | **Low** — planned enhancement per spec §4.2 |
|
||||
| 14 | `WaitForAttribute` not wired into CentralUI Test-Run sandbox | `docs/plans/2026-06-17-waitfor-deferred-items.md:222` | **Low** — Test-Run parity gap; production scripts unaffected |
|
||||
| 15 | `BrowseNext` continuation: server's final-page signal not surfaced (client may issue one extra empty pull) | `Commons/Messages/Management/BrowseCommands.cs:34` | **Low** — one wasted round-trip |
|
||||
| 16 | `StubOpcUaClient` throws on browse — test-infra gap for browse/search unit tests | m7 design "risks" (`2026-06-18-m7-opcua-mxgateway-ux-design.md:245`) | **Low** — limits offline coverage of browse UI |
|
||||
| 17 | Unified notifications+site-calls outbox page | `2026-06-15-stillpending-completion-design.md:118` (M9 deferrals) | **Low** — two pages remain; explicit decision |
|
||||
| 18 | Folder drag-drop | same, `[PERM]` permanently deferred | None — closed decision; menu reorder shipped |
|
||||
| 19 | Bundle signing (asymmetric, non-repudiation), direct cluster-to-cluster pull, differential bundles | `docs/plans/2026-05-24-transport-design.md:402-408` | **Low** — v1 manifest hash + passphrase AES-GCM held sufficient |
|
||||
| 20 | Deployment EXPIRED-but-unpurged row purge | `ConfigurationDatabase/Repositories/DeploymentManagerRepository.cs:310` ("deferred TODO" — workaround in place) | **Low** — code already compensates when reading |
|
||||
| 21 | `SiteAuditBacklogReporter` threshold consolidation into `SqliteAuditWriterOptions` | `AuditLog/Site/SiteAuditBacklogReporter.cs:28` | **Low** — config-shape cleanup |
|
||||
| 22 | KPI history downsampling (hourly rollups) | `Component-KpiHistory.md` (YAGNI) | **Low** — 90-day retention bounds table size |
|
||||
| 23 | Vestigial site-side notification list read/write surface (§1.3) | `SiteRuntime/Persistence/SiteStorageService.cs:721`, `Repositories/SiteNotificationRepository.cs` | **Low/Medium** — dead-but-live API around an intentionally empty table |
|
||||
| §1.1 Commons folders beyond documented four (`Observability/` etc.) | Low | **Fixed** | `docs/requirements/Component-Commons.md:294` documents `Observability/` in the layout tree (PLAN-08 T10 had narrowed this: `Serialization/`/`Validators/` were already documented) |
|
||||
| §1.2 POCO persistence-ignorance | Pass | **Still Pass** | 0 EF/DataAnnotations refs in Commons (excluding `cref`); csproj's only PackageReference remains `ZB.MOM.WW.Audit` |
|
||||
| §1.3 Site-side repository exception undocumented | Low | **Fixed** | `docs/requirements/Component-ConfigurationDatabase.md:170` documents the `SiteExternalSystemRepository` exception with the `NotSupportedException` rationale |
|
||||
| §1.3/#23 Vestigial `SiteNotificationRepository` + dead write paths | Low/Med | **Fixed** | File deleted (0 hits in `src/`); only historical why-comments remain (`SiteRuntime/Persistence/SiteStorageService.cs:804-805`); design-invariant lock `Site_NotificationRepository_IsNotRegistered` at `tests/ZB.MOM.WW.ScadaBridge.Host.Tests/CompositionRootTests.cs:446` |
|
||||
| §1.4 Communication → HealthMonitoring layering inversion | Low | **Deferred (accepted)** | Still present (`Communication/Actors/CentralCommunicationActor.cs:13`); register row with rationale + revisit trigger (`2026-07-08-deferred-work-register.md:48`) |
|
||||
| §1.4 `..\` csproj path separator | Low | **Fixed** | 0 backslash project references across all csprojs |
|
||||
| §1.5 Options validation on only ~6 of ~20 option classes | Med | **Partially fixed** | 16 `OptionsValidatorBase<T>` validators now exist (all 12 PLAN-08 target components + the prior 4), wired `.ValidateOnStart()` + `TryAddEnumerable`; **residue remains** — see New Finding NF4 (AuditLog's 6 sub-options, Host's Node/Database/Logging options) |
|
||||
| §1.6 UTC discipline | Pass | **Still Pass** | 0 non-Utc `DateTime.Now`/`DateTimeOffset.Now` in src (.cs + .razor) |
|
||||
| §1.7 Correlation IDs | Pass | **Still Pass** | 99 `CorrelationId` declarations in `Commons/Messages/` (was 96 — new messages kept the convention) |
|
||||
| §1.8 No contract-lock tests for ClusterClient records | Low | **Fixed** | `tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Messages/ClusterClientContractLockTests.cs` — 9 tests over 7 records (old-shape, unknown-field, round-trip); locks the real wire finding that Newtonsoft ignores C# ctor optional-param defaults → missing trailing fields deserialize to `default(T)` (lines 57–60); Newtonsoft pinned 13.0.3 in `Directory.Packages.props:54` |
|
||||
| §1.9 Naming/namespaces | Pass | **Still Pass** | spot checks consistent |
|
||||
| §2.3 PerformanceTests a correctness-at-small-scale suite | Med | **Partially fixed** | Real throughput test shipped: `PerformanceTests/Streaming/SiteStreamThroughputTests.cs` (100k events, asserts ≥10k events/s + ≥90% delivery, lines 50–94). Failover timing is still a `Skip` placeholder — and its skip reason is now stale (New Finding NF2) |
|
||||
| §2.4 CLI.Tests excluded from slnx | High | **Fixed** | `ZB.MOM.WW.ScadaBridge.slnx` contains all 30 test projects incl. CLI.Tests; CLAUDE.md gotcha retired; CLI suite (359 cases) runs at solution level |
|
||||
| §3.1 / §4 Transport `AreaName: null` vs doc claim | High | **Fixed** | Exporter now carries area by name: `Transport/Serialization/EntitySerializer.cs:273` resolves `AreaId` → `AreaNameById`; importer resolves-or-creates. Residual stale comment at :553–554 (New Finding NF6) |
|
||||
| §3.3 AuditLog reconciliation dials NodeA only | Med | **Fixed** | `AuditLog/Central/SiteEnumerator.cs:31-33, 64-77` — primary = NodeA else NodeB (NodeB-only sites no longer skipped), distinct NodeB rides along as failover endpoint |
|
||||
| §3.5 `SmsConfiguration.MaxRetryCount` stored but unused | Med | **Fixed** | `NotificationOutbox/NotificationOutboxActor.cs:470, 485` honors `SmsConfiguration.MaxRetries`/`RetryDelay` (PLAN-06 T18) |
|
||||
| §3.6 Role-check case-sensitivity asymmetry | Med | **Fixed** | `ManagementService/ManagementActor.cs:108, 499, 515, 530` — `StringComparer.OrdinalIgnoreCase` throughout; PLAN-07 T12 canonicalized casing at write |
|
||||
| §3.7 SecuredWrite audit rows `SourceNode` NULL | Low/Med | **Fixed** | `ManagementActor.cs:1208` routes secured-write audit through `ICentralAuditWriter`, which stamps `SourceNode` from `INodeIdentityProvider` |
|
||||
| §3.8 Hash-chain tamper evidence (no-op `verify-chain`) | Low/Med | **Deferred (accepted)** | Register row 8 (v1.x locked decision) |
|
||||
| §3.9 Parquet archival (501) | Low | **Deferred (accepted)** | Register row 9 |
|
||||
| §3.10 Aggregated live alarm stream | Low/Med | **Fixed (shipped 2026-07-10)** | `Communication/ISiteAlarmLiveCache.cs`, `Communication/Actors/SiteAlarmAggregatorActor.cs`, `SubscribeSite` rpc in `Communication/Protos/sitestream.proto:13`; options eagerly validated (`CommunicationOptionsValidator.cs:72-87`); no central store (in-memory only, no EF migration) — register row moved to Resolved |
|
||||
| §3.11 Central-persisted cert-trust audit | Low | **Deferred (accepted)** | Register row 11 |
|
||||
| §3.12 Native-alarm-source-override CSV import | Low | **Partially fixed** | Parser + ManagementActor handler + CLI `instance native-alarm-source import --file` shipped 2026-07-10; only the Central UI upload affordance remains (register row 12 updated accordingly) |
|
||||
| §3.13 `WaitForAttribute` quality-gated mode | Low | **Fixed** | `Commons/Messages/InboundApi/RouteToInstanceRequest.cs:117` — `RequireGoodQuality` param, enforced site-side, tested |
|
||||
| §3.14 `WaitForAttribute` in Test-Run sandbox | Low | **Fixed** | Sandbox `Attributes.WaitAsync`/`WaitForAsync` route via `ISandboxInstanceGateway`; predicate-form waits documented unsupported (register Resolved row 14) |
|
||||
| §3.15 `BrowseNext` final-page signal | Low | **Fixed / no longer applicable** | `Commons/Messages/Management/BrowseCommands.cs:18-36` — `ContinuationToken` + `Truncated` on `BrowseNodeResult`; "Load more" renders only while a token remains |
|
||||
| §3.16 `StubOpcUaClient` throws on browse | Low | **Fixed** | Browse + search supported; `tests/.../DataConnectionLayer.Tests/StubOpcUaClientBrowseTests.cs`, `StubOpcUaClientSearchTests.cs` |
|
||||
| §3.17 Unified outbox page | Low | **Deferred (accepted)** | Register row 17 (explicit M9 decision) |
|
||||
| §3.18 Folder drag-drop | — | **Closed (permanent)** | Register row 18 `[PERM]` |
|
||||
| §3.19 Bundle signing / cluster pull / differential bundles | Low | **Deferred (accepted)** | Register row 19 |
|
||||
| §3.20 Deployment EXPIRED-row purge | Low | **Fixed** | `Communication/Actors/PendingDeploymentPurgeActor.cs` (central singleton, options-validated interval; PLAN-04) |
|
||||
| §3.21 `SiteAuditBacklogReporter` threshold consolidation | Low | **Fixed** | `SqliteAuditWriterOptions.BacklogPollIntervalSeconds` drives the reporter cadence (`AuditLog/Site/SiteAuditBacklogReporter.cs:61-87`) |
|
||||
| §3.22 KPI hourly rollups | Low | **Fixed (shipped 2026-07-10)** | `Commons/Entities/Kpi/KpiRollupHourly.cs`; `KpiHistoryOptions.cs:46-73` (RollupInterval 1h, Lookback 3h, Retention 365d, Threshold 168h); validator bounds (`KpiHistoryOptionsValidator.cs:31-44`); `Component-KpiHistory.md` updated (24 rollup mentions) — register row moved to Resolved |
|
||||
| §4 `docs/components/` lags 3 components vs README claim | Low | **Fixed (via scoping)** | `README.md:111` + `docs/components/README.md:8-9` now state "24 of the 27… ScriptAnalysis/KpiHistory/DelmiaNotifier pending", pointing at the register; actual count verified: 24 component docs + TreeView + index = 26 files. Backfill itself remains deferred (register) |
|
||||
| §4 DelmiaNotifier breaks `Component-<Name>.md` convention | Low | **Fixed (documented exception)** | CLAUDE.md Document Conventions now carries the explicit DelmiaNotifier exception bullet |
|
||||
|
||||
Overall: ~20 open items, of which only #1–#4 warrant action before the next release; the rest are consciously logged, mostly with honest in-product behavior (501s, no-op stubs that say so).
|
||||
**Disposition counts: 20 Fixed (verified) · 3 Partially fixed · 6 Deferred (accepted, register rows) · 1 Closed-permanent · 0 Not fixed · 0 Regressed.** (The 7 always-Pass items stayed Pass.)
|
||||
|
||||
---
|
||||
|
||||
## 4. Docs-Code Drift
|
||||
## 2. Refreshed Convention Audit (round-2 sweep)
|
||||
|
||||
Drift is exceptionally low for a docs-as-spec repo. Spot checks:
|
||||
|
||||
- **Component-KpiHistory.md vs code** — verified in-sync: `KpiHistoryOptions` defaults (SampleInterval 60s, RetentionDays 90 [1,3650], PurgeInterval 1d, DefaultMaxSeriesPoints 200 [2,5000]) match `src/.../KpiHistory/KpiHistoryOptions.cs` exactly, including range constraints; source placement (`IKpiSampleSource` impls with owners, bucketer in Commons, query service in CentralUI) matches the actual file layout and test placement.
|
||||
- **Component-StoreAndForward.md vs code** — verified in-sync: the doc's nuanced notification-parking semantics (`DefaultMaxRetries` cap, `maxRetries: 0` escape hatch), `ICachedCallLifecycleObserver` telemetry hook, and tracking-table-vs-buffer split all correspond to the shipped files (`NotificationForwarder.cs`, `ParkedMessageHandlerActor.cs`, `StoreAndForwardService.cs`, `ReplicationService.cs`).
|
||||
- **Component-ClusterInfrastructure.md vs code** — in-sync, and exemplary: the code carries a comment (`ClusterInfrastructure/ServiceCollectionExtensions.cs:34-47`) explaining that a dead throwing extension was removed *because* the doc's "Code Placement" section settled ownership in Host — docs and code cross-reference each other.
|
||||
- **[High] Component-Transport.md:21 and CLAUDE.md #24 claim `Area` membership travels "carried by name"** — but the exporter emits `AreaName: null` unconditionally (`Transport/Serialization/EntitySerializer.cs:251-254`) with a TODO, and `BundleImporter.cs:511-518` confirms "Areas don't travel (see export TODO)". The import-side resolution (create-area-if-missing, `BundleImporter.cs:3104-3168`) is fully built, making the doc claim look shipped on casual inspection. Either finish the export (small: add Areas to `EntityAggregate` + the id→name lookup) or correct the doc/CLAUDE.md to say area membership is not yet carried.
|
||||
- **[Low] `docs/components/` reference docs lag by three components**: 24 per-component reference docs exist but ScriptAnalysis (#25), KpiHistory (#26), and DelmiaNotifier (#27) are missing, while `README.md:111` claims "One doc per component (plus the shared TreeView)".
|
||||
- **[Low] DelmiaNotifier breaks the requirements-doc naming convention**: no `docs/requirements/Component-DelmiaNotifier.md`; the README component table row 27 links to the project README instead. Defensible for an external tool, but CLAUDE.md's own Document Conventions say component documents live in `docs/requirements/` as `Component-<Name>.md` — either add a thin spec doc or note the exception in the conventions section.
|
||||
- **README component table** — all 27 rows present and links resolve to existing files; component descriptions match CLAUDE.md's component list. TreeView correctly footnoted as a sub-component. No stale cross-references found in the sampled docs.
|
||||
- Historical `docs/plans/` design docs contain superseded "future work" lists (e.g. transport-design §18 lists site-scoped transport as future, which M8 later shipped) — acceptable since they are dated decision records, and the living docs (Component-*.md, CLAUDE.md, README) reflect the shipped state.
|
||||
- **UTC**: 0 non-Utc `DateTime.Now`/`DateTimeOffset.Now` across src `.cs`/`.razor` — total compliance held through 268 commits.
|
||||
- **TODO surface**: 0 `// TODO` / `// FIXME` / `// HACK` in all of src (round 1 had two, both the Transport area export — now shipped). 0 `NotImplementedException` in production code. 29 "deferred/follow-up" comment mentions (was 39), sampled — all are pointer-style documentation of logged decisions, none load-bearing stubs.
|
||||
- **Commons purity**: intact — 0 EF/DataAnnotations dependencies; Commons csproj still references only `ZB.MOM.WW.Audit`.
|
||||
- **Correlation IDs**: 99 declarations in `Commons/Messages/` — the new cross-cluster messages added by the plans (per-node KPI requests, secured-write paging, `RouteToWaitForAttributeRequest`, browse `SiteIdentifier` additions) all kept the convention or ride `ManagementEnvelope`.
|
||||
- **Options pattern**: 16 eager validators (`OptionsValidatorBase<T>` + `ValidateOnStart` + `TryAddEnumerable`) covering every primary per-component options class, including the two post-initiative features (live-alarm-cache fields validated at `CommunicationOptionsValidator.cs:72-87`; rollup fields bounded in `KpiHistoryOptionsValidator.cs:31-44`). Residue: NF4 below.
|
||||
- **Message-contract enforcement**: additive-only is now *enforced* for the 7 highest-traffic ClusterClient records (`ClusterClientContractLockTests`) and the gRPC protos (existing proto contract tests; the new `SubscribeSite` stream has behavioral coverage in `Communication.Tests/Grpc/`). The lock suite deliberately covers "top" records, not all — new records added by the plans are outside it, which is the accepted design of the guard.
|
||||
- **csproj hygiene**: 0 `Version=` attributes on PackageReferences in src/tests (central package management honored); 0 backslash path separators; 0 third-party Blazor component frameworks (MudBlazor/Radzen/Blazorise/Syncfusion) — the "custom components only" UI rule held.
|
||||
- **Slnx**: all 30 test projects are members (the 31st `tests/` match is the `<Folder Name="/tests/">` element).
|
||||
- **CLAUDE.md accuracy spot-checks**: the claims audited all match code — CLI.Tests slnx note, live-alarm-stream description (`ISiteAlarmLiveCache`, seed-then-stream, 15s poll fallback, no persisted store — no EF migration exists for it), KPI rollup knobs (168h threshold / 365d retention match `KpiHistoryOptions.cs:65-73`), SecuredWrite `SourceNode` stamping, SMS retry policy.
|
||||
|
||||
---
|
||||
|
||||
## Prioritized Recommendations
|
||||
## 3. Refreshed Test Inventory
|
||||
|
||||
1. **[High] Add `tests/ZB.MOM.WW.ScadaBridge.CLI.Tests` to `ZB.MOM.WW.ScadaBridge.slnx`.** One line; removes a standing CI blind spot of 279 tests and retires a documented footgun.
|
||||
2. **[High] Resolve the Transport Area contradiction** — either implement area export (importer already complete; add Areas to `EntityAggregate` and the id→name lookup in `EntitySerializer.cs:251`) or amend `Component-Transport.md:21` + CLAUDE.md #24 to state area membership does not travel.
|
||||
3. **[Medium] Options-validation sweep**: add `IValidateOptions<>`/`ValidateOnStart` to the ~14 unvalidated option classes (Transport, StoreAndForward, SiteRuntime, Communication, InboundAPI, etc.), copying the existing HealthMonitoring/ClusterInfrastructure validator pattern.
|
||||
4. **[Medium] Close the AuditLog reconciliation NodeB gap** (`SiteEnumerator.cs:64`): dial NodeB when NodeA is unreachable — the shape (`SiteEntry`) already supports it, and reconciliation is the pipeline's loss-recovery safety net.
|
||||
5. **[Medium] Honor or remove `SmsConfiguration.MaxRetryCount`** (`Commons/Entities/Notifications/SmsConfiguration.cs:36`) — an admin-editable field with no effect is worse than no field.
|
||||
6. **[Medium] Decide the PerformanceTests story**: rename to reflect its correctness-at-scale scope, or add the two highest-value real measurements (failover time, gRPC stream throughput) behind the existing `Category=Performance` trait.
|
||||
7. **[Low/Medium] Excise the vestigial site-side notification-list surface** (`SiteNotificationRepository` reads + `StoreNotificationListAsync`) now that the purge guarantees the table stays empty.
|
||||
8. **[Low] Unify role-string casing handling** between UI `RequireClaim` policies and the ManagementActor check (`ManagementActor.cs:1910`) — normalize at write (already validated canonically) and compare consistently.
|
||||
9. **[Low] Backfill the three missing `docs/components/` reference docs** (ScriptAnalysis, KpiHistory, DelmiaNotifier) or scope the README claim.
|
||||
10. **[Low] Add contract-lock tests for the top ClusterClient message records** (mirror the existing proto contract tests) to enforce, not just intend, additive-only evolution under version skew.
|
||||
30 test projects (all in the slnx), **~5,620 `[Fact]`/`[Theory]` attribute sites** (round 1: ~5,050; +~570 from the fix plans), ~170k test LOC against ~212k src LOC. Executed-case counts run higher with Theory expansion — the 2026-07-10 terminal sweeps report e.g. CentralUI 898, Commons 609, SiteRuntime 494, ManagementService 448, CLI 359 — roughly **6,300+ executed cases**. Attribute counts per project (grep-based, approximate):
|
||||
|
||||
| Project | Attrs | Δ vs r1 | Notes |
|
||||
|---|---|---|---|
|
||||
| CentralUI.Tests | 860 | +31 | still the largest suite |
|
||||
| Commons.Tests | 506 | +41 | now incl. ClusterClientContractLockTests |
|
||||
| SiteRuntime.Tests | 497 | +40 | |
|
||||
| TemplateEngine.Tests | 452 | +19 | |
|
||||
| ManagementService.Tests | 306 | +72 | frozen 138-command authz matrix + dispatch-coverage guard (PLAN-07 UA2) |
|
||||
| CLI.Tests | 300 | +21 | **now in the slnx** |
|
||||
| Communication.Tests | 269 | +58 | live-alarm-cache/aggregator suites added |
|
||||
| AuditLog.Tests | 263 | +26 | |
|
||||
| InboundAPI.Tests / ConfigurationDatabase.Tests | 234 / 234 | +16 / +15 | |
|
||||
| DataConnectionLayer.Tests | 200 | +10 | incl. Stub browse/search tests |
|
||||
| Host.Tests | 164 | +22 | incl. the site-notification design-invariant lock |
|
||||
| Transport.Tests / Transport.IntegrationTests | 148 / 96 | +24 / +36 | round-trip equivalence guard (PLAN-05 T8) |
|
||||
| StoreAndForward.Tests / Security.Tests | 144 / 144 | +28 / +11 | |
|
||||
| NotificationOutbox.Tests | 132 | +10 | |
|
||||
| DeploymentManager.Tests | 112 | +14 | still thin relative to scope (register row) |
|
||||
| HealthMonitoring.Tests | 94 | +10 | |
|
||||
| ExternalSystemGateway.Tests | 85 | +16 | |
|
||||
| IntegrationTests | 74 | +5 | incl. `Cluster/TwoNodeClusterFixture` + failover/recovery drills |
|
||||
| SiteEventLogging.Tests | 58 | +4 | |
|
||||
| SiteCallAudit.Tests | 44 | +13 | grew ~40% but still the thinnest per src LOC (register row) |
|
||||
| ScriptAnalysis.Tests / NotificationService.Tests | 43 / 43 | +3 / +7 | |
|
||||
| CentralUI.PlaywrightTests | 36 | 0 | 8 env-only failures documented (need live docker cluster) |
|
||||
| KpiHistory.Tests | 29 | +15 | doubled with rollup coverage |
|
||||
| DelmiaNotifier.Tests | 23 | 0 | |
|
||||
| ClusterInfrastructure.Tests | 18 | +3 | |
|
||||
| PerformanceTests | 12 | +2 | see below |
|
||||
|
||||
**PLAN-08's new tests are real, with one asterisk.** The contract-lock suite (9 tests) does exactly what the review asked and surfaced a genuine wire-behavior finding (Newtonsoft `default(T)` on missing trailing fields — now locked as documented reality). The stream-throughput test is a genuine measurement (100k events through the real `SiteStreamManager` BroadcastHub, asserting ≥10k events/s and ≥90% delivery). The asterisk is the failover-timing placeholder: it shipped as designed (a documented, skipped protocol), but its skip precondition is already satisfied — see NF2.
|
||||
|
||||
---
|
||||
|
||||
## 4. Deferred-Work Register Assessment
|
||||
|
||||
The register (`docs/plans/2026-07-08-deferred-work-register.md`) exists, is committed, and is substantially accurate — including a "Resolved (verified against the code 2026-07-10)" section whose ten resolution claims I spot-verified (items 7, 10, 13, 14, 15, 16, 20, 21, 22, 12-CLI: all confirmed in source, citations in §1 above). Post-initiative work genuinely retired two register rows (#10 live alarm stream, #22 KPI rollups) and most of a third (#12 CSV import — only the Blazor upload affordance remains).
|
||||
|
||||
**Remaining open deferrals (verified still-accurate):** #8 hash-chain (CLI `verify-chain` still a no-op), #9 Parquet (501), #11 central cert-trust persistence, #12 (UI upload button only), #17 unified outbox page, #19 bundle signing/pull/differential, plus the four "new deferrals from review 08" rows (layering inversion — still present at `CentralCommunicationActor.cs:13`; three reference docs; SiteCallAudit/DeploymentManager test backfill; perf envelope). #18 is permanently closed. **Net: ~10 open deferrals, all with rationale + trigger, none mislabeled.**
|
||||
|
||||
Two register-integrity problems, though:
|
||||
|
||||
1. **One revisit trigger has already fired unnoticed** — the perf-envelope row's trigger is "PLAN-01 rig landing", and `tests/.../IntegrationTests/Cluster/TwoNodeClusterFixture.cs` landed 2026-07-08, *before* the placeholder itself shipped (2026-07-10). Nobody revisited (NF2).
|
||||
2. **The register is no longer the single place.** The master tracker grew its own initiative-level "Deferred / Won't-Fix Registry" (`archreview/plans/00-MASTER-TRACKER.md:137+`) holding items absent from the register — most notably the **SBR oldest-crash total-outage gap** (a real operational risk, owner: user decision) and the unapplied `deploy/wonder-app-vd03` overlay edits (whose absence NULLs `SourceNode` on that deployment's audit rows). An untracked root-level `deferred.md` snapshot duplicates the register a third time and has already drifted from it (NF5). The register's own Fix-now table also violates its stated remove-when-landed rule — all 7 rows landed but are still listed as open.
|
||||
|
||||
---
|
||||
|
||||
## 5. New Findings (round 2)
|
||||
|
||||
### NF1 [High] — Live production API key in plaintext at the repo root
|
||||
`test.txt` (untracked, repo root) contains a working Inbound API key for the production host `wonder-app-vd03.zmr.zimmer.com:8085` (`sbk_eb5acc0b…`, full key present). It is one `git add -A` (or one run of the `pushit` skill, which stages everything) away from entering history, and it is a live credential regardless. **Rotate/revoke the key** (the dual-key rotation procedure from PLAN-06 T24 exists for exactly this), delete the file, and consider a `.gitignore`/pre-commit secret-scan guard for the root. (File: `/Users/dohertj2/Desktop/ScadaBridge/test.txt`.)
|
||||
|
||||
### NF2 [Medium] — Failover-timing test's skip precondition is already satisfied; the 25s envelope remains unmeasured
|
||||
`tests/ZB.MOM.WW.ScadaBridge.PerformanceTests/Failover/FailoverTimingTests.cs:26-28` skips with "Requires the real two-node failover rig delivered by PLAN-01 … wire it to the rig when PLAN-01 lands" — but PLAN-01's rig landed 2026-07-08 (`tests/ZB.MOM.WW.ScadaBridge.IntegrationTests/Cluster/TwoNodeClusterFixture.cs`), two days before this placeholder shipped. The design's single most-quoted performance claim (~25s failover) is still unverified by any test, the register's revisit trigger for this row has fired, and the skip message is now misleading. Wire the protocol to the fixture (or update the skip reason + register row with the real blocker, e.g. "in-proc rig can't hard-kill a process").
|
||||
|
||||
### NF3 [Medium] — CHANGELOG.md abandoned and now factually wrong
|
||||
`CHANGELOG.md` was last touched 2026-06-02 (`b104760b`), 268+ commits and the entire arch-review initiative ago; its only entry sits under `[Unreleased]`. Worse, that entry asserts "`Operator`/`Engineer` are unused" (`CHANGELOG.md:11-12`) — false since M7 shipped the `Operator`/`Verifier` secured-write roles. A changelog that is both stale and wrong is worse than none: either backfill it from the merge history (the master tracker's wave summaries are ready-made material) or delete it and declare the tracker + `docs/plans/` the release record.
|
||||
|
||||
### NF4 [Low] — Options-validation residue: AuditLog sub-options and Host-owned options still bind without validation
|
||||
PLAN-08 covered the 12 primary component options, but six AuditLog sub-option classes bind via `AddOptions<T>().Bind(...)` with no `.ValidateOnStart()` and no validator: `SqliteAuditWriterOptions`, `SiteAuditTelemetryOptions`, `SiteAuditRetentionOptions` (`AuditLog/ServiceCollectionExtensions.cs:100-108`) and `AuditLogPartitionMaintenanceOptions`, `AuditLogPurgeOptions`, `SiteAuditReconciliationOptions` (`:368-390`). Host-owned `NodeOptions`, `DatabaseOptions`, `LoggingOptions` remain plain `services.Configure<>` (`Host/SiteServiceRegistration.cs:160-172`) — notable because an empty `NodeOptions.NodeName` silently NULLs the `SourceNode` audit column (the exact failure mode the tracker logged for wonder-app-vd03). Also, `OperationTrackingOptions` has no binding site at all (only consumed at `SiteRuntime/Tracking/OperationTrackingStore.cs:59`) — defaults-only, not configurable from appsettings; either bind it or document that it is code-configured.
|
||||
|
||||
### NF5 [Low] — Deferral tracking has re-scattered into three places, one of them untracked and drifting
|
||||
The register's charter ("new deferrals get appended here instead of scattering") is already violated: (a) the master tracker's own registry (`archreview/plans/00-MASTER-TRACKER.md:137-150`) holds the **SBR oldest-crash gap** and the **wonder-app-vd03 overlay edits** — arguably the two highest-risk open items in the whole program — with no register rows; (b) an untracked root `deferred.md` snapshot duplicates the register and has drifted (it counts "12 open deferrals" vs the register's ~10; different section layout); (c) the register's Fix-now table still lists all 7 landed items despite its remove-when-landed rule. Fold the tracker's two live items into the register, delete or commit-and-subordinate `deferred.md`, and clear the Fix-now table.
|
||||
|
||||
### NF6 [Low] — Stale comment contradicts the shipped Transport area export
|
||||
`Transport/Serialization/EntitySerializer.cs:553-554`: "Areas don't travel (see export TODO), so AreaId stays null" — but the export TODO was fixed (area travels by name, `:268-273`) and no TODO exists anymore. The comment sits in `FromBundleContent`, which still materializes `AreaId = null`; it is off the importer's path (BundleImporter walks the raw DTO), so this is comment drift plus a round-trip-fidelity trap in the helper rather than a functional bug — but it is precisely the "half-fixed-looking" confusion round 1 flagged for this feature. Update the comment (and either populate `AreaName`-awareness in the helper or say why it intentionally doesn't).
|
||||
|
||||
### NF7 [Low] — Untracked working-tree residue, including an uncommitted design plan
|
||||
Beyond `test.txt` (NF1): `docs/plans/2026-06-30-mes-alarm-status-api.md` is a 12-day-old draft plan sitting untracked inside `docs/plans/` — the repo's docs-as-spec convention says plan docs are committed decision records; untracked, it is invisible to every other session and one `git clean` from gone. `ScadaBridge-docs-fixed.md` / `ScadaBridge-docs-issues.md` (root-level generated reports) and `.claire/` (tool worktrees) are also untracked clutter. Commit the plan doc (marked Draft), and remove or relocate the rest.
|
||||
|
||||
### NF8 [Low] — Duplicate `DataConnectionOptions` binding in the Host
|
||||
`Host/SiteServiceRegistration.cs:140` binds `DataConnectionOptions` via `services.Configure<>` even though `AddDataConnectionLayer()` (called at `:51`) already binds it with `.ValidateOnStart()` (`DataConnectionLayer/ServiceCollectionExtensions.cs:17-19`). Both bindings compose so validation still runs, but the duplicate invites section-name drift between the two sites. Delete the Host copy (same applies to the `CommunicationOptions`/`NotificationOptions` `Configure<>` calls in `BindSharedOptions` at `:166/:171`, which are redundant on roles where the owning SCE runs).
|
||||
|
||||
---
|
||||
|
||||
## 6. What's Genuinely Good
|
||||
|
||||
- **The fix plans actually fixed things.** 20 of 23 actionable round-1 items verified fixed at the exact seams the review pointed at, with zero regressions found in this sweep — and several fixes exceeded the ask (the contract-lock suite surfaced a real wire-behavior discovery; the round-trip equivalence guard in Transport caught 5 silent-data-loss bugs the per-field tests missed).
|
||||
- **Convention discipline survived 268 commits of high-velocity parallel-agent churn.** UTC still total, Commons still pure, TODO count went 2 → 0, correlation IDs on every new message, central package management unviolated, no third-party UI frameworks crept in.
|
||||
- **The two post-initiative features shipped convention-complete on day one**: both came with validated options (live-cache fields in `CommunicationOptionsValidator`, rollup bounds in `KpiHistoryOptionsValidator`), tests, design-doc updates (`Component-KpiHistory.md`, the 2026-07-10 plan docs), CLAUDE.md updates, and register-row closure — the "docs + code + tests together" rule working as designed, unprompted.
|
||||
- **Deferral honesty**: shipped no-ops and 501s still say what they are; the register's Resolved section shows items being verified against code before being marked done, and two register rows were retired by real shipped work within 48 hours of the register's creation.
|
||||
- **Test posture improved where it was weakest**: KpiHistory.Tests doubled, SiteCallAudit.Tests +40%, the frozen 138-command authorization matrix turned the largest unaudited security surface into a locked contract, and CLI.Tests finally runs at the solution level.
|
||||
|
||||
---
|
||||
|
||||
## 7. Prioritized Recommendations (round 2)
|
||||
|
||||
1. **[High] Rotate the exposed wonder-app-vd03 API key and delete `test.txt`** (NF1); consider a root-level secret-scan/gitignore guard.
|
||||
2. **[Medium] Wire `FailoverTimingTests` to `TwoNodeClusterFixture`** or update the skip reason + register row with the true blocker (NF2) — the 25s envelope is the last unmeasured headline design claim.
|
||||
3. **[Medium] Fix or kill CHANGELOG.md** (NF3) — at minimum correct the now-false Operator/Engineer claim.
|
||||
4. **[Low] Finish the options sweep's tail**: ValidateOnStart + validators for the 6 AuditLog sub-options and a minimal `NodeOptions` validator (non-empty `NodeName` when auditing is active) (NF4); drop the duplicate Host bindings (NF8).
|
||||
5. **[Low] Re-consolidate deferral tracking** (NF5): move the SBR gap + wonder-app overlay items into the register, clear the landed Fix-now rows, delete/subordinate root `deferred.md`.
|
||||
6. **[Low] Commit `docs/plans/2026-06-30-mes-alarm-status-api.md` and clean root clutter** (NF7); fix the stale `EntitySerializer.cs:553` comment (NF6).
|
||||
|
||||
## Severity Tally — NEW findings only
|
||||
|
||||
| Severity | Count | Findings |
|
||||
|---|---|---|
|
||||
| Critical | 0 | — |
|
||||
| High | 1 | NF1 (production API key in working tree) |
|
||||
| Medium | 2 | NF2 (failover envelope unmeasured, stale skip), NF3 (CHANGELOG abandoned + wrong) |
|
||||
| Low | 5 | NF4 (options residue), NF5 (deferral scattering), NF6 (stale Transport comment), NF7 (untracked plan doc + clutter), NF8 (duplicate bindings) |
|
||||
|
||||
Reference in New Issue
Block a user