diff --git a/deferment.md b/deferment.md index 59d85e0c..2227fc5c 100644 --- a/deferment.md +++ b/deferment.md @@ -158,7 +158,7 @@ Every gap below is in the **authoring/dispatch layer, not driver runtime code**. | G-1 | **`DriverConfigModal.razor` has no `Calculation` case** — falls to the `default` "No typed config form" warning; no `CalculationDriverForm.razor` exists | `DriverConfigModal.razor:34-72`, default arm `:69-71` | `CalculationDriverOptions.RunTimeout` (`CalculationDriverOptions.cs:19`) is **unauthorable via the UI**. Create/rename still work, so degraded — not a hard block. **This is the same class as the Sql picker defect, one modal further downstream.** | ✅ Fixed — `CalculationDriverForm.razor` | | G-2 | **`DeviceModal.razor` is missing Sql, MTConnect, Calculation** | `DeviceModal.razor:42-74`, default arm `:71-73` | Mitigated: Sql/MTConnect hold connection at the **driver** level, and Test Connect still works via `BuildMergedProbeConfig` (`:180-184`). | ✅ Fixed — declared **single-connection** rather than given hollow device forms | | G-3 | **`DriverConfigModal.razor:77` tells Sql/MTConnect operators the wrong thing** — the single-connection branch is hardcoded to `Galaxy or Mqtt`, so Sql/MTConnect users are told the endpoint lives on the *device* when they just authored it on the driver | `DriverConfigModal.razor:77`, `:86-88` | Actively misleading, compounds G-2. | ✅ Fixed — `DeviceFormMap.IsSingleConnection` | -| G-4 | **No parity test guards `DriverConfigModal` or `DeviceModal`** — `RawDriverTypeDialogCoverageTests`/`…ParityTests` assert `DriverTypeNames` ↔ **picker** parity only | — | **This is exactly why G-1 and G-2 survived review.** The guard pattern exists and simply was not extended to the two downstream dispatch maps. | ✅ Fixed — `DriverFormMapParityTests` + `DriverDispatchMapParityTests` | +| G-4 | **No parity test guards `DriverConfigModal` or `DeviceModal`** — `RawDriverTypeDialogCoverageTests`/`…ParityTests` assert `DriverTypeNames` ↔ **picker** parity only | — | **This is exactly why G-1 and G-2 survived review.** The guard pattern exists and simply was not extended to the two downstream dispatch maps. | ✅ Fixed — `DriverFormMapParityTests` + `DriverDispatchMapParityTests` (since #558/`7482cf0c` the browse-commit fact lives in `RawBrowseCommitMapperParityTests`; `DriverDispatchMapParityTests` guards CsvColumnMap only) | | G-5 | **`CsvColumnMap` has no typed columns for Sql, MQTT, MTConnect** | `CsvColumnMap.cs:340-452` | CSV import/export degrades to the raw `TagConfigJson` fallback while the 7 older drivers get typed columns. | ✅ Fixed — CSV maps for Sql/Mqtt/MTConnect | | G-6 | **`RawBrowseCommitMapper` has no Sql branch** — falls to the generic `WriteSingleKey("address", …)` whose comment claims "Browsable drivers are all handled above" | `RawBrowseCommitMapper.cs:121-141`, fallback `:145` | Untrue since `SqlDriverBrowser` was registered (`EndpointRouteBuilderExtensions.cs:83`). Same failure mode the MTConnect branch (`:135-139`) was added to avoid: typed editor opens empty, blanks the field on save. | ✅ Fixed — **and at the browser end too**, which the audit missed | | G-7 | **`EquipmentTagConfigInspector` covers only 6 driver types** | `:24-29` | Pre-existing (OpcUaClient + Galaxy absent too); no new driver was added. **The audit understated it:** the AdminUI's `TagConfigValidator` covers **11 of 12**, so the *deploy* gate is weaker than the *authoring* gate — and config arriving by CSV import or API only meets the deploy gate. | ✅ **CLOSED 2026-08-09** — MQTT wired + reflection guard (2026-07-28), then the four remaining drivers gained Contracts-side `Inspect` wrapping their runtime parses (merge `0695f682`). The gate now covers **11 of 12** — parity with the AdminUI validator; Galaxy is the sole unmapped type on both, symmetrically. See §9 | @@ -1280,6 +1280,56 @@ drivers), 3 carried-open archreview findings led by 01/S-1 (`AddressSpaceApplier --- +### 2026-08-10 (fourth round) — #556 + #557 + #558 closed: Inspect trims and classifies; the last alias and the last hand-list fall + +The three residuals of the #554/#555 round, three isolated-worktree Opus agents, merged `7482cf0c` +(#558), `93a05828` (#557 OpcUaClient half), `be15ed16` (#556 + #557 Mqtt half). Consolidated +verification on the merged tree: full build clean; 14 suites / 4,583 tests green (AdminUI 1460, +ControlPlane 261, Modbus 395, S7 302, AbCip 409, AbLegacy 243, TwinCAT 219, FOCAS 355, Mqtt 686, +OpcUaClient 143, OpcUaClient.Browser 10, Galaxy.Browser 21, Sql.Browser 79). + +- **#556 CLOSED** — all seven Contracts factory `Inspect`s `TrimStart()` before the `[0] != '{'` + framing check, and each classifies its findings **structurally** into two lists — `rejections` + (the parse would refuse) and `advisories` (accepted, worth flagging) — with the "rejected blob + never draws zero warnings" backstop gating on `rejections.Count == 0`, not the combined count + (an advisory can no longer satisfy the backstop vacuously; no string matching anywhere). The fix + surfaced a discovery: **MQTT's runtime mappers uniquely also carry the framing gate**, so for + MQTT a leading-whitespace blob is genuinely refused at runtime and its trimmed Inspect reports a + real rejection — accept-gate-first symmetry holds. The other six runtimes tolerate the whitespace + (`JsonDocument.Parse` skips it), a cross-driver served-behavior inconsistency deliberately NOT + changed here (an Inspect-wording issue must not change served behavior) — **filed as #559**. + FOCAS's backstop is unreachable today (no unnamed refusal exists); pinned as a count relation so + it fails the day one appears, rather than deleted. +- **#557 CLOSED, both halves.** *OpcUaClient:* legacy `NodeId` was readable at runtime but the + editor dropped it — `OpcUaClientTagConfigModel` gained `NodeIdKeys` + `ReadNodeId` implementing + the runtime's **presence-shadows** semantics (`TryGetProperty("nodeId") || TryGetProperty("NodeId")`, + then one ValueKind/non-blank check) — deliberately NOT MTConnect's `FirstNonBlank`: a + present-but-blank `nodeId` short-circuits the `||`, blocks fallback, and the blob is REJECTED — + blank-beside-valid-legacy must refuse, and now does in both layers. `ToJson` drops aliases keyed + off the SAME array (#550 pattern). The `TagConfigNonEnumParityTests` OpcUaClient exemption is + removed — the anti-vacuity control (`Every_deliberately_stricter_entry_is_still_exactly_that`) + is what would have caught it going stale. *Mqtt:* the union parse verdict is the **decided + contract**, not a bug — `FromSparkplugTagConfig` hardcodes `PayloadFormat.Json` and never reads + the `payloadFormat` key, so a typo'd value on a sparkplug-shaped blob is genuinely servable and + refusing it would be stricter than runtime. Mitigation: a **mode-dependency advisory** on the + accepted blob (advisories ride accepted blobs by design, so the rejection backstop never trips). +- **#558 CLOSED** — one guard per seam: the browse-commit dispatch fact moved out of + `DriverDispatchMapParityTests` (now CsvColumnMap-only) into `RawBrowseCommitMapperParityTests` + (now 6 facts, the sole guard for that seam; subsumption proven under the same neuters before the + old fact was deleted). The hand-maintained `NonBrowsableDriverTypes` list is gone — the browsable + set derives from the real production `AddAdminUI()` registration, the non-derivable universal + arm covered by `DriverTypeNames.All` enumeration + `GenericKeyExemptions` + a + contradiction-refuting fact. **Ordinal (not IgnoreCase) membership compare is load-bearing** — a + case-only typo in a browser key is exactly the drift the guard exists to catch. Galaxy/OpcUaClient + browser `DriverType` literals became `DriverTypeNames` constants (Galaxy's "GalaxyMxGateway" + exact-matched before substitution), closing the last string-literal channel into the derived guard. + +**Residual filed:** **#559** — align MQTT's runtime whitespace handling with the fleet (or declare +the framing gate the contract). The authoring-vs-deploy-gate drift arc (#549 → #550/#551 → +#552/#553 → #554/#555 → #556/#557/#558) is otherwise closed end to end. + +--- + ### 2026-08-10 (third round) — #554 + #555 closed: authoring consults the parse; the cited guard now exists The two residuals of the #552/#553 round, again two isolated-worktree Opus agents, merged `7cfe7b4a`