using Serilog; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Core.Scripting; namespace ZB.MOM.WW.OtOpcUa.Core.ScriptedAlarms; /// /// subclass for alarm predicate evaluation. Reads from /// the engine's shared tag cache (driver + virtual tags), writes are rejected — /// predicates must be side-effect free so their output doesn't depend on evaluation /// order or drive cascade behavior. /// /// /// Per Phase 7 plan Shape A decision, alarm scripts are one-script-per-alarm /// returning bool. They read any tag they want but should not write /// anything (the owning alarm's state is tracked by the engine, not the script). /// public sealed class AlarmPredicateContext : ScriptContext { private readonly IReadOnlyDictionary _readCache; private readonly Func _clock; public AlarmPredicateContext( IReadOnlyDictionary readCache, ILogger logger, Func? clock = null) { _readCache = readCache ?? throw new ArgumentNullException(nameof(readCache)); Logger = logger ?? throw new ArgumentNullException(nameof(logger)); _clock = clock ?? (() => DateTime.UtcNow); } public override DataValueSnapshot GetTag(string path) { if (string.IsNullOrWhiteSpace(path)) return new DataValueSnapshot(null, 0x80340000u, null, _clock()); return _readCache.TryGetValue(path, out var v) ? v : new DataValueSnapshot(null, 0x80340000u, null, _clock()); } public override void SetVirtualTag(string path, object? value) { // Predicates must be pure — writing from an alarm script couples alarm state to // virtual-tag state in a way that's near-impossible to reason about. Rejected // at runtime with a clear message; operators see it in the scripts-*.log. throw new InvalidOperationException( "Alarm predicate scripts cannot write to virtual tags. Move the write logic " + "into a virtual tag whose value the alarm predicate then reads."); } public override DateTime Now => _clock(); public override ILogger Logger { get; } }