Skip to content

Fix ancillary store outbox schema for projection side effects (GH-2887)#2888

Merged
jeremydmiller merged 1 commit into
mainfrom
fix-2887-ancillary-outbox-schema
May 23, 2026
Merged

Fix ancillary store outbox schema for projection side effects (GH-2887)#2888
jeremydmiller merged 1 commit into
mainfrom
fix-2887-ancillary-outbox-schema

Conversation

@jeremydmiller
Copy link
Copy Markdown
Member

Problem

Fixes #2887.

In a modular monolith with a main Marten store plus an ancillary store on a separate physical database, integrated via IntegrateWithWolverine(x => x.SchemaName = "debtors"), an async multi-stream projection that calls slice.PublishMessage(...) in RaiseSideEffects fails with:

42P01: relation "public.wolverine_incoming_envelopes" does not exist

The projection-batch outbox writes its envelopes to public.wolverine_* (the main store's schema) instead of the ancillary store's configured debtors schema. On the ancillary database those public tables don't exist, so the batch fails and the side-effect message is never delivered.

A shared database masks the bug — the main store's public.wolverine_* tables happen to exist there, so the wrong-schema write silently succeeds. It only surfaces when the ancillary store is on its own database.

Root cause

MartenToWolverineOutbox.CreateBatch(session) builds a fresh MessageContext, whose Storage defaults to runtime.Storage — the main/default message store (schema public). MartenEnvelopeTransaction then builds schema-qualified envelope SQL from that store and runs it on the projection's (ancillary) session connection.

The same outbox class is used for every store and had no idea which store it belonged to. By contrast, OutboxedSessionFactory<T> correctly calls OverrideStorage(...) to retarget the ancillary store — the projection-batch path never did.

Fix

Thread the ancillary store's marker type into the outbox and retarget storage when present:

  • MartenToWolverineOutbox takes an optional Type? storeType; when set, calls context.OverrideStorage(runtime.Stores.FindAncillaryStore(storeType)).
  • MartenOverrides exposes protected virtual Type? StoreType => null (main store keeps default storage, unchanged); MartenOverrides<T> overrides it to typeof(T).

Test

Adds Bug_2887_ancillary_outbox_schema_on_separate_database — an ancillary store on a separate physical database (bug2887_refs) with a per-store envelope schema (debtors), durable local queues, and an async multi-stream projection that publishes a side effect. Fails with the exact 42P01 before the fix; passes after.

Verification

  • Bug_2887 repro: fails before (exact 42P01), passes after.
  • Regression run across related ancillary/outbox tests (multi_stream_projection_with_side_effects_on_ancillary_store, end_to_end_publish_messages_through_marten_to_wolverine, Bug_2669, Bug_2382 ancillary): 11/11 passed.

🤖 Generated with Claude Code

MartenToWolverineOutbox.CreateBatch built a MessageContext bound to the
runtime's default (main) message store, so projection-batch side-effect
envelope writes for an ancillary Marten store targeted the MAIN store's
schema. On an ancillary store living on a separate physical database
(IntegrateWithWolverine(x => x.SchemaName = ...)), those tables do not
exist -> 42P01 and the side-effect message is never delivered.

Thread the ancillary store's marker type into the outbox (via
MartenOverrides.StoreType, overridden in MartenOverrides<T> to typeof(T))
and OverrideStorage to the ancillary store's own message store when set.
The main store keeps StoreType => null and the default storage, unchanged.

Adds Bug_2887 repro test on a separate physical database with a per-store
envelope schema and durable local queues.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jeremydmiller jeremydmiller merged commit ff27148 into main May 23, 2026
22 checks passed
This was referenced May 28, 2026
This was referenced Jun 2, 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.

MartenToWolverineOutbox ignores per-store AncillaryMartenIntegration.SchemaName for projection-batch envelope writes

1 participant