Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
8b0a879
Serve synchronous update validation like Server 2.11.1
rmcdaniel Oct 23, 2026
3961dc2
Merge remote-tracking branch 'origin/main' into rust-325-update-valid…
rmcdaniel Oct 24, 2026
12aa2ce
Server parity: workflow poll request replay fixture, recorded from pu…
rmcdaniel Oct 24, 2026
72c6c09
Rust #325: workflow poll request IDs replay like published 2.11.1
rmcdaniel Oct 24, 2026
25097cb
Merge remote-tracking branch 'origin/rust-325-update-validation' into…
rmcdaniel Oct 24, 2026
02146cf
Server parity: give the update-validation probe a 90-second limit
rmcdaniel Oct 24, 2026
a99fc44
Retry typed lock-pressure refusals in waiting update requests; let su…
rmcdaniel Oct 24, 2026
44cb5f1
Record SQLite update validation apart, with PHP maintenance stopped f…
rmcdaniel Oct 24, 2026
a22abbf
Address Sonar's shell findings in the SQLite update-validation scripts
rmcdaniel Oct 24, 2026
8048008
Server parity: update validation records multiplexed poll request IDs
rmcdaniel Oct 24, 2026
7572684
Rust #325: poll request IDs bind to their task-kind set and replay va…
rmcdaniel Oct 24, 2026
54fbc62
Server parity: worker poll fence fixture, recorded from published 2.11.1
rmcdaniel Oct 24, 2026
31dc2a2
Rust #325: fence held workflow and activity polls against replaced re…
rmcdaniel Oct 24, 2026
1be4ac7
Rust #325: fence held query polls too, and record removal and query f…
rmcdaniel Oct 24, 2026
44b122a
Merge remote-tracking branch 'origin/main' into rust-325-update-valid…
rmcdaniel Oct 24, 2026
e8ce43a
Merge branch 'rust-325-update-validation-poll' into rust-325-worker-p…
rmcdaniel Oct 24, 2026
320c1e8
Rust #325: activity heartbeats answer times with microseconds, as sta…
rmcdaniel Oct 24, 2026
0add086
Merge remote-tracking branch 'origin/rust-325-update-validation' into…
rmcdaniel Oct 24, 2026
6632827
Give the cross-kind poll test's worker one process identity, so a rep…
rmcdaniel Oct 24, 2026
90965ac
Rust #325: a no-task poll answer replays a duplicate's lease instead …
rmcdaniel Oct 24, 2026
74aeeab
Rust #325: fence held polls by the registration's incarnation token
rmcdaniel Oct 24, 2026
8daafb4
Merge branch 'rust-325-update-validation-terminal' into rust-325-work…
rmcdaniel Oct 24, 2026
028bee1
Merge main (#746, #733) into the worker poll fence stack
rmcdaniel Oct 24, 2026
c576d6a
Rust #325: a fenced poll's stale answer also replays a duplicate's li…
rmcdaniel Oct 24, 2026
0625d9f
Rust #325: align two poll tests with the incarnation fence
rmcdaniel Oct 24, 2026
1b518e7
Keep the takeover resume worker fresh while it waits for the PHP time…
rmcdaniel Oct 24, 2026
890e280
Merge remote-tracking branch 'origin/rust-325-activity-cancel-ack' in…
rmcdaniel Oct 25, 2026
9b085b6
Merge remote-tracking branch 'origin/main' into rust-325-worker-poll-…
rmcdaniel Oct 25, 2026
17d061a
Merge remote-tracking branch 'origin/main' into rust-325-worker-poll-…
rmcdaniel Oct 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/phpunit-feature.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ jobs:
- name: Require safe PHP image build context
run: node --test tests/Unit/PhpImageContextTest.mjs

- name: Restore PHP's SQLite maintenance loop on every path
run: node --test tests/Unit/PhpMaintenanceWindowTest.mjs
- name: Restore the patch cohort's maintenance loop on every path
run: node --test tests/Unit/PatchCohortMaintenanceTest.mjs

Expand Down
14 changes: 13 additions & 1 deletion .github/workflows/server-parity.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ on:
- tests/Unit/ServerParityWorkerReplacementContractTest.mjs
- tests/Unit/ServerParityOperatorMetricsContractTest.mjs
- tests/Unit/ServerParityActivityLeaseRenewalContractTest.mjs
- tests/Unit/ServerParityWorkflowPollRequestContractTest.mjs
- tests/Unit/ServerParityActivityCancellationAckContractTest.mjs
- tests/Unit/ServerParityServiceCatalogContractTest.mjs
- tests/Unit/ServerParitySystemMetricsContractTest.mjs
Expand All @@ -49,6 +50,8 @@ on:
- tests/Unit/ServerParityRepairPassContractTest.mjs
- tests/Unit/ServerParityClusterInfoContractTest.mjs
- tests/Unit/ServerParityNamespaceDeleteContractTest.mjs
- tests/Unit/ServerParityUpdateValidationContractTest.mjs
- tests/Unit/ServerParityFixtureSelectionTest.mjs
- tests/Unit/ServerParityMessageStreamsContractTest.mjs
- tests/Unit/ServerParityWaitingHistoryContractTest.mjs
- tests/Unit/ServerParityTaskRetryContractTest.mjs
Expand Down Expand Up @@ -1595,7 +1598,7 @@ jobs:
-e COMPOSER_HOME=/tmp/composer -v "$PWD:/source" -w /source "$image" \
install --working-dir=scripts/conformance/server-parity --no-interaction --no-progress
docker run --rm --user=1000:1000 --entrypoint node -v "$PWD:/source:ro" -w /source \
"$image" --test tests/Unit/ServerParityRunnerTest.mjs tests/Unit/ServerParitySdkDirectionLanesTest.mjs tests/Unit/ServerParityCooperativeContractTest.mjs tests/Unit/ServerParityChildCancellationContractTest.mjs tests/Unit/ServerParityScheduleContractTest.mjs tests/Unit/ServerParityVisibilityContractTest.mjs tests/Unit/ServerParityAdmissionContractTest.mjs tests/Unit/ServerParityRoleTokenContractTest.mjs tests/Unit/ServerParityWorkerDeregistrationContractTest.mjs tests/Unit/ServerParitySearchAttributeContractTest.mjs tests/Unit/ServerParityWorkerListingContractTest.mjs tests/Unit/ServerParityWorkerReplacementContractTest.mjs tests/Unit/ServerParityPatchDeploymentContractTest.mjs tests/Unit/ServerParityWaitingHistoryContractTest.mjs tests/Unit/ServerParityTaskRetryContractTest.mjs tests/Unit/ServerParityPublishedPatchContractTest.mjs tests/Unit/ServerParityPublishedPatchTransportTest.mjs tests/Unit/ServerParityRecordFileTest.mjs tests/Unit/ServerParityLateWorkflowCompletionContractTest.mjs tests/Unit/ServerParityStorageTestContractTest.mjs tests/Unit/ServerParitySchedulePoliciesContractTest.mjs tests/Unit/ServerParitySignalsBeforeWorkerContractTest.mjs tests/Unit/ServerParityScheduleCatalogContractTest.mjs tests/Unit/ServerParityOperatorMetricsContractTest.mjs tests/Unit/ServerParityActivityLeaseRenewalContractTest.mjs tests/Unit/ServerParityActivityCancellationAckContractTest.mjs tests/Unit/ServerParityServiceCatalogContractTest.mjs tests/Unit/ServerParitySystemMetricsContractTest.mjs tests/Unit/ServerParityOperatorDashboardTypesContractTest.mjs tests/Unit/ServerParityPrometheusMetricsContractTest.mjs tests/Unit/ServerParityPayloadCleanupContractTest.mjs tests/Unit/ServerParityRepairPassContractTest.mjs tests/Unit/ServerParityClusterInfoContractTest.mjs tests/Unit/ServerParityNamespaceDeleteContractTest.mjs tests/Unit/ServerParityMessageStreamsContractTest.mjs > parity-evidence/comparator-tests.txt
"$image" --test tests/Unit/ServerParityRunnerTest.mjs tests/Unit/ServerParitySdkDirectionLanesTest.mjs tests/Unit/ServerParityCooperativeContractTest.mjs tests/Unit/ServerParityChildCancellationContractTest.mjs tests/Unit/ServerParityScheduleContractTest.mjs tests/Unit/ServerParityVisibilityContractTest.mjs tests/Unit/ServerParityAdmissionContractTest.mjs tests/Unit/ServerParityRoleTokenContractTest.mjs tests/Unit/ServerParityWorkerDeregistrationContractTest.mjs tests/Unit/ServerParitySearchAttributeContractTest.mjs tests/Unit/ServerParityWorkerListingContractTest.mjs tests/Unit/ServerParityWorkerReplacementContractTest.mjs tests/Unit/ServerParityPatchDeploymentContractTest.mjs tests/Unit/ServerParityWaitingHistoryContractTest.mjs tests/Unit/ServerParityTaskRetryContractTest.mjs tests/Unit/ServerParityPublishedPatchContractTest.mjs tests/Unit/ServerParityPublishedPatchTransportTest.mjs tests/Unit/ServerParityRecordFileTest.mjs tests/Unit/ServerParityLateWorkflowCompletionContractTest.mjs tests/Unit/ServerParityStorageTestContractTest.mjs tests/Unit/ServerParitySchedulePoliciesContractTest.mjs tests/Unit/ServerParitySignalsBeforeWorkerContractTest.mjs tests/Unit/ServerParityScheduleCatalogContractTest.mjs tests/Unit/ServerParityOperatorMetricsContractTest.mjs tests/Unit/ServerParityActivityLeaseRenewalContractTest.mjs tests/Unit/ServerParityActivityCancellationAckContractTest.mjs tests/Unit/ServerParityServiceCatalogContractTest.mjs tests/Unit/ServerParitySystemMetricsContractTest.mjs tests/Unit/ServerParityOperatorDashboardTypesContractTest.mjs tests/Unit/ServerParityPrometheusMetricsContractTest.mjs tests/Unit/ServerParityPayloadCleanupContractTest.mjs tests/Unit/ServerParityRepairPassContractTest.mjs tests/Unit/ServerParityClusterInfoContractTest.mjs tests/Unit/ServerParityNamespaceDeleteContractTest.mjs tests/Unit/ServerParityMessageStreamsContractTest.mjs tests/Unit/ServerParityWorkflowPollRequestContractTest.mjs tests/Unit/ServerParityUpdateValidationContractTest.mjs tests/Unit/ServerParityFixtureSelectionTest.mjs > parity-evidence/comparator-tests.txt
docker run --rm --user=1000:1000 --cpus=1 --memory=256m --memory-swap=256m \
--network=none --entrypoint node -v "$PWD:/source:ro" -w /source \
"$image" --test tests/Unit/ServerParityCodecDeadlineLoadTest.mjs > parity-evidence/codec-load-tests.txt
Expand Down Expand Up @@ -1654,6 +1657,7 @@ jobs:
-v "$PWD:/source:ro" -v "$PWD/parity-evidence:/evidence" -w /source \
-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 \
--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 \
Expand All @@ -1666,6 +1670,7 @@ jobs:
-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/embedded.json \
--exclude-fixture tests/Fixtures/ServerParity/update-validation.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 \
Expand All @@ -1678,6 +1683,7 @@ jobs:
-v "$PWD:/source:ro" -v "$PWD/parity-evidence:/evidence" -w /source \
-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 \
--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 \
Expand All @@ -1689,13 +1695,18 @@ jobs:
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/php.json /evidence/rust.json /evidence/embedded.json \
--exclude-fixture tests/Fixtures/ServerParity/update-validation.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 \
--include-fixture tests/Fixtures/ServerParityPending/patch-created-activity-completed.json \
--include-fixture tests/Fixtures/ServerParityPending/workflow-waiting-history-type.json \
--include-fixture tests/Fixtures/ServerParityPending/workflow-waiting-history-message.json \
--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.
update_validation_status=0
bash scripts/conformance/server-parity/update-validation-sqlite.sh http://parity-server:8080 http://parity-rust:8080 || update_validation_status=$?
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
Expand Down Expand Up @@ -1769,6 +1780,7 @@ jobs:
# check 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
- name: Compare every published-SDK direction profile in three concurrent lanes
if: ${{ matrix.part == 'sdk-directions' }}
env:
Expand Down
85 changes: 85 additions & 0 deletions docs/rust-server-port-checkpoints/2026-10-23-update-validation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# October 23, synchronous update validation

- **The gap.** The Python SDK lets a workflow declare update validators. A
worker with validators registers only when the server advertises
`update_validation_tasks` and its `synchronous_update_validation` contract.
Native advertised neither, and refused a validated update with 422
`update_validation_not_implemented`. So a Python worker with validators
could not run against native.
- **How PHP 2.11.1 does it.** Learned from `ValidatedExternalWorkflowUpdateAdmission`
and `WorkflowUpdateValidationTaskBroker`, then observed live against the
published image.
- A worker declares `update_validators` per workflow type and advertises
the `update_validation_tasks` capability. The run's `WorkflowStarted`
records `declared_update_validators` when the worker's contract has that
field.
- For a live run with a complete durable contract and a declared update,
admission checks, in order:
1. a recorded receipt for the `request_id`;
2. whether the contract states validators at all, else 409
`update_validator_contract_missing`, for any remote update;
3. the update's arguments;
4. a `request_id`, which a validated update requires.

None of these refusals is recorded.
- The validation task is keyed by run, update name and `request_id`, and
holds a hash of the arguments. Different arguments under the same key
conflict with 409. A task that has finished replays its result. The
queue's fresh workers must include one that is capable and matches the
run, else 409 with a routing reason.
- The caller waits up to `DW_UPDATE_VALIDATION_TIMEOUT` seconds (10). A
worker claims the task through `POST /api/worker/update-validation-tasks/poll`
or the workflow poll with `task_kinds`. The claim lasts
`DW_UPDATE_VALIDATION_LEASE_TIMEOUT` seconds (5). The task carries the
run's replay history and history export.
- Approval admits the update with its id equal to the task id. A validator
rejection records a rejected command and update under the task id, with
`UpdateRejected`, and answers 422. Any other worker failure answers 409
and records nothing. Without an answer, the task times out with 504 and
a reason: not claimed, worker lost, or execution timeout.
- Approve and reject are fenced, in order: task found, not finished,
leased, owner, attempt, lease unexpired, worker fresh. An expired lease
can be claimed again with the next attempt.
- The multiplexed poll keeps a cursor per queue. It tries the kind the
cursor names first, and turns the cursor to the other kind after each
lease. Every workflow task names `task_kind: "workflow"`.
- **What native does now.**
- Admission follows the same order, with the same bodies, and waits
without holding a transaction. Approval re-enters admission with the
task id, after checking that the run is still current and the approval
still stands.
- The validation task poll, approve and reject routes, with Laravel's
request rules.
- The workflow poll validates `task_kinds`. With validation tasks
requested, it refuses a worker without the capability, answers
`no_workflow_capability` for a worker without workflow types, and
alternates kinds by the `workflow_task_poll_cursors` row.
- The capability, its contract and both timeouts (`DW_UPDATE_VALIDATION_*`,
now honored settings) are advertised.
- A validated task's payload carries the unsigned history export, as
`HistoryExport::forRun` builds it.
- **Native changes found on the way.**
- An accepted update's receipt no longer carries `accepted: true`. PHP's
result mapper sends `accepted` only when it is false.
- A worker contract without `update_validators` now makes a remote update
refuse with 409, as on PHP. Current PHP, Python and Rust SDK workers all
send the field.
- **How it was checked.**
- The `update-validation` fixture records 49 steps: refusals before
validation, a rejection through the multiplexed poll, an approval through
the dedicated poll with its fences, retries, three timeouts run side by
side, lease expiry and re-claim, a worker failure, multiplexed ordering
and every routing refusal.
- Two published PHP runs normalize identically. Native matches all 49
steps on SQLite. The contract test refuses 19 changed observations.
- Native integration tests cover the same paths on SQLite, PostgreSQL and
MySQL.
- **Not done yet** (Server #325 follow-ups):
- **Poll request ids.** PHP replays a leased validation task for a retried
`poll_request_id` and binds the request id to its task kinds. Native
does neither, so a retried multiplexed poll waits for the lease to
expire before the task is offered again.
- **Cluster info.** PHP's `workflow_update_runtime_contract` manifest and
its `service_mode_update_validation` block are not published yet; that
belongs to the cluster info work.
- **Takeover.** PHP data with validation tasks is still refused.
17 changes: 11 additions & 6 deletions docs/rust-server-port.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,13 @@ Updated October 24, 2026.
([#737](https://github.com/durable-workflow/server/issues/737)).

No expectation is edited to make native pass.
- **Parity surface.** Native serves 126 of the 175 API route methods in
published 2.11.1's route table. An execution test sends each one through
the native router, and the ones still answering
- **Parity surface.** Native serves 133 of the 175 API route methods in
published 2.11.1's route table, with message streams, update validation
and activity cancellation acknowledgement. An execution test sends each
one through the native router, and the ones still answering
`501 rust_capability_not_implemented` must match
[`unserved-php-routes.v2.11.1.txt`](../rust/tests/fixtures/unserved-php-routes.v2.11.1.txt).
The work in review serves 12 more: message streams (3), update validation
(3), worker sessions (5) and activity cancellation acknowledgement (1).
The work in review serves 5 more: worker sessions.
- **Next action.** Land the items above in order, keep porting the unserved
routes fixture first, and package the native binary into the server image.

Expand Down Expand Up @@ -134,13 +134,18 @@ any other unknown path or method answers PHP's own 404 or 405
reported as its candidate's failure, with 207.
- **Worker protocol features.**
- Worker sessions.
- Update validation tasks.
- Local activities (prepare, checkpoint, recover, heartbeat, control,
outcome and cancellation acknowledgement).
- Cancellation scopes.
- Activity cancellation acknowledgement. (Lease renewal, from Server 2.11,
is served: `POST /api/worker/activity-tasks/{task}/renew-lease`, with the
`activity_lease_renewal` capability and the `activity-lease-renewal` fixture.)
- (Synchronous update validation is served: the validation task poll,
approve and reject routes, the multiplexed workflow poll with
`task_kinds`, and validator-gated update admission, with the
`update-validation` fixture. Native does not replay a validation task by
`poll_request_id` or bind a request id to its task kinds; see
`docs/rust-server-port-checkpoints/2026-10-23-update-validation.md`.)
- **Run operations.**
- Imports from embedded v2 and Waterline v1.
- **Other families.**
Expand Down
Loading
Loading