From 9973ae08be31fd0b67358a33737681e4aa0471d7 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Fri, 9 Oct 2026 12:51:34 -0400 Subject: [PATCH] =?UTF-8?q?docs(historian):=20design=20=C2=A79.10=20?= =?UTF-8?q?=E2=80=94=20comms-loss=20IS=20historized=20as=20an=20outage=20m?= =?UTF-8?q?arker=20(T3=20review=20correction)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe --- docs/plans/2026-10-09-pluggable-historian-design.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plans/2026-10-09-pluggable-historian-design.md b/docs/plans/2026-10-09-pluggable-historian-design.md index c3f33661..c22d853c 100644 --- a/docs/plans/2026-10-09-pluggable-historian-design.md +++ b/docs/plans/2026-10-09-pluggable-historian-design.md @@ -344,5 +344,5 @@ These were verified against the code and override anything above that conflicts. 7. **Processed reads** are capped at 100 000 buckets per request. 8. **HistoryUpdate admission.** The `HistoryWrite` AccessLevel bit decides whether the SDK base admits a node to HistoryUpdate at all, so the updater must be wired before any node materialises. `OtOpcUaServerHostedService.StartAsync` assigns it (via the new `OtOpcUaSdkServer.SetHistorianDataUpdater`) before `SetSink`. Nodes materialised earlier keep their AccessLevel (no retro-update). 9. **HistoryUpdate results.** Successful arms set `errors[i] = Good`. A `SourceTimestamp == DateTime.MinValue` is `BadInvalidTimestamp`. -10. **Comms-loss is not historized.** Watchdog `BadNoCommunication` stamps are node-only and never reach the mux, so they are not historized. A driver's own Bad publish is, usually with a null value, so it is stored as `Empty` kind with the driver's Bad code (Modbus `0x80050000`). +10. **Comms-loss IS historized, as an outage marker** (corrected after the T3 review). `DriverHostActor.FanCommsLossToDependencyMux` (#481) sends `null` + `BadNoCommunication` for every value ref to the mux, so the recorder receives it, and it cannot be told apart from a driver's own Bad publish. On a backend whose `SupportedKinds` include `Empty` (SQL Server), each disconnect stores one `Empty`/`BadNoCommunication` sample per historized tag. This is deliberate: it records the gap, so raw reads show the outage and aggregates and interpolation do not bridge it. On a numeric-only backend (Gateway) it is dropped as an unsupported kind and counted in `DroppedUnsupportedKind`. A driver's own Bad publish is handled the same way (`Empty` kind plus its Bad code, e.g. Modbus `0x80050000`). 11. **Parallel safety.** `SqlServerTagCatalog` belongs to the skeleton task (T8), so the parallel SQL tasks share one.