From 00cc1da362a77ff6ccdab155815fe16d6e8961a7 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sat, 27 Jun 2026 00:45:19 -0400 Subject: [PATCH] =?UTF-8?q?docs(historian-gateway):=20mark=20follow-up=20p?= =?UTF-8?q?lan=20status=20=E2=80=94=20FU-1=20documented-limitation,=20FU-2?= =?UTF-8?q?/3/4=20done?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../2026-06-27-otopcua-historian-followups.md | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/docs/plans/2026-06-27-otopcua-historian-followups.md b/docs/plans/2026-06-27-otopcua-historian-followups.md index d4084c22..0d296367 100644 --- a/docs/plans/2026-06-27-otopcua-historian-followups.md +++ b/docs/plans/2026-06-27-otopcua-historian-followups.md @@ -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 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 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`): @@ -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; 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 `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 @@ -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 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` 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 @@ -106,7 +136,13 @@ window anchored on the write time. ## Priority 2 — OtOpcUa-side follow-ups **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 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 @@ -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 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 `MaxAttempts <= 0` warning branch in `AlarmHistorianOptions.Validate()` is exercised in prod but not covered by a test (the sibling warnings for `DrainIntervalSeconds`/`Capacity`/`DeadLetterRetentionDays`