docs(historian-gateway): document gateway backend, config keys, EnsureTags hook, known gates; retire Wonderware from docs
v2-ci / build (pull_request) Failing after 38s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (pull_request) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (pull_request) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (pull_request) Has been skipped

HistorianGateway is now the sole historian backend (read + alarm SendEvent +
continuous WriteLiveValues). Document the final state and retire the Wonderware
sidecar from the docs/config/labels:

- CLAUDE.md: rewrite the Historian section — ServerHistorian /
  ContinuousHistorization / AlarmHistorian config keys, the IHistorianProvisioning
  EnsureTags hook, the GatewayAlarmHistorianWriter SendEvent path + ReadEvents
  dependency on gateway RuntimeDb:EventReadsEnabled=true, gateway-side
  prerequisites (RuntimeDb flags + historian:read/write/tags:write scopes),
  migration note, and two KNOWN-LIMITATION callouts (live-validation gate +
  empty historized-ref-set recorder follow-on).
- appsettings.json: fix the stale ServerHistorian block (Host/Port/SharedSecret/
  ServerCertThumbprint -> Endpoint/ApiKey/UseTls/AllowUntrustedServerCertificate/
  CaCertificatePath/CallTimeout, keep MaxTieClusterOverfetch); add a disabled
  ContinuousHistorization block; prune the orphaned Wonderware keys from
  AlarmHistorian (keep the SQLite knobs). ApiKey env-supplied via
  ServerHistorian__ApiKey (commented; valid strict JSON via _comment keys).
- README.md + docs (Historian.md, AlarmHistorian.md, Configuration.md,
  ServiceHosting.md, DriverLifecycle.md, drivers/README.md, Uns.md, VirtualTags.md,
  AlarmTracking.md, Client.UI.md, README.md, TestConnectProbes.md): retire the
  Wonderware historian backend from current-backend descriptions; fix the stale
  ServerHistorian/AlarmHistorian config tables (now gateway shape); convert
  drivers/Historian.Wonderware.md to a retired stub pointing at the gateway.
- Source/UI labels (descriptive text only, no behavior change):
  OtOpcUaServerHostedService.cs, HistoryPaging.cs, OtOpcUaSdkServer.cs,
  HistorianAdapterActor.cs, VirtualTagModal.razor, ScriptedAlarmModal.razor,
  AlarmsHistorian.razor now name the HistorianGateway backend.

