Skip to content

Commit a2def5a

Browse files
Always emit failure_category in FailureHandled history events
Closes #26 (TD-007). FailureHandled payloads previously omitted the failure_category field — downstream consumers had to load the WorkflowFailure DB row to see what kind of failure was caught. Now the event payload itself carries the canonical FailureCategory enum value, with fallback ordering: 1. WorkflowFailure model (authoritative; enum-cast) when loaded from DB 2. failurePayload['failure_category'] (used for child-propagated failures where the payload is copied from the child's WorkflowFailed event) 3. FailureCategory::Application as catch-all so the field is always present resolveFailureHandledCategory rejects non-enum values via tryFrom, so unmappable dev-era strings won't leak through — matching the issue directive that v2 dev artifacts get dropped, not preserved with shims. Test coverage extends testWorkflowCanHandleActivityFailureAndContinue to assert (a) the payload's category matches the WorkflowFailure row and (b) it's a member of the FailureCategory enum. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 20de2c8 commit a2def5a

2 files changed

Lines changed: 36 additions & 0 deletions

File tree

src/V2/Support/WorkflowExecutor.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2947,10 +2947,12 @@ private function recordFailureHandled(
29472947
$exceptionType = is_string($failurePayload['exception_type'] ?? null)
29482948
? $failurePayload['exception_type']
29492949
: (is_string($exceptionPayload['type'] ?? null) ? $exceptionPayload['type'] : null);
2950+
$failureCategory = self::resolveFailureHandledCategory($failure, $failurePayload);
29502951

29512952
WorkflowHistoryEvent::record($run, HistoryEventType::FailureHandled, array_filter([
29522953
'failure_id' => $failureId,
29532954
'sequence' => $workflowSequence,
2955+
'failure_category' => $failureCategory,
29542956
'source_kind' => $failure?->source_kind
29552957
?? (is_string($failurePayload['source_kind'] ?? null) ? $failurePayload['source_kind'] : null),
29562958
'source_id' => $failure?->source_id
@@ -2968,6 +2970,34 @@ private function recordFailureHandled(
29682970
], static fn (mixed $value): bool => $value !== null), $task);
29692971
}
29702972

2973+
/**
2974+
* Resolve a canonical FailureCategory value for a FailureHandled event.
2975+
*
2976+
* The DB row is authoritative when available (it carries an enum-cast
2977+
* value); fall back to the failurePayload (which carries the same string
2978+
* for child propagation), then to the catch-all Application category so
2979+
* downstream consumers can rely on the field being present and a member
2980+
* of the FailureCategory enum.
2981+
*
2982+
* @param array<string, mixed> $failurePayload
2983+
*/
2984+
private static function resolveFailureHandledCategory(
2985+
?WorkflowFailure $failure,
2986+
array $failurePayload,
2987+
): string {
2988+
$modelCategory = $failure?->failure_category;
2989+
if ($modelCategory instanceof FailureCategory) {
2990+
return $modelCategory->value;
2991+
}
2992+
2993+
$payloadCategory = $failurePayload['failure_category'] ?? null;
2994+
if (is_string($payloadCategory) && FailureCategory::tryFrom($payloadCategory) !== null) {
2995+
return $payloadCategory;
2996+
}
2997+
2998+
return FailureCategory::Application->value;
2999+
}
3000+
29713001
private function startChildRetryIfAvailable(
29723002
WorkflowRun $parentRun,
29733003
int $sequence,

tests/Feature/V2/V2WorkflowTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4311,6 +4311,12 @@ public function testWorkflowCanHandleActivityFailureAndContinue(): void
43114311
$this->assertSame('activity_execution', $handledEvent->payload['source_kind'] ?? null);
43124312
$this->assertSame($failure->source_id, $handledEvent->payload['source_id'] ?? null);
43134313
$this->assertSame('activity', $handledEvent->payload['propagation_kind'] ?? null);
4314+
$this->assertSame($failure->failure_category->value, $handledEvent->payload['failure_category'] ?? null);
4315+
$this->assertTrue(in_array(
4316+
$handledEvent->payload['failure_category'] ?? null,
4317+
array_map(static fn ($c) => $c->value, FailureCategory::cases()),
4318+
true,
4319+
), 'FailureHandled payload must carry a known FailureCategory enum value.');
43144320
$this->assertTrue($handledEvent->payload['handled'] ?? false);
43154321
}
43164322

0 commit comments

Comments
 (0)