code-reviews: 2026-06-18 re-review of array-write-ergonomics feature at 88915c3
Re-reviewed the 10 modules touched by the MxSparseArray / array-write ergonomics work (8df5ab3..88915c3). 16 new findings: - Server-057 (Medium): [] AddItem normalization skips AddItemBulk/AddBufferedItem - Client.Dotnet-030 (Medium): advise-supervisory missing from IsKnownGatewayCommand (dead command) - 14 Low: MxSparseArray doc/test gaps, advise-supervisory CLI gaps across clients, Client.Java-049 / Client.Python-037 version-bump consistency misses Worker.Tests and IntegrationTests clean. Worker unchanged by the feature, not re-reviewed.
This commit is contained in:
@@ -4,10 +4,10 @@
|
||||
|---|---|
|
||||
| Module | `src/ZB.MOM.WW.MxGateway.Server` |
|
||||
| Reviewer | Claude Code |
|
||||
| Review date | 2026-06-16 |
|
||||
| Commit reviewed | `8df5ab3` |
|
||||
| Review date | 2026-06-18 |
|
||||
| Commit reviewed | `88915c3` |
|
||||
| Status | Re-reviewed |
|
||||
| Open findings | 0 |
|
||||
| Open findings | 2 |
|
||||
|
||||
## Checklist coverage
|
||||
|
||||
@@ -69,6 +69,23 @@ findings (Server-001 through Server-032) are unchanged by this pass.
|
||||
| 9 | Testing coverage | Issues found: Server-037 (no test for the corrupt-snapshot restore path or for `PersistSnapshot = false` at the cache level). |
|
||||
| 10 | Documentation & comments | No issues found — XML docs match behavior; the `GalaxyRepository.md` "On-disk snapshot" section documents the Stale-on-restore lifecycle. |
|
||||
|
||||
### 2026-06-18 review (commit 88915c3)
|
||||
|
||||
Re-review of the array-write-ergonomics feature (`git diff 8df5ab3..88915c3 -- src/ZB.MOM.WW.MxGateway.Server/`): the new `SparseArrayExpander` and `ArrayAddressNormalizer`, the `NormalizeOutboundCommand` choke point in `GatewaySession.InvokeAsync`, the re-normalize at the `TrackCommandReply`/`MapCommand` tracking call sites, and the `ConstraintEnforcer.ResolveTarget` `[]`-suffix fallback. The range also lands the already-filed Server-055 (`_everHadEventSubscriber` detach-grace gate) and Server-056 (`SessionEventDistributor._completed` late-registrant guard) resolutions; both were re-verified sound here and remain closed. Security focus — the authorization `[]` fallback: it changes only *which* Galaxy metadata record resolves (turning a spurious `tag_metadata` deny into a real scope/classification decision), stays `IsArray`-gated, and the scope check (`MatchesPathOrTag` on `ContainedPath` + the original bare `tagAddress`) still runs unchanged, so a scoped key cannot reach a non-array tag or an out-of-scope array. The authz check (bare address) and the worker bind (suffixed via the same `IsArray`-gated probe) resolve the identical attribute — no check-vs-bind mismatch. Worker untouched; parity preserved (single whole-array COM write). Two new findings.
|
||||
|
||||
| # | Category | Result |
|
||||
|---|---|---|
|
||||
| 1 | Correctness & logic bugs | Issues found: Server-057 (`NormalizeOutboundCommand`/`MapCommand` normalize only single `AddItem`/`AddItem2`; bare array addresses added via `AddItemBulk`/`AddBufferedItem` register non-write-capable handles). SparseArrayExpander verified sound: total_length=0 / oversize / out-of-range / duplicate / kind-mismatch all → `InvalidArgument`; `Array.MaxLength` guard precedes the `(int)` cast; Int32-vs-Int64 selection mirrors the worker converter; expansion targets `Value` not `timestamp_value` across all 8 write variants. |
|
||||
| 2 | mxaccessgw conventions | No issues found — file-scoped namespaces, `sealed`/`internal static`, `Async` suffixes, primary constructors, MXAccess-aligned naming; worker still does an honest whole-array COM write; no UI libraries; no value/secret logging added (write payloads never logged). |
|
||||
| 3 | Concurrency & thread safety | No issues found — `NormalizeOutboundCommand` mutates the `WorkerCommand.Command` deep-clone produced by `MapCommand` (`request.Command.Clone()`), never the client's `MxCommand`, and the tracking path re-normalizes its own separate copy; `ArrayAddressNormalizer`/`SparseArrayExpander` are stateless (read the cache snapshot per call); `_everHadEventSubscriber` and the `SessionEventDistributor._completed` guard serialize correctly under their locks. |
|
||||
| 4 | Error handling & resilience | No issues found — invalid sparse payloads surface as `RpcException(InvalidArgument)` and propagate uncaught (correct client-facing error); the normalizer never throws and passes through when metadata is cold. |
|
||||
| 5 | Security | No issues found — the `ConstraintEnforcer` `[]` fallback is `IsArray`-gated, does not widen scope (scope/classification checks unchanged), and resolves the same attribute the worker binds; covered by `CheckReadTagAsync_WithBareArrayName_*` and the missing-non-array negative test. |
|
||||
| 6 | Performance & resource management | No issues found — expander allocates one array of `total_length` slots (inherent to a whole-array write) and the work is O(length + elements); the normalizer is a single dictionary probe per AddItem. |
|
||||
| 7 | Design-document adherence | No issues found — worker untouched, single whole-array COM write preserved; `gateway.md`, `docs/WorkerConversion.md`, `docs/DesignDecisions.md`, and all five client READMEs were updated in the same commit and match the code (sparse semantics, default-fill reset, `[]` normalization). |
|
||||
| 8 | Code organization & conventions | No issues found — new types live under `Sessions/`, registered as a singleton in `AddGatewaySessions` consistent with `IGalaxyHierarchyCache`; the optional `addressNormalizer` ctor parameter keeps legacy unit-construction paths working. |
|
||||
| 9 | Testing coverage | Issues found: Server-058 (no test asserts a bare-array name that resolves via the `[]` fallback is still *denied* when out of scope, and `CheckWriteHandleAsync` array-via-suffixed-registration / classification is untested). Otherwise strong: `SparseArrayExpanderTests` (210 lines), `ArrayAddressNormalizerTests` (105), and the two `ConstraintEnforcerTests` cases. |
|
||||
| 10 | Documentation & comments | No issues found — XML docs and inline comments on the new types are accurate (the GatewaySession comment correctly notes `MapCommand` deep-clones, contradicting the plan's stale "same instance" note); the `AddItemBulk` gap is documentation-adjacent and is captured under Server-057. |
|
||||
|
||||
### 2026-06-16 re-review (commit 8df5ab3)
|
||||
|
||||
Re-review of the session-resilience epic + §8 delta (`git diff 410acc9..8df5ab3`): `SessionEventDistributor` multi-subscriber fan-out, replay-on-reconnect, detach-grace retention, bounded worker-ready wait, dashboard auto-login.
|
||||
@@ -1084,3 +1101,33 @@ Additionally, `GatewayAlarmMonitor.ApplyProviderModeChangeAsync` increments the
|
||||
**Recommendation:** Record terminal completion (a `_completed` flag plus the terminal error) under `_lifecycleLock` and have both register paths complete a late registrant's channel immediately with the same terminal state.
|
||||
|
||||
**Resolution:** 2026-06-17: added `_completed` + `_completionError`, set inside `CompleteAllSubscribers` under `_lifecycleLock` — the same lock the register paths take, so completion and registration serialize (a subscriber added before the sweep is completed by the loop; one racing in after sees `_completed` and self-completes). `Register` and `RegisterWithReplay` now `TryComplete` a late registrant's channel with `_completionError` when `_completed`; a late resume still receives its retained replay batch, then a cleanly-completed empty live channel. No lock-ordering risk — `CompleteAllSubscribers` takes only `_lifecycleLock`, and the subscriber channels use `AllowSynchronousContinuations=false` so `TryComplete` under the lock runs no continuation inline. New regression `[Fact]` `Register_AfterSourceCompletes_CompletesLateSubscriberInsteadOfHanging` (`SessionEventDistributorTests.cs`) registers a subscriber after the pump completes and asserts its channel completes (bounded read); verified it fails without the fix (5 s timeout) and passes with it (12 ms). The racy `GatewaySessionDashboardMirrorTests.DashboardMirror_AndGrpcSubscriber_BothReceiveEvents` that exposed it was also made deterministic — see Tests-039.
|
||||
|
||||
### Server-057
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Severity | Medium |
|
||||
| Category | Correctness & logic bugs |
|
||||
| Location | `src/ZB.MOM.WW.MxGateway.Server/Sessions/GatewaySession.cs:976-1000` (`NormalizeOutboundCommand`), `:1085-1095` (`MapCommand` tracking), `gateway.md` (array-write ergonomics section), `clients/*/README.md` |
|
||||
| Status | Open |
|
||||
|
||||
**Description:** The array-suffix `[]` normalization runs only for the single-add commands `AddItem` and `AddItem2` — `NormalizeOutboundCommand` has no case for `AddItemBulk` (or `AddBufferedItem`), and the `MapCommand` tracking switch likewise normalizes only the `AddItem`/`AddItem2` arms (`AddItemBulk` flows through `TrackBulkItems`/`AddBufferedItem` with the raw address). MXAccess requires the `[]` suffix on the AddItem address for an array attribute to register a *write-capable* handle. A client that registers a bare array address via `AddItemBulk` therefore binds a non-write-capable handle, and a later `Write`/`WriteSecured`/sparse write against that handle silently lands on a read-only-ish handle — the exact failure mode this feature fixes for the single-add path. The behavior is inconsistent across the add family, and `gateway.md` / the client READMEs describe normalization as happening "at AddItem time" and explicitly carve out only `ReadBulk`, giving no signal that `AddItemBulk` is excluded. `AddBufferedItem` is lower-risk (buffered/historical read items are not normally written) but is the same gap.
|
||||
|
||||
**Recommendation:** Either (a) extend `NormalizeOutboundCommand` and the `MapCommand` tracking path to normalize each `AddItemBulk.TagAddresses` entry (and `AddBufferedItem.ItemDefinition`) the same `IsArray`-gated way, keeping the constraint check, the worker bind, and the stored `SessionItemRegistration.TagAddress` consistent; or (b) if bulk-add normalization is intentionally out of scope for this feature, state that explicitly in `gateway.md` and the client READMEs (alongside the existing `ReadBulk` carve-out) so clients know bulk-added array handles must carry the `[]` suffix themselves to be writable.
|
||||
|
||||
**Resolution:** _(empty until closed)_
|
||||
|
||||
### Server-058
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Severity | Low |
|
||||
| Category | Testing coverage |
|
||||
| Location | `src/ZB.MOM.WW.MxGateway.Tests/Security/Authorization/ConstraintEnforcerTests.cs` |
|
||||
| Status | Open |
|
||||
|
||||
**Description:** The new `ConstraintEnforcer.ResolveTarget` `[]`-suffix fallback is security-sensitive: it turns a bare-array address that previously missed the Galaxy index (→ spurious `tag_metadata` deny) into a real scope/classification decision. The added tests cover the in-scope-allow case (`CheckReadTagAsync_WithBareArrayName_ResolvesViaArraySuffixFallback`) and the missing/non-array negative case (`CheckReadTagAsync_WithMissingNonArrayName_StillFailsToResolve`), but **no test asserts that a bare-array name which now resolves via the fallback is still denied when it is out of the key's read/write scope** — i.e. that the fallback widened resolution but not authorization. There is also no `CheckWriteHandleAsync` test exercising an array handle whose `SessionItemRegistration.TagAddress` is the suffixed form (`Obj.Arr[]`) resolving directly through `ResolveTarget` for the write-scope and `MaxWriteClassification` checks. These are the precise paths a regression could silently widen.
|
||||
|
||||
**Recommendation:** Add a `CheckReadTagAsync` (and a `CheckWriteHandleAsync`) case where the bare/suffixed array attribute resolves but the configured `ReadTagGlobs`/`WriteSubtrees` exclude it, asserting a `read_scope`/`write_scope` `ConstraintFailure` is still returned; and a `CheckWriteHandleAsync` case asserting `MaxWriteClassification` is enforced against the array attribute's `SecurityClassification` via the suffixed registration address.
|
||||
|
||||
**Resolution:** _(empty until closed)_
|
||||
|
||||
Reference in New Issue
Block a user