From 57300579bb4d82314d0de1d46a1e96ad64222ef7 Mon Sep 17 00:00:00 2001 From: "Jeremy D. Miller" Date: Thu, 28 May 2026 10:37:22 -0500 Subject: [PATCH] Bump Polecat 4.1.1 -> 4.2.1 and unskip the three Polecat scheduled-cascade tests Polecat 4.2.1 ships polecat#161, the upstream companion fix to GH-2941. Its DocumentSessionBase.SaveChangesAsync no longer early-returns when only ITransactionParticipants are queued, so the StoreIncomingEnvelopeParticipant added via Session.StoreIncoming(...) inside PolecatEnvelopeTransaction. PersistIncomingAsync actually runs for scheduled cascades from [ReadAggregate] / [DocumentExists] handlers that don't write any documents. That lets the three previously [Skip]'d Polecat tests (added in #2943 with a Skip reason pointing at polecat#161) exercise the end-to-end path on CI: - Bug_2941_read_aggregate_scheduled_cascade.read_aggregate_handler_schedules_its_cascading_message - Bug_2941_document_exists_scheduled_cascade.document_exists_handler_schedules_its_cascading_message - Bug_2941_document_exists_scheduled_cascade.document_does_not_exist_handler_schedules_its_cascading_message Also tightens the PolecatPersistenceFrameProvider.CanApply comment so it documents the pairing with polecat#161 / Polecat 4.2.1 rather than the 'necessary but not sufficient' caveat from the original commit. Local: full wolverine.slnx -c Release builds clean (0 warnings, 0 errors). Marten Bugs sweep 45/45 pass. Polecat tests will be validated by CI - the local SQL Server 2025 image on Apple Silicon is too slow to run them (existing Polecat durable tests time out the same way, unrelated to this change). Co-Authored-By: Claude Opus 4.7 (1M context) --- Directory.Packages.props | 2 +- .../Bug_2941_document_exists_scheduled_cascade.cs | 4 ++-- .../Bug_2941_read_aggregate_scheduled_cascade.cs | 2 +- .../Sagas/PolecatPersistenceFrameProvider.cs | 12 +++++------- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index a4fedb371..5bf9dc31f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -40,7 +40,7 @@ - + diff --git a/src/Persistence/PolecatTests/Bugs/Bug_2941_document_exists_scheduled_cascade.cs b/src/Persistence/PolecatTests/Bugs/Bug_2941_document_exists_scheduled_cascade.cs index 5380d25ce..0b61a24c3 100644 --- a/src/Persistence/PolecatTests/Bugs/Bug_2941_document_exists_scheduled_cascade.cs +++ b/src/Persistence/PolecatTests/Bugs/Bug_2941_document_exists_scheduled_cascade.cs @@ -30,7 +30,7 @@ public Bug_2941_document_exists_scheduled_cascade(PolecatDocumentExistsScheduled private IHost theHost => _context.Host; - [Fact(Skip = "Requires Polecat upstream fix: see Bug_2941_read_aggregate_scheduled_cascade for details. Same SaveChangesAsync-skips-participants root cause.")] + [Fact] public async Task document_exists_handler_schedules_its_cascading_message() { var tracked = await theHost @@ -43,7 +43,7 @@ public async Task document_exists_handler_schedules_its_cascading_message() tracked.Received.MessagesOf().Count().ShouldBe(1); } - [Fact(Skip = "Requires Polecat upstream fix: see Bug_2941_read_aggregate_scheduled_cascade for details.")] + [Fact] public async Task document_does_not_exist_handler_schedules_its_cascading_message() { var tracked = await theHost diff --git a/src/Persistence/PolecatTests/Bugs/Bug_2941_read_aggregate_scheduled_cascade.cs b/src/Persistence/PolecatTests/Bugs/Bug_2941_read_aggregate_scheduled_cascade.cs index 7a589ee04..726e91532 100644 --- a/src/Persistence/PolecatTests/Bugs/Bug_2941_read_aggregate_scheduled_cascade.cs +++ b/src/Persistence/PolecatTests/Bugs/Bug_2941_read_aggregate_scheduled_cascade.cs @@ -74,7 +74,7 @@ public async Task read_aggregate_handler_publishes_its_cascading_message() tracked.Received.MessagesOf().Count().ShouldBe(1); } - [Fact(Skip = "Requires Polecat upstream fix: DocumentSessionBase.SaveChangesAsync early-returns when _workTracker has no outstanding work, which silently skips StoreIncomingEnvelopeParticipant added via Session.StoreIncoming(...) for a [ReadAggregate] handler whose body emits only a scheduled cascade (no doc ops, no streams). The Wolverine-side CanApply fix is necessary but not sufficient on Polecat. Unskip when Polecat ships a SaveChangesAsync that runs participants even when no document/stream work is outstanding. GH-2941.")] + [Fact] public async Task read_aggregate_handler_schedules_its_cascading_message() { // The GH-2941 case. Without the CanApply fix this times out: the cascade is recorded as diff --git a/src/Persistence/Wolverine.Polecat/Persistence/Sagas/PolecatPersistenceFrameProvider.cs b/src/Persistence/Wolverine.Polecat/Persistence/Sagas/PolecatPersistenceFrameProvider.cs index 9d3f46cf6..1b8014c3c 100644 --- a/src/Persistence/Wolverine.Polecat/Persistence/Sagas/PolecatPersistenceFrameProvider.cs +++ b/src/Persistence/Wolverine.Polecat/Persistence/Sagas/PolecatPersistenceFrameProvider.cs @@ -67,13 +67,11 @@ public bool CanApply(IChain chain, IServiceContainer container) // GH-2941: detect parameter attributes whose Modify() injects a non-MethodCall frame // depending on IDocumentSession. See MartenPersistenceFrameProvider.CanApply for the full - // explanation; Polecat mirrors the Marten path structurally. NOTE: this is necessary but - // not sufficient on the Polecat side - Polecat 4.1.1's DocumentSessionBase.SaveChangesAsync - // early-returns when _workTracker has no outstanding work, which skips transaction - // participants entirely. A handler that only adds a StoreIncomingEnvelopeParticipant via - // PolecatEnvelopeTransaction.PersistIncomingAsync therefore never gets its participant - // executed, even after this fix attaches the SaveChangesAsync postprocessor. The full - // Polecat fix is upstream in Polecat's SaveChangesAsync guard. + // explanation; Polecat mirrors the Marten path structurally. Pairs with the upstream + // Polecat fix (polecat#161, shipped in Polecat 4.2.1): DocumentSessionBase.SaveChangesAsync + // now also runs queued ITransactionParticipants when there are no document operations + // outstanding, so the StoreIncomingEnvelopeParticipant added via Session.StoreIncoming(...) + // for a scheduled cascade actually executes inside the chain's session transaction. if (ChainHasPolecatSessionAttributes(chain)) return true; var serviceDependencies = chain