Fix #4611: allow StartStream with mandatory stream types under per-tenant event partitioning#4613
Merged
jeremydmiller merged 2 commits intoJun 3, 2026
Conversation
jeremydmiller
added a commit
that referenced
this pull request
Jun 3, 2026
…sions #4610 fixed `values.Contains(p.EnumMember)` against an EnumStorage.AsString document member by routing the case through `EnumIsOneOfWhereFragment` in `EnumerableContains.Parse`. That worked on net9.0 but the regression test file failed on net10.0 in CI for PR #4613 and #4615 with the same `Writing values of 'YourEnum[]' is not supported for parameters having NpgsqlDbType '-2147483639'` shape from the original bug. Root cause: on net10.0 the C# compiler resolves `array.Contains(x)` to `MemoryExtensions.Contains` (via the implicit `T[]` → `ReadOnlySpan<T>` conversion), not `Enumerable.Contains`. The match runs through `MemoryExtensionsContains.Parse`, not `EnumerableContains.Parse`, and the former had the identical enum-array-as-raw-CommandParameter bug — #4610's fix had only patched the latter. Mirror the same fix into `MemoryExtensionsContains.Parse`. The existing Bug_enum_asstring_array_contains regression tests now cover both parsers because they target the user-facing shape (`values.Contains(p.Status)`) and each .NET version routes that to a different parser. Verified: - Bug_enum_asstring_array_contains: 6/6 pass on net9.0 - Bug_enum_asstring_array_contains: 6/6 pass on net10.0 - Full LinqTests on net10.0: 1269 passed, 0 failed Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…artitioned events When UseTenantPartitionedEvents is enabled, StartStream actions are routed through the bulk mt_quick_append_events operation (QuickEventAppender). The PostprocessAsync guard that rejects appending to a non-existent stream under UseMandatoryStreamTypeDeclaration (a new stream's first event comes back as version 1) also fired for a legitimate StartStream of a brand-new stream, throwing NonExistentStreamException. The events were then tombstoned, so a later append to the never-created stream failed as well. Only treat version 1 as an error for Append actions, not Start.
…artStream guard test Two additions to the regression test for JasperFx#4611: 1. The existing test now asserts the end state the fix is supposed to produce: the mt_streams row exists in the assigned shard for the started stream, and its `type` column is populated with the aggregate-type alias. This is the surface the original bug actually broke — events landed in mt_events but the StartStream got post-process-tombstoned, so the mt_streams row was never created (which then made every subsequent Append throw NonExistentStreamException). 2. New companion test under the same sharded + UseTenantPartitionedEvents + UseMandatoryStreamTypeDeclaration config that calls the no-type StartStream overloads and asserts they still throw StreamTypeMissingException synchronously. The fix in QuickAppendEventsOperationBase only relaxes the post-process "appended to a non-existent stream" guard for Start actions (Stream.ActionType != Append) — it does not, and must not, weaken the API-level guard in EventStore.StartStream that rejects untyped StartStream when UseMandatoryStreamTypeDeclaration is on. This test pins that guarantee so a future change to the bulk-path guard can't accidentally let untyped streams through. Pre-fix: the strengthened end-state test fails with the originally-reported NonExistentStreamException; the untyped-StartStream test passes (the API-level guard fires before the bulk path). Post-fix: both pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bb49e80 to
827a251
Compare
jeremydmiller
added a commit
that referenced
this pull request
Jun 3, 2026
…on pin Three new files, 7 tests covering #4617 section 3a remaining items (return-shape coverage, event-metadata propagation) plus the single-DB conjoined variant of the #4611 regression already pinned for sharded. ## 3a — Return-shape coverage (`AppendWrite/append_return_shapes_under_partitioning.cs`, 3 tests) The Append + StartStream overload matrix has multiple shapes (`params object[]`, `IEnumerable<object>`, single-event-via-params-of-one). Each must route through the bulk `mt_quick_append_events` function and land in the owning tenant's partition. Pinned per the spec's "Return shapes" item so a future overload-resolution change or appender refactor can't silently reshape what hits the partitioned tables. - `append_via_params_object_array_lands_in_tenants_partition` - `append_via_IEnumerable_lands_in_tenants_partition` - `append_single_event_via_params_array_of_one_lands_in_tenants_partition` (single-event boundary case — the loop bound at array_length(event_ids, 1)) ## 3a — Event metadata propagation (`AppendWrite/event_metadata_propagation_under_partitioning.cs`, 2 tests) Per the spec's TenantPropagation pin (#4424) and metadata-flow pin: - `event_TenantId_equals_stream_TenantId_under_partitioning` — shared fixture; pins the TenantPropagation invariant that every event's `TenantId` equals the stream's tenant, never null. - `event_optional_metadata_propagation_under_partitioning` — its own DocumentStore because CorrelationId / CausationId / Headers / UserName metadata columns are opt-in (MetadataConfig.* = true). Pins that the full set propagates from session → each event in the bulk batch, AND that TenantId stays paired with the right tenant alongside the opt-in metadata. ## Section 5 — #4611 single-DB regression pin (`Regressions/Bug_4611_mandatory_stream_type_under_partitioning.cs`, 2 tests) The sharded variant already lives in `Sharded/sharded_tenancy_per_tenant_events.cs` from PR #3. This is the single-DB conjoined variant the spec specifically calls out as missing. Own-store because UseMandatoryStreamTypeDeclaration is a store-wide flag. - `StartStream_with_aggregate_type_followed_by_Append_works` — Start + Append both succeed; mt_streams row exists with `type` = aggregate alias. - `untyped_StartStream_still_throws_StreamTypeMissingException` — companion pin: the API-level guard in EventStore.StartStream STILL fires synchronously for the no-type overload. PR #4613's fix didn't (and shouldn't) weaken this guard. ## Verified - **net9.0**: 118/118 pass (was 111 → +7) - **net10.0**: 118/118 pass ## Subsequent PRs per the #4617 checklist - 3c remaining: EventProjection, FlatTableProjection, MultiStreamProjection RollUpByTenant + AcrossTenants / AddGlobalProjection, custom IAggregateGrouper + fan-out, raw IProjection, same-projection lifecycle equivalence. - 3d remaining: AddMartenManagedTenantsAsync(Guid[]) N-format pin, sharded reuses ONE MartenDatabase per shard, IDynamicTenantSource lifecycle. - 3e remaining: DeleteAllTenantDataAsync orphan-sequence leak pin (needs own-store), RemoveMartenManagedTenantsAsync, AssertDatabaseMatchesConfigurationAsync drift, DDL generation, PerTenantEventSequences schema-object correctness, AutoCreate matrix. - 3f DCB partitioned coverage. - Section 4 daemon in-depth. - Section 5 remaining: 42P01 / 42P16 / 42P07 / MT002-rebuild regression families. - 3a remaining: Quick vs QuickWithServerTimestamps timestamp-source pin, seq_id gap-after-failed-batch. - 3b deferred items. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced Jun 3, 2026
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.
Fixes #4611.
With
UseTenantPartitionedEventsenabled,StartStreamactions are routed through the bulkmt_quick_append_eventsoperation (seeQuickEventAppender.registerOperationsForStreams, whereforceBulkFunctionis set fromUseTenantPartitionedEvents).QuickAppendEventsOperationBase.PostprocessAsynchas a guard forUseMandatoryStreamTypeDeclaration: a stream whose first event comes back as version 1 is treated as an append to a non-existent stream and rejected withNonExistentStreamException. That is correct for anAppend, but a legitimateStartStreamof a brand-new stream also produces version 1 — so under per-tenant partitioning the guard fired for validStartStreamcalls. The events were then tombstoned, and a later append to the (never-created) stream failed too.The fix limits the guard to
Appendactions by excludingStreamActionType.Start.StartStreamof a new stream is allowed; appending to a non-existent stream under mandatory stream types is still rejected.Added a regression test in
sharded_tenancy_per_tenant_events_tests(sharded +UseTenantPartitionedEvents+UseMandatoryStreamTypeDeclaration+ a typedStartStream). The existingmandatory_stream_type_behavioranduse_tenant_partitioned_events_quick_appendsuites still pass, so the append-to-non-existent rejection is preserved.