Skip to content

Commit cc9cde2

Browse files
Carry parent_close_policy on every ChildRunStarted history event
Per-child parent-close policy overrides were only recorded in the ChildWorkflowScheduled event. Continue-as-new and child-retry paths re-emit ChildRunStarted for the new run without a fresh ChildWorkflowScheduled, so history alone could not show which override applied to the latest child run without joining WorkflowLink. Add parent_close_policy to the ChildRunStarted payload contract, emit it from every ChildRunStarted producer, update the api-stability wire-format row, and assert per-child override visibility across continue-as-new in V2ParentClosePolicyTest.
1 parent 684a75d commit cc9cde2

6 files changed

Lines changed: 57 additions & 1 deletion

File tree

docs/api-stability.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ cross-SDK replay:
197197
| `SideEffectRecorded` | `sequence`, `result` | `WorkflowStepHistory`, `WorkflowExecutor`, `QueryStateReplayer` |
198198
| `VersionMarkerRecorded` | `sequence`, `change_id`, `version`, `min_supported`, `max_supported` | `WorkflowStepHistory`, `WorkflowExecutor`, `QueryStateReplayer` |
199199
| `ChildWorkflowScheduled` | `sequence`, `workflow_link_id`, `child_call_id`, `child_workflow_instance_id`, `child_workflow_run_id`, `child_workflow_class`, `child_workflow_type`, `child_run_number`, `parent_close_policy`, `retry_policy`, `timeout_policy`, `parallel_group_id`, `parallel_group_kind`, `parallel_group_base_sequence`, `parallel_group_size`, `parallel_group_index`, `parallel_group_path` | `WorkflowStepHistory`, `WorkflowExecutor`, `QueryStateReplayer`, `ChildRunHistory`, `ParallelChildGroup`, `RunLineageView` |
200-
| `ChildRunStarted` | `sequence`, `workflow_link_id`, `child_call_id`, `child_workflow_instance_id`, `child_workflow_run_id`, `child_workflow_class`, `child_workflow_type`, `child_run_number`, `child_status`, `retry_policy`, `timeout_policy`, `execution_timeout_seconds`, `run_timeout_seconds`, `execution_deadline_at`, `run_deadline_at`, `retry_attempt`, `retry_of_child_workflow_run_id`, `retry_backoff_seconds`, `parallel_group_id`, `parallel_group_kind`, `parallel_group_base_sequence`, `parallel_group_size`, `parallel_group_index`, `parallel_group_path` | `ChildRunHistory`, `ParallelChildGroup`, `RunLineageView`, worker history payload consumers |
200+
| `ChildRunStarted` | `sequence`, `workflow_link_id`, `child_call_id`, `child_workflow_instance_id`, `child_workflow_run_id`, `child_workflow_class`, `child_workflow_type`, `child_run_number`, `child_status`, `parent_close_policy`, `retry_policy`, `timeout_policy`, `execution_timeout_seconds`, `run_timeout_seconds`, `execution_deadline_at`, `run_deadline_at`, `retry_attempt`, `retry_of_child_workflow_run_id`, `retry_backoff_seconds`, `parallel_group_id`, `parallel_group_kind`, `parallel_group_base_sequence`, `parallel_group_size`, `parallel_group_index`, `parallel_group_path` | `ChildRunHistory`, `ParallelChildGroup`, `RunLineageView`, worker history payload consumers |
201201
| `ChildRunCompleted` | `sequence`, `workflow_link_id`, `child_call_id`, `child_workflow_instance_id`, `child_workflow_run_id`, `child_workflow_class`, `child_workflow_type`, `child_run_number`, `child_status`, `closed_reason`, `closed_at`, `output`, `parallel_group_id`, `parallel_group_kind`, `parallel_group_base_sequence`, `parallel_group_size`, `parallel_group_index`, `parallel_group_path` | `WorkflowExecutor`, `QueryStateReplayer`, `ChildRunHistory`, `ParallelChildGroup`, `RunLineageView` |
202202
| `ChildRunFailed` | `sequence`, `workflow_link_id`, `child_call_id`, `child_workflow_instance_id`, `child_workflow_run_id`, `child_workflow_class`, `child_workflow_type`, `child_run_number`, `child_status`, `closed_reason`, `closed_at`, `failure_id`, `failure_category`, `exception_type`, `exception_class`, `message`, `exception`, `code`, `parallel_group_id`, `parallel_group_kind`, `parallel_group_base_sequence`, `parallel_group_size`, `parallel_group_index`, `parallel_group_path` | `ChildRunHistory`, `FailureSnapshots`, `ParallelChildGroup`, `ParallelFailureSelector`, `RunLineageView` |
203203
| `ChildRunCancelled` | `sequence`, `workflow_link_id`, `child_call_id`, `child_workflow_instance_id`, `child_workflow_run_id`, `child_workflow_class`, `child_workflow_type`, `child_run_number`, `child_status`, `closed_reason`, `closed_at`, `failure_id`, `failure_category`, `exception_class`, `message`, `parallel_group_id`, `parallel_group_kind`, `parallel_group_base_sequence`, `parallel_group_size`, `parallel_group_index`, `parallel_group_path` | `ChildRunHistory`, `FailureSnapshots`, `ParallelChildGroup`, `RunLineageView`, parent-close projections |

