Skip to content

fix(journey): map session-store status into the workflow loop vocabulary (EVO-1690)#53

Merged
dpaes merged 1 commit into
developfrom
fix/EVO-1690
Jun 11, 2026
Merged

fix(journey): map session-store status into the workflow loop vocabulary (EVO-1690)#53
dpaes merged 1 commit into
developfrom
fix/EVO-1690

Conversation

@nickoliveira23

@nickoliveira23 nickoliveira23 commented Jun 10, 2026

Copy link
Copy Markdown

Summary

  • JourneyExecutionWorkflow copied the pre-existing session's status verbatim into its internal state, but the session store speaks 'active'/'waiting' while the execution loop only advances on 'running' — so while (state.status === 'running') was false on the first iteration and the journey "completed successfully" with completedNodes: 0, no error anywhere. Since EVO-1644 pre-creates the session before starting the workflow, every manually triggered journey silently executed zero nodes.
  • Extracted the decision into resolveInitialWorkflowStatus() (pure, regression-tested): only 'paused' survives the mapping (a paused session must not resume by itself); everything else enters the loop as 'running'.
  • Semantic note: a session read back as 'failed' now also re-enters the loop on workflow (re)start — previously it silently completed with zero nodes, which was the bug's same shape, so re-running is the correct retry semantics.
  • The line dates to the initial public-release commit and was unreachable before EVO-1644.

Test plan

  • npm test -- initial-workflow-status — 5 regression specs (active/waiting/completed/undefined → running; paused → paused)
  • npm test -- src/modules/temporal — 49 specs green
  • npm run typecheck — clean
  • Live E2E (2026-06-10): the same manual trigger that produced completedNodes: 0 executed trigger → send-message and created the message in the CRM conversation (journey 'E2E Smoke', conversation bb860c2f…). Discovered during validation (documented on the card): flows that don't end in exit-journey-node deliberately stay active for 30 days by design (condition(() => false, '30d')) — orthogonal to this fix.

Changed Files

  • src/modules/temporal/workflows/initial-workflow-status.util.ts (+ spec)
  • src/modules/temporal/workflows/journey-execution.workflow.ts — surgical +5/-4

Linked Issue

  • EVO-1690

Summary by Sourcery

Map journey session-store statuses into the workflow execution loop vocabulary so pre-created sessions enter the loop correctly and execute nodes.

Bug Fixes:

  • Ensure journeys with pre-created sessions start in a 'running' workflow state instead of silently completing with zero nodes when session status is 'active' or 'waiting'.
  • Preserve 'paused' sessions so they do not resume automatically when a workflow is (re)started.

Enhancements:

  • Introduce a dedicated utility for resolving initial workflow status from persisted session status and type it with a workflow execution status union.
  • Add regression tests covering the mapping of session statuses into initial workflow execution statuses.

…ary (EVO-1690)

The session store speaks 'active'/'waiting'/'completed'; the execution loop
only advances on 'running'. Copying the pre-created session's status
verbatim (the norm since EVO-1644) made the loop condition false on the
first iteration: every journey 'completed successfully' with zero nodes
executed and no error anywhere. Only 'paused' survives the mapping - a
paused session must not resume by itself.

Extracted as a pure util with regression specs; validated live: the same
manual trigger that produced completedNodes: 0 now executes
trigger -> send-message and creates the message in the CRM conversation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Reviewer's Guide

Maps persisted journey-session statuses into the workflow engine’s internal status vocabulary so pre-created sessions correctly enter the execution loop, and adds targeted tests for the new status resolution utility.

File-Level Changes

Change Details Files
Centralize and correct mapping of persisted session status into workflow execution status.
  • Introduce WorkflowExecutionStatus type representing the workflow loop’s status vocabulary.
  • Add resolveInitialWorkflowStatus(sessionStatus) utility that maps any non-'paused' status (including undefined, 'active', 'waiting', 'completed', 'failed') to 'running' while preserving 'paused'.
  • Document the semantics and EVO-1690 regression context in the utility’s JSDoc.
src/modules/temporal/workflows/initial-workflow-status.util.ts
Use the new status resolution logic when resuming JourneyExecutionWorkflow from an existing session.
  • Replace inline conditional logic that copied existingSession.status verbatim (with a special case for 'completed') with a call to resolveInitialWorkflowStatus(existingSession.status).
  • Clarify with an inline comment that session-store status vocabulary differs from workflow loop status vocabulary and that this caused the loop to never start for pre-created sessions.
src/modules/temporal/workflows/journey-execution.workflow.ts
Add regression tests for initial workflow status resolution.
  • Create tests that assert active/waiting/completed/undefined map to 'running'.
  • Create a test that asserts 'paused' stays 'paused' so the workflow does not auto-resume paused sessions.
  • Ensure tests cover the EVO-1690 regression scenario around pre-created sessions.
src/modules/temporal/workflows/initial-workflow-status.util.spec.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • Consider narrowing sessionStatus: string | undefined in resolveInitialWorkflowStatus to the concrete session-store status type (e.g., a union or existing type alias) so that impossible or misspelled values are caught at compile time rather than defaulting silently to 'running'.
  • Since WorkflowExecutionStatus includes 'completed' and 'failed' but resolveInitialWorkflowStatus currently only returns 'running' or 'paused', either extend the mapping logic to return all variants where appropriate or trim the union to the statuses this function can actually produce to avoid confusion.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider narrowing `sessionStatus: string | undefined` in `resolveInitialWorkflowStatus` to the concrete session-store status type (e.g., a union or existing type alias) so that impossible or misspelled values are caught at compile time rather than defaulting silently to `'running'`.
