diff --git a/CHANGELOG.md b/CHANGELOG.md index 134a02ee..17a60c68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index 8d3f60de..204d7941 100644 --- a/README.md +++ b/README.md @@ -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 | diff --git a/composer.json b/composer.json index 9d56b838..ddcecbd4 100644 --- a/composer.json +++ b/composer.json @@ -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", diff --git a/docs/laravel-adoption-contract.json b/docs/laravel-adoption-contract.json index f502d5e4..0dcb59f2 100644 --- a/docs/laravel-adoption-contract.json +++ b/docs/laravel-adoption-contract.json @@ -39,7 +39,7 @@ }, "service_v2": { "package": "durable-workflow/sdk", - "channel": "^3.9", + "channel": "^3.10", "state_owner": "server_or_cloud" } }, diff --git a/docs/portal/_data/navigation.json b/docs/portal/_data/navigation.json index 8990d56d..07b9841f 100644 --- a/docs/portal/_data/navigation.json +++ b/docs/portal/_data/navigation.json @@ -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/"}, diff --git a/docs/portal/build/workflows-activities.md b/docs/portal/build/workflows-activities.md index 52fec96c..0bb782e3 100644 --- a/docs/portal/build/workflows-activities.md +++ b/docs/portal/build/workflows-activities.md @@ -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 diff --git a/docs/portal/operate/authentication.md b/docs/portal/operate/authentication.md index 3b28d2d4..83dd1214 100644 --- a/docs/portal/operate/authentication.md +++ b/docs/portal/operate/authentication.md @@ -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/ @@ -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: diff --git a/docs/portal/operate/deployment.md b/docs/portal/operate/deployment.md index 1cb0f5b1..5936726f 100644 --- a/docs/portal/operate/deployment.md +++ b/docs/portal/operate/deployment.md @@ -7,8 +7,8 @@ previous: label: Symfony url: /frameworks/symfony/ next: - label: Authentication - url: /operate/authentication/ + label: Upgrading + url: /operate/upgrading/ --- ## Cloud diff --git a/docs/portal/operate/upgrading.md b/docs/portal/operate/upgrading.md new file mode 100644 index 00000000..74b2d3b1 --- /dev/null +++ b/docs/portal/operate/upgrading.md @@ -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. diff --git a/docs/quickstart-contract.json b/docs/quickstart-contract.json index 8e1fa7a5..6b8e29c2 100644 --- a/docs/quickstart-contract.json +++ b/docs/quickstart-contract.json @@ -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": {