Gateway historian: TimeAverage/Total on a discrete AVEVA tag answers BadHistoryOperationUnsupported and counts as a backend failure #897
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found by the #694 live gate (2026-11-12,
4c8e977e, local HistorianGatewaydffb5e2→ AVEVA 2023 R2 on wonder-sql-vd03). Evidence:docs/plans/evidence/2026-11-12-historian-694-700-746/aveva-gate.md, Finding 3.Native TimeAverage / Total are on by default again since #694. On a discrete AVEVA tag (
SysPulse, a Boolean node), AVEVA refusesTimeWeightedAverage/IntegralatStartQuery(UnknownException 85). The gateway answersInternal(HistorianServerOperationException), and the Gateway backend reports that as a read failure. The node answersBadHistoryOperationUnsupported, which is a Bad status and never a wrong value. But:/hostshealth until the next good read or probe), andBadAggregateNotSupported(not sent to the gateway, not a failure).Options:
BadAggregateNotSupportedand nothing is counted.GetTagMetadata: it reports nodata_type, i.e. INT1, which a real Int1 analog tag would report too, so this is a heuristic.Recommendation: option 1, since it is exact and needs no extra gateway call. The SQL backends are not affected.
Fixed in
ee39c169on master.What changed. HistoryRead-Processed now checks the node's scalar DataType before it calls any backend. An aggregate that is not valid for the node gets
BadAggregateNotSupported. Nothing is sent to the gateway and no backend failure is counted.Why this status. Part 11 §6.5.4 says: "If the Aggregate is not valid for the Node then the StatusCode shall be Bad_AggregateNotSupported." Part 13 §5.4.2.3 Table 50 lists TimeAverage, Total, Average, Min/Max and the rest of that group as numeric only.
Bad_AggregateInvalidInputsis the status for a single interval whose data can't be aggregated, not for a whole node.Where the check lives. It has two layers, both applied in
OtOpcUaNodeManager.HistoryReadProcessed:HistoryAggregateDataTypesin Core.Abstractions applies Table 50. A numeric-only aggregate on a String or DateTime node is refused. This changes what clients see on SqlServer/QuestDb: they used to answer every intervalBadAggregateInvalidInputs.IHistorianDataSource.SupportsAggregateFor(aggregate, TypeCode?). Every production backend declares it, and a guard test checks that. Only Gateway restricts it: native TimeAverage / Total on a Boolean node are refused. SqlServer/QuestDb keep serving Boolean as stepped 0/1 (TimeAverage = fraction of time true), and so do the Gateway's raw-evaluated Average/Min/Max. Boolean is therefore not refused by the shared rule.Health ("unsupported for this data" vs "backend down"). For a Boolean node the refusal now happens before anything is sent, so it never reaches health. The leftover case is a node declared numeric whose AVEVA tag is discrete. That read still reaches AVEVA and still counts as a failure. The gateway reports AVEVA's refusal only as
Internalwith the redacted detailHistorianServerOperationException, which is exactly what it sends for a historian storage-engine fault (the gateway's own drain path treats that as retryable). OtOpcUa can't tell the two apart on the wire, so it would need the gateway to send AVEVA's native error code (85) as a deterministic status (e.g.HistorianServerRejectionException→FailedPrecondition). That fix belongs in historiangw. Transport outages were already kept separate (BadNoCommunicationvsBadHistoryOperationUnsupported).Tests (each one fails under a mutation of the fix: check removed, shared rule removed, backend hint removed, Gateway hint forced true, shared rule always true, SQL backend inheriting the default):
HistoryAggregateDataTypesTests: the Table 50 sets.Every_implementation_declares_its_per_type_aggregate_support_itself.NodeManagerHistoryReadTests: String/DateTime refused without a backend call; backend per-type refusal; positive controls.GatewayNativeTimeWeightedGateTests.The_native_pair_is_declared_unsupported_on_a_Boolean_node_only.DiscreteTagTimeWeightedWireTests: the production Gateway backend over an AVEVA-shaped fake gateway, through a real SDK session. Boolean TimeAverage/Total giveBadAggregateNotSupported, the gateway is never called, 0 health failures. Int32 TimeAverage and Boolean Average are still served.Suites green: Core.Abstractions 774, Historian.Gateway 583, QuestDb 88, Sql.Common 300, SqlServer 45, Runtime 1037, OpcUaServer 1284. Release build: 0 warnings.
Live, read-only (2026-11-13): I ran a private HistorianGateway
fa9cf31container with auth disabled andHistorian__LiveWritesEnabled=false, then removed it. It talked to AVEVA 2023 R2 on wonder-sql-vd03, and an in-process OtOpcUa server read through a real SDK session. Result:700.existing.processed.TimeAverage.boolean(SysPulse) =BadAggregateNotSupported, with the health failure count unchanged across the wire read. The gate's own direct backend call still getsHistorianServerOperationException, which shows AVEVA still refuses the mode. Boolean Average was Good and both TimeAverage and Total are still advertised. The followups gate (65 cases) and the #801 gate (244 cases) passed; I didn't run the strict fixture. Report:docs/plans/evidence/2026-11-12-historian-694-700-746/aveva-gate-followups-fix-897.json.gz. No writes, no tags created, nothing ran on a wonder box.Docs:
docs/Historian.md§"Aggregates by node DataType (#897)",.claude/rules/historian.md, evidence Finding 3.