fix(historian): map Boolean historization to Int2, not degenerate Int1 (#441)
The historian's Int1 analog-tag creation path is server-degenerate: EnsureTags(Int1) stores an unusable stub (live-probed on the deployed 2023 R2 historian; root-caused + documented gateway-side in HistorianGateway #11 / PR #16). HistorianTypeMapper mapped DriverDataType.Boolean -> HistorianDataType.Int1, so Boolean historized tags failed auto-provisioning. Map Boolean -> Int2 instead (store 0/1). Int2 is a proven-creatable analog type, and the value-write path already sends every value as a double on WriteLiveValues regardless of tag type, so Boolean 0.0/1.0 round-trips cleanly. Both Float and Int2 are supported, so a pre-existing Float Boolean tag retypes cleanly on its next deploy. - HistorianTypeMapper: Boolean => Int2 (+ remarks) - Unit pins: HistorianTypeMapperTests, GatewayTagProvisionerTests (Boolean_definition_carries_explicit_Int2_presence) - Live gate: EnsureTags_boolean_sandbox_provisions_as_Int2 (was _Int1); retype probe reframed Float->Int2 (drops the dead Int1 leg) - Docs: docs/Historian.md 'Boolean historization maps to Int2', CLAUDE.md, archreview STATUS.md (R2-06 live gate -> 6/6) Resolves #441.
This commit is contained in:
+21
-14
@@ -327,24 +327,31 @@ convention) + a regression unit test + `StorageRateMs` on the live-suite inline
|
||||
- ✅ `Write_then_read_on_sandbox_tag` — `EnsureTags(Float)` + `WriteLiveValues` (SQL) + read-back.
|
||||
- ✅ `Alarm_SendEvent_is_acked` + ✅ `Alarm_SendEvent_then_ReadEvents` — alarm `SendEvent` + SQL `ReadEvents`.
|
||||
- ✅ `EnsureTags_type_change_outcome_is_observable` — now observes a **real** retype outcome (not a storage-rate error).
|
||||
- ❌ `EnsureTags_boolean_sandbox_provisions_as_Int1` — **Int1 (Boolean) tag creation is not supported by the
|
||||
gateway's histsdk on this historian**: `EnsureTags(Int1)` returns `ProtocolEvidenceMissingException` (the vendored
|
||||
client's "no capture evidence for this type" guard → gRPC `Unimplemented`), both fresh and as a retype target.
|
||||
A **type sweep** confirmed it is Int1-specific: **Int2/UInt2/Int4/UInt4/Float/Double all provision `success=True`;
|
||||
only Int1 fails.** Root cause: `histsdk` `HistorianTagWriteProtocol.GetAnalogDataTypeCode` has no captured
|
||||
CTagMetadata wire code for Int1. This is a **gateway/histsdk type-support gap**, not an OtOpcUa defect — **filed as
|
||||
[historiangw#11](https://gitea.dohertylan.com/dohertj2/historiangw/issues/11)**.
|
||||
- ⚠️ `EnsureTags_boolean_sandbox_provisions_as_Int1` (as originally written) — **Int1 (Boolean) tag creation is not
|
||||
supported by the gateway's histsdk on this historian**: `EnsureTags(Int1)` returns `ProtocolEvidenceMissingException`
|
||||
(the vendored client's "no capture evidence for this type" guard → gRPC `Unimplemented`), both fresh and as a retype
|
||||
target. A **type sweep** confirmed it is Int1-specific: **Int2/UInt2/Int4/UInt4/Float/Double all provision
|
||||
`success=True`; only Int1 fails.** Root cause: `histsdk` `HistorianTagWriteProtocol.GetAnalogDataTypeCode` has no
|
||||
captured CTagMetadata wire code for Int1 — a **gateway/histsdk type-support gap**, not an OtOpcUa defect
|
||||
(**filed as [historiangw#11](https://gitea.dohertylan.com/dohertj2/historiangw/issues/11)**, which routed the
|
||||
decision back as **[issue #441](https://gitea.dohertylan.com/dohertj2/lmxopcua/issues/441)**).
|
||||
|
||||
**✅ RESOLVED (issue #441, 2026-07-14):** `HistorianTypeMapper` now maps **`Boolean → Int2`** (0/1 integer) instead
|
||||
of the degenerate `Int1`. Int2 is a proven-creatable analog type on this historian, and the value-write path already
|
||||
sends every value as a `double` on `WriteLiveValues` regardless of tag type, so Boolean `0.0/1.0` round-trips
|
||||
cleanly. The live gate test is renamed `EnsureTags_boolean_sandbox_provisions_as_Int2` and now passes → **6/6**. Unit
|
||||
pins updated (`HistorianTypeMapperTests`, `GatewayTagProvisionerTests.Boolean_definition_carries_explicit_Int2_presence`);
|
||||
gateway driver unit `103/103`.
|
||||
|
||||
**✅ U-7 retype policy — ANSWERED (via `grpcurl` against the real historian):**
|
||||
- `EnsureTags` is a **real create-or-update upsert** — re-`EnsureTags` of an existing tag at the same type → `success`.
|
||||
- An **in-place retype between SUPPORTED types is ACCEPTED** — `Float→Double` on an existing tag returns `success`
|
||||
(of the three 06/U-7 hypotheses: **accepted-retyped**, not rejected).
|
||||
- **But the specific 0.2.0 Boolean case (Float→Int1) can't retype** — Int1 isn't a creatable type here, so a
|
||||
pre-0.2.0 **Float Boolean tag asked to become Int1 fails the provision** with `ProtocolEvidenceMissingException`
|
||||
— it is **neither silently retyped nor data-lost**; the provision just fails (and is metered `failed=N`). That is
|
||||
the practically-relevant U-7 answer for Boolean tags on this deployment.
|
||||
- **The specific `Int1` case can't retype** — Int1 isn't a creatable type here. Rather than accept a broken Boolean
|
||||
path, OtOpcUa **remapped `Boolean → Int2`** (issue #441, above), which retypes cleanly from a pre-existing Float
|
||||
Boolean tag (both are supported analog types) and never hits the Int1 gap.
|
||||
|
||||
The R2-06 **code** deliverables (S-11 fail-fast + `ServerHistorianOptionsValidator`; U-7 Boolean→Int1 mapping +
|
||||
The R2-06 **code** deliverables (S-11 fail-fast + `ServerHistorianOptionsValidator`; Boolean provisioning mapping +
|
||||
upsert; C-7 doc) were already unit-covered + merged; this live gate additionally **found + fixed the StorageRateMs
|
||||
provisioning bug** and **answered the U-7 retype policy**. The one remaining red test is a documented gateway
|
||||
histsdk Int1-support gap.
|
||||
provisioning bug**, **answered the U-7 retype policy**, and surfaced the Int1 histsdk gap that drove the
|
||||
**Boolean→Int2 remap (issue #441)** — closing the last red test to reach **6/6**.
|
||||
|
||||
Reference in New Issue
Block a user