Closes Phase 7 Gap 5: VirtualTagEngine called IHistoryWriter.Record per evaluation when Historize=true but Phase7EngineComposer always passed NullHistoryWriter, so virtual-tag history was computed but never persisted. The fix: - New RingBufferHistoryWriter implements both IHistoryWriter (write port for the evaluation pipeline) and IHistorianDataSource (read port for IHistoryRouter so OPC UA HistoryRead on virtual-tag nodes resolves here). Maintains one bounded ring buffer (1000 samples, configurable) per tag path; Record() is O(1) and never blocks evaluation. - Phase7EngineComposer.Compose now accepts IHistoryRouter? and, when any VirtualTagDefinition.Historize=true, creates a RingBufferHistoryWriter, passes it to VirtualTagEngine as historyWriter, adds it to the disposables list, and registers it under the "virtual:" prefix in the router for HistoryRead dispatch. - Phase7Composer accepts IHistoryRouter? from DI (already registered as singleton in Program.cs) and threads it through to Phase7EngineComposer.Compose. - NullHistoryWriter remains as fallback when no tags request historization. - 16 new unit tests in RingBufferHistoryWriterTests.cs cover ring-buffer semantics, eviction, per-tag isolation, ReadRawAsync windowing, IHistorianDataSource stubs, router registration, and the Historize=false / null-router fallback paths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
12 KiB
12 KiB