Repository navigation
GH-5673 TombstoneStream (hard stream delete) on JasperFx 2.90.0 - #5674
Merged
Merged
Conversation
Bumps the JasperFx family to 2.90.0 and implements IEventStoreOperations.TombstoneStream(Guid|string) as DeleteStreamOperation, a queued single-statement delete of the stream's tag rows, events and 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. Fills the EventAuxiliaryOperations.TombstoneStream slot in PostgresEventStoreDialect. Enrolls StreamTombstoningCompliance, flips SupportsTombstoneStream, and adds Marten-specific tests for archived and tenant partitioning, strict identity enforcement, projected documents and the identity mismatch guard. Carries #1018's RequiresUnreferencedCode/RequiresDynamicCode up to RegisterTagType<TTag> and adds the AOT-safe RegisterTagType<TTag, TInner> (unwrapper) backed by TagTypeRegistration.FromUnwrapper. Documents TombstoneStream in docs/events/archiving.md and the AOT tag registration in docs/events/dcb.md. Closes #5673 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J8KxC18Q51z5w4u6YARwe4
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 15, 2026
This was referenced Oct 16, 2026
erdtsieck
pushed a commit
to erdtsieck/marten
that referenced
this pull request
Oct 19, 2026
… cannot work Supersedes JasperFx#5663, whose API half landed independently in JasperFx#5674. What is left is the part JasperFx#5674 did not carry: the diagnostics that point at the overload, the argument validation, and the native coverage. RegisterTagType<TTag>() reflects over the tag's members with ValueTypeInfo and closes TagTypeRegistration<TTag, TInner> with MakeGenericType. JasperFx#5674 annotated both inferring overloads RequiresUnreferencedCode/RequiresDynamicCode, but an analyzer warning only reaches an application that is itself AOT-analyzed -- a store configured from inside a library gets nothing, which is how JasperFx#5649 was reported in the first place. EventGraph.CreateTagType now turns the failure into an InvalidOperationException naming the one line to write, keeping the original as its inner exception. The two halves are gated differently on purpose. NotSupportedException and MissingMethodException mean the generic could not be closed, which only happens with no JIT, so they are reinterpreted only when dynamic code is unavailable. InvalidValueTypeException is ambiguous -- the tag may genuinely not be a single-value wrapper, or a trimmed image may have lost the members that say so, and no runtime flag separates those because a trimmed non-AOT image still has dynamic code -- so it is reinterpreted either way, with a message covering both readings. The explicit overload is the answer to both. Auto-discovery from projection identities had the inverse of that problem. JasperFx#5657 logs a warning for a discovery failure, but screens InvalidValueTypeException into a silent catch as "this identity was never a value type" -- which is exactly how a trimmed native image reports a perfectly good tag whose members were trimmed. The one scenario JasperFx#5649 reported was the one scenario that stayed quiet. IsToleratedDiscoveryFailure now tolerates it only while dynamic code is available, and the warning names the registration line including the projection's ForAggregate<T>(). A trimmed non-AOT image is still silent, because guessing the other way would warn on every projection whose identity is an ordinary custom type. Argument validation the overload did not have: a null unwrapper is refused, and re-registering a tag type under a DIFFERENT inner type now throws rather than silently keeping the first registration and leaving this call's unwrapper unused. BEHAVIOUR CHANGE: an empty or whitespace tag table suffix is refused on every RegisterTagType overload. mt_event_tag_ is a real table name, so "" was accepted and put every tag type registered that way into one shared table. Pass null for the default. Marten.AotRuntimeSmoke gains six DCB checks -- tagging, a TagWith rule, Guid- and string-backed tag queries, EventsExistAsync, FetchForWritingByTags into a [BoundaryAggregate] with an append, DcbConcurrencyException, and the inferring overload's message. Verified by RUNNING the native osx-arm64 binary: 48/48, and the remedy message does fire natively. Marten.AotSmoke registers a tag through the overload to hold the consumer-side analyzer axis clean. Co-authored-by: Tore Hammervoll <hammervoll.tore@gmail.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01481eiEZg1Bv6pu4DrgPRg3
This was referenced Oct 20, 2026
This was referenced Oct 22, 2026
This was referenced Oct 29, 2026
Open
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.
Closes #5673
What
JasperFx 2.90.0. Bumps all five JasperFx pins from 2.88.0. No Weasel bump: 9.45.1, the only newer Weasel, patches the SQL Server and Oracle providers only, and restore and build are clean on 9.45.0.
TombstoneStream(Guid)/TombstoneStream(string)onsession.Events. It is queued and runs atSaveChangesAsync. The work is done by a new internalDeleteStreamOperation, named that way because "tombstone" already means Marten's sequence-gap bookkeeping stream. It goes through the shared seam:PostgresEventStoreDialect.BuildAuxiliaryOperationsnow fills theTombstoneStreamslot, and its doc comment is updated.mt_event_tag_*table (keyed by the stream'sseq_ids), thenmt_events, thenmt_streams_identitywhen strict identity enforcement is on, thenmt_streams. Natural key rows cascade frommt_streams.;-separated statements in a positional-parameter command. Starting extra batch commands inside one operation would breakOperationPage's alignment of one command per operation. All CTEs read the same snapshot, so the tag delete still finds the events, and the FK checks run at the end of the statement. I verified this with partitioned FKs directly in Postgres before writing it.tenant_id. Deleting through the parent tables reaches the archived partition, and the tenant predicate prunes to the tenant's partition underUseTenantPartitionedEvents.EnsureAsGuidStorage/EnsureAsStringStorage, so calling the wrong overload throwsInvalidOperationException.AOT (#1018). The
[RequiresUnreferencedCode]/[RequiresDynamicCode]attributes are carried up toRegisterTagType<TTag>()andRegisterTagType<TTag>(string), on bothIEventStoreOptionsandEventGraph. There is a new trim-safe overload,RegisterTagType<TTag, TInner>(Func<TTag, TInner> unwrapper, string? tableSuffix = null), backed byTagTypeRegistration.FromUnwrapper. Marten builds with no IL2026/IL3050 from tag registration. TheGetMethod(nameof(TagTypeRegistration.Create))auto-discovery lookup is unchanged and still resolves.Tests
All runs are local against Postgres on :5432.
stream_tombstoning_compliance(StreamTombstoningCompliance,SupportsTombstoneStream => true): 9/9.EventSourcingTests/Archiving/tombstoning_streams.cs: 11 facts.UseTenantPartitionedEventsdeletes only the session tenant.src/Marten.slnx: builds with 0 errors.Docs
docs/events/archiving.md: new section "Deleting a Stream Permanently", with a snippet sample and a comparison table againstArchiveStream. It covers queueing untilSaveChangesAsync, tag rows, tenant scoping, the missing-stream no-op, partition coverage, sequence numbers not being reused, and projected documents being left alone.docs/events/dcb.md: new section on registering tag types under trimming or Native AOT.Found along the way (not fixed here)
Archiving a tagged stream under
UseArchivedStreamPartitioningfails with an FK violation.mt_archive_streammoves the events to the archived partition but not the tag rows that reference them (mt_event_tag_*_seq_id_is_archived_fkey). This is unrelated to this change and is noted in the new test. It probably deserves its own issue.🤖 Generated with Claude Code
https://claude.ai/code/session_01J8KxC18Q51z5w4u6YARwe4