Critter Stack Round Up for the Past 2 Weeks

Critter Stack Roundup: Two Weeks, Six Repositories, and an EF Core Sweep

It’s been a busy couple of weeks across the Critter Stack. Between September 13th and today, we merged roughly 370 pull requests across JasperFx, Weasel, Marten, Polecat, Fisher, and Wolverine, and shipped a pile of releases along the way. More importantly, a big chunk of that work came from, or was driven by, people outside of JasperFx Software, so there’s a lot of thanking to do in this post.

Here’s the short version of what shipped:

ProjectReleases in the last two weeks
Wolverine6.37.0, 6.38.0, 6.39.0, 6.39.1, 6.40.0
Marten9.34.0, 9.35.0, 9.36.0, 9.37.0, 9.38.0, 9.39.0, 9.39.1, 9.40.0
Polecat5.27.0, 5.28.0, 5.28.1, 5.29.0, 5.30.0, 5.31.0, and now 5.32.0
Weasel9.33.0, 9.34.0, 9.35.0, 9.35.1, 9.35.2
JasperFx2.69.1 through 2.75.2
Fisher1.5.0 through 1.13.0

And Polecat 5.32 will be released by the time you read this.

And the big themes:

  1. An Entity Framework Core sweep through Wolverine and Weasel, touched off by a set of sharp bug reports — I wouldn’t expect any of the bugs we address to impact many people, but I’d like the number of EF Core features not supported or features we don’t support well to be essentially zero
  2. Vector, full text, and hybrid search across Marten, Polecat, and Fisher, all behind one shared API – this is to support our forthcoming agentic memory tool “Stoat”
  3. Conjoined multi-tenancy tightened up and held to a shared compliance suite on every store. This was mostly a testing effort, but there were some EF Core related issues that helped spawn that work. It did find some issues with Polecat and Fisher when we did more compliance testing against Marten behavior to the younger tools
  4. Error messages that tell you what to do instead of just telling you something went wrong – I wrote about that previously in our new AI Skills 1.14 release notes
  5. Idempotency and agent distribution improvements in Wolverine, a lot of it from the community – Some things are just flat out hard. Gulp.

The EF Core Sweep

This one started with a trio of excellent issue reports from Ali Yuksekkaya against Wolverine’s EF Core integration:

  • Conjoined EF Core tenancy was ignored in the Lightweight transaction mode, so a message could be written under the wrong tenant and HTTP cascades skipped the outbox (GH-4611)
  • Conjoined tenancy allowed detached updates and deletes to touch rows that belonged to another tenant (GH-4612)
  • Returning Update<T> or Store<T> from a handler saved nothing at all for an entity the DbContext wasn’t already tracking (GH-4613)

Those got fixed, but reports like that are a signal that nobody had been looking hard enough at that corner of the code, so we went looking. The resulting sweep landed as a wave of Wolverine pull requests this weekend:

  • Lightweight EF Core message handlers now get a real transactional outbox. Before, Lightweight mode quietly meant no outbox enlistment, no domain event scraping, and no idempotency check
  • Domain event envelopes scraped out of the DbContext are now flushed before the Eager transaction commits. This was a leftover from an earlier fix where the scraped envelopes were tracked after the only SaveChanges() call and never persisted
  • Wolverine’s conjoined tenant query filter now composes with your query filter instead of replacing it. EF Core 9 and EF Core 10 behave differently here (EF 9 discards the earlier filter, EF 10 throws), and we now cover both
  • A new EfCoreOps family of declarative side effects for ExecuteUpdate, ExecuteDelete, raw SQL, and bulk inserts. These force the Eager transaction mode they need and scope themselves to the current tenant
  • A conjoined tenancy test battery that now runs against Marten, Polecat, and Fisher backed message stores
  • Closing several test coverage holes, including owned, complex, and JSON mapped models end to end

::: warning One of these changes is technically breaking. If you’re using AutoApplyTransactions() and a single handler could be claimed by two persistence providers (say, it takes both a DbContext and a Marten IDocumentSession), Wolverine used to silently apply no transaction at all. It now fails loudly at startup and tells you how to resolve it (GH-4631). If your application hits this, it had a real bug that this change is surfacing. :::

The sweep reached down into Weasel as well, which is where our EF Core schema migration support lives. Ali also reported that EF Core batched queries silently returned incomplete entities for owned, complex, and JSON members, and then contributed a follow up pull request to prepare each batched query once while keeping the provider’s parameter types intact. On top of that, Weasel now:

  • Materializes EF Core batched queries through EF Core itself
  • Carries database indexes that EF Core can’t model as their own DDL
  • Maps the columns of table-split complex properties, which were previously omitted and then dropped by CreateOrUpdate
  • Never drops a column from an EF Core derived table just because the model doesn’t happen to declare it

