Skip to content

Native describe: a failed run's terminal failure, as PHP reports it - #968

Merged
rmcdaniel merged 2 commits into
mainfrom
rust-967-describe-failure
Nov 3, 2026
Merged

rmcdaniel merged 2 commits into
mainfrom
rust-967-describe-failure

Conversation

@rmcdaniel

Copy link
Copy Markdown
Member

Review tier B: a new read-path projection on describe; no write path or tier-A area.

Fixes #967. Ships in the next native patch with #953 and #965, which Rust 4.10.3 and Cloud's native flip wait on.

Problem

Native 3.0.1's run describe omitted error, failure, exception and failures for a failed run, and a timed-out run's failure.reason. Installed Rust SDK 4.10.2 builds its terminal error from describe:

  • WorkflowFailed came back with a null exception type, message and non_retryable;
  • a run-deadline timeout came back with the generic reason timed_out.

History was correct throughout.

Change

rust/src/runtime/failure_describe.rs ports, with the PHP source named on each function:

  • Workflow Core 2.15.12's FailureSnapshots::forRun (the version 2.11.8 locks):
    • one snapshot per workflow_failures row (failure_row_fallback);
    • replaced by the typed history event that names its failure_id (typed_history), from these events: ActivityFailed/ActivityTimedOut, ChildRunFailed/Cancelled/Terminated, WorkflowFailed/TimedOut/Cancelled/Terminated, UpdateCompleted;
    • Core's field precedence and per-event defaults for source_kind, source_id, propagation_kind and failure_category;
    • reason from timeout_kind, then reason;
    • FailureHandled marking;
    • ordering by event sequence (nulls last), then creation time, then id.
  • WorkflowController's describe compaction: compactFailureSnapshots, activityFailureSummaries, terminalFailureSnapshot (the last workflow_run or terminal snapshot), failureExceptionPayload, lastActivityFailureMessage and withoutNullOrEmptyArrays.
  • The four fields close the run description in PHP's order: error, failure, exception, failures. A timed-out run is failed in both runtimes, so its failure carries reason: run_timeout or execution_timeout, failure_category: timeout and the timeout message. As in PHP there's no top-level reason; the Rust SDK reads failure.reason.

Checked

  • Unit tests:
    • Native describe omits failure/exception for failed runs and reason for timed-out runs #967's failed run (activity failure, then WorkflowFailed) gives PHP's failure excerpt from the issue: message, ActivityFailed, durable_workflow::ActivityFailure, application, non_retryable: true, event_sequence: 6, the exception triple, activity_failures and two failures;
    • a run-deadline timeout gives reason: run_timeout;
    • non-failed runs and failures-less runs get nothing.
  • HTTP scenario a_failed_runs_describe_carries_its_terminal_failure.
  • The full SQLite suite passes (676 execution, 407 lib). CI runs PostgreSQL and MySQL.

Limits

  • An event exception stored as an Avro string isn't decoded here. Native writes objects.
  • message_full isn't preferred over message for row-only fallbacks.

sdk-rust will rerun its installed harness (#155) against the patched image.

🤖 Generated with Claude Code

…967)

Native describe left out `error`, `failure`, `exception` and `failures` for
a failed run, and a timed-out run's `failure.reason` (`run_timeout` or
`execution_timeout`). Installed Rust SDK 4.10.2 builds its terminal error
from describe, so it surfaced null exception details and a generic timeout.

failure_describe.rs ports Workflow Core 2.15's FailureSnapshots::forRun:
- one snapshot per failure row;
- replaced by the typed history event naming it (ActivityFailed/TimedOut,
  ChildRun*, Workflow*, UpdateCompleted), with Core's field precedence and
  per-event defaults;
- FailureHandled marking;
- ordered by event sequence, creation time and id.
It then applies the controller's compaction: terminalFailureSnapshot,
failureExceptionPayload, activityFailureSummaries, and
withoutNullOrEmptyArrays. The four fields close the run description in
PHP's order. A timed-out run is failed, and its failure's reason is the
event's timeout_kind.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rmcdaniel

Copy link
Copy Markdown
Member Author

Security bounded review clears exact head 109e9d5abbb15687749a7b30e20c16ed6f06114c on protected main 1124d86a45af0b824b523d18292e22308767fd9f.

