Repository navigation
Conversation
StartOptions gains timeoutCleanupSeconds / withTimeoutCleanup(), validated before any write: 1-86400 s, at least one hard timeout, and shorter than each hard timeout it reserves from. Both start paths record WorkflowStarted.timeout_cleanup {allowance_seconds, work_deadlines per kind}; the hard deadlines are unchanged. The engine does not act on it yet (steps 2-5 on this PR).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…p 2a) Per General's decision (A): workflow_runs.timeout_cleanup_work_deadline_at, nullable, no backfill, added outside the migrator transaction under the schema lock budget with the index built online (CONCURRENTLY / LOCK=NONE), re-runnable. Both WorkflowStub start paths set it to the earliest hard deadline minus the allowance. V2TimeoutCleanupMigrationTest covers the online-only shape and a populated, repeated run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#863) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…p 2b) The successor (executor and bridge) derives its work deadline from the inherited execution deadline and its own run deadline with the predecessor's admitted allowance, and records timeout_cleanup in its WorkflowStarted so the next continue-as-new reads it again. V2TimeoutCleanupAdmissionTest covers admission, no-option starts and continue-as-new keeping the execution work deadline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The redrive successor derives its work deadline from the inherited execution deadline and its new run deadline with the source's allowance, and its WorkflowStarted records the recomputed timeout_cleanup (absent when the source never opted in). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ep 3) CancellationContext gains the optional top-level cause (cancellation | timeout; absent means cancellation), emitted only for timeout so existing contexts stay byte-identical; descendants do not inherit it. WorkflowCancellationRequestedException is no longer final, and forContext() returns the new WorkflowTimeoutCleanupRequested subtype for a timeout context, used by the executor and the fiber runner, so existing cancellation handlers still catch it. Per General (SDK owner): top-level only, never inside requester. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… steps 2d and 4)
TimeoutCleanup::enter() (called from WorkflowExecutor::runTask under the run lock, once, between the work and hard deadlines) records a system RequestCancellation with cause timeout whose cleanup deadline is the earliest hard deadline, WorkflowTimeoutCleanupStarted and CooperativeCancellationRequested, so the existing delivery and cleanup-deadline enforcement apply. The watchdog wakes an idle opted-in run at timeout_cleanup_work_deadline_at.
A timeout-cause request always ends with WorkflowTimedOut (failure_category timeout) carrying timeout_cleanup {outcome, timeout_kind, started_at, cleanup_deadline_at, finished_at, failure_id}: completed when workflow code returns or rethrows, failed (cleanup failure recorded and linked) when it throws anything else, expired at the cleanup deadline. New HistoryEventType::WorkflowTimeoutCleanupStarted with its payload contract and timeline summary; CommandContext::system().
V2TimeoutCleanupTest: an idle run is woken at its work deadline and ends timed out with completed, failed and expired outcomes; without the option the hard timeout is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A hard cancel or terminate during timeout cleanup records timeout_cleanup.outcome preempted on WorkflowCancelled/WorkflowTerminated. CHANGELOG (run migrations) and api-stability list WorkflowTimeoutCleanupStarted and the timeout_cleanup keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
timeout-cleanup-outcome-recorded (completed, failed and expired outcomes end WorkflowTimedOut with timeout_cleanup), timeout-cleanup-continue-as-new-inheritance and timeout-cleanup-redrive-inheritance (successors keep the work deadline and record the allowance), per General's fixture conditions. Each consumer fails on base (no timeoutCleanupSeconds). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Per General's condition, the timed-out failure message (read by describe, run detail, history export and the failure list) and the timeline summary end with the cleanup outcome: completed, failed, did not finish before the hard deadline, or preempted, so a failed cleanup is never shown as a clean timeout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI (feature-postgresql) caught a configured custom run table without the new column: every run create wrote timeout_cleanup_work_deadline_at, even as null. Only a run that opted in writes it now, so custom run tables keep working until they migrate. V2ConfiguredCoreModelsTest passes locally. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From General's SDK port: (1) the context carries only cause plus the existing cleanup_deadline_at; timeout kind and work deadline stay on WorkflowTimeoutCleanupStarted (contract amended). (2) Scoped descendants no longer inherit the root's cause: scopedDescendantSnapshot drops it, so a scoped child of a timeout root is cancelled, not timed out, and its snapshot round-trips. (3) An explicit cause: cancellation is preserved on write-back so a context round-trips with exactly its keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Security: CHANGES REQUESTED at 5207d23. The core authority model is sound, but there's one integrity bug and several contract gaps. What passes:
Required:
Should fix:
|
Core settled the timeout-cleanup context shape (workflow#1129 at 14a4b944): the context carries only the optional top-level `cause` plus its existing cleanup_deadline_at. timeout_kind and work_deadline_at stay on WorkflowTimeoutCleanupStarted, so the context no longer reads or writes them. Port Core's scoped-descendant vector: a scoped child of a timeout root has no `cause` and is an ordinary cancellation, while its scope origin keeps the root's cause. Refs durable-workflow/workflow#863, durable-workflow/workflow#1129. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Workflow is driving the remaining Security findings on this PR. The current head Next actions, owned by Workflow:
The embedded implementation remains on this PR. Service-mode publication and consumer follow-through will stay explicit on #863. |
|
The remaining review corrections are implemented on the current PR branch. This remains draft while the complete quality cycle and new CI run finish.
Checks so far: MySQL 8.4.8 focused suite 28 tests / 135 assertions passed; ECS and PHPStan passed. On the preceding PR head |
|
Security re-review hold at The unit shard completed 6,035 tests / 81,923 assertions with two errors in Please make the abandonment probe require an explicitly terminal status while safely treating null/unhydrated status as not terminal (for example, an exact |
|
The two CI unit errors are fixed: abandonment now requires This is a one-line source correction after |
|
The corrected source's unit job passed in run Measured log evidence: eight completed official consumer runs each took 41.4–42.5 seconds, with 0.7 seconds to materialize each tree. Four new fixtures require one baseline plus two runs per fixture, so the final ninth counterfactual was still running at cancellation. The measured consumer work alone needs roughly 6.4 minutes before setup and cleanup. The CI correction raises this one bounded job budget to 12 minutes. It preserves the full baseline, isolated-head and counterfactual checks for every new fixture, with no skips or retry wrapper. Build-routing/YAML checks run before pushing; the complete required gate will rerun at the resulting exact head. Library source is unchanged from |
|
|
Security PASS for exact head The runtime/library/test/docs tree is unchanged from the corrected source head
The Security HOLD is lifted for this exact head. The PR remains draft, and the owner’s in-progress full local cycle remains an additional merge discipline; any source or head change requires counter-review. Parser/cleanup support remains subject to the already recorded Server #1201 capability and delivery gates. |



An opted-in embedded workflow now gets a durable cleanup turn before its existing hard timeout. The final outcome remains
WorkflowTimedOut, even when cleanup succeeds. Refs #863 and the agreed contract indocs/architecture/timeout-cleanup.md(#1122).StartOptions::timeoutCleanupSeconds/withTimeoutCleanup()reserves a finite part of each admitted hard timeout. The engine enters cleanup once under the run lock, recordsWorkflowTimeoutCleanupStarted, and deliversWorkflowTimeoutCleanupRequestedthrough cooperative cancellation. Detection, replacement and retries never renew the budget. The watchdog enforces the hard cap even when no worker executes the Ready task.workflow_runs.timeout_cleanup_work_deadline_atwithout backfill, using the lock budget and online index helpers.Validation at the review correction: focused MySQL 8.4.8 checks passed 28 tests / 135 assertions; four new expiry/replay-blocking controls fail on preceding head
14a4b944and pass with the fix. ECS/PHPStan and the replay corpus passed. The subsequent one-line null-status guard restores the existing deadline authority unit controls (2 tests / 8 assertions), with no test or coverage weakening.Full local unit/coverage/feature checks and required CI are running at the current head. This PR remains draft until they pass and Security confirms the exact head. Embedded publication alone does not complete #863's published cross-product acceptance.