using System.Collections.Concurrent; using System.Security.Cryptography; using System.Text; namespace ZB.MOM.WW.ScadaBridge.ScriptAnalysis; /// /// Process-wide memo of verdicts for the /// default policy reference set, keyed on the SHA-256 of the script code (review /// 2026-10-21 04/P3). /// /// /// A verdict is a pure function of (code, , /// ), and the policy and /// reference set are process-static, so the code hash alone identifies it. Each /// miss is a full-framework semantic analysis (~170 references) — the bundle /// importer runs the same script set through it on preview and again on apply, /// and the wizard re-enters preview — so memoising here removes that repeat cost /// without any change at the call sites. /// /// /// /// Bounded like the Template Engine's compile-verdict cache: two generations of /// entries; a hit in the cold generation is promoted, /// and overflow drops only the cold half nothing touched for a whole generation. /// Only completed verdicts are stored — an analysis that throws leaves the slot /// empty. Stored verdicts are read-only collections, so a caller cannot mutate a /// shared entry. /// /// internal static class TrustVerdictCache { /// Upper bound on entries per generation (2 × this in total). internal const int SegmentCapacity = 1024; private static readonly object RotateGate = new(); private static volatile ConcurrentDictionary> _hot = new(); private static volatile ConcurrentDictionary> _cold = new(); /// /// Returns the cached verdict for , or computes it with /// and caches it on a miss. /// /// The script source. /// Computes the verdict on a miss; a throw is not cached. /// The (read-only) violation list. internal static IReadOnlyList GetOrAdd(string code, Func> analyse) { var key = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(code))); var hot = _hot; var cold = _cold; if (hot.TryGetValue(key, out var verdict)) return verdict; if (cold.TryGetValue(key, out verdict)) { Store(key, verdict); return verdict; } verdict = analyse().ToList().AsReadOnly(); Store(key, verdict); return verdict; } private static void Store(string key, IReadOnlyList verdict) { if (_hot.Count >= SegmentCapacity) { lock (RotateGate) { if (_hot.Count >= SegmentCapacity) { _cold = _hot; _hot = new ConcurrentDictionary>(); } } } // TryAdd, not an indexer write: a racing thread that computed the same verdict // keeps the instance already published, so every reader shares one object. _hot.TryAdd(key, verdict); } }