Skip to content

Bump the thessera-dependencies group with 20 updates - #18

Open
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/nuget/thessera-dependencies-02cc3507d0
Open

dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/nuget/thessera-dependencies-02cc3507d0

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Oct 16, 2026

Copy link
Copy Markdown
Contributor

Updated coverlet.collector from 10.0.1 to 10.1.0.

Release notes

Sourced from coverlet.collector's releases.

10.1.0

Improvements

  • Publish Microsoft.Testing.Platform coverage messages from coverlet.MTP #​2019
  • Implement dynamic exclusion filters for assemblies (Coverlet.MTP) #​1946
  • Replace legacy .sln files with modern .slnx format #​1966
  • coverlet.console: add trace diagnostics and actionable warnings for instrumentation/hit/empty-result failures #​2005
  • Relax auto-property skip logic and improve coverage for records #​1941

Fixed

  • Fix coverlet.MTP does not collect coverage on the .NET Framework portion of a large project #​1980 #​1967
  • Fix Regression in branch coverage for lambda expressions #​1938
  • Fix When using "is" with "or" in pattern matching, branch coverage is lower than normal #​1979
  • Fix silent zero coverage on .NET Framework since 8.0.0 #​1985 by @​tobiwae
  • Fix Race condition between ProcessExit hit-file write and out-of-proc coverage read causes EndOfStreamException #​1987 #​1988 by @​bkoelman
  • Fix Regression TypeInitializationException when targeting .NET Framework - Could not load type 'System.Collections.Concurrent.ConcurrentBag #​2010
  • Fix use --config-file CLI arg in coverlet.MTP #​2030 by alexthornton1
  • Fix silently empty coverage for shared-framework assemblies missing from compileLibraries #​2032 by @​Eljees

Diff between 10.0.1 and 10.1.0

Commits viewable in compare view.

Updated Marten from 9.30.0 to 9.50.3.

Release notes

Sourced from Marten's releases.

9.50.3

The Native AOT and hard-delete release. One new feature on the event store, two schema-naming fixes that could not be worked around, and three behaviour changes — read those before upgrading.

Note on these notes. They were rewritten after publication. 9.50.3 was cut as the JasperFx 2.90.1 dependency bump, but five pull requests had merged in the minutes before the tag, so the tag carried all of them too. The original notes described only the dependency bump and said no code change was needed. Everything below is what 9.50.3 actually contains; nothing about the published packages changed.