src/V2/Support/DefaultWorkflowTaskBridge.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1678,6 +1678,7 @@ private function applyStartChildWorkflow(
16781678
'child_workflow_class' => $workflowType,
16791679
'child_workflow_type' => $workflowType,
16801680
'child_run_number' => 1,
1681+
'parent_close_policy' => $parentClosePolicy,
16811682
'retry_policy' => $retryPolicy,
16821683
'timeout_policy' => $timeoutPolicy,
16831684
'execution_timeout_seconds' => $executionTimeoutSeconds,
@@ -2063,6 +2064,7 @@ private function startChildRetryIfAvailable(
20632064
'child_workflow_class' => $retryRun->workflow_class,
20642065
'child_workflow_type' => $retryRun->workflow_type,
20652066
'child_run_number' => $retryRun->run_number,
2067+
'parent_close_policy' => $parentClosePolicy,
20662068
'retry_attempt' => $attemptCount + 1,
20672069
'retry_of_child_workflow_run_id' => $failedChildRun->id,
20682070
'retry_backoff_seconds' => $backoffSeconds,

src/V2/Support/HistoryEventPayloadContract.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,7 @@ final class HistoryEventPayloadContract
377377
'child_workflow_type',
378378
'child_run_number',
379379
'child_status',
380+
'parent_close_policy',
380381
'retry_policy',
381382
'timeout_policy',
382383
'execution_timeout_seconds',

src/V2/Support/WorkflowExecutor.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1660,6 +1660,7 @@ private function scheduleChildWorkflow(
16601660
'child_workflow_class' => $childRun->workflow_class,
16611661
'child_workflow_type' => $childRun->workflow_type,
16621662
'child_run_number' => $childRun->run_number,
1663+
'parent_close_policy' => $parentClosePolicy->value,
16631664
], $parallelMetadata ?? []), $task);
16641665

16651666
WorkflowHistoryEvent::record($childRun, HistoryEventType::StartAccepted, [
@@ -2410,6 +2411,7 @@ private function continueAsNew(
24102411
'child_workflow_class' => $continuedRun->workflow_class,
24112412
'child_workflow_type' => $continuedRun->workflow_type,
24122413
'child_run_number' => $continuedRun->run_number,
2414+
'parent_close_policy' => $continuedChildLink->parent_close_policy,
24132415
], $parallelMetadata), static fn ($value): bool => $value !== null));
24142416
}
24152417

@@ -3290,6 +3292,7 @@ private function startChildRetryIfAvailable(
32903292
'child_workflow_class' => $retryRun->workflow_class,
32913293
'child_workflow_type' => $retryRun->workflow_type,
32923294
'child_run_number' => $retryRun->run_number,
3295+
'parent_close_policy' => $parentClosePolicy,
32933296
'retry_attempt' => $attemptCount + 1,
32943297
'retry_of_child_workflow_run_id' => $failedChildRun->id,
32953298
'retry_backoff_seconds' => $backoffSeconds,

tests/Feature/V2/V2ParentClosePolicyTest.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,54 @@ public function testChildLinkRecordsParentClosePolicyInHistoryAndRow(): void
6565

6666
$this->assertNotNull($scheduledEvent);
6767
$this->assertSame('request_cancel', $scheduledEvent->payload['parent_close_policy']);
68+
69+
$startedEvent = $parentRun->historyEvents
70+
->first(
71+
static fn (WorkflowHistoryEvent $event): bool => $event->event_type === HistoryEventType::ChildRunStarted
72+
);
73+
74+
$this->assertNotNull($startedEvent);
75+
$this->assertSame(
76+
'request_cancel',
77+
$startedEvent->payload['parent_close_policy'] ?? null,
78+
'ChildRunStarted history payload should carry the per-child parent_close_policy override.',
79+
);
80+
}
81+
82+
public function testChildRunStartedCarriesParentClosePolicyAcrossContinueAsNew(): void
83+
{
84+
$workflow = WorkflowStub::make(
85+
TestParentWithClosePolicyContinuingChildWorkflow::class,
86+
'policy-history-survives-continue',
87+
);
88+
$workflow->start('request_cancel', 5);
89+
90+
$this->drainReadyTasks(maxIterations: 6);
91+
92+
$parentRun = WorkflowRun::query()
93+
->where('workflow_instance_id', 'policy-history-survives-continue')
94+
->first();
95+
96+
$startedEvents = $parentRun->historyEvents
97+
->filter(
98+
static fn (WorkflowHistoryEvent $event): bool => $event->event_type === HistoryEventType::ChildRunStarted
99+
)
100+
->values();
101+
102+
// Original ChildRunStarted plus at least one re-emission for a continued child run.
103+
$this->assertGreaterThanOrEqual(
104+
2,
105+
$startedEvents->count(),
106+
'Expected the continue-as-new child to emit additional ChildRunStarted events.',
107+
);
108+
109+
foreach ($startedEvents as $event) {
110+
$this->assertSame(
111+
'request_cancel',
112+
$event->payload['parent_close_policy'] ?? null,
113+
'Every ChildRunStarted history event should carry the per-child parent_close_policy override.',
114+
);
115+
}
68116
}
69117

70118
public function testDefaultPolicyIsAbandon(): void

tests/Unit/V2/HistoryEventWireFormatDocumentationTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ final class HistoryEventWireFormatDocumentationTest extends TestCase
178178
'child_workflow_type',
179179
'child_run_number',
180180
'child_status',
181+
'parent_close_policy',
181182
'retry_policy',
182183
'timeout_policy',
183184
'execution_timeout_seconds',
@@ -323,6 +324,7 @@ final class HistoryEventWireFormatDocumentationTest extends TestCase
323324
'child_workflow_class',
324325
'child_workflow_type',
325326
'child_run_number',
327+
'parent_close_policy',
326328
'retry_policy',
327329
'timeout_policy',
328330
'execution_timeout_seconds',

0 commit comments

Comments
 (0)