Repository navigation
Refuse a lost workflow task claim with PHP's ownership guard fields - #548
Conversation
A paused worker that resumed with workflow tasks already given to another worker got only a reason from native (task_not_leased, conflicting_completion) and exited. Published PHP refuses those requests through WorkflowTaskOwnership::guard, whose body carries the task status and, once the run has closed, can_continue false; current workers read those fields to drop a lost claim and keep running. Completion, failure, lease renewal and task history now share one guard in PHP's order, with PHP's refusal bodies. A completion that differs from the recorded one gets the guard's refusal instead of conflicting_completion. A completed workflow task clears its lease expiry and keeps its owner, as PHP's does. The late-workflow-completion fixture covers the replaced claim's requests while the new claim holds the lease, after it completes and after the run closes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Sonar's quality gate failed on new-code duplication (3.0%, limit 3): |
The guard needed its own copy of the store's where-clause in a separate module, which Sonar counted as duplicated new code. It now lives in the store's existing impl next to the fence; the module keeps only the refusal body builder. The two task_not_leased branches share one refusal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The multi-node schedule also passed on MariaDB 10.11. A confirmation run with the #548 binary shows every late request refused with PHP's ownership guard body; SDK 2.0.10's remaining exits are its own lack of lost-claim handling. With PHP SDK 2.5.3, all 1,080 workflows completed once on the four databases and no worker exited. Summaries now record the SDK version, and the record describes the concurrent bootstrap race found on the way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tion # Conflicts: # docs/rust-server-port.md
|
Merged main ( Parity. I cancelled the Shared Server fixtures run for the merged head (39507398656) as a duplicate:
Local check of the merged head. On SQLite, the native lib suite (120) and the execution suite (288, including Auto-merge is on for the required checks. |
…tion # Conflicts: # .github/workflows/server-parity.yml # docs/rust-server-port.md # scripts/conformance/server-parity/contract.mjs # scripts/conformance/server-parity/probe.php
|
Local checks of the merged head:
The Shared Server fixtures run for this head (39511283057) is left running. Auto-merge is back on for the required checks. |
|
|
…tion # Conflicts: # .github/workflows/server-parity.yml # docs/rust-server-port.md # rust/tests/execution.rs # scripts/conformance/server-parity/contract.mjs # scripts/conformance/server-parity/probe.php
|
|
|



Part of #325.
Why
In the multi-node runs (#549), a paused SDK worker resumed holding workflow tasks the server had already given to the other worker. Native refused its late lease renewals and completion with only a reason (
task_not_leased,conflicting_completion). The worker could not tell that its claim was lost, so it exited; this happened 6 times. Published PHP refuses the same requests through its ownership guard. Its body carries the task status and, once the run has closed,can_continue: false, and current workers read those fields to drop the claim and keep running.What changes
WorkflowTaskOwnership::guardorder:run_closed(stop reason, run status, closure, task status, owner) when its run has closed. Otherwise refused withtask_not_leasedand the task status.lease_expired), then owner (lease_owner_mismatch), then attempt (workflow_task_attempt_mismatchwithcurrent_attempt).error, the reason's fields,protocol_versionandserver_capabilities.conflicting_completion. An identical replay still returns the recordedalready_completedreceipt.Evidence
late-workflow-completionre-registers a worker that holds a claim. It then sends the replaced claim's completion, renewal and failure three times: while the new claim holds the lease, after the new claim completes, and after the run closes. The contract checks PHP's exact bodies apart from timestamps.a_replaced_workflow_claim_is_refused_with_the_published_ownership_fieldsis new. Two existing tests assertedconflicting_completionafter a run-closing completion. They now expect PHP'srun_closed; after a failed run, the task status isfailed, as PHP stores it.visibility-current-runs, which needs the published CLI artifact that CI supplies.tests/Unit/ServerParityLateWorkflowCompletionContractTest.mjs(19 tests) is added to the CI list.Not covered
PHP also refuses an otherwise valid claim whose worker registration is no longer fresh (
stale_worker_registration). The local activity and cancellation scope checkpoints still use the older fence. Both are recorded as known gaps and need their own fixtures.🤖 Generated with Claude Code