Skip to content

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

Open
mpge wants to merge 1 commit intofeat/workflow-executorfrom
feat/workflow-delay
Open

feat(workflow): delay action with deferred job queue#35
mpge wants to merge 1 commit 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 as a ninth entry in the catalog. Splits execution around a wait clause: actions before the delay run inline, remaining actions are persisted as a `DeferredWorkflowJob` row with `runAt = now + N seconds`. A `@Scheduled` poller on `WorkflowExecutorService` (60s cadence, matching SnoozeScheduler) picks up due pending jobs, rehydrates the ticket, resumes the saved action sequence, and flips status to `done` / `failed`. Rows are audit-retained.

What's added

  • `DeferredWorkflowJob` entity — `(ticket_id, remaining_actions JSON, run_at, status, last_error)` with a composite `(status, run_at)` index for efficient polling
  • `DeferredWorkflowJobRepository` — single derived query `findByStatusAndRunAtLessThanEqual`
  • New branch in `WorkflowExecutorService.execute` — when `type: "delay"` is seen, serialize the `actions[i+1:]` suffix + save + return
  • `@Scheduled` `runDueDeferredJobs()` polling method on the same executor (mirrors how the NestJS reference keeps the poller co-located with the executor rather than factoring out a separate scheduler component)
  • 4 new unit tests: pre-delay action runs + post-delay doesn't + DeferredWorkflowJob persisted correctly; invalid seconds skips cleanly; poller resumes + marks done; poller handles missing ticket with `failed` status

Why

Ports escalated-nestjs#26 to the Spring plugin. The other eight workflow actions already in `feat/workflow-executor` (`change_priority`, `change_status`, `assign_agent`, `set_department`, `add_tag`, `remove_tag`, `add_note`, `insert_canned_reply`) now have parity with the NestJS reference; `delay` was the last missing entry.

The H2 test DB uses `ddl-auto=create-drop`, so the new entity's schema is auto-generated in tests without a migration. A production Flyway migration can follow as a separate PR once this pattern is accepted.

Base / Stacking

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

Test plan

  • CI runs `./gradlew test` — 4 new + 22 existing WorkflowExecutorServiceTest cases should pass
  • Reviewer: confirm 60s polling cadence is acceptable (if you want a faster wake-up, override `escalated.workflow.deferred-check-interval-seconds` in `application.properties`)
  • Reviewer: sanity-check the `subList` usage in `executeParsed` — it creates a view, not a copy, which is fine here because Jackson only iterates once before the return

Splits workflow execution around a `delay N` step: actions before the
delay run inline, remaining actions are persisted as a
DeferredWorkflowJob with runAt = now + N seconds. A @scheduled poller
on WorkflowExecutorService (60s cadence by default, matching SnoozeScheduler
and the NestJS reference) picks up due pending jobs, re-invokes the
executor with the stored remainingActions, and marks the row done/failed
(retained for audit).

Ports the behavior from escalated-nestjs#26. The new entity has a
composite (status, run_at) index for efficient polling.

Stacked on feat/workflow-executor.
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