From e9d5b839fee022a1096d5aa76693ce592e03f002 Mon Sep 17 00:00:00 2001 From: Richard McDaniel <1130888+rmcdaniel@users.noreply.github.com> Date: Thu, 5 Nov 2026 19:28:04 +0000 Subject: [PATCH 1/3] Prepare Workflow 2.21 support for PHP 8.2 and Laravel 12 or 13 --- CHANGELOG.md | 18 ++++++ README.md | 45 +++++++------- composer.json | 8 +-- .../laravel-dependency-security-policy.json | 62 +++---------------- .../laravel-embedded-upgrade-contract.json | 15 +---- .../LaravelDependencySecurityPolicyTest.php | 57 ++++++++++++++--- 6 files changed, 107 insertions(+), 98 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9443ccf81..3b21332f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,24 @@ ## Unreleased +- Workflow 2.21.0 requires PHP `^8.2` and patched Laravel + `^12.69.0|^13.30.0`. Laravel 10/11 and PHP 8.1 are no longer supported. + Upgrade the application platform first while preserving its existing + Workflow 2.20.x lock, then update Workflow. The stable-v1 authority and + ownership of existing durable history are unchanged. +- The embedded source/published upgrade matrices now cover Laravel 12/13 + on their supported PHP 8.2–8.4 intersection. Laravel 13 still requires + PHP 8.3 or newer. Removed-framework constraints refuse before installation; + retired-platform advisory exceptions are not carried into 2.21. + +## 2.20.4 - 2026-11-05 + +- Fix an embedded MySQL/InnoDB REPEATABLE READ deadlock between + continue-as-new and unrelated signal admission. Caller-owned transactions + retain their original read views, pending default/named signal handoffs + remain durable, and user workflow code is not automatically replayed to + recover from the deadlock. + ## 2.20.3 - 2026-11-04 - Rollback, embedded Workflow package: 2.20.3 changes no schema, history diff --git a/README.md b/README.md index 5a44e765f..598124151 100644 --- a/README.md +++ b/README.md @@ -20,27 +20,30 @@ This package also provides the orchestration engine hosted by [Durable Workflow Cloud](https://cloud.durable-workflow.com/) when workers need to run outside the Laravel application or across PHP, Python, and Rust. -Use a patched Laravel 12 or 13 release for production. Laravel 10 and 11 -compatibility is retained for upgrading and draining existing applications, not -as security support for those frameworks. The [dependency security policy](resources/laravel-dependency-security-policy.json) -records the remaining advisories and review dates. The Laravel 10 and 11 -minimums include their upstream TokenGuard fixes; the advisory feed still -includes those patched branches. Keep Composer audits and application security -updates enabled. - -Workflow 2.17.0 no longer supports Laravel 9, which has no fix for the -[TokenGuard advisory](https://github.com/laravel/framework/security/advisories/GHSA-f9x3-7vq9-cph6). -Composer keeps a Laravel 9 application on Workflow 2.16.x, so updating does not -break it. To move past 2.16.x, upgrade the application to a patched Laravel 12 -or 13 release first. Until then, do not expose token-guard authentication; see -[#922](https://github.com/durable-workflow/workflow/issues/922). - -The legacy Laravel 10 and 11 exception requires an existing application -lockfile. Keep that lock, use a targeted Workflow update, and drain or migrate -to patched Laravel 12 or 13. It does not cover a fresh PHP 8.1 dependency -resolution, which Composer may refuse because of the legacy advisories. If -Composer refuses the migration, upgrade the framework first; do not globally -disable security blocking. +Workflow 2.21 requires **PHP 8.2 or newer** and **Laravel 12.69.0 or newer +on the 12.x line, or Laravel 13.30.0 or newer on the 13.x line**. Laravel 13 +requires PHP 8.3. The [dependency security policy](resources/laravel-dependency-security-policy.json) +records the supported framework floors. Keep Composer audits and application +security updates enabled. + +### Upgrade to 2.21 + +Upgrade PHP 8.1 applications to PHP 8.2 or newer and Laravel 10/11 applications +to patched Laravel 12 or 13 before requiring Workflow 2.21. Composer refuses +2.21 on the retired platforms. Keep the application's existing 2.20.x lock +while preparing that upgrade; older Workflow releases do not extend upstream +security support for PHP or Laravel. Do not disable advisory blocking to force +an upgrade. + +This platform change does not migrate durable history. Existing v1 runs remain +owned by v1; retain their code and workers until they finish, and use the +[embedded upgrade contract](resources/laravel-embedded-upgrade-contract.json) +to choose drain or coexistence. The stable 1.x authority is unchanged. + +Laravel 9 applications remain on their compatible Workflow 2.16.x line until +they upgrade the framework. Laravel 9 has no fix for the +[TokenGuard advisory](https://github.com/laravel/framework/security/advisories/GHSA-f9x3-7vq9-cph6) +and was removed in Workflow 2.17.0; see [#922](https://github.com/durable-workflow/workflow/issues/922). ## Install diff --git a/composer.json b/composer.json index 2009a01b2..6d525e3f9 100644 --- a/composer.json +++ b/composer.json @@ -59,8 +59,8 @@ } ], "require": { - "php": "^8.1", - "laravel/framework": "^10.50.4|^11.56.1|^12.69.0|^13.30.0", + "php": "^8.2", + "laravel/framework": "^12.69.0|^13.30.0", "react/promise": "^2.9|^3.0", "apache/avro": "^1.12", "symfony/yaml": "^6.0|^7.0|^8.0" @@ -71,7 +71,7 @@ "open-telemetry/api": "^1.10.1", "open-telemetry/exporter-otlp": "^1.4", "open-telemetry/sdk": "^1.0", - "orchestra/testbench": "^8.0|^11.0", + "orchestra/testbench": "^10.0|^11.0", "phpstan/phpstan": "^2.0", "symplify/easy-coding-standard": "^11.0" }, @@ -87,7 +87,7 @@ "dev-main": "2.0.x-dev" }, "durable-workflow": { - "product-train": "2.20.4", + "product-train": "2.21.0", "laravel-embedded-upgrade-contract": "resources/laravel-embedded-upgrade-contract.json", "laravel-dependency-security-policy": "resources/laravel-dependency-security-policy.json" }, diff --git a/resources/laravel-dependency-security-policy.json b/resources/laravel-dependency-security-policy.json index 5aa969445..9863552a9 100644 --- a/resources/laravel-dependency-security-policy.json +++ b/resources/laravel-dependency-security-policy.json @@ -10,14 +10,6 @@ "reason": "The host application owns the installed Laravel version and lockfile; GitHub dependency alerts remain visible while the per-major Composer audit owns resolution and accepted-risk tracking for this library constraint." }, "supported_majors": { - "10": { - "minimum": "10.50.4", - "disposition": "accepted-risk" - }, - "11": { - "minimum": "11.56.1", - "disposition": "accepted-risk" - }, "12": { "minimum": "12.69.0", "disposition": "secure" @@ -31,53 +23,15 @@ "9": { "last_workflow_minor": "2.16", "reason": "Laravel 9 has no fix for GHSA-f9x3-7vq9-cph6 (TokenGuard). Workflow 2.17.0 removes it from the supported constraint; Composer keeps Laravel 9 applications on Workflow 2.16.x until they upgrade the framework." - } - }, - "accepted_advisories": { - "PKSA-d5tc-s1qs-h781": { - "github_advisory": "GHSA-jh5r-qr3c-85q8", - "affected_supported_majors": [ - "10", - "11" - ], - "review_by": "2026-11-24", - "reason": "Laravel has not published a patch on these legacy majors. Compatibility is retained for embedded upgrade and drain paths; production applications must disable APP_DEBUG and migrate to a patched Laravel 12 or 13 release." }, - "PKSA-m5cs-t1y6-qpcs": { - "github_advisory": "GHSA-crmm-hgp2-wgrp", - "affected_supported_majors": [ - "10", - "11" - ], - "review_by": "2026-11-24", - "reason": "Laravel has not published a patched release on these majors; compatibility is retained only for embedded upgrade and drain paths." - }, - "PKSA-3r5d-mb8f-1qw9": { - "github_advisory": "GHSA-5vg9-5847-vvmq", - "affected_supported_majors": [ - "10", - "11" - ], - "review_by": "2026-11-24", - "reason": "Laravel has not published a patched release on these majors; compatibility is retained only for embedded upgrade and drain paths." - }, - "PKSA-mdq4-51ck-6kdq": { - "github_advisory": "GHSA-5vg9-5847-vvmq", - "affected_supported_majors": [ - "10", - "11" - ], - "review_by": "2026-11-24", - "reason": "The FriendsOfPHP record describes the same GitHub advisory, and Laravel has not published a patched release on these majors." + "10": { + "last_workflow_minor": "2.20", + "reason": "Laravel 10 is outside upstream security support. Workflow 2.21.0 removes this major; retain the existing 2.20.x application lock while upgrading PHP to at least 8.2 and Laravel to a patched 12 or 13 release before taking 2.21.0. Existing 2.20.x legacy risks remain recorded in that release's dependency security policy." }, - "PKSA-b41t-wbzk-9q51": { - "github_advisory": "GHSA-f9x3-7vq9-cph6", - "affected_supported_majors": [ - "10", - "11" - ], - "review_by": "2026-11-07", - "reason": "Laravel 10.50.4 and 11.56.1, the supported minimums, contain the strict string TokenGuard checks (upstream 47781ee0 and 5056ca53), but the advisory feed still classifies every release below 12.68.0 as affected. Laravel 9, which has no fix, was removed from the supported constraint in Workflow 2.17.0; Laravel 9 applications stay on 2.16.x. A GitHub advisory-database correction adding 10.50.4 and 11.56.1 as patched is under Security review (#922). Other declared legacy risks, dependency alerts and exact per-major audits remain." + "11": { + "last_workflow_minor": "2.20", + "reason": "Laravel 11 is outside upstream security support. Workflow 2.21.0 removes this major; retain the existing 2.20.x application lock while upgrading PHP to at least 8.2 and Laravel to a patched 12 or 13 release before taking 2.21.0. Existing 2.20.x legacy risks remain recorded in that release's dependency security policy." } - } + }, + "accepted_advisories": {} } diff --git a/resources/laravel-embedded-upgrade-contract.json b/resources/laravel-embedded-upgrade-contract.json index 19d9cf846..a7679c969 100644 --- a/resources/laravel-embedded-upgrade-contract.json +++ b/resources/laravel-embedded-upgrade-contract.json @@ -26,28 +26,19 @@ "laravel": "^9.0|^10.0|^11.0|^12.0|^13.0" }, "embedded_v2": { - "php": "^8.1", - "laravel": "^10.50.4|^11.56.1|^12.69.0|^13.30.0" + "php": "^8.2", + "laravel": "^12.69.0|^13.30.0" }, - "qualified_php_minors": ["8.1", "8.2", "8.3", "8.4"], + "qualified_php_minors": ["8.2", "8.3", "8.4"], "laravel_minimum_php": { - "10.*": "8.1", - "11.*": "8.2", "12.*": "8.2", "13.*": "8.3" } }, "cells": [ - {"php": "8.1", "laravel": "10.*"}, - {"php": "8.2", "laravel": "10.*"}, - {"php": "8.2", "laravel": "11.*"}, {"php": "8.2", "laravel": "12.*"}, - {"php": "8.3", "laravel": "10.*"}, - {"php": "8.3", "laravel": "11.*"}, {"php": "8.3", "laravel": "12.*"}, {"php": "8.3", "laravel": "13.*"}, - {"php": "8.4", "laravel": "10.*"}, - {"php": "8.4", "laravel": "11.*"}, {"php": "8.4", "laravel": "12.*"}, {"php": "8.4", "laravel": "13.*"} ] diff --git a/tests/Unit/V2/LaravelDependencySecurityPolicyTest.php b/tests/Unit/V2/LaravelDependencySecurityPolicyTest.php index b1ae20ac4..992905e5b 100644 --- a/tests/Unit/V2/LaravelDependencySecurityPolicyTest.php +++ b/tests/Unit/V2/LaravelDependencySecurityPolicyTest.php @@ -72,25 +72,68 @@ public function testTheComposerConstraintRefusesRemovedLaravelMajors(): void ); $constraint = $composer['require']['laravel/framework']; - $this->assertSame(['9'], array_map('strval', array_keys($policy['removed_majors']))); + $this->assertSame(['9', '10', '11'], array_map('strval', array_keys($policy['removed_majors']))); $this->assertSame('2.16', $policy['removed_majors']['9']['last_workflow_minor']); + $this->assertSame('2.20', $policy['removed_majors']['10']['last_workflow_minor']); + $this->assertSame('2.20', $policy['removed_majors']['11']['last_workflow_minor']); // 9.52.23 is the newest Laravel 9 release; its TokenGuard is unfixed. - foreach (['9.0.0', '9.52.17', '9.52.23', '9.99.99'] as $version) { + foreach ([ + '9.0.0', '9.52.17', '9.52.23', '9.99.99', + '10.50.4', '10.99.99', '11.56.1', '11.99.99', + ] as $version) { $this->assertFalse(Semver::satisfies($version, $constraint), "{$version} must be refused"); } - foreach (['10.50.3', '11.56.0', '12.68.9', '13.29.9'] as $version) { + foreach (['12.68.9', '13.29.9'] as $version) { $this->assertFalse(Semver::satisfies($version, $constraint), "{$version} is below its floor"); } foreach ($policy['supported_majors'] as $major) { $this->assertTrue(Semver::satisfies($major['minimum'], $constraint), $major['minimum']); } - $this->assertArrayNotHasKey('9.*', $upgrade['supported_intersection']['authority']['laravel_minimum_php']); - $this->assertNotContains('9.*', array_column($upgrade['supported_intersection']['cells'], 'laravel')); - foreach ($policy['accepted_advisories'] as $advisory) { - $this->assertNotContains('9', $advisory['affected_supported_majors']); + foreach (['9.*', '10.*', '11.*'] as $removed) { + $this->assertArrayNotHasKey($removed, $upgrade['supported_intersection']['authority']['laravel_minimum_php']); + $this->assertNotContains($removed, array_column($upgrade['supported_intersection']['cells'], 'laravel')); } + $this->assertSame([], $policy['accepted_advisories']); + } + + public function testTheEmbeddedPhpFloorPreservesTheStableV1Authority(): void + { + $root = dirname(__DIR__, 3); + $composer = json_decode( + (string) file_get_contents($root . '/composer.json'), + true, + 512, + JSON_THROW_ON_ERROR, + ); + $upgrade = json_decode( + (string) file_get_contents($root . '/resources/laravel-embedded-upgrade-contract.json'), + true, + 512, + JSON_THROW_ON_ERROR, + ); + $authority = $upgrade['supported_intersection']['authority']; + + foreach (['8.1.0', '8.1.33', '8.1.99'] as $version) { + $this->assertFalse(Semver::satisfies($version, $composer['require']['php'])); + } + foreach (['8.2.0', '8.3.0', '8.4.0'] as $version) { + $this->assertTrue(Semver::satisfies($version, $composer['require']['php'])); + } + $this->assertSame([ + 'php' => '^8.1', + 'laravel' => '^9.0|^10.0|^11.0|^12.0|^13.0', + ], $authority['stable_v1']); + $this->assertSame(['8.2', '8.3', '8.4'], $authority['qualified_php_minors']); + $this->assertSame([ + ['php' => '8.2', 'laravel' => '12.*'], + ['php' => '8.3', 'laravel' => '13.*'], + ], array_map( + static fn (string $laravel, string $php): array => ['php' => $php, 'laravel' => $laravel], + array_keys($authority['laravel_minimum_php']), + array_values($authority['laravel_minimum_php']), + )); } public function testRootComposerDependabotUpdatesCannotBeConfigured(): void From 797c221b4434bea5ef57e28863c35219db0aa3d4 Mon Sep 17 00:00:00 2001 From: Richard McDaniel <1130888+rmcdaniel@users.noreply.github.com> Date: Thu, 5 Nov 2026 19:32:52 +0000 Subject: [PATCH 2/3] Apply prescribed style to support-contract assertions --- .../LaravelDependencySecurityPolicyTest.php | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/tests/Unit/V2/LaravelDependencySecurityPolicyTest.php b/tests/Unit/V2/LaravelDependencySecurityPolicyTest.php index 992905e5b..c60fd337a 100644 --- a/tests/Unit/V2/LaravelDependencySecurityPolicyTest.php +++ b/tests/Unit/V2/LaravelDependencySecurityPolicyTest.php @@ -92,7 +92,10 @@ public function testTheComposerConstraintRefusesRemovedLaravelMajors(): void } foreach (['9.*', '10.*', '11.*'] as $removed) { - $this->assertArrayNotHasKey($removed, $upgrade['supported_intersection']['authority']['laravel_minimum_php']); + $this->assertArrayNotHasKey( + $removed, + $upgrade['supported_intersection']['authority']['laravel_minimum_php'] + ); $this->assertNotContains($removed, array_column($upgrade['supported_intersection']['cells'], 'laravel')); } $this->assertSame([], $policy['accepted_advisories']); @@ -127,10 +130,19 @@ public function testTheEmbeddedPhpFloorPreservesTheStableV1Authority(): void ], $authority['stable_v1']); $this->assertSame(['8.2', '8.3', '8.4'], $authority['qualified_php_minors']); $this->assertSame([ - ['php' => '8.2', 'laravel' => '12.*'], - ['php' => '8.3', 'laravel' => '13.*'], + [ + 'php' => '8.2', + 'laravel' => '12.*', + ], + [ + 'php' => '8.3', + 'laravel' => '13.*', + ], ], array_map( - static fn (string $laravel, string $php): array => ['php' => $php, 'laravel' => $laravel], + static fn (string $laravel, string $php): array => [ + 'php' => $php, + 'laravel' => $laravel, + ], array_keys($authority['laravel_minimum_php']), array_values($authority['laravel_minimum_php']), )); From b83f6aa9e882a01234d9eb4d50dc3eb01964a314 Mon Sep 17 00:00:00 2001 From: Richard McDaniel <1130888+rmcdaniel@users.noreply.github.com> Date: Thu, 5 Nov 2026 20:06:08 +0000 Subject: [PATCH 3/3] Qualify embedded support floors in existing PR unit profile --- .github/pr-test-selection.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/pr-test-selection.json b/.github/pr-test-selection.json index dd2460066..c28c0fe15 100644 --- a/.github/pr-test-selection.json +++ b/.github/pr-test-selection.json @@ -14,6 +14,7 @@ "tests/Unit/V2/EmbeddedHttpSnapshotTest.php", "tests/Unit/V2/ExecutionTracingTest.php", "tests/Unit/V2/ExternalPayloadStorageTest.php", + "tests/Unit/V2/LaravelDependencySecurityPolicyTest.php", "tests/Unit/V2/PlatformProtocolSpecsTest.php", "tests/Unit/V2/ReadmeBranchDestinationsTest.php", "tests/Unit/V2/ScopedCancellationContextTest.php",