diff --git a/src/Persistence/PolecatTests/AncillaryStores/bootstrapping_ancillary_polecat_stores_with_wolverine.cs b/src/Persistence/PolecatTests/AncillaryStores/bootstrapping_ancillary_polecat_stores_with_wolverine.cs index 0682113b7..7e96a11d2 100644 --- a/src/Persistence/PolecatTests/AncillaryStores/bootstrapping_ancillary_polecat_stores_with_wolverine.cs +++ b/src/Persistence/PolecatTests/AncillaryStores/bootstrapping_ancillary_polecat_stores_with_wolverine.cs @@ -96,6 +96,27 @@ public void resolves_both_the_primary_and_ancillary_event_stores() stores.ShouldContain(ancillary); } + [Fact] + public void does_not_register_any_event_store_twice() + { + // GH-3365: the primary Polecat store used to surface TWICE from GetServices() as the + // very same DocumentStore instance — Polecat's own AddPolecat() registers IEventStore, and + // Wolverine's IntegrateWithWolverine() was bridging it a second time. Anything enumerating the + // registered stores then double-counted the primary. Assert on REFERENCE distinctness, not just + // the count, so a genuinely distinct store never trips this and a duplicated instance always does. + var stores = theHost.Services.GetServices().ToArray(); + + var primary = (IEventStore)theHost.Services.GetRequiredService(); + var ancillary = (IEventStore)theHost.Services.GetRequiredService(); + + stores.Cast().Distinct(ReferenceEqualityComparer.Instance).Count() + .ShouldBe(stores.Length); + + stores.Count(x => ReferenceEquals(x, primary)).ShouldBe(1); + stores.Count(x => ReferenceEquals(x, ancillary)).ShouldBe(1); + stores.Length.ShouldBe(2); + } + [Fact] public async Task try_to_use_the_session_transactional_middleware_end_to_end() { diff --git a/src/Persistence/Wolverine.Polecat/WolverineOptionsPolecatExtensions.cs b/src/Persistence/Wolverine.Polecat/WolverineOptionsPolecatExtensions.cs index c13f7409a..961a94bb6 100644 --- a/src/Persistence/Wolverine.Polecat/WolverineOptionsPolecatExtensions.cs +++ b/src/Persistence/Wolverine.Polecat/WolverineOptionsPolecatExtensions.cs @@ -106,14 +106,20 @@ public static PolecatConfigurationExpression IntegrateWithWolverine( s.GetRequiredService(), (IMessageDatabase)s.GetRequiredService())); - // GH-3133 / GH-3219, Gap 2: Polecat's AddPolecat registers IDocumentStore but not the - // store-agnostic JasperFx.Events.IEventStore (the Polecat DocumentStore implements - // IEventStore). Bridge it UNCONDITIONALLY so the store is - // discoverable via GetServices() regardless of whether managed distribution is - // enabled — the EventSubscriptionAgentFamily resolves stores this way (when managed distribution - // is on) AND so does the read-only capabilities / CritterWatch projection-explorer surface - // (always). Marten registers IEventStore unconditionally in its own AddMarten. - expression.Services.AddSingleton(s => (IEventStore)s.GetRequiredService()); + // GH-3365: do NOT bridge the primary store to the store-agnostic JasperFx.Events.IEventStore + // here. Polecat's own AddPolecat() registers IEventStore for the primary DocumentStore on every + // overload (verified against the 4.8.0 floor of our package range), exactly the way AddMarten + // does — which is why Wolverine.Marten registers nothing of the sort for its primary store + // either. An earlier Polecat did not, so GH-3133 / GH-3219 added a bridge here; once Polecat + // started registering it, the two stacked and GetServices() handed back the very + // same DocumentStore instance twice. Everything enumerating the registered stores then + // double-counted the primary (e.g. CritterWatch's EventProgressionPoller polling it twice per + // pass). `bootstrapping_ancillary_polecat_stores_with_wolverine` pins the resulting shape — + // primary once, each ancillary once — so this fails loudly should Polecat ever drop its own + // registration. + // + // The ancillary bridge in AncillaryWolverineOptionsPolecatExtensions IS still required: + // AddPolecatStore() does not register IEventStore for the T store. if (integration.UseWolverineManagedEventSubscriptionDistribution) {