Durable Workflow for Laravel

repository·v2·Indexed 22 days ago

https://github.com/durable-workflow/workflow

A Laravel package for defining and managing complex, reliable workflows and activities. It enables developers to break large processes into modular, asynchronous, and retryable units using declarative PHP classes. The package provides service-provider integration, migrations, Eloquent models, and an embedded runtime for Laravel applications. It also supports a standalone/polyglot orchestration mode via a separate server and PHP SDK for multi-language worker support.

Tokens
95.4K
Snippets
49
Records
392
Agent score
79%

What's inside durable-workflow/workflow

  1. Workflow V2 Multi-Node Architecture Hardening Roadmap

    v2

    The Workflow V2 Multi-Node Architecture Hardening Roadmap is a structured plan to harden multi-node execution for the durable-workflow/workflow package (v2), the standalone durable-workflow/server, and any host embedding the package or communicating via HTTP.

    The roadmap addresses four primary architectural risks:

    1. Coupling: Preventing the coupling of control-plane and execution-plane responsibilities within generic application nodes.
    2. Scheduler Correctness: Ensuring correctness is based on durable dispatch state rather than shared cache or database polling.
    3. Mixed-Version Safety: Establishing explicit routing and rollout contracts for version compatibility.
    4. Unified Language: Standardizing the product language for duplicate execution, retries, lease expiry, and idempotency across the package, server, SDK, CLI, cloud, and Waterline.

    Note on Architecture: This roadmap is incremental. It does not aim to adopt Temporal, rebuild the engine in a single step, eliminate SQL persistence, or treat shared cache with identical nodes as the final architecture.

  2. Understand Workflow V2 Rollout Safety and Coordination Health

    v2

    The Workflow V2 Rollout Safety and Coordination Health Contract defines how the system ensures safety during deployments and how it exposes distributed coordination health to operators. It provides a standardized language and set of guarantees for boot-time admission, worker compatibility, routing drains, and stuck-task detection.

    This contract applies to:

    • The durable-workflow/workflow package at v2.
    • The standalone durable-workflow/server that embeds it.
    • Any host that embeds the package directly or communicates with the server via HTTP.

    Key areas covered include admission checks, stable worker-contract coverage, schema fencing, routing drains, coordination health metrics, and Waterline observability.

  3. Understand the Workflow V2 Worker Deployment Contract

    v2

    The Workflow V2 Worker Deployment Contract defines the formal interface for managing worker fleets. It provides a standardized way for operators and automated agents to promote, drain, resume, and roll back deployments. This contract ensures that all components—including the CLI, the standalone server, and any host embedding the package—use a consistent language for deployment lifecycles, workflow compatibility, and rollout blockages.

    Key areas covered by the contract include:

    • Deployment Identity: How deployments are uniquely identified.
    • Deployment Lifecycle: The explicit state machine for deployments.
    • Workflow Compatibility Policy: How long-lived workflows behave during updates.
    • Blockage Diagnoses: Machine-readable reasons for failed lifecycle transitions.
    • Promotion Safety: Guardrails for promoting new deployments.
    • Server-controlled Rollout Semantics: How the server exposes the lifecycle surface.
  4. What is the ordinary queued-activity baseline?

    v2

    The ordinary queued-activity baseline is the standard mode of activity dispatch in Durable Workflow. In this model:

    1. The workflow code records an activity command.
    2. The engine creates a durable activity task.
    3. A worker claims the task under a lease.
    4. The outcome of the activity is recorded on the workflow history.

    This baseline is the fallback mechanism for all activity executions and remains the canonical behavior when advanced primitives like Local Activities or Worker Sessions are not being utilized.

  5. How Commit Metadata scanning is scoped

    v2

    To avoid blocking new branches due to existing history in the target branch, the metadata scan uses specific Git ranges:

    For Pull Requests

    The workflow uses two environment variables to define the scope:

    • PUBLIC_BOUNDARY_GIT_RANGE: The range between the event base and head commits.
    • PUBLIC_BOUNDARY_GIT_BASELINE: The current public target branch (if available).

    The script evaluates git rev-list <range> --not <baseline> to identify only the new commits introduced by the candidate branch. This ensures that private wording already present in the target branch does not cause a rejection for new, unrelated branches.

    For Push Checks

    The workflow scans the pushed range from the previous public ref to the new ref. It does not apply a target-branch baseline for push checks.

    git rev-list <range> --not <baseline>
  6. Understand the SDK Neutrality Contract

    v2

    The SDK Neutrality Contract is a set of rules ensuring that all public Durable interfaces (RPC, events, payloads, and error shapes) are language-agnostic. This prevents the protocol from being hard-coded to language-specific assumptions (like PHP class names), allowing future SDKs in languages like TypeScript, Go, Java, or .NET to be built using only standard HTTP/JSON tooling and published specifications.

    Key Mental Model:

    • The Protocol is the Authority: If a new language needs to support Durable, it should be able to implement a client against the published wire protocol without requiring a redesign of that protocol.
    • SDK vs. Engine: The durable-workflow/workflow package is the Laravel engine and replay owner, not the standalone PHP SDK. The standalone PHP SDK is a separate, framework-neutral package.
  7. Limitations and Non-Goals of Cancellation Scope

    v2

    When designing workflows, be aware of the following constraints in the V2 cancellation model:

    • No Per-Call Scopes: Cancellation is run-level. You cannot use try/catch to cancel a specific block of code and keep the workflow run alive. To achieve 'stop doing X but keep the run alive', use signal-driven state transitions.
    • No Selective Activity Cancellation: You cannot cancel a specific activity call via an API. Cancellation is a consequence of a run-level cancel. To stop a specific activity, instruct the workflow to fail that specific call.
    • No Cancel-then-Continue: A cancelled run cannot be resumed. Any continuation requires starting a new run with a new workflow_run_id.
    • First Command Wins: If a run has already accepted a cancel command, it cannot be escalated to a terminate. The first terminal event received is the one that wins.
  8. Understand the Parent-History Blocking Invariant

    v2

    The core principle of the Workflow V2 child outcome contract is that the parent's history is the source of truth for whether a child call is resolved.

    The Invariant: Once a parent has committed ChildWorkflowScheduled or ChildRunStarted for a sequence, no consumer may resolve that slot until the parent commits a matching resolution event, even if the child run itself has reached a terminal status (e.g., completed or failed).

    This prevents race conditions where:

    • A cleanup process updates the child run before the parent engine reconciles it.
    • A test manipulates the child run directly.
    • A child worker races the parent bridge.

    This invariant is enforced via ChildRunHistory::parentHistoryBlocksResolutionWithoutEvent() and is consulted by the QueryStateReplayer and WorkflowExecutor.

  9. How API-node loss affects the engine

    v2

    When an API node crashes or is replaced, the engine's response is uniform and relies on the load balancer and durable substrate:

    • In-flight HTTP requests: Fail at the load-balancer or client layer. Callers should retry through the load balancer.
    • In-flight worker polls: Return an error. Workers should re-poll via the load balancer to a healthy node.
    • Leased tasks: Unaffected. Task leases are stored in the durable substrate, not API node memory. Lease expiry and redelivery proceed normally.
    • Cluster discovery: /api/cluster/info reports the local node's identity. No manual reconciliation of cluster membership is required as coordination is mediated by the database and Redis.
  10. Structure of the Coverage Manifest

    v2

    The coverage manifest is a YAML file used to track how the sample application demonstrates upstream features. It serves as the source of truth for linting scripts and is rendered into a human-readable table in docs/upstream-coverage.md.

    Each entry in the manifest must include:

    • An id: A stable identifier for the feature surface to prevent link rot in issues or documentation.
    • A status: Either covered (including the specific workflow class and the Artisan command used to exercise it) or gap (including a link to a sample-request issue).
    • Documentation reference: The name of the upstream documentation page defining the surface.
    • tracked_workflow_version: The version of the workflow package being tracked to ensure machine-readable cadence.
  11. How cross-node long-poll coordination works

    v2

    The workflow engine uses a long-polling mechanism to efficiently wait for new tasks or history events. In a distributed environment, this relies on a shared LongPollWakeStore to coordinate signals across different server nodes.

    The Coordination Mechanism

    1. Wake Signals: When a WorkflowTask or WorkflowHistoryEvent changes, Laravel model observers trigger a wake signal via the LongPollSignalStore. These signals write version stamps to specific cache channels (e.g., workflow-tasks:namespace:queue).
    2. Detection: The LongPoller on any node snapshots these channel versions. When it detects a change in the version stamp, it triggers a re-probe to fetch the new data.
    3. Timing Hints: To optimize polling frequency and reduce unnecessary load, the system uses 'timing hints'. The WorkflowTaskPoller uses available_at fields, and the HistoryController uses fields like next_task_at and wait_deadline_at to calculate the nextProbeAt time for the LongPoller.

    Critical Constraint

    For this coordination to work, the cache backend must be shared. If using the file cache, signals are trapped on the local node and will not wake pollers running on other nodes.

  12. Understand the Workflow V2 Boot-time Admission Checks

    v2

    Every V2 process executes a sequence of admission checks before accepting work to ensure the environment meets capability and safety requirements. These checks are authoritative and follow a fail-closed behavior based on their severity.

    Canonical Admission Layers

    • Backend Capabilities: Handled by Workflow\V2\Support\BackendCapabilities::snapshot(). It verifies if the database, queue, cache, and structural limits satisfy the V2 contract.
      • BackendCapabilities::isSupported() is the boolean used by the system to determine if the process can safely dispatch work.
      • Workflow\V2\Support\StructuralLimits::snapshot() provides authority on per-run bounds (e.g., pending activities, payload size).
    • Cache Validation: Handled by Workflow\V2\Support\LongPollCacheValidator::checkMultiNodeSafety(). This checks if the cache can propagate wake signals across nodes. This is warning-only; it will not block boot even if misconfigured, as the cache is an acceleration layer, not a correctness substrate.
    • Workflow Mode Guard: Handled by Workflow\V2\Support\WorkflowModeGuard::check(). It ensures registered workflow classes are replay-safe. Controlled by the DW_V2_GUARDRAILS_BOOT environment variable. Supported modes:
      • silent
      • warn (default)
      • throw (fail-closed mode for CI or strict production environments).
    • Readiness Contract: Handled by Workflow\V2\Support\ReadinessContract::definition(). This defines the availability of the V2 operator surface and determines the HTTP status codes for /api/health, /api/ready, and stats routes.

    Observability

    Admission check results are visible via OperatorMetrics::snapshot() (under backend, structural_limits, and workers.required_compatibility) and HealthCheck::snapshot() as named checks.