Repository navigation
Rust #325: serve cancellation-scope prefix checkpoints - #822
Conversation
POST /api/worker/workflow-tasks/{taskId}/cancellation-scopes/checkpoint was
the last published PHP route native answered 501. The published PHP SDK
(3.6.1) calls it under cooperative cancellation, which native advertises,
when a turn recorded commands before opening or delivering a scope, and it
fails the workflow task on any refusal.
Native now serves it as Server's checkpointCancellationScopePrefix over
Core's checkpointCancellationScopePrefix:
- protocol 1.20, then the checkpoint fields (checkpoint_id, start_sequence,
prohibited stream cursors, waits and sticky cache) with Laravel's
messages, then a completion's own validation and guards;
- the claim must be the cooperative claim (409
active_claim_cancellation_not_supported, without the refresh token);
- Core's checks in Core's order: the checkpoint arguments, terminal and
turn-closing commands refused, the claim, a closed run or passed
deadline, the retained receipt (an identical retry answers it with
duplicate: true, anything else under its id is a mismatch), a root
cancellation request (allowed only after its delivery, past the
delivered boundary, before the cleanup deadline), the authored sequence,
scope membership and update commands;
- the commands apply as a completion applies them, metered, and the task
stays leased with its lease unchanged; the task payload keeps the
portable_scope_checkpoint receipt.
Answers are 200, 404 for an unknown task, or 409, in the worker envelope
with history_refresh_page_token.
complete_workflow's non-terminal command arms move unchanged into
apply_non_terminal, which the completion and the checkpoint share.
The receipt's fingerprint is native's SHA-256 over the validated commands;
Core hashes its own normalized command arrays, so the values differ. No
client computes or compares it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cancellation-scopes probe adds a fifth run, R5, whose cooperative claim checkpoints the commands of its turn before opening a scope: - protocol 1.19, missing and invalid fields, a foreign lease owner; - Core's refusals: a terminal command, a signal wait; - the prefix (a side effect and a version marker at 1 and 2), its identical retry, the same id with other commands, a new id at a used sequence; - a second prefix (a timer at 3, with its task), and the first prefix refused once the second replaced its receipt; - the scope opening at 4 on the unchanged claim, the completion, and the run's history. R3's plain claim and R4's protocol 1.19 claim are refused with active_claim_cancellation_not_supported. Every step sits under scope_checkpoints, which sorts after every earlier key, and every new identity and fingerprint is named, so no earlier label in the normalized observation changes. The expectations are published Server 2.11.5's answers (image sha256:bcc67285…, SQLite), where every existing step still matched. The contract unit test's stored observation gains the same steps, with checks that a retry does not commit again, keeps its fingerprint and the claim, and that refusals carry the refresh token. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The port log's parity surface now counts all 175 published API route methods, its missing-surface list marks cancellation scopes served with the prefix checkpoint, and the registration-freshness note says the checkpoint shares the completion's guard. The new checkpoint file records the contract, why the release candidate needed it, the fingerprint divergence, and what was verified. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of the scope checkpoint found divergences from Core 2.15.2, three of them shared with the workflow task completion: - A signal-resumed claim's signal is applied once (recordAppliedSignalForSignalResume): an already-applied signal is left as it is instead of refused with signal_already_applied, so a claim that checkpointed its prefix still completes (completion and checkpoint). On the checkpoint path, a signal Core would not apply (command unavailable, its wait not open, another wait open) is skipped as Core skips it; the completion keeps native's refusals for those. - The authored sequence counts SearchAttributesUpserted, as nextDurableCommandSequence does: a turn or prefix ending in an upsert is followed at the next sequence (completion, checkpoint and scope open). - complete_update and fail_update take the batch's next sequence and advance it (applyCompleteUpdate, applyFailUpdate): a later command in the same batch takes the following sequence (completion and checkpoint). - A checkpoint changes no run status and no run summary: Core's markRunWaiting and projection run at completion. The completion keeps setting the run waiting after an activity, timer or wait, as before. - Core's own checkpoint refusals, signal application's included, answer as a refused checkpoint. Server's exception answers (structural limits, memo validation) keep their shapes, as PHP's catch block gives them for both. Also: the receipt's doc comment is back on COMPLETION_RECEIPT; the unreachable command-count and cancel_selection_operation checks are removed, with a note; one RFC 3339 parser serves the cancellation context checks; the fingerprint hashes serde_json::to_vec(commands), the same bytes. Each of the four new execution tests fails on the previous head and passes now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rows Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Security defensive review finding at exact head A checkpoint whose final command is
The existing regression covers Please add a focused regression for both update outcomes (or the shared path): accepted update -> checkpoint ending in update completion/failure -> open a scope or checkpoint again at the returned |
|
Security follow-up for exact reviewed head The update-tail sequence concern in my earlier review is now confirmed as an existing shared PHP/Core defect, not a native-only regression introduced by this PR. General reproduced it against normally installed Core 2.15.2 and published Core 2.15.4 for both This supersedes my earlier request to fix that behavior before merging #822. On this exact head, my complementary review of authorization/tenant binding, claim and lease fencing, replay/idempotency, sequence advancement, transaction rollback, bounded/malformed command handling, and refusal surfaces found no other defensive issue. Security acceptance remains conditional on the protected hosted checks; any head change needs delta review. |
|
General review at exact head The already-applied signal behavior, The update-tail concern is confirmed in PHP too. Against both normal-installed Core 2.15.2 and current published Core 2.15.4, each eight-case/32-assertion run has four update-tail regression failures and four passing marker-after-update controls, with no test errors. Both successful and failed update tails return a Independent executable review scope: 27/27 cancellation-scope contract tests pass against this exact source. These are the JavaScript fixture/validator controls. I did not repeat Rust execution, database qualification or the owner's recorded corpus comparison, and this review is not an installed-release acceptance claim. Preserve the explicit fingerprint and wait-row differences and link the eventual #953 native contract follow-through. |