New

  • TombstoneStream — a hard stream delete (#​5673, #​5674). session.Events.TombstoneStream(id) queues a single-statement delete of a stream's tag rows, its events and its stream row — plus the mt_streams_identity row under strict identity enforcement — scoped to the session tenant under conjoined tenancy. The event sequence is never touched, and projected documents are deliberately left alone. Queued until SaveChangesAsync, like every other write.

    It is on the shared IEventStoreOperations contract as a default member that throws NotSupportedException, so a store without a hard delete stays source-compatible, and the shared StreamTombstoningCompliance suite now runs against Marten.

  • DCB tag types can be registered for Native AOT (#​5649, #​5674, #​5676). RegisterTagType<TTag>() discovers the tag's inner type by reflecting over its members and closes a generic registration at runtime, neither of which a native image can do — so a store with a DCB tag type could not even be configured under AOT.

    opts.Events.RegisterTagType<StudentId, Guid>(x => x.Value, "student")
        .ForAggregate<StudentCourseEnrollment>();

    Naming the inner type closes the generic at compile time and the unwrapper replaces the reflection. The suffix is optional and defaults to the tag type's own name, so for a tag the inferring overload could register, this one registers the same inner type and the same table. It works identically under a JIT.

    The inferring overloads are now annotated [RequiresUnreferencedCode]/[RequiresDynamicCode], and — because an analyzer warning only reaches an application that is itself AOT-analyzed, while a store configured from inside a library gets nothing — a failure at runtime now throws naming the exact line to write, keeping the original exception as its inner exception. Automatic tag registration from projection identities logs a warning naming the same line, including the projection's ForAggregate<T>().

    With explicit registrations, tagging, tag rules, EventTagQuery, EventsExistAsync, AggregateByTagsAsync and FetchForWritingByTags into a [BoundaryAggregate] all run from a native binary. A CI job publishes and runs them.

Fixed

  • A custom foreign key name is no longer discarded under conjoined tenancy (#​5672, #​5678). Moving tenant_id to the front of a document foreign key regenerated the key's name unconditionally, so a name set through the foreignKeyConfiguration callback was thrown away. That is what made the identifier-length failure unfixable: the exception tells you to override the database object name, and overriding it had no effect. A generated name is still regenerated; a chosen one survives, as does the name of a ForeignKey added by hand.

  • Archiving a tagged stream no longer fails with a foreign key violation (#​5675, #​5679). With UseArchivedStreamPartitioning on and any DCB tag type registered, ArchiveStream came back as 23503. Each mt_event_tag_* table is partitioned by is_archived and carries a composite foreign key to mt_events; mt_archive_stream moved the events between partitions without moving the tag rows that referenced them. It now moves them, in the order that keeps both sides of a non-deferrable key consistent at every statement.

  • AllObjects() no longer applies the Hi-Lo DDL (#​5665, #​5670). Describing the schema provisioned part of it. The describing seam is now separate from the applying one, and is proven offline against an unreachable host.

  • A failed Rebuild-mode run can be restarted and finished from its committed progress (#​5668, #​5669).

Performance

  • A commit that only starts streams no longer queries for a snapshot that cannot exist (#​5671, thanks @​lahma). An inline single-stream projection asks storage only for the ids of streams being appended to, so a start-only commit passed an empty id array — and still paid a round trip on id = ANY($1) that could only return no rows, plus a second connection with the default UseIdentityMapForAggregates = false. The same guard removes a wasted round trip from an async daemon batch whose slices all hit the aggregate write cache.

Behaviour changes

  1. An empty DCB tag table suffix is refused. RegisterTagType<T>("") used to be accepted and named mt_event_tag_ — a real table that every tag type registered that way would share. Pass a suffix, or null for the tag type's own name.

  2. A tag type that cannot be read now throws InvalidOperationException, with the original InvalidValueTypeException as its inner exception, and a message naming the explicit registration. This happens whether or not dynamic code is available, because the same exception means "not a single-value wrapper" under a JIT and "the members were trimmed away" in a trimmed image, and nothing at runtime tells those apart.

  3. Document foreign key and index names longer than 63 characters are shortened with a deterministic hash suffix, the same treatment the document table's primary key already got. If you raised PostgreSQL's NAMEDATALEN and have a foreign key or index whose generated name is between 64 characters and your own limit, that object will be renamed and you will see it in a migration diff.

Known, not fixed

DCB tag queries return archived events, where every other event query excludes them by default — #​5677. It matters most for EventsExistAsync, which is what a consistency boundary assertion is built on: an archived stream still answers yes. Independent of storage mode and of archived-stream partitioning.

Dependencies

... (truncated)

9.50.2

A maintenance release. Nothing in a shipped Marten package changed, and nothing here requires a configuration or code change.

Dependencies

  • The NuGet vulnerability audit is clean (#​5666). Marten's shipped packages were never affected by GHSA-846f-f764-76mx (CVE-2026-84008, System.Security.Cryptography.Pkcs). Npgsql doesn't bring that package in. Every finding was in the Nuke build tool or the samples:
    • The build tool now uses patched System.Security.Cryptography.Pkcs, System.Security.Cryptography.Xml and NuGet.Packaging.
    • The OpenTelemetry packages used by the EventPublisher sample and the Aspire sample moved from 1.8.x to 1.19.x.

The same advisory does reach Polecat and Weasel's SQL Server and Oracle packages, through their database drivers. Those are fixed in Polecat 5.40.2 and Weasel 9.45.1.

9.50.1

A patch release with one addition, for Wolverine's agent assignment. Nothing here requires a configuration or code change.

Event store

  • IEventStore.TryDescribeDatabasesAsync() is answered straight from the tenancy (#​5664). JasperFx 2.87.1 adds this narrower call, the database half of TryCreateUsage(). It returns each database's server, name and tenant ids without reading the highest event sequence or describing every projection, subscription, event type and tag type. Wolverine 6.50.0 asks for it on every leader heartbeat, for every store, where it used to build the full usage (JasperFx/wolverine#​5002). It follows on from #​5652, so thanks again to @​uniquelau for a report that came with the heartbeat maths already done.

Dependencies

  • JasperFx 2.87.1.

9.50.0

A bug-fix release. Nothing here requires a configuration or code change, but three things change what an application observes — read the box below before upgrading.

Observable changes

  • TotalResults changes value for Stats() + Include() + Select() + Take(). It was reporting the size of the page; it now reports the number of matches (#​5654). If you compute page counts from it, those counts change — to the correct ones.
  • A new warning may appear at startup if tag-type auto-discovery cannot register a projection's identity type. That condition is not new; it was previously silent (#​5649).
  • db-patch drop files no longer DROP ... CASCADE every schema — they roll back only what the patch touched (Weasel 9.45.0, weasel#​706). This is the one to look at if you apply drop files in an environment you care about.

LINQ — Include() joined on the queried document's own id

Three separate defects in the same corner, found by pulling on the first one. An Include() joined on the queried document's own id builds a temp table, and that temp table was missing the id it is joined on.

  • #​5650 — failed with 42703: column d.id does not exist in a query session when the queried document had any duplicated field. Lightweight, identity-map and dirty-tracked sessions were unaffected. Thanks to @​Blackclaws for the report and the fix (#​5651).
  • #​5653 — the same 42703, in a query session, when the query also called Stats(). Unlike #​5650 this did not depend on a duplicated field; Stats() wraps the select clause, which hid it from the check that puts the id back.
  • #​5654 — Stats() with an Include() and a Select() projection reported TotalResults as the size of the page rather than the number of matches, whenever Take() was used. No exception, just a wrong number, on every session kind. The temp table already held the correct count — PostgreSQL evaluates a window function before LIMIT — but re-applying the projection emitted a second count(*) over() against the already-truncated temp table.

If you page a projected grid with Stats() and Include(), #​5654 is the one to read.

Event store

  • #​5652 — a store with no event types and no projections never has its event tables migrated, so building a usage descriptor asked Postgres for a table that was not there. Marten swallowed the failure, but Postgres logged an ERROR for every attempt; under UseWolverineManagedEventSubscriptionDistribution the leader does this for every store on a ten-second heartbeat, which is tens of thousands of server-side ERROR lines a day for a store that can never hold an event. Marten now asks the catalog first, so nothing reaches the server log. Thanks to @​uniquelau for a report that came with the heartbeat maths already done.
  • #​5658 — IEventStore.FindDatabaseForTenantAsync, so a caller waiting on or querying a single tenant touches only that tenant's database instead of all of them. Goes through ITenancy.TryFindDatabase, never FindOrCreateDatabase, so a lookup cannot bring an unknown tenant into existence.

Native AOT

  • #​5649 (partial) — a DCB [BoundaryAggregate] fetch failed natively with MissingMethodException, because EventGraph.Build<TDoc>() closed SingleStreamProjection<,> at runtime even though both type arguments were known at compile time. And tag-type auto-discovery swallowed the AOT failure in the same breath as a benign one, leaving a store running with the tag type quietly absent; it now says so. Verified by publishing and running the native binary, not by a clean publish.

    Still open: RegisterTagType<TTag>() closes its own open generic and remains broken under Native AOT. That needs a reflection-free construction path in JasperFx.Events plus a new overload — see the discussion on #​5649. Thanks to @​skoging for an unusually thorough report, with native runs and three costed alternatives.

Dependencies

  • Weasel 9.45.0 — db-patch drop files no longer drop every schema with CASCADE; they roll back only what the patch touched (weasel#​706). This changes what an existing drop file does. Primary keys can also now be added to and removed from existing tables (weasel#​707).
  • JasperFx 2.87.0 — across all five packages.

Known issue

Stats() with Include() and a Select() projection is fixed here, but note that the Include() itself still follows the page, not the match set — it reads from the same truncated temp table. Only the statistics column outlives the Take(). That is existing, intended behaviour; it is called out because #​5654 makes the distinction visible for the first time.

9.49.0

The LINQ projection surface works under Native AOT

The theme of this release. #​5624 came in as "a projection into an anonymous type cannot be
materialized under Native AOT". Publishing Marten.AotRuntimeSmoke natively and running it showed
something much wider: every Select() projection into a new object failed from a native binary,
a named DTO exactly as hard as an anonymous one
, and it failed before the serializer was ever
reached.

MissingMethodException: No parameterless constructor defined for type
'Marten.Linq.SqlGeneration.SelectDataSelectClause`1[YourDto]'

A projection is parsed out of an expression tree, so the parser only has selectExpression.Type and
closed its select clause over that Type with MakeGenericType + Activator.CreateInstance — the
defect class of #​5328, one step later in the pipeline. Only a bare string scalar survived, because
its clause happens not to be generic, which is what made the surface look healthy.

Shapes that threw from a native binary before this release and run now:

  • Select(x => new Summary { ... }) — a named DTO
  • Select(x => x.Lines) — a collection member, and the element type of a SelectMany(x => x.Lines)
  • Select(x => x.Amount) for every framework scalar type, decimal? and the other nullable ones included
  • Select(x => x.Status) and MaxAsync(x => x.Status) for an enum of your own — see the one line below
  • GroupBy(...).Select(g => new Tally { ... })

The fix is SelectClauseResolvers, which does for projected types what #​5328's
DocumentStorageResolvers does for document types: a registry populated from
MartenLinqQueryable<T>'s constructor, which every projected type reaches because
Queryable.Select<TSource, TResult>() asks the provider for CreateQuery<TResult>(). The reflective
path remains the fallback wherever a JIT is available, so nothing changes for JIT-mode apps.

One line for an enum or struct of your own

Marten's scalar select clause is declared where T : struct and the registration point above
constrains its own T to notnull, so Marten can root the scalar types it renders itself —
decimal, Guid, DateOnly and the rest need nothing — but not one out of your assembly:

opts.Schema.For<Order>();
opts.RegisterScalarType<OrderStatus>();   // new in 9.49.0; no-op under a JIT

Only bare scalar projections need it. Projecting the same enum as a member of a named type, and
filtering on it, never did. When the registration is missing, the failure now names that exact call
instead of reporting a missing parameterless constructor on an internal generic.

An anonymous projection is refused with the remedy

With a source-generated JsonSerializerContext assigned to
... (truncated)

9.48.0

Document diagnostics: filter and order by document properties (jasperfx#​869, #​5631)

IDocumentStoreDiagnostics.QueryDocumentsAsync now applies DocumentQueryOptions.Where / OrderBy / Arguments instead of refusing them. The text goes onto Query<T>() through JasperFx 2.84.0's Dynamic LINQ helper, so member casing, enum storage, duplicated fields, conjoined tenancy, soft deletes and hierarchies are Marten's own; only the SELECT list is swapped for the diagnostics read's columns, so the JSON comes back byte-exact with its metadata, and the total counts the filtered rows. Operator text is never spliced into SQL, and the read issues no DDL.

Measured against a LINQ-to-objects oracle over 83 shapes and three storage configurations; every shape is either right or refused:

  • Refused: range comparisons on, and ordering by, an enum stored as its name (string serializer storage or a varchar duplicated field), and <> / a negated comparison on a nullable member unless the text says what null should do (JasperFx's SqlNullSemantics). Date-part members and inline in (…) lists are refused honestly by the provider.
  • Fixed rather than refused: criteria orderings put nulls first ascending, as .NET does (PostgreSQL defaults to last), and DateTime arguments compare correctly against timestamp without time zone locators.
  • String ordering follows the database's collation (note: Alpine images use musl and sort bytewise).
  • Under Native AOT, criteria are refused.

Fixes

  • Grouped projections translate Count / LongCount predicates through the Where parser, with null semantics and bound parameters (#​5625).
  • A quick-mode StartStream that loses a race for the same stream id now throws ExistingStreamIdCollisionException instead of a raw 23505, so QuickWithServerTimestamps behaves like Rich and Quick (#​5628).

Dependencies

JasperFx 2.84.0

9.47.0

The FetchForWriting / FetchLatest consistency release, plus a bounded async-daemon shutdown.

Read the behaviour changes before upgrading. Two of them can change how your projections and
subscriptions behave on every deployment, and two more refuse a batch shape that used to be accepted.

⚠️ Behaviour changes

Subscription and projection drains are now bounded by StopAndDrainTimeout ([#​5610])

Via JasperFx 2.81.0 (jasperfx#​953 and jasperfx#​983). When a shard stops — host shutdown, or reassignment
to another node — the page in flight finishes, pages still queued are skipped and resumed by the next
owner from committed progression, and a page still running at Projections.StopAndDrainTimeout
(default 5 seconds) is cancelled with its transaction rolled back.

This fixes a real failure: a node that loses a shard's lock no longer keeps applying its backlog
alongside the new owner, an overlap that could end in ProgressionProgressOutOfOrderException and a
Stopped shard. Graceful shutdown is also bounded now, rather than waiting out the whole backlog and
potentially overrunning a Kubernetes termination grace period.

If your projection or subscription pages take longer than 5 seconds, raise the timeout:

opts.Projections.StopAndDrainTimeout = 30.Seconds();

Otherwise those pages are cancelled and re-processed on every shutdown and rebalance. Delivery was
always at-least-once, so no guarantee changed, but duplicates become much more likely — and side effects
performed outside the Marten session (HTTP calls, sends that bypass the transactional outbox) are not
rolled back with the page. Note this applies to async projections as well as subscriptions; the upstream
note covers only subscriptions and is out of date.

A batched FetchLatest or expected-version FetchForWriting must be first in its batch ([#​5604])

For an aggregate projected with ProjectionLifecycle.Async only. Both now bracket their reads in
begin transaction isolation level repeatable read read only … end so those reads share one snapshot,
and PostgreSQL accepts that only as a transaction's first statement. So:

  • batch.Events.FetchLatest<T>(id) and batch.Events.FetchForWriting<T>(id, version) have to be the
    first operation in their batch, and
  • neither can share a batch with a FetchForExclusiveWriting.

Both are refused at the enlisting call, naming the call to move, rather than surfacing later as a bare
25001. Inline and Live aggregates are unaffected, and inside a caller-owned transaction no bracket is
emitted so neither rule applies. If you batch a FetchLatest behind a Load, move it to the front.

New SnapshotRaceException from a batched exclusive fetch ([#​5613])

A batched FetchForExclusiveWriting on an Async aggregate now throws Marten.Exceptions.SnapshotRaceException
in the rare case where the daemon commits a snapshot between that batch's two reads. Retry the batch, or
fetch outside a batch — outside a batch Marten retries for you and you will never see it.
... (truncated)

9.46.0

Mostly a resiliency and Native AOT release. Five behaviour changes — the retry ones are the most likely to be noticed, because Marten now surfaces failures it used to absorb.

Behaviour changes

A command timeout is no longer retried on reads (#​5566, #​5580). An attempt that timed out has already spent the whole CommandTimeout, so retrying it three times held the caller for four timeouts instead of one. This covers both shapes: Npgsql's client-side CommandTimeout, and a server-side statement_timeout, which arrives as 57014 query_canceled with no TimeoutException anywhere in the exception. Connection-pool exhaustion arrives in the same shape and so is no longer retried either — a deliberate trade, documented in the resiliency guide along with what to do instead.

Sticky, ambient and external session lifetimes now fail loudly (#​5582, #​5598), via the new SessionTransactionUnusableException. Two things used to go wrong silently. A broken connection reported ConnectionState.Closed, so Marten reopened it — onto a different backend — and handed the command the stale transaction, which Npgsql accepts; a Serializable session then quietly answered from a newer snapshot. And a command timeout inside a transaction aborted it, after which every later call returned a bare 25P02 naming neither the timeout nor the way out. For caller-owned transactions Marten records the cause and reports it but rolls nothing back — the scope is still yours.

The async serializable session builders now produce a serializable session (#​5594). OpenSerializableSessionAsync(new SessionOptions()) previously returned a session that was neither serializable nor auto-closing: it held one connection open for its lifetime with no transaction. These two methods now pin IsolationLevel to Serializable and begin the transaction eagerly. A non-Serializable level passed to them is overridden rather than silently honoured.

The async daemon spends one retry budget instead of two (#​5593). ResilientEventLoader and the QuerySession inside it were both given the same pipeline, so a handled failure ran up to sixteen times.

A retried write no longer accumulates exceptions across attempts (#​5591). The exception list was shared, and ExecuteBatchPagesAsync branches on its count — so one operation failing twice turned a rethrow into an AggregateException, making the caller's exception type depend on how many retries happened.

Native AOT

Strong-typed document ids now work (#​5589, #​5579), with one registration per document type:

opts.Schema.For<Invoice>();
opts.RegisterValueTypeId<Invoice, InvoiceId, Guid>();

Previously a single strong-typed id anywhere in the store killed the application while the first document's mapping was built. The identity path closes a chain of generics over your id type, and a native image has no instantiation for them unless something roots it; naming all three types is what roots them. Under a JIT the call does nothing — unregistered types still take the reflective path. Not yet covered: F# single-case discriminated-union ids, and .IsOneOf over a strong-typed id. The intent is for Marten.SourceGenerator to emit these registrations so the call becomes unnecessary.

Also AOT: a decimal comparison in a LINQ filter is documented with a tested workaround (#​5597) — it is the BCL expression-tree factory rather than Marten, but it only shows up from a native binary. Marten's AOT runtime gate now covers strong-typed ids and decimals, so these stay pinned.

Fixes

  • PrefixSearch quotes each word as a tsquery lexeme (#​5571, #​5568), so a search box containing &, |, !, (, : or a tab no longer produces 42601: syntax error in tsquery. Not an injection — the term was already a parameter — but the documented contract says each word is treated as a prefix, and raw input has to be escaped to honour that.
  • A quick append nulls every DCB tag slot, not just the tagged ones (#​5570).
  • AdvisoryLock shutdown release is bounded, and the vendored copy moves back to Weasel (#​5572, #​5576).
  • Three catch-and-Console.WriteLine-and-rethrow blocks removed (#​5584) — they bypassed IMartenSessionLogger and any configured ILogger, and one of them also closed a connection on the line above the one that opened it.

Dependencies

JasperFx 2.80.2 (#​5590) and Weasel 9.41.0 (#​5596, #​5576). Both carry Native AOT work of their own on the identity path — JasperFx's ValueTypeInfo wrappers and aggregate identity sources, Weasel's non-generic IIdentification facade.

🤖 Generated with Claude Code

https://claude.ai/code/session_01481eiEZg1Bv6pu4DrgPRg3

9.45.0

This one is largely the community's release. Four of the six changes below were found, diagnosed, or fixed by people outside the core team, and two of them are bugs that would have been very hard to find from the inside.

Community contributions

@​Hawxy — two LINQ regressions (#​5556). A captured nullable in a where clause was throwing: a Nullable<T> never survives boxing, so an empty one boxes to null and PropertyInfo.GetValue had no target, breaking the thoroughly ordinary where !captured.HasValue || x.Id == captured. HasValue and Value are now read off the boxed form. The same PR fixes literals in select statements surviving paging, stats, and version select clauses.

@​Hawxy — inline-to-async on an empty database (#​5561). FetchHighestEventSequenceNumber read mt_events_sequence.last_value, which PostgreSQL reports as 1 with is_called = false before anything has drawn from the sequence. SubscribeAsInlineToAsync used that as its starting position, so on an empty store the shard started at 1 against a high water mark of 0 and the agent failed with "The last committed number (1) cannot be higher than the high water mark (0)". A new store is exactly when you least want that.

@​tomasherceg — ordering after a grouped projection (#​5557, shipped as #​5563). CompileGroupBy transferred the downstream Limit and Offset but never the OrderingExpressions — even though the comment sitting over that block had claimed OrderBy was transferred for as long as it existed. A GroupBy(...).Select(...) that was then ordered came back in whatever order PostgreSQL felt like, with no order by in the SQL and no error, so a paged one took an arbitrary page.

Tomáš found it, diagnosed it, and landed the hard part — re-expressing the ordering against the projected shape rather than the document. He also flagged his own uncertainty about edge cases, which is what sent us looking and turned up two more: a scalar projection (Select(g => g.Key), Select(g => g.Count())) has no projected members, so ordering one threw instead of ordering by its single value; and OrderBySql() reported Invalid OrderBy() expression ''. Both now work. His commit is in the tree with his authorship.

@​chrisbbe — the identity probe's side effects (#​5562). The best bug report we have had in a while: a minimal repro, the stack trace, the mechanism, and a correct suggested fix.

Identity resolution handed its candidate filter to a traversal that applies it to every property and field, and only afterwards picks the id by [Identity] or by the name Id. That filter is not free to ask — for any public struct shaped like a strong-typed id it registers the type on the global PostgresqlProvider singleton and closes an open generic. So an ordinary non-identity property got remapped process-wide the moment its document or event type was mapped, turning an Optional<string> used only for a nickname into varchar for everything, LINQ included. Under Native AOT the generic construction brought startup down outright with a MissingMethodException.

Both probes now split into a pure shape test and the effectful registration; the traversal asks the cheap question and only the chosen member gets the expensive one.

⚠️ That exposed a second thing worth knowing about. RegisterValueType fills StoreOptions.ValueTypes but has never registered the Postgres mapping, so a value type used as a duplicated field rather than as an id only ever worked because the probe happened to reach it on some document. If you call opts.RegisterValueType<T>() you were doing the right thing and relying on an accident. That registration now belongs to RegisterValueType.

Performance

Batch reads are one round trip again (#​5550). JasperFx 2.79 added LoadManyAsync to IDocumentReadOperations and FetchManyForWriting to IEventStoreOperations, both default-implemented. The defaults are correct, and both make one round trip per id.

Marten has sixteen LoadManyAsync overloads and not one of them takes the token last, so the contract's (ids, token) call matched none of them and silently bound to the default. Nothing failed — the shared compliance suite loads 2,500 documents through it, 2,500 round trips at a time, passing.

before now
LoadManyAsync, 25 ids 25 round trips 1
LoadManyAsync, 2,500 ids 2,500 1
FetchManyForWriting, 10 streams 10 1

FetchManyForWriting is new public surface on Marten: one handle per id in the order asked for, a handle (not a gap) for a stream that does not exist yet, and each handle keeping its own starting version so SaveChangesAsync guards every stream it appended to and no other.

::: One lifecycle keeps the sequential shape
An aggregate projected with ProjectionLifecycle.Async is fetched inside begin transaction isolation level repeatable read read only, and PostgreSQL only accepts that as the first statement in a transaction — so two of them cannot share a batch. FetchManyForWriting detects that lifecycle and falls back to fetching sequentially: same results, same ordering, same per-stream guards, no round-trip saving. Inline, Live and Snapshot get the fast path.
:::

See FetchManyForWriting and Loading Documents by Id for the details.

Also

Dependency bumps from Dependabot: fast-uri (#​5558), brace-expansion (#​5559), dompurify (#​5560).

9.44.0

Bumps the JasperFx family to 2.79.1 and lands three fixes. Two of them change behaviour, one in a way that changes what a query returns.

Fixes

#​5549 — a nested Any() with a plain equality silently returned nothing. A sub-query is parsed against a member tree rooted at the element type, so inside Middles.Any(m => m.Bottoms.Any(...)) the Bottoms member reports d.data -> 'Bottoms' — within that parse, d.data stands for one Middle. Nothing in the member tree records that Bottoms lives under Middles; the containment filter is re-anchored when the enclosing Any() lifts it out. That re-anchoring moved the jsonb payload but left the locator pointing at the inner collection, so the query went out as

d.data -> 'Bottoms' @> '[{"Middles":[{"Name":"Bill"}]}]'

Well-formed SQL against a key the document does not have. No error, no rows.

Three shapes of the same query diverged, which is what made this hard to pin down from the outside: a sibling predicate (m.Color == Blue && m.Bottoms.Any(...)) built the containment through a different path and was always correct, StartsWith is not containment-eligible and fell to a jsonpath filter that re-rooted properly, and only the plain single-equality nested Any() was wrong.

Four more failures came out of the same defect once the generated SQL was dumped across every nesting shape:

Shape Before
the nested Any() negated, or as an OR branch, or three levels deep same wrong locator
m.Bottoms.Any(x) && m.Bottoms.Any(y) — the workaround suggested in the issue the two merged into one payload element: one of the two values was silently dropped, and a single element had to satisfy both
three levels deep ending in a value collection NotSupportedException
a value-collection Any()/Contains() beside a sibling predicate NullReferenceException

⚠️ Behaviour change. t.Xs.Any(p) && t.Xs.Any(q) now matches when two different elements satisfy the two predicates. That is what the query asks for, and it is more rows than 9.43 returned. If you were relying on the old answer, you wanted t.Xs.Any(x => p(x) && q(x)).

Diagnostics surface (monitoring consoles)

jasperfx#​932 — DocumentTypesAsync lists document sub-classes. The contract was silent on this and the stores split: Polecat listed sub-classes, Marten and Fisher did not, and a store-agnostic type picker could not tell which answer it had. Settled in favour of listing them, because every member taking a documentTypeName already accepts a sub-class name and narrows to its rows — a picker that could not offer one was hiding a capability the contract guarantees.

Marten now emits an entry per registered sub-class alongside each mapped root. A sub-class entry carries its own mt_doc_type alias — the discriminator its rows actually hold, which is the string you pass back to QueryDocumentsAsync — and the root's schema, because the root's table is where those rows live. DocumentTypeRef.RootTypeName names the root and is null for a root itself; IsSubClass reads it.

⚠️ Behaviour change. A store with document hierarchies returns more entries from DocumentTypesAsync than it did in 9.43. IsSubClass tells them apart. RootTypeName is init-only, so the positional DocumentTypeRef shape a store compiled against an older JasperFx calls is unchanged.

Event sourcing

A composite projection could intermittently pause and stop advancing (jasperfx#​938). ProjectionExecution disposed its batch unconditionally through an await using. For a composite member that batch is the parent composite's, shared by design — so the member's scope-exit disposal raced the parent's execute, which then threw ObjectDisposedException out of Block.WaitForCompletionAsync and left the shard Paused with no progression row at all.

It takes a member on that path, so the exposed shape is a composite of an aggregation plus a plain event projection; an all-aggregation composite runs on GroupedProjectionExecution, which already guarded the disposal. Intermittent because the stages run in parallel and the race can fall either way — measured at roughly one run in eight on one such composite.

If you run composite projections with a non-aggregation member and have seen a shard quietly stop advancing, this is a strong candidate. Fixed in JasperFx 2.79.1.

Dependencies

JasperFx 2.79.1. Alongside jasperfx#​932 and jasperfx#​938 above: jasperfx#​930 adds LoadManyAsync to IDocumentReadOperations and FetchManyForWriting to IEventStoreOperations, jasperfx#​931 default-implements the 2.77 IDocumentStoreDiagnostics members so a store built against an older JasperFx still loads, and jasperfx#​933 ratchets abstract interface members so contract growth stays deliberate.

The new members arrive default-implemented and Marten has not yet overridden FetchManyForWriting / LoadManyAsync — the defaults are correct but call the single-id member in a loop. Batching them against Marten's existing batch-query API is tracked separately.

9.43.0

Bumps the JasperFx family to 2.78.0 and lands four issues, one of them a data-corruption fix worth reading even if you skip the rest.

Fixes

#​5539 — a zero-event quick append could permanently wedge a stream. mt_quick_append_events called with zero events read mt_streams.version without a lock and ended with a blind write-back, so under READ COMMITTED it could overwrite a concurrent appender's version and leave the stream row below its own events. Every later append then computed a version that already existed and failed on pk_mt_events_stream_and_version — permanently, with no recovery short of repairing the row by hand. A non-empty append could never do this: its first insert collides on that same primary key and rolls back, which is why only the empty call was exposed. The write-back is now skipped when nothing was appended, and uses greatest() so it can never lower a version whoever calls it.

Field context: a production console on 9.32.0 sat at mt_streams.version 608676 against max(mt_events.version) 608678 for 42 days, roughly 28 failed appends a minute. If you have a stream failing every append on that constraint, this is likely why.

#​5541 — ShardState.LastUpdated, liveness rather than progress. Sequence cannot distinguish caught up with nothing new from no longer maintained: both hold the same number forever. The progression row's last_updated now reaches ShardState and ProjectionProgressRow, and — the part that needed a product change — an idle high-water cycle now restamps the HighWaterMark row. Previously an idle cycle issued no SQL at all, so that stamp froze at the last real mark advance and read as "abandoned weeks ago" on a perfectly healthy store. The restamp never touches last_seq_id, so it cannot nudge the mark.

Diagnostics surface (monitoring consoles)

#​5543 — IDocumentStoreDiagnostics adopts the defined contract (jasperfx#​870). Five behaviours were previously "whatever the store happened to do", and Marten was on the wrong side of all five:

Before Now
Soft deletes returned as live excluded unless asked; a load by id returns the row flagged
Hierarchies every sub-class in the table filtered to the requested type; each row reports its own
Missing tenant all tenants the default tenant
"" tenant a tenant literally named "" the default tenant
Ids id::text = @​id converted to the stored identity type — uses the PK index, matches an upper-case Guid

Also: LoadDocumentAsync returning StoredDocument with metadata, populated DocumentQueryResult.Documents, a new IDocumentStoreDiagnosticsWriter (save-from-JSON and delete through a normal session, with an expected-version guard), DocumentStoreUsage.SerializerCasing, and structured index / duplicated-field descriptors. Query criteria Marten cannot yet translate are refused rather than silently ignored.

#​5544 — DocumentQueryOptions.AllTenants. The explicit replacement for the all-tenants read that #​5543 removed. Conjoined tenancy reads every tenant, ordered by tenant then id so paging cannot repeat a row. A store spreading tenants across databases refuses rather than quietly returning only the default tenant's rows.

⚠️ Behaviour change for diagnostics consumers: a null or blank TenantId now means the default tenant, not every tenant. If you relied on the old behaviour, set AllTenants = true.

Also

#​5542 — asynchronous aggregate events are ordered by stream sequence.

Internals: a diagnostic document read now resolves a tenant's database through TryFindDatabase rather than FindOrCreateDatabase, so asking a read-only question about an unknown tenant can no longer provision that tenant.

9.42.0

Mostly fixes, and two of them only became visible once the one underneath them was gone. Nothing here requires action on upgrade unless you use IsolationLevel.RepeatableRead or above, or batch a FetchForWriting for an Async-snapshot aggregate.

Migrations settle again on a long schema name

#​5536. ApplyAllDatabaseChangesOnStartup() against an already-migrated schema could fail with:

42710: constraint "fkey_mt_events_stream_id" for relation "mt_events" already exists

The foreign key was not the problem. PostgreSQL truncates identifiers at 63 characters, so a longer DatabaseSchemaName was stored truncated while Marten went on querying for the name it had asked for — found none of its objects, concluded they were all missing, and re-issued DDL the server resolved against the truncated schema, where they already existed. The first apply passed and the second failed, with an error pointing nowhere near the schema name.

DatabaseSchemaName is now truncated to 63 characters so the two sides agree. This does not move anyone's data: the server was already truncating it, and Marten now simply uses the name the server actually stored. Two distinct long names sharing their first 63 characters still collide, exactly as they already do in PostgreSQL.

The apply-changes lock waits long enough to be useful

#​5529. The advisory lock guarding startup migrations retried on a fixed 50 / 100 / 250ms ladder — about 400ms to win it. That is ample for the multi-replica rolling deploy it was designed for, where the loser only has to outlast a peer's finished migration. It is not enough when more than one store shares a database inside one host, where the loser has to outlast a migration that is still running: it failed startup, and the message's own advice (ResourceMigrationFailureMode.ContinueOnFailures) would have started the application against a half-migrated schema.

New knob, defaulting to 10 seconds:

opts.ApplyChangesLockTimeout = 30.Seconds();   // many stores on one database, or a large schema

This was held back until it could be measured. While weasel#​659 was unfixed the lock was stranded by any migration that threw, and a longer wait provably changed nothing. Weasel 9.37.0 releases it on the failure path, and only then did the residue show up as a genuine race worth budgeting for.

⚠️ No write retry from RepeatableRead upwards

#​5528, completing what 9.41.0 started. Marten's retry replays the operations it has already computed, not the application code that computed them — and from RepeatableRead up, the session's transaction is pinned to one snapshot. So any successful replay commits decisions derived from a snapshot that no longer exists. That is exactly the silent lost update 9.41.0 fixed for 40001, and 40P01 (deadlock) had the same flaw for the same structural reason.

The write-retry pipeline is now skipped entirely for RepeatableRead, Serializable and Snapshot. This is deliberately broader than refusing those two SQLSTATEs: class 53, 55, 57P03 and 58 all abort a snapshot-holding transaction too, so singling out class 40 would have left the identical hole open one error code over.

If you use those isolation levels, expect to see failures surface that were previously retried away. Handle them by re-reading and recomputing — the only place a snapshot conflict can be resolved. ReadCommitted is the default and is unchanged: it takes a fresh snapshot per statement, so replaying precomputed SQL still means what it meant, and every existing application keeps full retry behaviour.

A batched Async-snapshot FetchForWriting says what it needs

#​5535. For an aggregate projected with ProjectionLifecycle.Async, the non-exclusive FetchForWriting wraps its reads in begin transaction isolation level repeatable read read only … end so they share one snapshot — and PostgreSQL accepts that only as the first statement in a transaction. Enlisted after anything else in a batch, it failed at Execute() with 25001: SET TRANSACTION ISOLATION LEVEL must be called before any query, naming an isolation level the caller never mentioned and saying nothing about ordering. Nothing about CreateBatchQuery() hints that one of its members has to go first.

It is now refused at the enlisting call, with a message that names the aggregate and the constraint. The position that worked still works.

Dependencies

Weasel 9.37.0, whose weasel#​659 is the reason most of #​5529 went away: ApplyAllConfiguredChangesToDatabaseAsync released the global migration lock only on its success paths, so a migration that threw left it held and locked every other replica out of migrating — while the node that stranded it reported nothing. Disposing the connection was not enough, because Npgsql resets a pooled connection when it is next used rather than when it is returned, so the lock outlived the apply for as long as that connection sat idle.

Also in 9.37.0, and worth reading if you use them: SQL Server 2025 JSON indexes are no longer emitted as DROP INDEX on every apply (weasel#​661), Oracle descending indexes and co-migrated views settle (weasel#​660), TableDelta.HasChanges() answers instead of throwing for a table that does not exist yet (weasel#​658), and the reconnection loop no longer abandons a connection per attempt (weasel#​664).

Housekeeping

DISABLE_TEST_PARALLELIZATION is gone from the CI workflow and the contributor docs. Nothing ever read it, so it looked like a control and was not one — including to us, while diagnosing #​5529. Test parallelisation is disabled by CollectionBehavior in AssemblyInfo.cs, and both files now say where to change it.

9.41.0

Upgrade if you use IsolationLevel.Serializable. The first item below is a silent lost update, and it has been present since the write-retry pipeline shipped. The two items after it change behaviour you may be relying on.

🛑 Data integrity: Serializable sessions could silently lose an update

#​5528. A session opened with IsolationLevel.Serializable (or RepeatableRead) did not reliably protect against a lost update. PostgreSQL detected the write conflict correctly and Marten discarded the detection by retrying.

Two serializable sessions each load the same document at Amount = 100. One commits 100 - 500. The other should be refused with ConcurrentUpdateException; instead it committed 100 - 350 on top, and the first session's write was gone with no error raised anywhere. From the server's own log:

[14197] ERROR:  could not serialize access due to concurrent update
[14197] ROLLBACK
[14196] BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE    -- the replay
[14196] insert ... on conflict (id) do update ...         -- same values, same mt_version
[14196] COMMIT

WriteRetryClassifier (#​5262) listed 40001 serialization_failure as safe to replay, on the reasoning that the server had rolled the transaction back and said so — which is true, and turned out to be necessary but not sufficient. A replay re-issues the operations already computed, rather than re-running the application code that produced them, and a serialization failure means precisely that the snapshot those operations were derived from is no longer a valid basis for them. The replay opens a new transaction with a fresh snapshot, so the conflict is gone and the write lands.

40001 is now never replayed and surfaces as ConcurrentUpdateException, which is what Marten already translated it to. If you opted into Serializable you should expect to start seeing that exception — handle it by re-reading and recomputing, which is the only place the conflict can be resolved. Nothing inside Marten requests these isolation levels, so the async daemon and every default ReadCommitted session are unaffected.

40P01 deadlock_detected stays retryable deliberately: a deadlock aborts over lock ordering rather than a rejected snapshot, and replaying it is the textbook remedy at ReadCommitted. A deadlock retry under Serializable replays stale-snapshot work for the same structural reason, and the honest fix needs the isolation level plumbed into the classifier — recorded as a known gap rather than guessed at.

This went unnoticed because the test that catches it lives in StressTests, which has had no CI job since 2026-08-08.

⚠️ Behaviour change: the HotCold leadership lock is session-scoped again

#​5526. Events.UseAdvisoryLockTransaction now defaults to false, so HotCold leader election uses a session-scoped pg_try_advisory_lock rather than a transaction-scoped pg_try_advisory_xact_lock. The transaction-scoped lock had been the default since 8.37.3 (April 2026).

The reason is what the transaction-scoped lock costs while it works: the leader holds an open transaction for as long as it holds leadership, which on a long-lived daemon is an indefinitely long-running transaction sitting on the connection — with everything that implies for vacuum and for anything watching transaction age.

The transaction-scoped lock is still the better fit behind PgBouncer in transaction pooling mode, which can hand a session-scoped lock's server connection to another client. If that is your deployment, opt back in explicitly:

builder.Services.AddMarten(opts =>
{
    opts.Connection(connectionString);
    opts.Events.UseAdvisoryLockTransaction = true;
})
.AddAsyncDaemon(DaemonMode.HotCold);

⚠️ Breaking: DefaultTenantUsageDisabledException is no longer a MartenException

#​5514. Marten.Exceptions.DefaultTenantUsageDisabledException now derives from JasperFx.Events.DefaultTenantUsageDisabledException.

JasperFx.Events carries this exception so that code can handle the refusal once across Critter Stack stores, and its own contract is that stores subclass it. Polecat did; Marten did not. The consequence was not an error anywhere — a store-agnostic catch (JasperFx.Events.DefaultTenantUsageDisabledException) compiled, read as if it handled the refusal, and let it through unhandled on Marten only. That is the failure mode this fixes.

Multiple inheritance is not available, so it is one base or the other, and the cross-store catch was judged worth more than the marker base. If you rely on a broad catch (MartenException) to handle this refusal, it will no longer be caught — catch the exception itself, or the JasperFx base. The message is unchanged, including the single-argument overload's behaviour of appending to the standard prefix.

Also gone with the reparenting: the protected (SerializationInfo, StreamingContext) constructor, because the JasperFx base declares none to chain to. Binary serialization of exceptions has been obsolete since .NET 8.
... (truncated)

9.40.0

Minor rather than patch for one reason: the source-generated DetermineAction now decides one action per batch rather than per event. See the behaviour change below before upgrading if you archive streams from a projection.

⚠️ Behaviour changes

Archiving projections: one action per batch, not per event (JasperFx/jasperfx#​889, via the JasperFx 2.75.2 bump). The source-generated DetermineAction used to decide per event; it now decides once per batch. This changes what an archiving projection does when a single batch mixes an archive with later events for the same stream. StreamArchivingCompliance gained facts for it and Marten passes them, but if you depend on the old per-event evaluation, test before upgrading.

TenantIdStyle is now applied where the tenant id is stamped and keyed, not only where a tenant is resolved (#​5516). Under ForceLowerCase, these three previously wrote or matched the raw id:

  • BulkInsertEventsAsync / BulkInsertEventStreamAsync resolved the right database or partition and then stamped the raw id on every mt_streams / mt_events row — rows invisible to any normalised read under conjoined tenancy, and the wrong key inside the right partition under UseTenantPartitionedEvents.
  • TenantIsOneOf(...) filtered on the raw values, so a mixed-case id matched nothing.
  • DeleteAllTenantDataAsync used the raw id as its DELETE ... WHERE tenant_id = :p parameter, so it matched no rows and reported success — the one to look at hardest, since callers are usually honouring an erasure request.

If you have been calling these with mixed-case tenant ids, data already written under the raw casing stays where it is; it will not be found by a normalised read. ForTenant(string) also now normalises its nested-session cache key, which makes ForTenant("RED") and ForTenant("red") one session rather than two.

Event store

  • IEventStore.OpenReadOnlyEventStore(tenantId) is implemented (#​5513). With Advanced.DefaultTenantUsageEnabled = false — the automatic state once database-per-tenant tenancy is configured — the entire IReadOnlyEventStore surface was unreachable, because the tenant-less overload opens the store's default session and is refused before any tenant scope applies. That is why QueryStreamStates(tenantId)'s own parameter could not work around it. A null tenant keeps the previous store-global behaviour.
  • Diagnostic event-store reads answer "no results" instead of throwing (#​5511, #​5512), decided from the database rather than from configuration.
  • Progression and dead-letter reads answer nothing for an event store nothing provisions (#​5509, #​5510).

Diagnosing a missing source-generated dispatcher

The JasperFx 2.75.2 bump (#​5518) carries three changes that came out of #​5495, where a user's aggregates compiled with no dispatcher and the runtime message could not tell them why:

  • The generator now leaves [assembly: JasperFxSourceGeneratorApplied] in every assembly it processes, so "the generator never ran in that assembly" — a csproj problem — is distinguishable at runtime from "the generator ran and declined your type" — a code-shape problem. The exception says which.
  • Two copies of the generator in one compilation no longer collide with CS0433. This happens when a project references the standalone JasperFx.Events.SourceGenerator package as well as Marten, which bundles it: both copies emit identical file paths and the file-scoped evolvers mangle to the same name. If you hit that error — recognisable because it names the same assembly on both sides — remove the standalone package reference; Marten already carries the analyzer.
  • Opt in to a build-time check with <JasperFxEventsRequireSourceGenerator>true</JasperFxEventsRequireSourceGenerator> on projects that declare aggregates, and a missing analyzer becomes build error JFXEVT900 rather than a failure at the first FetchForWriting.

Compliance coverage

Marten is now enrolled in DocumentConjoinedTenancyCompliance (#​5517) — the first shared document coverage of conjoined tenancy on any store — with all 10 facts running. ConjoinedEventTenancyCompliance goes from 16 passing with 2 skipped to 18 passing with none, the two skips having been waiting on the read-only overload above.

Two of these enrollments exposed that Marten's own commit-listener compliance facts had been passing vacuously — the harness never installed the listener the suites register, so "no phantom deletion was reported" was trivially true of a store reporting nothing at all. Fixed in the #​5518 bump; no product change was needed, and both halves of that fact now pass for real.

Dependencies

JasperFx 2.75.2 and Weasel 9.35.2.

Weasel 9.35.x is mostly EF Core and SQL Server work, plus weasel#​634: an exhausted DropSchema retry is now reported instead of returning success.

Docs

The bundled analyzer does flow transitively across a ProjectReference (#​5508) — the composite-configuration page claimed the opposite. The claim was true of the standalone analyzer package, which Marten sets PrivateAssets="all" on, but #​4557 bundles the analyzer DLL into Marten's own package and that copy flows.

9.39.1

What's Changed

Full Changelog: JasperFx/marten@V9.39.0...v9.39.1

9.39.0

Security release

This release fixes two critical SQL injection vulnerabilities in Marten's LINQ provider. Both were reported privately and are disclosed today alongside this version. If your application routes untrusted input into either surface below, upgrade.

GHSA-q4xm-rhx9-xjm4 — SQL injection via a GroupBy HAVING operand (critical)

Affects 9.13.0 – 9.38.0. Fixed in 9.39.0.

A GroupBy(...).Where(g => ...) HAVING comparison rendered its constant or closure-captured operand with ToString() and appended it to the command text with no quoting, escaping, or parameterization. Because Marten put no quotes around the operand at all, an attacker did not need to break out of a literal — they supplied the entire literal and everything after it.

// `filter` from untrusted input
.GroupBy(x => x.Color).Where(g => g.Max(p => p.Name) == filter)

Impact is filter and authorization bypass, and subquery-based disclosure, at the session's database privilege. Numeric operands were never injectable.

Note on impact: Marten executes through NpgsqlBatch, which refuses multi-statement command text, so stacked-statement modification is not reachable through this sink.

Fix: a HAVING operand is now either SQL derived from the document model or a value from the caller's expression, and those are distinct types rather than both being string. Values always bind as parameters.

GHSA-frqq-p5g3-8jq5 — SQL injection via regConfig on the full-text ranking path (critical)

Affects 9.31.0 – 9.38.0. Fixed in 9.39.0.

OrderByTextRank / ThenByTextRank interpolated the caller-supplied regConfig into SQL as a '...'::regconfig literal with no validation. This is the same class as CVE-2026-45288, returning on a path added after that fix: the validator existed, but as a private member of FullTextWhereFragment, so the ranking path could not reuse it and did not re-implement it.

You are affected only if your application passes untrusted input as the regConfig argument. It is an optional parameter defaulting to english, so most applications never pass it at all.

Fix: the check is now a shared validator, applied in FullTextIndexResolver.ResolveVector — the sink every full-text path funnels through — so present and future callers are covered by construction rather than by remembering. The weighted-index and Marten.PgVector paths were folded onto the same validator.

Previously fixed, disclosed today

Two advisories for vulnerabilities already fixed in earlier releases are published alongside this one:

  • CVE-2026-77574 / GHSA-4jp4-64fp-89g7 (critical) — SQL injection via a non-string constant in a LINQ Select projection. Fixed in 9.15.3.
  • GHSA-3vp4-34pf-2rcw (high) — SQL injection through a tenant id used as a PostgreSQL partition suffix, under sharded tenancy with UseTenantPartitionedEvents. Fixed in 9.22.1.

If you are on a version older than those, upgrade.


Bug fixes

  • #​5473 — on the rich append path, the optimistic-concurrency exception is now built from the StreamAction rather than by regex over the PostgreSQL error detail. Id, AggregateType and the expected version are now correct regardless of whether Npgsql redacted the detail — which it does unless the connection string carries Include Error Detail=true, so this was the common production case. The fallback message no longer passes PostgreSQL's own text through.
  • #​5479 — a tenant that exists but has been disabled now throws DisabledTenantException instead of UnknownTenantIdException, so an operator who disabled a tenant is no longer told it does not exist. The new type derives from UnknownTenantIdException, so existing catch blocks and `On.....

Description has been truncated

Bumps coverlet.collector from 10.0.1 to 10.1.0
Bumps Marten from 9.30.0 to 9.50.3
Bumps Microsoft.Extensions.DependencyInjection from 10.0.11 to 10.0.13
Bumps Microsoft.Extensions.Hosting.Abstractions from 10.0.11 to 10.0.13
Bumps Microsoft.Extensions.Logging.Abstractions from 10.0.11 to 10.0.13
Bumps Microsoft.Extensions.TimeProvider.Testing from 10.9.0 to 10.11.0
Bumps Microsoft.NET.Test.Sdk from 18.9.0 to 18.11.0
Bumps Microsoft.SourceLink.GitHub from 10.0.400 to 10.0.402
Bumps MinVer from 7.0.0 to 8.0.0
Bumps Testcontainers.Kafka from 4.14.0 to 4.16.0
Bumps Testcontainers.PostgreSql from 4.14.0 to 4.16.0
Bumps Testcontainers.RabbitMq from 4.14.0 to 4.16.0
Bumps WolverineFx.EntityFrameworkCore from 6.32.0 to 6.51.0
Bumps WolverineFx.Kafka from 6.32.0 to 6.51.0
Bumps WolverineFx.Postgresql from 6.32.0 to 6.51.0
Bumps WolverineFx.RabbitMQ from 6.32.0 to 6.51.0
Bumps WolverineFx.RuntimeCompilation from 6.32.0 to 6.51.0
Bumps WolverineFx.Sqlite from 6.32.0 to 6.51.0
Bumps xunit.runner.visualstudio from 4.0.0 to 4.0.1
Bumps xunit.v3.extensibility.core from 4.0.0 to 4.0.2

---
updated-dependencies:
- dependency-name: coverlet.collector
  dependency-version: 10.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: thessera-dependencies
- dependency-name: Marten
  dependency-version: 9.50.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: thessera-dependencies
- dependency-name: Microsoft.Extensions.DependencyInjection
  dependency-version: 10.0.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: thessera-dependencies
- dependency-name: Microsoft.Extensions.Hosting.Abstractions
  dependency-version: 10.0.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: thessera-dependencies
- dependency-name: Microsoft.Extensions.Logging.Abstractions
  dependency-version: 10.0.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: thessera-dependencies
- dependency-name: Microsoft.Extensions.TimeProvider.Testing
  dependency-version: 10.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: thessera-dependencies
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-version: 18.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: thessera-dependencies
- dependency-name: Microsoft.SourceLink.GitHub
  dependency-version: 10.0.402
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: thessera-dependencies
- dependency-name: MinVer
  dependency-version: 8.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: thessera-dependencies
- dependency-name: Testcontainers.Kafka
  dependency-version: 4.16.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: thessera-dependencies
- dependency-name: Testcontainers.PostgreSql
  dependency-version: 4.16.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: thessera-dependencies
- dependency-name: Testcontainers.RabbitMq
  dependency-version: 4.16.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: thessera-dependencies
- dependency-name: WolverineFx.EntityFrameworkCore
  dependency-version: 6.51.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: thessera-dependencies
- dependency-name: WolverineFx.Kafka
  dependency-version: 6.51.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: thessera-dependencies
- dependency-name: WolverineFx.Postgresql
  dependency-version: 6.51.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: thessera-dependencies
- dependency-name: WolverineFx.RabbitMQ
  dependency-version: 6.51.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: thessera-dependencies
- dependency-name: WolverineFx.RuntimeCompilation
  dependency-version: 6.51.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: thessera-dependencies
- dependency-name: WolverineFx.Sqlite
  dependency-version: 6.51.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: thessera-dependencies
- dependency-name: xunit.runner.visualstudio
  dependency-version: 4.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: thessera-dependencies
- dependency-name: xunit.v3.extensibility.core
  dependency-version: 4.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: thessera-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot dependabot Bot added .NET Pull requests that update .NET code dependencies Pull requests that update a dependency file labels Oct 16, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file .NET Pull requests that update .NET code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants