docs(historian-gateway): mark follow-up plan status — FU-1 documented-limitation, FU-2/3/4 done

Record the execution outcomes in the follow-up plan: FU-1 resolved as a documented
protocol limitation (gateway pending.md C4; not fixable without histsdk wire-capture
evidence), FU-2 done + live-validated (exact round-trip), FU-3 done (mux-ref vs
historian-name decoupled via HistorizedTagRef), FU-4 done. FU-5 (pre-existing Modbus
failure) and FU-6 (post-merge propagation) remain tracked.

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
This commit is contained in:
Joseph Doherty
2026-06-27 00:45:19 -04:00
parent 111adc92b6
commit 00cc1da362
@@ -7,6 +7,22 @@ the live `Category=LiveIntegration` suite is green (read ✅, write-persist ✅,
alarm-readback ⏭ skip). This doc tracks everything deliberately deferred or surfaced during alarm-readback ⏭ skip). This doc tracks everything deliberately deferred or surfaced during
validation, with the **owning repo** for each. validation, with the **owning repo** for each.
> **Execution update (2026-06-27 — this follow-up pass):**
> - **FU-1 — RESOLVED as a documented protocol limitation** (NOT a fixable gateway bug): the captured
> CM_EVENT event-send wire never carries `SourceName`, so `Source_Object` cannot be populated by the
> gateway. Recorded as `pending.md` **C4** + a CLAUDE.md note in the HistorianGateway repo (commit
> `174a4a9` on `fix/gateway-otopcua-followups`). The OtOpcUa live test stays skipped with the corrected
> reason. See FU-1 below for the (now-confirmed) root cause.
> - **FU-2 — ✅ DONE + live-validated** in HistorianGateway (`fix/gateway-otopcua-followups`, commits
> `150868c` + `1c2d11d`). The SQL live-write path converts UTC→server-local in-SQL via
> `DATEADD(MINUTE, DATEPART(TZOFFSET, SYSDATETIMEOFFSET()), @dt)`; an explicit-timestamp round-trip is
> now EXACT against the live historian (delta 00:00:00).
> - **FU-3 — ✅ DONE** in OtOpcUa (this branch, commit `111adc92`): `HistorizedTagRef(MuxRef, HistorianName)`
> carried through the sink/recorder; interest registered by mux ref, values written under the historian
> name. Recorder + applier tests green.
> - **FU-4 — ✅ DONE** in OtOpcUa (this branch, commit `b2276b5b`).
> - **FU-5** — still pre-existing/not-ours (tracked below). **FU-6** — still pending the merges.
**Live-validation harness recap (how to reproduce any of the live findings below):** run the **Live-validation harness recap (how to reproduce any of the live findings below):** run the
HistorianGateway locally against the live historian, then point the OtOpcUa live tests (or `grpcurl`) HistorianGateway locally against the live historian, then point the OtOpcUa live tests (or `grpcurl`)
at it. The gateway boots from env-var config (secrets from `~/.zshenv`): at it. The gateway boots from env-var config (secrets from `~/.zshenv`):
@@ -34,7 +50,15 @@ always include an `EventTimeUtc` range. `sqlcmd -S $HISTORIAN_GRPC_HOST -d Runti
**Owning repo: `~/Desktop/HistorianGateway` (HistorianGateway).** OtOpcUa code is correct for both; **Owning repo: `~/Desktop/HistorianGateway` (HistorianGateway).** OtOpcUa code is correct for both;
these are gateway defects that gate the "write OtOpcUa's own data, read it back" use case. these are gateway defects that gate the "write OtOpcUa's own data, read it back" use case.
### FU-1 — `SendEvent` does not populate `Source_Object` (alarm write-back-by-source) ### FU-1 — `SendEvent` does not populate `Source_Object` — ✅ RESOLVED as a documented protocol limitation (2026-06-27)
> **Outcome:** root-caused and confirmed **not fixable at the gateway** — the captured CM_EVENT event-send
> wire (`HistorianEventWriteProtocol.SerializeEventValueBlob`) serializes Namespace/Type/properties but
> **never `SourceName`** (the gateway threads it correctly; the wire drops it). `Source_Object` is a
> Galaxy-platform association for object-raised events. Documented as `pending.md` **C4** + a CLAUDE.md note
> in HistorianGateway; likely won't-fix (would need new wire-capture evidence in `histsdk` — vendored
> sources aren't hand-edited). The "Investigation/Proposed fix" below is retained for the record; option 1
> is now known to be infeasible.
**Symptom (live-proven):** OtOpcUa's `GatewayAlarmHistorianWriter.SendEvent` of an event with **Symptom (live-proven):** OtOpcUa's `GatewayAlarmHistorianWriter.SendEvent` of an event with
`source_name="HistGW.LiveTest.AlarmSource"` **acks** and **lands in `Runtime.dbo.Events`** with the `source_name="HistGW.LiveTest.AlarmSource"` **acks** and **lands in `Runtime.dbo.Events`** with the
correct `Type` (`LimitAlarm`) and `EventTimeUtc` (no shift) — but with **`Source_Object = NULL`** (and correct `Type` (`LimitAlarm`) and `EventTimeUtc` (no shift) — but with **`Source_Object = NULL`** (and
@@ -74,7 +98,13 @@ window. Then **un-skip** `Alarm_SendEvent_then_ReadEvents` in
`tests/Drivers/.../Live/GatewayLiveIntegrationTests.cs` (it currently `Assert.Skip`s on a 0-result with `tests/Drivers/.../Live/GatewayLiveIntegrationTests.cs` (it currently `Assert.Skip`s on a 0-result with
the accurate reason). the accurate reason).
### FU-2 — `WriteLiveValues` shifts an explicit timestamp by the local↔UTC offset (~+4h) ### FU-2 — `WriteLiveValues` shifts an explicit timestamp by the local↔UTC offset (~+4h) — ✅ DONE + live-validated (2026-06-27)
> **Outcome:** fixed in HistorianGateway (`fix/gateway-otopcua-followups`). The SQL live-write path now
> converts UTC→server-local in-SQL via `DATEADD(MINUTE, DATEPART(TZOFFSET, SYSDATETIMEOFFSET()), @dt)` (a
> single atomic offset read). An explicit-timestamp round-trip (real SQL write → gateway UTC ReadRaw) is now
> EXACT against the live 2023 R2 historian (delta 00:00:00); offline unit test locks the exact conversion
> expression. The OtOpcUa live write test can now be tightened (see acceptance).
**Symptom (live-proven, reproduces via raw `grpcurl` — no OtOpcUa code involved):** a `WriteLiveValues` **Symptom (live-proven, reproduces via raw `grpcurl` — no OtOpcUa code involved):** a `WriteLiveValues`
with an **explicit** `timestamp=2026-06-27T03:45:00Z` lands in the historian at with an **explicit** `timestamp=2026-06-27T03:45:00Z` lands in the historian at
`2026-06-27T07:45:00Z` (+4h = the deployment's local↔UTC delta). A **server-stamped** write (null `2026-06-27T07:45:00Z` (+4h = the deployment's local↔UTC delta). A **server-stamped** write (null
@@ -106,7 +136,13 @@ window anchored on the write time.
## Priority 2 — OtOpcUa-side follow-ups ## Priority 2 — OtOpcUa-side follow-ups
**Owning repo: `~/Desktop/OtOpcUa` (this repo).** **Owning repo: `~/Desktop/OtOpcUa` (this repo).**
### FU-3 — Continuous-historization `HistorianTagname` override edge case ### FU-3 — Continuous-historization `HistorianTagname` override edge case — ✅ DONE (2026-06-27, commit `111adc92`)
> **Outcome:** implemented the "carry both identifiers" fix below. A new `HistorizedTagRef(MuxRef,
> HistorianName)` record threads through `IHistorizedTagSubscriptionSink` → the recorder; the recorder keeps
> a muxRef→historianName map, registers/filters mux interest by `MuxRef` (= driver `FullName`) but writes
> under `HistorianName` (override-or-FullName). The applier resolves both. Divergent-override + override-
> rename-no-churn recorder tests added; applier feed tests assert the full pairs.
The `ContinuousHistorizationRecorder` registers `DependencyMuxActor` interest **by the resolved The `ContinuousHistorizationRecorder` registers `DependencyMuxActor` interest **by the resolved
historian name** (`HistorianTagname` override else `FullName`) — the same key the EnsureTags hook and historian name** (`HistorianTagname` override else `FullName`) — the same key the EnsureTags hook and
the writer use. The mux fans `DependencyValueChanged` **keyed by `FullReference`** (the driver's the writer use. The mux fans `DependencyValueChanged` **keyed by `FullReference`** (the driver's
@@ -120,7 +156,7 @@ under the resolved historian name — i.e. carry both identifiers through `IHist
a divergent override. **Low urgency** (overrides are uncommon); only matters for non-Galaxy historized a divergent override. **Low urgency** (overrides are uncommon); only matters for non-Galaxy historized
tags that set an explicit `HistorianTagname`. tags that set an explicit `HistorianTagname`.
### FU-4 — `AlarmHistorianOptions.Validate()` `MaxAttempts<=0` test coverage (minor) ### FU-4 — `AlarmHistorianOptions.Validate()` `MaxAttempts<=0` test coverage (minor) — ✅ DONE (2026-06-27, commit `b2276b5b`)
T19 pruned the Wonderware-shaped fields and reworked `AlarmHistorianRegistrationTests`. The T19 pruned the Wonderware-shaped fields and reworked `AlarmHistorianRegistrationTests`. The
`MaxAttempts <= 0` warning branch in `AlarmHistorianOptions.Validate()` is exercised in prod but not `MaxAttempts <= 0` warning branch in `AlarmHistorianOptions.Validate()` is exercised in prod but not
covered by a test (the sibling warnings for `DrainIntervalSeconds`/`Capacity`/`DeadLetterRetentionDays` covered by a test (the sibling warnings for `DrainIntervalSeconds`/`Capacity`/`DeadLetterRetentionDays`