Implement IEventDatabase.DeleteProjectionProgressByShardNameAsync (#4785)#4786
Merged
Merged
Conversation
) Override the store-agnostic abstraction added in JasperFx.Events 2.16.0 (#473 / #474) so Marten can drop a single mt_event_progression row by its raw ShardName.Identity, bypassing the registered-projection lookup that DeleteProjectionProgressAsync goes through (and that throws ArgumentOutOfRangeException for an unregistered name). This is the eject path for an orphan shard whose projection has been renamed/versioned/removed since the row was written. - Bump JasperFx + JasperFx.Events + sourcegens 2.15.0 -> 2.16.0 - Override on MartenDatabase queues a DeleteProjectionProgress against the EventGraph progression table via a lightweight session (SessionOptions.AllowAnyTenant so per-tenant identities pass through); non-existent identity is a clean no-op - Per-tenant scoping flows through the identity itself (the trailing :tenantId on the 3-segment {Name}:{ShardKey}:{tenantId} grammar) - Bug_4785 regression test covers: orphan delete, no-op on unknown name, isolation across siblings, per-tenant identity scoping Unblocks CritterWatch#476 Part 1 (operator "Eject Shard" action). Closes #4785 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an exhaustive permutation walk over every distinct Identity shape the
ShardName ctors / Compose() can produce, pinning both the JasperFx grammar
contract (expected literal per input) and the exact-match WHERE clause in
DeleteProjectionProgress.
Permutations covered:
- 2-segment Name:ShardKey (version=1, no tenant) — default-shardKey ctor
variant separated as its own Fact since it's a distinct entry point
- 3-segment Name:V{n}:ShardKey (version>1, no tenant)
- 3-segment Name:ShardKey:tenant (version=1, tenant set)
- 4-segment Name:V{n}:ShardKey:tenant (version>1, tenant set)
- HighWaterMark literal (ctor collapses Identity to the constant)
- All grammars seeded together + targeted delete confirms zero collateral
Also confirms Compose() produces the same Identity as the 4-arg ctor for
each row. 15 tests total, all green on net10.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Strengthen Bug_4785 coverage around the version+tenant axis the user
flagged. Two new tests:
- deleting_one_does_not_touch_a_sibling_that_differs_by_one_axis —
Theory walking every "differ by ONE axis" pair (version-only,
tenant-only, shardKey-only, name-only, AND a combined version+tenant
pair). Asserts the exact-match WHERE clause never picks up a sibling
by prefix, normalization, or accident. Catches any future regression
if the delete path ever grew a fuzzy match.
- writer_and_deleter_agree_on_identity_for_versioned_per_tenant_shard
— drives an InsertProjectionProgress through a real lightweight
session for a 4-segment Name:V{n}:ShardKey:tenant identity, then
deletes via the override. Proves byte-for-byte agreement between the
real Marten writer and the new delete contract when BOTH knobs are on.
24 tests, all green on net10.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced Jun 26, 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.
Summary
IEventDatabase.DeleteProjectionProgressByShardNameAsync(WritePatchByType #473 / WritePatchByType - inject into writer stream #474). The default implementation throwsNotSupportedException(silent no-op on a delete is dangerous), so Marten must override it.MartenDatabase: queuesDeleteProjectionProgress(Events, shardIdentity)againstmt_event_progressionvia a lightweight session (AllowAnyTenant = trueso per-tenant identities pass through). The match is on the exactnamevalue — bypassing theProjections.Alllookup thatIEventStore<,>.DeleteProjectionProgressAsyncuses (and that throwsArgumentOutOfRangeExceptionfor an unregistered name).{Name}:{ShardKey}:{tenantId}3-segment identity deletes only that one tenant's row.This is the eject path for an orphan shard — one whose projection has been renamed/versioned/removed since the row was written, or that was left behind by a topology change. Today the registered-projection-keyed delete throws in exactly that scenario.
Unblocks CritterWatch#476 Part 1 (operator "Eject Shard" action). Polecat needs the symmetric override (separate issue).
Test plan
Bug_4785_delete_projection_progress_by_shard_name(4 tests, all green locally on net10):deletes_an_orphan_progression_row_by_raw_shard_identity— row written via raw SQL with no matching projection registered; delete drops it.non_existent_identity_is_a_clean_no_op— unknown identity does not throw; nothing collateral touched.only_the_matching_identity_is_deleted— three sibling rows seeded; only the named one is removed.per_tenant_identity_deletes_only_that_tenants_row—:tenantId-suffixed identity scopes the delete to one tenant.Closes #4785
🤖 Generated with Claude Code