docs(r2-06): note WriteLiveValues Quality is a documented-dead input (0.2.0 C-002); keep 192 populated (06/C-7)

This commit is contained in:
Joseph Doherty
2026-07-13 10:04:29 -04:00
parent e5cf986a10
commit 4640ecb19b
4 changed files with 28 additions and 2 deletions
@@ -73,7 +73,7 @@
{
"id": "T10",
"subject": "C-7: keep-populating decision + dead-Quality doc comments at GatewayHistorianValueWriter.cs:63, ContinuousHistorizationRecorder.GoodQuality, docs/Historian.md (cites 0.2.0 Contracts C-002); commit 3 (C-7)",
"status": "pending",
"status": "completed",
"blockedBy": []
},
{
+6
View File
@@ -102,6 +102,12 @@ write path) and the `ContinuousHistorization` section (driver-value capture). Al
**same** gateway — but only `ServerHistorian` carries the connection (endpoint/key/TLS); the other two
source it from there.
> **Continuous-historization value quality is server-stamped.** The gateway's `WriteLiveValues` SQL path
> **discards** the quality the recorder sends (a server-managed column; 0.2.0 Contracts C-002), so persisted
> quality is stamped gateway-side regardless of what the `ContinuousHistorizationRecorder` provides (it
> hardcodes OPC-DA Good, `192`). Capturing real per-node quality will not persist until the gateway honors
> the field — see `GatewayHistorianValueWriter`'s remarks (archreview 06/C-7).
---
## Tag auto-provisioning (EnsureTags)
@@ -24,6 +24,17 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Recorder;
/// A success ack OR a store-forward-queued ack maps to <c>true</c>: a value the gateway
/// durably queued must not be re-drained.
/// </para>
/// <para>
/// <b>Quality is a documented-dead input</b> on the <c>WriteLiveValues</c> SQL path
/// (HistorianGateway 0.2.0 Contracts C-002): <c>HistorianLiveValue.Quality</c> /
/// <c>QualityDetail</c> are server-managed columns — any value sent is accepted on the wire and
/// then silently discarded. We populate <c>Quality</c> anyway (see the mapping below): proto3
/// transmits <c>0</c> when a field is left unset, and <c>0</c> is "Bad" in OPC-DA terms, so
/// <em>not</em> setting it would send a <em>wrong</em> quality to any future gateway version that
/// starts honoring the field — whereas the recorder's <c>192</c> ("Good") is the correct value.
/// <b>Do NOT extend the recorder to capture real node quality expecting it to persist</b> — it
/// will not, until the gateway honors the field.
/// </para>
/// </remarks>
public sealed class GatewayHistorianValueWriter : IHistorianValueWriter, IAsyncDisposable
{
@@ -60,6 +71,8 @@ public sealed class GatewayHistorianValueWriter : IHistorianValueWriter, IAsyncD
var live = new HistorianLiveValue
{
NumericValue = value.Value,
// Documented-dead input on the WriteLiveValues SQL path (0.2.0 Contracts C-002 —
// server-managed, silently discarded). Populated deliberately; see the class <remarks>.
Quality = value.Quality,
};
@@ -41,7 +41,14 @@ namespace ZB.MOM.WW.OtOpcUa.Runtime.Historian;
public sealed class ContinuousHistorizationRecorder : ReceiveActor, IWithTimers
{
/// <summary>OPC-DA quality byte recorded for mux-fanned values (the mux drops quality; driver-published
/// values are Good by the same convention the scripted-alarm host applies).</summary>
/// values are Good by the same convention the scripted-alarm host applies).
/// <para>
/// Note (archreview 06/C-7): the historian gateway's <c>WriteLiveValues</c> SQL path <b>discards</b>
/// quality (server-managed column, 0.2.0 Contracts C-002 — see
/// <c>GatewayHistorianValueWriter</c>'s remarks). This constant is therefore what a future
/// quality-honoring gateway path <em>would</em> receive; capturing real per-node quality here will
/// NOT persist until the gateway honors the field.
/// </para></summary>
public const ushort GoodQuality = 192;
private const string DrainTimerKey = "drain";