HistoryServerCapabilities: every boolean capability is false even though data and event history are served #881

Closed
opened 2026-11-12 10:12:20 -05:00 by dohertj2 · 1 comment
Owner

With the HistorianGateway backend (vd03, real AVEVA), every Boolean under Server/ServerCapabilities/HistoryServerCapabilities reads false, including AccessHistoryDataCapability and AccessHistoryEventsCapability, even though raw, processed and at-time reads and event history all work. AggregateFunctions correctly lists the 10 supported aggregates.

Impact: a spec-following client that checks these flags before calling HistoryRead skips history entirely.

Expected: populate the flags from the active backend's real capabilities: AccessHistoryData/Events true, insert/replace/update/delete false for the Gateway backend (it has no HistoryUpdate path), and the per-backend values for SqlServer/QuestDb, which do support HistoryUpdate. Add a test per backend.

Evidence: lmxopcua#801 AVEVA history gate (comment 11735), OtOpcUa master 008f0b36 docs/plans/evidence/2026-11-10-opcua2/aveva-history-801.md; harness AvevaGatewayHistoryLiveGateTests (f607ee75). Reproduces identically on SDK 1.5.378.182 (efb9197a) and SDK 2.0.1, so it is not an SDK2 regression.

With the HistorianGateway backend (vd03, real AVEVA), every Boolean under `Server/ServerCapabilities/HistoryServerCapabilities` reads `false`, including `AccessHistoryDataCapability` and `AccessHistoryEventsCapability`, even though raw, processed and at-time reads and event history all work. `AggregateFunctions` correctly lists the 10 supported aggregates. Impact: a spec-following client that checks these flags before calling HistoryRead skips history entirely. Expected: populate the flags from the active backend's real capabilities: AccessHistoryData/Events true, insert/replace/update/delete false for the Gateway backend (it has no HistoryUpdate path), and the per-backend values for SqlServer/QuestDb, which do support HistoryUpdate. Add a test per backend. Evidence: lmxopcua#801 AVEVA history gate (comment 11735), OtOpcUa master `008f0b36` `docs/plans/evidence/2026-11-10-opcua2/aveva-history-801.md`; harness `AvevaGatewayHistoryLiveGateTests` (`f607ee75`). Reproduces identically on SDK 1.5.378.182 (`efb9197a`) and SDK 2.0.1, so it is not an SDK2 regression.
dohertj2 added the type:bugpriority:p2 labels 2026-11-12 10:12:20 -05:00
Author
Owner

Fixed on master b27b5b9c (not deployed).

