From 73da5800baf941e72cf9f94aab92c41d0dc2a8cd Mon Sep 17 00:00:00 2001 From: Richard McDaniel <1130888+rmcdaniel@users.noreply.github.com> Date: Sun, 1 Nov 2026 18:42:29 +0000 Subject: [PATCH 1/2] Rust #325: move the parity reference to published Server 2.11.8 Published 2.11.8 (Core 2.15.12) replaces 2.11.7 as the reference the shared fixtures compare native and embedded against. - Native always reports post-commit payload deletion counts, as 2.11.8 does: external_payload_cleanup on the retention pass and namespace delete, retention_cleanup on the payload cleanup routes. Four fixtures pin them. - child-open-parent-task gains the leased case (workflow#962): 2.11.8 now offers one follow-up task that replays the child's result, as native does. Native describes the moment after the lease (a resolved child beside the lease's timer) as 2.11.8 does instead of leaving it unprojected. - History import: native refuses an instance of another namespace before writing (target.instance_namespace_conflict), as Core 2.15.12 does. The workflow-import probe's conflict bundle renames every run reference, and the published-defect allowances for 2.11.1 to 2.11.7 are gone. - Embedded probes configure workflows.v2.namespace: Core 2.15.12 resolves a run through its instance's namespace and reserves a caller-named instance without model events, which the probes' creating hooks relied on. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/native-image-parity.yml | 2 +- .github/workflows/server-parity.yml | 58 +-- .../2026-11-01-reference-2118.md | 54 +++ docs/rust-server-port.md | 78 ++-- rust/src/runtime/child_summary.rs | 10 + rust/src/runtime/execution.rs | 1 + rust/src/runtime/namespace_cleanup.rs | 16 +- rust/src/runtime/operator_metrics.rs | 4 +- rust/src/runtime/payload_cleanup.rs | 19 +- rust/src/runtime/payload_deletions.rs | 8 - rust/src/runtime/workflow_import.rs | 21 + .../execution/external_payloads/cleanup.rs | 4 +- .../external_payloads/namespace_delete.rs | 2 +- .../execution/external_payloads/retention.rs | 6 +- rust/tests/execution/namespaces.rs | 5 +- scripts/conformance/server-parity.mjs | 4 +- scripts/conformance/server-parity/README.md | 2 +- .../activity-deadline-embedded.php | 3 + .../activity-deadline-receipts-embedded.php | 3 + .../server-parity/cancellation-probe.php | 3 + .../child-open-parent-task-probe.php | 95 ++-- .../server-parity/continue-embedded.php | 3 + .../server-parity/cooperative-probe.php | 3 + .../server-parity/memo-embedded.php | 3 + .../namespace-delete-contract.mjs | 4 +- .../namespace-delete-payloads-probe.php | 4 +- .../server-parity/native-image-corpus.sh | 2 +- .../server-parity/patch-deployment-probe.php | 3 + scripts/conformance/server-parity/probe.php | 3 + .../server-parity/range-pending-probe.php | 3 + .../server-parity/range-repair-probe.php | 3 + .../retention-payloads-probe.php | 4 +- .../conformance/server-parity/role-profile.sh | 2 +- .../server-parity/update-validation-sqlite.sh | 2 +- .../workflow-import-contract.mjs | 14 +- .../server-parity/workflow-import-probe.php | 15 +- .../ServerParity/child-open-parent-task.json | 51 +++ .../namespace-delete-payloads.json | 15 + .../ServerParity/namespace-delete.json | 1 + .../ServerParity/payload-cleanup.json | 24 +- .../retention-external-payloads.json | 40 ++ .../ServerParity/workflow-import.json | 410 +----------------- .../ServerParityBaselines/php-2.11.8.json | 20 + .../workflow-import-observation.json | 54 ++- tests/Unit/ServerParityImagePullTest.mjs | 2 +- ...erverParityNamespaceDeleteContractTest.mjs | 8 +- ...ServerParityWorkflowImportContractTest.mjs | 15 +- 47 files changed, 514 insertions(+), 592 deletions(-) create mode 100644 docs/rust-server-port-checkpoints/2026-11-01-reference-2118.md create mode 100644 tests/Fixtures/ServerParityBaselines/php-2.11.8.json diff --git a/.github/workflows/native-image-parity.yml b/.github/workflows/native-image-parity.yml index d28b8a992..878cbd0f0 100644 --- a/.github/workflows/native-image-parity.yml +++ b/.github/workflows/native-image-parity.yml @@ -47,7 +47,7 @@ jobs: run: | set -euo pipefail bash scripts/ci/pull-images.sh \ - "$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json)" \ + "$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.8.json)" \ composer:2.10.3@sha256:da624c2c705d54e2f5a9bb77a5dc6c7d091711433ebefd7035672e84c2010fee - name: Set up Docker Buildx diff --git a/.github/workflows/server-parity.yml b/.github/workflows/server-parity.yml index 54eaaa326..d636db515 100644 --- a/.github/workflows/server-parity.yml +++ b/.github/workflows/server-parity.yml @@ -258,7 +258,7 @@ jobs: bash scripts/ci/pull-images.sh \ mirror.gcr.io/library/rust@sha256:300ec56abce8cc9448ddea2172747d048ed902a3090e6b57babb2bf19f754081 \ mirror.gcr.io/library/python@sha256:bf44cdfcb76cd3b41e879bc058fc37ec5872002ccfde7fcb765e218cde0cd79c \ - "$(baseline php-2.11.7)" + "$(baseline php-2.11.8)" # The adapters' pinned, --locked dependencies build the same way every # run. Restore their public Cargo registry data and build tree, keyed # by the toolchain image, the runner and every tracked adapter input. @@ -530,7 +530,7 @@ jobs: bash scripts/ci/pull-images.sh \ "${NATIVE_RUST_IMAGE}" \ "$MYSQL_IMAGE" \ - "$(baseline php-2.11.7)" \ + "$(baseline php-2.11.8)" \ "$(baseline php-2.5.14)" \ composer:2.10.3@sha256:da624c2c705d54e2f5a9bb77a5dc6c7d091711433ebefd7035672e84c2010fee - name: Fetch this run's published SDK adapters @@ -566,7 +566,7 @@ jobs: - name: Create TLS fixture certificates run: | set -euo pipefail - image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.8.json) mkdir -p mysql-tls parity-evidence chmod 0777 parity-evidence docker run --rm --user="$(id -u):$(id -g)" --network=none \ @@ -677,7 +677,7 @@ jobs: docker run --rm --user="$(id -u):$(id -g)" --network "$RESOURCE_SCOPE" --entrypoint mysql \ "$MYSQL_IMAGE" -h parity-mysql -uroot -pparity-disposable-password \ -e 'CREATE DATABASE native_ref CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci' - image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.8.json) # The physical layout stays the frozen published 2.5.14 one; behavior # runs on the current reference after migrating that database forward. layout=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.5.14.json) @@ -726,7 +726,7 @@ jobs: bash scripts/conformance/server-parity/container-log-tails.sh } trap container_log_tails ERR - image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.8.json) # Composer runs confined in the pinned image, never in the published image under test. bash scripts/ci/run-composer.sh --rm -v "$PWD:/source" -w /source -- \ install --working-dir=scripts/conformance/server-parity --no-interaction --no-progress @@ -803,7 +803,7 @@ jobs: bash scripts/conformance/server-parity/container-log-tails.sh } trap container_log_tails ERR - image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.8.json) export DW_PARITY_RUNNER_REVISION=$(git rev-parse HEAD) mkdir -p parity-consumers curl --fail --location --retry 3 --output parity-consumers/dw.phar https://github.com/durable-workflow/cli/releases/download/2.2.0/dw.phar @@ -869,7 +869,7 @@ jobs: bash scripts/conformance/server-parity/container-log-tails.sh } trap container_log_tails ERR - image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.8.json) bash scripts/conformance/server-parity/published-sdk-patch-profile.sh mysql http://parity-php:8080 http://parity-native-a:8080 bash scripts/conformance/server-parity/repeated-patch-profile.sh mysql http://parity-php:8080 http://parity-native-a:8080 bash scripts/conformance/server-parity/legacy-marker-profile.sh mysql http://parity-php:8080 http://parity-native-a:8080 @@ -949,7 +949,7 @@ jobs: bash scripts/conformance/server-parity/container-log-tails.sh } trap container_log_tails ERR - image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.8.json) bash scripts/ci/run-composer.sh --rm -v "$PWD:/source" -w /source -- \ install --working-dir=scripts/conformance/server-parity --no-interaction --no-progress # Each profile brings its own published PHP and embedded databases and @@ -997,7 +997,7 @@ jobs: if: ${{ matrix.part == 'cohorts' }} run: | set -euo pipefail - image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.8.json) bash scripts/ci/run-composer.sh --rm -v "$PWD:/source" -w /source -- \ install --working-dir=scripts/conformance/server-parity --no-interaction --no-progress docker run -d --name "$RESOURCE_SCOPE-native-b" --network "$RESOURCE_SCOPE" --network-alias=parity-native-b \ @@ -1097,7 +1097,7 @@ jobs: if: ${{ matrix.part == 'corrected-markers' }} run: | set -euo pipefail - image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.8.json) bash scripts/ci/run-composer.sh --rm -v "$PWD:/source" -w /source -- \ install --working-dir=scripts/conformance/server-parity --no-interaction --no-progress docker run -d --name "$RESOURCE_SCOPE-native-b" --network "$RESOURCE_SCOPE" --network-alias=parity-native-b \ @@ -1227,7 +1227,7 @@ jobs: run: | set -euo pipefail mkdir -p parity-evidence parity-summary - image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.8.json) docker run --rm --user="$(id -u):$(id -g)" --network=none --cpus=1 --memory=256m --memory-swap=256m \ --entrypoint node -e PARITY_JOB_OUTCOME -e GITHUB_SHA \ -v "$PWD:/source:ro" -v "$PWD/parity-evidence:/evidence:ro" -v "$PWD/parity-summary:/summary" \ @@ -1280,7 +1280,7 @@ jobs: bash scripts/ci/pull-images.sh \ "${NATIVE_RUST_IMAGE}" \ "$POSTGRES_IMAGE" \ - "$(baseline php-2.11.7)" \ + "$(baseline php-2.11.8)" \ "$(baseline php-2.5.14)" \ composer:2.10.3@sha256:da624c2c705d54e2f5a9bb77a5dc6c7d091711433ebefd7035672e84c2010fee - name: Fetch this run's published SDK adapters @@ -1402,7 +1402,7 @@ jobs: "$POSTGRES_IMAGE" --version > parity-evidence/postgres-version.txt docker run --rm --user="$(id -u):$(id -g)" --network "$RESOURCE_SCOPE" \ -e PGPASSWORD=parity-disposable-password --entrypoint createdb "$POSTGRES_IMAGE" -h parity-pg -U parity native_ref - image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.8.json) # The physical layout stays the frozen published 2.5.14 one; behavior # runs on the current reference after migrating that database forward. layout=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.5.14.json) @@ -1453,7 +1453,7 @@ jobs: bash scripts/conformance/server-parity/container-log-tails.sh } trap container_log_tails ERR - image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.8.json) # Composer runs confined in the pinned image, never in the published image under test. bash scripts/ci/run-composer.sh --rm -v "$PWD:/source" -w /source -- \ install --working-dir=scripts/conformance/server-parity --no-interaction --no-progress @@ -1531,7 +1531,7 @@ jobs: bash scripts/conformance/server-parity/container-log-tails.sh } trap container_log_tails ERR - image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.8.json) export DW_PARITY_RUNNER_REVISION=$(git rev-parse HEAD) mkdir -p parity-consumers curl --fail --location --retry 3 --output parity-consumers/dw.phar https://github.com/durable-workflow/cli/releases/download/2.2.0/dw.phar @@ -1599,7 +1599,7 @@ jobs: bash scripts/conformance/server-parity/container-log-tails.sh } trap container_log_tails ERR - image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.8.json) bash scripts/conformance/server-parity/published-sdk-patch-profile.sh pgsql http://parity-php:8080 http://parity-native-a:8080 bash scripts/conformance/server-parity/repeated-patch-profile.sh pgsql http://parity-php:8080 http://parity-native-a:8080 bash scripts/conformance/server-parity/legacy-marker-profile.sh pgsql http://parity-php:8080 http://parity-native-a:8080 @@ -1679,7 +1679,7 @@ jobs: bash scripts/conformance/server-parity/container-log-tails.sh } trap container_log_tails ERR - image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.8.json) bash scripts/ci/run-composer.sh --rm -v "$PWD:/source" -w /source -- \ install --working-dir=scripts/conformance/server-parity --no-interaction --no-progress # Each profile brings its own published PHP and embedded databases and @@ -1726,7 +1726,7 @@ jobs: if: ${{ matrix.part == 'cohorts' }} run: | set -euo pipefail - image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.8.json) bash scripts/ci/run-composer.sh --rm -v "$PWD:/source" -w /source -- \ install --working-dir=scripts/conformance/server-parity --no-interaction --no-progress docker run -d --name "$RESOURCE_SCOPE-native-b" --network "$RESOURCE_SCOPE" --network-alias=parity-native-b \ @@ -1828,7 +1828,7 @@ jobs: if: ${{ matrix.part == 'corrected-markers' }} run: | set -euo pipefail - image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.8.json) bash scripts/ci/run-composer.sh --rm -v "$PWD:/source" -w /source -- \ install --working-dir=scripts/conformance/server-parity --no-interaction --no-progress docker run -d --name "$RESOURCE_SCOPE-native-b" --network "$RESOURCE_SCOPE" --network-alias=parity-native-b \ @@ -2000,7 +2000,7 @@ jobs: run: | set -euo pipefail mkdir -p parity-evidence parity-summary - image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.8.json) docker run --rm --user="$(id -u):$(id -g)" --network=none --cpus=1 --memory=256m --memory-swap=256m \ --entrypoint node -e PARITY_JOB_OUTCOME -e GITHUB_SHA \ -v "$PWD:/source:ro" -v "$PWD/parity-evidence:/evidence:ro" -v "$PWD/parity-summary:/summary" \ @@ -2046,7 +2046,7 @@ jobs: } bash scripts/ci/pull-images.sh \ "${NATIVE_RUST_IMAGE}" \ - "$(baseline php-2.11.7)" \ + "$(baseline php-2.11.8)" \ "$(baseline php-2.5.14)" \ composer:2.10.3@sha256:da624c2c705d54e2f5a9bb77a5dc6c7d091711433ebefd7035672e84c2010fee - name: Fetch this run's published SDK adapters @@ -2155,7 +2155,7 @@ jobs: -v "$PWD/parity-evidence:/evidence" -w /source/rust --entrypoint bash \ "${NATIVE_RUST_IMAGE}" -c \ 'cargo test --locked --offline --all-targets > /evidence/rust-tests.txt && cargo build --locked --offline && cargo run --locked --offline --example codec-fixtures -- ../tests/Fixtures/ServerParity/Codec/v1.json > /evidence/rust-codec.json' - image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.8.json) docker run --rm --user="$(id -u):$(id -g)" --network=none --entrypoint php \ -v "$PWD:/source:ro" -v "$PWD/parity-evidence:/evidence" -w /source "$image" \ scripts/conformance/server-parity/codec.php tests/Fixtures/ServerParity/Codec/v1.json \ @@ -2179,7 +2179,7 @@ jobs: bash scripts/conformance/server-parity/container-log-tails.sh } trap container_log_tails ERR - image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.8.json) mkdir -p parity-state parity-evidence chmod 0777 parity-state parity-evidence touch parity-state/php.sqlite parity-state/embedded.sqlite parity-state/php-layout.sqlite @@ -2334,7 +2334,7 @@ jobs: bash scripts/conformance/server-parity/container-log-tails.sh } trap container_log_tails ERR - image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.8.json) export DW_PARITY_RUNNER_REVISION=$(git rev-parse HEAD) mkdir -p parity-consumers curl --fail --location --retry 3 --output parity-consumers/dw.phar https://github.com/durable-workflow/cli/releases/download/2.2.0/dw.phar @@ -2412,7 +2412,7 @@ jobs: bash scripts/conformance/server-parity/container-log-tails.sh } trap container_log_tails ERR - image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.8.json) bash scripts/conformance/server-parity/published-sdk-patch-profile.sh sqlite http://parity-server:8080 http://parity-rust:8080 bash scripts/conformance/server-parity/repeated-patch-profile.sh sqlite http://parity-server:8080 http://parity-rust:8080 bash scripts/conformance/server-parity/legacy-marker-profile.sh sqlite http://parity-server:8080 http://parity-rust:8080 @@ -2493,7 +2493,7 @@ jobs: bash scripts/conformance/server-parity/container-log-tails.sh } trap container_log_tails ERR - image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.8.json) bash scripts/ci/run-composer.sh --rm -v "$PWD:/source" -w /source -- \ install --working-dir=scripts/conformance/server-parity --no-interaction --no-progress mkdir -p parity-state parity-evidence @@ -2537,7 +2537,7 @@ jobs: RESOURCE_SCOPE: dw-parity-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }} run: | set -euo pipefail - image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.8.json) bash scripts/ci/run-composer.sh --rm -v "$PWD:/source" -w /source -- \ install --working-dir=scripts/conformance/server-parity --no-interaction --no-progress mkdir -p parity-state parity-evidence @@ -2658,7 +2658,7 @@ jobs: RESOURCE_SCOPE: dw-parity-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }} run: | set -euo pipefail - image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.8.json) bash scripts/ci/run-composer.sh --rm -v "$PWD:/source" -w /source -- \ install --working-dir=scripts/conformance/server-parity --no-interaction --no-progress mkdir -p parity-state parity-evidence @@ -2776,7 +2776,7 @@ jobs: run: | set -euo pipefail mkdir -p parity-evidence parity-summary - image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.8.json) docker run --rm --user="$(id -u):$(id -g)" --network=none --cpus=1 --memory=256m --memory-swap=256m \ --entrypoint node -e PARITY_JOB_OUTCOME -e GITHUB_SHA \ -v "$PWD:/source:ro" -v "$PWD/parity-evidence:/evidence:ro" -v "$PWD/parity-summary:/summary" \ diff --git a/docs/rust-server-port-checkpoints/2026-11-01-reference-2118.md b/docs/rust-server-port-checkpoints/2026-11-01-reference-2118.md new file mode 100644 index 000000000..55f9055e7 --- /dev/null +++ b/docs/rust-server-port-checkpoints/2026-11-01-reference-2118.md @@ -0,0 +1,54 @@ +# November 1, the parity reference moves to published 2.11.8 + +- **Before.** The Shared Server fixtures compared native and embedded + against published Server 2.11.7 (Core 2.15.7). +- **Reference.** Published Server 2.11.8, Core 2.15.12, as General + published it (Server #789 receipt 6558026433, #765 receipt 6558025643): + - source `fa3fc2f2f6b8935ea4a6eea47f6a9221e2adafff`; + - index `sha256:3848d5d5cd1b23bd2881e3fdecea64cc44b72e6fb812e577c2db6d0872d065dd`; + - linux/amd64 `sha256:980fe0a052089ced34ec3fcd2be2ea4e77436861d8fb537fb6654cb652a53e07`; + - linux/arm64 `sha256:cea163f8fcedf3a17302acf0141b073a85fbdb395ebbcd59e5af6f5fa9ad75e1`. + + `tests/Fixtures/ServerParityBaselines/php-2.11.8.json` records it. The + SDK, CLI, Waterline and sample-app pins are unchanged. The takeover + cohorts keep their own source images. +- **What changed on 2.11.8, and the answers recorded.** + - **Post-commit payload deletion counts** (#911). Every retention pass + and namespace delete answer carries `external_payload_cleanup`, and the + payload cleanup status and pass carry `retention_cleanup` + (`pending`, `failed`, `blocked`). Native added them only while a + deletion was pending (#914); it now always does. `namespace-delete`, + `payload-cleanup`, `retention-external-payloads` and + `namespace-delete-payloads` pin the counts. + - **A child resolved under a leased parent task** (workflow#962, Core + 2.15.10). `child-open-parent-task` gains a leased case: the parent's + resume task is claimed, the child completes, and the lease completes + with a timer and without the result. 2.11.8 then offers one follow-up + task that replays the child's result, as native does. Native left that + moment's run summary unprojected (a resolved child beside the timer); + it now describes it as 2.11.8 does: `timer_scheduled`, waiting for the + timer, no repair attention. + - **History import** (Core 2.15.8 to 2.15.12). The `workflow-import` + probe's run id conflict bundle renamed only `workflow.run_id`; 2.11.8 + checks each command's and signal's `resolved_run_id` first, so the + probe now renames every `*run_id` naming the original run, and the case + still answers `target.run_id_conflict`. The cross-namespace attach now + answers `target.instance_namespace_conflict` before writing, and a run + imported into another namespace answers as a conflict; native matches + both, and the fixture's published-defect allowances for 2.11.1 to + 2.11.7 are removed. + - **Embedded probes.** Core 2.15.12 resolves a run through its + instance's namespace and reserves a caller-named instance without + model events, so the probes' `creating` hooks left that instance's + namespace empty and every embedded recording failed to find its run. + The embedded probes now configure `workflows.v2.namespace`. A host + application without the hooks is unaffected. +- **Local checks before the pull request** (SQLite, this host): + - the four cleanup fixtures, `child-open-parent-task` and + `workflow-import` recorded against a fresh 2.11.8 harness pass, and + native built from this branch passes `child-open-parent-task` and + `workflow-import`; + - without the namespace setting every embedded recording fails on 2.11.8 + and passes on 2.11.7; with it, embedded recordings pass on 2.11.8; + - the native SQLite execution suite and the node unit suite (see the pull + request for counts and the full corpus run). diff --git a/docs/rust-server-port.md b/docs/rust-server-port.md index f50db6877..3d292139c 100644 --- a/docs/rust-server-port.md +++ b/docs/rust-server-port.md @@ -47,6 +47,11 @@ Updated October 27, 2026. - The parity reference moves to published 2.11.7 (#863), with native's v1 projection history bytes in Core's canonical unit (#770) ([checkpoint](rust-server-port-checkpoints/2026-10-29-reference-2117.md)). + - The parity reference moves to published 2.11.8 (Core 2.15.12): native + always reports post-commit payload deletion counts, and a child's + resolution under a leased parent task now gives PHP a follow-up task + (workflow#962) + ([checkpoint](rust-server-port-checkpoints/2026-11-01-reference-2118.md)). - [#814](https://github.com/durable-workflow/server/pull/814): the shared fixtures accept published PHP's typed `429 long_poll_capacity_exhausted` poll refusal, and retry a locked Composer install after a transient @@ -433,28 +438,45 @@ contract fixture before its correction. PHP encodes nested objects as Avro maps in request order; native encodes them with sorted keys. The decoded values are equal, but the blobs differ. - **Child completion while the parent has an open workflow task** - (November 1; ready task closed, leased task decided; - [checkpoint](rust-server-port-checkpoints/2026-11-01-child-open-parent-task.md)). - Published PHP 2.11.7 records the child resolution and, while the parent - has a ready or leased workflow task, creates none and leaves that task's - payload as it is (`dispatchParentResumeTasks`). - - **Ready task.** Native now does the same: the ready task replays the - parent with the resolution in its history. An update task does not - replay the workflow, so beside one the resolution still gets its own - resume task. The `child-open-parent-task` fixture records it against - published 2.11.7. - - **Leased task.** On 2.11.7 a lease whose worker read history before the - resolution completes without it, and the parent is left with no task; - the repair pass does not recover it - ([workflow#962](https://github.com/durable-workflow/workflow/issues/962)). - Core 2.15.10, not yet in a published Server release, creates one child - resume task when such a lease completes. Native gives the resolution - its own resume task at once, as activity and timer resolutions do on - both servers. Compare again when the parity reference moves to a Server - release with Core 2.15.10. + (November 1, closed; + [checkpoint](rust-server-port-checkpoints/2026-11-01-child-open-parent-task.md), + [reference move](rust-server-port-checkpoints/2026-11-01-reference-2118.md)). + Published PHP records the child resolution and, while the parent has a + ready or leased workflow task, creates none and leaves that task's payload + as it is (`dispatchParentResumeTasks`). + - **Ready task.** Native does the same: the ready task replays the parent + with the resolution in its history. An update task does not replay the + workflow, so beside one the resolution still gets its own resume task. + - **Leased task.** A lease whose worker read history before the + resolution completes without it. Published 2.11.7 then left the parent + with no task ([workflow#962](https://github.com/durable-workflow/workflow/issues/962)); + 2.11.8 (Core 2.15.10 and later) gives it one follow-up task that + replays the child's result. Native gives the resolution its own resume + task at once, as activity and timer resolutions do, so after the lease + both servers offer exactly one task that replays the result. While the + lease's new timer is open beside that task, native now describes the + run as 2.11.8 does (`timer_scheduled`, waiting for the timer) instead of + leaving it unprojected. The `child-open-parent-task` fixture records + both cases against published 2.11.8. - A signal creates no resume while the parent waits only on a child, as on PHP: `PendingMessageTask::signalCanAdvanceRun` advances only signal and condition waits. +- **History import fences of Core 2.15.8 to 2.15.12** (November 1, + narrowed with the reference move to 2.11.8; + [checkpoint](rust-server-port-checkpoints/2026-11-01-reference-2118.md)). + Published 2.11.8 checks a bundle's internal references before its target: + each command's and signal's `resolved_run_id` must name the imported run, + messages must bind to the bundle's commands, and lineage must stay within + verified endpoints (`EmbeddedV2ImportReferences`, + `EmbeddedV2ImportMessageReferences`, `EmbeddedV2ImportLineage`). It also + refuses an instance of another namespace before writing + (`target.instance_namespace_conflict`), and a run already imported into + another namespace as a run id conflict. Native now gives both target + answers as 2.11.8 does, and the `workflow-import` fixture records them; + the published-defect allowances for 2.11.1 to 2.11.7 are gone. Native's + own reference fence (#765) runs after the run id check, and its rules and + rule names have not yet been compared with Core's for bundles whose + references are inconsistent; no fixture case sends such a bundle. - **Update delivery into a ready task** (November 1, decided: native keeps its behavior). Published PHP merges an accepted update into the run's ready workflow task (`ExternalWorkflowUpdateAdmission::wakeWorkflowTask`). @@ -509,14 +531,16 @@ contract fixture before its correction. the prune records the deletion in `dw_external_payload_deletions` (schema version 8), so a prune that fails later keeps the run, its registrations and every object. A provider failure after the commit leaves the deletion - pending, retried by the next pass. While one is pending the retention pass - reports `external_payload_cleanup`, the payload cleanup routes report - `retention_cleanup` (the pass answering 207), and namespace delete reports - `external_payload_cleanup` with 207; 2.11.8 adds those counts to clean - answers too, which native will do once CI's parity reference moves from - 2.11.7, whose clean answers lack them. An online-backup hold still fails the prune before it commits. The - `retention-external-payloads` fixture records it against published 2.11.7 - and passes unchanged against 2.11.8. + pending, retried by the next pass. As on 2.11.8, every answer carries the + counts (`pending`, `failed`, `blocked`): the retention pass and namespace + delete as `external_payload_cleanup`, the payload cleanup routes as + `retention_cleanup`. Namespace delete answers 207 while a deletion is + pending, and the cleanup pass also when it blocked a reference. An + online-backup hold still fails the prune before it commits. The + `retention-external-payloads`, `namespace-delete-payloads`, + `namespace-delete` and `payload-cleanup` fixtures pin the counts against + published 2.11.8 + ([checkpoint](rust-server-port-checkpoints/2026-11-01-reference-2118.md)). A run holding a reference native cannot settle (PHP's stored format from a workflow import, an envelope, or one on an inbound or durable stream) is still skipped with `external_payload_cleanup_unavailable`. Since November 1, diff --git a/rust/src/runtime/child_summary.rs b/rust/src/runtime/child_summary.rs index 65126f721..3421a22af 100644 --- a/rust/src/runtime/child_summary.rs +++ b/rust/src/runtime/child_summary.rs @@ -203,6 +203,16 @@ where { return Ok(true); } + // A resolved child beside an open timer, while a workflow task that + // will apply it carries the run (a lease that completed without the + // child's result, workflow#962): published 2.11.8 describes the + // timer and reports no problem (`child-open-parent-task`, leased). + if summary.wait.as_ref().is_some_and(|wait| wait.kind == "timer") + && summary.task_type.as_deref() == Some("workflow") + && rows.iter().all(|row| DB::string(row, "status").is_ok_and(|status| status != "open")) + { + return Ok(true); + } if summary .wait .as_ref() diff --git a/rust/src/runtime/execution.rs b/rust/src/runtime/execution.rs index 6bbb83d6c..2dae5ef87 100644 --- a/rust/src/runtime/execution.rs +++ b/rust/src/runtime/execution.rs @@ -1773,6 +1773,7 @@ impl Runtime { delegate!(show_service_call(names: [&str; 3], id: &str) -> Result); delegate!(cancel_service_call(names: [&str; 3], id: &str, reason: Option) -> Result<(StatusCode, Value)>); delegate!(import_existing_run(run_id: &str) -> Result>); + delegate!(import_instance_namespace(instance_id: &str) -> Result>); delegate!(import_write(plan: &super::workflow_import::Plan) -> std::result::Result); delegate!(waterline_existing(workflow_id: &str, run_id: &str) -> Result); delegate!(projected_current_run(workflow_id: &str) -> Result>); diff --git a/rust/src/runtime/namespace_cleanup.rs b/rust/src/runtime/namespace_cleanup.rs index 750499c53..e5e9d3beb 100644 --- a/rust/src/runtime/namespace_cleanup.rs +++ b/rust/src/runtime/namespace_cleanup.rs @@ -10,8 +10,9 @@ //! `RuntimeExternalPayloadRegistry::deleteForNamespace` release them //! (`delete_namespace_payloads`). Their provider deletions are recorded in the //! delete's transaction and run only after it commits (`payload_deletions`), -//! so a failed delete keeps every object. While a deletion is still pending -//! the answer carries `external_payload_cleanup` and is 207. +//! so a failed delete keeps every object. The answer always carries their +//! `external_payload_cleanup` counts, as 2.11.8's does, and is 207 while a +//! deletion is still pending. //! A namespace holding a reference native cannot settle (PHP's stored //! format, an envelope, or one on a stream) refuses before changing anything, //! with PHP's answer for unavailable external payload storage. @@ -278,13 +279,12 @@ store_methods! { Self::query("DELETE FROM workflow_namespaces WHERE name=$1").bind(&name).execute(&mut *tx).await?; tx.commit().await?; // The delete has committed; only now may provider objects go. A - // deletion left pending answers 207 with its counts. + // deletion left pending answers 207. self.settle_payload_deletions(&staged).await; - let mut body = json!({"name": name, "status": "deleted", "deleted": deleted}); - if let Some(cleanup) = self.reported_payload_deletions(&name).await? { - body["external_payload_cleanup"] = cleanup; - } - Ok(body) + Ok(json!({ + "name": name, "status": "deleted", "deleted": deleted, + "external_payload_cleanup": self.payload_deletion_status(&name).await?, + })) } /// The namespace's external payloads, inside its delete's transaction: diff --git a/rust/src/runtime/operator_metrics.rs b/rust/src/runtime/operator_metrics.rs index 7807d2521..0529e2292 100644 --- a/rust/src/runtime/operator_metrics.rs +++ b/rust/src/runtime/operator_metrics.rs @@ -1256,9 +1256,7 @@ where let failed = report["failed"].as_i64().is_some_and(|failed| failed > 0); let mut fields = policy.fields(self.namespace.as_ref()); fields.extend(report); - if let Some(cleanup) = self.reported_payload_deletions(self.namespace.as_ref()).await? { - fields.insert("external_payload_cleanup".into(), cleanup); - } + fields.insert("external_payload_cleanup".into(), self.payload_deletion_status(self.namespace.as_ref()).await?); Ok((Value::Object(fields), failed)) } diff --git a/rust/src/runtime/payload_cleanup.rs b/rust/src/runtime/payload_cleanup.rs index ca164444b..54417321e 100644 --- a/rust/src/runtime/payload_cleanup.rs +++ b/rust/src/runtime/payload_cleanup.rs @@ -60,11 +60,11 @@ struct Report { store_methods! { /// `externalPayloadCleanupStatus`: the namespace's cleanup metrics. pub(crate) async fn payload_cleanup_status(&self, config: &super::operator_metrics::MetricsConfig) -> Result { - let mut status = json!({"namespace": self.namespace.as_ref(), "cleanup": self.external_payload_cleanup_metrics(config).await?}); - if let Some(cleanup) = self.reported_payload_deletions(self.namespace.as_ref()).await? { - status["retention_cleanup"] = cleanup; - } - Ok(status) + Ok(json!({ + "namespace": self.namespace.as_ref(), + "cleanup": self.external_payload_cleanup_metrics(config).await?, + "retention_cleanup": self.payload_deletion_status(self.namespace.as_ref()).await?, + })) } /// The pass's report; the caller answers 207 when it blocked anything or @@ -113,18 +113,15 @@ store_methods! { .bind(DB::bind_time(cutoff)) .fetch_one(&self.pool) .await?; - let mut body = json!({ + Ok(json!({ "processed": report.processed, "deleted_references": report.deleted_references, "deleted_backing_objects": report.deleted_backing_objects, "shared_objects_preserved": report.shared_objects_preserved, "skipped": report.skipped, "blocked": report.blocked, "storage_driver_failures": report.storage_driver_failures, "backlog": backlog, "scan_limit": limit, "scan_pressure": backlog > 0 && candidates.len() as i64 >= limit, - }); - if let Some(cleanup) = self.reported_payload_deletions(self.namespace.as_ref()).await? { - body["retention_cleanup"] = cleanup; - } - Ok(body) + "retention_cleanup": self.payload_deletion_status(self.namespace.as_ref()).await?, + })) } /// `RuntimePayloadCompletionUploads::cleanup`: an expired budget whose diff --git a/rust/src/runtime/payload_deletions.rs b/rust/src/runtime/payload_deletions.rs index c92f3ac4a..4f0bd50cb 100644 --- a/rust/src/runtime/payload_deletions.rs +++ b/rust/src/runtime/payload_deletions.rs @@ -221,12 +221,4 @@ store_methods! { Ok(json!({"pending": pending, "failed": failed, "blocked": blocked})) } - /// The status where a response reports it: 2.11.8 always adds the counts, - /// but CI's parity reference is still 2.11.7, whose clean answers have no - /// such member. Until the reference moves to 2.11.8 native adds them only - /// when a deletion is pending; a clean answer stays 2.11.7's. - pub(super) async fn reported_payload_deletions(&self, namespace: &str) -> Result> { - let status = self.payload_deletion_status(namespace).await?; - Ok((status["pending"].as_i64().unwrap_or(0) > 0).then_some(status)) - } } diff --git a/rust/src/runtime/workflow_import.rs b/rust/src/runtime/workflow_import.rs index 8c036d5a5..8772f38ec 100644 --- a/rust/src/runtime/workflow_import.rs +++ b/rust/src/runtime/workflow_import.rs @@ -1054,6 +1054,15 @@ impl Runtime { return Ok(answer("rejected", Some(prepared.eligibility.with_error("target.run_id_conflict", "A workflow run with this run_id already exists and was not imported from the same bundle.")), &empty, None)); } + // Core 2.15.12 (Server 2.11.8): an instance of another namespace is + // refused before a dry run or a write, not left to the write's failure. + let instance_id = prepared.summary.instance_id.clone().unwrap_or_default(); + if let Some(namespace) = self.import_instance_namespace(&instance_id).await? + && namespace != prepared.summary.namespace + { + return Ok(answer("rejected", Some(prepared.eligibility.with_error("target.instance_namespace_conflict", + "A workflow instance with this instance_id already exists outside the target namespace.")), &empty, None)); + } let root = Php::Array(prepared.bundle.clone()); // Native's reference fence (Server #765), after PHP's eligibility and // run checks so their answers stay PHP's, and before a dry run or a @@ -1133,6 +1142,18 @@ store_methods! { })) } + /// The namespace of an existing workflow instance, if there is one. + pub(crate) async fn import_instance_namespace(&self, instance_id: &str) -> Result> { + let Some(row) = Self::query("SELECT namespace FROM workflow_instances WHERE id=$1") + .bind(instance_id) + .fetch_optional(&self.pool) + .await? + else { + return Ok(None); + }; + Ok(Some(DB::optional_string(&row, "namespace")?.unwrap_or_default())) + } + pub(crate) fn namespace_name(&self) -> String { self.namespace.as_ref().to_owned() } diff --git a/rust/tests/execution/external_payloads/cleanup.rs b/rust/tests/execution/external_payloads/cleanup.rs index 4b4e32bb0..b8f3bf974 100644 --- a/rust/tests/execution/external_payloads/cleanup.rs +++ b/rust/tests/execution/external_payloads/cleanup.rs @@ -38,7 +38,9 @@ async fn status(router: &Router) -> Value { fn report(values: [i64; 7], backlog: i64, limit: i64, pressure: bool) -> Value { json!({"processed": values[0], "deleted_references": values[1], "deleted_backing_objects": values[2], "shared_objects_preserved": values[3], "skipped": values[4], "blocked": values[5], - "storage_driver_failures": values[6], "backlog": backlog, "scan_limit": limit, "scan_pressure": pressure}) + "storage_driver_failures": values[6], "backlog": backlog, "scan_limit": limit, "scan_pressure": pressure, + // 2.11.8 always reports committed retention deletions still pending. + "retention_cleanup": {"pending": 0, "failed": 0, "blocked": 0}}) } #[tokio::test] diff --git a/rust/tests/execution/external_payloads/namespace_delete.rs b/rust/tests/execution/external_payloads/namespace_delete.rs index cbf1a053b..307173a83 100644 --- a/rust/tests/execution/external_payloads/namespace_delete.rs +++ b/rust/tests/execution/external_payloads/namespace_delete.rs @@ -46,7 +46,7 @@ async fn namespace_delete_settles_its_external_payloads() { assert_eq!(body["deleted"]["external_payloads_deleted"], 1, "{body}"); assert_eq!(body["deleted"]["runtime_external_payload_references"], 2, "{body}"); // Nothing pending: the answer is 2.11.7's, without the cleanup counts. - assert!(body.get("external_payload_cleanup").is_none(), "{body}"); + assert_eq!(body["external_payload_cleanup"], json!({"pending": 0, "failed": 0, "blocked": 0}), "{body}"); assert!(!object(&root, &sole).exists()); assert!(!object(&root, &unused).exists()); assert!(object(&root, &theirs).exists()); diff --git a/rust/tests/execution/external_payloads/retention.rs b/rust/tests/execution/external_payloads/retention.rs index 74b03ad2c..9ee9d6cf3 100644 --- a/rust/tests/execution/external_payloads/retention.rs +++ b/rust/tests/execution/external_payloads/retention.rs @@ -187,7 +187,7 @@ async fn retention_without_storage_or_after_a_failed_delete_keeps_the_run() { std::fs::set_permissions(&directory, std::fs::Permissions::from_mode(0o755)).unwrap(); let (status, body) = cleanup_pass(&router).await; assert_eq!(status, StatusCode::OK, "{body}"); - assert!(body["cleanup"].get("retention_cleanup").is_none(), "{body}"); + assert_eq!(body["cleanup"]["retention_cleanup"], json!({"pending": 0, "failed": 0, "blocked": 0}), "{body}"); } else { std::fs::remove_file(directory.join("probe")).unwrap(); std::fs::set_permissions(&directory, std::fs::Permissions::from_mode(0o755)).unwrap(); @@ -199,7 +199,7 @@ async fn retention_without_storage_or_after_a_failed_delete_keeps_the_run() { let (status, body) = ns_request(&router, "POST", PASS, "default", json!({"run_ids": [second]})).await; assert_eq!(status, StatusCode::OK, "{body}"); pruned(&body["results"][0], 1); - assert!(body.get("external_payload_cleanup").is_none(), "nothing pending: {body}"); + assert_eq!(body["external_payload_cleanup"], json!({"pending": 0, "failed": 0, "blocked": 0}), "nothing pending: {body}"); assert!(!object(&root, &shared).exists()); } @@ -308,7 +308,7 @@ async fn pending_deletions_wait_out_a_backup_hold_and_yield_to_a_new_registratio database.execute("UPDATE runtime_external_payload_backup_hold SET expires_at='2000-01-01 00:00:00'").await; let (status, body) = cleanup_pass(&router).await; assert_eq!(status, StatusCode::OK, "{body}"); - assert!(body["cleanup"].get("retention_cleanup").is_none(), "{body}"); + assert_eq!(body["cleanup"]["retention_cleanup"], json!({"pending": 0, "failed": 0, "blocked": 0}), "{body}"); assert!(!object(&root, &held).exists()); assert!(object(&root, &again).exists()); assert_eq!(registered(&database, &again).await, 1); diff --git a/rust/tests/execution/namespaces.rs b/rust/tests/execution/namespaces.rs index 7849e0d3e..7c51a003b 100644 --- a/rust/tests/execution/namespaces.rs +++ b/rust/tests/execution/namespaces.rs @@ -797,8 +797,9 @@ async fn namespace_delete_removes_only_what_the_namespace_owns() { let (status, body) = scoped(&app, "default", "DELETE", "/api/namespaces/DOOMED", Value::Null).await; assert_eq!(status, StatusCode::OK, "{body}"); // What published PHP 2.5.14 reports for one started run and one search - // attribute definition. - assert_eq!(body, json!({"name":"doomed","status":"deleted","deleted":{ + // attribute definition, with 2.11.8's post-commit deletion counts. + assert_eq!(body, json!({"name":"doomed","status":"deleted", + "external_payload_cleanup":{"pending":0,"failed":0,"blocked":0},"deleted":{ "workflow_commands":1,"workflow_history_events":2,"workflow_tasks":1, "search_attribute_definitions":1,"workflow_run_summaries":1,"workflow_runs":1,"workflow_instances":1}})); for (method, uri) in [("GET", "/api/namespaces/doomed"), ("DELETE", "/api/namespaces/doomed")] { diff --git a/scripts/conformance/server-parity.mjs b/scripts/conformance/server-parity.mjs index c12e7148c..617d4a10a 100644 --- a/scripts/conformance/server-parity.mjs +++ b/scripts/conformance/server-parity.mjs @@ -44,7 +44,7 @@ Requires Node 20+ and PHP 8.3+ with the locked adapter installed. Each target must have its own isolated, already bootstrapped database. Set DW_PARITY_TOKEN for the HTTP target; it is never recorded. Use the same --prefix on all targets and a fresh database or prefix for a new run. --artifacts selects the exact -consumer tuple; the default is tests/Fixtures/ServerParityBaselines/php-2.11.7.json. +consumer tuple; the default is tests/Fixtures/ServerParityBaselines/php-2.11.8.json. Repeat --fixture PATH to record or compare an explicitly selected candidate set. --database-resource-limits accepts inspected memory_bytes and memory_swap_bytes as a JSON object; comparisons require equal effective @@ -81,7 +81,7 @@ try { } const record = { schema: 'durable-workflow.server-parity-record/v1', target: values.target, mode: values.mode, - runner_revision: values['runner-revision'], artifacts: json(values.artifacts ?? resolve(root, 'tests/Fixtures/ServerParityBaselines/php-2.11.7.json')), + runner_revision: values['runner-revision'], artifacts: json(values.artifacts ?? resolve(root, 'tests/Fixtures/ServerParityBaselines/php-2.11.8.json')), started_at: new Date().toISOString(), outcome: 'runner-blocked', fixture_hashes: hashes, cases: [], database_resource_limits: parseDatabaseResourceLimits(values['database-resource-limits']), }; diff --git a/scripts/conformance/server-parity/README.md b/scripts/conformance/server-parity/README.md index e6710ff1a..58c0521ad 100644 --- a/scripts/conformance/server-parity/README.md +++ b/scripts/conformance/server-parity/README.md @@ -287,7 +287,7 @@ Docker kill and verifies exit code 137 before restarting the native process. For an adapter installed elsewhere, set `DW_PARITY_SDK_AUTOLOAD` to its locked `vendor/autoload.php`. `--artifacts` selects a frozen tuple manifest. The default -is `tests/Fixtures/ServerParityBaselines/php-2.11.7.json`; its SDK version must match +is `tests/Fixtures/ServerParityBaselines/php-2.11.8.json`; its SDK version must match the installed lock. A frozen tuple does not prove every listed consumer ran. The original `tests/Fixtures/ServerParity/php-baseline.json` remains an immutable 2.5.13 / Workflow 2.5.4 reference, together with its known NUL reproducer outside diff --git a/scripts/conformance/server-parity/activity-deadline-embedded.php b/scripts/conformance/server-parity/activity-deadline-embedded.php index 293dd8323..94f7c434e 100644 --- a/scripts/conformance/server-parity/activity-deadline-embedded.php +++ b/scripts/conformance/server-parity/activity-deadline-embedded.php @@ -53,6 +53,9 @@ public function handle(mixed $value): mixed } config(['queue.default' => 'database', 'workflows.v2.task_dispatch_mode' => 'queue', + // Core 2.15.12 (Server 2.11.8) resolves a run through its instance's + // namespace, and reserves a caller-named instance without model events. + 'workflows.v2.namespace' => 'default', 'workflows.v2.types.workflows' => ['parity.v1.activity_deadline' => ParityDeadlineWorkflow::class], 'workflows.v2.types.activities' => ['parity.v1.deadline_activity' => ParityDeadlineActivity::class]]); foreach ([\Workflow\V2\Models\WorkflowInstance::class, \Workflow\V2\Models\WorkflowRun::class, diff --git a/scripts/conformance/server-parity/activity-deadline-receipts-embedded.php b/scripts/conformance/server-parity/activity-deadline-receipts-embedded.php index 2e68bbadd..064a238d4 100644 --- a/scripts/conformance/server-parity/activity-deadline-receipts-embedded.php +++ b/scripts/conformance/server-parity/activity-deadline-receipts-embedded.php @@ -51,6 +51,9 @@ public function handle(mixed $value): mixed } config(['queue.default' => 'database', 'workflows.v2.task_dispatch_mode' => 'queue', + // Core 2.15.12 (Server 2.11.8) resolves a run through its instance's + // namespace, and reserves a caller-named instance without model events. + 'workflows.v2.namespace' => 'default', 'workflows.v2.types.workflows' => [$fixture['workflow_type'] => ParityReceiptWorkflow::class], 'workflows.v2.types.activities' => ['parity.v1.deadline_activity' => ParityReceiptActivity::class]]); foreach ([\Workflow\V2\Models\WorkflowInstance::class, \Workflow\V2\Models\WorkflowRun::class, diff --git a/scripts/conformance/server-parity/cancellation-probe.php b/scripts/conformance/server-parity/cancellation-probe.php index df76cfece..df4e04b39 100644 --- a/scripts/conformance/server-parity/cancellation-probe.php +++ b/scripts/conformance/server-parity/cancellation-probe.php @@ -174,6 +174,9 @@ function embeddedCancellationObservation(array $fixture, string $workflowId, str $app->make(Kernel::class)->bootstrap(); require __DIR__.'/embedded-types.php'; config(['queue.default' => 'database', 'workflows.v2.task_dispatch_mode' => 'queue', + // Core 2.15.12 (Server 2.11.8) resolves a run through its instance's + // namespace, and reserves a caller-named instance without model events. + 'workflows.v2.namespace' => $namespace, 'workflows.v2.types.workflows' => [ 'parity.v1.cancel_before_claim' => \ServerParity\CancelBeforeClaimWorkflow::class, 'parity.v1.cancel_pending_timer' => \ServerParity\CancelPendingTimerWorkflow::class, diff --git a/scripts/conformance/server-parity/child-open-parent-task-probe.php b/scripts/conformance/server-parity/child-open-parent-task-probe.php index 0b4df8ab0..6dc45c8f5 100644 --- a/scripts/conformance/server-parity/child-open-parent-task-probe.php +++ b/scripts/conformance/server-parity/child-open-parent-task-probe.php @@ -13,6 +13,10 @@ // result leaves the parent a ready resume task; then the child completes. // Records the parent's description, every workflow task a worker can then // claim for the parent, and the run's close from the first of them. +// +// The leased case claims that resume task before the child completes, so the +// lease's history lacks the child's result, then completes the lease with a +// long timer (workflow#962: Core 2.15.10 gives the parent a follow-up task). function finishHttpChildOpenParentTask(Client $admin, array $fixture, string $workflowId, string $url): array { $definition = $fixture['child_open_parent_task']; @@ -66,51 +70,74 @@ function finishHttpChildOpenParentTask(Client $admin, array $fixture, string $wo ['lease_owner' => $task['lease_owner'], 'workflow_task_attempt' => $task['workflow_task_attempt'], 'commands' => $commands]); $answer = static fn (array $result): array => ['status' => $result['status'], 'reason' => $result['response']['reason'] ?? null, 'run_status' => $result['response']['run_status'] ?? null]; - $described = static fn (): array => array_intersect_key( - $raw('GET', 'workflows/'.rawurlencode($id))['response'], array_flip($definition['described_fields'])); - - $first = $poll('workflow', $worker, $queue, 5) ?? throw new RuntimeException('Child open parent task: no first task'); - $opened = $complete($first, [ - ['type' => 'start_child_workflow', 'workflow_type' => $definition['child_type'], 'arguments' => $empty, 'queue' => $childQueue], - ['type' => 'schedule_activity', 'activity_type' => $definition['activity_type'], 'arguments' => $empty], - ]); - $activity = $poll('activity', $worker, $queue, 5) ?? throw new RuntimeException('Child open parent task: no activity'); - $activityDone = $raw('POST', 'worker/activity-tasks/'.$activity['task_id'].'/complete', ['lease_owner' => $activity['lease_owner'], - 'activity_attempt_id' => $activity['activity_attempt_id'], 'result' => $empty]); - $child = $poll('workflow', $childWorker, $childQueue, 5) ?? throw new RuntimeException('Child open parent task: no child task'); - $childDone = $complete($child, [['type' => 'complete_workflow', 'result' => $empty]]); - $afterChild = $described(); - - // Every workflow task a worker can now claim for the parent. - $claimed = []; - for ($attempt = 0; $attempt < 3; $attempt++) { - $task = $poll('workflow', $worker, $queue, $attempt === 0 ? 5 : 1); - if ($task === null) { - break; + $claimable = static function (string $runId) use ($poll, $worker, $queue): array { + // Every workflow task a worker can now claim for the parent. + $claimed = []; + for ($attempt = 0; $attempt < 3; $attempt++) { + $task = $poll('workflow', $worker, $queue, $attempt === 0 ? 5 : 1); + if ($task === null) { + break; + } + if (isset($task['workflow_id']) && $task['workflow_id'] !== $runId) { + throw new RuntimeException('Child open parent task: a task for another run: '.json_encode($task['workflow_id'] ?? null)); + } + $claimed[] = $task; } - $claimed[] = $task; - } - $tasks = array_map(static fn (array $task): array => [ + + return $claimed; + }; + $shape = static fn (array $claimed): array => array_map(static fn (array $task): array => [ 'workflow_update_id' => $task['workflow_update_id'] ?? null, 'replays_child_result' => in_array('ChildRunCompleted', array_map( static fn (array $event): string => (string) ($event['event_type'] ?? ''), $task['history_events'] ?? []), true), ], $claimed); - $closed = $claimed === [] ? null : $answer($complete($claimed[0], [['type' => 'complete_workflow', 'result' => $empty]])); - $final = $described(); - if ($final['status'] !== 'completed') { - $raw('POST', 'workflows/'.rawurlencode($id).'/terminate', ['reason' => 'child open parent task recorded']); - } + $case = static function (string $runId, bool $leased, bool $start) use ($raw, $poll, $complete, $answer, $claimable, $shape, $definition, + $empty, $queue, $childQueue, $worker, $childWorker): array { + $described = static fn (): array => array_intersect_key( + $raw('GET', 'workflows/'.rawurlencode($runId))['response'], array_flip($definition['described_fields'])); + if ($start && $raw('POST', 'workflows', ['workflow_id' => $runId, 'workflow_type' => $definition['workflow_type'], + 'task_queue' => $queue, 'input' => $empty])['status'] !== 201) { + throw new RuntimeException('Child open parent task: start '.$runId); + } + $first = $poll('workflow', $worker, $queue, 5) ?? throw new RuntimeException('Child open parent task: no first task'); + $opened = $complete($first, [ + ['type' => 'start_child_workflow', 'workflow_type' => $definition['child_type'], 'arguments' => $empty, 'queue' => $childQueue], + ['type' => 'schedule_activity', 'activity_type' => $definition['activity_type'], 'arguments' => $empty], + ]); + $activity = $poll('activity', $worker, $queue, 5) ?? throw new RuntimeException('Child open parent task: no activity'); + $activityDone = $raw('POST', 'worker/activity-tasks/'.$activity['task_id'].'/complete', ['lease_owner' => $activity['lease_owner'], + 'activity_attempt_id' => $activity['activity_attempt_id'], 'result' => $empty]); + $lease = $leased ? ($poll('workflow', $worker, $queue, 5) ?? throw new RuntimeException('Child open parent task: no resume task')) : null; + $child = $poll('workflow', $childWorker, $childQueue, 5) ?? throw new RuntimeException('Child open parent task: no child task'); + $childDone = $complete($child, [['type' => 'complete_workflow', 'result' => $empty]]); + $afterChild = $described(); + // The lease's worker has not seen the child's result; it waits on a + // long timer instead, as a workflow body awaiting a timer would. + $leaseDone = $lease === null ? null : $answer($complete($lease, [['type' => 'start_timer', 'delay_seconds' => 3600]])); + $afterLease = $lease === null ? null : $described(); + $claimed = $claimable($runId); + $closed = $claimed === [] ? null : $answer($complete($claimed[0], [['type' => 'complete_workflow', 'result' => $empty]])); + $final = $described(); + if ($final['status'] !== 'completed') { + $raw('POST', 'workflows/'.rawurlencode($runId).'/terminate', ['reason' => 'child open parent task recorded']); + } - return [ - 'applicable' => true, - 'ready' => [ + return [ 'opened' => $answer($opened), 'activity_completed' => $activityDone['status'], + ...($lease === null ? [] : ['lease_replays_child_result' => $shape([$lease])[0]['replays_child_result']]), 'child_completed' => $answer($childDone), 'described_after_child' => $afterChild, - 'claimable_parent_tasks' => $tasks, + ...($lease === null ? [] : ['lease_completed' => $leaseDone, 'described_after_lease' => $afterLease]), + 'claimable_parent_tasks' => $shape($claimed), 'closed' => $closed, 'described_after_close' => $final, - ], + ]; + }; + + return [ + 'applicable' => true, + 'ready' => $case($id, false, false), + ...(isset($definition['expected']['leased']) ? ['leased' => $case($id.'-leased', true, true)] : []), ]; } diff --git a/scripts/conformance/server-parity/continue-embedded.php b/scripts/conformance/server-parity/continue-embedded.php index b67f07a71..cebc81d4f 100644 --- a/scripts/conformance/server-parity/continue-embedded.php +++ b/scripts/conformance/server-parity/continue-embedded.php @@ -69,6 +69,9 @@ public function handle(array $input): mixed } config(['queue.default' => 'database', 'workflows.v2.task_dispatch_mode' => 'queue', + // Core 2.15.12 (Server 2.11.8) resolves a run through its instance's + // namespace, and reserves a caller-named instance without model events. + 'workflows.v2.namespace' => 'default', 'workflows.v2.types.workflows' => [$fixture['workflow_type'] => ContinueEmbeddedWorkflow::class], 'workflows.v2.types.activities' => ['parity.v1.continue_echo' => ContinueEmbeddedActivity::class]]); foreach ([WorkflowInstance::class, WorkflowRun::class, \Workflow\V2\Models\WorkflowTask::class] as $model) { diff --git a/scripts/conformance/server-parity/cooperative-probe.php b/scripts/conformance/server-parity/cooperative-probe.php index 3a52e3ee4..4013998a7 100644 --- a/scripts/conformance/server-parity/cooperative-probe.php +++ b/scripts/conformance/server-parity/cooperative-probe.php @@ -184,6 +184,9 @@ function embeddedCooperativeObservation(array $fixture, string $workflowId, stri } require __DIR__.'/embedded-types.php'; config(['queue.default' => 'database', 'workflows.v2.task_dispatch_mode' => 'queue', + // Core 2.15.12 (Server 2.11.8) resolves a run through its instance's + // namespace, and reserves a caller-named instance without model events. + 'workflows.v2.namespace' => $namespace, 'workflows.v2.types.workflows' => ['parity.v1.cooperative_cleanup' => \ServerParity\CooperativeCleanupWorkflow::class], 'workflows.v2.types.activities' => ['parity.v1.cooperative_cleanup_activity' => \ServerParity\CooperativeCleanupActivity::class]]); foreach ([WorkflowInstance::class, WorkflowRun::class, WorkflowTask::class] as $model) { diff --git a/scripts/conformance/server-parity/memo-embedded.php b/scripts/conformance/server-parity/memo-embedded.php index ad4931e4d..a22eb092e 100644 --- a/scripts/conformance/server-parity/memo-embedded.php +++ b/scripts/conformance/server-parity/memo-embedded.php @@ -64,6 +64,9 @@ public function handle(mixed $value): mixed } config(['queue.default' => 'database', 'workflows.v2.task_dispatch_mode' => 'queue', + // Core 2.15.12 (Server 2.11.8) resolves a run through its instance's + // namespace, and reserves a caller-named instance without model events. + 'workflows.v2.namespace' => 'default', 'workflows.v2.types.workflows' => [$fixture['workflow_type'] => MemoEmbeddedWorkflow::class], 'workflows.v2.types.activities' => ['parity.v1.memo_echo' => MemoEmbeddedActivity::class]]); foreach ([\Workflow\V2\Models\WorkflowInstance::class, WorkflowRun::class, \Workflow\V2\Models\WorkflowTask::class] as $model) { diff --git a/scripts/conformance/server-parity/namespace-delete-contract.mjs b/scripts/conformance/server-parity/namespace-delete-contract.mjs index b7cb741e1..f6fcdd5f4 100644 --- a/scripts/conformance/server-parity/namespace-delete-contract.mjs +++ b/scripts/conformance/server-parity/namespace-delete-contract.mjs @@ -24,7 +24,9 @@ export function checkNamespaceDelete(fixture, observation, workflowId) { equal(state.deleted.status, 200, 'namespace deleted'); const {deleted, ...response} = state.deleted.response; - equal(response, {name: namespace, status: 'deleted'}, 'delete response'); + // 2.11.8 always reports its post-commit payload deletions (#911). + equal(response, {name: namespace, status: 'deleted', external_payload_cleanup: definition.external_payload_cleanup}, + 'delete response'); const exact = Object.fromEntries(Object.entries(deleted).filter(([key]) => !definition.asynchronous_projections.includes(key))); equal(exact, definition.deleted, 'each owned table deleted with its count'); for (const key of definition.asynchronous_projections) { diff --git a/scripts/conformance/server-parity/namespace-delete-payloads-probe.php b/scripts/conformance/server-parity/namespace-delete-payloads-probe.php index e7202376b..f97ca136d 100644 --- a/scripts/conformance/server-parity/namespace-delete-payloads-probe.php +++ b/scripts/conformance/server-parity/namespace-delete-payloads-probe.php @@ -86,7 +86,9 @@ function finishHttpNamespaceDeletePayloads(Client $admin, array $fixture, string $state['steps'][$step] = $status === 200 ? ['status' => 200, 'name_matches' => ($body['name'] ?? null) === $name, 'outcome' => $body['status'] ?? null, 'payloads' => array_intersect_key($body['deleted'] ?? [], - array_flip(['external_payloads_deleted', 'runtime_external_payload_references']))] + array_flip(['external_payloads_deleted', 'runtime_external_payload_references'])), + // 2.11.8's post-commit provider deletions still pending (#911). + 'cleanup' => $body['external_payload_cleanup'] ?? null] : ['status' => $status, 'body' => $body, ...$named]; }; $described = static function (string $step, string $name) use ($json, &$state): void { diff --git a/scripts/conformance/server-parity/native-image-corpus.sh b/scripts/conformance/server-parity/native-image-corpus.sh index 39b5d061e..d1f4512d0 100755 --- a/scripts/conformance/server-parity/native-image-corpus.sh +++ b/scripts/conformance/server-parity/native-image-corpus.sh @@ -27,7 +27,7 @@ scope="dw-parity-${GITHUB_RUN_ID:-local}-${GITHUB_RUN_ATTEMPT:-1}-${GITHUB_JOB:- export RESOURCE_SCOPE="$scope" export DW_PARITY_RUNNER_REVISION DW_PARITY_RUNNER_REVISION=$(git rev-parse HEAD) -image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json) +image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.8.json) # CI's corpus selection: the reviewed corpus without service-calls, which # records separately below with PHP's maintenance loop stopped (Server #796), diff --git a/scripts/conformance/server-parity/patch-deployment-probe.php b/scripts/conformance/server-parity/patch-deployment-probe.php index e89a0cd8b..7c4dc84a4 100644 --- a/scripts/conformance/server-parity/patch-deployment-probe.php +++ b/scripts/conformance/server-parity/patch-deployment-probe.php @@ -319,6 +319,9 @@ function patchEmbeddedPhase(array $fixture, array $options): array $definition = ($fixture['patch_deployment']['original_patch'] ?? false) ? 'replacement' : $options['patch-phase']; require __DIR__.'/patch-deployment-'.$definition.'.php'; config(['queue.default' => 'database', 'workflows.v2.task_dispatch_mode' => 'queue', + // Core 2.15.12 (Server 2.11.8) resolves a run through its instance's + // namespace, and reserves a caller-named instance without model events. + 'workflows.v2.namespace' => 'default', 'workflows.v2.types.workflows' => [$fixture['workflow_type'] => \ServerParityPatch\DeploymentWorkflow::class], 'workflows.v2.types.activities' => ['parity.v1.echo_activity' => \ServerParity\EchoActivity::class]]); foreach ([WorkflowInstance::class, WorkflowRun::class, WorkflowTask::class] as $model) { diff --git a/scripts/conformance/server-parity/probe.php b/scripts/conformance/server-parity/probe.php index f58c064f1..2d006fdfe 100644 --- a/scripts/conformance/server-parity/probe.php +++ b/scripts/conformance/server-parity/probe.php @@ -667,6 +667,9 @@ function embeddedObservation(array $fixture, string $workflowId, string $namespa config([ 'queue.default' => 'database', 'workflows.v2.task_dispatch_mode' => 'queue', + // Core 2.15.12 (Server 2.11.8) resolves a run through its instance's + // namespace, and reserves a caller-named instance without model events. + 'workflows.v2.namespace' => $namespace, 'workflows.v2.types.workflows' => [ 'parity.v1.echo' => EchoWorkflow::class, 'parity.v1.one_activity' => OneActivityWorkflow::class, diff --git a/scripts/conformance/server-parity/range-pending-probe.php b/scripts/conformance/server-parity/range-pending-probe.php index 35785ff3c..863084028 100644 --- a/scripts/conformance/server-parity/range-pending-probe.php +++ b/scripts/conformance/server-parity/range-pending-probe.php @@ -146,6 +146,9 @@ public function handle(array $value): array $definition = __DIR__.'/range-pending-embedded-'.($phase === 'incompatible' ? 'incompatible' : 'original').'.php'; require $definition; config(['queue.default' => 'database', 'workflows.v2.task_dispatch_mode' => 'queue', + // Core 2.15.12 (Server 2.11.8) resolves a run through its instance's + // namespace, and reserves a caller-named instance without model events. + 'workflows.v2.namespace' => 'default', 'workflows.v2.types.workflows' => [$fixture['workflow_type'] => RangeRepairWorkflow::class], 'workflows.v2.types.activities' => ['parity.v1.echo_activity' => RangeRepairActivity::class]]); foreach ([\Workflow\V2\Models\WorkflowInstance::class, \Workflow\V2\Models\WorkflowRun::class, diff --git a/scripts/conformance/server-parity/range-repair-probe.php b/scripts/conformance/server-parity/range-repair-probe.php index 0c78e45e5..35fb61207 100644 --- a/scripts/conformance/server-parity/range-repair-probe.php +++ b/scripts/conformance/server-parity/range-repair-probe.php @@ -187,6 +187,9 @@ public function handle(array $value): array $definition = __DIR__.'/range-repair-embedded-'.($phase === 'incompatible' ? 'incompatible' : 'original').'.php'; require $definition; config(['queue.default' => 'database', 'workflows.v2.task_dispatch_mode' => 'queue', + // Core 2.15.12 (Server 2.11.8) resolves a run through its instance's + // namespace, and reserves a caller-named instance without model events. + 'workflows.v2.namespace' => 'default', 'workflows.v2.types.workflows' => [$fixture['workflow_type'] => RangeRepairWorkflow::class], 'workflows.v2.types.activities' => ['parity.v1.echo_activity' => RangeRepairActivity::class]]); foreach ([\Workflow\V2\Models\WorkflowInstance::class, \Workflow\V2\Models\WorkflowRun::class, diff --git a/scripts/conformance/server-parity/retention-payloads-probe.php b/scripts/conformance/server-parity/retention-payloads-probe.php index 7dbd8b711..0ffee16d9 100644 --- a/scripts/conformance/server-parity/retention-payloads-probe.php +++ b/scripts/conformance/server-parity/retention-payloads-probe.php @@ -67,7 +67,9 @@ function finishHttpRetentionPayloads(Client $admin, array $fixture, string $work $result = $report['results'][0] ?? []; $state['steps'][$step] = ['status' => $status, 'processed' => $report['processed'] ?? null, 'run' => ($result['run_id'] ?? null) === $runId ? $label : ($result['run_id'] ?? null), - 'result' => array_intersect_key($result, array_flip(['outcome', 'reason', 'external_payloads_deleted']))]; + 'result' => array_intersect_key($result, array_flip(['outcome', 'reason', 'external_payloads_deleted'])), + // 2.11.8's post-commit provider deletions still pending (#911). + 'cleanup' => $report['external_payload_cleanup'] ?? null]; }; $fetch = static function (string $step, string $in, array $reference) use ($http, $headers, &$state): void { $response = $http->get('external-payloads/v1/'.rawurlencode((string) ($reference['reference_id'] ?? '')), [ diff --git a/scripts/conformance/server-parity/role-profile.sh b/scripts/conformance/server-parity/role-profile.sh index 49d645546..0c874b7ac 100644 --- a/scripts/conformance/server-parity/role-profile.sh +++ b/scripts/conformance/server-parity/role-profile.sh @@ -8,7 +8,7 @@ test -n "$RESOURCE_SCOPE" scope_identity="$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT-$GITHUB_JOB" case "$RESOURCE_SCOPE" in *"$scope_identity"*) ;; *) exit 2 ;; esac backend="$1" -image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json) +image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.8.json) rust_image=mirror.gcr.io/library/rust@sha256:ba81bc3eaa4422af576c0262515d96b0111a628a6ccc2c86557cf55c9a4bbee0 fixture=tests/Fixtures/ServerParityProfiles/role-tokens/admission-role-tokens.json credentials=(-e DW_AUTH_TOKEN=parity-role-legacy -e DW_WORKER_TOKEN=parity-role-worker diff --git a/scripts/conformance/server-parity/update-validation-sqlite.sh b/scripts/conformance/server-parity/update-validation-sqlite.sh index 15dd65c22..fbdebc5cf 100644 --- a/scripts/conformance/server-parity/update-validation-sqlite.sh +++ b/scripts/conformance/server-parity/update-validation-sqlite.sh @@ -37,7 +37,7 @@ name="${3:-update-validation}" [[ -n "$DW_PARITY_RUNNER_REVISION" ]] fixture="tests/Fixtures/ServerParity/$name.json" test -f "$fixture" -image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json) +image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.8.json) . scripts/conformance/server-parity/php-maintenance-window.sh prefix=parity-v1 diff --git a/scripts/conformance/server-parity/workflow-import-contract.mjs b/scripts/conformance/server-parity/workflow-import-contract.mjs index 02db4c5ac..12dc3eb47 100644 --- a/scripts/conformance/server-parity/workflow-import-contract.mjs +++ b/scripts/conformance/server-parity/workflow-import-contract.mjs @@ -1,18 +1,18 @@ import assert from 'node:assert/strict'; // Reviewed history import (EmbeddedV2ImportController), recorded against -// published Server 2.11.1. The fixture's `expected` holds the whole +// published Server 2.11.1 and moved to 2.11.8. The fixture's `expected` holds the whole // normalized observation, with the probe's workflow prefix as `{id}`, bundle // identities, checksums and signatures as `{