Skip to content

feat(workflow): delay action with deferred job queue#35

Open
mpge wants to merge 3 commits intofeat/workflow-executorfrom
feat/workflow-delay
Open

feat(workflow): delay action with deferred job queue#35
mpge wants to merge 3 commits intofeat/workflow-executorfrom
feat/workflow-delay

Conversation

@mpge
Copy link
Copy Markdown
Member

@mpge mpge commented Apr 24, 2026

Summary

Adds the `delay` workflow action to WordPress as the ninth entry in the catalog. Splits execution around a wait clause: actions before the delay run inline, remaining actions are persisted as an `escalated_deferred_workflow_jobs` row with `run_at = now + N seconds`. A WP-Cron handler running on the shared `escalated_every_minute` interval picks up due pending jobs, re-invokes the executor with the stored JSON, and flips the row to `done` / `failed` (retained for audit).

What's added

  • `Models\DeferredWorkflowJob` — `find/create/update/pending` helpers matching the existing Reply/Tag/Ticket model patterns
  • `Cron\Deferred_Workflow_Jobs_Check` registered on the new `escalated_run_due_deferred_workflow_jobs` hook
  • Activator — `CREATE TABLE escalated_deferred_workflow_jobs (id, ticket_id, remaining_actions LONGTEXT, run_at DATETIME, status VARCHAR(16), last_error TEXT, ...)` with composite `(status, run_at)` index for efficient polling + `wp_schedule_event` for the minute-cadence sweep
  • Bootstrap — registers the new cron handler alongside `Snooze_Check`
  • Executor — detects `"delay"` before dispatching, serializes the remainder via `wp_json_encode`, persists the job, and short-circuits the rest of the run. Includes public `run_due_deferred_jobs()` method so the cron handler can invoke it
  • 5 new PHPUnit cases: delay pauses + persists correctly, invalid value skips cleanly without persisting or running the tail, resumer processes a due row end-to-end and flips to done, resumer marks failed when ticket has been deleted, resumer correctly skips rows not yet due

Upgrade note

Existing installs need to reactivate the plugin to pick up the new table (via `deactivate → activate`). New installs get it automatically. This is a pre-existing infrastructure gap unrelated to this PR — a proper `plugins_loaded` version-check migration path is a separate future concern.

Base / Stacking

Stacked on `feat/workflow-executor` (PR #28). CI won't exercise this branch until that base merges and this rebases onto `main`.

Test plan

  • `php -l` passes on all modified + new files
  • CI runs `vendor/bin/phpunit` — 5 new tests in `Test_Workflow_Executor_Service` should pass
  • Reviewer: confirm the activator upgrade path matches what the team expects (existing installs need reactivation)
  • Reviewer: sanity-check the `run_at` UTC semantics (`gmdate('Y-m-d H:i:s', time() + $seconds)` writes UTC; `current_time('mysql')` in `pending()` reads the WP timezone setting — this should match because `pending()` uses `$wpdb->prepare` and relies on the DB comparing strings lexicographically). If the host app is configured with a non-UTC timezone, we'd want to normalize. Flagging for review.

mpge added 3 commits April 24, 2026 09:36
Splits workflow execution around a `delay N` step: actions before the
delay run inline, remaining actions are persisted as a
`escalated_deferred_workflow_jobs` row with run_at = now + N seconds.
A WP-Cron job on the shared `escalated_every_minute` interval picks up
due pending jobs, re-invokes the executor with the stored
remaining_actions JSON, and flips the row to `done` / `failed`
(retained for audit, composite `(status, run_at)` index for efficient
polling).

What's added:
- `Models\DeferredWorkflowJob` — find/create/update/pending helpers
- `Cron\Deferred_Workflow_Jobs_Check` registered on
  `escalated_run_due_deferred_workflow_jobs` hook
- Activator: new table creation + cron event scheduling
- Bootstrap: registers the new cron handler alongside Snooze_Check
- Executor: detects `delay`, serializes remaining actions, persists
  the job, short-circuits the rest of the run
- Executor: `run_due_deferred_jobs()` sweeps pending rows
- 5 new PHPUnit cases: delay pauses + persists, invalid value skips
  cleanly, resumer marks done, resumer marks failed when ticket
  deleted, resumer skips rows not yet due

Mirrors the NestJS reference impl in
escalated-nestjs/src/services/workflow-executor.service.ts and the
Spring port in escalated-spring PR #35.

Stacked on feat/workflow-executor.

Upgrade note: existing installs need to reactivate the plugin to pick
up the new table. New installs get it automatically on first activate.
run_at is written via gmdate() (UTC). pending() was reading via
current_time('mysql') which returns the WP-configured timezone —
on non-UTC sites, due jobs could be picked up early or late by up to
the timezone offset. Switch to current_time('mysql', true) to force
GMT on read, matching the write path.

created_at/updated_at stay on current_time('mysql') to match the rest
of the plugin's models (Reply, Tag, etc.) — those are audit-display
fields, not load-bearing for logic.
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.

1 participant