Skip to content

4.15.0

Choose a tag to compare

@github-actions github-actions released this 13 Nov 10:01
· 96 commits to main since this release

Known inline fan-out limitation: 4.15.0 applies the ordinary request cap
(2 MiB by default) to the entire command batch during payload planning. On a
namespace without external payload storage, the published 300 × 10 KB fan-out
exits with external_payload_unavailable and leaves the run pending. Use
smaller command batches while SDK #254
and Server #1136 are
being corrected and qualified. This is separate from the over-limit HTTP 413
transport failure described below. Scoped published acceptance
passes ordinary lifecycle/deadline behavior and pending-to-completed-null
update reads; fan-out and over-limit acceptance remain open.

4.15.0

  • All workflow and run routes encode each identifier or name as one path
    segment. Empty identifiers and . or .. segments return
    Error::InvalidPathSegment before any HTTP request, including capability
    discovery for cooperative cancellation.

  • Cooperative cancellation now reads the optional top-level cause of the
    portable cancellation context (workflow#863, workflow#1129). A
    timeout-cleanup request (cause: "timeout": the run passed its work
    deadline and is now in cleanup) arrives through the existing
    Error::CooperativeCancellationRequested, so existing
    Error::CooperativeCancellationRequested(_) arms, such as saga
    compensation, run for it unchanged. There is no new Error variant. Tell
    the two apart with the new #[non_exhaustive] CancellationCause:
    Requested for an ordinary request (cause absent or "cancellation") or
    Timeout. The request's cleanup deadline is its existing
    cleanup_deadline_at. CancellationRequest::cause(), CancellationContext::cause() and is_timeout()
    read it from the context. A context with any other cause is refused as
    Error::InvalidCooperativeCancellation. The cause is never inherited: a
    descendant cancelled under the request, scoped or not, carries no cause
    and is an ordinary cancellation, even when its scope origin's root context
    carries cause: "timeout". CancellationContext::to_value() writes cause
    back exactly as read, including an explicit "cancellation", and an
    ordinary context's output is unchanged.

    CancellationRequest::cause() derives the discriminator from the existing
    context, preserving stable 4.x struct literals and exhaustive destructuring.
    This parser support is experimental. Service-mode timeout-cleanup admission
    and execution require the upstream Core/native implementation and separate
    published-artifact qualification.

  • A workflow-task completion that native Server 3.6.0 refuses as too large
    (HTTP 413, limit_kind: workflow_task_completion_bytes, server#1152,
    durable-workflow/.github#228) is now a typed, non-retryable outcome.
    Client::complete_workflow_task returns the new
    Error::WorkflowTaskCompletionTooLarge with the Server's limit, the encoded
    request size and the Server's message, and the request is never resent. A
    Worker no longer leaves the task to be redelivered into the same refusal:
    it completes the task once with a non-retryable fail_workflow command whose
    message starts with Structural limit exceeded:, so the run fails with
    failure category structural_limit and guidance to split the fan-out, use
    child workflows or continue as new. Other 413 and 5xx responses are
    unchanged. The README documents the completion size limit.

    Known transport limitation: this typed failure requires the Server's
    HTTP 413 to reach the worker. An aggregate over-limit request can currently
    lose that response and return a transport error instead. Automatic
    structural failure for that path is not yet qualified. The Server response
    correction and published-artifact controls remain open in
    the fan-out issue.

  • An update whose wait ends before its handler finishes now returns
    Error::UpdateStillPending instead of Ok(Null) (durable-workflow/.github#212).
    update_workflow used to return the Server's result: null for a 202
    wait_timed_out receipt, so a pending update looked like a handler that
    returned null. The error carries the update_id, request_id, current
    run_id, update_status and full receipt; a real null result is still
    Ok(Null). update_workflow_avro_value returns the same error instead of a
    missing_payload_envelope codec error.

  • Client::update_outcome, Client::update_outcome_avro_value and
    Client::update_outcome_receipt (and WorkflowHandle::update_outcome) read an
    accepted update's outcome later by its update id, without submitting it
    again (server#1131). They need a Server that advertises
    update_outcome_reads (native 3.6.0 and later) and return
    Error::UpdateOutcomeReadsUnavailable otherwise, before reading anything. A
    still-running update returns Error::UpdateStillPending with a hint to read
    again by update id. A failed update is Error::Http 422, and
    Error::is_update_not_found() detects 404 update_not_found. An empty, .
    or .. workflow id or update id is refused before any request, so a read
    can't collapse onto another route. A successful answer that isn't a receipt
    for the requested update (matching update_id and an update_status), or a
    non-completed receipt where an outcome was expected, returns
    Error::UnrecognizedUpdateOutcome instead of a value.

  • describe_workflow and update_workflow (and update_workflow_avro_value)
    percent-encode the workflow id, and the update name, as one path segment.
    Before, they were put into the path raw, so an id containing /, ? or #
    addressed a different route. An empty, . or .. id or update name now
    returns Error::InvalidPathSegment before any request; .. would otherwise
    have described or updated another route. Found in the security review of
    sdk-rust#243 (durable-workflow/.github#212).

  • A request_id_conflict refusal from native Server 3.9.0 (server#934,
    server#1191) now names the key that was sent. Native doesn't echo the key, so
    Error::request_id() returned None and RequestIdConflict::request_id was
    empty. The SDK now adds the sent key to the refusal body when the Server
    doesn't echo it. The error is still Error::Http with status 409, it is sent
    once and never resent, and RequestIdConflict documents native's conflict
    parts (name, arguments, target_or_arguments). The README describes
    request IDs and the command_request_keys capability. Older Servers are
    unaffected.

  • Replay ignores the activity cancellations a run's own close records
    (durable-workflow/server#1193, durable-workflow/workflow#1117). When a run
    completes, fails or continues as new, Workflow 2.28.0 (and Server with
    server#1193) records ActivityCancelled with cancellation_reason: run_closed
    for each activity it never awaited, such as a select() loser. Replay now
    leaves that activity open instead of treating the cancellation as an outcome:
    a handled all() failure is no longer replaced by a lower-index member
    cancelled at close. The new abandoned_activities key on WorkflowCompleted,
    WorkflowFailed and WorkflowContinuedAsNew is accepted. Activity workers
    already treat a run_closed heartbeat or late outcome (recorded: false) as
    final; new tests cover it, including an abandon-policy activity that keeps
    running after close.