fix(modbus): review r3 - one address authority (ModbusTagAddressResolution)

N3: a keyless blob carrying the addressResolved marker was served from its
string by the driver but saved as HR[0] by the editor model / CSV path. The
precedence rule now lives in ONE pure resolver in Driver.Modbus.Contracts
(ModbusTagAddressResolution.Classify/Resolve): keys when region+address are
both present, else a string-only addressString resolved with the driver's
family (refused when it cannot parse), else refused. The marker never decides.

Consumers that now ask it instead of restating it: the driver's tag reader
(ModbusTagDefinitionFactory.FromTagConfig), the deploy gate (Inspect /
DescribeRejection), the editor model (AddressShape / IsStringOnly /
HasKeyAddress / ResolveWith, ToJson stamping, Validate), the CSV export cells
and the editor hint. The model writes keys only when it loaded them or a field
was authored - never defaults it did not resolve (also closes two divergences
the new matrix found: malformed-string+marker and non-string addressString were
saved as HR[0]).

One table-driven test (ModbusAddressAuthorityAgreementTests) runs 15 persisted
shapes through driver, deploy gate, model load/save/Validate and the real CSV
export->import under all four families; breaking the CSV export or reverting
the model to the marker rule fails it.

Minors: m1 CSV address cells keep the string+marker while it still describes
them (lossless unchanged round trip), drop it when contradicted, and the review
grid reports the drop; m2 the pre-lane test uses the real export residual; m3
unmarked string-only + typed-cell case added; m4 stale docs/summary and the
unused factory parameter removed.

Refs #741

Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
This commit is contained in:
Joseph Doherty
2026-10-22 16:46:21 -04:00
parent 13b29e8d4a
commit 92749402b5
12 changed files with 807 additions and 206 deletions
+22 -5
View File
@@ -243,19 +243,36 @@ than asking users to rewrite spreadsheets.
## Address strings, families and region/type rules (#741)
- **One authority: `ModbusTagAddressResolution` (Driver.Modbus.Contracts).** The
driver's tag reader, the deploy gate (`Inspect`), the AdminUI editor model
(load / save / `Validate` / the hint text) and the CSV import/export all ask
it which part of a blob is the address; none restates the rule. Its
agreement is pinned by one table-driven test that runs every persisted shape
through every consumer (`ModbusAddressAuthorityAgreementTests`).
- **Whenever a tag carries both `region` and `address`, those keys are its
address** — a classic tag, an editor-pinned tag, and a pre-#741 row that
carried an `addressString` beside its keys (the old driver never read the
string) all load from the keys, exactly as before, and a family change never
remaps them. Only a **string-only** blob (an `addressString` with NO
`region`/`address` — CSV / API authoring) is resolved through
`ModbusAddressParser` with the **driver's** `Family` / `MelsecSubFamily`; the
AdminUI never stamps default keys onto it, so it cannot collapse onto HR[0],
and a family that cannot parse it refuses the tag (BadNodeIdUnknown).
`ModbusAddressParser` with the **driver's** `Family` / `MelsecSubFamily`; a
family that cannot parse it refuses the tag (BadNodeIdUnknown). It is never
served as HR[0].
- `"addressResolved": true` is editor display / CSV state only: it never makes
keys authoritative, and a marker on a blob WITHOUT keys is ignored (the blob
is string-only) and dropped on the next editor save.
- The AdminUI never writes default keys it did not resolve: the editor model
stamps `region`/`address`/type only when the loaded blob had the keys, or when
a field is set (hand edit, CSV address cell, Resolve). A string-only,
non-string-`addressString` or address-less blob keeps its keys unwritten, and
its CSV export leaves the address cells blank.
- The editor's "Resolve with driver family" persists the resolved
`region`/`address`/type fields plus `"addressResolved": true` (the pinned
state). Editing a resolved field by hand — in the editor or a typed CSV
address cell — drops the string and the marker.
state). Editing a resolved field by hand in the editor drops the string and
the marker. A typed CSV address cell keeps them only while the string still
describes the cells under some family (so an unchanged export → import of a
pinned row is lossless); a contradicting cell drops them, and the import
review grid reports that column as an override.
- Coils / DiscreteInputs carry only Bool. A bit-region tag persisted with a
register `dataType` (the pre-#741 editor saved `Int16`) is coerced to Bool,
never refused. A register region with `Bool` is refused (use