Skip to content

#4509: document FetchForWriting inline-snapshot no-mutation contract + decider regression test#4512

Merged
jeremydmiller merged 1 commit into
masterfrom
fix/4509-document-fetchforwriting-mutation-contract
May 19, 2026
Merged

#4509: document FetchForWriting inline-snapshot no-mutation contract + decider regression test#4512
jeremydmiller merged 1 commit into
masterfrom
fix/4509-document-fetchforwriting-mutation-contract

Conversation

@jeremydmiller
Copy link
Copy Markdown
Member

Addresses #4509.

Resolution: by-design contract + documentation (not a behavior change)

#4509 reports that with FetchForWriting<T> + an inline snapshot + the V9 default UseIdentityMapForAggregates = true, mutating stream.Aggregate in place and then appending events makes the persisted snapshot = (mutated aggregate) + (events), which diverges from the AggregateStreamAsync rebuild.

Root cause (same mechanism as #4439): FetchForWriting calls session.UseIdentityMapFor<TDoc>() and stashes the fetched aggregate in the identity map. On SaveChangesAsync the inline projection's baseline load (LoadManyAsync) reads that same mutated instance rather than a fresh DB snapshot, then applies the new events on top. The optimization deliberately reuses the fetched aggregate as the apply baseline, so it is only correct under the decider pattern (return events, never mutate stream.Aggregate).

Per maintainer decision, resolving as contract/documentation rather than changing the optimization:

Follow-up (separate repo)

The 4 failing Wolverine [AggregateHandler] tests are a Wolverine-side fix — those handlers/tests should not mutate the aggregate. Filed as a Wolverine follow-up issue (linked in #4509).

Test plan

  • decider_pattern_snapshot_matches_event_rebuild_4509 passes (net10).
  • Existing silently_turns_on_identity_map_for_inline_aggregates still passes (net10).
  • markdownlint + cspell clean on both docs.

🤖 Generated with Claude Code

…t + decider-pattern regression test

#4509 reports that mutating stream.Aggregate from FetchForWriting under an
inline snapshot + UseIdentityMapForAggregates=true (the V9 default) makes the
persisted snapshot diverge from the AggregateStreamAsync rebuild. This is the
same mechanism as #4439 and is by design: the optimization reuses the fetched
aggregate as the inline projection's apply baseline, so in-place mutation is
unsupported.

Resolving as contract/documentation rather than a behavior change:
- Cross-reference #4509 in the existing UseIdentityMapForAggregates warnings
  (command_handler_workflow.md + migration-guide.md), and state explicitly that
  the snapshot/rebuild divergence is by design — honor the decider pattern or
  set the flag to false.
- Add decider_pattern_snapshot_matches_event_rebuild_4509 pinning the supported
  path: FetchForWriting -> append events without mutating stream.Aggregate ->
  persisted snapshot equals AggregateStreamAsync.

The failing Wolverine [AggregateHandler] tests are a Wolverine-side fix (the
handler should not mutate the aggregate) — filed separately.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant