diff --git a/ZB.MOM.WW.ScadaBridge.slnx b/ZB.MOM.WW.ScadaBridge.slnx
index 90ed56aa..c62d9096 100644
--- a/ZB.MOM.WW.ScadaBridge.slnx
+++ b/ZB.MOM.WW.ScadaBridge.slnx
@@ -23,6 +23,7 @@
+
@@ -51,5 +52,6 @@
+
diff --git a/src/ZB.MOM.WW.ScadaBridge.ScriptAnalysis/RoslynScriptCompiler.cs b/src/ZB.MOM.WW.ScadaBridge.ScriptAnalysis/RoslynScriptCompiler.cs
new file mode 100644
index 00000000..8203e5ca
--- /dev/null
+++ b/src/ZB.MOM.WW.ScadaBridge.ScriptAnalysis/RoslynScriptCompiler.cs
@@ -0,0 +1,83 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Scripting;
+using Microsoft.CodeAnalysis.Scripting;
+
+namespace ZB.MOM.WW.ScadaBridge.ScriptAnalysis;
+
+///
+/// M3.1: the single authoritative Roslyn compile gate. Ported from the
+/// SiteRuntime ScriptCompilationService.CompileCore, but returns
+/// diagnostic messages rather than a compiled Script delegate — this is
+/// the design-time gate (the deploy-time validation that previously relied on
+/// the FAKE substring + brace-balance check in
+/// TemplateEngine/Validation/ScriptCompiler.cs), which needs to know
+/// whether the script compiles, not to execute it.
+///
+public static class RoslynScriptCompiler
+{
+ ///
+ /// Parses the script as C# script source and returns syntax-error diagnostic
+ /// messages (severity Error only). Empty list means the script parses.
+ ///
+ /// The C# script source to parse.
+ /// Error-severity parse diagnostic messages; empty if the script parses.
+ public static IReadOnlyList ParseDiagnostics(string code)
+ {
+ var tree = CSharpSyntaxTree.ParseText(
+ code, new CSharpParseOptions(kind: SourceCodeKind.Script));
+
+ return tree.GetDiagnostics()
+ .Where(d => d.Severity == DiagnosticSeverity.Error)
+ .Select(d => d.GetMessage())
+ .ToList();
+ }
+
+ ///
+ /// Compiles the script against the trust-model references and imports and
+ /// returns error-severity compilation diagnostic messages. Empty list means
+ /// the script compiles cleanly against .
+ ///
+ /// The C# script source to compile.
+ ///
+ /// Optional globals type the script binds against — e.g.
+ /// ScriptCompileSurface for instance/shared scripts or
+ /// TriggerCompileSurface for trigger expressions.
+ ///
+ /// Optional additional metadata references.
+ /// Optional additional namespace imports.
+ /// Error-severity compile diagnostic messages; empty if the script compiles.
+ public static IReadOnlyList Compile(
+ string code,
+ Type? globalsType = null,
+ IEnumerable? extraReferences = null,
+ IEnumerable? extraImports = null)
+ {
+ try
+ {
+ var references = ScriptTrustPolicy.DefaultReferences.ToList();
+ if (extraReferences != null)
+ references.AddRange(extraReferences);
+
+ var imports = ScriptTrustPolicy.DefaultImports.AsEnumerable();
+ if (extraImports != null)
+ imports = imports.Concat(extraImports);
+
+ var options = ScriptOptions.Default
+ .WithReferences(references)
+ .WithImports(imports);
+
+ var script = CSharpScript.Create