Status: in-flight (JasperFx side complete; cross-product audits in progress)
Part of [Master] Critter Stack 2026 (#217).
Sister pillars: cold-start (#212), dedupe Marten ↔ Polecat (#214).
Every shipped library publishes AOT-clean (or trim-clean with explicit, accurate annotations). Apps that don't need runtime codegen should be able to publish with PublishAot=true / IsTrimmable=true and have the trimmer drop the entire Roslyn graph.
Definition of done
- Every shipping library either:
- Compiles AOT-clean with no
IL2026 / IL3050 warnings, or
- Has every dynamic / unreferenced surface annotated with
[RequiresDynamicCode] / [RequiresUnreferencedCode] so a downstream IsAotCompatible=true consumer sees a precise punch-list rather than a wall of analyzer noise.
- A user app in
Static TypeLoadMode with pre-generated code can dotnet publish /p:PublishAot=true against Marten 9 + Wolverine 6 + JasperFx 2.0 and produce a working binary.
- Wolverine's runtime codegen is annotated, not removed (per Critter Stack 2026 post — source-gen is architecturally rejected). AOT users either pre-generate Wolverine code or accept the dynamic-code warning.
Codegen story for AOT users
Marten 9 and Wolverine 6 both lean on JasperFx's existing dotnet run -- codegen write for their pre-generated code. There is no Marten-specific or Wolverine-specific equivalent — JasperFx provides the canonical pre-gen pipeline via dependency. AOT-publishing apps:
- Run
dotnet run -- codegen write at dev time. This pulls JasperFx.RuntimeCompiler (and Roslyn) in transitively only at codegen time, producing the source files on disk.
- Publish with
TypeLoadMode = Static and without calling services.AddRuntimeCompilation(). The trimmer drops the entire Roslyn graph; the published binary contains only the pre-generated sources baked in.
This is the "conditional runtime-compiler dependency" model — present at dev time, absent in production. Per-product master plans for Marten 9 and Wolverine 6 just need to verify their pre-generated output is byte-stable and round-trips correctly under Static mode.
Cross-product workstreams
| Workstream |
Drives |
Status |
| RuntimeCompiler / Roslyn becomes opt-in via DI |
All |
✅ landed in JasperFx 2.0 (PR #190) |
| AOT attributes on every reflective public API |
All |
✅ JasperFx + JasperFx.Events complete; per-product audits in flight |
| Source-generator adoption |
Marten, Polecat, JasperFx |
Per-product |
| AOT-clean smoke-test app per stack |
Cross-stack |
✅ JasperFx side shipped (JasperFx.AotSmoke, PR d4077d8); per-stack consumer apps still pending |
| Documented "publish AOT with the Critter Stack" guide |
Cross-stack |
✅ JasperFx side shipped (docs/codegen/aot.md); cross-stack guide pending |
JasperFx workstreams
✅ Already shipped (2.0 branch)
What's left
JasperFx-side work is complete. Remaining items are cross-product and tracked under each product's master plan:
Cross-product dependencies
- Marten 9 / Polecat 4 / Wolverine 6 AOT readiness depends on JasperFx 2.0 + JasperFx.Events 2.0 being annotation-complete.
- The cross-stack AOT smoke-test app depends on at least one consumer (Marten or Polecat) reaching annotation-complete state.
Open design questions
None at filing time. Per-product audits may surface new questions as they go.
Explicitly out of scope for this pillar
- Wolverine source-generator conversion.
- Native-AOT Roslyn (the AssemblyGenerator + Microsoft.CodeAnalysis pipeline doesn't AOT-publish; that's by design — it's the dev-time path).
Linked implementation work
Filled in as per-product master plans land.
Every shipped library publishes AOT-clean (or trim-clean with explicit, accurate annotations). Apps that don't need runtime codegen should be able to publish with
PublishAot=true/IsTrimmable=trueand have the trimmer drop the entire Roslyn graph.Definition of done
IL2026/IL3050warnings, or[RequiresDynamicCode]/[RequiresUnreferencedCode]so a downstreamIsAotCompatible=trueconsumer sees a precise punch-list rather than a wall of analyzer noise.StaticTypeLoadModewith pre-generated code candotnet publish /p:PublishAot=trueagainst Marten 9 + Wolverine 6 + JasperFx 2.0 and produce a working binary.Codegen story for AOT users
Marten 9 and Wolverine 6 both lean on JasperFx's existing
dotnet run -- codegen writefor their pre-generated code. There is no Marten-specific or Wolverine-specific equivalent — JasperFx provides the canonical pre-gen pipeline via dependency. AOT-publishing apps:dotnet run -- codegen writeat dev time. This pullsJasperFx.RuntimeCompiler(and Roslyn) in transitively only at codegen time, producing the source files on disk.TypeLoadMode = Staticand without callingservices.AddRuntimeCompilation(). The trimmer drops the entire Roslyn graph; the published binary contains only the pre-generated sources baked in.This is the "conditional runtime-compiler dependency" model — present at dev time, absent in production. Per-product master plans for Marten 9 and Wolverine 6 just need to verify their pre-generated output is byte-stable and round-trips correctly under
Staticmode.Cross-product workstreams
JasperFx.AotSmoke, PR d4077d8); per-stack consumer apps still pendingdocs/codegen/aot.md); cross-stack guide pendingJasperFx workstreams
✅ Already shipped (2.0 branch)
ITypeLoaderinterface;DynamicTypeLoadercarries[RequiresDynamicCode]/[RequiresUnreferencedCode].StaticTypeLoaderis AOT-safe by construction.JasperFx.RuntimeCompiler.AssemblyGeneratorannotated.services.AddRuntimeCompilation()is the single recognizable seam; AOT consumers omit it and the trimmer drops Roslyn.CloseAndBuildAs<T>overloads carry both AOT attributes;GenericFactoryCacheis the AOT-friendly replacement for hot paths.<IsAotCompatible>true</IsAotCompatible>set onsrc/JasperFx/JasperFx.csprojandsrc/JasperFx.Events/JasperFx.Events.csproj(both carry inline xmldoc pointing back to this issue).MakeGenericType/Activator.CreateInstance/Type.GetMethod(...).Invokecall sites in JasperFx + JasperFx.Events — annotated with[RequiresDynamicCode]/[RequiresUnreferencedCode]/[UnconditionalSuppressMessage]+ justification, or migrated toGenericFactoryCache. Live surfaces:ServiceContainer.cs:217/303/350,JasperFx.Events.Aggregation/*(FEC-driven; class-level suppressions pointing at the SourceGenerator escape hatch per the AOT publishing guide).JasperFx.CommandLine—CommandFactory.cslines 68/76/124/142/148/162/208 carry[RequiresUnreferencedCode]/[RequiresDynamicCode]; line 304 carries[DynamicallyAccessedMembers(Interfaces)]. AOT-publishing apps should consume commands through the source-generated manifest emitted byJasperFx.SourceGenerator.src/JasperFx.AotSmoke/(commitd4077d8). References JasperFx + JasperFx.Events, sets<IsAotCompatible>true</IsAotCompatible>+<TrimMode>full</TrimMode>, escalates IL2026/IL2046/IL2055/IL2065/IL2067/IL2070/IL2072/IL2075/IL2090/IL2091/IL2111/IL3050/IL3051 to errors. Wired into the standard build viajasperfx.sln; a regression in JasperFx core's AOT annotations fails the build in our CI.docs/codegen/aot.md(Static mode, noAddRuntimeCompilation, pre-gen viacodegen write).What's left
JasperFx-side work is complete. Remaining items are cross-product and tracked under each product's master plan:
Cross-product dependencies
Open design questions
None at filing time. Per-product audits may surface new questions as they go.
Explicitly out of scope for this pillar
Linked implementation work
Filled in as per-product master plans land.