Skip to content

[DO NOT MERGE] CI vehicle — net10 superset + dotnet-script 2.0.1 - #2127

Draft
NickJosevski wants to merge 13 commits into
mainfrom
nj/net10-ds201
Draft

[DO NOT MERGE] CI vehicle — net10 superset + dotnet-script 2.0.1#2127
NickJosevski wants to merge 13 commits into
mainfrom
nj/net10-ds201

Conversation

@NickJosevski

@NickJosevski NickJosevski commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Not for review or merge. Sibling of #2125; exists so TeamCity publishes a second pre-release for A/B testing on a pre-production cloud instance.

Identical to #2125 (net10 superset, includes DOTNET_ROLL_FORWARD=Major) plus one commit: bundled dotnet-script 1.6.0 → 2.0.1.

Why the bump is needed as well as roll-forward

Roll-forward gets dotnet-script launching on a net10-only machine. It does not make it work. Once launched, #r "nuget: <framework-provided package>" fails:

Error loading lock file .../net10.0/obj/project.assets.json :
  Cannot compare the value of a token type 'Number' to text.
The lock file ... does not contain a runtime target.

.NET 10 SDK package pruning emits NU1510 — the same warning already suppressed in Calamari.Common.csproj. dotnet-script 1.6.0 bundles NuGet.ProjectModel 6.10.0, which cannot parse the project.assets.json the .NET 10 SDK writes when that pruning applies.

Correction. An earlier revision of this description claimed the specific trigger was a numeric "warningLevel" in the assets file's top-level logs array. That is wrong — I tested RestSharp 110.2.0, which produces an assets file with the identical shape (a logs entry with "warningLevel": 1, for NU1902) and restores fine on net10. So the numeric warningLevel is not the trigger. What is reproducible, and is what this PR rests on, is the behaviour below: framework-provided packages fail on 6.10.0 and pass on 6.14.3. The precise field that breaks the 6.10.0 parser has not been isolated.

2.0.1 bundles NuGet.ProjectModel 6.14.3 (upstream dotnet-script#791) and parses it. Verified against a net10-only runtime:

1.6.0 + roll-forward  → Cannot compare the value of a token type 'Number' to text
2.0.1 + roll-forward  → runtime : .NET 10.0.10 / OK : {"ok":true}
2.0.1, no roll-forward → You must install or update .NET to run this application

The published 2.0.1 zip is still tfm: net8.0 requiring Microsoft.NETCore.App 8.0.0, so this does not replace the roll-forward change — both are required.

Behaviour changes to watch in pre-production

Both are upstream 2.0 defaults, left unpinned deliberately so testing exercises what would actually ship:

  • Roslyn 4.11.0 → 5.0.0-2.final (prerelease). ScriptCompiler hardcodes LanguageVersion.Preview, so the language ceiling available to customer scripts moves with it.
  • Isolated assembly load context becomes the default. --isolated-load-context was removed and inverted to --disable-isolated-load-context; Calamari passes neither.

⚠️ Does this change require a corresponding Server Change? No — CI vehicle only.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here

NickJosevski and others added 13 commits August 18, 2026 17:48
dotnet-script is framework-dependent and requests Microsoft.NETCore.App 8.0.0
with no rollForward, so the default Minor policy binds it to an 8.x runtime and
nothing higher. On a target with no 8.x runtime it fails to launch with exit 150
before any script runs. Calamari itself is published self-contained and is
unaffected - this is only the separate dotnet-script process.

Applies "rollForward": "Major" by overwriting the extracted runtimeconfig with a
copy held in source control, so the vendored zip stays byte-for-byte upstream and
the policy stays reviewable as text that a future re-vendor cannot silently drop.

Major rather than LatestMajor, which the decision doc recommended. LatestMajor
regresses a target that works today: dotnet-script derives its generated csproj
TFM from the loaded corelib, so rolling the host to 10 makes it emit net10.0, and
on a box with the .NET 10 runtime but only the .NET 8 SDK the restore then fails
with NETSDK1045. Measured in a container with runtimes 10.0.11 + 8.0.29 and SDK
8.0.423 only: no policy and Major both work on 8.0.29, LatestMajor exits 1.
Major fixes the reported failure and moves nobody else.

Also adds Octopus.Action.Script.CSharp.RollForward for a per-step override, set
as DOTNET_ROLL_FORWARD only when the variable is non-empty. That outranks the
runtimeconfig and is the only mechanism that reaches a dotnet-script the customer
installed themselves, which Calamari prefers over the bundled copy.

Verified end to end on a Hosted Windows worker through the real dotnet-script.cmd
launch path: a runtimeconfig requesting framework 6.0.0 with no policy fails to
launch, and the same config with rollForward Major starts on 8.0.27.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The zip is vendored byte-for-byte from the upstream GitHub release
(sha256 6675357...e0e1e13, verifiable against the release asset). Its
runtimeconfig is byte-identical to 1.6.0's, so the roll-forward policy in the
previous commit is still needed and is unaffected by the bump.

Takes upstream's 2.0 default of an isolated assembly load context, which fixes
dotnet-script #763 - native NuGet assets (SQLite, SkiaSharp,
Microsoft.Data.SqlClient) have never worked in C# script steps - and gives a
script the package version it asked for instead of whichever version
dotnet-script itself carries. The cost is that a type loaded via
Assembly.LoadFrom is no longer reference-equal to the same type in the script's
own closure, so Octopus.Action.Script.CSharp.DisableIsolatedLoadContext restores
the old behaviour for a step that needs it.

