Skip to content

Route raw workflow queries through the configured storage connection#394

Merged
rmcdaniel merged 1 commit into
durable-workflow:v2from
discovery-ukraine:fix/command-sequence-storage-connection
Jun 8, 2026
Merged

Route raw workflow queries through the configured storage connection#394
rmcdaniel merged 1 commit into
durable-workflow:v2from
discovery-ukraine:fix/command-sequence-storage-connection

Conversation

@discovery-ukraine

Copy link
Copy Markdown

Summary

CommandSequence (v2 command-sequence reservation) and the workflow:v1:list
command read and wrote workflow tables through DB::table('...'), which always
targets the application's default database connection. This bypasses the
workflows.storage.connection routing added in #391, so whenever workflow state
lives on a dedicated connection these call sites silently hit the wrong database.
This PR routes those raw queries through the model's resolved connection and adds
a fail-closed regression test.

Root cause

#391 made all workflow persistence connection-aware: every Eloquent model honors
workflows.storage.connection through the ResolvesStorageConnection trait, and
every migration extends WorkflowMigration. The routing intentionally lives on
the model class so that both ConfiguredV2Models::resolve() and hardcoded model
references agree on a single connection (no split-brain).

Two code paths reach workflow tables through the raw query builder instead of
a model, so they never picked up that routing:

  • src/V2/Support/CommandSequence.phpensureBackfilled() / backfill() use
    DB::table('workflow_commands') (3 call sites) to read and renumber the command
    sequence for a run.
  • src/Commands/V1ListCommand.phpDB::table('workflows') to list active v1
    workflows.

DB::table('workflow_commands') resolves the default connection. When
workflows.storage.connection points at a separate database (the entire purpose
of the key), the WorkflowRun / WorkflowCommand models route to the storage
connection while these raw queries go to the default one. In practice that throws:

PDOException: SQLSTATE[HY000]: General error: 1 no such table: workflow_commands

because the workflow tables only exist on the storage connection — or, where a
default-connection copy happens to exist, it reads/writes a stale store
(split-brain). CommandSequence::reserveNext() is on the hot path for starting a
workflow step, so this blocks v2 execution entirely on any dedicated-storage
deployment.

What changed

  • CommandSequence — a private commandsTable() helper returns a query
    builder bound to the configured command model's own connection and table
    ($command->getConnection()->table($command->getTable())). The three
    DB::table('workflow_commands') call sites now go through it.
  • V1ListCommand — the listing query is built from the v1 model's resolved
    connection and table ($storedWorkflow->getConnection()->table($storedWorkflow->getTable())).

Both follow the connection-from-the-model pattern already used by
WaterlineEngineSource (DB::connection($model->getConnectionName())), keeping a
single source of truth for where workflow data lives.

Backward compatibility

When workflows.storage.connection is unset (the default, null), the models
resolve to the application's default connection and their existing table names
(workflow_commands, workflows) — identical to the previous DB::table()
behavior. No config or migration change is required, and the default-connection
command-sequence path is covered by the existing v2 unit tests, which continue to
pass unchanged.

Copilot AI 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.

Pull request overview

This PR fixes two v2/v1 call sites that used DB::table(...) (always the app’s default DB connection) by routing raw query-builder access through the relevant model’s resolved storage connection (workflows.storage.connection). This aligns raw queries with the connection-aware persistence introduced in #391 and prevents “wrong database / missing table” failures when workflow tables live on a dedicated connection.

Changes:

  • Route CommandSequence maintenance queries for workflow_commands through the configured command model’s connection/table.
  • Route workflow:v1:list queries through StoredWorkflow’s resolved connection/table instead of the default connection.
  • Add a unit regression test that fail-closes when workflow tables exist only on the configured storage connection.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
tests/Unit/CommandSequenceStorageConnectionTest.php Adds a regression test ensuring CommandSequence::reserveNext() executes command-sequence queries on the configured storage connection.
src/V2/Support/CommandSequence.php Replaces DB::table('workflow_commands') with a helper that builds a query on the configured command model’s connection/table.
src/Commands/V1ListCommand.php Builds the v1 listing query via StoredWorkflow’s resolved connection/table instead of the default connection.

@rmcdaniel rmcdaniel merged commit 12cac5d into durable-workflow:v2 Jun 8, 2026
1 check passed
@rmcdaniel

Copy link
Copy Markdown
Member

Thanks for the PR. This is merged and available in durable-workflow/workflow 2.0.0-alpha.201.

I verified the Packagist package for 2.0.0-alpha.201 maps to merge commit 12cac5d1e2f36a1d1e19e938a28925a726b5c353, so users can install it with:

composer require durable-workflow/workflow:2.0.0-alpha.201

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.

3 participants