Polecat#46: Add Polecat.AotSmoke consumer project#106
Merged
Conversation
New consumer-side smoke test that boots a minimal Host with AddPolecat, registers a single self-aggregating snapshot projection, resolves an IDocumentSession via DI, and constructs a LINQ query — without referencing JasperFx.RuntimeCompiler. The csproj sets IsAotCompatible=true and promotes IL2026/IL2046/IL2055/IL2065/IL2067/IL2070/IL2072/IL2075/ IL2090/IL2091/IL2111/IL3050/IL3051 to errors so any regression in Polecat's AOT-clean surface fails the build. Mirrors src/JasperFx.AotSmoke in the jasperfx repo. CI wiring lands in a follow-up commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add .github/workflows/aot-smoke.yml — separate workflow that builds src/Polecat.AotSmoke on push/PR to main. No SQL Server needed (the smoke test never opens a connection). Runs on net9.0 + net10.0 via the project's inherited TargetFrameworks. Existing polecat.yml only builds Polecat.Tests so the AOT gate wouldn't fire there even with the project in the solution. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 18, 2026
Closed
jeremydmiller
added a commit
that referenced
this pull request
May 18, 2026
CI surfaced the broader scope of JasperFx#276 Phase 1's fail-fast contract:
any closed `SingleStreamProjection<TDoc, TId>` (or sibling) instantiated by
the live-aggregation path — `AggregateStreamAsync<T>`, `FetchLatest<T>`,
`FetchForWriting<T>`, `Projections.Snapshot<T>` — relies on the source
generator emitting a standalone `TEvolver` for the self-aggregating
document type `T`. The SG only emits when `T` is `partial` AND the
assembly references the SG analyzer.
Marks the following document types `partial`:
- Polecat.Tests:
- Bug4197Aggregate, DeletableAggregate, StudentCourseEnrollment,
QuestAggregate, InlineSeAggregate, InvoiceAggregate, OrderAggregate,
Report, ScenarioQuestParty, CompositeQuestParty, QuestStats,
QuestParty, CustomerSummary, SelfAggregatingStringQuest,
StringQuestParty, SnapshotParty, SnapshotPartyByString,
MonthlyAccountActivity, Payment, Payment2
- Polecat.AspNetCore.Testing:
- StreamingQuestParty (+ wires
JasperFx.Events.SourceGenerator as an Analyzer PackageReference)
Adds two new sections to docs/migration-guide.md:
- "Projections and self-aggregating documents must be `partial`" under
Event-sourcing API changes — covers the fail-fast diagnostic, the
partial requirement, the analyzer-PackageReference requirement for
consumer assemblies, the EvolveAsync / DetermineActionAsync override
escape hatch, and the surfaces unaffected (FlatTableProjection,
EF Core projections).
- "Inline-lambda projection registration APIs removed" — covers the
Project<T>(lambda) / CreateEvent / DeleteEvent / ProjectEvent
removals from jasperfx#276 / #286 and the conventional-method
replacement shape.
Refreshes the foundation pin table (JasperFx alpha.11→alpha.13,
JasperFx.Events alpha.4→alpha.11, SG alpha.2→alpha.4) and extends the
AOT/codegen posture section to reference #107 (reflective surface
tightening) and #106 (Polecat.AotSmoke CI gate) plus the new FEC-free
posture from #276.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jeremydmiller
added a commit
that referenced
this pull request
May 18, 2026
…#109) * Bump JasperFx.Events to 2.0.0-alpha.11 + SourceGenerator to 2.0.0-alpha.4 JasperFx#276 Phase 1 (alpha.11) removed the FastExpressionCompiler fallback for projection apply-method dispatch. Source-generated dispatchers ([GeneratedEvolver]) are now the only path, with fail-fast at registration when no generated dispatcher is found. SG alpha.4 pulls in a downstream fix (JasperFx/jasperfx#291) for a sync-DetermineActionAsync emit bug that surfaced on Polecat projections combining sync Apply + ShouldDelete (StringQuestPartyProjection, QuestPartyProjection, DeletableAggregate). Without alpha.4 the generated dispatcher fails to compile with CS8135. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Verify Polecat projections register generated dispatchers (JFx#276 Phase 3) JasperFx.Events 2.0.0-alpha.11 (#276 Phase 1) made source-generated dispatchers the only path; DocumentStore fail-fasts on projection types without [GeneratedEvolver]. Two consumer-side changes needed: 1. **partial class** on every projection registered through Projections.Add<T>(...) or Snapshot<T>(...) — so the SG can emit a partial override of Evolve / EvolveAsync / DetermineActionAsync. Affects: QuestLogProjection, MultiEventQuestLogProjection, SimpleEnrichmentProjection, EnrichmentCallOrderProjection, DbLookupEnrichmentProjection, MonthlyAccountActivityProjection, CompositeOrderProjection, OrderShippingNotificationProjection, StringQuestPartyProjection, CustomerSummaryProjection, and the nested InlineSeProjection (whose outer test class also becomes partial). 2. **SG analyzer wired into Polecat.AotSmoke.csproj** as an Analyzer-only PackageReference, and QuestProjection declared partial. The smoke gate now exercises the SG-only dispatch path end-to-end; without it, AotSmoke runtime throws InvalidProjectionException at host build. Test-side migrations off the removed inline-lambda registration API (JasperFx#276 / #286 dropped Project<TEvent>(lambda) + CreateEvent/DeleteEvent/ProjectEvent overloads): - event_projection_enrichment_tests.cs (3 projections) — converted constructor `Project<TEvent>((e, ops) => ...)` calls to conventional `public void Project(TEvent e, IDocumentSession ops)` methods. - event_projection_tests.cs — removed QuestLogWithLambdaProjection + event_projection_with_lambda test; the API they exercised no longer exists and the conventional path is already covered by QuestLogProjection. FlatTableProjection unaffected — it has its own Apply dispatch via _handlers dictionary, doesn't use JasperFx.Events Apply discovery. EF Core projections (EfCoreSingleStreamProjection etc.) override DetermineActionAsync directly, also bypassing the SG-required path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Bump Polecat to 4.0.0-alpha.5 First Polecat alpha consuming the FEC-free JasperFx.Events alpha (2.0.0-alpha.11 + SourceGenerator 2.0.0-alpha.4). All projections registered through Polecat now go through source-generated dispatchers exclusively — no FastExpressionCompiler fallback. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Mark self-aggregating document types `partial` + migration guide CI surfaced the broader scope of JasperFx#276 Phase 1's fail-fast contract: any closed `SingleStreamProjection<TDoc, TId>` (or sibling) instantiated by the live-aggregation path — `AggregateStreamAsync<T>`, `FetchLatest<T>`, `FetchForWriting<T>`, `Projections.Snapshot<T>` — relies on the source generator emitting a standalone `TEvolver` for the self-aggregating document type `T`. The SG only emits when `T` is `partial` AND the assembly references the SG analyzer. Marks the following document types `partial`: - Polecat.Tests: - Bug4197Aggregate, DeletableAggregate, StudentCourseEnrollment, QuestAggregate, InlineSeAggregate, InvoiceAggregate, OrderAggregate, Report, ScenarioQuestParty, CompositeQuestParty, QuestStats, QuestParty, CustomerSummary, SelfAggregatingStringQuest, StringQuestParty, SnapshotParty, SnapshotPartyByString, MonthlyAccountActivity, Payment, Payment2 - Polecat.AspNetCore.Testing: - StreamingQuestParty (+ wires JasperFx.Events.SourceGenerator as an Analyzer PackageReference) Adds two new sections to docs/migration-guide.md: - "Projections and self-aggregating documents must be `partial`" under Event-sourcing API changes — covers the fail-fast diagnostic, the partial requirement, the analyzer-PackageReference requirement for consumer assemblies, the EvolveAsync / DetermineActionAsync override escape hatch, and the surfaces unaffected (FlatTableProjection, EF Core projections). - "Inline-lambda projection registration APIs removed" — covers the Project<T>(lambda) / CreateEvent / DeleteEvent / ProjectEvent removals from jasperfx#276 / #286 and the conventional-method replacement shape. Refreshes the foundation pin table (JasperFx alpha.11→alpha.13, JasperFx.Events alpha.4→alpha.11, SG alpha.2→alpha.4) and extends the AOT/codegen posture section to reference #107 (reflective surface tightening) and #106 (Polecat.AotSmoke CI gate) plus the new FEC-free posture from #276. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Split self-aggregating docs that mix Guid Id with string streams Polecat 3.x's FEC-based live aggregator was happy to construct SingleStreamProjection<TDoc, TId> with a TId that didn't match the document's Id property — the FEC dispatcher just used whatever TId the call site passed. Two tests relied on this latent flexibility: - project_latest_with_string_key_includes_pending_events and project_latest_merges_committed_and_pending_for_string_key registered Report (Guid Id) against string-keyed streams. - always_enforce_consistency_with_string_stream_id registered QuestAggregate (Guid Id) against string-keyed streams. Polecat 4 routes through the JasperFx.Events source generator, which keys the generated evolver on the document's Id property type. The SG emits IGeneratedSyncEvolver<Report, Guid>, runtime instantiates <Report, string>, and lookup fails. Splits each affected document type into a separate string-id sibling: - new StringReport with `string Id` for the project_latest string-key tests - new StringQuestAggregate with `string Id` for the always_enforce_consistency string-id test Same events, same Apply/Create methods — only the Id type differs. Remaining CI failures on this PR (QuestParty / DeletableAggregate / StringQuestParty / SelfAggregatingStringQuest with conventional ShouldDelete methods routed through the assembly-registered evolver lookup) are blocked on JasperFx/jasperfx#298 — a guard bug in JasperFxAggregationProjectionBase.tryUseAssemblyRegisteredEvolver short-circuits before checking IGeneratedSyncDetermineAction (the interface the SG emits for ShouldDelete docs and which handles ShouldDelete natively). 5-line upstream fix sketched in the issue. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Bump JasperFx.Events 2.0.0-alpha.11 → alpha.12 + SourceGenerator alpha.4 → alpha.5 JasperFx.Events alpha.12 ships JasperFx/jasperfx#300 (closes #298) — the fix for tryUseAssemblyRegisteredEvolver's guard that was short-circuiting on HasShouldDeleteMethods() before reaching the IGeneratedSyncDetermineAction branch (the very interface the SG emits for self-aggregating docs with ShouldDelete, which handles deletions natively). Unblocks the ~25 Polecat test failures on this PR from the previous CI run — all on the closed generic `SingleStreamProjection<TDoc, Guid>` where TDoc was QuestParty, StringQuestParty, or DeletableAggregate (each has a ShouldDelete method). JasperFx.Events.SourceGenerator alpha.5 is a coordinated version bump that came with the alpha.12 release — the SG package itself is unchanged from alpha.4 in any way that affects Polecat (the fix lives in the JasperFx.Events runtime, not the SG). Verified locally via Polecat.AotSmoke pointed at alpha.12 — bare `opts.Projections.Add<SingleStreamProjection<DocWithShouldDelete, Guid>>(...)` now boots cleanly. Local SQL-based test verification was blocked by a Docker daemon issue; CI on amd64 is the authoritative run. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Resolve live-aggregator TId via DocumentMapping (strong-typed-id fix) CI surfaced 4 of 1105 test failures — all on strong-typed-id aggregates (Payment with PaymentId wrapping Guid; Payment2 with Payment2Id wrapping string). The closed runtime SingleStreamProjection<TDoc, TId> instances were built with TId = the underlying *primitive* (Guid / string), but the source generator keys its emitted IGeneratedSyncEvolver on TDoc's actual Id property type — the *wrapper* (PaymentId / Payment2Id). The TId mismatch tripped JasperFxAggregationProjectionBase.tryUseAssembly RegisteredEvolver's interface-assignability check, so the dispatcher wasn't found and the post-FEC fail-fast threw. Two surfaces to fix: 1. **EventGraph.Build<TDoc>()** — the live-aggregation entry. Was hardcoding `idType = StreamIdentity == AsGuid ? typeof(Guid) : typeof(string)`, missing the wrapper. Now resolves via `new DocumentMapping(typeof(TDoc), _options).IdType`, the same shape PolecatProjectionOptions.AddSnapshotProjection<T>() already uses. This unblocks the no-explicit-registration paths (AggregateStreamAsync<Payment>, FetchLatest<Payment2>, etc). 2. **using_guid_based_strong_typed_id_for_aggregate_identity.cs** — two tests explicitly registered `Add<SingleStreamProjection<Payment, Guid>>(...)` instead of `Add<SingleStreamProjection<Payment, PaymentId>>(...)`. Worked under Polecat 3.x's FEC fallback (which didn't care about TId); under SG-only dispatch the registration must match the SG's emitted evolver shape. Both inline + async variants updated. (The Payment2 string-based test file already registers with the wrapper type Payment2Id; only the live-aggregation path needed fixing there.) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jeremydmiller
added a commit
that referenced
this pull request
May 19, 2026
Brings docs/migration-guide.md to release-readiness. Four additions on top of the existing scaffolding from #109 + #111: 1. **Pin table refreshed + extended** - JasperFx.Events alpha.12 → alpha.15 (the post-#306 SG fixes already baked into Polecat 4.0.0-alpha.6 from #111) - JasperFx.Events.SourceGenerator alpha.5 → alpha.8 (ditto) - Add JasperFx.RuntimeCompiler 5.0.0-alpha.4 row, with a callout warning consumers off the parallel stale 2.0.x line on NuGet - Add JasperFx.SourceGeneration 2.0.0-alpha.5 row - "pin all FIVE packages" → "pin all SEVEN" + lockstep callout 2. **SG-only dispatch section extended** (under the existing "Projections and self-aggregating documents must be `partial`"): - Convention-method visibility — `Apply` / `Create` / `ShouldDelete` must be `public` (the FEC fallback reflected over private / internal / protected too; the SG only sees public) - `[Identity]` attribute for non-conventional id-property names - Required-member aggregate `Create` factory recipe (preferred over the `new T { Required = default! }` fallback the SG emits when no factory is supplied) - Explicit cross-link to Marten's migration guide section that covers the same shared consumer contract 3. **Inline-lambda removal section cross-linked to Marten**. The migration recipe is identical between products; cross-link instead of forking the longer worked example. 4. **AOT publishing promoted to a top-level `### Publishing AOT` section** with three cross-links: - JasperFx's live AOT guide (https://jasperfx.github.io/codegen/aot) - Marten's AOT publishing walkthrough (https://martendb.io/configuration/aot-publishing) - `Polecat.AotSmoke` (#106) as the canonical in-tree consumer example, plus the `IsAotCompatible=true` flag on Polecat.csproj itself The existing AOT call-outs about #107 / FEC removal / #106's WarningsAsErrors set stay; "Publishing AOT" is the new section header that frames them as the consumer-side recipe. `npm run docs-build` (VitePress) renders clean: build complete in 4.27s, exit code 0. References [#46](#46) (master plan — "Migration guide Polecat 3 → Polecat 4" item). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a CI-gated AOT smoke-test project (
src/Polecat.AotSmoke) that references Polecat as a static-mode AOT consumer and fails the build on any IL2026/IL2046/IL2055/IL2065/IL2067/IL2070/IL2072/IL2075/IL2090/IL2091/IL2111/IL3050/IL3051 warning emitted from its compilation. Ticks off the AOT-smoke checkbox on #46.Mirrors
src/JasperFx.AotSmoke(jasperfx commitd4077d8) adapted for Polecat's surface.What's exercised
Top-level statements in
Program.cstouch:IServiceCollection.AddPolecat(Action<StoreOptions>)(the main consumer DI surface)PolecatProjectionOptions.Snapshot<T>(SnapshotLifecycle.Inline)(projection registration)IDocumentSessionresolution throughISessionFactoryIQuerySession.Query<Quest>().Where(...)(LINQ provider entry + extension surface)Crucially, the project does NOT reference
JasperFx.RuntimeCompilerand does NOT callservices.AddRuntimeCompilation()— it's the "StaticTypeLoadMode" consumer Polecat#46 promises.The smoke runs to completion (
Polecat AOT smoke OK.thenreturn 0) — it never opens a SQL connection, so no DB container is needed in CI.Gate verification
Temporarily added a deliberately-AOT-hostile call (
JsonSerializer.Serialize((object)session)) and confirmed it broke the build on both net9.0 and net10.0:Reverted before commit. Final
dotnet build -c Releaseis clean (0 warnings, 0 errors) on net9.0 and net10.0;dotnet runexits 0 on both.CI wiring
New workflow
.github/workflows/aot-smoke.ymlruns on push/PR to main, builds onlysrc/Polecat.AotSmoke/Polecat.AotSmoke.csproj. Cannot piggy-back onpolecat.ymlbecause that workflow targets onlyPolecat.Tests.csproj— adding the project topolecat.slnxalone wouldn't gate CI.Pre-existing Polecat IL warnings (not addressed in this PR)
dotnet buildofPolecat.csprojemits 14 pre-existing IL warnings (PolecatProjectionBatchMakeGenericMethod, DocumentMappingValueTypeInfo.ForType, StreamCompactingISerializer.ToJson). These don't fail the smoke gate (which only promotes warnings emitted under the AotSmoke compilation context to errors), and they're real reflective surfaces not currently reached from the smoke surface. Capturing as a follow-up under #46 rather than expanding scope of this PR.Test plan
dotnet build src/Polecat.AotSmoke/Polecat.AotSmoke.csproj -c Releasesucceeds with 0 warnings, 0 errors locally on net9.0 + net10.0dotnet run --project src/Polecat.AotSmoke/Polecat.AotSmoke.csprojexits 0 on both TFMsaot-smokeworkflow passes on this PRReferences #46.
🤖 Generated with Claude Code