Scanner-worker .NET call graph: M1 enablement
Source verification: ee0f1b9896 (DWL-3, Windows and sealed Linux). Reverify the semantic/negative controls with pwsh tools/scripts/test-targeted-xunit.ps1 -Project src/Scanner/__Tests/StellaOps.Scanner.CallGraph.Tests/StellaOps.Scanner.CallGraph.Tests.csproj -Class "*DotNet*" -BuildProjectReferences. Delivered-payload verification: 3f7a1f1141 (DWL-5), the worker’s own reachability stage run from a worker publish payload, offline, in the reviewed SDK and runtime-base images. See the delivered-payload receipt. Reverify a payload or a local worker image with tools/scripts/test/qualify-dotnet-m1-delivered.py. Neither is published-image or live acceptance.
M1 uses an operator-provided .NET SDK/MSBuild toolchain, restored project inputs and Roslyn semantic analysis. M2 remains the default lightweight path. The worker selects M1 only when Scanner:Worker:Reachability:DotNetExtractor is msbuild, restore or M1 (case-insensitive). auto selects M2.
The implementation is in DotNetCallGraphExtractor, DotNetWorkspaceLoader and the worker’s ReachabilityInputStageExecutor. ADR-042 records the original dual-mode design. Its proposed automatic selector was not the shipped configuration path.
Toolchain and input preparation
The ordinary runtime image does not contain an SDK. Provision an M1-capable worker image with a reviewed, digest-pinned SDK/toolchain and its notices before enabling M1. Do not fetch/install an SDK during scans. Use the existing release build/signing procedures for the resulting image.
The SDK must support the analysed project’s global.json, target frameworks and reference packs. The default executable is dotnet on PATH. An operator-set MSBUILD_EXE_PATH selects an explicit MSBuild executable (or DLL run by dotnet) for design-time evaluation. Traditional non-SDK projects can use installed .NET Framework reference assemblies or the operator’s matching build tools. A .NET 10 analyser process does not itself supply those target reference packs.
M1 evaluates project imports, tasks and Roslyn generators with the worker’s permissions. Use trusted project inputs or an appropriately isolated worker environment; design-time compilation is not a sandbox. The application being analysed is not started by this procedure.
Configure the worker
{
"Scanner": {
"Worker": {
"Reachability": {
"DotNetExtractor": "msbuild",
"DotNetRestoreTimeout": "00:05:00"
}
}
}
}
Environment equivalents:
Scanner__Worker__Reachability__DotNetExtractor=msbuild
Scanner__Worker__Reachability__DotNetRestoreTimeout=00:05:00
The worker restores the discovered solution/project before analysis. For offline operation set NUGET_OFFLINE_PACKAGES to the mounted operator-maintained package feed. The existing restorer then uses only that source with --no-http-cache. Framework/reference packs must also be available. Do not interpret a successful package metadata lookup as a completed restore.
What M1 does
The workspace adapter runs design-time MSBuild under a five-minute process budget; Roslyn/generators receive the same cancellation token and trusted generator code must cooperate. It disables build-node reuse and shared compilation, kills its process tree on cancellation/timeout, and limits each private binary log to 32 MiB and each redirected output stream to 256 Ki characters. It reads the local log with Buildalyzer 9.0.0, preserves C# compiler options and analyzer configuration, joins project references, and uses the existing Roslyn graph traversal. Generated sources receive stable virtual paths under the project’s .stella-generated/ directory; generator exceptions and load failures are explicit failures even when Roslyn classifies them as warnings.
The adapter does not call Buildalyzer’s synchronous build method. It does not carry Microsoft.VisualStudio.Setup.Configuration.Interop, MSBuildLocator or Roslyn’s nested Framework/Core BuildHost payloads. The removed component is not made MIT by adding a notice. See the dependency selection.
EF Core’s private design-time dependencies can independently copy the old Roslyn BuildHost content into a service. The shared build targets preserve that content in developer build output for EF tooling, but mark it CopyToPublishDirectory=Never. Published product artifacts use the replacement M1 adapter; they do not distribute those unused developer hosts. Check actual nested payloads with tools/scripts/validate/check-dotnet-workspace-distribution.py, since a service’s root dependency manifest alone does not list every copied content file.
Binary logs can contain private build/environment values. They stay in a private, invocation-owned temporary directory and are deleted after analysis, including failed analysis. They are never copied into graph evidence or emitted as user-visible error text.
Verify success and failure
A successful worker run records the restored package-cache path in reachability.dotnet.restore.cache_path, emits a non-empty semantic graph and proceeds to reachability evidence. Check the graph’s nodes, edges and expected source-to-sink witness. A successful service health check alone is not M1 acceptance.
Explicitly requested M1 now fails the stage when its restorer/toolchain, restore, project evaluation, compilation or graph is unavailable. It does not continue with an unrestored graph or silently switch to M2. On restore failure reachability.dotnet.restore.error contains the safe code dotnet.workspace.restore_failed; raw restore output is not copied into lease metadata.
The worker restores before it evaluates the project, so SDK and target-framework problems usually surface as restore_failed. The stage metadata carries only the safe code. The restorer’s log line carries the dotnet restore exit code; the raw restore output is not logged. Run the same restore by hand in the worker’s environment to see the message.
| Restorer exit code | Typical cause | Check |
|---|---|---|
| 155 | No SDK: only the .NET runtime host is present | Use an M1-capable image with a reviewed SDK. The service image starts the worker through the dotnet host, so the restorer reaches that runtime-only host even when PATH has no dotnet. |
| 1 | Restore error, such as NU1101 or NETSDK1045 | Package present in NUGET_OFFLINE_PACKAGES; the SDK supports the project’s target frameworks and global.json |
| Failure code | Check |
|---|---|
dotnet.workspace.restore_failed | SDK present, offline feed complete, target frameworks supported by the SDK, restore within DotNetRestoreTimeout |
dotnet.workspace.tool_unavailable | The design-time build executable cannot start: dotnet, or an MSBUILD_EXE_PATH executable that does not exist |
dotnet.workspace.restore_required | Evaluated SDK project-assets path exists after restore |
dotnet.workspace.design_time_build_failed | Project imports, installed reference packs and operator build toolchain; also an MSBUILD_EXE_PATH .dll that the dotnet host cannot run |
dotnet.workspace.compilation_failed | Source/reference/generator errors; partial semantic graphs are refused |
dotnet.workspace.timeout | Analysis exceeds the five-minute budget |
dotnet.workspace.output_limit / binary_log_limit | Project diagnostic or binary-log size exceeds the bound |
dotnet.workspace.restorer_unavailable | Worker M1 restore registration |
dotnet.workspace.empty_graph | Required M1 analysis produced no graph nodes |
To return to lightweight operation, explicitly set DotNetExtractor=auto and restart using the normal operator procedure. M2 retains its documented lower-confidence generic, source-generator and cross-assembly limits. Switching modes is an operator choice, not an error-recovery fallback.
