Skip to content

Commit the events queued by a ProjectionScenario's final step - #5130

Merged
jeremydmiller merged 1 commit into
masterfrom
fix/5126-scenario-trailing-append
Aug 3, 2026
Merged

Commit the events queued by a ProjectionScenario's final step#5130
jeremydmiller merged 1 commit into
masterfrom
fix/5126-scenario-trailing-append

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Closes #5126

The bug

ScenarioAction only flushes the session when the step after it is an assertion:

_action(scenario.Session.Events);
if (scenario.NextStep is ScenarioAssertion)   // null for the last step
{
    await scenario.Session.SaveChangesAsync(ct);
    await scenario.WaitForNonStaleData();
}

NextStep peeks the queue after the current step was dequeued, so it is null on the final step and the branch never runs. Execute's finally then disposes the session, and Marten's dispose does not commit.

Consequences: a scenario ending in an append silently loses those events, and an arrange-only scenario is a complete no-op that passes.

The fix

Flush once more after the step loop. Unconditional on purpose — SaveChangesAsync returns immediately when the unit of work is empty (DocumentSessionBase.SaveChangesAsync checks HasOutstandingWork before doing anything) and WaitForNonStaleData is already a no-op when no daemon is running, so scenarios that already flushed pay nothing for it.

The flush sits inside the try and aggregates into ProjectionScenarioException like any other step, so a failing commit is reported as a scenario failure with the rest rather than escaping raw.

Tests

Two regression tests, both verified to fail on master before the fix:

  • trailing_append_is_committed — an append after an assertion, with nothing following it
  • arrange_only_scenario_actually_writes — a scenario with no assertions at all

None of the eight existing tests covered this, because every one of them happens to end with an assertion.

net9.0: DaemonTests 262 / 0 / 0.

Not addressed here

The same NextStep is ScenarioAssertion rule means consecutive actions batch into a single transaction, so a scenario cannot express "two separate commits" — which is what you would want for optimistic concurrency or per-batch projection behaviour. That is a design decision rather than a defect, and it is called out in #5126 and #5127 rather than changed silently here.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VpDCvJcBDZerieJB4JEHde

Closes #5126

ScenarioAction only flushes the session when the step AFTER it is an assertion:

    _action(scenario.Session.Events);
    if (scenario.NextStep is ScenarioAssertion)   // null for the last step
    {
        await scenario.Session.SaveChangesAsync(ct);
        await scenario.WaitForNonStaleData();
    }

NextStep peeks the queue after the current step was dequeued, so it is null on
the final step and the branch never runs. Execute's finally then disposes the
session, and Marten's dispose does not commit -- so a scenario ending in an
append silently lost those events, and an arrange-only scenario was a complete
no-op that passed.

Flushes once more after the step loop. Unconditional on purpose: SaveChangesAsync
returns immediately when the unit of work is empty (DocumentSessionBase checks
HasOutstandingWork before doing anything), and WaitForNonStaleData is already a
no-op when no daemon is running, so scenarios that already flushed pay nothing.
The flush is inside the try and aggregates into ProjectionScenarioException like
any other step, so a failing commit reports as a scenario failure rather than
escaping raw.

Two regression tests, both verified to fail without the fix: a trailing append
after an assertion, and a scenario with no assertions at all. None of the eight
existing tests covered this because every one of them happens to end with an
assertion.

net9.0: DaemonTests 262/0/0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VpDCvJcBDZerieJB4JEHde
@jeremydmiller
jeremydmiller merged commit 9c71630 into master Aug 3, 2026
10 checks passed
@jeremydmiller
jeremydmiller deleted the fix/5126-scenario-trailing-append branch August 3, 2026 01:02
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.

ProjectionScenario silently drops a trailing append — the events are never committed

1 participant