Durable handoff (version-controlled at
docs/handoffs/). The next session's job is to start Saga #2, design-first. Committed by Erik on 2026-06-30.
Begin Saga #2 — CritterMart's second Wolverine.Saga, this one EF-Core-backed in the Identity
bounded context, to prove the saga store is swappable (the contrast with the Marten-backed Saga #1 is the
whole point). Run it design-first:
- ADR-009 revisit — the next session's deliverable (a decision-only session; no code).
- then: Workshop 002 amendment → OpenSpec proposal → narrative → prompt → implement → retro.
Do not jump to code. The ADR-009 revisit is the gate; produce that first.
docs/decisions/009-polecat-deferred-for-round-one.md established Identity as the deliberately boring,
non-event-sourced EF-Core data store (no Polecat) — its teaching value is being the persistence-agnostic
foil. Saga #2 gives Identity its first stateful consumer. The revisit question: does "deliberately
boring CRUD" still hold, or is the saga the intended next proof of the persistence-agnostic thesis?
Guard (from the research): the saga must stay "EF Core doing relational things the Wolverine way," NOT
drift into re-implementing event sourcing on SQL. The revisit can also absorb/formalize the saga-showcase
ADR that Saga #1 never got a dedicated one for (decision lives in the feasibility note + Workshop 001 +
retro 035): "CritterMart uses convention sagas, additively, alongside PMvH (ADR 007)."
- Flow:
RequestEmailChange→ opens anEmailChangesaga (holdsPendingEmail, schedules a 24hEmailChangeTimeout) →ConfirmEmailChangewithin the window → apply (setCustomer.Email, still respecting theux_customers_emailunique index) +MarkCompleted(); on timeout → drop +MarkCompleted(). - Why this flow and not name/address edits: it's the smallest honest Identity saga — a real confirm-or-expire window. A name/address change is a single mutating command with no awaited second event, so a saga there is ceremony. Don't invent a workflow.
- Plumbing already wired (confirmed from docs): EF-Core saga storage is activated by the existing
AddDbContextWithWolverineIntegration<IdentityDbContext>call insrc/CritterMart.Identity/Program.cs— no new infra. The saga is just aDbSet<EmailChange>onIdentityDbContext, string key (= CustomerId), mapped with the same lowercase-column discipline theCustomerentity uses. Postgres dodges the SQL-ServeruseNVarCharForStringIdfootgun. It rides the existingUseEntityFrameworkCoreWolverineManagedMigrations(no extra DDL); keyed by PK so no secondary index.
- A message that BOTH starts and continues a saga uses ONE
StartOrHandlemethod — NOT separate staticStart+ instanceHandlefor the same type (that resolves to the continuation path → blank-id saga → Marten/EF "id null or empty" error). For EmailChange,RequestEmailChangeis the start. NotFound(msg)static methods are MANDATORY for the spec's "silent no-op" — Wolverine THROWS otherwise on a non-start message for a missing/completed saga (e.g. aConfirmEmailChangearriving after the window expired, or anEmailChangeTimeoutfiring after confirmation already completed the saga). Empty bodies suffice.[SagaIdentity]lives inWolverine.Persistence.Sagas(notWolverine.Attributes) — correlate onCustomerIdsince it isn't namedId/SagaId.TimeoutMessageis a record:record EmailChangeTimeout(string CustomerId) : TimeoutMessage(duration)compiles. Prefer a config-driven duration (mirror Saga #1'sReplenishDeadlineconfig-singleton) so a demo knob can shorten it.- ctx7 was quota-blocked on 2026-06-30 — fallback is a direct WebFetch of
wolverinefx.net/guide/durability/efcore/sagas.html(+guide/durability/sagas.html).
<claude-memory>/next-pickup.md— authoritative pickup state (auto-loaded viaMEMORY.md).docs/research/wolverine-saga-feasibility.md— Candidate #2 section is the saga blueprint (shape, the two plumbing unknowns resolved, the promotion-path table, "why this flow").docs/research/identity-ef-core-first-class-expansion.md— the parent "first-class EF-Core Identity" idea; the saga is its concrete first flow (open question #2 = "does the contrast survive?").docs/decisions/009-polecat-deferred-for-round-one.md— the ADR to revisit.docs/workshops/002-identity-event-model.md— exists; amend it with the email-change slices.- Identity code:
src/CritterMart.Identity/Program.cs,Customers/IdentityDbContext.cs,Features/RegisterCustomer.cs. - Saga #1 reference implementation (Marten-backed):
src/CritterMart.Inventory/Stock/Replenishment.cs+ its siblings; retrodocs/retrospectives/implementations/035-slices-2-5-2-7-replenishment-saga.md; the archived change atopenspec/changes/archive/2026-06-30-slices-2-5-2-7-replenishment-saga/.
| Step | Artifact |
|---|---|
| Saga-showcase decision (additive, NOT a PMvH conversion) + Identity grows a stateful consumer | ADR-009 revisit |
| The email-change slices (events / commands / saga / timeout) | Workshop 002 amendment → OpenSpec → narrative → prompt → implement → retro |
Sequencing was settled with Erik: Inventory first (done — Saga #1), then Identity once ADR-009 is revisited — exactly where we are now. A workshop amendment also satisfies the design-return cadence.
main@9ac3db0, clean tree. Shipped this session (all merged + post-merged + live-verified):- PR #115 — Saga #1 demo affordances (
Inventory__ReplenishTimeout=25sknob,demo-traffic.ps1 -Backorder/-Cover, runbook §5c). - PR #116 — Aspire aligned to 13.4.6 (SDK + Hosting).
- PR #117 — CritterWatch 1.0.0-beta.1 + WolverineFx 6.16.0 + a necessitated Marten-9.12
stateless-projection fix (retro
docs/retrospectives/chore/004-critterwatch-next-release-upgrade.md).
- PR #115 — Saga #1 demo affordances (
- Tooling: .NET 10 / C# 14, Wolverine 6.16.0, Marten 9.12.0, CritterWatch 1.0.0-beta.1, Aspire 13.4.6, EF Core 10 (Npgsql 10.0.2) in Identity.
- DO NOT bump Wolverine PAST 6.16.0 until a newer CritterWatch targets it — same startup
TypeLoadExceptioncrash class. CW beta.1 ↔ WolverineFx 6.16.0 is the current coupling (Directory.Packages.propscomments are the canonical record). The old "no 6.13.x" hold is resolved. - Marten 9.12 gotcha: instance-registered inline projections lose constructor-injected state — keep projections stateless, apply per-projection config on the read side. (Relevant if Saga #2 adds a view.)
- MessagePack CVE (
GHSA-hv8m-jj95-wg3x) still suppressed (beta.1 → 2.5.302 < the 3.0.214 fix); re-check on the next CW release. - CritterWatch trial expires 2026-07-10.
- Post-talk: delete the FOUR AppHost demo knobs (
Payment__DeclineOverAmount,Payment__AuthDelay,Orders__PaymentTimeout,Inventory__ReplenishTimeout). - Dependabot #104/#105 will self-regenerate (their branches were deleted on close); #106/#107/#109 (otel, test-stack, Swashbuckle) remain as separate concerns.
Present options + a recommendation at genuine forks and let Erik decide ([[feedback-collaborate-on-decisions]]), ideally as 2–4 AskUserQuestion options with rich previews ([[feedback-options-with-previews]]); prefer tool-backed over freeform ([[feedback-prefer-tool-backed-over-freeform]]); live-verify after substantial changes and drive the demo flows yourself ([[feedback-live-verify-after-changes]], [[feedback-drive-demo-flows]]); when asked to "persist/make durable," ask where it should live ([[feedback-ask-where-to-persist]]).
- ADR-009 revisit: no special skill — follow the
docs/decisions/ADR format; cross-reference ADR 007 (PMvH) and the feasibility note. - Workshop amendment: the in-repo
event-modelingskill. - Implementation:
critterstack-arch-new-project-wolverine-efcore,wolverine-handlers-efcore,wolverine-handlers-declarative-persistence;find-docs/ctx7 (or the WebFetch fallback above) for Wolverine EF-Core saga specifics. - OpenSpec:
opsx:propose/openspec-propose.