Files
lmxopcua/src
Joseph Doherty cf2af8a5a2 fix(csv): stop the typed-column merge laundering the TagConfigJson cell (#553)
ModelBackedCsvDriverTagMap.ApplyTypedColumns round-tripped the row's free-form
TagConfigJson cell through a driver model's FromJson/ToJson to fold the typed
columns in. That load is deliberately lenient — it has to be, so a poisoned blob
can be opened and repaired — so a typo'd value under a key the model owns
("dataType":"Intt16") came back out as the model's DEFAULT, before the #551
import gate or any later gate could judge it. The row imported clean and the tag
the operator described was gone. Only keys the model did not own survived to be
judged, which is why RawTagConfigGateTests looked like it covered this.

MTConnect carried the same defect from the export end. It is the one driver whose
blob accepts several spellings per field, and the runtime PREFERS driverDataType
over the dataType key the MtDataType column writes. The export residual carried
that alias (the map owned only the canonical key), so a re-imported row let it
out-rank the very column the operator had just edited — accepted, then silently
ignored at the driver — while the exported cell printed the spelling the driver
was ignoring, or nothing at all.

The fix preserves evidence; it does not move the verdict:

- ApplyTypedColumns writes the base cell's own tokens back over the model's
  serialisation for every key no non-blank typed column overrode. The model still
  stamps defaults for keys the row declares nowhere, so fragment cells compose
  exactly as before.
- CsvTypedColumn gains ReadKeys / OwnedJsonKeys: the full ordered set of spellings
  the RUNTIME reads for that column, mirroring TryFromTagConfig's FirstNonBlank
  pairs. A firing column clears the out-ranking aliases; the export residual drops
  them; the export cell shows the EFFECTIVE value.
- The review grid's override provenance reads the whole owned set, so an
  overridden driverDataType is announced as an overridden dataType would be.

Nothing here decides which values are legal. The verdict stays with
TagConfigValidator on the assembled blob and with IRawTreeService.ImportTagsAsync
behind it — the deliberate reason NOT to follow the issue's own "run the validator
over the pre-merge cell" direction, which would have imposed completeness on a
cell that is legitimately a fragment and false-rejected every typed-column row.

Semantics chosen for the override contradiction: a cell token that a non-blank
column overrides is ACCEPTED with the column winning, and reported by name in
TypedOverrodeFallback. Typed-column-wins is the map's documented contract, the
override is announced rather than silent, and erroring would remove the only way
to repair a poisoned tag through the CSV surface. Every token that reaches the
stored blob is judged; a token the operator overrode is discarded openly.

Falsification: neutering the restore loop + alias handling turns exactly 7 of the
15 new tests red, while RawTagConfigGateTests (#551), RawTagCsvRoundTripTests and
CsvColumnMapReflectionTests all stay green — layering, not replacement.

AdminUI 1248, MTConnect 534, Modbus 373, S7 285, TwinCAT 205 — all green.
2026-08-10 01:18:55 -04:00
..