I compared the new projection directly with the exact locked Workflow Core 2.15.12 source 7c80ffccdb6ab6e6ace8339618a9d6f1d6d9432f and the published PHP WorkflowController compaction:

  • the supported typed-event set, failure-row fallback, event-over-row field precedence, event defaults, handled marking, and sequence/creation/id ordering match FailureSnapshots::forRun;
  • controller selection of the last run-level/terminal snapshot, activity-failure fallback message, exception type/class/message compaction, null/empty-list removal inside the failure object, and final top-level error, failure, exception, failures order match the PHP describe surface;
  • timeout reason comes from timeout_kind before reason, so run_timeout and execution_timeout remain distinguishable to installed SDKs;
  • the projection deliberately exposes only the already-published compact failure fields. It does not add trace, file, line, properties, or other diagnostic payloads to describe;
  • reads remain bound to the already namespace-authorized exact run ID and run inside a read snapshot. No write or retry authority is introduced.

The disclosed Avro-string exception limit does not weaken Native-authored histories, whose typed events store objects and mirror type/class/message in the event/failure row used by this projection. It remains an explicit historical-import limit, not evidence for a broader claim.

The exact head has one parent/current main. GitHub's tested synthetic merge is 2d04d8d176581912d4a67cebec996d21ea87be2d with parents exactly 1124d86a… + 109e9d5a…, and its tree equals the head. Hosted checks are terminal: 57 success + 1 intentional candidate-source skip, zero failures or pending checks, including the SQLite/PostgreSQL/MySQL matrices, PHPUnit/contracts, CodeQL, Sonar and public-boundary checks.

There is no Security hold on merging this exact current-base head. If another PR advances protected main first, this clearance becomes stale and requires the ordinary carry/readback. Published-image and installed Rust SDK validation remain separate release evidence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rmcdaniel

Copy link
Copy Markdown
Member Author

Security post-#957 carry review at exact ac06c2e0a9c270c03ae61ea93f942f43a2a47655: its parents are the previously reviewed 109e9d5abbb15687749a7b30e20c16ed6f06114c and exact merged main 9524e80b2f88e6282aacbcd51560a080302d2d33; the merge has no combined-diff conflict resolution. The reviewed PR delta is content-equivalent across the carry (stable patch ID 89605603237859f19815208fc7c097fee1db629c before and after). Source carry is clear; final merge clearance still waits terminal hosted checks on this exact head.

@sonarqubecloud

sonarqubecloud Bot commented Nov 2, 2026

Copy link
Copy Markdown

@rmcdaniel

Copy link
Copy Markdown
Member Author

Security final carry clearance: exact head ac06c2e0a9c270c03ae61ea93f942f43a2a47655 is terminal.

  • This is the previously source-cleared failed/timed-out run describe parity correction carried onto reviewed main 9524e80b; the stable patch identity was recorded in the carry review.
  • Fresh readback now shows all 58 reported hosted contexts terminal on that exact head: 57 success, 1 expected skip, 0 failed/cancelled/timed out.

Security final-clear is granted for #968 at ac06c2e0…, sequenced after #965 as General recorded. Preserve this exact head through the merge queue; any content change needs a fresh carry. This does not itself authorize a release dispatch.

@rmcdaniel
rmcdaniel added this pull request to the merge queue Nov 3, 2026
Merged via the queue into main with commit 0265411 Nov 3, 2026
58 checks passed
@rmcdaniel

Copy link
Copy Markdown
Member Author

Security post-merge receipt: #968 merged through the queue as 02654110766abc37ac944ddbb930394cb61b199c after its merge-group qualification completed with 44 successful fixture contexts plus the completed feature/policy/public-boundary checks and no bad context.

The generated commit has parents current protected main 6ea2d051… (#965) and exact reviewed head ac06c2e0…. Relative to the protected-main parent it changes exactly the four reviewed #968 paths with the same additions/deletions, and each of those four blob IDs is byte-identical to the reviewed head. There was no content-level merge drift. This closes #968's source/merge carry for 3.0.2; published-image acceptance remains with the eventual exact release tuple.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Native describe omits failure/exception for failed runs and reason for timed-out runs

1 participant