Part of #325 (decision comment 6449500902). Native now serves
POST /api/worker/workflow-tasks/{taskId}/cancellation-scopes/checkpoint, the last published PHP route it answered with501. All 175 published API route methods are now served;unserved-php-routes.v2.11.1.txtis empty.Why it matters. Published PHP SDK 3.6.1 calls this route when cooperative cancellation is on and a turn recorded commands (a side effect, marker, or memo/search-attribute upsert) before opening or delivering to a scope. Native advertises
cooperative_cancellation: true, and the SDK fails the task on any refusal, so such workflows got stuck on native.What it does (Server's
checkpointCancellationScopePrefixwith Core 2.15'scheckpointCommands):portable_scope_checkpointreceipt in the task payload. An identical retry answersduplicate: true; a changed retry answerscancellation_scope_checkpoint_mismatch.history_refresh_page_token.complete_workflow's non-terminal command application is factored into a sharedapply_non_terminal.Completion corrections found in review, each now matching Core, with a regression test that fails on the earlier head:
409 signal_already_applied(CorerecordAppliedSignalForSignalResume).SearchAttributesUpsertedcounts toward the next command sequence (CorenextDurableCommandSequence).complete_update/fail_updateadvance the batch's sequence, so a later command in the same batch no longer reuses it.Recorded divergences (in
docs/rust-server-port-checkpoints/2026-10-27-scope-checkpoints.md):fingerprintis an opaque retry identity. Core hashes its parsed internal command arrays; native hashes the validated request commands. No client computes it, and the fixture compares only its reuse.Shared fixture.
cancellation-scopesgains a run under the new keyscope_checkpoints. Its expectations are what published 2.11.5 answered, and no existing step or label changes.Checked (by the implementer, on the Rust dev host):
scope_checkpointsexecution tests, route coverage, and the four regression tests.updates_have_no_lifetime_cap_and_preserve_accepted_work_after_wait_timeout, a one-second wait test. It failed while a release build saturated the host and passed 3/3 alone on each database.native-image-corpus.shwithmain'sphp-2.11.5.jsonand a native image built from this branch: published 2.11.5, native and embedded each pass 73/73, and the records compare equal.🤖 Generated with Claude Code