Marten, Polecat, and Fisher also all fixed the same bug where an EF Core backed projection leaked the DbContext it created per batch. Thanks to wpei-infotrack for reporting the Marten version of that one, which turned out to be a leaked PostgreSQL connection per batch in the async daemon.

Vector, Full Text, and Hybrid Search Everywhere

The other big feature push was around search. JasperFx now has a shared, store-neutral vector and hybrid search surface in JasperFx.Events.Vectors, and all three of our document stores implement it:

  • Marten.PgVector moved onto the shared contracts with scored search, HNSW index declarations, and hybrid search using reciprocal rank fusion over PostgreSQL’s ts_rank and the vector leg. Marten also now warns you when a full text search falls back to an unindexed, whole document scan
  • Polecat picked up vector search on SQL Server 2025’s native VECTOR type, a Polecat-owned full text inverted index with LINQ operators and BM25 scoring, prefix search, and hybrid search on top of both
  • Fisher got hybrid search and embeddings produced from an event stream

Because they all implement the same contract, there’s now a DocumentSearchCompliance suite in JasperFx that holds all three stores to the same behavior. As usual, the first real run of that suite found defects in the suite itself as well as in the stores, which is exactly what it’s for.

Polecat 5.32

Polecat 5.32 is the release that rolls up the last few days of work, and it’s largely about parity with Marten and about multi-tenancy:

  • Conjoined document tenancy sweep. Every document shape is now tested in both directions, and conjoined tenancy now also holds on the event, vector, and partition onboarding paths
  • Raw SQL in IBatchedQuery, bringing batching up to parity with Marten
  • Strongly typed identifiers are now assigned onto a live aggregated aggregate, just like Marten does
  • Document indexes, computed columns, and foreign keys are now modeled as Weasel schema objects, which means db-dump finally reproduces the full configured schema
  • Every tenant database is described in the store’s usage descriptor for a multi-tenanted store, which matters for CritterWatch
  • Event store diagnostic reads answer “no results” rather than throwing when the schema was never applied or has drifted
  • Adoption of JasperFx 2.75 and Weasel 9.35

Earlier in the window, Polecat 5.31 also made startup migrations take a real cross-process lock through sp_getapplock and routed the last few hand-escaped SQL construction sites through a shared escaping helper.

Multi-Tenancy, Everywhere

Multi-tenancy was a recurring thread through all six repositories:

  • JasperFx now has conjoined document tenancy compliance tests, and Marten, Polecat, and Fisher all enrolled
  • TenantIdStyle is now applied consistently. Marten applies it at every boundary that stamped or keyed on the raw tenant id, and Wolverine now normalizes Envelope.TenantId through it so that the stores reading that value write the right tenant
  • There’s a new canonical DisabledTenantException in JasperFx. All the stores and Wolverine now refuse a disabled tenant with that exception instead of reporting “Unknown tenant id”
  • IEventStore.OpenReadOnlyEventStore(tenantId) is now tenant aware, so the read-only tier is actually reachable in multi-tenanted systems
  • Marten and Fisher both fixed bugs with the diagnostic and explorer reads that CritterWatch depends on, including one in Marten where a read against an unknown tenant under sharded tenancy could provision a new tenant and run DDL

Error Messages That Name the Remedy

I spent a chunk of the last two weeks going through the exception messages across the stack, asking one question of each: does this tell the user what to do next? A lot of them didn’t. That turned into a wave of small pull requests:

  • Wolverine saga failures, handler discovery, missing aggregates, oversized Azure Service Bus messages, mismatched RabbitMQ queue declarations, missing Redis streams, missing HTTP transport clients, and SNS configuration problems all name the remedy now. Named connection strings are validated in one pass at startup. The SignalR transport fails the host start if the hub refuses the connection. Wolverine.HTTP gets a one-line opt in for mapping concurrency failures to a 409 ProblemDetails, and an unknown tenant id maps to a 404 instead of a 500
  • Marten stream identity mismatches, stream collisions, LINQ refusals, and the rich append concurrency exception all got clearer
  • Weasel decodes sp_getapplock failures, translates database permission failures into a typed exception, and now warns before AutoCreate.All drops and recreates an object
  • JasperFx lifted canonical ArchivedStreamException, DisabledTenantException, and stream exceptions so that all three stores throw the same types with the same guidance

On a related note, Marten 9.39 includes two SQL injection fixes, for GroupBy() HAVING comparison operands and for full text search regConfig values on every sink, not just the WHERE clause. If you’re on an older 9.x version, please upgrade.

Wolverine