Also strips the 1.6.0 --isolated-load-context flag from a step's script
parameters. 2.0 dropped that option, and dotnet-script forwards options it does
not recognise into the *script's* argument list rather than rejecting them -
measured on 1.6.0 and 2.0.1 alike, silently and with exit 0. Left in place the
flag becomes Env.ScriptArgs[0] and shifts every real argument along by one, which
would silently break the existing steps that pass it. Isolation is the default
now, so dropping the flag preserves what those steps asked for.

The two UsingIsolatedAssemblyLoadContext cases are replaced rather than updated.
The old test asserted that isolation *off* fails outright, which held only
because 1.6.0 bundled NuGet 6.10.0.107 - lower than the 6.10.1.5 the script pins,
and the default load context refuses a downgrade while accepting an upgrade.
2.0.1 bundles 6.14.3.1, so the same collision resolves silently to the wrong
assembly. The replacements assert the version binding in each direction, so they
test the load context rather than an accident of which version is vendored.

Separately shippable: dropping this commit leaves the roll-forward fix intact.
Roslyn 4.11 -> 5.0.0-2.final also raises the C# ceiling from 13 to 14, which is a
widening but a one-way door - a script using C# 14 will not compile if the bundle
is ever rolled back.

DotnetScriptFixture is 11/11 and DotnetScriptBootstrapperFixture plus
DotnetScriptRuntimeConfigFixture are 26/26. DotnetScriptProxyFixture fails the
same 4 of 11 as clean main, verified side by side.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
.NET 8 reaches end of support on 10 November 2026. Moving the SDK and moving the
product target framework are two separate risks. Doing both together makes a
failure hard to attribute. This commit moves only the toolchain.

global.json goes to SDK 10.0.302. The Nuke build project targets net10.0.
Frameworks.cs gains Net100 constants alongside the existing ones.

The .NET 10 SDK audits transitive packages during restore. Auditing surfaced
vulnerable dependencies reached through Octopus.Nuke.Common. TreatWarningsAsErrors
turns those advisories into build errors. Fixed versions are pinned rather than the
audit being suppressed.

Signing.cs suppresses SYSLIB0057 at one call site. X509CertificateLoader only loads
certificate files. No replacement exists for reading an Authenticode signature out
of a signed PE.

Verified: source/Calamari.sln builds at 164 warnings and 0 errors, identical to the
pre-change baseline.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The .NET 10 SDK raises NU1510 for direct references to packages the framework
already provides. TreatWarningsAsErrors turns NU1510 into a build error. Removing
the references now, on net8, keeps the change isolated from the framework move.

