# Shared Script-Analysis Consolidation (M3) — Implementation Plan > **For Claude:** executed via superpowers-extended-cc:subagent-driven-development in this session. **Goal:** Extract one shared, authoritative script-trust analyzer (`ZB.MOM.WW.ScadaBridge.ScriptAnalysis`) and make all four call sites delegate to it, turning the fake design-time deploy gate into a real semantic compile + authoritative forbidden-API check. **Architecture:** New project just above Commons (refs Commons + Microsoft.CodeAnalysis.CSharp.Scripting/Workspaces). Hosts the trust policy, the fused semantic+syntactic validator, a Roslyn compile wrapper, and compile-only globals stubs. TemplateEngine/SiteRuntime/InboundAPI/CentralUI delegate; the executing globals stay with their owners. Design: `docs/plans/2026-06-16-script-analysis-consolidation-design.md`. **Tech Stack:** C#/.NET 10, Microsoft.CodeAnalysis (Roslyn) C# Scripting + Workspaces, xUnit. **Conventions:** targeted builds/tests per task (`dotnet build `, `dotnet test --filter`); full-solution build only in M3.6. Implementers do NOT create worktrees (already in one) and commit with pathspec form `git commit -m "..." -- ` (retry on index.lock). --- # Wave 0 — Spike ### Task M3.0: Confirm compile-surface stub feasibility + package refs **Classification:** small · **Estimated implement time:** ~4 min · **Parallelizable with:** none **Files (read-only spike):** `src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptCompilationService.cs` (ScriptGlobals), `TriggerExpressionGlobals.cs`, `src/ZB.MOM.WW.ScadaBridge.CentralUI/ScriptAnalysis/SandboxScriptHost.cs`, `Directory.Packages.props`. **Steps:** 1. Enumerate the full public member surface of `ScriptGlobals` and `TriggerExpressionGlobals` (names + rough shapes) that a script can reference. 2. Confirm every member's referenced types resolve from Commons alone (e.g. `ScriptParameters`, `AlarmContext`, `ScriptScope` in `Commons.Types(.Scripts)`); list any member whose type lives in SiteRuntime/CentralUI and would need a loose (`object`/`dynamic`-returning) stand-in on the stub. 3. Confirm `Microsoft.CodeAnalysis.CSharp.Scripting` (5.0.0) + `Microsoft.CodeAnalysis.CSharp.Workspaces` (5.0.0) are in `Directory.Packages.props`. **AC:** a short written inventory the M3.1 implementer can build `ScriptCompileSurface`/`TriggerCompileSurface` from, with each member's compile-surface return type decided (Commons type vs loose `object`). No code committed. --- # Wave 1 — Foundation ### Task M3.1: Build `ZB.MOM.WW.ScadaBridge.ScriptAnalysis` (policy + validator + compiler + surfaces + tests) **Classification:** high-risk (security boundary) · **Estimated implement time:** ~5 min · **Parallelizable with:** none · **Depends on:** M3.0 **Files:** - Create: `src/ZB.MOM.WW.ScadaBridge.ScriptAnalysis/ZB.MOM.WW.ScadaBridge.ScriptAnalysis.csproj` (refs Commons + the two CodeAnalysis packages; add to `ZB.MOM.WW.ScadaBridge.slnx`). - Create: `ScriptTrustPolicy.cs`, `ScriptTrustValidator.cs`, `RoslynScriptCompiler.cs`, `ScriptCompileSurface.cs`, `TriggerCompileSurface.cs`. - Create: `tests/ZB.MOM.WW.ScadaBridge.ScriptAnalysis.Tests/...` (+ add to slnx). **Design points:** - `ScriptTrustPolicy`: ForbiddenScopes = `System.IO`, `System.Diagnostics.Process`, `System.Threading`, `System.Reflection`, `System.Net`, `System.Runtime.InteropServices`, `Microsoft.Win32`. AllowedExceptions = `System.Threading.Tasks`, `System.Threading.CancellationToken`, `System.Threading.CancellationTokenSource`. ReflectionGatewayMembers = the InboundAPI `ForbiddenMemberNames` set. ForbiddenIdentifiers = `dynamic`, `Activator`. DefaultReferences = the SiteRuntime `ScriptAssemblies` set (object, Enumerable, Math, CSharpArgumentInfo, Commons). DefaultImports = `System`, `System.Collections.Generic`, `System.Linq`, `System.Threading.Tasks`. - `ScriptTrustValidator.FindViolations(code, extraReferences?)`: port SiteRuntime `ValidateTrustModel` (CreateScriptCompilation + semantic model; `GetSymbolScopes`/`GetSyntacticScopes`/`IsUnderScope` symbol-vs-syntax resolution; dedup via `SortedSet`), and FUSE in InboundAPI's reflection-gateway member-name + `dynamic`/`Activator` syntactic checks. Returns combined violations. - `RoslynScriptCompiler.Compile(code, globalsType?, extraReferences?, extraImports?)`: `CSharpScript.Create(code, options, globalsType).Compile()`, map error diagnostics to messages. `ParseDiagnostics(code)`: parse with `SourceCodeKind.Script`, return syntax error diagnostics. - `ScriptCompileSurface`/`TriggerCompileSurface`: mirror the M3.0 inventory; compile-only, never executed. **Tests (TDD):** adversarial corpus → violations (alias, `using static`, `global::`, `typeof(x).Assembly.GetType("System.IO.File")`, `dynamic`, `Activator`, `System.Runtime.InteropServices`, `Microsoft.Win32`); legit → clean (`await Task.Delay`, `Stopwatch`, LINQ, `Math`, `CancellationToken`); `ParseDiagnostics` flags syntax error; `Compile(..., typeof(ScriptCompileSurface))` rejects undefined symbol AND accepts a real script using `Attributes`/`Notify`/`ExternalSystem`; parity test: public member names of `ScriptCompileSurface` ⊇ those of … (parity asserted in M3.3 once SiteRuntime references the project — here assert the surface compiles a representative real script). **AC:** `dotnet build` + `dotnet test` for ScriptAnalysis green; adversarial corpus all rejected; legit all clean. --- # Wave 2 — Consumers delegate (parallelizable; disjoint projects) ### Task M3.2: TemplateEngine deploy gate → shared analyzer **Classification:** high-risk (the authoritative deploy gate) · **~5 min** · **Parallelizable with:** M3.3, M3.4, M3.5 · **Depends on:** M3.1 **Files:** `src/ZB.MOM.WW.ScadaBridge.TemplateEngine/ZB.MOM.WW.ScadaBridge.TemplateEngine.csproj` (+ ProjectReference to ScriptAnalysis), `Validation/ScriptCompiler.cs`, `Validation/ValidationService.cs` (`CheckExpressionSyntax`), `tests/ZB.MOM.WW.ScadaBridge.TemplateEngine.Tests/Validation/ScriptCompilerTests.cs`. **Changes:** `ScriptCompiler.TryCompile` → empty-check, then `ScriptTrustValidator.FindViolations`, then `RoslynScriptCompiler.Compile(code, typeof(ScriptCompileSurface))`; return first failure as `Result.Failure`. `CheckExpressionSyntax` → `FindViolations` + `Compile(expr, typeof(TriggerCompileSurface))`. Delete `ForbiddenPatterns` + the substring/brace path (keep `CSharpDelimiterScanner` only if still referenced elsewhere — else remove). Re-point/adjust `ScriptCompilerTests` expected messages. **AC:** invalid C# (undefined symbol) fails validation; adversarial alias/`using static`/`global::`/reflection-gateway scripts fail; valid real scripts pass. `dotnet test` TemplateEngine green. ### Task M3.3: SiteRuntime → shared analyzer (keep real-globals compile) **Classification:** high-risk (runtime trust gate) · **~4 min** · **Parallelizable with:** M3.2, M3.4, M3.5 · **Depends on:** M3.1 **Files:** `src/ZB.MOM.WW.ScadaBridge.SiteRuntime/ZB.MOM.WW.ScadaBridge.SiteRuntime.csproj` (+ ref), `Scripts/ScriptCompilationService.cs`, `tests/.../Scripts/TrustModelSemanticTests.cs`, `ScriptCompilationServiceTests.cs`. **Changes:** `ValidateTrustModel` → delegate to `ScriptTrustValidator.FindViolations`. Keep `CompileCore`/`CSharpScript.Compile` against the real `ScriptGlobals`/`TriggerExpressionGlobals`. Remove the now-duplicated `ForbiddenNamespaces`/`AllowedExceptions`/scope helpers. **Add the parity test** (`ScriptCompileSurface` public member-name set ⊇ `ScriptGlobals`; `TriggerCompileSurface` ⊇ `TriggerExpressionGlobals`) here, where both types are referenceable. **AC:** SiteRuntime trust tests green; parity test green; reflection-gateway bypass now rejected at the site too. ### Task M3.4: InboundAPI → shared analyzer **Classification:** standard · **~3 min** · **Parallelizable with:** M3.2, M3.3, M3.5 · **Depends on:** M3.1 **Files:** `src/ZB.MOM.WW.ScadaBridge.InboundAPI/ZB.MOM.WW.ScadaBridge.InboundAPI.csproj` (+ ref to ScriptAnalysis), `ForbiddenApiChecker.cs`, `InboundScriptExecutor.cs` (call site if needed), `tests/.../ForbiddenApiCheckerTests.cs`. **Changes:** `ForbiddenApiChecker.FindViolations` → delegate to `ScriptTrustValidator.FindViolations` (keep the public method as a thin shim so callers are untouched). Remove the local walker/lists. Adjust test expectations where wording changed; the now-loosened `System.Diagnostics` (Stopwatch allowed) and now-semantic resolution should be reflected. **AC:** InboundAPI forbidden-API tests green against the shared validator; reflection-gateway + `dynamic` + `Activator` still rejected. ### Task M3.5: CentralUI → shared analyzer (keep editor markers + Test-Run host) **Classification:** standard · **~4 min** · **Parallelizable with:** M3.2, M3.3, M3.4 · **Depends on:** M3.1 **Files:** `src/ZB.MOM.WW.ScadaBridge.CentralUI/ZB.MOM.WW.ScadaBridge.CentralUI.csproj` (+ ref), `ScriptAnalysis/ScriptAnalysisService.cs`, `tests/.../ScriptAnalysis/ScriptAnalysisServiceTests.cs`. **Changes:** `FindForbiddenApiUsages`/`ValidateTrustModel` → delegate to `ScriptTrustValidator.FindViolations` for the trust verdict, then map to `DiagnosticMarker`s (keep marker positions/codes). Remove the local `ForbiddenNamespacePrefixes`/`ForbiddenNameFor`/`IsForbiddenName`. Keep `Diagnose` compile + `SandboxScriptHost` Test-Run execution unchanged. Adjust tests where the (now stricter) threading policy changes a marker. **AC:** CentralUI script-analysis tests green; editor now flags the previously-allowed `System.Threading` usages. --- # Wave 3 — Integration + docs ### Task M3.6: Integration verification + docs + fixture cleanup **Classification:** high-risk (final integration reviewer) · **~5 min** · **Parallelizable with:** none · **Depends on:** M3.2–M3.5 **Steps:** 1. `dotnet build ZB.MOM.WW.ScadaBridge.slnx` (full solution). 2. Targeted tests across ScriptAnalysis, TemplateEngine, SiteRuntime, InboundAPI, CentralUI; fix any latent-invalid fixtures/scripts the real compile surfaces. 3. Docs: `docs/requirements/Component-TemplateEngine.md` (real compile gate), `Component-SiteRuntime.md`, `Component-InboundAPI.md`, `Component-Commons.md` if surfaces move; add the new component to `README.md` table + `CLAUDE.md` component list (24 → 25); remove the "SECURITY LIMITATION / advisory" notes in `ScriptCompiler.cs`/`ValidationService.cs` now that the gate is authoritative. 4. Commit; final integration code review of the whole `8e99f22..HEAD` diff. **AC:** full build green; trust tests + adversarial deploy test green; no doc claims the design-time gate is advisory; component list updated. --- ## Native tasks & dependencies Sub-tasks created as native tasks under umbrella #14 (M3). Edges: M3.1 ⟵ M3.0; M3.2/M3.3/M3.4/M3.5 ⟵ M3.1; M3.6 ⟵ M3.2..M3.5.