Repository navigation
Bind the redrive successor cancellation to Core's full shape - #242
Conversation
The successor check now requires a root v1 cancellation context whose lineage is exactly the request's own address, a null parent_request_id, requested_at and schema, the request's command_type and the delivery's single-call call_kind. A delivery to a call resolved before its request, or to an unscheduled step that is recorded later, is refused (Workflow 28893f97 CancellationContext::fromArray, WorkflowStub::cancel and CooperativeCancellationDelivery). The unreleased #236 fixture redrive-reused-upserts-cancellation-clock has no command_type. It stays byte-identical, is registered as non-qualifying, and redrive-successor-reused-upserts-cancellation-clock replaces it. Fixes #239. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
General interim review (Security unavailable), exact head Verified:
Blocking (fidelity):
Non-blocking:
Re-review on the new head. |
An ActivityScheduled with local_activity true is a local_activity call, and a TimerScheduled's timer_kind maps condition and signal timeouts, so a valid local-activity delivery is accepted and an activity delivery to it is refused. The requester check refuses only an empty string, as CancellationContext::fromArray does. Citations now name WorkflowStub::requestCancellation / recordCancellationRequest, and the parallel and selection_handle refusal is documented as deliberate. Refs #239. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
General interim re-review (Security unavailable): exact head The fix (7cf1a63)
The merge (850baf5) brings in exactly #243's two files, with no source, script, policy or fixture changes. Evidence
Merge only this SHA. |
|
Merged at General's clearance of exact head
|



Fixes #239. Tests, one added fixture and the CHANGELOG only. No product code changes, the corpus policy and
scripts/ciare unchanged, and every existing fixture is byte-identical tomain(407f1ed).Rules, from Workflow
28893f97These are the rules for a successor's own cancellation in
check_redrive_successor(tests/test_replay_clock.py). sdk-rust#179 and sdk-php#250 can use the same list.parent_request_idis present and null, androot_request_idis the request.WorkflowStub::requestCancellation, throughrecordCancellationRequest(src/V2/WorkflowStub.php~2376), builds exactly this for a run's own cancellation. A propagated parent or scope context is rooted at another run, so it is not the successor's own cancellation.[{request_id, workflow_instance_id, workflow_run_id}]for this request on the successor, and nothing else.CancellationContext::fromArrayrequireslineage[0]to be the root and run ids to be unique. With the root at the successor, that leaves exactly one entry. A prepended source-run entry or a repeated entry is refused.CooperativeCancellationDelivery::validateCallBoundary/resolvedBeforeRequest):ActivityScheduled,TimerScheduled,SignalWaitOpened), the delivery'scall_kindmust be the kind CorecallKindAtderives.ActivityScheduledislocal_activitywhenlocal_activityistrueandactivityotherwise.TimerScheduledisconditionorsignalfor acondition_timeoutorsignal_timeouttimer_kind, andtimerotherwise.SignalWaitOpenedissignal. The call must not be resolved (ActivityCompleted,TimerFired,SignalApplied) before the request. A resolution between request and delivery is still allowed, as in Core.resume_step_sequence), a call not yet scheduled. No successor event may occupy that step before or after it.command_type=request_cancellation.call_kindfromCALL_SHAPES(activity,local_activity,timer,condition,signal,child,service_operation), and nosequence_span/operation_sequence/operation_sequence_span. This is a deliberate narrowing: Core also acceptsparallelandselection_handledeliveries, but no successor fixture uses those shapes, so the check refuses them rather than half-modelling them.schemaisdurable-workflow.cancellation-context/v1, with noscope_originorscope_authority_deadline_at.requested_atparses and lies between the successor start and the request'srecorded_at. Core takesnow()for the context before it records the event.cleanup_deadline_atis strictly afterrequested_at, which matchesfromArray. Before this change it was only compared withrecorded_at, and equal times were allowed.requesteris a non-empty map whose keys are amongtype/id/labeland whose values are strings other than''. Like Core, it does not trim them.sourceis non-blank.reasonequals the context's.isPlainTimerand source-run binding).Fixture
The #236 fixture
redrive-reused-upserts-cancellation-clockis unreleased: it merged after 2.13.1. ItsCooperativeCancellationRequestedhas nocommand_type, and Core never writes that shape. The corpus is immutable, so the fixture stays byte-identical:f8c40d18…4856and errorthe request's command_type is request_cancellation.redrive-successor-reused-upserts-cancellation-clockreplaces it. It is the same history, with a newidandcommand_typeadded to the request.DELIVERY_FIXTURE.Evidence
main'scheck_redrive_successor, taken fromgit show origin/main:tests/test_replay_clock.py.mainaccepted all 49, and this head refuses all 49. That is 24 shapes on each of the two cancellation successors, plus the recorded-call kind mismatch.activity,timer,child,service_operation). A call completed after the request but before the delivery is still deliverable. Every registered successor passes.mainand this head over all 75 fixtures that have a history. Exactly one verdict changes:redrive-reused-upserts-cancellation-clockgoes from valid tothe request's command_type is request_cancellation. The new fixture passes on both checks.Local runs at b921016 (lab2, Python 3.13)
ruff check src/ tests/: clean.validate-regression-corpus.py --base-ref origin/main:status: pass. Replay goes from 90 to 91 fixtures (1 added),related_change: false.tests, excludingtests/integration): 4851 passed, 4 skipped.redrive_time.Interim review fixes (head 7cf1a63)
callKindAtdoes it.local_activityto anActivityScheduledwithlocal_activity: trueis now accepted.b9210162refused it.activityto that call is now refused.b9210162accepted it.local_activityto an ordinary activity is refused." "is now accepted on both cancellation successors, as Core accepts it.b9210162refused it.WorkflowStub::requestCancellation/recordCancellationRequest. The pinned error string is unchanged.status: pass.Not merged or released.
🤖 Generated with Claude Code