Morphir.Compilation
0.4.0-alpha.5
Prefix Reserved
See the version list below for details.
dotnet add package Morphir.Compilation --version 0.4.0-alpha.5
NuGet\Install-Package Morphir.Compilation -Version 0.4.0-alpha.5
<PackageReference Include="Morphir.Compilation" Version="0.4.0-alpha.5" />
<PackageVersion Include="Morphir.Compilation" Version="0.4.0-alpha.5" />
<PackageReference Include="Morphir.Compilation" />
paket add Morphir.Compilation --version 0.4.0-alpha.5
#r "nuget: Morphir.Compilation, 0.4.0-alpha.5"
#:package Morphir.Compilation@0.4.0-alpha.5
#addin nuget:?package=Morphir.Compilation&version=0.4.0-alpha.5&prerelease
#tool nuget:?package=Morphir.Compilation&version=0.4.0-alpha.5&prerelease
<img src="https://github.com/finos/branding/blob/master/project-logos/active-project-logos/Morphir%20Logo/Horizontal/2020_Morphir_Logo_Horizontal.png?raw=true" width="450">
Morphir
Morphir is a library of tools that works to capture business logic as data.
For the first time, business logic can be shared, stored, translated and visualised, all with the reliability of standardisation ensured in the Morphir framework.
What is it?
A set of tools for integrating technologies. Morphir is composed of a library of tools that facilitate the digitisation of business logic into multiple different languages & platforms. The Morphir framework is unique too in that facilities elements of automation and conversion that were previously unavailable in the field of finance-tech.
Why is it important?
Makes business logic portable. Business logic digitised provides distinct advantages: capacity for movement across departments and fields & the ability to be converted to new languages and applications.
How does it work?
Defines a standard format for storing and sharing business logic. A clear set of standards and format is in-place from the input/output, allowing for coherent structure.
What are the benefits?
✔️ Eliminates technical debt risk
Refactoring code libraries is often a harmful and time-sensitive issue for businesses, Morphir ensure the standards introduced from input eliminate delays at deployment.
✔️ Increases agility
Adaptability and usability are key concepts of the Morphir framework, business logic can now move with the code, be easily understood and adopted, in an ever-developing eco-system.
✔️ Ensures correctness
Certifying that specified functions behave as intended from input to output is assured through the Morphir library / tool chain.
✔️ Disseminates information through automation
Morphir’s automated processing helps disseminate information which otherwise may not be understood or shared at all, a useful tool when brining elements of business logic to conversation outside of its immediate audience (i.e developers).
Morphir .NET
Provides tooling for the Morphir ecosystem and .NET libraries that can be used to help you work with Morphir.
The additive shared configuration libraries
provide typed TOML, YAML, JSON/JSON5 documents, pure source resolution, named
profiles, validation and winning origins without constructing the CLI.
Morphir.Configuration.IO reads explicit files or supplied text and exposes
bounded project/workspace discovery through native and in-memory trees. See the
discovery guide for
ProjectSystem.Discover, per-root/member ProjectSystem.Load, membership/selection,
host boundaries and explicitly authorized machine sources.
Existing Git-first artifact discovery, cache-only tooling and CI overlays keep
their compatibility behavior. Shared inspection APIs
and morphir config get/list/show/path/explain expose typed values, source reports,
scoped queries and redacted explanations. The optional
Microsoft adapter projects one
resolved document with explicit/source-generated Options binding. See the
CLI reference for read controls, JSON schema
and exit codes. Shared editing services
and morphir config set/unset provide typed, scoped TOML previews and writes,
source-preserving changes, optimistic conflicts and explicit recovery ownership.
Shared classic project migration
and morphir project migrate provide loss-checked previews and guarded TOML
adoption while retaining classic bytes. Shared workspace migration
adds bounded inventory, verified membership and recoverable batch application through
morphir workspace migrate [directory]. See the
reader profile for syntax and provenance.
SDK libraries
Morphir.SDK.CSharp is an independent net11.0 and net10.0 runtime package with immutable
C#/.NET public types. See the C# SDK guide
for its bounded operation contract and package usage.
The in-memory C# generator lowers typed classic/v4 libraries with explicit numeric C# 14/15 profiles, immutable values, executable functions/patterns/record operations and public SDK bindings. Original business fixtures and the bounded modern package-host/trimming checks are verified under #474. Implementation #478 landed. The main specification and archived acceptance evidence landed in archive follow-up #480. Coordinator cleanup remains pending.
Generate C# through the existing CLI, with numeric profiles and a validated optional namespace:
morphir generate --input model.json --target csharp --output generated --option language-version=14 --option namespace=Example.Generated --json
The C# walkthrough builds and runs a fresh consumer against the immutable runtime. CLI implementation #482 landed; its main specification and archived evidence are synchronized in this follow-up. Archive PR landing and root-verified cleanup remain pending.
Getting Started
Building from source
The repository builds with the .NET 11 SDK and C# 15. Applications, .NET tools and tests target net11.0.
Packable libraries target net11.0 and net10.0 (.NET 10 LTS), so .NET 10 applications can use them.
.NET 8 and .NET 9 reached the end of support on 2026-11-10, so .NET 10 is the lowest target.
MSBuild tasks and Myriad plugins target net10.0, so the .NET 10 SDK can load them.
The netstandard2.0 attribute packages keep that target. The properties
MorphirCurrentTargetFramework, MorphirLibraryTargetFrameworks and MorphirHostedTargetFramework in
Directory.Build.props set these targets.
Install the SDK pinned in global.json, or use mise install to install the tools in
mise.toml. CI uses the same SDK pin.
VS Code users can run Dev Containers: Reopen in Container to use the
Microsoft .NET SDK image in .devcontainer/Dockerfile.
The SDK includes the C# and F# compilers. Renovate groups the container, global.json,
and mise SDK updates together and keeps them on the .NET 11 SDK. Moving to the next SDK
major version requires an explicit baseline update.
./build.sh --target Test
./build.sh --target PackAll
./build.sh --target TestBuild
To compile classic Morphir model JSON into F#, see the Myriad getting-started article, plugin reference, and car rental tutorial. The runnable example covers inventory, reservation, pickup, return and billing with one generated F# file per module.
Quick Start
Install Morphir using one of the following methods:
Using proto (Recommended for multi-language toolchains):
# Install proto (if not already installed)
curl -fsSL https://moonrepo.dev/install/proto.sh | bash # Linux/macOS
# or: irm https://moonrepo.dev/install/proto.ps1 | iex # Windows
# Add Morphir plugin and install
proto plugin add morphir "source:https://github.com/finos/morphir-dotnet/releases/latest/download/morphir_plugin.wasm"
proto install morphir
Using Platform-Specific Install Scripts:
# Linux
curl -fsSL https://raw.githubusercontent.com/finos/morphir-dotnet/main/scripts/install-linux.sh | bash
# macOS
curl -fsSL https://raw.githubusercontent.com/finos/morphir-dotnet/main/scripts/install-macos.sh | bash
# Windows (PowerShell)
irm https://raw.githubusercontent.com/finos/morphir-dotnet/main/scripts/install-windows.ps1 | iex
Using .NET Tool (requires .NET SDK):
dotnet tool install --global Morphir.Tool --prerelease
Verify Installation:
morphir --version # native executable
dotnet-morphir --version # .NET tool
Compile F# to Morphir IR: follow the five-minute Compile F# guide.
For detailed installation instructions and troubleshooting, see the Installation Guide.
Documentation
Building
This project uses Nuke for build orchestration, providing a strongly-typed, cross-platform build system written in C#.
Requirements
- .NET SDK 11.0.100 or a later patch in the same feature band, as specified in
global.json
Basic Commands
# First-time setup: Restore tools and dependencies
dotnet tool restore
./build.sh --target Restore
# Build the solution (default target)
./build.sh
# Run tests
./build.sh --target Test
# Run linting/formatting checks
./build.sh --target Lint
# Format code
./build.sh --target Format
# Run full CI pipeline (restore, build, test, lint)
./build.sh --target CI
# Show all available targets
./build.sh --help
Windows users: Use build.cmd or build.ps1 instead of ./build.sh
Configuration
You can set the build configuration using the --configuration parameter:
# Build in Debug mode
./build.sh --configuration Debug
# Run tests in Debug mode
./build.sh --target Test --configuration Debug
By default, commands use Release configuration.
Build system
The build runs on Fallout 10.5.0, the successor to Nuke. ./build.sh, build.ps1, and build.cmd are the entry points. Target names are unchanged from the earlier Just-to-Nuke move, which is recorded in NUKE_MIGRATION.md.
Developing
The shared .NET SDK contract freezes the bounded operation signatures, scalar limits and independent expectations used by both SDK deliveries. See the fixture guide for the build command, schema and provenance. After building its test project, run the offline validator with:
dotnet exec tests/Morphir.SDK.Conformance.Tests/bin/Debug/net11.0/Morphir.SDK.Conformance.Tests.dll --maximum-parallel-tests 2
F# runtime consumers use Morphir.SDK.FSharp. Its namespace and runtime assembly
remain Morphir.SDK, so existing generated source and runtime type identities
remain valid. The legacy Morphir.SDK package depends on the canonical package
and ships no runtime assembly. See the F# runtime migration guide
for dependency selection and covered behavior corrections.
Project Structure
morphir-dotnet/
├── src/
│ ├── Morphir.Core/ # Core IR types and utilities
│ ├── Morphir.Tooling/ # Tooling infrastructure
│ └── Morphir/ # CLI application
├── tests/
│ ├── Morphir.Core.Tests/ # Unit tests for Core
│ ├── Morphir.Tooling.Tests/ # Unit tests for Tooling
│ └── Morphir.BlackBox.Tests/ # Black-box tests of the built artifacts (BDD/Gherkin)
├── build/
│ ├── _build.csproj # Nuke build project
│ └── Build.cs # Build orchestration (strongly-typed)
├── scripts/ # Build and utility scripts (C# scripts)
├── build.sh/cmd/ps1 # Nuke bootstrap scripts
└── justfile # Legacy build commands (preserved for reference)
Development Workflow
Restore dependencies:
./build.sh --target RestoreBuild the solution:
./build.sh --target CompileRun tests:
./build.sh --target TestCheck code formatting:
./build.sh --target LintFormat code (if needed):
# Unix/macOS ./build.sh --target Format # Windows build.cmd --target Format # or ./build.ps1 --target Format
Scripts
The project uses C# scripts (.cs files) for build automation, leveraging the direct C# file execution of the .NET SDK. These scripts are located in the scripts/ directory:
run-tests.cs- Runs unit testsrun-e2e-tests.cs- Runs end-to-end testsgenerate-wolverine-code.cs- Generates Wolverine code
Note: The deprecated build-tool-dll.cs, pack-tool-platform.cs, publish-single-file.cs and publish-single-file-untrimmed.cs scripts have been removed. Use the Nuke build targets (PackTool, PublishTool, PackRelease, PublishSingleFile, PublishSingleFileUntrimmed) instead.
Build Commands
All Nuke build commands support both Unix/macOS (./build.sh) and Windows (build.cmd or ./build.ps1) bootstrap scripts. Examples below show both formats.
Building Libraries
# Unix/macOS: Pack library projects as NuGet packages
./build.sh --target PackLibs
# Windows
build.cmd --target PackLibs
# or
./build.ps1 --target PackLibs
# Pack all projects (libraries and tool)
./build.sh --target PackAll # Unix/macOS
build.cmd --target PackAll # Windows
# Specify configuration and version
./build.sh --target PackLibs --configuration Debug --version 1.2.3
build.cmd --target PackLibs --configuration Debug --version 1.2.3
# Custom output directory
./build.sh --target PackLibs --output-dir ./my-packages
build.cmd --target PackLibs --output-dir ./my-packages
Building Release Profiles
The four .NET deployment offerings each have a named release profile. PackRelease publishes one profile for one platform and packs it into an archive named morphir-dotnet-<release>-<deployment>-<rid>.<zip|tar.gz> (zip on Windows, tar.gz elsewhere, files at the archive root), or for dotnet-tool the Morphir.Tool.<release>.nupkg package. The release comes from CHANGELOG.md. Output goes to artifacts/release/; the publish folders are in artifacts/release/publish/<deployment>/<rid>/.
# Native AOT morphir (src/Morphir). Build on the operating system of the RID.
./build.sh --target PackRelease --deployment native-aot --rid linux-x64
build.cmd --target PackRelease --deployment native-aot --rid win-arm64
# Managed morphir with the in-process F# frontend (src/Morphir.Managed), untrimmed folder archives
./build.sh --target PackRelease --deployment self-contained --rid osx-arm64
./build.sh --target PackRelease --deployment framework-dependent --rid linux-arm64
# The Morphir.Tool package (dotnet-morphir). Portable, so no --rid.
./build.sh --target PackRelease --deployment dotnet-tool
# Publish only, without the archive
./build.sh --target PublishRelease --deployment self-contained --rid win-x64
Platforms: win-x64, win-arm64, osx-x64, osx-arm64, linux-x64, linux-arm64. Any other RID is refused. Beside each archive, <archive>.build-launch.json records its launch path, execution mode, layout (single-file, folder or tool-package), bundled runtime, required runtime or SDK, F# frontend, prerequisites and SHA-256. The operating-system prerequisites are not declared yet; qualifying the artifact on each platform establishes them. It is a build output, not a public contract. PackRelease fails when the publish folder contradicts it, for example a framework-dependent folder that carries coreclr. Building a profile does not qualify a platform: no platform is release-qualified yet. Checking the release profiles from packaged artifacts runs the E2E suite against the packaged archives and lists what has been run on each platform; nothing is qualified by it. PackRelease also writes <archive>.build-contents.json and artifacts/release/size-report.md, which list every file each artifact contains with its size.
The same publish folder always gives the same archive bytes. A publish for one RID writes its NuGet lock files under obj/publish-lock/<rid>/, so it leaves the committed packages.lock.json files alone. To check, run git status --short before and after PackRelease: it should list nothing new outside artifacts/, which is ignored.
On Windows, the Native AOT link needs the Visual Studio C++ build tools for the target architecture, and the ILCompiler targets call vswhere.exe. If the link fails with "'vswhere.exe' is not recognized", put %ProgramFiles(x86)%\Microsoft Visual Studio\Installer on PATH in that shell.
Building Executables
These targets keep the layouts that the workflows and E2E tests use. PublishSingleFile builds the legacy release asset that releases ship as morphir-<rid>[.exe] and morphir-<rid>-v<version>.tar.gz.
# Build a trimmed single-file executable for a specific platform
./build.sh --target PublishSingleFile --rid linux-x64 # Unix/macOS
build.cmd --target PublishSingleFile --rid linux-x64 # Windows
# Build an AOT (ahead-of-time compiled) executable
./build.sh --target PublishExecutable --rid linux-x64
build.cmd --target PublishExecutable --rid linux-x64
# Build an untrimmed executable (larger, better for debugging)
./build.sh --target PublishSingleFileUntrimmed --rid win-x64
build.cmd --target PublishSingleFileUntrimmed --rid win-x64
# Common RIDs: linux-x64, linux-arm64, win-x64, osx-x64, osx-arm64
# With custom configuration and output directory
./build.sh --target PublishSingleFile --rid linux-x64 --configuration Debug --single-file-dir ./bin
build.cmd --target PublishSingleFile --rid linux-x64 --configuration Debug --single-file-dir ./bin
Building the Dotnet Tool
# Pack the Morphir CLI as a dotnet tool
./build.sh --target PackTool # Unix/macOS
build.cmd --target PackTool # Windows
# With version and output directory
./build.sh --target PackTool --version 2.0.0 --output-dir ./packages
build.cmd --target PackTool --version 2.0.0 --output-dir ./packages
Testing
# Run unit tests
./build.sh --target Test # Unix/macOS
build.cmd --target Test # Windows
# Run tests in Debug configuration
./build.sh --target Test --configuration Debug
build.cmd --target Test --configuration Debug
# Build the black-box test project for the published targets
./build.sh --target BuildE2ETests
build.cmd --target BuildE2ETests
# Run end-to-end tests
./build.sh --target TestE2E # Unix/macOS
build.cmd --target TestE2E # Windows
# Run the black-box tests on one target (a value of MORPHIR_TEST_TARGET, or tool, all or offerings)
./build.sh --target TestE2E --executable-type trimmed
build.cmd --target TestE2E --executable-type trimmed
Publishing to NuGet
# Publish library packages to NuGet.org
./build.sh --target PublishLibs --api-key YOUR_API_KEY # Unix/macOS
build.cmd --target PublishLibs --api-key YOUR_API_KEY # Windows
# Publish the Morphir CLI tool package to NuGet.org
./build.sh --target PublishTool --api-key YOUR_API_KEY
build.cmd --target PublishTool --api-key YOUR_API_KEY
# Publish all packages
./build.sh --target PublishAll --api-key YOUR_API_KEY
build.cmd --target PublishAll --api-key YOUR_API_KEY
# Publish to custom NuGet source
./build.sh --target PublishLibs --nuget-source https://custom-feed.com --api-key YOUR_KEY
build.cmd --target PublishLibs --nuget-source https://custom-feed.com --api-key YOUR_KEY
# Publish to local NuGet feed (for testing)
./build.sh --target PublishLocalLibs # Unix/macOS
build.cmd --target PublishLocalLibs # Windows
# With custom local source
./build.sh --target PublishLocalLibs --local-source ./my-local-feed
build.cmd --target PublishLocalLibs --local-source ./my-local-feed
# Install tool locally from package
./build.sh --target PublishLocalTool
build.cmd --target PublishLocalTool
# Install globally
./build.sh --target PublishLocalTool --global true
build.cmd --target PublishLocalTool --global true
Available Nuke Targets
Run ./build.sh --help (Unix/macOS) or build.cmd --help (Windows) to see all available targets with descriptions and parameters.
Key targets:
Restore- Restore .NET dependenciesCompile- Build the solution (default target)Test- Run unit testsLint- Check code formattingFormat- Format codeCI- Run full CI pipeline (restore, build, test, lint)PackLibs- Pack library projects as NuGet packagesPackTool- Pack the Morphir CLI as a dotnet toolPackAll- Pack all projects (libraries and tool)PublishRelease- Publish one release profile (requires--deployment, and--ridexcept fordotnet-tool)PackRelease- Pack one release profile into its named archive and launch metadataPublishSingleFile- Publish the legacy trimmed single-file release asset (requires--rid)PublishExecutable- Publish AOT executable (requires--rid)PublishSingleFileUntrimmed- Publish untrimmed single-file executable (requires--rid)TestE2E- Run each group of the black-box tests against a published or packaged targetPublishLibs- Publish library packages to NuGetPublishTool- Publish tool package to NuGetPublishAll- Publish all packages to NuGetPublishLocalLibs- Publish libraries to local feedPublishLocalTool- Install tool locally from packagePublishLocalAll- Publish all to local feed
Contributing
We welcome contributions! Please see our Contributing Guide for details.
License
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
Links
SDK runtime delivery and archival evidence is recorded in the runtime delivery record. The canonical C# runtime spec, F# package spec, and stable C# API handoff describe the landed runtimes. SDK archive #472 landed; source SDK worktrees and their owned artifacts were removed. Empty shared archive directory cleanup remains pending with the recorded permission/policy limitations.
OpenSpec change proposals
The extension installation library documents the implemented distribution parsers, platform mappings and MEP eligibility checks, along with the remaining installer work tracked by #558. Extension installation commands are not implemented yet.
The Morphir Compatibility Kit seed contains portable F# frontend cases, a closed step vocabulary and the deterministic library runner. Its reader/runner tests and Native AOT smoke check are independent of the production F# frontend, which is still being implemented.
This repository uses the shared html-design OpenSpec workflow. New proposals
produce capability specs, a standalone design.html, and implementation tasks.
See the OpenSpec guide for setup, verification and updates.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. net11.0 is compatible. |
-
net10.0
- FSharp.Core (>= 10.1.403)
- Morphir.Models (>= 0.4.0-alpha.5)
-
net11.0
- FSharp.Core (>= 10.1.403)
- Morphir.Models (>= 0.4.0-alpha.5)
NuGet packages (3)
Showing the top 3 NuGet packages that depend on Morphir.Compilation:
| Package | Downloads |
|---|---|
|
Morphir.Tooling
Package Description |
|
|
Morphir.Extensions
Morphir extension registry domain: parsed extension identities, capability claims and the extension catalog |
|
|
Morphir.Compilation.Mep
The MEP 0.2.0-draft.1 host side of guest processes: the guest process and session, and the frontend adapter, an IFrontend over a guest that serves morphir.frontend.compileModel. Trim-safe and AOT-safe. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.5.0-alpha.3 | 56 | 11/14/2026 |
| 0.5.0-alpha.2 | 54 | 11/13/2026 |
| 0.5.0-alpha.1 | 51 | 11/13/2026 |
| 0.4.0-alpha.5 | 56 | 11/13/2026 |
| 0.4.0-alpha.4 | 63 | 11/10/2026 |
| 0.4.0-alpha.3 | 66 | 11/9/2026 |
### Added
- Releases attach the Windows toolchain for `win-arm64` as well as `win-x64`: the self-contained host, the F# frontend and backend archives, the candidate, the extension selection and the bundle, with one `SHA256SUMS` over both platforms' files, so `morphirup-win-arm64.exe setup` installs from the release. Each Windows toolchain is built once, by the same job as its manager, after `CheckBuildMachine`; the separate win-x64 toolchain job is gone. Pull requests build and check the `win-arm64` toolchain in `morphirup.yml` and keep it downloadable for seven days.
- `deployment.yml` has a `dry-run` input for a manual run: it builds every platform, assembles the release assets and checksum files, uploads them as the `dry-run-release-assets` artifact, and publishes nothing to NuGet or GitHub releases.
- Windows ARM64 (`win-arm64`) is a released platform. The release workflow builds `morphir-win-arm64.exe`, `morphir-win-arm64-v<version>.tar.gz`, `morphirup-win-arm64.exe` and `morphirup-win-arm64-v<version>.tar.gz` on the hosted `windows-11-arm` runner, natively, and the morphirup checksum file lists the morphirup ones; the pull request workflow `morphirup.yml` builds and checks `morphirup` there too. The names, contents and checks of the other platforms' assets are unchanged. Earlier releases have none of these files ([#611](https://github.com/finos/morphir-dotnet/pull/611)).
- Experimental Windows release setup with `morphirup`: in a project, `morphirup setup --yes` downloads the runtime-bundled Morphir host and the independent F# frontend and backend from the exact `finos/morphir-dotnet` release of the manager's own version (or the exact version `.morphir/toolchain.toml` requests), checks each file's byte count and SHA-256 against the release's `SHA256SUMS`, installs the complete folders into the content-addressed store, runs the fixed readiness workflow and selects the completed generation last. `morphirup run -- compile` and `morphirup run -- generate` then use the installed folders with no network access. `setup --dry-run` creates nothing. This is release-checksum delivery, not publisher-authenticated: HTTPS and a matching checksum do not authenticate the publisher, and trust commands are not included. The release transport starts at the exact repository and version tag and follows at most three HTTPS redirects to `github.com`, `release-assets.githubusercontent.com` or `objects.githubusercontent.com`. The release attaches the setup bundle for Windows x64 and Windows ARM64. `Morphir.Extensions.Installation` is a new library package ([#608](https://github.com/finos/morphir-dotnet/pull/608)).
- Build: `CheckBuildMachine --rid <rid>` fails unless the machine's operating system and architecture are the RID's and the .NET SDK runs natively (not x64 under emulation on ARM64), and logs the machine. The CLI release jobs and `cli-offering.yml` run it before they build.
- Build: `FlattenReleaseAssets` copies each downloaded platform output into the release assets once, accepting byte-identical duplicates and failing on two different files under one name instead of overwriting; the morphirup checksum jobs use it. `WindowsToolchainChecksums` writes one `SHA256SUMS` over both Windows toolchain outputs, and writes nothing when a Windows RID's bundle or manager is missing, or an asset its bundle names is missing or differs.
- `scripts/install-windows.ps1` and the proto plugin choose `win-arm64` on Windows ARM64, where they refused before. The Windows installer now reads the machine's architecture, so an x64 PowerShell under emulation on ARM64 also chooses `win-arm64`. Releases before this one have no `win-arm64` file, so asking either for an earlier version fails on Windows ARM64.
- Experimental configured Python WASM commands: `morphir compile --language python` and `morphir generate --target python` use `extensions.python.path` through the existing MEP model adapters and owned WASM sessions. Includes a pinned Python 0.6.0 walkthrough; Windows x64 retains its named runtime refusal ([#610](https://github.com/finos/morphir-dotnet/pull/610)).
- Experimental WASM host-kit reports adopt the pinned parent report schema, computed ordered verdict reasons, per-profile guest-kit citations and eight named refusals. All 22 fixed policy controls are checked independently; the runner retains observation recomputation and exact report rebuilding. Citation-bearing runs fail closed with an explicit unavailable-checker result until the actual generic-kit report checker is integrated. This does not qualify a host, guest, artifact or platform ([#564](https://github.com/finos/morphir-dotnet/issues/564)).
- Internal current-family standalone and embedded claim reader, retaining supplied JSON beside existing MEP values and an understood-member projection, with recursive metadata checks, critical paths and host comparators reused from Toolchain. Claims 27/27 and affected SharedReaders 19/19 pass on Windows, repeated after pinned formatting. Public codecs, full corpus conformance, probes and installation lifecycle remain unfinished ([#558](https://github.com/finos/morphir-dotnet/issues/558)).
- Internal extension-family reader increment with pinned, offline hash-checked schemas and case data from parent `0439c4c0`. The focused Windows SharedReaders suite passes 19/19, including six parent release/schema cases and two I-JSON refusal cases with valid parse controls. Full claim/package semantics, state codecs, inventory, probes and command lifecycle remain unfinished; the 137 identity and 66 package cases have not all been executed ([#558](https://github.com/finos/morphir-dotnet/issues/558)).
- Extension installer security corrections: descriptor-bound single-file publication, interprocess run-directory ownership coordination, directory/owner identity checks, and gzip metadata/padding budgets with cancellation below the tar reader. Windows regression evidence is 131 passed with three Unix-only cases ignored. Independent security/interop re-review of `b6936ba5` found no concrete remaining finding under the approved cooperating Home boundary; Unix runs and AOT remain pending. Recorded the approved selection, command, provenance and explicit developer-consent decisions; inherited default staged `auto` grants no automatic install consent. Shared readers use parent source `0439c4c0`; portable pin/overlay formats remain parent toolchain work ([#558](https://github.com/finos/morphir-dotnet/issues/558)).
- Extension installation library foundations: portable distribution identifiers over the existing domain types, RID/descriptor/index mappings and exact implemented MEP eligibility within the supported 0.2 line. Includes an Expecto suite and C# probe; CLI installation and Native AOT qualification remain unfinished ([#558](https://github.com/finos/morphir-dotnet/issues/558)).
- Extension installation I/O: a single-file content-addressed store that hashes while copying, rehashes reuse and refuses corrupt objects without replacement; ZIP and tar-gzip inspection with the recorded path, type and resource limits; and exclusive ownership and stale cleanup of .NET run directories. Windows tests and the C# JIT probe cover these APIs. Package formats, extraction, inventory, activation and CLI installation are unfinished; Unix filesystem runs and Native AOT qualification remain pending ([#558](https://github.com/finos/morphir-dotnet/issues/558)).
- `morphir mck <args>` runs the separately installed Morphir Compatibility Kit, `morphir-mck <args>`, a development tool from finos/morphir, in every entry point (`morphir`, the managed `morphir`, `dotnet-morphir`). The arguments, working directory, environment and standard streams are passed on unchanged, and the exit code is the tool's. `MORPHIR_MCK` names the tool's absolute path; otherwise only absolute `PATH` folders are searched, never the current directory, and on Windows only `morphir-mck.exe` is a candidate. With no tool, the CLI writes a diagnostic to stderr and exits with 127. On Windows, killing `morphir` also stops the tool and its children (a kill-on-close job object); on Linux and macOS, SIGTERM or SIGHUP stops the tool's tree. `mck` is the only command routed to another executable, and the CLI includes no kit of its own ([#589](https://github.com/finos/morphir-dotnet/issues/589))
- `morphir extension list --json` and `dotnet-morphir extension list --json` say which offering is running and what it needs from the machine: `deployment` (`native-aot`, `self-contained`, `framework-dependent` or `dotnet-tool`), set at build time by the named release profile and never inferred from how the process runs; `execution` (`native-aot` or `jit`); `platform` (the runtime identifier, left out for the portable tool package); and `prerequisites` (`runtime`, `sdk` with its `phase`, and `native`). A build that no named profile produced, such as `dotnet run`, a test host or the legacy `morphir-<rid>` single-file assets, leaves out `deployment`, `platform` and `prerequisites`. Each extension entry gains `deployments`, `prerequisites` and, when it is not available, a `reason`. The `host` member keeps its meaning as the command name ([#558](https://github.com/finos/morphir-dotnet/issues/558))
- The built-in F# frontend is listed as `morphir-dotnet.fsharp`, available in the self-contained and framework-dependent `morphir` and in `dotnet-morphir`. The Native AOT `morphir` lists it as not available, with the reason; the registry and `compile` take the claim from the same declaration ([#558](https://github.com/finos/morphir-dotnet/issues/558))
- `Morphir.Toolchain`: the recipe binding checks of the toolset recipe page, as pure functions over a recipe, the authenticated targets and their bytes. `RecipeBindings.check` runs every host row (a `tool-release` offering's v1 release descriptor is authenticated, not revoked, served with its length and digest and readable, and lists each bound artifact for its platform with matching `tool-artifact` metadata) and every provider row (the extension release descriptor is bound, served, readable and names the provider's identity; each artifact or package is bound and listed with the delivery's runtime, digest and platform; and the claims name the capability kind, MEP version, language or target and model tuple), each with the row's code and path. It reads tool release metadata v1 release descriptors (`ToolReleaseDocument`), extension release descriptors at `2.0.0-draft.2` (`ExtensionReleaseDocument`) and capability claim sets at `0.1.0-draft.2` (`ClaimSetDocument`), and nothing earlier. `ToolResolver.resolve` selects a tool release by the v1 channel, CLI compatibility and platform rules. No command uses them, nothing is downloaded, and none of it has had a security review ([#558](https://github.com/finos/morphir-dotnet/issues/558))
- `morphirup setup --dry-run`: the same request as `setup --plan`, with the same plan, `planSha256`, result, refusals and exit codes, and nothing created, changed or removed. `--plan` is unchanged; both may be given; neither permits `--yes`. Every later `morphirup` command that creates, updates, repairs or removes persistent state will have `--dry-run` on its own planning and validation path ([#558](https://github.com/finos/morphir-dotnet/issues/558))
- `Morphir.Toolchain`: `Comparator.allMet`, which says whether an installed version meets a list of comparators by the Rust `semver` and npm rule (a prerelease matches only when a comparator names its own major, minor and patch with a prerelease; an empty list is always met), and `Discovery.requestIn` with `Discovery.PathCase`, which compares names without case on Windows, and `Discovery.within`. The suite also runs the contract's `nfc`, `setup-platforms`, `prerequisite-versions` and `version` binding cases and its Windows discovery case ([#558](https://github.com/finos/morphir-dotnet/issues/558))
- `Morphir.Toolchain`: the verification part of a TUF client for the tool release metadata v1 profile, as pure functions over supplied bytes and one trusted start time: strict readers for root, timestamp, snapshot and targets metadata, Ed25519 threshold verification with BouncyCastle, root rotation with both thresholds and a retry after 32 roots, timestamp, snapshot and targets checks with rollback, link and expiry, and typed failures mapped to the stable codes. A refresh returns a `RefreshResult` whose every outcome (`Refreshed`, `NothingNew` for a timestamp at the trusted version, `RootRotationPaused` and `Failed`) carries the newest trust state accepted, so a root accepted before a failure is kept. No exception leaves the client: a defect is a `Failed` with `TufFailure.Internal`. A trust anchor carries its purpose, and a `Production` anchor must be the repository identity the caller names; every root accepted later meets the same rules. Text inside `signed` that is not NFC, a member differing from a known member only in letter case, a prerelease `spec_version` and an integer above 253 - 1 are refused. It does no I/O and no command uses it. It has not had a security review and must not be used to authenticate a production release until it has. Where the profile is silent it takes the stricter reading, marked provisional ([#558](https://github.com/finos/morphir-dotnet/issues/558))
- `morphir generate --extension` and the configuration key `[codegen.<target>].extension` select a MEP backend extension (a command name, a path, or a `.dll`, as for `compile --extension`), for example `morphir-fsharp-backend`. The option wins over the key; without either, the built-in backend runs as before. Every entry point has the option. The generate report names the extension by its own id and adds `backendTransport` (`mep`) and `backendVersion`. Failures have the codes `extension.unavailable`, `extension.protocol_error`, `extension.timeout`, `extension.tuple_unavailable`, `extension.not_admitted`, `extension.unsafe_artifact` and `invalid-extension`. An extension cannot be selected by an installed extension id yet
- `MepGenerationBackend` in `Morphir.Tooling`: an `IGenerationBackend` over a MEP guest's `morphir.backend.generateModel`. It sends the model's JSON IR (IR `4.0.0` or `3.0.0`) only within the limits that the session agreed (`MepBackendOptions.OfferLimits`), accepts at most 10,000 artifacts in one result, and checks every returned artifact path before publication, including Windows-equivalent names, reserved device names and file/directory conflicts. `MepBackendOptions.Environment` sets the guest's whole environment. A guest that gives the id of a built-in backend is refused
- `MepGuestProcess` in `Morphir.Compilation.Mep`: the guest-process lifecycle (start, initialize, describe, request with a deadline, shutdown or kill of the process tree) that `MepFrontend` and `MepGenerationBackend` share. `MepGuestOptions.Environment`, and an additive `MepFrontend.ConnectAsync` overload, set the guest's whole environment
- `Morphir.CodeGeneration.FSharp`, for C# callers: the public union `FSharpFormatting` (`Applied`, `Off`, `Unavailable`, with `WireValue`), which `Transpiler.formatting` now records; `FSharpGeneration.TryGetFormatting(tree, out formatting)` and `FSharpGeneration.WithRequiredSemantics(context, semantics)`; and `GenerationOptions.WithOptimize` and `WithFormat`. The project now sets nullable metadata, warnings as errors, and the trim and AOT properties
- `morphir generate --target fsharp --option format=false`: the F# backend option `format` (Boolean, `true` by default, beside `optimize`) turns Fantomas formatting off. The files are the generator's own text, with LF line endings only and exactly one final LF, the same bytes on every operating system, under JIT and Native AOT, and in every culture. The external backend `morphir-fsharp-backend` accepts the same option through the `generateModel` options. `GenerationOptions.Format` and `Transpiler.formatting` carry it in `Morphir.CodeGeneration.FSharp`
- The generate report member `formatting`: `applied`, `off` or `unavailable`, written for a built-in backend only: an external backend (`backendTransport` `mep`) has no `formatting` member, even with `format=false`, because a MEP generate result does not say whether the guest formatted. `compile --target` writes the same member in its `generation` report member. Under the default, a host that cannot run the formatter (the Native AOT `morphir`) writes a warning on stderr from `generate` and from `compile --target`, never on stdout or in the JSON output; its bytes are unchanged
### Changed
- Build: the repository builds with the .NET 11 SDK (`11.0.100`) and C# 15. `morphir`, `dotnet-morphir`, `morphirup`, the F# extension tools and the tests target `net11.0`, so the framework-dependent `morphir` and `dotnet-morphir` need `Microsoft.NETCore.App` 11.x, and installing `dotnet-morphir` needs a .NET SDK 11.0.100 or later. The library packages target `net11.0` and `net10.0`, so .NET 10 applications can still use them. .NET 8 and .NET 9 reached the end of support on 2026-11-10, so .NET 10 is the lowest target. The MSBuild tasks and Myriad plugins in `Morphir.Myriad` and `Morphir.Myriad.SystemTextJson` stay on `net10.0`, so the .NET 10 SDK can load them. Their MSBuild reference packages return to the 18.0 line, because 18.1 and later have no `net10.0` assets. `Microsoft.Extensions.*` and `System.Text.Json` move to 11.0.0, which also supports `net10.0`. The target frameworks are set in one place, `Directory.Build.props`
- Build: the Native AOT publish of `morphir` treats IL3050 as an error. The F# formatter (`Emit.formatCore`, Fantomas) is `[RequiresDynamicCode]` and is called only behind `RuntimeFeature.IsDynamicCodeSupported`, so if that guard goes, ILC refuses the publish instead of putting Fantomas (about 20 MB) into an image that cannot run it
- Release process: finos/morphir now builds, qualifies and publishes the official Morphir CLI from a pinned commit of this repository. The deployment workflow here publishes the library packages and the F# extension tools, and builds and publishes the CLI (the `morphir-<rid>` executables, the `morphirup-<rid>` assets and the `Morphir.Tool` package) only when the repository variable `MORPHIR_DOTNET_CLI_PUBLICATION` is `enabled`, as a rollback. The reusable workflow `cli-offering.yml` builds one of the four offerings for one platform at a given commit with `PackRelease`, starts the produced executable (or the installed `dotnet-morphir`) with `--version`, and uploads the file, its launch metadata and its SHA-256. `PublishTool` is now `PublishCliTool` plus `PublishExtensionTools`, and `PublishTool` and `PublishAll` publish what they did. See `docs/contributing/cli-build-interface.md` ([#589](https://github.com/finos/morphir-dotnet/issues/589))
- In the Native AOT `morphir`, the hint of `compile.frontend_unavailable` for F# now names the offerings that have a built-in F# frontend and the process delivery `morphir-fsharp-frontend` ([#558](https://github.com/finos/morphir-dotnet/issues/558))
- `Morphir.Toolchain` and `morphirup` follow the finos/morphir toolchain contract at `9e1f853b7` (the draft finos/morphir#1147), re-pinned from `1451c081f`. The same input now gets another code or path:
- Documents that were read before are now refused with `inconsistent_document`: a built-in with `protocolVersion` or `modelTuple` in a recipe, lock or receipt, at that member; an offering that lists a MEP version or a WASM ABI twice, at the repetition; a plan whose host source is `recipe-digest` or whose provider source is `repository-target`, at its `source`; and a diagnostic with `document` or `path` but not both, at the one present
- `morphirup` results: an `unsupported-artifact` diagnostic from `setup --plan` names the request member it concerns with `document` `request` and `path`, such as `toolset.providers[1]`, and a `repository_mismatch` diagnostic names the recipe member (`setup --plan`) or lock member (`doctor`) that names another repository. A recipe, lock or request that `0.4.0-alpha.4` accepted and that is now refused turns a `planned` or `succeeded` result (exit 0) into a `failed` one (exit 1); for example, a recipe that lists a MEP version twice in one offering. Exit code 2 (the command was not run) and exit code 3 (an internal error) mean what they did, and the plan digest of any input both read is unchanged. The version bound (256 characters, 15 digits per numeric identifier) is the contract's rule and no longer provisional
- `Morphir.Toolchain` API, for F# callers: `DiagnosticDetail.UnsupportedArtifact` and `DiagnosticDetail.TrustFailure` have a last field `concerns: DocumentMember option`; `BindingFailure.RepositoryMismatch` carries the document whose member its path names; `Diagnostics.ofTargetRefusal` takes that document; and `RecipeTargets.checkLockedGeneration` takes the generation's recipe as a function `unit -> Result<Recipe, TargetRefusal>`, asked for only for the host release row (a refusal it returns is the check's, and an exception it throws is `invalid_document` at `recipe.target`), and refuses a generation whose recipe target the trust state does not list with `target_not_authenticated` at `recipe.target`, before reading the recipe ([#558](https://github.com/finos/morphir-dotnet/issues/558))
- `morphir generate --input` now loads the project configuration (from `--project`, `--config` or discovery) to read `[codegen.<target>].extension` when `--extension` is absent. With `--project` or `--config`, a configuration that fails to load fails the command with exit code 2. With neither, a discovered configuration that does not load is a warning on stderr and the built-in backend runs, as before
- `compile --extension`, from the shared guest-process lifecycle: a user cancellation (Ctrl+C) while the guest initializes is now a cancelled compile (exit code 130) instead of `extension.unavailable`, and a guest that honors `$/cancelRequest` at the deadline now gives `extension.timeout` instead of `extension.protocol_error`
- `compile --target` does not run a backend extension: it runs the built-in backend of the target, as before. When the project configuration sets `[codegen.<target>].extension` for that target, it now prints a note on stderr saying so
- `GenerationOptions` (`Morphir.CodeGeneration.FSharp`) has the new field `Format`, so a record expression that sets every field needs it, and C# that calls its two-argument constructor must pass both; `{ GenerationOptions.Default with Optimize = ... }` and `GenerationOptions.Default.WithOptimize(...)` keep working
### Fixed
- `morphirup setup`: a `setup.readiness_failed` refusal now includes the host's own diagnostic code and message; a completed attempt removes its staging folders under Home, on success and on refusal; `run -- generate --target csharp --output fsharp` keeps the built-in C# backend, because installed F# providers are chosen from the `--target` and `--language` values only; and on Windows, dry run and apply refuse before creating Home when an installed host or provider executable path would reach 260 characters, naming Home, the executable and the 259-character limit ([#616](https://github.com/finos/morphir-dotnet/pull/616)).
- WASM execution now refuses Windows x64 with `win_x64_trap_unsupported` before loading the engine or a module. Other CLI capabilities remain available; the CET/Spectre-preserving fix is tracked in #609. The diagnostic signal-trap override is removed.
- Corrected Darwin ARM64 variadic mode placement for installer `open`/`openat`, preserving no-follow/exclusive flags and descriptor identity. Hosted macOS failures at `4c908047` are the regression evidence. Focused Windows Store 15/15 and RunCopies 5/5 pass, with three Unix-only tests ignored; exact-head macOS/Linux verification remains pending ([#593](https://github.com/finos/morphir-dotnet/pull/593)).
- Configuration: a member of `[frontend]` that is not a table (for example `[frontend]` `note = "x"`) is no longer refused as an invalid language table. Only an object-valued member is a `[frontend.<language>]` table with `project_file` and `extension`; any other member stays unconstrained, as the upstream schema leaves it
- `compile --extension`: a frontend guest that never answers `compileModel` now fails with `extension.timeout` at the deadline, and a guest that does not answer `shutdown` or take `exit` within the deadline (for example one whose handler blocks its read loop) has its process tree stopped. Before, the host could wait indefinitely in both cases. The same holds for `generate --extension`, so a timed-out request ends within about two deadlines
- A frontend or backend guest that fails to start or initialize no longer leaves its stderr reader or process handle behind
- `compile`: for a language id with a dot, `[frontend."acme.lang"].extension` and `project_file` are now read from that quoted table; before, the id was split at the dot, so the keys were looked up under `[frontend.acme.lang]` and the quoted table was ignored. A project file key that is not read for lack of a language is now named with the quotes too
### Security
- Morphir.Live: centrally pin the transitive `Microsoft.AspNetCore.WebUtilities` dependency to 9.0.23 for [CVE-2026-114149 / GHSA-x33c-w48q-qrgc](https://github.com/dotnet/announcements/issues/511), replacing the affected 9.0.19 selected through Fun.Blazor.
- `compile --extension`, `generate --extension` and the `extension` configuration keys refuse a guest that reports an extension id that starts with `morphir.`, which is reserved for the built-in frontends and backends, including built-ins that do not ship yet (`extension.unavailable`). Before, `generate` refused only the id of a registered built-in backend, and `compile` checked no id. `org.finos.morphir.*` ids are not reserved. `Morphir.Mep.ReservedExtensionIds` and `ExtensionId.ReservedForBuiltIns` hold the rule
- `compile --extension`, `generate --extension` and the `extension` configuration keys: a command name without a directory (for example `morphir-fsharp-backend`) is now searched only in the absolute `PATH` directories, and the resolved absolute path starts. Before, process start also looked in the directory of `morphir` and in the current directory first (CreateProcess on Windows, and .NET on Unix), so a checkout could plant a binary of that name and have it run. A `.dll` extension's `dotnet` host is resolved the same way when `DOTNET_HOST_PATH` is not set. A name that is not on `PATH` now fails with `extension.unavailable` before any process starts. `MepFrontendExtensionConnector.Resolve` throws `FileNotFoundException` for such a name instead of returning it unchanged