Cause: nothing ever wrote the Boolean HistoryServerCapabilities properties, so the SDK default (all false) was what clients saw. Only AggregateFunctions (#682) and MaxReturnDataValues (#736) were synced.

Fix: each backend declares its own capabilities, and the server advertises and enforces those declarations.

  • IHistorianDataSource.ReadCapabilities (Data / Events / ServerTimestamps). Every production source declares it itself, and a reflection guard checks this. The Null source declares None.
  • IHistorianDataUpdater.Capabilities is a required member (InsertData / ReplaceData / UpdateData / DeleteRaw / DeleteAtTime).
  • The node manager re-writes the flags whenever a source or updater is assigned (AdvertisedHistoryCapabilities). The same declarations also drive dispatch:
    • An update the updater doesn't declare returns BadHistoryOperationUnsupported and never reaches the updater.
    • The HistoryWrite AccessLevel bit follows a non-empty declaration.
    • Promoting a notifier folder to event history follows the Events flag.
  • AggregateFunctions is unchanged. MaxReturnEventValues stays 0 (no limit).
Flag Gateway (default) SqlServer QuestDb No historian
AccessHistoryDataCapability true true true false
AccessHistoryEventsCapability true true true false
ServerTimestampSupported false (AVEVA stores no server time; the read time is stamped) true (ServerTimeUtc stored) true (stored) false
InsertDataCapability false (no updater) true true false
ReplaceDataCapability false true true false
UpdateDataCapability false true true false
DeleteRawCapability false true true (soft delete: tombstone) false
DeleteAtTimeCapability false true true (tombstone) false
Insert/Replace/Update/DeleteEventCapability false false false false
InsertAnnotationCapability false false false false

The event and annotation flags are false on every backend because the node manager dispatches no UpdateEventDetails or annotation update to any backend. IsDeleteModified and Remove are still refused.

Caveat: a SqlServer schema at v1 (AutoCreateSchema=false with no modification trail; a Warning is logged at boot) still advertises the update flags. Each update on that schema still returns BadHistoryOperationUnsupported at run time, as before.

Tests:

  • New HistoryServerCapabilitiesWireTests boots a real server and reads every flag over a client session, with the backends built through HistorianBackendRegistry the way the Host builds them. It covers Gateway, SqlServer and QuestDb, no historian (all false), and a SqlServer→Gateway rewire on the same session (Insert/DeleteRaw/ServerTimestamp go from true to false).
  • Mutation check: dropping DeleteRaw from the SqlServer updater's declaration makes 2 tests fail; restored.
  • Dispatch-gating unit test in HistoryUpdateTests.
  • The #801 live gate AvevaGatewayHistoryLiveGateTests now asserts the Gateway flags. Not run here (it needs vd03).

Runs:

  • Core.Abstractions 729/0
  • OpcUaServer.Tests 1265/0 (1 skip)
  • OpcUaServer.IntegrationTests 304/0 (35 skip)
  • Historian Gateway 523/0
  • QuestDb 88/0
  • SqlServer 45/0
  • Sql.Common 300/0
  • Host.Tests 288/0
  • The env-gated SqlServer/QuestDb integration suites skipped.
Fixed on master `b27b5b9c` (not deployed). **Cause:** nothing ever wrote the Boolean `HistoryServerCapabilities` properties, so the SDK default (all false) was what clients saw. Only `AggregateFunctions` (#682) and `MaxReturnDataValues` (#736) were synced. **Fix: each backend declares its own capabilities, and the server advertises and enforces those declarations.** - `IHistorianDataSource.ReadCapabilities` (`Data` / `Events` / `ServerTimestamps`). Every production source declares it itself, and a reflection guard checks this. The Null source declares `None`. - `IHistorianDataUpdater.Capabilities` is a required member (`InsertData` / `ReplaceData` / `UpdateData` / `DeleteRaw` / `DeleteAtTime`). - The node manager re-writes the flags whenever a source or updater is assigned (`AdvertisedHistoryCapabilities`). The same declarations also drive dispatch: - An update the updater doesn't declare returns `BadHistoryOperationUnsupported` and never reaches the updater. - The `HistoryWrite` AccessLevel bit follows a non-empty declaration. - Promoting a notifier folder to event history follows the `Events` flag. - `AggregateFunctions` is unchanged. `MaxReturnEventValues` stays 0 (no limit). | Flag | Gateway (default) | SqlServer | QuestDb | No historian | |---|---|---|---|---| | AccessHistoryDataCapability | **true** | **true** | **true** | false | | AccessHistoryEventsCapability | **true** | **true** | **true** | false | | ServerTimestampSupported | false (AVEVA stores no server time; the read time is stamped) | **true** (`ServerTimeUtc` stored) | **true** (stored) | false | | InsertDataCapability | false (no updater) | **true** | **true** | false | | ReplaceDataCapability | false | **true** | **true** | false | | UpdateDataCapability | false | **true** | **true** | false | | DeleteRawCapability | false | **true** | **true** (soft delete: tombstone) | false | | DeleteAtTimeCapability | false | **true** | **true** (tombstone) | false | | Insert/Replace/Update/DeleteEventCapability | false | false | false | false | | InsertAnnotationCapability | false | false | false | false | The event and annotation flags are false on every backend because the node manager dispatches no `UpdateEventDetails` or annotation update to any backend. `IsDeleteModified` and `Remove` are still refused. **Caveat:** a SqlServer schema at v1 (`AutoCreateSchema=false` with no modification trail; a Warning is logged at boot) still advertises the update flags. Each update on that schema still returns `BadHistoryOperationUnsupported` at run time, as before. **Tests:** - New `HistoryServerCapabilitiesWireTests` boots a real server and reads every flag over a client session, with the backends built through `HistorianBackendRegistry` the way the Host builds them. It covers Gateway, SqlServer and QuestDb, no historian (all false), and a SqlServer→Gateway rewire on the same session (Insert/DeleteRaw/ServerTimestamp go from true to false). - Mutation check: dropping `DeleteRaw` from the SqlServer updater's declaration makes 2 tests fail; restored. - Dispatch-gating unit test in `HistoryUpdateTests`. - The #801 live gate `AvevaGatewayHistoryLiveGateTests` now asserts the Gateway flags. Not run here (it needs vd03). **Runs:** - Core.Abstractions 729/0 - OpcUaServer.Tests 1265/0 (1 skip) - OpcUaServer.IntegrationTests 304/0 (35 skip) - Historian Gateway 523/0 - QuestDb 88/0 - SqlServer 45/0 - Sql.Common 300/0 - Host.Tests 288/0 - The env-gated SqlServer/QuestDb integration suites skipped.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dohertj2/lmxopcua#881