Removed System.ValueTuple, System.ComponentModel.TypeConverter,
System.Diagnostics.Tracing, System.IO.FileSystem,
System.Runtime.InteropServices.RuntimeInformation, and the
Microsoft.NETFramework.ReferenceAssemblies.net462 reference left over from #1669.

System.Threading.AccessControl stays with NoWarn="NU1510". NuGet flags the package
as likely unnecessary. That heuristic assumes a -windows target framework. These
projects target plain net8.0 and need the package for SemaphoreSecurity.

Verified: 164 warnings and 0 errors, identical to baseline.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Autofac 4.8.0 dates from 2018 and is five majors behind. Octopus Server already
runs 9.3.1 on net10.

Done while Calamari still targets net8. Autofac 9.3.1 resolves on net8. The DI
upgrade is therefore verified against a known-good baseline instead of being
entangled with the framework change.

Notes for reviewers. ContainerBuilder.Update() is the headline Autofac 5 removal
and was never used here. The .Update call sites in this repo belong to LibGit2Sharp.
The API surface in use is mainstream and unchanged across the version range. The
custom RegisterPrioritisedList<T> extension builds on Meta<T> and WithMetadata.
Both APIs are stable.

Verified: clean rebuild with no new warnings. 26 tests pass covering the custom
registration ordering and flavour resolution.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Continues the dependency work on net8 before the target framework changes.

System.Linq.Async goes to 7.0.1. .NET 10 pulls System.Linq.AsyncEnumerable into the
core libraries. 7.0.1 is the version Octopus Server runs against net10.

Serilog collapses a three-way version spread onto 4.0.2. Microsoft.Extensions.Http
and Microsoft.Extensions.Logging go to 10.0.10.

System.Text.Json and System.Text.Encoding.CodePages are deliberately left alone.
Both become framework-provided on net10. Both are still required on net8. Removing
them now would downgrade System.Text.Json to the in-box 8.x version and lose
security fixes.

Verified: 165 warnings and 0 errors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Flips 41 projects to net10.0 and net10.0-windows. Also updates the build scripts and
source/Directory.Build.props. Directory.Build.props compares the target framework as
a string. Calamari.AzureWebApp.NetCoreShim stays on net462. Web Deploy is
.NET Framework only.

OctoVersion.Tool 1.0.50 already ships a net10.0 folder. No tool bump was needed.

System.Text.Json and System.Text.Encoding.CodePages are removed here rather than
earlier. Both raise NU1510 on net10. Both were genuinely required on net8.

.NET 10 audits transitive packages during restore. Auditing surfaced vulnerable
dependencies that net8 never reported. The direct references that pulled them in
were upgraded rather than the audit being suppressed. WireMock.Net goes to 2.13.0.
System.DirectoryServices.AccountManagement goes to 10.0.10. RestSharp is pinned to
112.1.0. Serilog goes to 4.4.0 for Serilog.Extensions.Logging 10.0.0.

Certificate loading moves off the constructors obsoleted by SYSLIB0057. All four
call sites load .pfx files. X509CertificateLoader.LoadPkcs12FromFile and
LoadPkcs12CollectionFromFile are direct equivalents with no behaviour change.

AzureWebAppBehaviour suppresses SYSLIB0014 around its ServicePointManager callback
and carries a TODO. The callback looks like dead code. Web Deploy runs in a separate
net462 child process. A callback registered in the parent cannot affect the child.
The behaviour predates this change. Removing security-adjacent code deserves its own
reviewed commit.

Verified: 171 warnings and 0 errors on net10. build/_build.csproj clean. 24 DI and
structured-variable tests pass against net10.0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
build.sh and build.ps1 hardcode the SDK channel to 8.0 in an Octopus-local
modification to the Nuke bootstrapper. The modification deliberately ignores
global.json. Builds then roll forward to the latest patch automatically. On an
agent without .NET preinstalled the bootstrapper would install an 8.0 SDK and then
fail against a global.json requiring 10.0.302.

Both bootstrappers now use the 10.0 channel.

Verified end to end. ./build.sh --target PublishCalamariProjects --target-runtime
linux-x64 succeeds. The published output is genuinely self-contained on the new
runtime. Calamari.runtimeconfig.json reports net10.0 with Microsoft.NETCore.App
10.0.10 included.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
.NET 10 changed Environment.SetEnvironmentVariable(name, ""). On net8 an empty
value deleted the variable. On net10 it stores an empty string. Verified directly
against 8.0.27 and 10.0.10. Passing null still deletes on both.

The proxy fixtures relied on the old behaviour. Two uses together account for 11 of
the 13 test failures in PR #2091's chain 2026.3.533.

Reset helpers passed string.Empty to mean unset. On net10 that leaves HTTP_PROXY,
HTTPS_PROXY and NO_PROXY set to "". ProxyEnvironmentVariablesGenerator tests for
presence rather than value. The generator therefore took the "proxy variables
already exist" branch and leaked empty values into every later fixture in the run.

ProxySettingsInitializerFixture passed "" for absent credentials and then asserted
BeNull(). On net10 the read returns "".

Reset helpers now pass null. Credential assertions use BeNullOrEmpty(). The assertion states the actual intent
rather than a representation detail. The setter deliberately keeps
"" so the empty-string case is now genuinely covered.

No product change and no customer-visible behaviour change. All 30 uses of the idiom
were in test code. Inherited environment variables behave identically on both
runtimes. Calamari inherits its environment in production.

Verified on net10.0 locally: ProxySettingsInitializerFixture 5/5, ScriptEngineFixture
1/1, SetupKubectlAuthenticationFixture 3/3. The five Bash and DotnetScript proxy
cases need bash, pwsh and the proxy fixture host. Those share the same teardown
mechanism and ScriptProxyFixtureBase got the same fix. CI is the confirmation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CodeGenerator.GenerateConsoleApplication hardcoded a net8-era toolchain. It wrote a
global.json pinning SDK 8.0.10 and ran dotnet new console -f net8.0. The agents
provide only a .NET 10 SDK. SDK 8.0.10 is unsatisfiable because rollForward
latestFeature only rolls in the 8.0.x band. The test failed before it could
exercise anything.

That is the ExecuteManifestCommandFixture.WithInstructions failure on the Linux
netcore-testing configs in PR #2091's chain 2026.3.534. It was the last remaining
Linux test failure after the proxy fixture fix landed.

Bumped to SDK 10.0.302 and net10.0, matching the repository's root global.json.

Verification is partial. The SDK resolution error is gone. dotnet new console -f
net10.0 succeeds and the project is created. The test still fails on an Apple
Silicon dev machine for a pre-existing and unrelated reason. RID selection a few
lines below sets osx-x64 for Mac and then overwrites it with linux-arm64 whenever
ProcessArchitecture is Arm64. That check is not nested under the IsRunningOnNix
branch. The generated app is published for linux-arm64 and exec'd on macOS. CI is
unaffected and is the confirmation for this change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The rebase onto main auto-merged both sides' SharpCompress and
System.Linq.Async entries into the same ItemGroup, producing duplicates
that failed restore with NU1504.

Keeps the newer of each, which preserves both sides' intent:
- SharpCompress 0.49.1 from main, dropping the 0.37.2 entry and its
  NoWarn NU1902 suppression (main re-armed that alerting in SF-1864)
- System.Linq.Async 7.0.1 from this branch's dependency alignment

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
main added System.Text.Json 9.0.16 to Calamari.csproj after this branch
diverged. On net10.0 it is framework-provided, so NuGet prunes it and
NU1510 fails the restore under TreatWarningsAsErrors.

The net10 shared framework ships a newer System.Text.Json than 9.0.16,
so removing the explicit reference keeps the version floor main was
after. Same rationale as "Remove framework-provided package references".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The net10 stack's dependency alignment and the merged #2120 dependency work
both set Serilog in these projects, so replaying one onto the other produced
duplicate PackageReference items and NU1504 at restore. Keep the higher version.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant