Skip to content

Live aggregation stamps a strong-typed aggregate id (#426) - #428

Merged
jeremydmiller merged 1 commit into
mainfrom
fisher-426-strong-typed-live-id
Oct 4, 2026
Merged

jeremydmiller merged 1 commit into
mainfrom
fisher-426-strong-typed-live-id

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Closes #426.

The bug. AggregateIdentity.TrySetIdentity backfills the stream id onto an aggregate whose Create didn't set one, as Marten and Polecat do. It only assigned a value the identity member could hold as-is, and a raw Guid is never a KilnId. So a strong-typed id was skipped, and the aggregate came back with a default Id from live aggregation, FetchForWriting (by raw id and by wrapper), FetchManyForWriting and ProjectLatest. The fold was correct; only the identity was wrong, with no error. It isn't AOT-specific: it reproduced under the JIT on 2.80.1.

The fix. When the member is a strong-typed wrapper around the stream id's own type, the id is wrapped through StrongTypedId.Wrap and then assigned. A Guid is never turned into a string-backed id or the reverse, the same rule FetchForWriting<T, TId> refuses by.

Verification.

  • strong_typed_aggregate_identity covers each of those paths plus a string-backed wrapper; 5 of its 7 tests fail against the old backfill. The inline snapshot never had the bug (it goes through the projection's identity setter), and a test pins that, since the issue couldn't say either way.
  • The AOT smoke now asserts both strong-typed aggregates' ids. Native publish and run on macOS arm64 pass.
  • Full CI-shaped run green on net9.0 and net10.0: 2538 / 36 / 31. check_scoreboard.py agrees for both TFMs; leak check clean.

🤖 Generated with Claude Code

AggregateIdentity.TrySetIdentity backfills the stream id onto an
aggregate whose Create did not set it, as Marten and Polecat do. It
assigned only a value the identity member could hold as-is, and a raw
Guid is never a KilnId, so a strong-typed id was skipped and the
aggregate came back with a default id from live aggregation,
FetchForWriting, FetchManyForWriting and ProjectLatest. The fold was
right and the identity was not, silently. Not AOT-specific: it
reproduced under the JIT.

It now wraps the stream id through StrongTypedId.Wrap when the member is
a wrapper around the stream id's own type. A Guid is never turned into a
string-backed id or the reverse, the rule FetchForWriting<T, TId> refuses
by.

strong_typed_aggregate_identity fails 5 of 7 against the old backfill.
The inline snapshot never had the bug (it goes through the projection's
identity setter), and that test pins it. The AOT smoke asserts both
strong-typed aggregates' ids natively.

Closes #426

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jeremydmiller
jeremydmiller merged commit d749468 into main Oct 4, 2026
3 checks passed
@jeremydmiller
jeremydmiller deleted the fisher-426-strong-typed-live-id branch October 4, 2026 21:53
@jeremydmiller jeremydmiller mentioned this pull request Oct 4, 2026
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.

Live aggregation leaves a strong-typed aggregate id at its default

1 participant