fix(historian): trim only HistoryServerCapabilities/AggregateFunctions; null set = all (#682 review)

ServerCapabilities/AggregateFunctions (i=2997) is Part 5's list of every
aggregate the server supports, and live AggregateFilter subscriptions still
compute all 37 SDK aggregates, so it keeps the SDK's full set; only i=11201
follows the backend. A null SupportedAggregates is treated as the interface
default (all) by both the advertisement and HistoryRead-Processed (it used to
NRE inside the batch). Docs: i=2997/i=11201 split and the boot window.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-10-12 09:18:53 -04:00
parent ede8610804
commit c019d0fc2b
8 changed files with 165 additions and 93 deletions
@@ -176,9 +176,10 @@ public interface IHistorianDataSource : IDisposable
/// <summary>
/// The Part 13 aggregates this backend serves for a processed HistoryRead (Gitea #682) — the ONE authority for
/// both what the server advertises under <c>ServerCapabilities/AggregateFunctions</c> (i=2997) and
/// <c>HistoryServerCapabilities/AggregateFunctions</c> (i=11201), and what its HistoryRead-Processed answers
/// <c>BadAggregateNotSupported</c> up front without calling the backend.
/// both what the server advertises under <c>HistoryServerCapabilities/AggregateFunctions</c> (i=11201) and what
/// its HistoryRead-Processed answers <c>BadAggregateNotSupported</c> up front without calling the backend.
/// (<c>ServerCapabilities/AggregateFunctions</c>, i=2997, is not affected: it lists every aggregate the server
/// computes, including for live <c>AggregateFilter</c> subscriptions.)
/// </summary>
/// <remarks>
/// The default is <see cref="HistoryAggregateTypes.All"/> — "every aggregate the server can name; a member I cannot
@@ -187,7 +188,8 @@ public interface IHistorianDataSource : IDisposable
/// <c>null</c> "all" sentinel so every consumer handles one shape. Every PRODUCTION backend declares its set itself
/// (a reflection guard pins that): a wrapper that forgot to forward this would otherwise advertise every aggregate.
/// The set is read once when the source is wired (the backend is fixed at boot by <c>ServerHistorian:Backend</c>)
/// and again on every processed read, so it must be cheap and stable — return a static frozen set.
/// and again on every processed read, so it must be cheap and stable — return a static frozen set. A source that
/// returns <c>null</c> anyway is treated as the default (all) by both consumers.
/// </remarks>
IReadOnlySet<HistoryAggregateType> SupportedAggregates => HistoryAggregateTypes.All;