Repository navigation
Bump the dotnet-production group with 7 updates - #17
Closed
dependabot[bot] wants to merge 1 commit into
Closed
dependabot[bot] wants to merge 1 commit into
dependabot[bot] wants to merge 1 commit into
Conversation
Bumps cake.tool from 6.2.0 to 6.3.0 Bumps coverlet.collector from 10.0.1 to 10.1.0 Bumps Marten from 9.33.0 to 9.43.0 Bumps Microsoft.NET.Test.Sdk from 18.10.0 to 18.10.1 Bumps OpenIddict.Core from 7.7.0 to 7.7.1 Bumps Weasel.Storage from 9.31.1 to 9.38.0 Bumps xunit.v3 from 4.0.0 to 4.0.1 --- updated-dependencies: - dependency-name: cake.tool dependency-version: 6.3.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dotnet-production - dependency-name: coverlet.collector dependency-version: 10.1.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dotnet-production - dependency-name: Marten dependency-version: 9.43.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dotnet-production - dependency-name: Microsoft.NET.Test.Sdk dependency-version: 18.10.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dotnet-production - dependency-name: OpenIddict.Core dependency-version: 7.7.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dotnet-production - dependency-name: Weasel.Storage dependency-version: 9.38.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dotnet-production - dependency-name: xunit.v3 dependency-version: 4.0.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dotnet-production ... Signed-off-by: dependabot[bot] <support@github.com>
Contributor
Author
|
Superseded by #18. |
dependabot
Bot
deleted the
dependabot/nuget/dot-config/dotnet-production-877a96b21a
branch
October 8, 2026 03:17
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Updated cake.tool from 6.2.0 to 6.3.0.
Release notes
Sourced from cake.tool's releases.
6.3.0
As part of this release we had 60 issues closed.
Bugs
Features
Improvements
IAzurePipelinesCommandsby devlead resolved in !4915 by perclausenContributors
6 contributors made this release possible.
... (truncated)
Commits viewable in compare view.
Updated coverlet.collector from 10.0.1 to 10.1.0.
Release notes
Sourced from coverlet.collector's releases.
10.1.0
Improvements
Fixed
Diff between 10.0.1 and 10.1.0
Commits viewable in compare view.
Updated Marten from 9.33.0 to 9.43.0.
Release notes
Sourced from Marten's releases.
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_eventscalled with zero events readmt_streams.versionwithout 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 onpk_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 usesgreatest()so it can never lower a version whoever calls it.Field context: a production console on 9.32.0 sat at
mt_streams.version608676 againstmax(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.Sequencecannot distinguish caught up with nothing new from no longer maintained: both hold the same number forever. The progression row'slast_updatednow reachesShardStateandProjectionProgressRow, and — the part that needed a product change — an idle high-water cycle now restamps theHighWaterMarkrow. 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 toucheslast_seq_id, so it cannot nudge the mark.Diagnostics surface (monitoring consoles)
#5543 —
IDocumentStoreDiagnosticsadopts 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:""tenant""id::text = @idAlso:
LoadDocumentAsyncreturningStoredDocumentwith metadata, populatedDocumentQueryResult.Documents, a newIDocumentStoreDiagnosticsWriter(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.TenantIdnow means the default tenant, not every tenant. If you relied on the old behaviour, setAllTenants = true.Also
#5542 — asynchronous aggregate events are ordered by stream sequence.
Internals: a diagnostic document read now resolves a tenant's database through
TryFindDatabaserather thanFindOrCreateDatabase, 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.RepeatableReador above, or batch aFetchForWritingfor an Async-snapshot aggregate.Migrations settle again on a long schema name
#5536.
ApplyAllDatabaseChangesOnStartup()against an already-migrated schema could fail with:The foreign key was not the problem. PostgreSQL truncates identifiers at 63 characters, so a longer
DatabaseSchemaNamewas 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.DatabaseSchemaNameis 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:
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.
RepeatableReadupwards#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
RepeatableReadup, 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 for40001, and40P01(deadlock) had the same flaw for the same structural reason.The write-retry pipeline is now skipped entirely for
RepeatableRead,SerializableandSnapshot. 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.
ReadCommittedis 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
FetchForWritingsays what it needs#5535. For an aggregate projected with
ProjectionLifecycle.Async, the non-exclusiveFetchForWritingwraps its reads inbegin transaction isolation level repeatable read read only…endso 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 atExecute()with25001: SET TRANSACTION ISOLATION LEVEL must be called before any query, naming an isolation level the caller never mentioned and saying nothing about ordering. Nothing aboutCreateBatchQuery()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:
ApplyAllConfiguredChangesToDatabaseAsyncreleased 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 INDEXon 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_PARALLELIZATIONis 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 byCollectionBehaviorinAssemblyInfo.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:
Serializablesessions could silently lose an update#5528. A session opened with
IsolationLevel.Serializable(orRepeatableRead) 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 commits100 - 500. The other should be refused withConcurrentUpdateException; instead it committed100 - 350on top, and the first session's write was gone with no error raised anywhere. From the server's own log:WriteRetryClassifier(#5262) listed40001 serialization_failureas 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.40001is now never replayed and surfaces asConcurrentUpdateException, which is what Marten already translated it to. If you opted intoSerializableyou 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 defaultReadCommittedsession are unaffected.40P01 deadlock_detectedstays retryable deliberately: a deadlock aborts over lock ordering rather than a rejected snapshot, and replaying it is the textbook remedy atReadCommitted. A deadlock retry underSerializablereplays 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.#5526.
Events.UseAdvisoryLockTransactionnow defaults to false, soHotColdleader election uses a session-scopedpg_try_advisory_lockrather than a transaction-scopedpg_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:
DefaultTenantUsageDisabledExceptionis no longer aMartenException#5514.
Marten.Exceptions.DefaultTenantUsageDisabledExceptionnow derives fromJasperFx.Events.DefaultTenantUsageDisabledException.JasperFx.Eventscarries 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-agnosticcatch (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
DetermineActionnow decides one action per batch rather than per event. See the behaviour change below before upgrading if you archive streams from a projection.Archiving projections: one action per batch, not per event (JasperFx/jasperfx#889, via the JasperFx 2.75.2 bump). The source-generated
DetermineActionused 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.StreamArchivingCompliancegained facts for it and Marten passes them, but if you depend on the old per-event evaluation, test before upgrading.TenantIdStyleis now applied where the tenant id is stamped and keyed, not only where a tenant is resolved (#5516). UnderForceLowerCase, these three previously wrote or matched the raw id:BulkInsertEventsAsync/BulkInsertEventStreamAsyncresolved the right database or partition and then stamped the raw id on everymt_streams/mt_eventsrow — rows invisible to any normalised read under conjoined tenancy, and the wrong key inside the right partition underUseTenantPartitionedEvents.TenantIsOneOf(...)filtered on the raw values, so a mixed-case id matched nothing.DeleteAllTenantDataAsyncused the raw id as itsDELETE ... WHERE tenant_id = :pparameter, 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 makesForTenant("RED")andForTenant("red")one session rather than two.Event store
IEventStore.OpenReadOnlyEventStore(tenantId)is implemented (#5513). WithAdvanced.DefaultTenantUsageEnabled = false— the automatic state once database-per-tenant tenancy is configured — the entireIReadOnlyEventStoresurface was unreachable, because the tenant-less overload opens the store's default session and is refused before any tenant scope applies. That is whyQueryStreamStates(tenantId)'s own parameter could not work around it. A null tenant keeps the previous store-global behaviour.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:
[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.CS0433. This happens when a project references the standaloneJasperFx.Events.SourceGeneratorpackage 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.<JasperFxEventsRequireSourceGenerator>true</JasperFxEventsRequireSourceGenerator>on projects that declare aggregates, and a missing analyzer becomes build errorJFXEVT900rather than a failure at the firstFetchForWriting.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.ConjoinedEventTenancyCompliancegoes 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
DropSchemaretry 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 setsPrivateAssets="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 withToString()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.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/ThenByTextRankinterpolated the caller-suppliedregConfiginto SQL as a'...'::regconfigliteral 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 ofFullTextWhereFragment, 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
regConfigargument. It is an optional parameter defaulting toenglish, 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 andMarten.PgVectorpaths were folded onto the same validator.Previously fixed, disclosed today
Two advisories for vulnerabilities already fixed in earlier releases are published alongside this one:
Selectprojection. Fixed in 9.15.3.UseTenantPartitionedEvents. Fixed in 9.22.1.If you are on a version older than those, upgrade.
Bug fixes
StreamActionrather than by regex over the PostgreSQL error detail.Id,AggregateTypeand the expected version are now correct regardless of whether Npgsql redacted the detail — which it does unless the connection string carriesInclude Error Detail=true, so this was the common production case. The fallback message no longer passes PostgreSQL's own text through.DisabledTenantExceptioninstead ofUnknownTenantIdException, so an operator who disabled a tenant is no longer told it does not exist. The new type derives fromUnknownTenantIdException, so existingcatchblocks andOnException<T>()policies keep working.GroupJoin/SelectMany, andGROUP BYrendering over a join.Diagnostics and messages
... (truncated)
9.38.0
Eight changes since 9.37.0. Most of this release is silent-wrongness: a permanent sequence gap that stalls the async daemon, telemetry spans attached to the wrong parent, a
DbContextnothing ever disposed, and a vector projection that embedded stale text. Three of them were reported from production by the community.A
VectorProjectiondeclaringMapFromAggregateregistered anything other thanAsyncis now refused when the store is built (#5451, #5468). The class remarks always said Async was required; nothing enforced it, so an Inline registration built without complaint and then embedded text that missed the event that triggered it. Measured: the first save wrote no row at all, because live aggregation reads committed events and the stream had none yet; every later save wrote the state as of the previous event. The content hash recorded that stale text as current and nothing failed.If you have such a registration it will now fail at
DocumentStore.For(...)with a message namingMapFromAggregateandProjectionLifecycle.Async. Event-mapped vector projections registeredInlineare unaffected and remain supported.Event store
A failed batch no longer leaves a permanent gap in the event sequence (#5454, #5456). Under
QuickWithServerTimestamps— the V9 default — aStartStreamtook a per-event INSERT route whoseseq_idcame from an inlinednextval()that nothing read back. The events keptSequence0, and tombstone creation skips exactly those, so any failure later in the same batch (a losingUpdateRevision, a duplicate document INSERT) rolled back with sequence numbers drawn and nothing to fill them. The async daemon's high-water detector then stalled for every tenant in the database until it had seen the gap forStaleSequenceThreshold. AStartStreamnow goes through the samemt_quick_append_eventsfunction an ordinary append already used, which returns its sequence numbers. Reported and fixed by @vpetrevski.mt_quick_append_eventsno longer writes a new stream'smt_streamsrow twice (#5464). It inserted the row with version 0 and then let the trailingUPDATEset the final version — two heap tuples and two WAL records per stream creation, in one transaction, where nothing outside it could observe the intermediate value. This became the default stream-creation path with #5456, so it was worth removing. A one-eventStartStreammeasured 471µs on the old per-event route, 511µs through the function, and 480µs with this change (PostgreSQL 17.10, medians of 3).FetchForWritingagainst a stream that does not exist yet gets the same saving.Projections and read models
An EF Core projection's
DbContextis disposed (#5457, #5466).EfCoreProjectionStorageowns a context built once per tenant per batch, and nothing disposed it on any path —IProjectionStorage<,>declares no disposal contract, so the type that owned it had nothing to hook. Disposal now belongs to the transaction participant, whose lifetime already equals a batch. A second, unconditional leak is fixed alongside it: the live-aggregation fallback registered its owner only when the session was anITransactionParticipantRegistrar, and a plainIQuerySession— whatAggregateStreamAsyncpasses — is not one, so nobody released an eagerly-opened connection. Reported by @wpei-infotrack, with investigation from @arnelirobles.An absent key no longer projects as an explicit JSON null in a
Select()(#5461, #5462).Telemetry
MartenTracingpassedparentActivity?.ParentIdtoActivitySource.StartActivity, which parents a span to the parent of the activity it is handed — its grandparent. With an incoming Server/Consumer span carrying a remotetraceparent, everymarten.connectionspan hung off the sending service's span instead of the one doing the work. It stayed hidden because it is invisible in the two easy cases: with no ambient activity, or with an ambient root,ParentIdis null andActivitySourcefalls back toActivity.Current, which is the right answer by accident. Diagnosed from the source by @bohdan-hukivskyi.Documentation
Unit testing event-sourced handlers with
StubEventStreamis now documented (#5455, #5458, #5463). Also fixesStubEventStream(aggregate, StoreOptions), which built a secondEventGraphsoMapEventTypealiases never reached it.The pgvector docs no longer describe the
ef_searchcap that 9.37 removed (#5452, #5467), including the non-obvious case that a hybrid search asks for itsCandidateDepthrather than its limit and so reaches pgvector's 1000-row ceiling at a limit above 250. Contributed by @arnelirobles.Dependencies
JasperFx and JasperFx.Events 2.73.2; Weasel 9.32.0.
9.37.0
Eighteen commits, and almost all of them are
Marten.PgVectorcorrectness — found by auditing the search surface 9.36.0 had just shipped. Every one of these was silent: a plausible answer, in a plausible order, that was wrong.Vector and hybrid search returned the wrong rows
Cosinerather than the metric the index declared, so anL2index was searched by cosine and silently fell back to a sequential scanhnsw.ef_search(default 40) — so it under-returned however large thelimitT, never the concrete subtypeto_tsvectorwhen no index matched theregConfigVectorProjectionwrote the wrong rowsid, so the later tenant's write replaced the earlier one'sForTenant(...)appends landed in the wrong tenantGuid.Emptyon string-identified streamsSearches now run through the session (#5421, #5423)
The session's connection, transaction, command timeout, resilience pipeline and
IMartenSessionLogger. A search on a session inside a caller-managed transaction now sees that session's uncommitted writes, the wayQuery<T>()does.SET LOCALstill cannot leak onto a pooled connection.VectorSearchAsync,VectorSearchWithScoresAsyncandVectorProjectionSearchAsyncgainCancellationTokenoverloads. Overloads rather than defaulted parameters: an optional argument is compiled into the caller, so widening a shipped signature breaks every assembly that has not been rebuilt.ColumnWeightsis refused, not ignored (#5446)HybridSearchOptionsgained per-column weights for the text leg (jasperfx#854, JasperFx 2.72.0). Marten weights at index time throughWeightedFullTextIndex, so there is nothing a per-call weight could apply to — and it is refused by name rather than ignored, because a caller who weights a title column and silently gets an unweighted ranking has no way to find out.Timestamps and the explorer
#5379 —⚠️ Only patching was affected — an ordinary
mt_last_modifiedno longer jumps by the server's UTC offset when a document is patched.now() at time zone 'utc'strips the offset, and the naive result is re-read in the session'sTimeZone: on a UTC+2 database the stored instant was two hours in the past.Store/SaveChangestakes the column default, which was always correct.#5430 (thanks @erikshafer) covers the event half of the same defect, reached through stream compaction, which #5445 fixed and left untested — and its assertions run over two time zones in both directions, where a one-sided bound passes against a negative offset.
#5383 — database-scoped explorer reads, honest store-global reads, and two tenant-scoping defects.
On JasperFx 2.72.0.
9.36.0
Takes
Marten.PgVectorfrom a vector search that could not be indexed and could not be called portably, to one that shares theJasperFx.Events.Vectorscontracts with Fisher and Polecat, can be served by an HNSW index, returns scores, and fuses with full text.VectorIndex— declare an HNSW index for a vector searchts_rankleg and the vector leg#5413 deleted that package's own
IEmbeddingProviderandDistanceFunctionin favour of the shared ones, and removed the deadVectorOn/PgVectorOptionsregistry, which had no references anywhere including the tests. ThePgvector.Vectoroverload ofVectorSearchAsyncwas kept and forwards, so the common call site is source-compatible.Minor rather than major because the whole package family shares one version and Marten itself has no break in this range — a major bump would move every other package for an extension package's API.
The search surface shipped here was audited immediately afterwards, and ten silent defects came out of it — searches that skipped Marten's default filters and returned soft-deleted rows, a distance metric that defaulted to
Cosinerather than the one the index declared, an indexed search capped at 40 rows byhnsw.ef_search, aVectorProjectionthat ignored conjoined tenancy, and more. Every one of them returned a plausible answer in a plausible order.All of them are fixed in 9.37.0. If you are picking up
Marten.PgVectorfor the first time, start there rather than here.On the tag. This points at
daeb27b5, not at the "Release 9.36.0" commitbaf1fc3f. The publish dispatch from the release commit was cancelled, and the run that actually pushed the packages went fromdaeb27b5— one commit later, carrying #5417. The tag names what shipped.9.35.0
Marten 9.35.0 — Event Model naming, store identity, and idempotent archiving.
Three, all deliberate. None require a code change to adopt, but each changes what an existing application observes.
1. A store's derived Event Model is now named after your service, not
"EventModel"(#5408).The store-derived Event Model source fell back to the literal
"EventModel"— the one default guaranteed to be wrong for every host. Wolverine names its derived model afterJasperFxOptions.ServiceName, and so does Bobcat's spec assembly, so the common case (Wolverine + one Marten store) assembled two models out of the box and had to restate a name it had already declared.The fallback is now the service name. An explicit name still wins:
If you were relying on the model being called
EventModel, setopts.EventModelName = "EventModel"explicitly.2. The primary store's
Subjectnow followsStoreName(#5409).Subjectwas a literalmarten://mainthat ignoredStoreOptions.StoreName, whileIdentityhad always been built from it — so naming a primary store moved one and left the other behind, andSubjectis the one consumers key on. Both are now built fromStoreName. Tooling that keys onstore.Subject(CritterWatch's explorer reads and shard progression ids among them) will seemarten://{storename}for a named store where it previously sawmarten://main.A store name is user-supplied text and a uri host is not, so names are sanitized:
My Storewould otherwise throwUriFormatExceptionanda/bwould silently parse its tail as a path. Ordinary names are unchanged.3. Archiving an already-archived stream is a no-op instead of an error (#5403).
Under
UseArchivedStreamPartitioning, a second archive of the same stream raised23505onmt_streams_archived_pkey. This also stalled async single-stream projections withIncludeArchivedEvents = truewhen they processed anArchivedmarker after an inline snapshot had already archived the stream. Repeated archiving now succeeds and does nothing.A genuine collision is still reported: a different active stream reusing an archived stream id still fails, rather than being silently swallowed and losing its metadata.
Fixes and improvements
StoreOptions.EventModelName. A host that calledAddEventModel("Something", …)assembled two models: its own, and one the store contributed under the default name. The name is configured onStoreOptionsand read lazily when the model is assembled, soAddEventModelmay be called before or afterAddMarten.BuildStoreOptionsassignedStoreNameboth before and after theIConfigureMarten<T>chain, so a contribution that named the store was silently reverted to the marker type's name.is_archivedis the list partition key on bothmt_streamsandmt_events, the archive function's new predicates also let PostgreSQL prune to the active partition, where the previous statements had to consider both.ImHashMapkeyed byFrame, which does not overrideGetHashCode, meant statement order followed identity hash codes and varied per process. Every Event Model slice also now carries the store it came from, so two stores projecting a document of the same simple name leave a recorded disagreement rather than one silently winning (jasperfx#836).Compatibility
Binary compatible with 9.34.0. The public
AddMarten/AddMartenStore<T>surface is unchanged — verified by diffing the extracted signature list against the 9.34.0 tag. New configuration is additive onStoreOptions.9.34.0
Native AOT support for event-sourced applications, and a round of integrity fixes to the diagnostics and monitoring surface — including one deliberate behaviour change worth reading before upgrading.
Native AOT
Event-sourced applications now run under Native AOT.
Utf8JsonWriterinstead of round-tripping through the consumer's serializer, removing a reflection-dependent path from AOT reads.Diagnostics and monitoring
Three fixes to the event store explorer and projection-status APIs. All three are cases where a monitoring read returned something misleading, or changed the system it was observing.
#5400 — Behaviour change. The four explorer read APIs no longer provision anything. They resolved their database through
ITenancy.FindOrCreateDatabase, and two tenancy models take the "or create" half literally:ShardedTenancyassigned an unknown tenant to a shard and ran partition + per-tenant sequence DDL for it, andSingleServerMultiTenancyissuedCREATE DATABASEfor a database named after the id. A console polling a retired or mistyped tenant id therefore brought that tenant — or a whole database — into existence.An unrecognized tenant id now throws
UnknownTenantIdException, which is whatStaticMultiTenancyandMasterTableTenancyalready did, so the explorer answers the same way across every tenancy model. Adds the publicITenancy.TryFindDatabase, a read-only counterpart toFindOrCreateDatabase; it is a default interface member, so customITenancyimplementations keep working unchanged.FindOrCreateDatabaseitself is untouched — create-on-demand remains its documented job for real tenant traffic.If you call
GetRecentStreamsAsync,ReadStreamAsync,QueryByTagsAsyncorGetProjectionStatusesAsyncwith a tenant id that may not exist, it now throws where it previously succeeded.#5382 —
GetProjectionStatusesAsync(ct)threwNotSupportedExceptionon a database-per-tenant store, because a tenant-less call has no default tenant to open a session against. It now answers from the projection registry, so "is this projection still registered?" — the only store-agnostic way to ask, and what orphan detection is built on — works on the store shape that most needs it.Note the original issue reported that both overloads threw. Only the tenant-less one did; passing a tenant id or a database identifier from
AllDatabases()has always worked and reads real per-database progression.#5396 —
ShardStatus.Statereports the state of a reachable daemon instead of always answeringUnknown.Unknownnow means what it is documented to mean: there is no daemon here to ask.Correctness
IVersionedmarker interface. Thanks to @JurJean for the report and the original fix.[JsonStringEnumMemberName]/[EnumMember]names are honored in LINQ translation rather than the CLR member name being assumed.byte[], and its limits are documented. Thanks to @erdtsieck.Event modeling
ProjectionEventModelSourceis registered fromAddMartenandAddMartenStore<T>, so Event Model views resolve from a Marten store without manual wiring.pgvector
Docs and internals
PrefixSearchand the session-level full-text search shortcuts.GuidOptimisticConcurrencyCompliancein the shared compliance suite.Dependencies
... (truncated)
Commits viewable in compare view.
Updated Microsoft.NET.Test.Sdk from 18.10.0 to 18.10.1.
Release notes
Sourced from Microsoft.NET.Test.Sdk's releases.
18.10.1
What's Changed
Full Changelog: microsoft/vstest@v18.10.0...v18.10.1
Commits viewable in compare view.
Updated OpenIddict.Core from 7.7.0 to 7.7.1.
Release notes
Sourced from OpenIddict.Core's releases.
7.7.1
This release introduces the following changes:
AuthenticateAsync()API when using pushed authorization requests, authorization request caching or end session request caching.Commits viewable in compare view.
Updated Weasel.Storage from 9.31.1 to 9.38.0.
Release notes
Sourced from Weasel.Storage's releases.
9.37.0
Five fixes. Full upgrade notes.
A JSON index is in
sys.indexes, but itssys.index_columnsrow carrieskey_ordinal = 0, which the column query filtered out. The index came back with no columns, matched nothing declared, landed inIndexes.Extras, and was emitted as aDROP INDEX— so every apply silently removed it. JSON indexes are now read throughsys.json_indexes/sys.json_index_paths, behind anobject_idcheck so nothing changes on 2022 or earlier. (#661)The same change adds
JsonIndexDefinition, soCREATE JSON INDEXcan be declared, rendered, compared and asserted on like any other schema object.Oracle: a descending index and co-migrated views never settled
Split commands dropped
InitialLONGFetchSize, soALL_IND_EXPRESSIONS.COLUMN_EXPRESSIONandALL_VIEWS.TEXT— both LONGs — read back empty.PRIORITY DESCcame back as its hiddenSYS_NC00007$column and every apply rebuilt the index. It failed only in combination, which is why per-object tests never caught it. Contributed by @lahma, found through Quartz.NET's Weasel integration. (#660)A delta for a table that does not exist answers instead of throwing
TableDelta.HasChanges()threw aNullReferenceExceptionfor a table that needs creating — the one case whose answer is unambiguously "yes". It reached operators through Wolverine's resource check, which reports any exception as "Missing known broker resources". SQL Server, PostgreSQL and SQLite. (#658)A failed migration no longer strands the global lock
The lock was released only on the 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: Npgsql resets a pooled connection when it is next used, not when it is returned, so the session lock outlived the apply for as long as that connection sat idle. (#659)
The reconnection loop no longer abandons a connection
It replaced
connwithout disposing what it overwrote, leaving a live connection per attempt for the finalizer to find — against a server that had just refused or terminated one. (#664)Minor rather than patch because #661 adds public API and two of these change migration output — the same reason 9.29.1 was renumbered to 9.30.0.
Verified across both target frameworks and all eight suites against PostgreSQL 17, SQL Server 2022, MySQL 8.0.46, Oracle Free 23 and SQLite: net9.0 3984 passed, net10.0 3980 passed, 0 failed.
9.36.0
A drift release, and one new diagnostic.
Most of 9.36.0 is a single shape of defect found five times over: a model declares something in a spelling the server rewrites when it stores it, so the column or index never matches itself, every migration re-issues DDL that changes nothing, and the delta never settles. MySQL type synonyms, Oracle ANSI type names, Oracle and MySQL index key direction, Oracle index tablespaces and SQL Server
CASTall did this.📖 Full upgrade notes
Read this before upgrading
Oracle: a table with a string column default could not be created at all under the default
CreateIfNotExistsstyle — the quote ended theEXECUTE IMMEDIATEliteral and the create failed with PLS-00103 (#643).Two migrations change behaviour. Changing a computed column's definition now drops and recreates the indexes and foreign keys that depend on it (#638). That migration used to fail outright on SQL Server, and used to succeed while silently dropping the index on PostgreSQL.
Every drift fix was checked in both directions. The spellings that already reconciled still reconcile, and a genuinely different type, length or direction is still reported as drift. No settled table acquires a new
ALTER.JasperFx 2.76.0 is the new floor, the first release carrying the
IAdvisoryLock.FindHolderAsynccontract.Who holds this advisory lock?
#650.
IAdvisoryLockcould acquire a distribution lock but not say who holds it —HasLock(lockId)answers only "does this node". So when a projection agent stopped withProgressionProgressOutOfOrderException, meaning two processes believed they owned the same shard, nothing could name the other one. Under Marten HotCold and Polecat the lock is the authority.