11 KiB
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 <project>, 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 "..." -- <paths> (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:
- Enumerate the full public member surface of
ScriptGlobalsandTriggerExpressionGlobals(names + rough shapes) that a script can reference. - Confirm every member's referenced types resolve from Commons alone (e.g.
ScriptParameters,AlarmContext,ScriptScopeinCommons.Types(.Scripts)); list any member whose type lives in SiteRuntime/CentralUI and would need a loose (object/dynamic-returning) stand-in on the stub. - Confirm
Microsoft.CodeAnalysis.CSharp.Scripting(5.0.0) +Microsoft.CodeAnalysis.CSharp.Workspaces(5.0.0) are inDirectory.Packages.props. AC: a short written inventory the M3.1 implementer can buildScriptCompileSurface/TriggerCompileSurfacefrom, with each member's compile-surface return type decided (Commons type vs looseobject). 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 toZB.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 InboundAPIForbiddenMemberNamesset. ForbiddenIdentifiers =dynamic,Activator. DefaultReferences = the SiteRuntimeScriptAssembliesset (object, Enumerable, Math, CSharpArgumentInfo, Commons). DefaultImports =System,System.Collections.Generic,System.Linq,System.Threading.Tasks.ScriptTrustValidator.FindViolations(code, extraReferences?): port SiteRuntimeValidateTrustModel(CreateScriptCompilation + semantic model;GetSymbolScopes/GetSyntacticScopes/IsUnderScopesymbol-vs-syntax resolution; dedup viaSortedSet), and FUSE in InboundAPI's reflection-gateway member-name +dynamic/Activatorsyntactic checks. Returns combined violations.RoslynScriptCompiler.Compile(code, globalsType?, extraReferences?, extraImports?):CSharpScript.Create<object?>(code, options, globalsType).Compile(), map error diagnostics to messages.ParseDiagnostics(code): parse withSourceCodeKind.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);ParseDiagnosticsflags syntax error;Compile(..., typeof(ScriptCompileSurface))rejects undefined symbol AND accepts a real script usingAttributes/Notify/ExternalSystem; parity test: public member names ofScriptCompileSurface⊇ 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 testfor 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<bool>.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 DiagnosticMarkers (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:
dotnet build ZB.MOM.WW.ScadaBridge.slnx(full solution).- Targeted tests across ScriptAnalysis, TemplateEngine, SiteRuntime, InboundAPI, CentralUI; fix any latent-invalid fixtures/scripts the real compile surfaces.
- Docs:
docs/requirements/Component-TemplateEngine.md(real compile gate),Component-SiteRuntime.md,Component-InboundAPI.md,Component-Commons.mdif surfaces move; add the new component toREADME.mdtable +CLAUDE.mdcomponent list (24 → 25); remove the "SECURITY LIMITATION / advisory" notes inScriptCompiler.cs/ValidationService.csnow that the gate is authoritative. - Commit; final integration code review of the whole
8e99f22..HEADdiff. 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.