feat(engines): IVirtualTagEvaluator snapshot overload; Roslyn read cache stops fabricating Good (02/S14)

This commit is contained in:
Joseph Doherty
2026-08-20 10:45:11 -04:00
parent 60e5b40228
commit cc9b845f99
5 changed files with 166 additions and 19 deletions
@@ -1,3 +1,5 @@
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Commons.Engines;
/// <summary>
@@ -18,6 +20,34 @@ public interface IVirtualTagEvaluator
/// <param name="dependencies">Read-only dictionary of variable names to values for expression evaluation.</param>
/// <returns>Result containing success flag, evaluated value, and optional failure reason.</returns>
VirtualTagEvalResult Evaluate(string virtualTagId, string expression, IReadOnlyDictionary<string, object?> dependencies);
/// <summary>
/// Evaluate <paramref name="expression"/> against dependency <b>snapshots</b> — value, status
/// code and source timestamp per input — so a script's <c>ctx.GetTag(path).StatusCode</c> reports
/// what the source actually said (archreview 02/S14).
/// </summary>
/// <remarks>
/// <b>Default-implemented on purpose.</b> It projects to values and calls the older overload, so
/// every existing implementation — the Roslyn evaluator aside, that is the null evaluator and the
/// test fakes — compiles and behaves unchanged; an implementation that can do better (the Roslyn
/// one) overrides it. Making it abstract would have been a breaking change for a fix whose whole
/// point is that it changes nothing on the Good path.
/// <para>The default necessarily discards the status codes it was given, which is exactly the
/// laundering this overload exists to end — so a consumer that cares must call an implementation
/// that overrides it.</para>
/// </remarks>
/// <param name="virtualTagId">The unique identifier of the virtual tag being evaluated.</param>
/// <param name="expression">The expression string to evaluate.</param>
/// <param name="dependencies">Read-only dictionary of dependency snapshots, keyed by tag path.</param>
/// <returns>Result containing success flag, evaluated value, and optional failure reason.</returns>
VirtualTagEvalResult Evaluate(
string virtualTagId, string expression, IReadOnlyDictionary<string, DataValueSnapshot> dependencies)
{
ArgumentNullException.ThrowIfNull(dependencies);
var values = new Dictionary<string, object?>(dependencies.Count, StringComparer.Ordinal);
foreach (var kv in dependencies) values[kv.Key] = kv.Value?.Value;
return Evaluate(virtualTagId, expression, values);
}
}
/// <summary>Result of one virtual-tag expression eval. Stash a Reason on every Failure so