Follow-up to #3291 (fixed by #3298) and #3353 (fixed by #3357). Filed for completeness while auditing that area — the remaining uncovered shape in the same defect family.
Summary
The GH-3291 branch in EFCorePersistenceFrameProvider.ApplyTransactionSupport(chain, container) — the two-argument overload reached from AutoApplyTransactions — gates the Lightweight-mode outbox enlistment on chain.RequiresOutbox():
else if (isHttpChain(chain)
&& !isMultiTenanted(container, dbContextType)
&& chain.RequiresOutbox() // <-- this gate
&& chain.ShouldFlushOutgoingMessages()
&& hasDatabaseBackedMessagePersistence(container))
HttpChain.RequiresOutbox() only reflects an injected IMessageBus/MessageContext service dependency (HttpChain.cs, ServiceDependencies(...).Contains(typeof(IMessageBus))). An endpoint that injects the DbContext (so CanApply is true and AutoApplyTransactions claims the chain through this overload) but cascades only via its return tuple never satisfies it:
[WolverinePost("/ef/lightweight/dbcontext-cascade")]
public static (IResult, ItemStored) Post(CreateItemCommand command, ItemsDbContext db)
{
var item = new Item { Id = Guid.NewGuid(), Name = command.Name };
db.Items.Add(item);
return (Results.Ok(), new ItemStored(item.Id));
}
Verified
Probed on main + #3357 (EF-Core-only host, TransactionMiddlewareMode.Lightweight, AutoApplyTransactions, durable local queues, PostgreSQL message store), instrumenting the generated chain:
enlist=False cascade=True save=True standaloneFlush=True requiresOutbox=False
No EnlistInOutboxAsync; EnqueueCascadingAsync runs before SaveChangesAsync, and with MessageContext.Transaction null the send-now branch of MessageBus.PersistOrSendAsync dispatches the cascade before the commit — the same runtime symptom as #3291/#3353, reached through the third and last endpoint shape:
| Endpoint shape |
Claimed by |
Status |
injects IMessageBus, publishes explicitly |
two-arg overload |
fixed by #3298 |
storage action only, no DbContext injection |
entityType overload |
fixed by #3357 |
injects DbContext, cascades via tuple |
two-arg overload |
this issue |
Suggested fix
Since #3357 the branch already requires database-backed message persistence (hasDatabaseBackedMessagePersistence, mirroring EfCoreEnvelopeTransaction's constructor precondition), so the RequiresOutbox() gate could simply be dropped — enlisting an endpoint that never cascades is a no-op flush at commit time, which is exactly the trade #3357 made for the storage-action overload. Alternatively, teach HttpChain.RequiresOutbox() to also return true when the chain has cascading return values (note RequiresOutbox() is only consumed by the EF Core frame provider plus a diagnostics usage source, so the blast radius is small either way).
Either way this should come with test automation mirroring Bug_3353_lightweight_storage_action_cascade (the Wolverine.Http.Tests.EfCoreOnly pinned-ApplicationAssembly pattern): assert EnlistInOutboxAsync present, no pre-commit dispatch, enlist → SaveChangesAsync → CommitAsync ordering.
🤖 Generated with Claude Code
Follow-up to #3291 (fixed by #3298) and #3353 (fixed by #3357). Filed for completeness while auditing that area — the remaining uncovered shape in the same defect family.
Summary
The GH-3291 branch in
EFCorePersistenceFrameProvider.ApplyTransactionSupport(chain, container)— the two-argument overload reached fromAutoApplyTransactions— gates the Lightweight-mode outbox enlistment onchain.RequiresOutbox():HttpChain.RequiresOutbox()only reflects an injectedIMessageBus/MessageContextservice dependency (HttpChain.cs,ServiceDependencies(...).Contains(typeof(IMessageBus))). An endpoint that injects theDbContext(soCanApplyis true andAutoApplyTransactionsclaims the chain through this overload) but cascades only via its return tuple never satisfies it:Verified
Probed on
main+ #3357 (EF-Core-only host,TransactionMiddlewareMode.Lightweight,AutoApplyTransactions, durable local queues, PostgreSQL message store), instrumenting the generated chain:No
EnlistInOutboxAsync;EnqueueCascadingAsyncruns beforeSaveChangesAsync, and withMessageContext.Transactionnull the send-now branch ofMessageBus.PersistOrSendAsyncdispatches the cascade before the commit — the same runtime symptom as #3291/#3353, reached through the third and last endpoint shape:IMessageBus, publishes explicitlyDbContextinjectionDbContext, cascades via tupleSuggested fix
Since #3357 the branch already requires database-backed message persistence (
hasDatabaseBackedMessagePersistence, mirroringEfCoreEnvelopeTransaction's constructor precondition), so theRequiresOutbox()gate could simply be dropped — enlisting an endpoint that never cascades is a no-op flush at commit time, which is exactly the trade #3357 made for the storage-action overload. Alternatively, teachHttpChain.RequiresOutbox()to also return true when the chain has cascading return values (noteRequiresOutbox()is only consumed by the EF Core frame provider plus a diagnostics usage source, so the blast radius is small either way).Either way this should come with test automation mirroring
Bug_3353_lightweight_storage_action_cascade(theWolverine.Http.Tests.EfCoreOnlypinned-ApplicationAssemblypattern): assertEnlistInOutboxAsyncpresent, no pre-commit dispatch, enlist →SaveChangesAsync→CommitAsyncordering.🤖 Generated with Claude Code