Repository navigation
Redrive runs whose workflow code upserted memos or search attributes - #1001
Conversation
…utes (#864) A2d of #864. Any MemoUpserted or SearchAttributesUpserted event in the completed prefix refused redrive with unsupported_history. - The redrive plan treats both upsert events as reusable steps, like side effects and version markers. The successor records them with reused_from_run_id and reused_recorded_at, and the executor, query replayer and fiber runner replay them at the original time. - Memo and search attribute rows carry to the successor unchanged, keeping upserted_at_sequence and inherited_from_parent, so the successor's rows match its reused history and a successor that fails again can be redriven again. - A row written by workflow code with no matching reused upsert step is still refused with unsupported_visibility_metadata. Adds feature coverage (single redrive and a redrive chain) and replay corpus evidence redrive-reused-upserts-clock. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> # Conflicts: # CHANGELOG.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Security full source review at Accepted source behavior:
Remaining merge gate:
This is source acceptance, not permission to merge |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Security exact-head readback of
This lifts the earlier release-cut refresh hold and grants merge clearance for this exact head. No clearance carries to a changed head. #1002 remains separately held/draft until it is refreshed after this merge and returns with its full exact-head evidence. |



A2d of #864.
Problem
Redrive refused any failed run whose workflow code called
upsertMemo()orupsertSearchAttributes()before the failed step. The plan sawMemoUpserted/SearchAttributesUpsertedin the completed prefix and refused withunsupported_history, and eligibility separately refused any memo or search attribute row withupserted_at_sequence > 0asunsupported_visibility_metadata.Related gap: A1 copied start-time rows to the successor as inherited rows at step 1, so a successor that failed again was refused for its own memos.
Change
task,command) stripped.reused_from_run_idandreused_recorded_at. A step reused twice keeps its first time.WorkflowExecutor,QueryStateReplayerandWorkflowFiberRunnersendRedriveReplayClock::recordedAt()for these events, sonow()after an upsert matches the source run.MemoUpsertService::copyForRedrive()/SearchAttributeUpsertService::copyForRedrive()copy rows unchanged, keepingupserted_at_sequenceandinherited_from_parent, so the successor's rows match its reused history. Continue-as-new still usesinheritFromParent().unsupported_visibility_metadata) only when workflow code wrote it (upserted_at_sequence > 0, not inherited) and no reused upsert step of that type has that sequence. The existing refusal test for a row with no history behind it still passes.docs/api-stability.md. Both events accept the two reuse keys. TheSearchAttributesUpsertedrow now also lists its existingattribute_typeskey.SDK replay parity for these keys follows the (C) issues General filed (sdk-python#232, sdk-php#240, sdk-rust#164). Native parity stays with server#980.
Tests
Local runs, PHP 8.4, SQLite + Redis:
V2FailedRunRedriveTest(13 tests), with two new tests:testRedriveReusesWorkflowCodeMemoAndSearchAttributeUpserts: the redrive happens 5 minutes later. The successor completes without rerunning the first activity, rows match the source exactly (values, sequence, inherited flag), reused payloads equal the source minus recording context, and workflow time after each upsert matches the source in the live run, export replay and stored-history replay.testARedriveSuccessorWithUpsertsCanBeRedrivenAgain: source → successor fails → second redrive completes, keeping the first run's time.DefaultWorkflowControlPlaneRedriveRejectionTest,FailedRunRedrivePlanTest,V2FailedRunRedrivePlanTest,HistoryEventPayloadContractTest,SurfaceStabilityContractTestandReplayRegressionCorpusTestpass. OneDefaultWorkflowControlPlaneRedriveRejectionTestcase fails locally on a cache-store difference; it fails the same way on main and passes in CI.redrive-reused-upserts-clockfixture plus a scenario test.validate-regression-corpus.py --base-ref <main>reports replaycounterfactual_fixture_paths: 1: it fails on main and passes here.ecsandphpstanare clean on the changed files.🤖 Generated with Claude Code