diff --git a/docs/operate/backend-architecture.md b/docs/operate/backend-architecture.md index 43c8c727..e7390cf8 100644 --- a/docs/operate/backend-architecture.md +++ b/docs/operate/backend-architecture.md @@ -107,9 +107,12 @@ required. The Marten outbox is still active for event side-effects: SignalR notifications fire after `SaveChangesAsync` via `ProjectionSideEffects`. -Codegen runs with `TypeLoadMode.Auto` — Wolverine/Marten generated -classes are pre-generated at build time to save cold-start time and -avoid Roslyn compilation at runtime. +Codegen mode is environment-aware. Production runs `TypeLoadMode.Dynamic` +— Wolverine/Marten handler classes are generated in memory on first use +and never written to disk, so the container never tries to write into its +read-only application directory. Local dev and tests run +`TypeLoadMode.Auto`, which generates into `Internal/Generated/` on first +boot and reuses it on the next. ## Marten usage @@ -241,5 +244,6 @@ Integration tests (`Modgud.Api.Tests`) use: - **Shared PostgreSQL container** — one container instance for all test collections, parallelised - **WireMock** — fake OIDC server for external login tests -- **Pre-generated Wolverine/Marten code** (`TypeLoadMode.Auto`) — - eliminates Roslyn compilation at runtime +- **Wolverine/Marten codegen** (`TypeLoadMode.Auto`) — generated on the + first boot into the test working tree and reused, so repeat runs skip + Roslyn compilation diff --git a/src/dotnet/Modgud.Api/Program.cs b/src/dotnet/Modgud.Api/Program.cs index 5cd24c0e..614e95bd 100644 --- a/src/dotnet/Modgud.Api/Program.cs +++ b/src/dotnet/Modgud.Api/Program.cs @@ -885,7 +885,21 @@ opts.Discovery.IncludeAssembly(typeof(Modgud.Authentication.Api.Admin.RecoveryCli).Assembly); opts.Discovery.IncludeAssembly(typeof(Modgud.Authorization.Commands.CreateGroupCommand).Assembly); opts.Durability.Mode = wolverineMode; - opts.CodeGeneration.TypeLoadMode = JasperFx.CodeGeneration.TypeLoadMode.Auto; + // Production generates handler code in-memory (Dynamic): each handler is + // compiled via Roslyn on first use and never written to disk. The previous + // Auto mode tried to persist the generated source under /app/Internal — + // which the non-root container user cannot write — logging "Access to the + // path '/app/Internal' is denied" on the first hit of every handler. + // Dev/Test keep Auto, which writes into Internal/Generated/ on a writable + // working tree and reuses it across restarts. + // + // (Pre-generating at build time + TypeLoadMode.Static would also avoid the + // runtime Roslyn pass, but it requires JasperFx's `RunJasperFxCommands` + // entry point, which is incompatible with this app's WebApplicationFactory + // integration tests — see git history for fix/wolverine-codegen-static-prod.) + opts.CodeGeneration.TypeLoadMode = builder.Environment.IsProduction() + ? JasperFx.CodeGeneration.TypeLoadMode.Dynamic + : JasperFx.CodeGeneration.TypeLoadMode.Auto; // Wolverine 6 made `ServiceLocationPolicy.NotAllowed` the default. We // keep that strict default — accidental new service-location dependencies