- Since `WorkflowExecutionStatus` includes `'completed'` and `'failed'` but `resolveInitialWorkflowStatus` currently only returns `'running'` or `'paused'`, either extend the mapping logic to return all variants where appropriate or trim the union to the statuses this function can actually produce to avoid confusion.

## Individual Comments

### Comment 1
<location path="src/modules/temporal/workflows/initial-workflow-status.util.ts" line_range="16-17" />
<code_context>
+ * with zero nodes executed. Only 'paused' survives the mapping: a paused
+ * session must not resume by itself.
+ */
+export function resolveInitialWorkflowStatus(
+  sessionStatus: string | undefined,
+): WorkflowExecutionStatus {
+  return sessionStatus === 'paused' ? 'paused' : 'running';
</code_context>
<issue_to_address>
**suggestion:** Tighten the `sessionStatus` type to known session-store statuses instead of `string`.

`string | undefined` makes it easy for unsupported statuses to slip in unnoticed. If the session store exposes a finite set of statuses, model that as a string union type (e.g. `'active' | 'waiting' | 'completed' | 'paused' | undefined`) so TypeScript can enforce exhaustive handling when new statuses are introduced.

Suggested implementation:

```typescript
/**
 * Maps a persisted journey-session status into the workflow loop's vocabulary
 * (EVO-1690). The session store speaks 'active'/'waiting'/'completed'; the
 * execution loop only advances on 'running' — copying the session status
 * verbatim made `while (state.status === 'running')` false on the first
 * iteration and every pre-created session (the norm since EVO-1644) completed
 * with zero nodes executed. Only 'paused' survives the mapping: a paused
 * session must not resume by itself.
 */
export type JourneySessionStoreStatus =
  | 'active'
  | 'waiting'
  | 'completed'
  | 'paused';

export function resolveInitialWorkflowStatus(
  sessionStatus: JourneySessionStoreStatus | undefined,
): WorkflowExecutionStatus {
  return sessionStatus === 'paused' ? 'paused' : 'running';
}

```

If there is already a shared type for the journey-session status (for example in a session-store module), you should reuse that instead of introducing `JourneySessionStoreStatus` here, and import it into this file. Replace the local `JourneySessionStoreStatus` definition with an import and keep the parameter typed as that union plus `undefined`.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +16 to +17
export function resolveInitialWorkflowStatus(
sessionStatus: string | undefined,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Tighten the sessionStatus type to known session-store statuses instead of string.

string | undefined makes it easy for unsupported statuses to slip in unnoticed. If the session store exposes a finite set of statuses, model that as a string union type (e.g. 'active' | 'waiting' | 'completed' | 'paused' | undefined) so TypeScript can enforce exhaustive handling when new statuses are introduced.

Suggested implementation:

/**
 * Maps a persisted journey-session status into the workflow loop's vocabulary
 * (EVO-1690). The session store speaks 'active'/'waiting'/'completed'; the
 * execution loop only advances on 'running' — copying the session status
 * verbatim made `while (state.status === 'running')` false on the first
 * iteration and every pre-created session (the norm since EVO-1644) completed
 * with zero nodes executed. Only 'paused' survives the mapping: a paused
 * session must not resume by itself.
 */
export type JourneySessionStoreStatus =
  | 'active'
  | 'waiting'
  | 'completed'
  | 'paused';

export function resolveInitialWorkflowStatus(
  sessionStatus: JourneySessionStoreStatus | undefined,
): WorkflowExecutionStatus {
  return sessionStatus === 'paused' ? 'paused' : 'running';
}

If there is already a shared type for the journey-session status (for example in a session-store module), you should reuse that instead of introducing JourneySessionStoreStatus here, and import it into this file. Replace the local JourneySessionStoreStatus definition with an import and keep the parameter typed as that union plus undefined.

@dpaes dpaes left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Code review — APPROVED (EVO-1690)

Fixes the exact root cause: the workflow copied the persisted session status (active/waiting) verbatim into state.status, so while (... state.status === 'running') never ran and every manually-triggered journey completed with 0 nodes. Fix extracts resolveInitialWorkflowStatus()paused stays paused, everything else → running — wired into the init state. Verified the trigger path end-to-end (startJourney pre-creates ACTIVE → workflow maps ACTIVE→running → loop entered).

Acceptance criteria

  • AC1 manual trigger executes nodes E2E ✓ (live-validated; see caveat)
  • AC2 regression: active/waiting → loop ✓ (5 util specs)
  • AC3 paused stays paused (resume preserved) ✓

Non-blocking

  • [low] Mapper preserves only paused; terminal statuses (completed/failed/cancelled) collapse to running. Intentional ("failed re-runs instead of silently completing") and only reachable on a worker-crash replay; allowlist-by-exclusion — note if new statuses are added.
  • [low] No workflow-level integration test; AC1's "message created" is self-reported (evo-flow CI = Sourcery only, util spec not even gated). Fix is small + visible, so acceptable.
  • [nit] 30-day stuck-active discovery (flow with no exit node) correctly out of scope — surface to product.

Approving.

@dpaes dpaes merged commit 06a8e69 into develop Jun 11, 2026
2 checks passed
@dpaes dpaes deleted the fix/EVO-1690 branch June 11, 2026 01:03
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.

2 participants