You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Bridge adapter update_workflow skips external update admission, so SDK-worker updates are always rejected #747
POST /api/bridge-adapters/webhook/{adapter} with action: update_workflow can never accept an update for a workflow served by an SDK (external) worker.
BridgeAdapterController::updateWorkflow calls WorkflowControlPlane::update directly. WorkflowController::update first tries ExternalWorkflowUpdateAdmission::admit and falls back to the control plane only when that returns null. The bridge path skips external admission. For an SDK-worker workflow, the control plane cannot load a PHP definition, so the update is refused.
Reproduction (published Server 2.11.1)
A raw worker registers runtime: python with workflow_command_contracts declaring update bump (capabilities query_tasks, workflow_updates).
Start the workflow through the bridge, and leave it idle on a signal wait.
update_workflow with idempotency_key: u-2, target: {workflow_id, update_name: bump, wait_for: accepted}.
Observed: 422, outcome: rejected, reason: workflow_definition_unavailable, control_plane_outcome: rejected_workflow_definition_unavailable, and an UpdateRejected history event. Replaying the same idempotency key is rejected again and records another UpdateRejected, because duplicate detection only matches accepted commands.
Expected (what POST /api/workflows/{id}/update/bump does for the same run): 202 accepted, with the bridge command context recorded on an accepted update command. The replay then answers 200 duplicate, control_plane_outcome: deduped_existing_command.
Signals are unaffected: signal_workflow is accepted and deduplicated.
Boundary in the Rust port
The shared fixture tests/Fixtures/ServerParity/bridge-adapters.json (Server #325 bridge port, in review) records the published answers under published_defects.update_skips_external_admission. At those two steps and in the history, its contract accepts either the published refusal or the intended acceptance and deduplication. Native implements the intended behavior. When a fixed PHP release is published, the fixture will be re-recorded and the tolerance removed.
Suggested fix: in updateWorkflow, try ExternalWorkflowUpdateAdmission::admit with the bridge command context first, exactly as WorkflowController::update does, before falling back to WorkflowControlPlane::update.
Correction to the Expected section. The regular route accepts this update only when the worker's contract declares update_validators.
Checked on published Server 2.11.1 (ghcr.io/durable-workflow/server@sha256:bf8bed13…, SQLite) by running the bridge-adapters fixture, then sending the same update to POST /api/workflows/{id}/update/bump:
As in the reproduction above, the worker contract declares update bump without update_validators. The regular route then answers 409update_validation_failed / update_validator_contract_missing ("The durable workflow contract does not state whether this update has a validator."). WorkflowStartService writes a complete durable contract for the external worker, and ValidatedExternalWorkflowUpdateAdmission::preAdmissionValidation refuses a remote update whose start event lacks declared_update_validators.
With update_validators: [] declared, the regular route answers 202 with update_status: accepted. The bridge path still shows the defect described here, unchanged.
So the corrected bridge behavior is "admit as the regular route does", including that route's validator-declaration refusal. The bridge fixture's worker now declares update_validators: [], so its expected 202 is the regular route's actual answer. That fix is on the Rust bridge branch (rust-325-bridge-adapters, commit ae97a3d), and native passes it on SQLite, PostgreSQL 17, MySQL 8 and MariaDB 10.11. The PHP fix for the bridge path should go through the same admission, so it inherits this refusal for undeclared validators.
Parity evidence: published Server 2.11.7 answers this case as intended in the bridge-adapters fixture, so the fixture now compares it exactly and no longer accepts the earlier published answer (#868, merged as 56aad9fe). Native answers the same way. Closing this issue is for its owner.
Fixed and verified in published Server 2.11.7 / Core 2.15.7, actual amd64 imagesha256:be7c2288ff935396262d743590530b204fac11b76582e56a525cc81a8da840d3.
The real bridge-adapters HTTP probe registers an external worker with runtime: python, starts/idles its workflow, and sends the original bump update with keyu-2: response 202 / accepted true. The identical redelivery is 200 / accepted false / duplicate withcontrol_plane_outcome: deduped_existing_command and the same original run. The fixture retains one UpdateAccepted; its separate undeclared-update control is the sole UpdateRejected. Independent stock-image before/after snapshots also show exact equality across ten workflow tables on redelivery, proving no extra duplicate history/write. Published image and HTTP receipt.
The fresh full PHP-only MariaDB record again passes this case (all 83 cases pass) at the same published tuple: exact record and limitations. The native owner also removed this published-defect allowance in merged #868; current parity now requires the intended outcome exactly. No modified product/dependency overlay is used.
Closing this external-update-admission defect as completed. Broader Bridge concurrency/identity acceptance remains in #777; this closure does not claim that separate scope complete.
Activity
Server parity: declare the bridge fixture's update validators
rmcdaniel commentedon Oct 26, 2026
Correction to the Expected section. The regular route accepts this update only when the worker's contract declares
update_validators.Checked on published Server 2.11.1 (
ghcr.io/durable-workflow/server@sha256:bf8bed13…, SQLite) by running thebridge-adaptersfixture, then sending the same update toPOST /api/workflows/{id}/update/bump:bumpwithoutupdate_validators. The regular route then answers 409update_validation_failed/update_validator_contract_missing("The durable workflow contract does not state whether this update has a validator.").WorkflowStartServicewrites a complete durable contract for the external worker, andValidatedExternalWorkflowUpdateAdmission::preAdmissionValidationrefuses a remote update whose start event lacksdeclared_update_validators.update_validators: []declared, the regular route answers 202 withupdate_status: accepted. The bridge path still shows the defect described here, unchanged.So the corrected bridge behavior is "admit as the regular route does", including that route's validator-declaration refusal. The bridge fixture's worker now declares
update_validators: [], so its expected 202 is the regular route's actual answer. That fix is on the Rust bridge branch (rust-325-bridge-adapters, commit ae97a3d), and native passes it on SQLite, PostgreSQL 17, MySQL 8 and MariaDB 10.11. The PHP fix for the bridge path should go through the same admission, so it inherits this refusal for undeclared validators.rmcdaniel commentedon Oct 29, 2026
Parity evidence: published Server 2.11.7 answers this case as intended in the
bridge-adaptersfixture, so the fixture now compares it exactly and no longer accepts the earlier published answer (#868, merged as56aad9fe). Native answers the same way. Closing this issue is for its owner.rmcdaniel commentedon Oct 29, 2026
Fixed and verified in published Server 2.11.7 / Core 2.15.7, actual amd64 image
sha256:be7c2288ff935396262d743590530b204fac11b76582e56a525cc81a8da840d3.The real
bridge-adaptersHTTP probe registers an external worker withruntime: python, starts/idles its workflow, and sends the originalbumpupdate with keyu-2: response 202 / accepted true. The identical redelivery is 200 / accepted false / duplicate withcontrol_plane_outcome: deduped_existing_commandand the same original run. The fixture retains one UpdateAccepted; its separate undeclared-update control is the sole UpdateRejected. Independent stock-image before/after snapshots also show exact equality across ten workflow tables on redelivery, proving no extra duplicate history/write. Published image and HTTP receipt.The fresh full PHP-only MariaDB record again passes this case (all 83 cases pass) at the same published tuple: exact record and limitations. The native owner also removed this published-defect allowance in merged #868; current parity now requires the intended outcome exactly. No modified product/dependency overlay is used.
Closing this external-update-admission defect as completed. Broader Bridge concurrency/identity acceptance remains in #777; this closure does not claim that separate scope complete.