docs(historian): close #491 — read-back and EnsureTags proven against wonder-sql-vd03
The 2026-08-01 run closed every leg except read-back, which no reachable historian could
serve that day (the LAN instance accepts writes but serves no reads under a licence tag-count
violation; the licensed one needed the VPN). With the VPN up the run was repeated against
wonder-sql-vd03 and the remaining legs pass:
- EnsureTags provisioning: ensured=2, failed=0 — the path the LAN box could not exercise at
all (it is RemoteGrpc-only and that CAP was not listening).
- Read-back: two Modbus registers carrying DISTINCT NON-ZERO constants were historized, and
each read back as its own value (HR20 -> 8224, HR3 -> 120, quality 192). A first attempt
read back 0, which proves nothing — the same trap #536 recorded, so the proof was rebuilt
on values a crossed or fabricated answer could not fake.
Found during the run and FILED rather than fixed:
- #538 — one permanently-failing tag re-writes every healthy tag on every drain pass, forever.
DrainOnceAsync writes EVERY per-tag group and then acks all-or-nothing, so a tag whose
historian tag does not exist stops the FIFO head advancing while the healthy tags' values
are re-submitted every drain interval; the outbox never truncates (Capacity defaults to 0 =
unbounded, observed at 8.3 MB). The all-or-nothing ack is deliberate — RemoveAsync truncates
THROUGH an id, so partial acking could drop older unwritten entries — so the fix changes the
outbox contract and does not belong in this commit.
Also re-confirmed live: EnsureTags provisioning fires only for newly-ADDED historized tags, so
flipping an existing tag's historianTagname provisions nothing (Issue 5 of
2026-06-27-historian-gateway-integration-issues.md).
No production code changes — the recorder fix shipped in ad744734.
Claude-Session: https://claude.ai/code/session_015p7wGqy3YpZNCpDzTpGMKo
This commit is contained in:
+19
-11
@@ -114,12 +114,17 @@ source it from there.
|
||||
> 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).
|
||||
|
||||
> ✅ **Continuous value capture is LIVE-PROVEN as far as the historian's door
|
||||
> ([#491](https://gitea.dohertylan.com/dohertj2/lmxopcua/issues/491), gate run 2026-08-01 — see
|
||||
> `docs/plans/2026-08-01-continuous-historization-live-gate.md`).** Against a real
|
||||
> `ZB.MOM.WW.HistorianGateway` talking to a real AVEVA Historian: deploy → dependency-mux value delta →
|
||||
> FasterLog outbox append → drain → `WriteLiveValues` **accepted**, and **restart convergence** (the
|
||||
> recorder re-registers the deployed ref set after a process restart, with no new deployment) both pass.
|
||||
> ✅ **Continuous value capture is LIVE-PROVEN END-TO-END
|
||||
> ([#491](https://gitea.dohertylan.com/dohertj2/lmxopcua/issues/491) CLOSED — gate run 2026-08-01,
|
||||
> completed 2026-08-04; see `docs/plans/2026-08-01-continuous-historization-live-gate.md`).** Against a
|
||||
> real `ZB.MOM.WW.HistorianGateway` talking to the real AVEVA Historian on `wonder-sql-vd03`: deploy →
|
||||
> `EnsureTags` provisioning → dependency-mux value delta → FasterLog outbox append → drain →
|
||||
> `WriteLiveValues` → **value read back from the historian**, plus **restart convergence** (the recorder
|
||||
> re-registers the deployed ref set after a process restart, with no new deployment).
|
||||
>
|
||||
> The read-back used two registers carrying **distinct non-zero** constants (`8224`, `120`), each of
|
||||
> which read back as its own value — a `0`-valued source cannot distinguish a correct value from a
|
||||
> wrong one.
|
||||
>
|
||||
> ⚠️ The gate found a **production-fatal defect**: the recorder threw `NotSupportedException` on the
|
||||
> **first value it ever appended**, because it touched `Self` after an `await …ConfigureAwait(false)`,
|
||||
@@ -128,12 +133,15 @@ source it from there.
|
||||
> value. Fixed; pinned by a regression test whose outbox fake completes on a **pool** thread (a fake
|
||||
> returning `ValueTask.CompletedTask`, or even `await Task.Yield()`, cannot reproduce it).
|
||||
>
|
||||
> ❌ **Still unproven: the read-back hop** — no available historian can serve it today (the LAN instance
|
||||
> accepts writes but serves no reads under a licence tag-count violation; the licensed corporate one needs
|
||||
> the VPN). `EnsureTags` provisioning is likewise unexercised there (`RemoteGrpc`-only). **#491 stays open
|
||||
> for that hop alone.**
|
||||
> ⚠️ **One permanently-failing tag re-writes every healthy tag, forever
|
||||
> ([#538](https://gitea.dohertylan.com/dohertj2/lmxopcua/issues/538)).** `DrainOnceAsync` writes **every**
|
||||
> per-tag group in a batch and then acks **all-or-nothing**, so a tag whose historian tag does not exist
|
||||
> stops the FIFO head advancing while the healthy tags' values are re-submitted on every drain pass —
|
||||
> duplicate samples at identical timestamps, and an outbox that never truncates (`Capacity` defaults to
|
||||
> `0` = unbounded; observed at 8.3 MB). The all-or-nothing ack itself is deliberate.
|
||||
>
|
||||
> ⚠️ **Both nodes of a redundant pair historize the same value** — the recorder has no Primary gate,
|
||||
> ⚠️ **Both nodes of a redundant pair historize the same value
|
||||
> ([#537](https://gitea.dohertylan.com/dohertj2/lmxopcua/issues/537))** — the recorder has no Primary gate,
|
||||
> unlike the alarm store-and-forward sink, so the historian receives duplicate live values per tag.
|
||||
>
|
||||
> **Related, separately open:** `EnsureTags` provisioning fires only for **newly-added** historized
|
||||
|
||||
Reference in New Issue
Block a user