Besides the EF Core work above, here are some of the highlights in Wolverine:

  • Capacity aware agent assignment. Michael Harris contributed per-node capacity ceilings for agent distribution (GH-3959), so one node dying no longer pushes its entire share onto the survivors. Anne Erdtsieck filed the original issue, and also contributed a fix for group affinity placement during blue/green deployments. There’s new documentation for the whole thing
  • Transactional deduplication. Wolverine’s deduplication claim can now ride the native Marten, Polecat, or Fisher transaction. Laurence Gillian reported that an HTTP deduplication claim survived a non-2xx response and turned legitimate retries into false duplicates, and that’s fixed too
  • An Oracle external table transport contributed by Travis Kirke, along with a fix for the Oracle durability agent’s incoming message recovery
  • Kafka replay fixes from Marko Lahma
  • GCP Pub/Sub now shares one subscription across nodes by default, thanks to a report from bittercoder about duplicated messages
  • Topology scoped message grouping rules, from a request by Anne Erdtsieck
  • Recurring schedule operability with occurrence attribution and a manual trigger, and a fix for non-UTC recurring schedules. Both came from issues filed by Babu Annamalai
  • OpenTelemetry parenting fixes. Recurring messages, inline receivers, and Wolverine’s internal agent loops no longer inherit whatever Activity happened to be current when they were started. Marten had a similar fix for spans being re-parented to their grandparent, reported by bohdan-hukivskyi. Open Telemetry sometimes has some weird behavior in terms of how parents are tracked. I expect or hope this will help the CritterWatch graphing of Otel spans from Wolverine
  • Tore Hammervoll fixed TypeLoadMode.Static so a handler chain finds its pre-generated type by full name instead of scanning exported types per chain
  • Two concurrency fixes reported by Marcin Aumiler: delayed sends to a partitioned PostgreSQL queue could be deleted without ever being handled, and the listener collection could be corrupted when agents started in parallel

Marten

Other than the search work, multi-tenancy, and messages, Marten had a lot of community driven fixes:

  • Anne Erdtsieck fixed the outer projection of GroupJoin/SelectMany and GROUP BY rendering over a join, made the projection batch fault properly when an operation can’t be configured, and made an unprovisioned event store answer “nothing” for its progression and dead letter tables
  • Erik Shafer fixed patched documents and replaced events to be stamped with the session’s actual instant (reported by BaerMitUmlaut)
  • vpetrevski routed QuickWithServerTimestamps stream starts through mt_quick_append_events to avoid sequence gaps
  • Mark van der Dam fixed quoting of duplicated field column names
  • Raymond Masciarella made partitioned stream archiving idempotent
  • Arnel Robles corrected the pgvector docs and reported two async daemon bugs in the skip-ahead loader and progression writes
  • tychomensing-topicus reported a Select() projection problem with absent JSON keys

Weasel

Besides the EF Core work, Weasel got two nice community contributions. Joel Reinford made SQL Server migration scripts runnable under sqlcmd and safe to re-run, and Jaedyn moved us onto the patched advisory lock. Anne Erdtsieck also contributed a change to let the schema delta decide when an index needs a concurrent build.

JasperFx

JasperFx is just a foundational shared library, but a lot happened there:

  • The shared vector and hybrid search surface described above
  • A store-agnostic StubEventStream<T> for unit testing event sourced handlers, with documentation on all three stores
  • The @jasperfx/event-model-vue renderer moved into the JasperFx repository, next to the Event Model descriptor it draws, and the Event Model now handles services that host more than one model
  • Hardening the aggregate source generator, including an opt-in build-time assertion that the generator is actually attached
  • Andre Vieira fixed codegen test failing for every message handler since Wolverine 6.37, and Alan Klimowski fixed a code generation frame ordering issue (and a duplicated service declaration in Wolverine.HTTP)

Fisher

Fisher went from 1.5 to 1.13 in two weeks. Beyond the search work, Fisher now creates its event store tables on first use, supports directory tenancy on Windows, validates tenant ids before turning them into file names, fixes decimal comparisons in LINQ, and requires the source generator with a smoke test of the packed package. Kebin contributed a fix for enlisted sessions with an inline projection registered.

Thank You

The Critter Stack only gets this good because people use it hard (thanks?), tell us when it breaks with actionable error reports, and increasingly send in the fix too. Thank you to everybody who contributed code over the past two weeks:

Ali Yuksekkaya, Anne Erdtsieck, Michael Harris, Travis Kirke, Marko Lahma, Laurence Gillian, Tore Hammervoll, Alan Klimowski, Erik Shafer, Andre Vieira, Jakob Tikjøb Andersen, Joel Reinford, Jaedyn, Mark van der Dam, Raymond Masciarella, Arnel Robles, vpetrevski, Kebin, Marcin Aumiler, Jorge L. Torres M, and Rayan-and-beyond.

And to everyone who filed a good issue with a reproduction, including ArieGato, michielpeeters, raypet-visma, AndreiKopylov, framos-varajo, syserr500, BaharAtNode, Petteroe, zxjon22, r0ss88, bittercoder, BaerMitUmlaut, wpei-infotrack, bohdan-hukivskyi, tychomensing-topicus, and Babu Annamalai: those reports are what drove a lot of this.

No, seriously, the Critter Stack community is as far as we can tell far, far about average for OSS projects in terms of how helpful the community is to help drive and improve the tools.

Leave a comment