From aaa5d8b85158a5029c8f49bfa5aa461bb3be306b Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 15 Jun 2026 10:43:29 -0400 Subject: [PATCH] docs(vtags): document runtime Historize honoring + infra-gated durable sink (Phase 1 H5) --- docs/VirtualTags.md | 6 ++++-- docs/plans/2026-06-15-stillpending-backlog-design.md | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/VirtualTags.md b/docs/VirtualTags.md index e4e4867c..3c979540 100644 --- a/docs/VirtualTags.md +++ b/docs/VirtualTags.md @@ -94,7 +94,9 @@ What the engine pulls driver-tag values from. Reads are **synchronous** because ### `IHistoryWriter` -Fire-and-forget sink for evaluation results when `VirtualTagDefinition.Historize = true`. Implementations must queue internally and drain on their own cadence — a slow historian must not block script evaluation. `NullHistoryWriter.Instance` is the no-op default. Today no production writer is wired into the virtual-tag path; scripted-alarm emissions flow through `Core.AlarmHistorian` via `Phase7EngineComposer.RouteToHistorianAsync` (a separate concern; see [AlarmTracking.md](AlarmTracking.md)). +Fire-and-forget sink for evaluation results when `VirtualTagDefinition.Historize = true`. Implementations must queue internally and drain on their own cadence — a slow historian must not block script evaluation. `NullHistoryWriter.Instance` is the no-op default. Scripted-alarm emissions flow through `Core.AlarmHistorian` via `Phase7EngineComposer.RouteToHistorianAsync` (a separate concern; see [AlarmTracking.md](AlarmTracking.md)). + +**Equipment-namespace path (H5).** The `Historize` flag is threaded end-to-end on the equipment path: `VirtualTag.Historize` → composer + artifact-decode (byte-parity) → `EquipmentVirtualTagPlan.Historize` → `VirtualTagHostActor`, which calls `IHistoryWriter.Record(nodeId, snapshot)` for every historized result (in addition to publishing the live value). The writer is injectable via DI — `DriverHostActor` resolves `IHistoryWriter` (`TryAddSingleton`, `NullHistoryWriter` default) and threads it into `VirtualTagHostActor`. **The durable AVEVA data-value sink is infra-gated**: the Wonderware historian sidecar exposes only HistoryRead + alarm-event writes (no live-data `WriteDataValues` RPC), so the production default stays `NullHistoryWriter` until that backend exists. A deployment can bind a custom `IHistoryWriter` via DI today. ## Dispatch integration @@ -112,7 +114,7 @@ Per [ADR-002](v2/implementation/adr-002-driver-vs-virtual-dispatch.md) Option B, `ITagUpstreamSource` and `IHistoryWriter` are the two ports the engine requires from its host. Both live in `Core.VirtualTags`. In the v2 actor system: - **Upstream-tag feed.** `DependencyMuxActor` (`src/Server/ZB.MOM.WW.OtOpcUa.Runtime/VirtualTags/DependencyMuxActor.cs`) routes `DriverInstanceActor.AttributeValuePublished` events to the `VirtualTagActor` instances that declared interest in those tag refs. Each `VirtualTagActor` holds the in-memory per-tag dependency map; the `IVirtualTagEvaluator` (`RoslynVirtualTagEvaluator`) receives the dependency snapshot synchronously on the actor message thread. Reads of never-pushed dependency refs return `null` values in the dependency snapshot. -- **`IHistoryWriter`** — no production implementation is wired for virtual tags; `VirtualTagEngine` receives `NullHistoryWriter` by default. +- **`IHistoryWriter`** — the equipment-namespace path threads `Historize` end-to-end and `VirtualTagHostActor` invokes the injected writer on historized results (H5); the writer is resolved through `DriverHostActor` DI with a `NullHistoryWriter` default. The standalone `VirtualTagEngine` likewise receives `NullHistoryWriter` by default. No *durable* writer ships because the historian sidecar has no live-data write RPC (infra-gated) — see the `IHistoryWriter` section above. ## Composition diff --git a/docs/plans/2026-06-15-stillpending-backlog-design.md b/docs/plans/2026-06-15-stillpending-backlog-design.md index 98dced55..813d7af8 100644 --- a/docs/plans/2026-06-15-stillpending-backlog-design.md +++ b/docs/plans/2026-06-15-stillpending-backlog-design.md @@ -113,6 +113,7 @@ Forward-looking reservations or out-of-repo; left out unless explicitly pulled b - Monaco InlayHints — parameter-name hints; likely intended-permanent stub. - The full **HistoryUpdate service** — infra-gated (no backend insert/replace/delete RPC). - Galaxy `ExecuteWrite` fire-and-forget write-outcome — lives in the **mxaccessgw sibling repo**, not here. +- **Durable AVEVA virtual-tag history sink** (the concrete `IHistoryWriter` behind H5) — infra-gated: the Wonderware historian sidecar exposes only HistoryRead + alarm-event writes, no live-data `WriteDataValues` RPC. H5 (Phase 1) wired the injectable seam with a `NullHistoryWriter` default; the durable sink needs a sidecar RPC first (same class of constraint as the HistoryUpdate service). ## Verification per phase