From 7b5ada60598b2240f78f56ff8f026f5c60edc8a9 Mon Sep 17 00:00:00 2001 From: Richard McDaniel <1130888+rmcdaniel@users.noreply.github.com> Date: Tue, 27 Oct 2026 04:47:37 +0000 Subject: [PATCH 1/7] Follow Core 2.14 on the repair report, service-call wait and idempotency key Published Server 2.11.3 and 2.11.4 (Core 2.14.2) change three answers native gives differently from 2.11.1: - The repair-pass report adds service_call_dispatch_candidates, service_call_dispatches_enqueued and service_call_dispatch_failures for its dispatch outbox. Native runs a call's handler when it accepts the call, so it never has a dispatch to enqueue and reports zeros. - An execute request's wait_timeout_seconds is bounded at 60 (#775's update-wait ceiling, extended to update-bound operations), not 86400. - A dispatched activity or carrier handler's metadata names its service_call_idempotency_key; a call dispatched when it is accepted has none, so it is null. Co-Authored-By: Claude Opus 5.5 --- rust/src/runtime/repair_pass.rs | 5 +++++ rust/src/runtime/service_calls.rs | 10 +++++++--- rust/tests/execution/service_calls.rs | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/rust/src/runtime/repair_pass.rs b/rust/src/runtime/repair_pass.rs index 8c48a90b3..1b7459901 100644 --- a/rust/src/runtime/repair_pass.rs +++ b/rust/src/runtime/repair_pass.rs @@ -102,6 +102,11 @@ fn empty_report(request: &PassRequest, throttled: bool) -> Map { "deadline_expired_failures": [], "activity_timeout_candidates": 0, "activity_timeouts_enforced": 0, "activity_timeout_failures": [], "service_call_timeout_candidates": 0, "service_call_timeouts_enforced": 0, "service_call_timeout_failures": [], + // Published 2.11.3 (Core 2.14) also reports its service-call dispatch + // outbox. Native runs a call's handler when it accepts the call, so it + // never has a dispatch to enqueue. + "service_call_dispatch_candidates": 0, "service_call_dispatches_enqueued": 0, + "service_call_dispatch_failures": [], }); match value { Value::Object(map) => map, diff --git a/rust/src/runtime/service_calls.rs b/rust/src/runtime/service_calls.rs index ebedfab7c..a5978c0e2 100644 --- a/rust/src/runtime/service_calls.rs +++ b/rust/src/runtime/service_calls.rs @@ -64,7 +64,8 @@ const EXECUTE_RULES: &laravel_rules::Rules = &[ ("payload_codec", &[Rule::Nullable, Rule::String, Rule::Max(191)]), ("mode_override", &[Rule::Nullable, Rule::String, Rule::In(MODES)]), ("wait_for", &[Rule::Nullable, Rule::String, Rule::In(&["accepted", "completed"])]), - ("wait_timeout_seconds", &[Rule::Nullable, Rule::Integer, Rule::Min(0), Rule::Max(86400)]), + // Published 2.11.3 bounds an update-bound wait at 60 seconds (#775). + ("wait_timeout_seconds", &[Rule::Nullable, Rule::Integer, Rule::Min(0), Rule::Max(60)]), ("idempotency_key", &[Rule::Nullable, Rule::String, Rule::Max(191)]), ("caller_namespace", &[Rule::Nullable, Rule::String, Rule::Max(255)]), ("caller_workflow_instance_id", &[Rule::Nullable, Rule::String, Rule::Max(191)]), @@ -1732,7 +1733,10 @@ store_methods! { return Self::handler_failure(connection, call, "handler_target_missing", "Activity binding has no activity class or type.", attempt, max).await; }; let id = binding_string(&binding, &["activity_execution_id"]).unwrap_or_else(upper_ulid); - let mut metadata = json!({"activity_execution_id": id, "activity_class": class, "activity_type": kind}); + // Core 2.14 names the dispatch attempt's idempotency key; a call + // dispatched when it is accepted has none. + let mut metadata = json!({"activity_execution_id": id, "activity_class": class, "activity_type": kind, + "service_call_idempotency_key": null}); for (key, value) in [("connection", &execution.connection), ("queue", &execution.queue)] { if let Some(value) = binding_string(&binding, &[key]).or_else(|| value.clone()) { metadata[key] = json!(value); @@ -1755,7 +1759,7 @@ store_methods! { let handler = binding_string(&binding, &["carrier_handler", "handler", "handler_name"]); let carrier = binding_string(&binding, &["carrier", "carrier_name"]); let id = binding_string(&binding, &["carrier_request_id"]).unwrap_or_else(upper_ulid); - let mut metadata = json!({"carrier_request_id": id, "carrier_endpoint": endpoint}); + let mut metadata = json!({"carrier_request_id": id, "carrier_endpoint": endpoint, "service_call_idempotency_key": null}); if let Some(handler) = &handler { metadata["carrier_handler"] = json!(handler); } diff --git a/rust/tests/execution/service_calls.rs b/rust/tests/execution/service_calls.rs index 09785f5b7..eb1da06f8 100644 --- a/rust/tests/execution/service_calls.rs +++ b/rust/tests/execution/service_calls.rs @@ -312,7 +312,7 @@ async fn execute_requests_are_checked_like_the_controller() { (json!({"arguments": {"codec": "avro", "blob": 5}}), "arguments.blob", "The payload envelope blob must be a string."), (json!({"search_attributes": {"1bad": "x"}}), "search_attributes", "Search attribute key [1bad] must start with a letter"), (json!({"mode_override": "fast"}), "mode_override", "The selected mode override is invalid."), - (json!({"wait_timeout_seconds": 86401}), "wait_timeout_seconds", "The wait timeout seconds field must not be greater than 86400."), + (json!({"wait_timeout_seconds": 61}), "wait_timeout_seconds", "The wait timeout seconds field must not be greater than 60."), ] { let (status, refused) = execute(&app, "charge", body.clone()).await; assert_eq!(status, StatusCode::UNPROCESSABLE_ENTITY, "{body}: {refused}"); From a4c1de7af20419a4228b43b730f607f0a3d62917 Mon Sep 17 00:00:00 2001 From: Richard McDaniel <1130888+rmcdaniel@users.noreply.github.com> Date: Tue, 27 Oct 2026 04:47:37 +0000 Subject: [PATCH 2/7] Report 2.11.3's poll_transport in task queue views as unavailable Published 2.11.3 adds poll_transport to every task-queue list and detail entry (WorkerPollDiagnostics::snapshot). Native keeps no record of worker polls that ended early, so it reports the observation store's unavailable state, as PHP does without its cache, and never claims no_recent_observation. Native also returns the ordinary JSON response to every poll, declining the opt-in progress transport, which the contract allows because SDKs must accept both (server#325 comment 6432802069). Co-Authored-By: Claude Opus 5.5 --- rust/src/runtime/task_queues.rs | 12 ++++++++++-- rust/tests/execution/waterline_reads.rs | 5 +++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/rust/src/runtime/task_queues.rs b/rust/src/runtime/task_queues.rs index 68d9129bd..7e8ad23a7 100644 --- a/rust/src/runtime/task_queues.rs +++ b/rust/src/runtime/task_queues.rs @@ -371,6 +371,14 @@ where "current_leases":self.current_leases(queue, at).await?,"repair":self.repair_stats(queue, at).await?})) } + /// `WorkerPollDiagnostics::snapshot` as published 2.11.3 reports it when + /// its observation store is unavailable. Native keeps no record of worker + /// polls that ended early, so it never claims `no_recent_observation`. + /// PHP's retention for an observation is 15 minutes. + fn poll_transport() -> Value { + json!({"status":"unavailable","retention_seconds":900,"last_disconnect":null}) + } + /// One queue's detail with recent flow, activity attempts and admission. async fn queue_view(&self, queue: &str, config: &QueryTaskConfig, detail: bool) -> Result { let at = now(); @@ -395,9 +403,9 @@ where }); Ok(if detail { json!({"name":queue,"pollers":pollers,"stats":stats,"current_leases":base["current_leases"], - "repair":repair,"admission":admission}) + "repair":repair,"admission":admission,"poll_transport":Self::poll_transport()}) } else { - json!({"name":queue,"stats":stats,"repair":repair,"admission":admission}) + json!({"name":queue,"stats":stats,"repair":repair,"admission":admission,"poll_transport":Self::poll_transport()}) }) } diff --git a/rust/tests/execution/waterline_reads.rs b/rust/tests/execution/waterline_reads.rs index be9146b37..9959b4b1f 100644 --- a/rust/tests/execution/waterline_reads.rs +++ b/rust/tests/execution/waterline_reads.rs @@ -24,6 +24,11 @@ async fn task_queue_views_follow_registrations_and_tasks() { assert_eq!(status, StatusCode::OK, "{queue}"); assert_eq!(queue["pollers"][0]["worker_id"], "queue-worker", "{queue}"); assert_eq!(queue["admission"]["query_tasks"]["status"], "accepting", "{queue}"); + // Published 2.11.3's poll diagnostics, reported as unobserved: native keeps + // no record of worker polls that ended early. + let unobserved = json!({"status":"unavailable","retention_seconds":900,"last_disconnect":null}); + assert_eq!(queue["poll_transport"], unobserved, "{queue}"); + assert_eq!(queues[0]["poll_transport"], unobserved, "{list}"); let (status, other) = get(&app, "queues", "/api/task-queues").await; assert_eq!(status, StatusCode::OK, "{other}"); assert_eq!(other["task_queues"], json!([]), "{other}"); From e5d2aeb154f16537d4534ed615e86447513b9549 Mon Sep 17 00:00:00 2001 From: Richard McDaniel <1130888+rmcdaniel@users.noreply.github.com> Date: Tue, 27 Oct 2026 04:47:51 +0000 Subject: [PATCH 3/7] Move the Shared Server fixtures reference to published 2.11.4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The published PHP reference was Server 2.11.1. It is now Server 2.11.4, Core 2.14.2: source ad3ac5fda0a30a58084b2d68569f2d91e23fabf3, images amd64 sha256:bee30cde… and arm64 sha256:cc297aa7…, as General and Security verified them. The SDK, CLI, Waterline and sample-app pins are unchanged. - tests/Fixtures/ServerParityBaselines/php-2.11.4.json is the new baseline. Every reference pin (image pulls, the PHP runner image, the script defaults) names it. - The existing-PHP takeover cohorts now expect the php-2.11.3 layout: 2.11.4 has no schema change. - repair-pass expects the three dispatch-outbox counts, and service-calls the 60-second wait bound and the null service_call_idempotency_key in handler metadata. - service-calls no longer repeats an idempotency key from another caller namespace. Published 2.11.3 and 2.11.4 replay the first call for that caller and rebind its handler identity, the defect Workflow #931 corrects; the correction refuses the foreign caller. The step returns when a release carries the correction. Nothing else in the fixture referred to that call, and its contract says so. 90 requests. Checked: CI's fixture selection recorded against published 2.11.4 on SQLite; before the boundary only service-calls differed, in the replayed-from-another-caller step and three steps that follow from it. After it, repair-pass, service-calls and storage-test pass against both 2.11.4 and native from this branch, and the two records compare equal. The service-calls contract test passes 22/22. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/server-parity.yml | 70 ++++----- scripts/conformance/server-parity.mjs | 4 +- scripts/conformance/server-parity/README.md | 2 +- .../conformance/server-parity/role-profile.sh | 2 +- .../server-parity/update-validation-sqlite.sh | 2 +- tests/Fixtures/ServerParity/repair-pass.json | 15 +- .../Fixtures/ServerParity/service-calls.json | 144 ++++-------------- .../ServerParityBaselines/php-2.11.4.json | 20 +++ .../ServerParityServiceCallsContractTest.mjs | 2 +- 9 files changed, 101 insertions(+), 160 deletions(-) create mode 100644 tests/Fixtures/ServerParityBaselines/php-2.11.4.json diff --git a/.github/workflows/server-parity.yml b/.github/workflows/server-parity.yml index 33ca9f07b..aa362f073 100644 --- a/.github/workflows/server-parity.yml +++ b/.github/workflows/server-parity.yml @@ -144,7 +144,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.1)" + "$(baseline php-2.11.4)" # 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. @@ -415,7 +415,7 @@ jobs: bash scripts/ci/pull-images.sh \ mirror.gcr.io/library/rust@sha256:ba81bc3eaa4422af576c0262515d96b0111a628a6ccc2c86557cf55c9a4bbee0 \ "$MYSQL_IMAGE" \ - "$(baseline php-2.11.1)" \ + "$(baseline php-2.11.4)" \ "$(baseline php-2.5.14)" - name: Fetch this run's published SDK adapters uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 @@ -450,7 +450,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.1.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.4.json) mkdir -p mysql-tls parity-evidence chmod 0777 parity-evidence docker run --rm --user="$(id -u):$(id -g)" --network=none \ @@ -514,7 +514,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.1.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.4.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) @@ -563,7 +563,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.1.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.4.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 @@ -640,7 +640,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.1.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.4.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 @@ -706,7 +706,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.1.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.4.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 @@ -786,7 +786,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.1.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.4.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 @@ -834,7 +834,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.1.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.4.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 \ @@ -886,7 +886,7 @@ jobs: timeout-minutes: 8 env: DW_PARITY_UPGRADE_LABEL: php2111 - DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.1 + DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.3 DW_PARITY_UPGRADE_PHP_IMAGE: ghcr.io/durable-workflow/server@sha256:bf8bed13f5f809ee3425233180a581353eac4683adbee5c4ecbf63bb18bbd102 run: bash scripts/conformance/server-parity/upgrade-source-profile.sh mysql - name: Retire PHP 2.11.1 upgrade source cohort @@ -894,7 +894,7 @@ jobs: timeout-minutes: 3 env: DW_PARITY_UPGRADE_LABEL: php2111 - DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.1 + DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.3 DW_PARITY_UPGRADE_PHP_IMAGE: ghcr.io/durable-workflow/server@sha256:bf8bed13f5f809ee3425233180a581353eac4683adbee5c4ecbf63bb18bbd102 run: bash scripts/conformance/server-parity/upgrade-source-cleanup.sh mysql - name: Back up, take over and resume a published PHP 2.11.3 upgrade source @@ -918,7 +918,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.1.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.4.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 \ @@ -1048,7 +1048,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.1.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.4.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" \ @@ -1100,7 +1100,7 @@ jobs: bash scripts/ci/pull-images.sh \ mirror.gcr.io/library/rust@sha256:ba81bc3eaa4422af576c0262515d96b0111a628a6ccc2c86557cf55c9a4bbee0 \ "$POSTGRES_IMAGE" \ - "$(baseline php-2.11.1)" \ + "$(baseline php-2.11.4)" \ "$(baseline php-2.5.14)" - name: Fetch this run's published SDK adapters uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 @@ -1174,7 +1174,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.1.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.4.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) @@ -1225,7 +1225,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.1.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.4.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 @@ -1303,7 +1303,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.1.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.4.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 @@ -1371,7 +1371,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.1.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.4.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 @@ -1451,7 +1451,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.1.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.4.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 @@ -1498,7 +1498,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.1.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.4.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 \ @@ -1552,7 +1552,7 @@ jobs: timeout-minutes: 8 env: DW_PARITY_UPGRADE_LABEL: php2111 - DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.1 + DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.3 DW_PARITY_UPGRADE_PHP_IMAGE: ghcr.io/durable-workflow/server@sha256:bf8bed13f5f809ee3425233180a581353eac4683adbee5c4ecbf63bb18bbd102 run: bash scripts/conformance/server-parity/upgrade-source-profile.sh pgsql - name: Retire PHP 2.11.1 upgrade source cohort @@ -1560,7 +1560,7 @@ jobs: timeout-minutes: 3 env: DW_PARITY_UPGRADE_LABEL: php2111 - DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.1 + DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.3 DW_PARITY_UPGRADE_PHP_IMAGE: ghcr.io/durable-workflow/server@sha256:bf8bed13f5f809ee3425233180a581353eac4683adbee5c4ecbf63bb18bbd102 run: bash scripts/conformance/server-parity/upgrade-source-cleanup.sh pgsql - name: Back up, take over and resume a published PHP 2.11.3 upgrade source @@ -1584,7 +1584,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.1.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.4.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 \ @@ -1756,7 +1756,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.1.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.4.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" \ @@ -1799,7 +1799,7 @@ jobs: } bash scripts/ci/pull-images.sh \ mirror.gcr.io/library/rust@sha256:ba81bc3eaa4422af576c0262515d96b0111a628a6ccc2c86557cf55c9a4bbee0 \ - "$(baseline php-2.11.1)" \ + "$(baseline php-2.11.4)" \ "$(baseline php-2.5.14)" - name: Fetch this run's published SDK adapters uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 @@ -1870,7 +1870,7 @@ jobs: -v "$PWD/parity-evidence:/evidence" -w /source/rust --entrypoint bash \ mirror.gcr.io/library/rust@sha256:ba81bc3eaa4422af576c0262515d96b0111a628a6ccc2c86557cf55c9a4bbee0 -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.1.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.4.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 \ @@ -1894,7 +1894,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.1.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.4.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 @@ -1965,7 +1965,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.1.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.4.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 @@ -2041,7 +2041,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.1.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.4.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 @@ -2122,7 +2122,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.1.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.4.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 @@ -2166,7 +2166,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.1.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.4.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 @@ -2232,7 +2232,7 @@ jobs: env: RESOURCE_SCOPE: dw-parity-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }} DW_PARITY_UPGRADE_LABEL: php2111 - DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.1 + DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.3 DW_PARITY_UPGRADE_PHP_IMAGE: ghcr.io/durable-workflow/server@sha256:bf8bed13f5f809ee3425233180a581353eac4683adbee5c4ecbf63bb18bbd102 run: bash scripts/conformance/server-parity/upgrade-source-profile.sh sqlite - name: Retire PHP 2.11.1 upgrade source cohort @@ -2241,7 +2241,7 @@ jobs: env: RESOURCE_SCOPE: dw-parity-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }} DW_PARITY_UPGRADE_LABEL: php2111 - DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.1 + DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.3 DW_PARITY_UPGRADE_PHP_IMAGE: ghcr.io/durable-workflow/server@sha256:bf8bed13f5f809ee3425233180a581353eac4683adbee5c4ecbf63bb18bbd102 run: bash scripts/conformance/server-parity/upgrade-source-cleanup.sh sqlite - name: Back up, take over and resume a published PHP 2.11.3 upgrade source @@ -2269,7 +2269,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.1.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.4.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 @@ -2387,7 +2387,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.1.json) + image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.4.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/scripts/conformance/server-parity.mjs b/scripts/conformance/server-parity.mjs index 175a27757..b3ca96a0d 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.1.json. +consumer tuple; the default is tests/Fixtures/ServerParityBaselines/php-2.11.4.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.1.json')), + runner_revision: values['runner-revision'], artifacts: json(values.artifacts ?? resolve(root, 'tests/Fixtures/ServerParityBaselines/php-2.11.4.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 7ca4e4f67..0cf7c2674 100644 --- a/scripts/conformance/server-parity/README.md +++ b/scripts/conformance/server-parity/README.md @@ -282,7 +282,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.1.json`; its SDK version must match +is `tests/Fixtures/ServerParityBaselines/php-2.11.4.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/role-profile.sh b/scripts/conformance/server-parity/role-profile.sh index 0fa79101c..1681daef4 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.1.json) +image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.4.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 e4c4a285b..342ccebda 100644 --- a/scripts/conformance/server-parity/update-validation-sqlite.sh +++ b/scripts/conformance/server-parity/update-validation-sqlite.sh @@ -29,7 +29,7 @@ native_url="$2" [[ -n "$RESOURCE_SCOPE" ]] [[ -n "$DW_PARITY_RUNNER_REVISION" ]] fixture=tests/Fixtures/ServerParity/update-validation.json -image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.1.json) +image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.4.json) . scripts/conformance/server-parity/php-maintenance-window.sh prefix=parity-v1 diff --git a/tests/Fixtures/ServerParity/repair-pass.json b/tests/Fixtures/ServerParity/repair-pass.json index 8bc98874c..d18609afb 100644 --- a/tests/Fixtures/ServerParity/repair-pass.json +++ b/tests/Fixtures/ServerParity/repair-pass.json @@ -233,7 +233,10 @@ "activity_timeout_failures": [], "service_call_timeout_candidates": 0, "service_call_timeouts_enforced": 0, - "service_call_timeout_failures": [] + "service_call_timeout_failures": [], + "service_call_dispatch_candidates": 0, + "service_call_dispatches_enqueued": 0, + "service_call_dispatch_failures": [] } ], "trimmed filters": [ @@ -269,7 +272,10 @@ "activity_timeout_failures": [], "service_call_timeout_candidates": 0, "service_call_timeouts_enforced": 0, - "service_call_timeout_failures": [] + "service_call_timeout_failures": [], + "service_call_dispatch_candidates": 0, + "service_call_dispatches_enqueued": 0, + "service_call_dispatch_failures": [] } ], "empty instance": [ @@ -304,7 +310,10 @@ "activity_timeout_failures": [], "service_call_timeout_candidates": 0, "service_call_timeouts_enforced": 0, - "service_call_timeout_failures": [] + "service_call_timeout_failures": [], + "service_call_dispatch_candidates": 0, + "service_call_dispatches_enqueued": 0, + "service_call_dispatch_failures": [] } ], "run ids not a list": [ diff --git a/tests/Fixtures/ServerParity/service-calls.json b/tests/Fixtures/ServerParity/service-calls.json index 493ee5e9e..9580dc93b 100644 --- a/tests/Fixtures/ServerParity/service-calls.json +++ b/tests/Fixtures/ServerParity/service-calls.json @@ -2,7 +2,7 @@ "schema": "durable-workflow.server-parity-fixture/v1", "id": "service-calls", "review_state": "reviewed", - "contract": "Published Server 2.11.1 Nexus service-call execution (ServiceCatalogController over Workflow 2.10.2's DefaultServiceControlPlane, operator): POST .../operations/{operation}/execute validates like Laravel (mode, wait, ids, labels, memo, search attributes, payload codec and an Avro arguments payload or {codec, blob} envelope; a provider external_storage reference is refused by the runtime transport before routing), then admits at the service boundary and writes an audit row for every decision. Required roles, caller-namespace deny and allow lists answer 403 with the target redacted (show on the audit row is not redacted), an open circuit 503 circuit_open with retry 30, an invalid shared limit 503 service_boundary_admission_unavailable. Shared admission limits count per caller boundary: a per-minute rate refuses 429 rate_limit_exceeded once the window is spent, a concurrency limit 429 concurrency_limit_exceeded while a lease is taken; an async call keeps its lease, a sync call returns it when the request ends. An activity_execution or invocable_http call stays accepted with its handler stamped (an uppercase ULID unless the binding names one); a binding without a target is a handler failure (409), retried in the request up to retry_policy.max_attempts unless its type is non-retryable. A deadline policy sets call_deadline_at; one already past is admitted and timed out (200, accepted). GET .../service-calls/{id} reads a call of the operation by trimmed id; POST .../cancel cancels an open call once (repeat or terminal 409 service_call_terminal, a policy with mode none 409 cancellation_not_allowed), validating the reason after the lookup. A repeated idempotency key replays the first call in the describe shape, re-checking authorization (a refusal writes a new audit row). The catalog reads list the recorded calls by scope, status and outcome filter or bucket, newest accepted first (each list holds only accepted or only rejected calls, since a rejected call's null accepted_at sorts by the database's NULL order), and read one call's detail with its caller link; an unknown call is 404 catalog_object_not_found. Generated ids and times are placeholders; @own is the fixture's namespace. Embedded applications have no HTTP service calls.", + "contract": "Published Server 2.11.1 Nexus service-call execution (ServiceCatalogController over Workflow 2.10.2's DefaultServiceControlPlane, operator): POST .../operations/{operation}/execute validates like Laravel (mode, wait, ids, labels, memo, search attributes, payload codec and an Avro arguments payload or {codec, blob} envelope; a provider external_storage reference is refused by the runtime transport before routing), then admits at the service boundary and writes an audit row for every decision. Required roles, caller-namespace deny and allow lists answer 403 with the target redacted (show on the audit row is not redacted), an open circuit 503 circuit_open with retry 30, an invalid shared limit 503 service_boundary_admission_unavailable. Shared admission limits count per caller boundary: a per-minute rate refuses 429 rate_limit_exceeded once the window is spent, a concurrency limit 429 concurrency_limit_exceeded while a lease is taken; an async call keeps its lease, a sync call returns it when the request ends. An activity_execution or invocable_http call stays accepted with its handler stamped (an uppercase ULID unless the binding names one); a binding without a target is a handler failure (409), retried in the request up to retry_policy.max_attempts unless its type is non-retryable. A deadline policy sets call_deadline_at; one already past is admitted and timed out (200, accepted). GET .../service-calls/{id} reads a call of the operation by trimmed id; POST .../cancel cancels an open call once (repeat or terminal 409 service_call_terminal, a policy with mode none 409 cancellation_not_allowed), validating the reason after the lookup. A repeated idempotency key replays the first call in the describe shape, re-checking authorization (a refusal writes a new audit row). Reusing a key from another caller namespace is outside this fixture until the Workflow #931 correction is published: published 2.11.3 and 2.11.4 replay the first call and rebind its handler identity, and the correction refuses the foreign caller. The catalog reads list the recorded calls by scope, status and outcome filter or bucket, newest accepted first (each list holds only accepted or only rejected calls, since a rejected call's null accepted_at sorts by the database's NULL order), and read one call's detail with its caller link; an unknown call is 404 catalog_object_not_found. Generated ids and times are placeholders; @own is the fixture's namespace. Embedded applications have no HTTP service calls.", "workflow_type": "parity.v1.echo", "input": { "message": "service calls λ", @@ -386,7 +386,7 @@ "path": "service-endpoints/pay/services/billing/operations/charge/execute", "body": { "mode_override": "fast", - "wait_timeout_seconds": 86401 + "wait_timeout_seconds": 61 } }, { @@ -727,15 +727,6 @@ "arguments": "wwHioz3/VYAiNwoIZG9uZQ==" } }, - { - "label": "replay other caller", - "method": "POST", - "path": "service-endpoints/pay/services/billing/operations/replay-target/execute", - "body": { - "idempotency_key": "k1", - "caller_namespace": "other" - } - }, { "label": "cancel replayed", "method": "POST", @@ -1490,7 +1481,7 @@ "The selected mode override is invalid." ], "wait_timeout_seconds": [ - "The wait timeout seconds field must not be greater than 86400." + "The wait timeout seconds field must not be greater than 60." ] }, "validation_errors": { @@ -1498,7 +1489,7 @@ "The selected mode override is invalid." ], "wait_timeout_seconds": [ - "The wait timeout seconds field must not be greater than 86400." + "The wait timeout seconds field must not be greater than 60." ] } } @@ -1674,6 +1665,7 @@ "activity_execution_id": "{ULID:charge}", "activity_class": "charge-activity", "activity_type": "charge-activity", + "service_call_idempotency_key": null, "service_call_attempts": [ { "attempt": 1, @@ -1777,6 +1769,7 @@ "activity_execution_id": "{ULID:charge with options}", "activity_class": "charge-activity", "activity_type": "charge-activity", + "service_call_idempotency_key": null, "connection": "cx", "queue": "qx", "service_call_attempts": [ @@ -1882,6 +1875,7 @@ "activity_execution_id": "{ULID:charge sync}", "activity_class": "charge-activity", "activity_type": "charge-activity", + "service_call_idempotency_key": null, "service_call_attempts": [ { "attempt": 1, @@ -1985,6 +1979,7 @@ "activity_execution_id": "{ULID:charge override}", "activity_class": "charge-activity", "activity_type": "charge-activity", + "service_call_idempotency_key": null, "service_call_attempts": [ { "attempt": 1, @@ -2088,6 +2083,7 @@ "activity_execution_id": "act-fixed-1", "activity_class": "C", "activity_type": "t", + "service_call_idempotency_key": null, "queue": "bq", "service_call_attempts": [ { @@ -2191,6 +2187,7 @@ "boundary_key": "@own|@own|pay|billing|notify", "carrier_request_id": "{ULID:notify}", "carrier_endpoint": "https://carrier.example/notify", + "service_call_idempotency_key": null, "service_call_attempts": [ { "attempt": 1, @@ -2294,6 +2291,7 @@ "boundary_key": "@own|@own|pay|billing|notify-bound", "carrier_request_id": "car-fixed-1", "carrier_endpoint": "https://c.example/x", + "service_call_idempotency_key": null, "carrier_handler": "h", "carrier": "c", "service_call_attempts": [ @@ -2400,6 +2398,7 @@ "activity_execution_id": "{ULID:charge}", "activity_class": "charge-activity", "activity_type": "charge-activity", + "service_call_idempotency_key": null, "service_call_attempts": [ { "attempt": 1, @@ -2494,6 +2493,7 @@ "activity_execution_id": "{ULID:charge}", "activity_class": "charge-activity", "activity_type": "charge-activity", + "service_call_idempotency_key": null, "service_call_attempts": [ { "attempt": 1, @@ -3234,6 +3234,7 @@ "activity_execution_id": "{ULID:timed}", "activity_class": "t", "activity_type": "t", + "service_call_idempotency_key": null, "service_call_attempts": [ { "attempt": 1, @@ -3577,6 +3578,7 @@ "activity_execution_id": "{ULID:charge}", "activity_class": "charge-activity", "activity_type": "charge-activity", + "service_call_idempotency_key": null, "service_call_attempts": [ { "attempt": 1, @@ -3672,6 +3674,7 @@ "activity_execution_id": "{ULID:charge}", "activity_class": "charge-activity", "activity_type": "charge-activity", + "service_call_idempotency_key": null, "service_call_attempts": [ { "attempt": 1, @@ -3798,6 +3801,7 @@ "activity_execution_id": "{ULID:nocancel}", "activity_class": "t", "activity_type": "t", + "service_call_idempotency_key": null, "service_call_attempts": [ { "attempt": 1, @@ -3903,6 +3907,7 @@ "activity_execution_id": "{ULID:nocancel}", "activity_class": "t", "activity_type": "t", + "service_call_idempotency_key": null, "service_call_attempts": [ { "attempt": 1, @@ -4227,6 +4232,7 @@ "activity_execution_id": "{ULID:rated}", "activity_class": "t", "activity_type": "t", + "service_call_idempotency_key": null, "service_call_attempts": [ { "attempt": 1, @@ -4379,6 +4385,7 @@ "activity_execution_id": "{ULID:rated other caller}", "activity_class": "t", "activity_type": "t", + "service_call_idempotency_key": null, "service_call_attempts": [ { "attempt": 1, @@ -4497,6 +4504,7 @@ "activity_execution_id": "{ULID:held}", "activity_class": "t", "activity_type": "t", + "service_call_idempotency_key": null, "service_call_attempts": [ { "attempt": 1, @@ -4648,6 +4656,7 @@ "activity_execution_id": "{ULID:held sync}", "activity_class": "t", "activity_type": "t", + "service_call_idempotency_key": null, "service_call_attempts": [ { "attempt": 1, @@ -4765,6 +4774,7 @@ "activity_execution_id": "{ULID:held sync again}", "activity_class": "t", "activity_type": "t", + "service_call_idempotency_key": null, "service_call_attempts": [ { "attempt": 1, @@ -4951,6 +4961,7 @@ "activity_execution_id": "{ULID:replay first}", "activity_class": "charge-activity", "activity_type": "charge-activity", + "service_call_idempotency_key": null, "service_call_attempts": [ { "attempt": 1, @@ -5043,109 +5054,6 @@ "failure_message": null } ], - "replay other caller": [ - 200, - { - "id": "{id:replay other caller}", - "namespace": "@own", - "endpoint_id": "{id:register endpoint}", - "endpoint_name": "pay", - "service_id": "{id:register service}", - "service_name": "billing", - "operation_id": "{id:register replay-target}", - "operation_name": "replay-target", - "caller_namespace": "other", - "caller_workflow_instance_id": null, - "caller_workflow_run_id": null, - "target_namespace": "@own", - "linked_workflow_instance_id": null, - "linked_workflow_run_id": null, - "linked_workflow_update_id": null, - "status": "accepted", - "outcome": "accepted", - "outcome_category": "accepted", - "outcome_reason": "accepted", - "outcome_message": null, - "outcome_metadata": { - "boundary_key": "other|@own|pay|billing|replay-target" - }, - "service_error_type": null, - "caller_observed_error_type": null, - "typed_error_message": null, - "policy_name": "server.shared-service-boundary", - "retry_after_seconds": null, - "operation_mode": "async", - "resolved_binding_kind": "activity_execution", - "resolved_target_reference": "{ULID:replay other caller}", - "caller_principal_subject": "legacy-token", - "caller_principal_method": "token", - "caller_principal_roles": [ - "admin" - ], - "caller_principal_tenant": null, - "caller_principal_claims": { - "legacy_credential": true - }, - "payload_codec": null, - "input_payload_reference": null, - "output_payload_reference": null, - "failure_payload_reference": null, - "failure_message": null, - "idempotency_key": "k1", - "deadline_policy": null, - "idempotency_policy": null, - "cancellation_policy": null, - "retry_policy": null, - "boundary_policy": null, - "metadata": { - "boundary_key": "other|@own|pay|billing|replay-target", - "activity_execution_id": "{ULID:replay other caller}", - "activity_class": "charge-activity", - "activity_type": "charge-activity", - "service_call_attempts": [ - { - "attempt": 1, - "status": "accepted", - "outcome": "accepted", - "outcome_reason": "accepted", - "started_at": "{µs}", - "retry_scheduled": false - } - ], - "retry_attempt_count": 1 - }, - "service_call_attempts": [ - { - "attempt": 1, - "status": "accepted", - "outcome": "accepted", - "outcome_reason": "accepted", - "started_at": "{µs}", - "retry_scheduled": false - } - ], - "retry_attempt_count": 1, - "accepted_at": "{µs}", - "call_deadline_at": null, - "started_at": null, - "completed_at": null, - "failed_at": null, - "cancelled_at": null, - "created_at": "{time}", - "updated_at": "{time}", - "service_call_id": "{id:replay other caller}", - "accepted": true, - "idempotent_replay": false, - "handler": { - "accepted": true, - "kind": "activity_execution", - "activity_execution_id": "{ULID:replay other caller}", - "activity_class": "charge-activity", - "activity_type": "charge-activity" - }, - "reason": null - } - ], "cancel replayed": [ 200, { @@ -5205,6 +5113,7 @@ "activity_execution_id": "{ULID:replay first}", "activity_class": "charge-activity", "activity_type": "charge-activity", + "service_call_idempotency_key": null, "service_call_attempts": [ { "attempt": 1, @@ -5348,6 +5257,7 @@ "activity_execution_id": "{ULID:replay second key}", "activity_class": "charge-activity", "activity_type": "charge-activity", + "service_call_idempotency_key": null, "service_call_attempts": [ { "attempt": 1, @@ -5504,6 +5414,7 @@ "activity_execution_id": "{ULID:replay second key}", "activity_class": "charge-activity", "activity_type": "charge-activity", + "service_call_idempotency_key": null, "service_call_attempts": [ { "attempt": 1, @@ -6762,6 +6673,7 @@ "activity_execution_id": "{ULID:charge with options}", "activity_class": "charge-activity", "activity_type": "charge-activity", + "service_call_idempotency_key": null, "connection": "cx", "queue": "qx", "service_call_attempts": [ diff --git a/tests/Fixtures/ServerParityBaselines/php-2.11.4.json b/tests/Fixtures/ServerParityBaselines/php-2.11.4.json new file mode 100644 index 000000000..06d4cd6c9 --- /dev/null +++ b/tests/Fixtures/ServerParityBaselines/php-2.11.4.json @@ -0,0 +1,20 @@ +{ + "schema": "durable-workflow.server-port-artifacts/v1", + "server": { + "version": "2.11.4", + "source_commit": "ad3ac5fda0a30a58084b2d68569f2d91e23fabf3", + "images": { + "linux/amd64": "durableworkflow/server@sha256:bee30cde5b70a429561a49f25daf4ef71a1e405d43322f78ff1b8137029c80f3", + "linux/arm64": "durableworkflow/server@sha256:cc297aa7d1f81b8bfa1169de6980dabe58d9776dcf513f379dcba2a51b1f5cc7" + } + }, + "workflow": "2.14.2", + "sdk_php": "2.2.6", + "sdk_python": "2.5.0", + "sdk_rust": "3.4.1", + "cli": "2.2.0", + "waterline": "2.3.1", + "sample_app_commit": "4427a537e092f9d92c96d2a4867b9186e0e75ccd", + "capacity_suite": "1.9.0", + "performance_status": "not_measured" +} diff --git a/tests/Unit/ServerParityServiceCallsContractTest.mjs b/tests/Unit/ServerParityServiceCallsContractTest.mjs index 562f2980f..aea85a9d0 100644 --- a/tests/Unit/ServerParityServiceCallsContractTest.mjs +++ b/tests/Unit/ServerParityServiceCallsContractTest.mjs @@ -50,7 +50,7 @@ const observation = () => { }; test('the service calls accept the published answers', () => { - assert.deepStrictEqual(checkServiceCalls(fixture, observation(), id), {scope: 'service_calls', cases: 91}); + assert.deepStrictEqual(checkServiceCalls(fixture, observation(), id), {scope: 'service_calls', cases: 90}); }); test('the fixture covers execute, show, cancel and every reachable refusal', () => { From 2bf5fdfeeed8d0972ed2224cb459537074c06419 Mon Sep 17 00:00:00 2001 From: Richard McDaniel <1130888+rmcdaniel@users.noreply.github.com> Date: Tue, 27 Oct 2026 04:48:47 +0000 Subject: [PATCH 4/7] Record the move of the parity reference to 2.11.4 The checkpoint names the verified 2.11.4 artifacts, the Core 2.14 expectation changes native now follows, the poll_transport position and the Workflow #931 boundary, with what was checked. The Rust port's current state lists #799 and #801 as merged, #803 and this move as next, and 2.11.4 as the compared reference. Co-Authored-By: Claude Opus 5.5 --- .../2026-10-27-reference-2114.md | 51 +++++++++++++++++++ docs/rust-server-port.md | 30 ++++++----- 2 files changed, 67 insertions(+), 14 deletions(-) create mode 100644 docs/rust-server-port-checkpoints/2026-10-27-reference-2114.md diff --git a/docs/rust-server-port-checkpoints/2026-10-27-reference-2114.md b/docs/rust-server-port-checkpoints/2026-10-27-reference-2114.md new file mode 100644 index 000000000..221b4dd72 --- /dev/null +++ b/docs/rust-server-port-checkpoints/2026-10-27-reference-2114.md @@ -0,0 +1,51 @@ +# October 27, the parity reference moves to published 2.11.4 + +- **Before.** The Shared Server fixtures compared native and embedded + against published Server 2.11.1 (Workflow 2.10.2). +- **Reference.** Published Server 2.11.4, Core 2.14.2, as General and + Security verified it: + - source `ad3ac5fda0a30a58084b2d68569f2d91e23fabf3`; + - linux/amd64 `sha256:bee30cde5b70a429561a49f25daf4ef71a1e405d43322f78ff1b8137029c80f3`; + - linux/arm64 `sha256:cc297aa7d1f81b8bfa1169de6980dabe58d9776dcf513f379dcba2a51b1f5cc7`. + + `tests/Fixtures/ServerParityBaselines/php-2.11.4.json` records it. The SDK, + CLI, Waterline and sample-app pins are unchanged. 2.11.4 has no schema + change from 2.11.3, so the existing-PHP takeover cohorts expect the + `php-2.11.3` layout. Its API route table is the same as 2.11.1's, so the + route inventory still holds. +- **Expectation changes**, all Core 2.14 behavior: + - `repair-pass`: the report adds the dispatch-outbox counts + (`service_call_dispatch_candidates`, `service_call_dispatches_enqueued`, + `service_call_dispatch_failures`). + - `service-calls`: + - an execute request's `wait_timeout_seconds` is bounded at 60; + - handler metadata names `service_call_idempotency_key` (null for a call + dispatched when it is accepted). +- **Native.** + - The repair report adds the three counts as zeros: native runs a call's + handler when it accepts the call, so it has no outbox. + - The execute bound is 60. + - Handler metadata carries the null key. + - Task-queue views report published 2.11.3's `poll_transport` as + `unavailable`, since native records no early poll disconnects. Every poll + returns the ordinary JSON response, declining the opt-in progress + transport; SDKs must accept both. +- **Boundary: Workflow #931.** `service-calls` no longer repeats an + idempotency key from another caller namespace. + - Published 2.11.1 created a separate call for that caller. + - 2.11.3 and 2.11.4 replay the first call and rebind its handler identity. + - The prepared correction refuses the foreign caller. + + Recorded against 2.11.4, that step and the three that follow from it were + the only differences in the whole selection. The step returns, with the + corrected answer for native too, once a release carries the correction. + Until then native keeps 2.11.1's separate call. +- **Checked.** + - CI's fixture selection (the corpus without `update-validation`) recorded + on SQLite against published 2.11.4. Before the boundary, only + `service-calls` differed. + - After it, `repair-pass`, `service-calls` and `storage-test` pass against + both 2.11.4 and native from this branch, and the two records compare + equal. + - The service-calls contract test passes 22/22. CI runs the full selection + on all four databases. diff --git a/docs/rust-server-port.md b/docs/rust-server-port.md index 917de2887..70e7d4008 100644 --- a/docs/rust-server-port.md +++ b/docs/rust-server-port.md @@ -30,32 +30,34 @@ Updated October 27, 2026. maintenance repair loop (opt-in, `DW_RUST_MAINTENANCE_LOOP=1`); takeover keeps runtime credentials and update validation tasks; takeover of published 2.11.3, fresh and upgraded from 2.9.x. -- **In the queue or next, in order.** - 1. [#799](https://github.com/durable-workflow/server/pull/799): the fixture - adapters' Cargo cache, saved only by pushes to main. - 2. Message-stream retention (PHP's `MessageStreamRetentionCleanup`), and - takeover that keeps message and workflow streams. - 3. The native release-candidate image (`Dockerfile.native`), its release - workflow and the takeover runbook's image commands. - 4. The parity reference moves to published 2.11.3. Native already matches it - on 62 of 65 fixtures. The move waits on two published 2.11.3 defects - General owns: the storage diagnostic (#789) and cross-caller idempotent - replay (Workflow #931). + - [#799](https://github.com/durable-workflow/server/pull/799): the fixture + adapters' Cargo cache, saved only by pushes to main. + - [#801](https://github.com/durable-workflow/server/pull/801): + message-stream retention (PHP's `MessageStreamRetentionCleanup`), and + takeover that keeps message and workflow streams. +- **In the queue or next.** + - [#803](https://github.com/durable-workflow/server/pull/803): the native + release-candidate image (`Dockerfile.native`), its release workflow and + the takeover runbook's image commands. + - The parity reference moves to published 2.11.4 + ([checkpoint](rust-server-port-checkpoints/2026-10-27-reference-2114.md)). - **Still refused at takeover:** workflow-to-workflow messages (`workflow_messages`, written only by embedded PHP workflows) and external payload references. PHP keeps references in about 30 table and column locations in their URI form, so taking them over is its own project; the recommendation is that the release candidate keeps refusing them. -- **Shared Server fixtures.** Native, embedded and published PHP 2.11.1 are +- **Shared Server fixtures.** Native, embedded and published PHP 2.11.4 are compared on SQLite, MySQL 8.0, MariaDB 10.11 and PostgreSQL 17. Where the published reference itself misbehaves, an explicit boundary separates it from native, and the PHP owner has an issue for each case. Recent ones: + - Workflow #931, an idempotency key repeated from another caller namespace + (left out of `service-calls` until a release carries the correction); - #796, `service-calls` under full-corpus SQLite load; - #794, workflow-bound Nexus handlers. No expectation is edited to make native pass. - **Parity surface.** Native answers 174 of the 175 API route methods in - published 2.11.1's route table. Eight of those are the prepared + published 2.11.1's route table, which 2.11.4 leaves unchanged. Eight of those are the prepared local-activity routes, which it answers with PHP's refusal for a bridge without the prepared role. Nexus workflow-bound handler kinds answer 501 until #794 settles their contract. An execution test sends each route @@ -63,7 +65,7 @@ Updated October 27, 2026. `501 rust_capability_not_implemented` must match [`unserved-php-routes.v2.11.1.txt`](../rust/tests/fixtures/unserved-php-routes.v2.11.1.txt). - **Remaining for the release candidate:** - - the 2.11.3 reference move; + - landing the 2.11.4 reference move; - the scope question with the operator: prepared local activities, workflow-bound Nexus handlers and external payload takeover; - publishing the native image (General runs the release); From 912f49a6d00266155dcfa8f26d609c697300a769 Mon Sep 17 00:00:00 2001 From: Richard McDaniel <1130888+rmcdaniel@users.noreply.github.com> Date: Tue, 27 Oct 2026 05:10:17 +0000 Subject: [PATCH 5/7] Update two tests the reference move left on 2.11.1 This PR's first run failed in two places: - ServerParityImagePullTest pins the reference pull as a regex (php-2\.11\.1), which the pin move did not match. It now names php-2.11.4. The whole comparator unit batch passes (2417). - The update-wait ceiling execution test sent a 61-second wait to an update-bound operation and expected 501 for the unserved handler kind. With the 60-second bound, that request is now a validation failure before routing, as published 2.11.3 answers; a 60-second wait still reaches the unserved kind. Both are asserted. Co-Authored-By: Claude Opus 5.5 --- rust/tests/execution/command_requests.rs | 14 +++++++++----- tests/Unit/ServerParityImagePullTest.mjs | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/rust/tests/execution/command_requests.rs b/rust/tests/execution/command_requests.rs index e156b55e9..7a31c1856 100644 --- a/rust/tests/execution/command_requests.rs +++ b/rust/tests/execution/command_requests.rs @@ -80,9 +80,10 @@ async fn update_wait_timeout_is_bounded_before_admission() { } // Server #775: a service operation bound to a workflow update hands its wait -// to the update admission. Native dispatches no workflow-bound handler yet, so -// neither the execute route nor the workflow-task command can carry a longer -// wait here. +// to the update admission. Published 2.11.3 bounds an execute request's wait +// at 60 seconds before routing, so a longer one is a validation failure. +// Native dispatches no workflow-bound handler yet, so a wait within the bound +// reaches the unserved handler kind, and the workflow-task command is refused. #[tokio::test] async fn service_operations_cannot_carry_an_update_wait_past_the_ceiling() { let database = TestDatabase::new().await; @@ -103,7 +104,10 @@ async fn service_operations_cannot_carry_an_update_wait_past_the_ceiling() { let (status, created) = request(&app, "POST", path, body).await; assert_eq!(status, StatusCode::CREATED, "{created}"); } - let (status, unserved) = request(&app, "POST", "/api/service-endpoints/billing/services/approvals/operations/approve/execute", - json!({"wait_timeout_seconds":61})).await; + let execute = "/api/service-endpoints/billing/services/approvals/operations/approve/execute"; + let (status, refused) = request(&app, "POST", execute, json!({"wait_timeout_seconds":61})).await; + assert_eq!((status, refused["reason"].clone()), (StatusCode::UNPROCESSABLE_ENTITY, json!("validation_failed")), "{refused}"); + assert_eq!(refused["errors"]["wait_timeout_seconds"][0], "The wait timeout seconds field must not be greater than 60.", "{refused}"); + let (status, unserved) = request(&app, "POST", execute, json!({"wait_timeout_seconds":60})).await; assert_eq!((status, unserved["reason"].clone()), (StatusCode::NOT_IMPLEMENTED, json!("rust_capability_not_implemented")), "{unserved}"); } diff --git a/tests/Unit/ServerParityImagePullTest.mjs b/tests/Unit/ServerParityImagePullTest.mjs index 441dc79e1..aa5f7e552 100644 --- a/tests/Unit/ServerParityImagePullTest.mjs +++ b/tests/Unit/ServerParityImagePullTest.mjs @@ -32,7 +32,7 @@ test('each container-heavy fixture job pulls its pinned images first, with retri const pull = steps[1]; assert.match(pull, /- name: Pull the pinned images, retrying registry errors\n/, name); assert.match(pull, /bash scripts\/ci\/pull-images\.sh \\\n/, name); - assert.match(pull, /"\$\(baseline php-2\.11\.1\)"/, name); + assert.match(pull, /"\$\(baseline php-2\.11\.4\)"/, name); for (const image of extra) assert.ok(pull.includes(image), `${name} pulls ${image}`); // Every Rust image the job runs is in its pull list, so a digest bump // cannot leave the pull behind. From 0a64b6edeee52ab4ec2decc4c43ab0e3dcb7121a Mon Sep 17 00:00:00 2001 From: Richard McDaniel <1130888+rmcdaniel@users.noreply.github.com> Date: Tue, 27 Oct 2026 05:36:50 +0000 Subject: [PATCH 6/7] Keep the 2.11.1 takeover cohort on its own layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR's second run failed every cohorts part: the php2111 takeover cohort, which takes over a database published 2.11.1 creates (image bf8bed13…), was told to expect the php-2.11.3 layout and its 51 tables. The move treated its six DW_PARITY_UPGRADE_EXPECTED_LAYOUT lines as reference pins; they are not. Each takeover cohort names its own source release and layout and never uses the parity reference, so the six lines are php-2.11.1 again and the workflow no longer changes any expected layout. The checkpoint said otherwise and is corrected. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/server-parity.yml | 12 ++++++------ .../2026-10-27-reference-2114.md | 7 ++++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/server-parity.yml b/.github/workflows/server-parity.yml index aa362f073..bb43aa53a 100644 --- a/.github/workflows/server-parity.yml +++ b/.github/workflows/server-parity.yml @@ -886,7 +886,7 @@ jobs: timeout-minutes: 8 env: DW_PARITY_UPGRADE_LABEL: php2111 - DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.3 + DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.1 DW_PARITY_UPGRADE_PHP_IMAGE: ghcr.io/durable-workflow/server@sha256:bf8bed13f5f809ee3425233180a581353eac4683adbee5c4ecbf63bb18bbd102 run: bash scripts/conformance/server-parity/upgrade-source-profile.sh mysql - name: Retire PHP 2.11.1 upgrade source cohort @@ -894,7 +894,7 @@ jobs: timeout-minutes: 3 env: DW_PARITY_UPGRADE_LABEL: php2111 - DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.3 + DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.1 DW_PARITY_UPGRADE_PHP_IMAGE: ghcr.io/durable-workflow/server@sha256:bf8bed13f5f809ee3425233180a581353eac4683adbee5c4ecbf63bb18bbd102 run: bash scripts/conformance/server-parity/upgrade-source-cleanup.sh mysql - name: Back up, take over and resume a published PHP 2.11.3 upgrade source @@ -1552,7 +1552,7 @@ jobs: timeout-minutes: 8 env: DW_PARITY_UPGRADE_LABEL: php2111 - DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.3 + DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.1 DW_PARITY_UPGRADE_PHP_IMAGE: ghcr.io/durable-workflow/server@sha256:bf8bed13f5f809ee3425233180a581353eac4683adbee5c4ecbf63bb18bbd102 run: bash scripts/conformance/server-parity/upgrade-source-profile.sh pgsql - name: Retire PHP 2.11.1 upgrade source cohort @@ -1560,7 +1560,7 @@ jobs: timeout-minutes: 3 env: DW_PARITY_UPGRADE_LABEL: php2111 - DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.3 + DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.1 DW_PARITY_UPGRADE_PHP_IMAGE: ghcr.io/durable-workflow/server@sha256:bf8bed13f5f809ee3425233180a581353eac4683adbee5c4ecbf63bb18bbd102 run: bash scripts/conformance/server-parity/upgrade-source-cleanup.sh pgsql - name: Back up, take over and resume a published PHP 2.11.3 upgrade source @@ -2232,7 +2232,7 @@ jobs: env: RESOURCE_SCOPE: dw-parity-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }} DW_PARITY_UPGRADE_LABEL: php2111 - DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.3 + DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.1 DW_PARITY_UPGRADE_PHP_IMAGE: ghcr.io/durable-workflow/server@sha256:bf8bed13f5f809ee3425233180a581353eac4683adbee5c4ecbf63bb18bbd102 run: bash scripts/conformance/server-parity/upgrade-source-profile.sh sqlite - name: Retire PHP 2.11.1 upgrade source cohort @@ -2241,7 +2241,7 @@ jobs: env: RESOURCE_SCOPE: dw-parity-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }} DW_PARITY_UPGRADE_LABEL: php2111 - DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.3 + DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.1 DW_PARITY_UPGRADE_PHP_IMAGE: ghcr.io/durable-workflow/server@sha256:bf8bed13f5f809ee3425233180a581353eac4683adbee5c4ecbf63bb18bbd102 run: bash scripts/conformance/server-parity/upgrade-source-cleanup.sh sqlite - name: Back up, take over and resume a published PHP 2.11.3 upgrade source diff --git a/docs/rust-server-port-checkpoints/2026-10-27-reference-2114.md b/docs/rust-server-port-checkpoints/2026-10-27-reference-2114.md index 221b4dd72..1dc74adf2 100644 --- a/docs/rust-server-port-checkpoints/2026-10-27-reference-2114.md +++ b/docs/rust-server-port-checkpoints/2026-10-27-reference-2114.md @@ -9,9 +9,10 @@ - linux/arm64 `sha256:cc297aa7d1f81b8bfa1169de6980dabe58d9776dcf513f379dcba2a51b1f5cc7`. `tests/Fixtures/ServerParityBaselines/php-2.11.4.json` records it. The SDK, - CLI, Waterline and sample-app pins are unchanged. 2.11.4 has no schema - change from 2.11.3, so the existing-PHP takeover cohorts expect the - `php-2.11.3` layout. Its API route table is the same as 2.11.1's, so the + CLI, Waterline and sample-app pins are unchanged. The takeover cohorts are + not affected: each names its own source release and layout (`php2111` + still takes over published 2.11.1 as `php-2.11.1`). 2.11.4 has no schema + change from 2.11.3. Its API route table is the same as 2.11.1's, so the route inventory still holds. - **Expectation changes**, all Core 2.14 behavior: - `repair-pass`: the report adds the dispatch-outbox counts From 7f6564f53724a655543bab9f9fa8a1f97b5dffd0 Mon Sep 17 00:00:00 2001 From: Richard McDaniel <1130888+rmcdaniel@users.noreply.github.com> Date: Tue, 27 Oct 2026 07:27:07 +0000 Subject: [PATCH 7/7] Record service-calls apart from the SQLite corpus (Server #796) On SQLite the published PHP reference shares one database file with its one-second maintenance loop. Under that writer contention it has answered the first call to a limited service operation with 429 twice: rated[0], one call per minute, on 2.11.1 (merge queue run 40418397301), and held[0], one call in flight, on 2.11.4 (run 40463072450). Each dropped an otherwise-green merge group. The SQLite job already records update-validation apart from its main corpus with PHP's maintenance loop stopped (Server #723), and retries the PHP record once under a fresh prefix when PHP logged "database is locked" (#754). The helper now takes an optional fixture name and records service-calls the same way, under service-calls-* evidence names. Its fresh-prefix retry also gets a fresh namespace and admission counters, since the fixture's namespace derives from the workflow id. The fixture's assertions are unchanged, and the MySQL and PostgreSQL jobs keep service-calls in their main corpus with their loops running. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/server-parity.yml | 10 ++++++ .../server-parity/update-validation-sqlite.sh | 36 +++++++++++-------- .../Unit/ServerParityFixtureSelectionTest.mjs | 28 ++++++++++----- .../ServerParityUpdateValidationRetryTest.mjs | 28 +++++++++++++-- 4 files changed, 76 insertions(+), 26 deletions(-) diff --git a/.github/workflows/server-parity.yml b/.github/workflows/server-parity.yml index bb43aa53a..31dac1aa8 100644 --- a/.github/workflows/server-parity.yml +++ b/.github/workflows/server-parity.yml @@ -1976,6 +1976,7 @@ jobs: -e DW_PARITY_TOKEN=parity-test-token -e DW_PARITY_RUNNER_REVISION -e DW_PARITY_CLI_PHAR "$image" scripts/conformance/server-parity.mjs record \ --mode http --url http://parity-server:8080 --target php --output /evidence/php.json \ --exclude-fixture tests/Fixtures/ServerParity/update-validation.json \ + --exclude-fixture tests/Fixtures/ServerParity/service-calls.json \ --include-fixture tests/Fixtures/ServerParityPending/patch-insertion-activity-pending.json \ --include-fixture tests/Fixtures/ServerParityPending/patch-insertion-activity-completed.json \ --include-fixture tests/Fixtures/ServerParityPending/patch-created-activity-pending.json \ @@ -1989,6 +1990,7 @@ jobs: -e CACHE_STORE=file -e QUEUE_CONNECTION=database -e DW_PARITY_RUNNER_REVISION "$image" scripts/conformance/server-parity.mjs record \ --mode embedded --application-root /app --target embedded --output /evidence/embedded.json \ --exclude-fixture tests/Fixtures/ServerParity/update-validation.json \ + --exclude-fixture tests/Fixtures/ServerParity/service-calls.json \ --include-fixture tests/Fixtures/ServerParityPending/patch-insertion-activity-pending.json \ --include-fixture tests/Fixtures/ServerParityPending/patch-insertion-activity-completed.json \ --include-fixture tests/Fixtures/ServerParityPending/patch-created-activity-pending.json \ @@ -2002,6 +2004,7 @@ jobs: -e DW_PARITY_TOKEN=parity-test-token -e DW_PARITY_RUNNER_REVISION -e DW_PARITY_CLI_PHAR "$image" scripts/conformance/server-parity.mjs record \ --mode http --url http://parity-rust:8080 --target rust --output /evidence/rust.json \ --exclude-fixture tests/Fixtures/ServerParity/update-validation.json \ + --exclude-fixture tests/Fixtures/ServerParity/service-calls.json \ --include-fixture tests/Fixtures/ServerParityPending/patch-insertion-activity-pending.json \ --include-fixture tests/Fixtures/ServerParityPending/patch-insertion-activity-completed.json \ --include-fixture tests/Fixtures/ServerParityPending/patch-created-activity-pending.json \ @@ -2014,6 +2017,7 @@ jobs: -v "$PWD:/source:ro" -v "$PWD/parity-evidence:/evidence" -w /source "$image" \ scripts/conformance/server-parity.mjs compare /evidence/php.json /evidence/rust.json /evidence/embedded.json \ --exclude-fixture tests/Fixtures/ServerParity/update-validation.json \ + --exclude-fixture tests/Fixtures/ServerParity/service-calls.json \ --include-fixture tests/Fixtures/ServerParityPending/patch-insertion-activity-pending.json \ --include-fixture tests/Fixtures/ServerParityPending/patch-insertion-activity-completed.json \ --include-fixture tests/Fixtures/ServerParityPending/patch-created-activity-pending.json \ @@ -2023,13 +2027,19 @@ jobs: --include-fixture tests/Fixtures/ServerParityPending/workflow-task-transient-retry.json > parity-evidence/comparison.json || compare_status=$? # Server #723: update-validation records apart from the main corpus, and # its published PHP record runs with PHP's maintenance loop stopped. + # Server #796: so does service-calls until the reference carries that + # fix. Under the loop's writer contention published PHP has refused a + # limited operation's first call. update_validation_status=0 bash scripts/conformance/server-parity/update-validation-sqlite.sh http://parity-server:8080 http://parity-rust:8080 || update_validation_status=$? + service_calls_status=0 + bash scripts/conformance/server-parity/update-validation-sqlite.sh http://parity-server:8080 http://parity-rust:8080 service-calls || service_calls_status=$? # A native fixture difference fails the step only after the # comparison has run, so one difference cannot hide the rest. test "$native_record_status" -eq 0 test "$compare_status" -eq 0 test "$update_validation_status" -eq 0 + test "$service_calls_status" -eq 0 - name: Compare published patch, legacy-marker and range-repair profiles if: ${{ matrix.part == 'restarts' }} env: diff --git a/scripts/conformance/server-parity/update-validation-sqlite.sh b/scripts/conformance/server-parity/update-validation-sqlite.sh index 342ccebda..c861f1bf5 100644 --- a/scripts/conformance/server-parity/update-validation-sqlite.sh +++ b/scripts/conformance/server-parity/update-validation-sqlite.sh @@ -1,6 +1,11 @@ #!/usr/bin/env bash +# update-validation-sqlite.sh [fixture name] +# # Server #723. The SQLite job records the update-validation fixture apart from -# its main corpus, for all three targets, and compares the three records. +# its main corpus, for all three targets, and compares the three records. The +# optional third argument names another fixture recorded the same way: +# service-calls, whose published PHP record under SQLite writer contention has +# refused the first call to a limited operation with 429 (Server #796). # # The published PHP record runs with PHP's maintenance loop stopped. On SQLite # the API server and that loop share one database file. Under the loop's writer @@ -26,9 +31,12 @@ set -euo pipefail php_url="$1" native_url="$2" +name="${3:-update-validation}" +[[ "$name" =~ ^[a-z0-9-]+$ ]] [[ -n "$RESOURCE_SCOPE" ]] [[ -n "$DW_PARITY_RUNNER_REVISION" ]] -fixture=tests/Fixtures/ServerParity/update-validation.json +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.4.json) . scripts/conformance/server-parity/php-maintenance-window.sh @@ -38,7 +46,7 @@ http_record() { docker run --rm --user=1000:1000 --entrypoint node --network "$RESOURCE_SCOPE" \ -v "$PWD:/source:ro" -v "$PWD/parity-evidence:/evidence" -w /source \ -e DW_PARITY_TOKEN=parity-test-token -e DW_PARITY_RUNNER_REVISION "$image" scripts/conformance/server-parity.mjs record \ - --mode http --url "$url" --target "$target" --output "/evidence/update-validation-$target.json" --fixture "$fixture" \ + --mode http --url "$url" --target "$target" --output "/evidence/$name-$target.json" --fixture "$fixture" \ --prefix "$prefix" } @@ -53,35 +61,35 @@ php_record() { record_finished=$(date -u +%Y-%m-%dT%H:%M:%S.%NZ) } -run_without_maintenance parity-evidence/update-validation-maintenance-schedule.txt -- php_record +run_without_maintenance "parity-evidence/$name-maintenance-schedule.txt" -- php_record if ((php_status != 0)); then - locked=parity-evidence/update-validation-php.attempt-1.locked.txt + locked="parity-evidence/$name-php.attempt-1.locked.txt" { docker logs --since "$record_started" --until "$record_finished" "$RESOURCE_SCOPE-server" 2>&1 || true; } \ | grep -m 5 'database is locked' | cut -c 1-400 > "$locked" || true if [[ ! -s "$locked" ]]; then rm -f "$locked" exit "$php_status" fi - for name in update-validation-php.json update-validation-php.failure-excerpt.txt; do - if [[ -e "parity-evidence/$name" ]]; then - mv "parity-evidence/$name" "parity-evidence/${name/update-validation-php./update-validation-php.attempt-1.}" + for file in "$name-php.json" "$name-php.failure-excerpt.txt"; do + if [[ -e "parity-evidence/$file" ]]; then + mv "parity-evidence/$file" "parity-evidence/${file/$name-php./$name-php.attempt-1.}" fi done - excerpt=$(tr '\n' ' ' < parity-evidence/update-validation-php.attempt-1.failure-excerpt.txt 2>/dev/null || true) - echo "::warning title=Published PHP update-validation record retried::The PHP record failed (${excerpt:-no excerpt}) after published PHP logged 'database is locked'; recording all three targets under a fresh prefix (Server #754)." + excerpt=$(tr '\n' ' ' < "parity-evidence/$name-php.attempt-1.failure-excerpt.txt" 2>/dev/null || true) + echo "::warning title=Published PHP $name record retried::The PHP record failed (${excerpt:-no excerpt}) after published PHP logged 'database is locked'; recording all three targets under a fresh prefix (Server #754)." prefix=parity-v1-retry - run_without_maintenance parity-evidence/update-validation-maintenance-schedule-retry.txt -- php_record + run_without_maintenance "parity-evidence/$name-maintenance-schedule-retry.txt" -- php_record ((php_status == 0)) || exit "$php_status" fi docker run --rm --user=1000:1000 --entrypoint node \ -v "$PWD:/source:ro" -v "$PWD/parity-state:/state" -v "$PWD/parity-evidence:/evidence" -w /source \ -e DB_CONNECTION=sqlite -e DB_DATABASE=/state/embedded.sqlite -e DW_MODE=embedded \ -e CACHE_STORE=file -e QUEUE_CONNECTION=database -e DW_PARITY_RUNNER_REVISION "$image" scripts/conformance/server-parity.mjs record \ - --mode embedded --application-root /app --target embedded --output /evidence/update-validation-embedded.json --fixture "$fixture" \ + --mode embedded --application-root /app --target embedded --output "/evidence/$name-embedded.json" --fixture "$fixture" \ --prefix "$prefix" http_record "$native_url" rust docker run --rm --user=1000:1000 --entrypoint node \ -v "$PWD:/source:ro" -v "$PWD/parity-evidence:/evidence" -w /source "$image" \ scripts/conformance/server-parity.mjs compare \ - /evidence/update-validation-php.json /evidence/update-validation-rust.json /evidence/update-validation-embedded.json \ - --fixture "$fixture" > parity-evidence/update-validation-comparison.json + "/evidence/$name-php.json" "/evidence/$name-rust.json" "/evidence/$name-embedded.json" \ + --fixture "$fixture" > "parity-evidence/$name-comparison.json" diff --git a/tests/Unit/ServerParityFixtureSelectionTest.mjs b/tests/Unit/ServerParityFixtureSelectionTest.mjs index 3c5ca16b1..cccbfb1b8 100644 --- a/tests/Unit/ServerParityFixtureSelectionTest.mjs +++ b/tests/Unit/ServerParityFixtureSelectionTest.mjs @@ -1,5 +1,5 @@ // Fixture selection for parity records and comparisons, and the SQLite job's -// separate update-validation record (Server #723). +// separate update-validation and service-calls records (Server #723, #796). import test from 'node:test'; import assert from 'node:assert/strict'; import {readFileSync, readdirSync} from 'node:fs'; @@ -56,16 +56,24 @@ function mainCorpusCommands(job) { return job.split(/(?=docker run )/).filter(command => /server-parity\.mjs (record|compare)/.test(command)); } -test('the SQLite job covers update-validation only through its separate record', () => { +test('the SQLite job covers update-validation and service-calls only through their separate records', () => { const sqlite = jobs()['execution-and-codec']; const commands = mainCorpusCommands(sqlite); assert.equal(commands.length, 4, 'php, embedded and rust records plus their comparison'); - for (const command of commands) assert.match(command, /--exclude-fixture tests\/Fixtures\/ServerParity\/update-validation\.json/); + for (const command of commands) { + assert.deepEqual(command.match(/--exclude-fixture \S+/g), [ + '--exclude-fixture tests/Fixtures/ServerParity/update-validation.json', + '--exclude-fixture tests/Fixtures/ServerParity/service-calls.json', + ]); + } + assert.ok(corpus.includes('service-calls.json')); assert.match(sqlite, /update_validation_status=0\n\s+bash scripts\/conformance\/server-parity\/update-validation-sqlite\.sh http:\/\/parity-server:8080 http:\/\/parity-rust:8080 \|\| update_validation_status=\$\?/); + assert.match(sqlite, /service_calls_status=0\n\s+bash scripts\/conformance\/server-parity\/update-validation-sqlite\.sh http:\/\/parity-server:8080 http:\/\/parity-rust:8080 service-calls \|\| service_calls_status=\$\?/); assert.match(sqlite, /test "\$update_validation_status" -eq 0/); + assert.match(sqlite, /test "\$service_calls_status" -eq 0/); }); -test('the MySQL and PostgreSQL jobs keep update-validation in their main corpus', () => { +test('the MySQL and PostgreSQL jobs keep update-validation and service-calls in their main corpus', () => { const all = jobs(); for (const name of ['mysql-execution', 'postgres-storage']) { assert.ok(mainCorpusCommands(all[name]).length > 0, name); @@ -75,18 +83,20 @@ test('the MySQL and PostgreSQL jobs keep update-validation in their main corpus' test('the separate record stops maintenance for the PHP record only and compares all three targets', () => { const script = readFileSync(resolve(root, 'scripts/conformance/server-parity/update-validation-sqlite.sh'), 'utf8'); - assert.match(script, /^fixture=tests\/Fixtures\/ServerParity\/update-validation\.json$/m); + // update-validation by default, or the named reviewed fixture. + assert.match(script, /^name="\$\{3:-update-validation\}"\n\[\[ "\$name" =~ \^\[a-z0-9-\]\+\$ \]\]$/m); + assert.match(script, /^fixture="tests\/Fixtures\/ServerParity\/\$name\.json"\ntest -f "\$fixture"$/m); assert.match(script, /^\. scripts\/conformance\/server-parity\/php-maintenance-window\.sh$/m); // The PHP record, and its one retry after a lost SQLite write (#754). const windows = script.match(/^ *run_without_maintenance .*$/gm).map(line => line.trim()); assert.deepEqual(windows, [ - 'run_without_maintenance parity-evidence/update-validation-maintenance-schedule.txt -- php_record', - 'run_without_maintenance parity-evidence/update-validation-maintenance-schedule-retry.txt -- php_record', + 'run_without_maintenance "parity-evidence/$name-maintenance-schedule.txt" -- php_record', + 'run_without_maintenance "parity-evidence/$name-maintenance-schedule-retry.txt" -- php_record', ]); assert.match(script, /^php_record\(\) \{\n php_status=0\n record_started=\$\(date -u \+%Y-%m-%dT%H:%M:%S\.%NZ\)\n http_record "\$php_url" php \|\| php_status=\$\?\n record_finished=\$\(date -u \+%Y-%m-%dT%H:%M:%S\.%NZ\)\n\}$/m); assert.match(script, /docker logs --since "\$record_started" --until "\$record_finished" "\$RESOURCE_SCOPE-server"/); assert.match(script, /^http_record "\$native_url" rust$/m); - assert.match(script, /--mode embedded --application-root \/app --target embedded --output \/evidence\/update-validation-embedded\.json --fixture "\$fixture"/); - assert.match(script, /update-validation-php\.json \/evidence\/update-validation-rust\.json \/evidence\/update-validation-embedded\.json \\\n\s+--fixture "\$fixture"/); + assert.match(script, /--mode embedded --application-root \/app --target embedded --output "\/evidence\/\$name-embedded\.json" --fixture "\$fixture"/); + assert.match(script, /"\/evidence\/\$name-php\.json" "\/evidence\/\$name-rust\.json" "\/evidence\/\$name-embedded\.json" \\\n\s+--fixture "\$fixture" > "parity-evidence\/\$name-comparison\.json"/); assert.match(script, /^set -euo pipefail$/m); }); diff --git a/tests/Unit/ServerParityUpdateValidationRetryTest.mjs b/tests/Unit/ServerParityUpdateValidationRetryTest.mjs index 2ff2d0a0d..d000b4792 100644 --- a/tests/Unit/ServerParityUpdateValidationRetryTest.mjs +++ b/tests/Unit/ServerParityUpdateValidationRetryTest.mjs @@ -1,6 +1,7 @@ // Server #754: the SQLite job's separate update-validation record retries the // published PHP record once, and only when that record failed after PHP -// logged "database is locked". Native and embedded records never retry. +// logged "database is locked". Native and embedded records never retry. The +// separate service-calls record (Server #796) runs the same script by name. import test from 'node:test'; import assert from 'node:assert/strict'; import {chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, symlinkSync, writeFileSync} from 'node:fs'; @@ -71,7 +72,7 @@ case "$1" in esac `; -function run(outcomes) { +function run(outcomes, ...name) { const dir = mkdtempSync(join(tmpdir(), 'update-validation-retry-')); for (const name of ['scripts', 'tests']) symlinkSync(join(root, name), join(dir, name)); for (const name of ['bin', 'parity-evidence', 'parity-state']) mkdirSync(join(dir, name)); @@ -80,7 +81,7 @@ function run(outcomes) { for (const tool of ['docker', 'jq']) chmodSync(join(dir, 'bin', tool), 0o755); writeFileSync(join(dir, 'state'), 'running\n'); writeFileSync(join(dir, 'records'), ''); - const result = spawnSync('bash', ['scripts/conformance/server-parity/update-validation-sqlite.sh', 'http://parity-server:8080', 'http://parity-rust:8080'], { + const result = spawnSync('bash', ['scripts/conformance/server-parity/update-validation-sqlite.sh', 'http://parity-server:8080', 'http://parity-rust:8080', ...name], { cwd: dir, encoding: 'utf8', env: {...process.env, PATH: `${join(dir, 'bin')}:${process.env.PATH}`, OUTCOMES: outcomes, RESOURCE_SCOPE: 'dw-parity-7-2-sqlite', GITHUB_RUN_ID: '7', GITHUB_RUN_ATTEMPT: '2', GITHUB_JOB: 'sqlite', @@ -156,3 +157,24 @@ test('a native or embedded failure is never retried, even after a PHP retry', () assert.deepEqual(result.records, records, outcomes); } }); + +test('a named fixture records and retries under its own evidence names', () => { + const result = run('php1=fail-locked', 'service-calls'); + assert.equal(result.status, 0, result.stderr); + assert.deepEqual(result.records, ['php parity-v1', 'php parity-v1-retry', 'embedded parity-v1-retry', 'rust parity-v1-retry']); + assert.match(result.stdout, /^::warning title=Published PHP service-calls record retried::/m); + for (const name of ['service-calls-php.json', 'service-calls-embedded.json', 'service-calls-rust.json', 'service-calls-comparison.json', + 'service-calls-php.attempt-1.json', 'service-calls-php.attempt-1.locked.txt', 'service-calls-maintenance-schedule-retry.txt']) { + assert.equal(result.evidence(name), true, name); + } + assert.equal(result.evidence('update-validation-php.json'), false); + assert.equal(result.state, 'running'); +}); + +test('a fixture name outside the reviewed corpus records nothing', () => { + for (const name of ['no-such-fixture', '../ServerParityPending/workflow-task-transient-retry', 'Service-Calls']) { + const result = run('', name); + assert.notEqual(result.status, 0, name); + assert.deepEqual(result.records, [], name); + } +});