Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ project follows [Semantic Versioning](https://semver.org/).

## [Unreleased]

### Upgrade notes

- PHP `^8.1`, Laravel 10–13 and Symfony 6.4/7/8 support remain unchanged.
Upgrade the complete task queue before recording v2 condition fingerprints;
legacy fingerprints still require their exact source-line match. Read the
[upgrade guide](docs/portal/operate/upgrading.md) before deployment, including
its rollback boundary and atomic-source/OPcache restart requirements.
- Static credential validation deliberately rejects malformed UTF-8, edge
Unicode whitespace and embedded controls before fallback or role selection.
Valid credential bytes remain exact. The [authentication guide](docs/portal/operate/authentication.md#static-credential-validation)
covers application, framework and process-handoff settings.

### Added

- An unreleased schedule-to-start histogram for workflow/activity dispatch uses
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ transient, while injected collaborators may remain shared. Constructors run
during replay and must be free of external side effects. See the
[registration migration guide](https://php.durable-workflow.com/build/workflows-activities/#migrate-workflow-registration-from-php-sdk-2).

For a minor SDK upgrade, review the [upgrade guide](docs/portal/operate/upgrading.md)
for condition-history compatibility, queue-wide rollout and static credential
validation before replacing workers.

## Choose Your Path

| Starting point | Recommended guide |
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
}
},
"durable-workflow": {
"product-train": "3.9.4",
"product-train": "3.10.0",
"supported-server-versions": "2.5.0",
"supported-server-release-baselines": {
"2": "2.5.0",
Expand Down
2 changes: 1 addition & 1 deletion docs/laravel-adoption-contract.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
},
"service_v2": {
"package": "durable-workflow/sdk",
"channel": "^3.9",
"channel": "^3.10",
"state_owner": "server_or_cloud"
}
},
Expand Down
1 change: 1 addition & 0 deletions docs/portal/_data/navigation.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"label": "Operate",
"items": [
{"label": "Deployment", "url": "/operate/deployment/"},
{"label": "Upgrading", "url": "/operate/upgrading/"},
{"label": "Authentication", "url": "/operate/authentication/"},
{"label": "Graceful shutdown", "url": "/operate/graceful-shutdown/"},
{"label": "Execution tracing", "url": "/operate/tracing/"},
Expand Down
3 changes: 3 additions & 0 deletions docs/portal/build/workflows-activities.md
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,9 @@ fingerprints, and cannot safely take over runs that already contain them.

Deploy workflow code atomically and restart workers. The fingerprint reads the deployed source file. If files are replaced in place under any long-running worker that still has the old code loaded, the loaded code and the file on disk can disagree, and replay reports a mismatch until the worker restarts. `opcache.validate_timestamps=0` widens this window. Ship the PHP sources with the worker image; the fingerprint cannot be computed without them.

The [upgrade guide](/operate/upgrading/) covers the queue-wide deployment and
rollback boundary when both legacy and v2 condition histories exist.

## Put side effects in activities

```php
Expand Down
51 changes: 49 additions & 2 deletions docs/portal/operate/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ title: Authentication
description: Configure bearer, role-specific, and static-header authentication for Durable Workflow PHP clients and workers without leaking credentials.
lead: Authentication is applied at the transport boundary. Prefer separate control-plane and worker principals, rotate them through your secret manager, and never place tokens in workflow history.
previous:
label: Deployment
url: /operate/deployment/
label: Upgrading
url: /operate/upgrading/
next:
label: Graceful shutdown
url: /operate/graceful-shutdown/
Expand Down Expand Up @@ -36,6 +36,53 @@ $workerClient = new Client(

Control-plane operations use the control token; worker registration, heartbeats, polls, and task completion use the worker token. The fallback rules keep a deliberately shared token usable, but explicit role tokens make least privilege review clearer.

## Static credential validation

PHP SDK {{ release.sdkVersion }} validates each supplied static credential in its original form,
before choosing a role or falling back to a shared token. This applies to
`Client` and `TokenAuthentication` constructor options `token`, `controlToken`
and `workerToken`, framework `ServiceConfiguration` credential settings, and
Laravel's process-environment and explicit process-token handoff. An invalid
unused role also fails validation; supplying a valid token for another role
does not hide it.

Static credential strings refuse:

- malformed UTF-8 byte sequences;
- leading or trailing Unicode `White_Space`, including ordinary spaces,
nonbreaking spaces and Unicode line separators;
- C0 controls, DEL and C1 controls anywhere in the string, including tabs,
newlines and NUL.

This deliberately rejects malformed UTF-8 that older releases could accept.
Valid text is preserved byte for byte: the SDK does not trim, case-fold,
normalize Unicode or rewrite internal spaces. Text validation alone does not
establish that a runtime or HTTP intermediary accepts an arbitrary opaque
credential. Obtain a correctly formed replacement from the credential issuer
instead of trimming an invalid credential to make it pass.

`null` and the literal empty string retain their existing optional fallback
behavior. `TokenAuthentication` still requires at least one nonempty token;
an anonymous `Client` remains available where it was already supported. An
empty scoped token can fall back to a valid shared token, but a malformed
nonempty scoped token is an error.

Errors identify only the setting, for example
`Invalid static credential setting DURABLE_WORKFLOW_WORKER_TOKEN.` They do
not include the rejected value. Keep that boundary in application error
handling and diagnostics too.

For [Laravel](/frameworks/laravel/#service-mode), keep credentials out of cached
configuration and inject the required role at process startup. The resolver
validates shared, client, worker and explicit process-handoff text before
selection, including values belonging to an unused role. For
[Symfony](/frameworks/symfony/), the shared configuration path applies the same
policy before constructing either role's client. Review all supplied settings
before deploying, rather than checking only the selected token.

Custom dynamic `Authentication` implementations retain their existing contract.
The static-token policy does not change their signing or refresh behavior.

## Static headers for an authenticated gateway

`StaticHeadersAuthentication` adds the same fixed header set to both roles. Use it only when a trusted gateway owns authentication and the fixed headers contain no per-request signature:
Expand Down
4 changes: 2 additions & 2 deletions docs/portal/operate/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ previous:
label: Symfony
url: /frameworks/symfony/
next:
label: Authentication
url: /operate/authentication/
label: Upgrading
url: /operate/upgrading/
---
## Cloud

Expand Down
87 changes: 87 additions & 0 deletions docs/portal/operate/upgrading.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
---
layout: layout.njk
title: Upgrading
description: Upgrade PHP SDK workers with compatible condition histories, exact static credentials and a clear rollback boundary.
lead: Upgrade the complete task queue together, preserve durable replay identities, and restart workers with the matching source files.
previous:
label: Deployment
url: /operate/deployment/
next:
label: Authentication
url: /operate/authentication/
---
This guide covers PHP SDK {{ release.sdkVersion }} and its changes listed in the
[changelog](https://github.com/durable-workflow/sdk-php/blob/main/CHANGELOG.md).
PHP `^8.1`, Laravel 10–13 and Symfony 6.4/7/8 support remain unchanged; continue
to use each framework's patched minimum from the installed package manifest.
The embedded Workflow package has its own support contract and release cycle.

## Condition fingerprints and replay

The worker records a condition predicate's identity in durable history. The
new reader accepts both versions, with different comparisons:

| First recorded fingerprint | Replay and physical reopen |
| --- | --- |
| `sha256:` (v1) | The original source-line hash must match exactly. A reopen retains that same v1 value. |
| `dwphp2:sha256:` (v2) | The predicate's canonical tokens must match. A reopen retains that same v2 value. |
| No fingerprint | The existing absence is retained on reopen; the upgrade does not invent a fingerprint for that wait. |

For a new wait, tokenizable supported source records v2. Whitespace and
comments outside literals, source locations and neighboring call arguments
do not change its token identity. Literals and operator boundaries remain
significant. The key, timeout and authored call order have their own replay
checks; token tolerance does not permit changing those values.

If the callable cannot be isolated or PHP's tokenizer is unavailable, the
worker falls back to the existing v1 computation. If its source cannot be
read, that computation can remain absent. Every worker that replays v2
histories needs the tokenizer enabled and the corresponding PHP source files.
These are the implemented recording and read rules; there is no configuration
switch to disable strict legacy comparison or opt an existing wait into v2.

Existing v1 waits remain sensitive to reindentation, comments and neighboring
code on their original source lines. V2 formatting tolerance applies to waits
first opened with v2. Upgrading a worker does not convert older histories.
Indistinguishable same-line closures can share a combined candidate identity;
put predicates on separate lines before starting runs that need independent
identities. Use [version markers](/build/workflows-activities/#evolve-workflow-code-with-version-markers)
for a deliberate workflow behavior change.

## Upgrade the whole queue and preserve the rollback boundary

1. Inventory every worker that can receive work from the task queue, including
standby and autoscaling pools. Prepare a consistent SDK, workflow source and
tokenizer configuration for all of them.
2. Stop or drain the older workers before replacement workers begin recording
v2 waits. A mixed queue is unsafe once any worker can write a fingerprint
that another worker cannot replay.
3. Deploy the PHP sources atomically and restart all long-running workers,
including workers using OPcache. Do not replace source files underneath
processes that still execute their previous definitions.
4. Confirm that the replacement pool can replay representative existing v1
histories and new v2 waits before restoring the full worker pool.

The fingerprint reads deployed files. Loaded code and files replaced in place
can disagree; `opcache.validate_timestamps=0` widens that window. Keep source
files with the worker deployment and restart processes when changing them.

After v2 histories exist, rolling back to an older SDK that cannot read v2
can fail replay, even when workflow behavior has not changed. Keep a rollback
build with the new reader and compatible workflow sources. Retain that reader
for as long as those histories may be replayed; switching a worker back to v1
recording would not remove already recorded v2 values.

## Validate all static credential settings

Review shared, client, worker and framework process-handoff values before
starting either role. The SDK validates every original nonempty static
credential before selection, including unused roles. Malformed UTF-8, Unicode
whitespace at either edge and embedded control characters fail instead of
being trimmed or ignored. Valid bytes and literal-empty optional fallback are
preserved. See [static credential validation](/operate/authentication/#static-credential-validation)
for exact boundaries and framework configuration guidance.

Keep runtime capability negotiation in place during the upgrade. Installing an
SDK does not add a producer capability to Server or Cloud, and the supported
Server release baseline remains separate from individual feature support.
6 changes: 3 additions & 3 deletions docs/quickstart-contract.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
"schema_version": 2,
"package": {
"name": "durable-workflow/sdk",
"published_version": "3.9.4",
"composer_requirement": "3.9.4",
"onboarding_requirement": "^3.9"
"published_version": "3.10.0",
"composer_requirement": "3.10.0",
"onboarding_requirement": "^3.10"
},
"runtime_targets": {
"server": {
Expand Down
Loading