Build clean (0 errors); AdminUI.Tests green (514 passed).

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
This commit is contained in:
Joseph Doherty
2026-06-26 19:46:27 -04:00
parent 0b4b2e4cfd
commit 2124f21ab6
23 changed files with 364 additions and 320 deletions
+27 -28
View File
@@ -16,7 +16,7 @@ and [ServiceHosting.md](ServiceHosting.md).
## Why store-and-forward
Scripted alarms (and any future non-Galaxy `IAlarmSource`, e.g. AB CIP ALMD)
must reach AVEVA Historian, but the historian sidecar can be slow, busy, or
must reach AVEVA Historian, but the historian gateway can be slow, busy, or
disconnected. The sink decouples the alarm engine from historian reachability:
every qualifying transition is committed to a **local SQLite queue first**, and
a background drain worker forwards rows to the historian on a backoff-aware
@@ -52,8 +52,8 @@ unless noted.
`TimestampUtc`.
- **`IAlarmHistorianWriter`** — what the drain worker delegates writes to.
`WriteBatchAsync(batch, ct)` returns one `HistorianWriteOutcome` per event,
in order. Production binds this to `WonderwareHistorianClient` (the AVEVA
Historian sidecar IPC client).
in order. Production binds this to `GatewayAlarmHistorianWriter` (the
HistorianGateway `SendEvent` path).
- **`HistorianWriteOutcome`** — per-event drain result: `Ack` (persisted,
remove from queue), `RetryPlease` (transient failure — leave queued, retry
after backoff), `PermanentFail` (malformed/unrecoverable — move to
@@ -160,9 +160,9 @@ node whose `RedundancyRole` is `Primary` historizes, giving exactly-once
writes across a redundant pair. `AlarmTransitionEvent` carries `AlarmTypeName`
(the Part 9 subtype string) and `Comment` (the operator comment from the
originating ack/shelve command) that populate the corresponding fields of
`AlarmHistorianEvent`. `WonderwareHistorianClient` is the `IAlarmHistorianWriter`
the drain worker delegates to. See [ServiceHosting.md](ServiceHosting.md) for
the sidecar setup.
`AlarmHistorianEvent`. `GatewayAlarmHistorianWriter` is the `IAlarmHistorianWriter`
the drain worker delegates to (the gateway `SendEvent` path). See
[ServiceHosting.md](ServiceHosting.md) for the (external) HistorianGateway setup.
**Scope:** scripted alarms only. Galaxy-native alarms historize via System
Platform's `HistorizeToAveva` toggle (not this actor); AB CIP ALMD is not on
@@ -174,40 +174,40 @@ The real sink is opt-in via the `AlarmHistorian` section of `appsettings.json`.
When `Enabled` is `false` (the default), `AddAlarmHistorian` registers
`NullAlarmHistorianSink` and the feature is dormant. When `Enabled` is `true`,
`AddAlarmHistorian` constructs `SqliteStoreAndForwardSink` and registers
`WonderwareHistorianClient` as the `IAlarmHistorianWriter`.
`GatewayAlarmHistorianWriter` as the `IAlarmHistorianWriter`. This section carries
**only** the `Enabled` gate + the SQLite store-and-forward knobs — the downstream
gateway connection (endpoint / key / TLS) is sourced from the `ServerHistorian`
section (see [Historian.md](Historian.md)).
```json
{
"AlarmHistorian": {
"Enabled": true,
"DatabasePath": "C:\\ProgramData\\OtOpcUa\\alarmhistorian.db",
"SharedSecret": "<token from historian sidecar config>",
"BatchSize": 100
},
"Historian": {
"Wonderware": {
"Host": "localhost",
"Port": 32569,
"UseTls": false,
"ServerCertThumbprint": ""
}
"BatchSize": 100,
"DrainIntervalSeconds": 5,
"Capacity": 1000000,
"DeadLetterRetentionDays": 30
}
}
```
| Key | Type | Default | Description |
|---|---|---|---|
| `Enabled` | bool | `false` | Enable the real SQLite + Wonderware sink. `false``NullAlarmHistorianSink`. |
| `DatabasePath` | string | — | Absolute path to the SQLite queue file. Created on first use (WAL mode). Required when `Enabled`. |
| `SharedSecret` | string | — | Shared secret token the sidecar expects on every connection. Required when `Enabled`. |
| `Enabled` | bool | `false` | Enable the SQLite store-and-forward sink (drains to the HistorianGateway `SendEvent` path). `false``NullAlarmHistorianSink`. |
| `DatabasePath` | string | `alarm-historian.db` | Path to the SQLite queue file. Created on first use (WAL mode). Set an **absolute** path in production. |
| `BatchSize` | int | `100` | Max rows per drain cycle handed to `IAlarmHistorianWriter.WriteBatchAsync`. |
| `DrainIntervalSeconds` | int | `5` | Seconds between drain-worker ticks. |
| `Capacity` | long | `1000000` | Max queued rows before the sink evicts the oldest (data-loss signal via `EvictedCount`). |
| `DeadLetterRetentionDays` | int | `30` | Days to retain dead-lettered rows before purge. |
| `MaxAttempts` | int | `10` | Maximum delivery attempts before a poison (perpetually-retrying) row is dead-lettered automatically. Must be > 0. |
| `AlarmHistorian:Host` | string | `localhost` | DNS name or IP of the machine running the historian sidecar. |
| `AlarmHistorian:Port` | int | `32569` | TCP port the sidecar listens on (`OTOPCUA_HISTORIAN_TCP_PORT`). |
| `AlarmHistorian:UseTls` | bool | `false` | Wrap the TCP stream in TLS before the Hello handshake. |
| `AlarmHistorian:ServerCertThumbprint` | string | — | Optional SHA-1 thumbprint to pin the sidecar's TLS server certificate. Leave empty to use normal CA-chain validation. |
> Dev and docker-dev deployments leave `Enabled` unset (defaults to `false`) so alarm transitions historize to nowhere unless a historian sidecar is present.
> The downstream gateway connection lives in `ServerHistorian` (`Endpoint` + env `ServerHistorian__ApiKey`,
> `UseTls`, `CaCertificatePath`); alarm-history `ReadEvents` additionally requires the gateway running
> `RuntimeDb:EventReadsEnabled=true`. The old Wonderware connection keys (`SharedSecret` /
> `AlarmHistorian:Host`/`Port`/`UseTls`/`ServerCertThumbprint`) were pruned.
> Dev and docker-dev deployments leave `Enabled` unset (defaults to `false`) so alarm transitions historize to nowhere unless a HistorianGateway is configured.
---
@@ -217,8 +217,7 @@ When `Enabled` is `false` (the default), `AddAlarmHistorian` registers
Part 9 surface; which alarms route to this sink.
- [DriverLifecycle.md](DriverLifecycle.md) — `IHistorianDataSource` (the
historian *read* surface; this page covers the *write* path) and the
`WonderwareHistorianClient`.
`GatewayHistorianDataSource`.
- [ScriptedAlarms.md](ScriptedAlarms.md) — the scripted-alarm engine that emits
most events into this sink.
- [ServiceHosting.md](ServiceHosting.md) — the optional Wonderware historian
sidecar.
- [ServiceHosting.md](ServiceHosting.md) — the external HistorianGateway backend.