Phase 7 Stream B — Core.VirtualTags engine + dep graph + timer + source #180
Reference in New Issue
Block a user
Delete Branch "phase-7-stream-b-virtual-tag-engine"
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?
Ships the evaluation engine that consumes compiled scripts from Stream A, subscribes to upstream driver tags, runs on change + on timer, cascades evaluations through dependent virtual tags in topological order, and emits changes through a driver-capability-shaped adapter the DriverNodeManager can dispatch to per ADR-002.
New project
Core.VirtualTagsDependencyGraph— Kahn topological sort + iterative Tarjan SCC cycle detection (both non-recursive, handles 10k deep chains)VirtualTagDefinition— operator config row (Path / DataType / ScriptSource / ChangeTriggered / TimerInterval / Historize)ITagUpstreamSource/IHistoryWriter— abstractions for driver-tag reads + history sinkVirtualTagContext— per-evaluationScriptContextwith cached reads + write-callback + injectable clockVirtualTagEngine— orchestrator: compiles every script, builds dep graph, checks cycles, subscribes to upstream, cascades evaluations in topological order through aSemaphoreSlim, per-tag error isolation, Historize routingTimerTriggerScheduler— groups tags by interval into sharedTimersVirtualTagSource—IReadable + ISubscribableadapter forDriverNodeManagerdispatch per ADR-002 (IWritabledeliberately omitted — OPC UA writes to virtual tags rejected upstream)Tests — 36/36
DependencyGraphTests(12): empty / single / topo ordering / self-loop / 2-node + 3-node cycles / multiple disjoint cycles all reported / throws on cycle / DirectDependents / TransitiveDependentsInOrder / re-add cleans up / leaf implicit / 10k deep no stack overflowVirtualTagEngineTests(13): simple read+coerce / 2-level cascade / cycle rejected at Load / compile errors aggregated / runtime exception isolates to owning tag / timeout isolates / subscribers receive changes / Historize on+off / ChangeTriggered=false / reload replaces+cleans subscriptions / Dispose releases / SetVirtualTag fires observers / type coercion 3.7→4VirtualTagSourceTests(6): ReadAsync cache hit / unknown returns Bad / SubscribeAsync initial-data per spec / cascade-emits-on-upstream / Unsubscribe stops events / null rejectionTimerTriggerSchedulerTests(4): periodic ticks / no-timer tags not scheduled / multiple intervals grouped / disposed rejects re-StartTwo bugs fixed during implementation
Monitor.Enter/Exitcan't be held acrossawait— swapped toSemaphoreSlim.WaitAsyncinDegree[dep]instead ofinDegree[nodeId], producing false cycle detection on valid DAGsTotals
Full Phase 7 tests: 99 green (63 Scripting + 36 VirtualTags). Streams C (scripted alarms) and G (address-space integration) plug the engine + source into the live OPC UA dispatch path.