feat(historian): advertise only the backend's aggregates under AggregateFunctions (#682)

IHistorianDataSource.SupportedAggregates (default: every HistoryAggregateType)
is the one authority for both ServerCapabilities/AggregateFunctions (i=2997) and
HistoryServerCapabilities/AggregateFunctions (i=11201) and for the up-front
BadAggregateNotSupported in HistoryRead-Processed. Gateway declares
AggregateModeMapper.Served (4), SqlServer/QuestDB AggregateFamilies.Served (17),
Null none. Only the folder references are trimmed; the SDK's calculator
factories stay so live AggregateFilter subscriptions keep working.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-10-12 08:52:47 -04:00
parent 8ebee2ac40
commit 6ea3ba44da
20 changed files with 654 additions and 58 deletions
@@ -63,4 +63,19 @@ public sealed class HistorianDataSourceReverseEventsGuardTests
$"{type.FullName} must declare SupportsReverseEvents itself, not inherit the interface default");
}
}
[Fact]
public void Every_implementation_declares_its_supported_aggregates_itself()
{
// Gitea #682: the interface default is "every aggregate" — a production backend (or a future wrapper) that
// inherited it would advertise all of them under AggregateFunctions and forward reads it cannot serve.
foreach (var type in ProductionImplementations())
{
var map = type.GetInterfaceMap(typeof(IHistorianDataSource));
var getter = map.TargetMethods[Array.FindIndex(map.InterfaceMethods,
m => m.Name == "get_" + nameof(IHistorianDataSource.SupportedAggregates))];
getter.DeclaringType.ShouldBe(type,
$"{type.FullName} must declare SupportedAggregates itself, not inherit the interface default");
}
}
}