diff --git a/src/Dapr.Workflow.Grpc/Dapr.Workflow.Grpc.csproj b/src/Dapr.Workflow.Grpc/Dapr.Workflow.Grpc.csproj
index bfd5b76f2..679cede27 100644
--- a/src/Dapr.Workflow.Grpc/Dapr.Workflow.Grpc.csproj
+++ b/src/Dapr.Workflow.Grpc/Dapr.Workflow.Grpc.csproj
@@ -20,8 +20,13 @@
+
+
+
+
+
-
+
diff --git a/src/Dapr.Workflow.Grpc/attestation.proto b/src/Dapr.Workflow.Grpc/attestation.proto
new file mode 100644
index 000000000..dd3030597
--- /dev/null
+++ b/src/Dapr.Workflow.Grpc/attestation.proto
@@ -0,0 +1,292 @@
+/*
+Copyright 2026 The Dapr Authors
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+syntax = "proto3";
+
+option csharp_namespace = "Dapr.DurableTask.Protobuf";
+option java_package = "io.dapr.durabletask.implementation.protobuf";
+option go_package = "/api/protos";
+
+// ============================================================================
+// Cross-workflow completion attestations
+// ============================================================================
+//
+// An attestation is a compact, portable proof that a child workflow or
+// activity task executed a specific invocation, was executed by a specific
+// SPIFFE identity, reached a specific terminal state, and produced a
+// specific (input, output) pair. Attestations let sibling or downstream
+// workflows verify these claims without trusting the parent that forwarded
+// them.
+//
+// Each attestation is a (payload, signature) wrapper. The payload is a
+// deterministically serialized protobuf encoding of the inner ...Payload
+// message (same approach as HistorySignature in backend_service.proto),
+// produced once by the signer and thereafter treated as opaque bytes.
+// Receivers, storage layers, and verifiers never re-marshal the payload;
+// signatures are validated against the exact bytes the signer produced.
+// Cross-language interoperability does not depend on protobuf having a
+// universal canonical encoding (it does not): the bytes that get signed
+// are the bytes that get verified, and they are never reconstructed by
+// any peer. This makes the inner payload proto safely evolvable: old
+// attestations with older field sets remain verifiable because their
+// stored bytes are preserved.
+//
+// The signature covers sha256(payload). Algorithm is determined by the
+// signer certificate's key type (Ed25519, ECDSA, RSA), same rules as
+// HistorySignature.signature in backend_service.proto.
+
+// ============================================================================
+// Canonical byte serialization for ioDigest
+// ============================================================================
+//
+// ioDigest fields commit to an invocation's input and output. To stay stable
+// across protobuf versions, implementations, and languages, the bytes fed
+// into the digest are defined directly in terms of user-visible data — the
+// protobuf marshaler's output is never used.
+//
+// The digest is:
+// sha256( u64be(len(inputBytes)) || inputBytes ||
+// u64be(len(outputBytes)) || outputBytes )
+// where inputBytes and outputBytes come from the rules below. Length
+// prefixes are big-endian uint64 to prevent concatenation ambiguity (same
+// pattern as HistorySignature.eventsDigest).
+//
+// --- String normalization ---
+// All UTF-8 string bytes used below are first normalized to Unicode
+// Normalization Form C (NFC). Different-language SDKs (Go, .NET, Python,
+// Java, JS) may default to different Unicode normalization forms; NFC is
+// the web-standard canonical form and ensures that semantically equal
+// strings produce identical bytes regardless of which SDK emitted them.
+// Verifiers MUST NFC-normalize before hashing to compare against a
+// signer-produced digest.
+//
+// --- Input bytes (child workflows and activities) ---
+// Inputs are carried as google.protobuf.StringValue in the source event.
+// Canonical bytes:
+// wrapper unset: zero-length
+// wrapper set: nfc_utf8(string_value.value)
+// The StringValue envelope is not included — only the NFC-normalized
+// UTF-8 content of the value field.
+//
+// --- Output bytes, COMPLETED (child and activity) ---
+// Same rule as input: NFC-normalized UTF-8 bytes of the result
+// StringValue's value field, or zero-length if unset.
+//
+// --- Output bytes, FAILED (child and activity) ---
+// Spec-defined canonical serialization of TaskFailureDetails, independent
+// of protobuf wire format:
+//
+// u32be(len(errorType)) || nfc_utf8(errorType)
+// u32be(len(errorMessage)) || nfc_utf8(errorMessage)
+// u32be(len(stackTrace)) || nfc_utf8(stackTrace) // StringValue.value,
+// // zero-length if unset
+// u8(0 if innerFailure is unset, 1 if set)
+// if innerFailure is set:
+//
+//
+// Fields appear in this fixed order regardless of proto field numbers.
+// Unset string fields serialize as zero-length. No protobuf tags, varints,
+// or envelopes are emitted. The TaskFailureDetails.isNonRetriable field is
+// intentionally excluded — it is a framework retry-policy hint, not a
+// description of the failure, and committing to it would couple attestation
+// verification to retry-semantics evolution with no security benefit.
+//
+// The innerFailure recursion depth is capped (implementation-defined, at
+// least 32 levels). Chains exceeding the cap are treated as malformed:
+// the signer refuses to produce an attestation and the verifier refuses
+// to verify one. This bounds the work performed on attacker-controlled
+// input.
+//
+// --- Versioning ---
+// The canonicalSpecVersion field on each payload identifies which revision
+// of the rules above produced the attestation's ioDigest. Verifiers that
+// don't recognize the value reject the attestation rather than risk a
+// silent digest mismatch. Current value: 1. When TaskFailureDetails or
+// another canonicalized input grows an attestation-relevant field, the
+// spec is revised and canonicalSpecVersion is incremented — the rules
+// above are never silently changed.
+//
+// --- Certificate validity ---
+// Verifiers check that the signer certificate is valid at a specific
+// point in time. The correct choice of time depends on whether the
+// attestation is being verified at ingestion or from stored history:
+//
+// * Ingestion (parent absorbs an inbound attestation from a child/
+// activity): use a trusted wallclock (time.Now()). The enclosing
+// HistoryEvent.timestamp is set by the sender and is not yet covered
+// by a signature at this point, so it cannot be trusted. Wallclock
+// gives "is the cert still valid right now" which is the right
+// freshness guarantee at the trust boundary.
+//
+// * Stored / propagated history (re-verification after the enclosing
+// event has been signed): use the enclosing HistoryEvent.timestamp.
+// Once the event is covered by a HistorySignature, the timestamp is
+// tamper-evident and provides a stable historical point-in-time for
+// cert validity — the cert was valid at signing time, even if it has
+// since expired. This mirrors how HistorySignature itself is checked
+// against the last event in its signed range.
+
+// Terminal state of a child workflow at the moment of attestation.
+enum TerminalStatus {
+ TERMINAL_STATUS_UNSPECIFIED = 0;
+ TERMINAL_STATUS_COMPLETED = 1;
+ TERMINAL_STATUS_FAILED = 2;
+}
+
+// Terminal state of an activity task at the moment of attestation.
+// Activities have no "terminate" operation, so the space is smaller than
+// TerminalStatus.
+enum ActivityTerminalStatus {
+ ACTIVITY_TERMINAL_STATUS_UNSPECIFIED = 0;
+ ACTIVITY_TERMINAL_STATUS_COMPLETED = 1;
+ ACTIVITY_TERMINAL_STATUS_FAILED = 2;
+}
+
+// Inner signed payload for a child workflow completion attestation. The
+// deterministically serialized form of this message is what the signer
+// signs over and what receivers verify against; the bytes are produced
+// once and never re-marshaled.
+message ChildCompletionAttestationPayload {
+ // Parent workflow instance ID. Binds the attestation to a single parent
+ // run, preventing replay by other instances of the same parent workflow
+ // that share a signing key.
+ string parentInstanceId = 1;
+
+ // taskScheduledId from the parent's ChildWorkflowInstanceCreatedEvent.
+ // Unique within the parent instance; distinguishes multiple invocations
+ // of the same child workflow.
+ int32 parentTaskScheduledId = 2;
+
+ // sha256 commitment to this invocation's input and output. The bytes
+ // fed into the digest are produced by the canonical byte serialization
+ // spec at the top of this file — not by any protobuf marshaler — so
+ // the digest is stable across proto versions, implementations, and
+ // languages. Use terminalStatus to select the output serialization rule
+ // (COMPLETED or FAILED).
+ bytes ioDigest = 3;
+
+ // sha256 of the DER-encoded X.509 certificate chain bytes of the
+ // signer (leaf first, intermediates concatenated; same byte format
+ // as the `certificate` field of SigningCertificate). Computed directly
+ // over the DER bytes rather than any protobuf envelope, so the digest
+ // is stable across protobuf version changes. The certificate itself
+ // is carried as a companion field on the enclosing event on first
+ // delivery and stored once in the receiver's external certificate
+ // table (ext-sigcert-NNNNNN), looked up by this digest.
+ bytes signerCertDigest = 4;
+
+ // Terminal state of the child workflow at the moment of attestation.
+ // Signed so that a verifier reading the attestation from propagated
+ // history can tell whether the child succeeded without relying on the
+ // enclosing event type (Completed vs Failed), which may not be
+ // visible or trustworthy when the attestation is inspected in
+ // isolation.
+ TerminalStatus terminalStatus = 5;
+
+ // Version of the canonical byte serialization spec used to compute
+ // ioDigest. See the "Versioning" section of the spec block at the top
+ // of this file. Verifiers that don't recognize the value reject the
+ // attestation rather than risk a silent digest mismatch. Current
+ // value: 1.
+ uint32 canonicalSpecVersion = 6;
+}
+
+// Signed wrapper around ChildCompletionAttestationPayload.
+message ChildCompletionAttestation {
+ // Deterministically serialized form of ChildCompletionAttestationPayload
+ // produced once by the signer. Opaque bytes thereafter; receivers,
+ // storage layers, and verifiers never re-marshal.
+ bytes payload = 1;
+
+ // Cryptographic signature over sha256(payload) using the private key
+ // corresponding to the certificate whose digest is in the payload's
+ // signerCertDigest field. Signature format follows the same rules as
+ // HistorySignature.signature.
+ bytes signature = 2;
+}
+
+// Inner signed payload for an activity completion attestation. Activities
+// have no signed history chain of their own (unlike child workflows), so
+// there is no finalSignatureDigest field. Activity identity is the hosting
+// app's SPIFFE identity; a compromised app can attest only to activities
+// it hosts, not to activities hosted on other apps.
+message ActivityCompletionAttestationPayload {
+ // Parent workflow instance ID that scheduled the activity.
+ string parentInstanceId = 1;
+
+ // taskScheduledId from the parent's TaskScheduledEvent. Unique within
+ // the parent instance.
+ int32 parentTaskScheduledId = 2;
+
+ // Activity name from the parent's TaskScheduledEvent. Explicit because
+ // no separate creation event binds it in the parent's history the way
+ // ChildWorkflowInstanceCreatedEvent does for child workflows.
+ string activityName = 3;
+
+ // sha256 commitment to this invocation's input and output. See the
+ // canonical byte serialization spec at the top of this file. Use
+ // terminalStatus (ACTIVITY_TERMINAL_STATUS_COMPLETED or _FAILED) to
+ // select the output serialization rule.
+ bytes ioDigest = 4;
+
+ // sha256 of the DER-encoded X.509 certificate chain bytes of the
+ // activity executor's signer. Same semantics and storage behavior as
+ // ChildCompletionAttestationPayload.signerCertDigest.
+ bytes signerCertDigest = 5;
+
+ // Terminal state of the activity at the moment of attestation.
+ ActivityTerminalStatus terminalStatus = 6;
+
+ // Version of the canonical byte serialization spec used to compute
+ // ioDigest. See the "Versioning" section of the spec block at the top
+ // of this file. Verifiers that don't recognize the value reject the
+ // attestation rather than risk a silent digest mismatch. Current
+ // value: 1.
+ uint32 canonicalSpecVersion = 7;
+}
+
+// Signed wrapper around ActivityCompletionAttestationPayload.
+message ActivityCompletionAttestation {
+ // Deterministically serialized form of
+ // ActivityCompletionAttestationPayload produced once by the signer.
+ // Opaque bytes thereafter; receivers, storage layers, and verifiers
+ // never re-marshal.
+ bytes payload = 1;
+
+ // Cryptographic signature over sha256(payload).
+ bytes signature = 2;
+}
+
+// A foreign signer's X.509 certificate; one belonging to another workflow
+// instance or activity executor whose attestations this workflow has
+// received. Stored once per unique digest and referenced by digest from
+// any attestation embedded in history. Stored as individual actor state
+// keys: ext-sigcert-000000, ext-sigcert-000001, etc.
+//
+// Lifecycle mirrors SigningCertificate (monotonically appended within a
+// run, cleared on ContinueAsNew and instance purge, tracked by
+// BackendWorkflowStateMetadata.externalSigningCertificateLength). Dedup
+// within a run is performed by in-memory digest→index lookup built at
+// load time.
+message ExternalSigningCertificate {
+ // sha256 of the DER-encoded X.509 certificate chain bytes (the value
+ // in `certificate` below). Also the primary lookup key used by
+ // attestations' signerCertDigest fields. Stored explicitly so
+ // load-time index construction and post-load integrity checks do not
+ // have to re-hash every entry.
+ bytes digest = 1;
+
+ // Same byte format as SigningCertificate.certificate: DER-encoded
+ // X.509 chain, leaf first, intermediates concatenated.
+ bytes certificate = 2;
+}
\ No newline at end of file
diff --git a/src/Dapr.Workflow.Grpc/backend_service.proto b/src/Dapr.Workflow.Grpc/backend_service.proto
new file mode 100644
index 000000000..f3676e330
--- /dev/null
+++ b/src/Dapr.Workflow.Grpc/backend_service.proto
@@ -0,0 +1,193 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+syntax = "proto3";
+
+package durabletask.protos.backend.v1;
+
+option csharp_namespace = "Dapr.DurableTask.Protobuf";
+option java_package = "io.dapr.durabletask.implementation.protobuf";
+option go_package = "/api/protos";
+
+import "orchestration.proto";
+import "history_events.proto";
+
+import "google/protobuf/timestamp.proto";
+import "google/protobuf/wrappers.proto";
+
+// Request payload for adding new workflow events.
+message AddEventRequest {
+ // The ID of the workflow to send an event to.
+ WorkflowInstance instance = 1;
+ // The event to send to the workflow.
+ HistoryEvent event = 2;
+}
+
+// Response payload for adding new workflow events.
+message AddEventResponse {
+ // No fields
+}
+
+// Request payload for completing an activity work item.
+message CompleteActivityWorkItemRequest {
+ // The completion token that was provided when the work item was fetched.
+ string completionToken = 1;
+
+ // The response event that will be sent to the workflow.
+ // This must be either a TaskCompleted event or a TaskFailed event.
+ HistoryEvent responseEvent = 2;
+}
+
+// Response payload for completing an activity work item.
+message CompleteActivityWorkItemResponse {
+ // No fields
+}
+
+// Request payload for completing a workflow work item.
+message CompleteWorkflowWorkItemRequest {
+ // The completion token that was provided when the work item was fetched.
+ string completionToken = 1;
+ WorkflowInstance instance = 2;
+ OrchestrationStatus runtimeStatus = 3;
+ google.protobuf.StringValue customStatus = 4;
+ repeated HistoryEvent newHistory = 5;
+ repeated HistoryEvent newTasks = 6;
+ repeated HistoryEvent newTimers = 7;
+ repeated WorkflowMessage newMessages = 8;
+
+ // The number of work item events that were processed by the workflow.
+ // This field is optional. If not set, the service should assume that the workflow processed all events.
+ google.protobuf.Int32Value numEventsProcessed = 9;
+}
+
+// Response payload for completing a workflow work item.
+message CompleteWorkflowWorkItemResponse {
+ // No fields
+}
+
+// A message to be delivered to a workflow by the backend.
+message WorkflowMessage {
+ // The ID of the workflow instance to receive the message.
+ WorkflowInstance instance = 1;
+ // The event payload to be received by the target workflow.
+ HistoryEvent event = 2;
+}
+
+message BackendWorkflowState {
+ repeated HistoryEvent inbox = 1;
+ repeated HistoryEvent history = 2;
+ google.protobuf.StringValue customStatus = 3;
+ uint64 generation = 4;
+}
+
+// ActivityInvocation wraps a TaskScheduled HistoryEvent with optional
+// propagated history for delivery to an activity actor.
+message ActivityInvocation {
+ HistoryEvent historyEvent = 1;
+
+ // Propagated history from the calling workflow.
+ optional PropagatedHistory propagatedHistory = 2;
+}
+
+message CreateWorkflowInstanceRequest {
+ HistoryEvent startEvent = 1;
+ reserved 2;
+ reserved "policy";
+
+ // Propagated history from the parent workflow.
+ optional PropagatedHistory propagatedHistory = 3;
+}
+
+message WorkflowMetadata {
+ string instanceId = 1;
+ string name = 2;
+ OrchestrationStatus runtimeStatus = 3;
+ google.protobuf.Timestamp createdAt = 4;
+ google.protobuf.Timestamp lastUpdatedAt = 5;
+ google.protobuf.StringValue input = 6;
+ google.protobuf.StringValue output = 7;
+ google.protobuf.StringValue customStatus = 8;
+ TaskFailureDetails failureDetails = 9;
+ google.protobuf.Timestamp completedAt = 10;
+ string parentInstanceId = 11;
+ optional google.protobuf.StringValue version = 12;
+ optional google.protobuf.StringValue parentAppId = 13;
+ optional google.protobuf.Timestamp startedAt = 14;
+}
+
+message BackendWorkflowStateMetadata {
+ uint64 inboxLength = 1;
+ uint64 historyLength = 2;
+ uint64 generation = 3;
+
+ // Number of HistorySignature entries stored (signature-NNNNNN keys).
+ uint64 signatureLength = 4;
+
+ // Number of SigningCertificate entries stored (sigcert-NNNNNN keys).
+ uint64 signingCertificateLength = 5;
+
+ // Number of ExternalSigningCertificate entries stored
+ // (ext-sigcert-NNNNNN keys). Same lifecycle as signingCertificateLength:
+ // monotonically grows within a run as new foreign signer certificates
+ // are absorbed from incoming attestations, zeroed on ContinueAsNew,
+ // cleared on instance purge. Subject to the same maxStateEntries
+ // tampering bound.
+ uint64 externalSigningCertificateLength = 6;
+}
+
+// A signing identity's X.509 certificate, stored once and referenced by index
+// from HistorySignature entries. This avoids duplicating the certificate
+// across every signature from the same identity. Stored as individual actor
+// state keys: sigcert-000000, sigcert-000001, etc.
+message SigningCertificate {
+ // X.509 certificate chain of the signing identity. Certificates are
+ // DER-encoded and concatenated directly in order: leaf first, followed by
+ // intermediates. Each certificate is a self-delimiting ASN.1 SEQUENCE,
+ // so the chain can be parsed by reading consecutive DER structures.
+ bytes certificate = 1;
+}
+
+// Signing metadata for a contiguous range of history events.
+// This is metadata-only — it does NOT contain the events themselves.
+// Events are stored once in history-NNNNNN keys; this message references them
+// by index range and stores only the signing artifacts.
+// Stored as individual actor state keys: signature-000000, signature-000001,
+// etc.
+message HistorySignature {
+ // Index of the first event covered by this signature (inclusive).
+ uint64 startEventIndex = 1;
+
+ // Number of events covered by this signature.
+ uint64 eventCount = 2;
+
+ // SHA-256 digest of the previous HistorySignature message (the entire
+ // deterministically serialized protobuf message). Absent for the first
+ // signature in the chain (no predecessor). When computing the signature
+ // input for the root case, this value is treated as empty (zero-length).
+ optional bytes previousSignatureDigest = 3;
+
+ // SHA-256 digest over the concatenation of the raw serialized bytes of each
+ // history event in this range, in order. The bytes are the exact values
+ // persisted to the state store (one per history-NNNNNN key).
+ bytes eventsDigest = 4;
+
+ // Index into the SigningCertificate table (sigcert-NNNNNN keys).
+ // Multiple signatures from the same identity share the same index.
+ // A new entry is appended only when the certificate rotates.
+ uint64 certificateIndex = 5;
+
+ // Cryptographic signature over
+ // SHA-256(previousSignatureDigest || eventsDigest)
+ // using the private key corresponding to the referenced certificate.
+ // The algorithm is determined by the certificate's key type:
+ // Ed25519: raw Ed25519 signature over the input bytes
+ // ECDSA: fixed-size r||s over SHA-256(input), each component
+ // zero-padded to the curve byte length
+ // RSA: PKCS#1 v1.5 with SHA-256
+ bytes signature = 6;
+}
+
+message DurableTimer {
+ HistoryEvent timerEvent = 1;
+ uint64 generation = 2;
+}
\ No newline at end of file
diff --git a/src/Dapr.Workflow.Grpc/history_events.proto b/src/Dapr.Workflow.Grpc/history_events.proto
new file mode 100644
index 000000000..59ae017df
--- /dev/null
+++ b/src/Dapr.Workflow.Grpc/history_events.proto
@@ -0,0 +1,304 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+syntax = "proto3";
+
+option csharp_namespace = "Dapr.DurableTask.Protobuf";
+option java_package = "io.dapr.durabletask.implementation.protobuf";
+option go_package = "/api/protos";
+
+import "orchestration.proto";
+import "attestation.proto";
+
+import "google/protobuf/timestamp.proto";
+import "google/protobuf/wrappers.proto";
+
+message ExecutionStartedEvent {
+ string name = 1;
+ google.protobuf.StringValue version = 2;
+ google.protobuf.StringValue input = 3;
+ WorkflowInstance workflowInstance = 4;
+ ParentInstanceInfo parentInstance = 5;
+ google.protobuf.Timestamp scheduledStartTimestamp = 6;
+ TraceContext parentTraceContext = 7;
+ google.protobuf.StringValue workflowSpanID = 8;
+ map tags = 9;
+}
+
+message ExecutionCompletedEvent {
+ OrchestrationStatus workflowStatus = 1;
+ google.protobuf.StringValue result = 2;
+ TaskFailureDetails failureDetails = 3;
+}
+
+message ExecutionTerminatedEvent {
+ google.protobuf.StringValue input = 1;
+ bool recurse = 2;
+}
+
+message TaskScheduledEvent {
+ string name = 1;
+ google.protobuf.StringValue version = 2;
+ google.protobuf.StringValue input = 3;
+ TraceContext parentTraceContext = 4;
+ string taskExecutionId = 5;
+
+ // If defined, indicates that this task was the starting point of a new
+ // workflow execution as the result of a rerun operation.
+ optional RerunParentInstanceInfo rerunParentInstanceInfo = 6;
+
+ // History propagation scope used when this task was originally scheduled.
+ // Persisted on the event so rerun can re-issue the task with the same
+ // scope after the action has been discarded.
+ optional HistoryPropagationScope historyPropagationScope = 7;
+}
+
+message TaskCompletedEvent {
+ int32 taskScheduledId = 1;
+ google.protobuf.StringValue result = 2;
+ string taskExecutionId = 3;
+
+ // Attestation signed by the activity executor's SPIFFE identity.
+ // Present when the activity was executed under a signing-enabled
+ // configuration. Verified on inbox ingestion against the companion
+ // signerCertificate and preserved in stored history for future audit
+ // and forwarding via provenance bundles.
+ optional ActivityCompletionAttestation attestation = 4;
+
+ // Companion: DER-encoded X.509 certificate chain of the executor's
+ // signing identity (leaf first, intermediates concatenated; same
+ // format as SigningCertificate.certificate in backend_service.proto).
+ // Wire-only; stripped by the receiver before the event is written to
+ // history-NNNNNN. The certificate lives once in ext-sigcert-NNNNNN,
+ // referenced by attestation payload's signerCertDigest.
+ optional bytes signerCertificate = 5;
+}
+
+message TaskFailedEvent {
+ int32 taskScheduledId = 1;
+ TaskFailureDetails failureDetails = 2;
+ string taskExecutionId = 3;
+
+ // See TaskCompletedEvent.attestation.
+ optional ActivityCompletionAttestation attestation = 4;
+
+ // Wire-only companion; see TaskCompletedEvent.signerCertificate.
+ optional bytes signerCertificate = 5;
+}
+
+message ChildWorkflowInstanceCreatedEvent {
+ string instanceId = 1;
+ string name = 2;
+ google.protobuf.StringValue version = 3;
+ google.protobuf.StringValue input = 4;
+ TraceContext parentTraceContext = 5;
+
+ // If defined, indicates that this task was the starting point of a new
+ // workflow execution as the result of a rerun operation.
+ optional RerunParentInstanceInfo rerunParentInstanceInfo = 6;
+
+ // History propagation scope used when this child workflow was originally
+ // scheduled. Persisted on the event so rerun can re-issue the child with
+ // the same scope after the action has been discarded.
+ optional HistoryPropagationScope historyPropagationScope = 7;
+}
+
+message ChildWorkflowInstanceCompletedEvent {
+ int32 taskScheduledId = 1;
+ google.protobuf.StringValue result = 2;
+
+ // Attestation signed by the completing child workflow's SPIFFE
+ // identity. Present when the child was executed under a signing-enabled
+ // configuration. Verified on inbox ingestion against the companion
+ // signerCertificate and preserved in stored history for future audit
+ // and forwarding via provenance bundles.
+ optional ChildCompletionAttestation attestation = 3;
+
+ // Companion: DER-encoded X.509 certificate chain of the child's signing
+ // identity (leaf first, intermediates concatenated; same format as
+ // SigningCertificate.certificate in backend_service.proto). Wire-only;
+ // stripped by the receiver before the event is written to
+ // history-NNNNNN. The certificate lives once in ext-sigcert-NNNNNN,
+ // referenced by attestation payload's signerCertDigest.
+ optional bytes signerCertificate = 4;
+}
+
+message ChildWorkflowInstanceFailedEvent {
+ int32 taskScheduledId = 1;
+ TaskFailureDetails failureDetails = 2;
+
+ // See ChildWorkflowInstanceCompletedEvent.attestation.
+ optional ChildCompletionAttestation attestation = 3;
+
+ // Wire-only companion; see
+ // ChildWorkflowInstanceCompletedEvent.signerCertificate.
+ optional bytes signerCertificate = 4;
+}
+
+// DetachedWorkflowInstanceCreatedEvent records that a running workflow
+// created a new, detached workflow instance via CreateDetachedWorkflowAction.
+// The new workflow has no parent linkage (no completion or failure flows
+// back), so this event only stores a pointer to the spawned instance — the
+// inputs themselves are consumed directly from the action when scheduling.
+// Replay matches on instanceId, so it is the same value the action carried.
+message DetachedWorkflowInstanceCreatedEvent {
+ string instanceId = 1;
+}
+
+// Indicates the timer was created by a createTimer call with no special origin.
+message TimerOriginCreateTimer {}
+
+// Indicates the timer was created as a timeout for a waitForExternalEvent call.
+message TimerOriginExternalEvent {
+ // The name of the external event being waited on, matching EventRaisedEvent.name.
+ string name = 1;
+}
+
+// Indicates the timer was created as a retry delay for an activity execution.
+message TimerOriginActivityRetry {
+ // The task execution ID of the activity being retried.
+ string taskExecutionId = 1;
+}
+
+// Indicates the timer was created as a retry delay for a child workflow execution.
+message TimerOriginChildWorkflowRetry {
+ // The instance ID of the workflow being retried.
+ string instanceId = 1;
+}
+
+message TimerCreatedEvent {
+ google.protobuf.Timestamp fireAt = 1;
+ optional string name = 2;
+
+ // If defined, indicates that this task was the starting point of a new
+ // workflow execution as the result of a rerun operation.
+ optional RerunParentInstanceInfo rerunParentInstanceInfo = 3;
+
+ // If set, provides additional context attached to this timer.
+ oneof origin {
+ TimerOriginCreateTimer createTimer = 4;
+ TimerOriginExternalEvent externalEvent = 5;
+ TimerOriginActivityRetry activityRetry = 6;
+ TimerOriginChildWorkflowRetry childWorkflowRetry = 7;
+ }
+}
+
+message TimerFiredEvent {
+ google.protobuf.Timestamp fireAt = 1;
+ int32 timerId = 2;
+}
+
+message WorkflowStartedEvent {
+ optional WorkflowVersion version = 1;
+}
+
+message WorkflowCompletedEvent {
+ // No payload data
+}
+
+message EventSentEvent {
+ string instanceId = 1;
+ string name = 2;
+ google.protobuf.StringValue input = 3;
+}
+
+message EventRaisedEvent {
+ string name = 1;
+ google.protobuf.StringValue input = 2;
+}
+
+message ContinueAsNewEvent {
+ google.protobuf.StringValue input = 1;
+}
+
+message ExecutionSuspendedEvent {
+ google.protobuf.StringValue input = 1;
+}
+
+message ExecutionResumedEvent {
+ google.protobuf.StringValue input = 1;
+}
+
+message ExecutionStalledEvent {
+ StalledReason reason = 1;
+ optional string description = 2;
+}
+
+message HistoryEvent {
+ int32 eventId = 1;
+ google.protobuf.Timestamp timestamp = 2;
+ oneof eventType {
+ ExecutionStartedEvent executionStarted = 3;
+ ExecutionCompletedEvent executionCompleted = 4;
+ ExecutionTerminatedEvent executionTerminated = 5;
+ TaskScheduledEvent taskScheduled = 6;
+ TaskCompletedEvent taskCompleted = 7;
+ TaskFailedEvent taskFailed = 8;
+ ChildWorkflowInstanceCreatedEvent childWorkflowInstanceCreated = 9;
+ ChildWorkflowInstanceCompletedEvent childWorkflowInstanceCompleted = 10;
+ ChildWorkflowInstanceFailedEvent childWorkflowInstanceFailed = 11;
+ TimerCreatedEvent timerCreated = 12;
+ TimerFiredEvent timerFired = 13;
+ WorkflowStartedEvent workflowStarted = 14;
+ WorkflowCompletedEvent workflowCompleted = 15;
+ EventSentEvent eventSent = 16;
+ EventRaisedEvent eventRaised = 17;
+ ContinueAsNewEvent continueAsNew = 20;
+ ExecutionSuspendedEvent executionSuspended = 21;
+ ExecutionResumedEvent executionResumed = 22;
+ ExecutionStalledEvent executionStalled = 31;
+ DetachedWorkflowInstanceCreatedEvent detachedWorkflowInstanceCreated = 32;
+ }
+ reserved 18, 19, 23, 24, 25, 26, 27, 28, 29;
+ optional TaskRouter router = 30;
+}
+
+// A self-contained range of events produced by a single app, used when
+// history from multiple workflows is propagated to a downstream workflow
+// or activity. Each chunk owns the raw event bytes its producer signed;
+// receivers digest those bytes directly and decode them into typed
+// HistoryEvents on demand.
+message PropagatedHistoryChunk {
+ // Raw deterministic bytes of each HistoryEvent in this chunk, in execution
+ // order. The producer marshals each event once and signs over these exact
+ // bytes; receivers digest them directly and never re-marshal, so chunk
+ // verification is independent of protobuf marshaler-version stability
+ // across producer and receiver. This mirrors the approach attestations use
+ // for ioDigest: signed bytes travel verbatim end-to-end. The chunk's
+ // length is len(rawEvents).
+ repeated bytes rawEvents = 1;
+
+ string appId = 2;
+
+ // The workflow instance ID/name that produced the events in this chunk.
+ string instanceId = 3;
+ string workflowName = 4;
+
+ // Raw deterministic bytes of each HistorySignature message produced by the
+ // chunk's app at dispatch time, covering rawEvents in order. Receivers
+ // unmarshal these on demand to verify the chain. Raw bytes are required
+ // because HistorySignature.previousSignatureDigest commits to the exact
+ // persisted serialization; re-marshaling on the wire would break chain
+ // linkage. See backend_service.proto: HistorySignature.
+ repeated bytes rawSignatures = 5;
+
+ // X.509 certificate chains of the chunk app's signing identities,
+ // DER-concatenated leaf-first then intermediates (same encoding as
+ // backend_service.proto: SigningCertificate.certificate). Each
+ // HistorySignature in rawSignatures has a certificateIndex that indexes
+ // into this list, scoped to the chunk's producer app. Raw bytes here avoid
+ // a circular import on backend_service.proto's SigningCertificate type.
+ repeated bytes signingCertChains = 6;
+}
+
+message PropagatedHistory {
+ // The propagation scope that was used to produce this history.
+ HistoryPropagationScope scope = 1;
+
+ // Per-app history chunks. Each chunk owns the raw event bytes its producer
+ // signed (PropagatedHistoryChunk.rawEvents); receivers digest those bytes
+ // directly and decode them into typed HistoryEvents on demand. Chunks are
+ // ordered, non-overlapping, and together describe the full propagated
+ // event sequence.
+ repeated PropagatedHistoryChunk chunks = 2;
+}
\ No newline at end of file
diff --git a/src/Dapr.Workflow.Grpc/orchestration.proto b/src/Dapr.Workflow.Grpc/orchestration.proto
new file mode 100644
index 000000000..c8f954bf0
--- /dev/null
+++ b/src/Dapr.Workflow.Grpc/orchestration.proto
@@ -0,0 +1,115 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+syntax = "proto3";
+
+option csharp_namespace = "Dapr.DurableTask.Protobuf";
+option java_package = "io.dapr.durabletask.implementation.protobuf";
+option go_package = "/api/protos";
+
+import "google/protobuf/timestamp.proto";
+import "google/protobuf/wrappers.proto";
+
+enum StalledReason {
+ PATCH_MISMATCH = 0;
+ VERSION_NOT_AVAILABLE = 1;
+ PAYLOAD_SIZE_EXCEEDED = 2;
+}
+
+enum OrchestrationStatus {
+ ORCHESTRATION_STATUS_RUNNING = 0;
+ ORCHESTRATION_STATUS_COMPLETED = 1;
+ ORCHESTRATION_STATUS_CONTINUED_AS_NEW = 2;
+ ORCHESTRATION_STATUS_FAILED = 3;
+ ORCHESTRATION_STATUS_CANCELED = 4;
+ ORCHESTRATION_STATUS_TERMINATED = 5;
+ ORCHESTRATION_STATUS_PENDING = 6;
+ ORCHESTRATION_STATUS_SUSPENDED = 7;
+ ORCHESTRATION_STATUS_STALLED = 8;
+}
+
+message TaskRouter {
+ string sourceAppID = 1;
+ optional string targetAppID = 2;
+ optional string targetAppNamespace = 3;
+}
+
+message WorkflowVersion {
+ repeated string patches = 1;
+
+ // The name of the executed workflow
+ optional string name = 2;
+}
+
+message WorkflowInstance {
+ string instanceId = 1;
+ google.protobuf.StringValue executionId = 2;
+}
+
+message TaskFailureDetails {
+ string errorType = 1;
+ string errorMessage = 2;
+ google.protobuf.StringValue stackTrace = 3;
+ TaskFailureDetails innerFailure = 4;
+ bool isNonRetriable = 5;
+}
+
+message ParentInstanceInfo {
+ int32 taskScheduledId = 1;
+ google.protobuf.StringValue name = 2;
+ google.protobuf.StringValue version = 3;
+ WorkflowInstance workflowInstance = 4;
+ optional string appID = 5;
+ optional string appNamespace = 6;
+}
+
+// RerunParentInstanceInfo is used to indicate that this workflow was
+// started as part of a rerun operation. Contains information about the parent
+// workflow instance which was rerun.
+message RerunParentInstanceInfo {
+ // instanceID is the workflow instance ID this workflow has been
+ // rerun from.
+ string instanceID = 1;
+}
+
+message TraceContext {
+ string traceParent = 1;
+ string spanID = 2 [deprecated=true];
+ google.protobuf.StringValue traceState = 3;
+}
+
+// HistoryPropagationScope controls how history is propagated to a child
+// workflow or activity
+enum HistoryPropagationScope {
+ // No propagation. This is the default for an unset/missing field; the
+ // child receives no history from the caller.
+ HISTORY_PROPAGATION_SCOPE_NONE = 0;
+ // Propagate the caller's own history events only. The child does
+ // not see any ancestral history (trust boundary).
+ HISTORY_PROPAGATION_SCOPE_OWN_HISTORY = 1;
+ // Propagate the caller's own history events AND the full ancestral
+ // chain. Any propagated history this workflow received from its
+ // parent is forwarded to the child.
+ HISTORY_PROPAGATION_SCOPE_LINEAGE = 2;
+}
+
+message WorkflowState {
+ string instanceId = 1;
+ string name = 2;
+ google.protobuf.StringValue version = 3;
+ OrchestrationStatus workflowStatus = 4;
+ google.protobuf.Timestamp scheduledStartTimestamp = 5;
+ google.protobuf.Timestamp createdTimestamp = 6;
+ google.protobuf.Timestamp lastUpdatedTimestamp = 7;
+ google.protobuf.StringValue input = 8;
+ google.protobuf.StringValue output = 9;
+ google.protobuf.StringValue customStatus = 10;
+ TaskFailureDetails failureDetails = 11;
+ google.protobuf.StringValue executionId = 12;
+ google.protobuf.Timestamp completedTimestamp = 13;
+ google.protobuf.StringValue parentInstanceId = 14;
+ map tags = 15;
+ google.protobuf.StringValue parentAppId = 16;
+ optional google.protobuf.Timestamp startedAt = 17;
+
+}
\ No newline at end of file
diff --git a/src/Dapr.Workflow.Grpc/orchestrator_actions.proto b/src/Dapr.Workflow.Grpc/orchestrator_actions.proto
new file mode 100644
index 000000000..fedae5631
--- /dev/null
+++ b/src/Dapr.Workflow.Grpc/orchestrator_actions.proto
@@ -0,0 +1,118 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+syntax = "proto3";
+
+option csharp_namespace = "Dapr.DurableTask.Protobuf";
+option java_package = "io.dapr.durabletask.implementation.protobuf";
+option go_package = "/api/protos";
+
+import "orchestration.proto";
+import "history_events.proto";
+
+import "google/protobuf/timestamp.proto";
+import "google/protobuf/wrappers.proto";
+
+message ScheduleTaskAction {
+ string name = 1;
+ google.protobuf.StringValue version = 2;
+ google.protobuf.StringValue input = 3;
+ optional TaskRouter router = 4;
+ string taskExecutionId = 5;
+
+ // History propagation scope. Absent/SCOPE_NONE = no propagation.
+ optional HistoryPropagationScope historyPropagationScope = 6;
+}
+
+message CreateChildWorkflowAction {
+ string instanceId = 1;
+ string name = 2;
+ google.protobuf.StringValue version = 3;
+ google.protobuf.StringValue input = 4;
+ optional TaskRouter router = 5;
+
+ // History propagation scope. Absent/SCOPE_NONE = no propagation.
+ optional HistoryPropagationScope historyPropagationScope = 6;
+}
+
+// CreateDetachedWorkflowAction creates a new, detached workflow instance from
+// a running workflow. Mirrors the fields of CreateInstanceRequest (the client
+// scheduling API) so the runtime has all the information needed to schedule
+// the new instance directly from this action. The spawned workflow is fully
+// decoupled from the caller: no parent pointer is recorded on the new
+// workflow, no completion is awaited, and no failure propagation flows back.
+// The creation is recorded once in the caller's history as a
+// DetachedWorkflowInstanceCreatedEvent referencing the new instance ID.
+message CreateDetachedWorkflowAction {
+ // instanceId is the ID assigned to the new workflow. It is mandatory:
+ // implementors must set a stable, deterministic ID so that on replay the
+ // call resolves to the same DetachedWorkflowInstanceCreatedEvent in
+ // history.
+ string instanceId = 1;
+ // name of the workflow to schedule. Mandatory.
+ string name = 2;
+
+ // The remaining fields mirror the optional inputs of
+ // CreateInstanceRequest. Wrapper types (StringValue) carry presence via
+ // the wrapper; bare message fields are explicitly marked optional.
+ google.protobuf.StringValue version = 3;
+ google.protobuf.StringValue input = 4;
+ optional google.protobuf.Timestamp scheduledStartTimestamp = 5;
+ google.protobuf.StringValue executionId = 6;
+ map tags = 7;
+ optional TraceContext parentTraceContext = 8;
+ optional TaskRouter router = 9;
+}
+
+message CreateTimerAction {
+ google.protobuf.Timestamp fireAt = 1;
+ optional string name = 2;
+
+ // If set, provides additional context attached to this timer.
+ oneof origin {
+ TimerOriginCreateTimer createTimer = 3;
+ TimerOriginExternalEvent externalEvent = 4;
+ TimerOriginActivityRetry activityRetry = 5;
+ TimerOriginChildWorkflowRetry childWorkflowRetry = 6;
+ }
+}
+
+message SendEventAction {
+ WorkflowInstance instance = 1;
+ string name = 2;
+ google.protobuf.StringValue data = 3;
+}
+
+message CompleteWorkflowAction {
+ OrchestrationStatus workflowStatus = 1;
+ google.protobuf.StringValue result = 2;
+ google.protobuf.StringValue details = 3;
+ google.protobuf.StringValue newVersion = 4;
+ repeated HistoryEvent carryoverEvents = 5;
+ TaskFailureDetails failureDetails = 6;
+}
+
+message TerminateWorkflowAction {
+ string instanceId = 1;
+ google.protobuf.StringValue reason = 2;
+ bool recurse = 3;
+}
+
+message WorkflowVersionNotAvailableAction {
+}
+
+message WorkflowAction {
+ int32 id = 1;
+ oneof workflowActionType {
+ ScheduleTaskAction scheduleTask = 2;
+ CreateChildWorkflowAction createChildWorkflow = 3;
+ CreateTimerAction createTimer = 4;
+ SendEventAction sendEvent = 5;
+ CompleteWorkflowAction completeWorkflow = 6;
+ TerminateWorkflowAction terminateWorkflow = 7;
+ WorkflowVersionNotAvailableAction workflowVersionNotAvailable = 10;
+ CreateDetachedWorkflowAction createDetachedWorkflow = 11;
+ }
+ reserved 8;
+ optional TaskRouter router = 9;
+}
\ No newline at end of file
diff --git a/src/Dapr.Workflow.Grpc/orchestrator_service.proto b/src/Dapr.Workflow.Grpc/orchestrator_service.proto
index dafd57c21..d593204b1 100644
--- a/src/Dapr.Workflow.Grpc/orchestrator_service.proto
+++ b/src/Dapr.Workflow.Grpc/orchestrator_service.proto
@@ -7,68 +7,27 @@ option csharp_namespace = "Dapr.DurableTask.Protobuf";
option java_package = "io.dapr.durabletask.implementation.protobuf";
option go_package = "/api/protos";
+import "orchestration.proto";
+import "history_events.proto";
+import "orchestrator_actions.proto";
+
import "google/protobuf/timestamp.proto";
-import "google/protobuf/duration.proto";
import "google/protobuf/wrappers.proto";
import "google/protobuf/empty.proto";
-enum StalledReason {
- PATCH_MISMATCH = 0;
- VERSION_NAME_MISMATCH = 1;
-}
-
-// HistoryPropagationScope determines which ancestor workflow history events are
-// propagated to a child workflow when it is scheduled.
-enum HistoryPropagationScope {
- // No history is propagated. This is the default behavior.
- HISTORY_PROPAGATION_SCOPE_NONE = 0;
-
- // Only the calling workflow's own history events are propagated to the child.
- // Ancestor history is excluded, acting as a trust boundary.
- HISTORY_PROPAGATION_SCOPE_OWN_HISTORY = 1;
-
- // The calling workflow's history and all ancestor history (the full lineage) is propagated.
- HISTORY_PROPAGATION_SCOPE_LINEAGE = 2;
-}
-
-// PropagatedHistorySegment represents the history of a single ancestor workflow instance
-// that has been propagated to a child workflow.
-message PropagatedHistorySegment {
- // The Dapr App ID of the application that ran the ancestor workflow.
- string app_id = 1;
- // The orchestration instance ID of the ancestor workflow.
- string instance_id = 2;
- // The name of the ancestor workflow.
- string workflow_name = 3;
- // The ordered list of history events from the ancestor workflow.
- repeated HistoryEvent events = 4;
-}
-
-message TaskRouter {
- string sourceAppID = 1;
- optional string targetAppID = 2;
-}
-
-message OrchestrationVersion {
- repeated string patches = 1;
-
- // The name of the executed workflow
- optional string name = 2;
-}
-
-message OrchestrationInstance {
- string instanceId = 1;
- google.protobuf.StringValue executionId = 2;
-}
-
message ActivityRequest {
string name = 1;
google.protobuf.StringValue version = 2;
google.protobuf.StringValue input = 3;
- OrchestrationInstance orchestrationInstance = 4;
+ WorkflowInstance workflowInstance = 4;
int32 taskId = 5;
TraceContext parentTraceContext = 6;
string taskExecutionId = 7;
+
+ // Propagated history from the calling workflow.
+ // Delivered via the work item stream to the SDK, so that the
+ // activity function can access it via ctx.
+ optional PropagatedHistory propagatedHistory = 8;
}
message ActivityResponse {
@@ -79,382 +38,32 @@ message ActivityResponse {
string completionToken = 5;
}
-message TaskFailureDetails {
- string errorType = 1;
- string errorMessage = 2;
- google.protobuf.StringValue stackTrace = 3;
- TaskFailureDetails innerFailure = 4;
- bool isNonRetriable = 5;
-}
-
-enum OrchestrationStatus {
- ORCHESTRATION_STATUS_RUNNING = 0;
- ORCHESTRATION_STATUS_COMPLETED = 1;
- ORCHESTRATION_STATUS_CONTINUED_AS_NEW = 2;
- ORCHESTRATION_STATUS_FAILED = 3;
- ORCHESTRATION_STATUS_CANCELED = 4;
- ORCHESTRATION_STATUS_TERMINATED = 5;
- ORCHESTRATION_STATUS_PENDING = 6;
- ORCHESTRATION_STATUS_SUSPENDED = 7;
- ORCHESTRATION_STATUS_STALLED = 8;
-}
-
-message ParentInstanceInfo {
- int32 taskScheduledId = 1;
- google.protobuf.StringValue name = 2;
- google.protobuf.StringValue version = 3;
- OrchestrationInstance orchestrationInstance = 4;
- optional string appID = 5;
-}
-
-// RerunParentInstanceInfo is used to indicate that this orchestration was
-// started as part of a rerun operation. Contains information about the parent
-// orchestration instance which was rerun.
-message RerunParentInstanceInfo {
- // instanceID is the orchestration instance ID this orchestration has been
- // rerun from.
- string instanceID = 1;
-}
-
-message TraceContext {
- string traceParent = 1;
- string spanID = 2 [deprecated=true];
- google.protobuf.StringValue traceState = 3;
-}
-
-message ExecutionStartedEvent {
- string name = 1;
- google.protobuf.StringValue version = 2;
- google.protobuf.StringValue input = 3;
- OrchestrationInstance orchestrationInstance = 4;
- ParentInstanceInfo parentInstance = 5;
- google.protobuf.Timestamp scheduledStartTimestamp = 6;
- TraceContext parentTraceContext = 7;
- google.protobuf.StringValue orchestrationSpanID = 8;
- map tags = 9;
-}
-
-message ExecutionCompletedEvent {
- OrchestrationStatus orchestrationStatus = 1;
- google.protobuf.StringValue result = 2;
- TaskFailureDetails failureDetails = 3;
-}
-
-message ExecutionTerminatedEvent {
- google.protobuf.StringValue input = 1;
- bool recurse = 2;
-}
-
-message TaskScheduledEvent {
- string name = 1;
- google.protobuf.StringValue version = 2;
- google.protobuf.StringValue input = 3;
- TraceContext parentTraceContext = 4;
- string taskExecutionId = 5;
-
- // If defined, indicates that this task was the starting point of a new
- // workflow execution as the result of a rerun operation.
- optional RerunParentInstanceInfo rerunParentInstanceInfo = 6;
-}
-
-message TaskCompletedEvent {
- int32 taskScheduledId = 1;
- google.protobuf.StringValue result = 2;
- string taskExecutionId = 3;
-}
-
-message TaskFailedEvent {
- int32 taskScheduledId = 1;
- TaskFailureDetails failureDetails = 2;
- string taskExecutionId = 3;
-}
-
-message SubOrchestrationInstanceCreatedEvent {
- string instanceId = 1;
- string name = 2;
- google.protobuf.StringValue version = 3;
- google.protobuf.StringValue input = 4;
- TraceContext parentTraceContext = 5;
-}
-
-message SubOrchestrationInstanceCompletedEvent {
- int32 taskScheduledId = 1;
- google.protobuf.StringValue result = 2;
-}
-
-message SubOrchestrationInstanceFailedEvent {
- int32 taskScheduledId = 1;
- TaskFailureDetails failureDetails = 2;
-}
-
-message TimerCreatedEvent {
- google.protobuf.Timestamp fireAt = 1;
- optional string name = 2;
-
- // If defined, indicates that this task was the starting point of a new
- // workflow execution as the result of a rerun operation.
- optional RerunParentInstanceInfo rerunParentInstanceInfo = 3;
-
- // Indicates the reason this timer was created.
- oneof origin {
- TimerOriginCreateTimer originCreateTimer = 4;
- TimerOriginExternalEvent originExternalEvent = 5;
- TimerOriginActivityRetry originActivityRetry = 6;
- TimerOriginChildWorkflowRetry originChildWorkflowRetry = 7;
- }
-}
-
-message TimerFiredEvent {
- google.protobuf.Timestamp fireAt = 1;
- int32 timerId = 2;
-}
-
-message OrchestratorStartedEvent {
- optional OrchestrationVersion version = 1;
-}
-
-message OrchestratorCompletedEvent {
- // No payload data
-}
-
-message EventSentEvent {
- string instanceId = 1;
- string name = 2;
- google.protobuf.StringValue input = 3;
-}
-
-message EventRaisedEvent {
- string name = 1;
- google.protobuf.StringValue input = 2;
-}
-
-message GenericEvent {
- google.protobuf.StringValue data = 1;
-}
-
-message HistoryStateEvent {
- OrchestrationState orchestrationState = 1;
-}
-
-message ContinueAsNewEvent {
- google.protobuf.StringValue input = 1;
-}
-
-message ExecutionSuspendedEvent {
- google.protobuf.StringValue input = 1;
-}
-
-message ExecutionResumedEvent {
- google.protobuf.StringValue input = 1;
-}
-
-message ExecutionStalledEvent {
- StalledReason reason = 1;
- optional string description = 2;
-}
-
-message EntityOperationSignaledEvent {
- string requestId = 1;
- string operation = 2;
- google.protobuf.Timestamp scheduledTime = 3;
- google.protobuf.StringValue input = 4;
- google.protobuf.StringValue targetInstanceId = 5; // used only within histories, null in messages
-}
-
-message EntityOperationCalledEvent {
- string requestId = 1;
- string operation = 2;
- google.protobuf.Timestamp scheduledTime = 3;
- google.protobuf.StringValue input = 4;
- google.protobuf.StringValue parentInstanceId = 5; // used only within messages, null in histories
- google.protobuf.StringValue parentExecutionId = 6; // used only within messages, null in histories
- google.protobuf.StringValue targetInstanceId = 7; // used only within histories, null in messages
-}
-
-message EntityLockRequestedEvent {
- string criticalSectionId = 1;
- repeated string lockSet = 2;
- int32 position = 3;
- google.protobuf.StringValue parentInstanceId = 4; // used only within messages, null in histories
-}
-
-message EntityOperationCompletedEvent {
- string requestId = 1;
- google.protobuf.StringValue output = 2;
-}
-
-message EntityOperationFailedEvent {
- string requestId = 1;
- TaskFailureDetails failureDetails = 2;
-}
-
-message EntityUnlockSentEvent {
- string criticalSectionId = 1;
- google.protobuf.StringValue parentInstanceId = 2; // used only within messages, null in histories
- google.protobuf.StringValue targetInstanceId = 3; // used only within histories, null in messages
-}
-
-message EntityLockGrantedEvent {
- string criticalSectionId = 1;
-}
-
-message HistoryEvent {
- int32 eventId = 1;
- google.protobuf.Timestamp timestamp = 2;
- oneof eventType {
- ExecutionStartedEvent executionStarted = 3;
- ExecutionCompletedEvent executionCompleted = 4;
- ExecutionTerminatedEvent executionTerminated = 5;
- TaskScheduledEvent taskScheduled = 6;
- TaskCompletedEvent taskCompleted = 7;
- TaskFailedEvent taskFailed = 8;
- SubOrchestrationInstanceCreatedEvent subOrchestrationInstanceCreated = 9;
- SubOrchestrationInstanceCompletedEvent subOrchestrationInstanceCompleted = 10;
- SubOrchestrationInstanceFailedEvent subOrchestrationInstanceFailed = 11;
- TimerCreatedEvent timerCreated = 12;
- TimerFiredEvent timerFired = 13;
- OrchestratorStartedEvent orchestratorStarted = 14;
- OrchestratorCompletedEvent orchestratorCompleted = 15;
- EventSentEvent eventSent = 16;
- EventRaisedEvent eventRaised = 17;
- GenericEvent genericEvent = 18;
- HistoryStateEvent historyState = 19;
- ContinueAsNewEvent continueAsNew = 20;
- ExecutionSuspendedEvent executionSuspended = 21;
- ExecutionResumedEvent executionResumed = 22;
- EntityOperationSignaledEvent entityOperationSignaled = 23;
- EntityOperationCalledEvent entityOperationCalled = 24;
- EntityOperationCompletedEvent entityOperationCompleted = 25;
- EntityOperationFailedEvent entityOperationFailed = 26;
- EntityLockRequestedEvent entityLockRequested = 27;
- EntityLockGrantedEvent entityLockGranted = 28;
- EntityUnlockSentEvent entityUnlockSent = 29;
- ExecutionStalledEvent executionStalled = 31;
- }
- optional TaskRouter router = 30;
-}
-
-message ScheduleTaskAction {
- string name = 1;
- google.protobuf.StringValue version = 2;
- google.protobuf.StringValue input = 3;
- optional TaskRouter router = 4;
- string taskExecutionId = 5;
-}
-
-message CreateSubOrchestrationAction {
- string instanceId = 1;
- string name = 2;
- google.protobuf.StringValue version = 3;
- google.protobuf.StringValue input = 4;
- optional TaskRouter router = 5;
- // Specifies which ancestor history events should be propagated to the child workflow.
- optional HistoryPropagationScope history_propagation_scope = 6;
- // The history segments to propagate to the child workflow.
- // Populated by the SDK based on the history_propagation_scope.
- repeated PropagatedHistorySegment propagated_history = 7;
-}
-
-// Timer created explicitly by the workflow via CreateTimer().
-message TimerOriginCreateTimer {
-}
-
-// Timer created to track the timeout of a WaitForExternalEvent call.
-message TimerOriginExternalEvent {
- string name = 1;
-}
-
-// Timer created to manage the retry delay of an activity.
-message TimerOriginActivityRetry {
- string taskExecutionId = 1;
-}
-
-// Timer created to manage the retry delay of a child workflow.
-message TimerOriginChildWorkflowRetry {
- string instanceId = 1;
-}
-
-message CreateTimerAction {
- google.protobuf.Timestamp fireAt = 1;
- optional string name = 2;
-
- // Indicates the reason this timer is being created.
- oneof origin {
- TimerOriginCreateTimer originCreateTimer = 3;
- TimerOriginExternalEvent originExternalEvent = 4;
- TimerOriginActivityRetry originActivityRetry = 5;
- TimerOriginChildWorkflowRetry originChildWorkflowRetry = 6;
- }
-}
-
-message SendEventAction {
- OrchestrationInstance instance = 1;
- string name = 2;
- google.protobuf.StringValue data = 3;
-}
-
-message CompleteOrchestrationAction {
- OrchestrationStatus orchestrationStatus = 1;
- google.protobuf.StringValue result = 2;
- google.protobuf.StringValue details = 3;
- google.protobuf.StringValue newVersion = 4;
- repeated HistoryEvent carryoverEvents = 5;
- TaskFailureDetails failureDetails = 6;
-}
-
-message TerminateOrchestrationAction {
- string instanceId = 1;
- google.protobuf.StringValue reason = 2;
- bool recurse = 3;
-}
-
-message SendEntityMessageAction {
- oneof EntityMessageType {
- EntityOperationSignaledEvent entityOperationSignaled = 1;
- EntityOperationCalledEvent entityOperationCalled = 2;
- EntityLockRequestedEvent entityLockRequested = 3;
- EntityUnlockSentEvent entityUnlockSent = 4;
- }
-}
-
-message OrchestratorAction {
- int32 id = 1;
- oneof orchestratorActionType {
- ScheduleTaskAction scheduleTask = 2;
- CreateSubOrchestrationAction createSubOrchestration = 3;
- CreateTimerAction createTimer = 4;
- SendEventAction sendEvent = 5;
- CompleteOrchestrationAction completeOrchestration = 6;
- TerminateOrchestrationAction terminateOrchestration = 7;
- SendEntityMessageAction sendEntityMessage = 8;
- }
- optional TaskRouter router = 9;
-}
-
-message OrchestratorRequest {
+message WorkflowRequest {
string instanceId = 1;
google.protobuf.StringValue executionId = 2;
repeated HistoryEvent pastEvents = 3;
repeated HistoryEvent newEvents = 4;
- OrchestratorEntityParameters entityParameters = 5;
+ reserved 5;
bool requiresHistoryStreaming = 6;
optional TaskRouter router = 7;
- // Workflow history propagated from ancestor workflow instances.
- // Populated when the parent scheduled this workflow with a non-None HistoryPropagationScope.
- repeated PropagatedHistorySegment propagated_history = 8;
+
+ // Propagated history from a parent workflow.
+ // Delivered via the work item stream to the SDK, so that the
+ // workflow function can access it via ctx.
+ optional PropagatedHistory propagatedHistory = 8;
}
-message OrchestratorResponse {
+message WorkflowResponse {
string instanceId = 1;
- repeated OrchestratorAction actions = 2;
+ repeated WorkflowAction actions = 2;
google.protobuf.StringValue customStatus = 3;
string completionToken = 4;
- // The number of work item events that were processed by the orchestrator.
- // This field is optional. If not set, the service should assume that the orchestrator processed all events.
+ // The number of work item events that were processed by the workflow.
+ // This field is optional. If not set, the service should assume that the workflow processed all events.
google.protobuf.Int32Value numEventsProcessed = 5;
- optional OrchestrationVersion version = 6;
+ optional WorkflowVersion version = 6;
}
message CreateInstanceRequest {
@@ -463,23 +72,13 @@ message CreateInstanceRequest {
google.protobuf.StringValue version = 3;
google.protobuf.StringValue input = 4;
google.protobuf.Timestamp scheduledStartTimestamp = 5;
- OrchestrationIdReusePolicy orchestrationIdReusePolicy = 6;
+ reserved 6;
+ reserved "orchestrationIdReusePolicy";
google.protobuf.StringValue executionId = 7;
map tags = 8;
TraceContext parentTraceContext = 9;
}
-message OrchestrationIdReusePolicy {
- repeated OrchestrationStatus operationStatus = 1;
- CreateOrchestrationAction action = 2;
-}
-
-enum CreateOrchestrationAction {
- ERROR = 0;
- IGNORE = 1;
- TERMINATE = 2;
-}
-
message CreateInstanceResponse {
string instanceId = 1;
}
@@ -491,34 +90,7 @@ message GetInstanceRequest {
message GetInstanceResponse {
bool exists = 1;
- OrchestrationState orchestrationState = 2;
-}
-
-message RewindInstanceRequest {
- string instanceId = 1;
- google.protobuf.StringValue reason = 2;
-}
-
-message RewindInstanceResponse {
- // Empty for now. Using explicit type incase we want to add content later.
-}
-
-message OrchestrationState {
- string instanceId = 1;
- string name = 2;
- google.protobuf.StringValue version = 3;
- OrchestrationStatus orchestrationStatus = 4;
- google.protobuf.Timestamp scheduledStartTimestamp = 5;
- google.protobuf.Timestamp createdTimestamp = 6;
- google.protobuf.Timestamp lastUpdatedTimestamp = 7;
- google.protobuf.StringValue input = 8;
- google.protobuf.StringValue output = 9;
- google.protobuf.StringValue customStatus = 10;
- TaskFailureDetails failureDetails = 11;
- google.protobuf.StringValue executionId = 12;
- google.protobuf.Timestamp completedTimestamp = 13;
- google.protobuf.StringValue parentInstanceId = 14;
- map tags = 15;
+ WorkflowState workflowState = 2;
}
message RaiseEventRequest {
@@ -559,26 +131,6 @@ message ResumeResponse {
// No payload
}
-message QueryInstancesRequest {
- InstanceQuery query = 1;
-}
-
-message InstanceQuery{
- repeated OrchestrationStatus runtimeStatus = 1;
- google.protobuf.Timestamp createdTimeFrom = 2;
- google.protobuf.Timestamp createdTimeTo = 3;
- repeated google.protobuf.StringValue taskHubNames = 4;
- int32 maxInstanceCount = 5;
- google.protobuf.StringValue continuationToken = 6;
- google.protobuf.StringValue instanceIdPrefix = 7;
- bool fetchInputsAndOutputs = 8;
-}
-
-message QueryInstancesResponse {
- repeated OrchestrationState orchestrationState = 1;
- google.protobuf.StringValue continuationToken = 2;
-}
-
message PurgeInstancesRequest {
oneof request {
string instanceId = 1;
@@ -609,273 +161,15 @@ message PurgeInstancesResponse {
google.protobuf.BoolValue isComplete = 2;
}
-message CreateTaskHubRequest {
- bool recreateIfExists = 1;
-}
-
-message CreateTaskHubResponse {
- //no playload
-}
-
-message DeleteTaskHubRequest {
- //no playload
-}
-
-message DeleteTaskHubResponse {
- //no playload
-}
-
-message SignalEntityRequest {
- string instanceId = 1;
- string name = 2;
- google.protobuf.StringValue input = 3;
- string requestId = 4;
- google.protobuf.Timestamp scheduledTime = 5;
-}
-
-message SignalEntityResponse {
- // no payload
-}
-
-message GetEntityRequest {
- string instanceId = 1;
- bool includeState = 2;
-}
-
-message GetEntityResponse {
- bool exists = 1;
- EntityMetadata entity = 2;
-}
-
-message EntityQuery {
- google.protobuf.StringValue instanceIdStartsWith = 1;
- google.protobuf.Timestamp lastModifiedFrom = 2;
- google.protobuf.Timestamp lastModifiedTo = 3;
- bool includeState = 4;
- bool includeTransient = 5;
- google.protobuf.Int32Value pageSize = 6;
- google.protobuf.StringValue continuationToken = 7;
-}
-
-message QueryEntitiesRequest {
- EntityQuery query = 1;
-}
-
-message QueryEntitiesResponse {
- repeated EntityMetadata entities = 1;
- google.protobuf.StringValue continuationToken = 2;
-}
-
-message EntityMetadata {
- string instanceId = 1;
- google.protobuf.Timestamp lastModifiedTime = 2;
- int32 backlogQueueSize = 3;
- google.protobuf.StringValue lockedBy = 4;
- google.protobuf.StringValue serializedState = 5;
-}
-
-message CleanEntityStorageRequest {
- google.protobuf.StringValue continuationToken = 1;
- bool removeEmptyEntities = 2;
- bool releaseOrphanedLocks = 3;
-}
-
-message CleanEntityStorageResponse {
- google.protobuf.StringValue continuationToken = 1;
- int32 emptyEntitiesRemoved = 2;
- int32 orphanedLocksReleased = 3;
-}
-
-message OrchestratorEntityParameters {
- google.protobuf.Duration entityMessageReorderWindow = 1;
-}
-
-message EntityBatchRequest {
- string instanceId = 1;
- google.protobuf.StringValue entityState = 2;
- repeated OperationRequest operations = 3;
-}
-
-message EntityBatchResult {
- repeated OperationResult results = 1;
- repeated OperationAction actions = 2;
- google.protobuf.StringValue entityState = 3;
- TaskFailureDetails failureDetails = 4;
- string completionToken = 5;
- repeated OperationInfo operationInfos = 6; // used only with DTS
-}
-
-message EntityRequest {
- string instanceId = 1;
- string executionId = 2;
- google.protobuf.StringValue entityState = 3; // null if entity does not exist
- repeated HistoryEvent operationRequests = 4;
-}
-
-message OperationRequest {
- string operation = 1;
- string requestId = 2;
- google.protobuf.StringValue input = 3;
-}
-
-message OperationResult {
- oneof resultType {
- OperationResultSuccess success = 1;
- OperationResultFailure failure = 2;
- }
-}
-
-message OperationInfo {
- string requestId = 1;
- OrchestrationInstance responseDestination = 2; // null for signals
-}
-
-message OperationResultSuccess {
- google.protobuf.StringValue result = 1;
-}
-
-message OperationResultFailure {
- TaskFailureDetails failureDetails = 1;
-}
-
-message OperationAction {
- int32 id = 1;
- oneof operationActionType {
- SendSignalAction sendSignal = 2;
- StartNewOrchestrationAction startNewOrchestration = 3;
- }
-}
-
-message SendSignalAction {
- string instanceId = 1;
- string name = 2;
- google.protobuf.StringValue input = 3;
- google.protobuf.Timestamp scheduledTime = 4;
-}
-
-message StartNewOrchestrationAction {
- string instanceId = 1;
- string name = 2;
- google.protobuf.StringValue version = 3;
- google.protobuf.StringValue input = 4;
- google.protobuf.Timestamp scheduledTime = 5;
-}
-
-message AbandonActivityTaskRequest {
- string completionToken = 1;
-}
-
-message AbandonActivityTaskResponse {
- // Empty.
-}
-
-message AbandonOrchestrationTaskRequest {
- string completionToken = 1;
-}
-
-message AbandonOrchestrationTaskResponse {
- // Empty.
-}
-
-message AbandonEntityTaskRequest {
- string completionToken = 1;
-}
-
-message AbandonEntityTaskResponse {
- // Empty.
-}
-
-service TaskHubSidecarService {
- // Sends a hello request to the sidecar service.
- rpc Hello(google.protobuf.Empty) returns (google.protobuf.Empty);
-
- // Starts a new orchestration instance.
- rpc StartInstance(CreateInstanceRequest) returns (CreateInstanceResponse);
-
- // Gets the status of an existing orchestration instance.
- rpc GetInstance(GetInstanceRequest) returns (GetInstanceResponse);
-
- // Rewinds an orchestration instance to last known good state and replays from there.
- rpc RewindInstance(RewindInstanceRequest) returns (RewindInstanceResponse);
-
- // Waits for an orchestration instance to reach a running or completion state.
- rpc WaitForInstanceStart(GetInstanceRequest) returns (GetInstanceResponse);
-
- // Waits for an orchestration instance to reach a completion state (completed, failed, terminated, etc.).
- rpc WaitForInstanceCompletion(GetInstanceRequest) returns (GetInstanceResponse);
-
- // Raises an event to a running orchestration instance.
- rpc RaiseEvent(RaiseEventRequest) returns (RaiseEventResponse);
-
- // Terminates a running orchestration instance.
- rpc TerminateInstance(TerminateRequest) returns (TerminateResponse);
-
- // Suspends a running orchestration instance.
- rpc SuspendInstance(SuspendRequest) returns (SuspendResponse);
-
- // Resumes a suspended orchestration instance.
- rpc ResumeInstance(ResumeRequest) returns (ResumeResponse);
-
- // rpc DeleteInstance(DeleteInstanceRequest) returns (DeleteInstanceResponse);
-
- rpc QueryInstances(QueryInstancesRequest) returns (QueryInstancesResponse);
- rpc PurgeInstances(PurgeInstancesRequest) returns (PurgeInstancesResponse);
-
- rpc GetWorkItems(GetWorkItemsRequest) returns (stream WorkItem);
- rpc CompleteActivityTask(ActivityResponse) returns (CompleteTaskResponse);
- rpc CompleteOrchestratorTask(OrchestratorResponse) returns (CompleteTaskResponse);
- rpc CompleteEntityTask(EntityBatchResult) returns (CompleteTaskResponse);
-
- // Gets the history of an orchestration instance as a stream of events.
- rpc StreamInstanceHistory(StreamInstanceHistoryRequest) returns (stream HistoryChunk);
-
- // Deletes and Creates the necessary resources for the orchestration service and the instance store
- rpc CreateTaskHub(CreateTaskHubRequest) returns (CreateTaskHubResponse);
-
- // Deletes the resources for the orchestration service and optionally the instance store
- rpc DeleteTaskHub(DeleteTaskHubRequest) returns (DeleteTaskHubResponse);
-
- // sends a signal to an entity
- rpc SignalEntity(SignalEntityRequest) returns (SignalEntityResponse);
-
- // get information about a specific entity
- rpc GetEntity(GetEntityRequest) returns (GetEntityResponse);
-
- // query entities
- rpc QueryEntities(QueryEntitiesRequest) returns (QueryEntitiesResponse);
-
- // clean entity storage
- rpc CleanEntityStorage(CleanEntityStorageRequest) returns (CleanEntityStorageResponse);
-
- // Abandons a single work item
- rpc AbandonTaskActivityWorkItem(AbandonActivityTaskRequest) returns (AbandonActivityTaskResponse);
-
- // Abandon an orchestration work item
- rpc AbandonTaskOrchestratorWorkItem(AbandonOrchestrationTaskRequest) returns (AbandonOrchestrationTaskResponse);
-
- // Abandon an entity work item
- rpc AbandonTaskEntityWorkItem(AbandonEntityTaskRequest) returns (AbandonEntityTaskResponse);
-
- // Rerun a Workflow from a specific event ID of a workflow instance.
- rpc RerunWorkflowFromEvent(RerunWorkflowFromEventRequest) returns (RerunWorkflowFromEventResponse);
-
- rpc ListInstanceIDs (ListInstanceIDsRequest) returns (ListInstanceIDsResponse);
- rpc GetInstanceHistory (GetInstanceHistoryRequest) returns (GetInstanceHistoryResponse);
-}
-
message GetWorkItemsRequest {
- int32 maxConcurrentOrchestrationWorkItems = 1;
- int32 maxConcurrentActivityWorkItems = 2;
- int32 maxConcurrentEntityWorkItems = 3;
-
- repeated WorkerCapability capabilities = 10;
+ reserved 1, 2, 3, 10;
}
enum WorkerCapability {
WORKER_CAPABILITY_UNSPECIFIED = 0;
// Indicates that the worker is capable of streaming instance history as a more optimized
- // alternative to receiving the full history embedded in the orchestrator work-item.
+ // alternative to receiving the full history embedded in the workflow work-item.
// When set, the service may return work items without any history events as an optimization.
// It is strongly recommended that all SDKs support this capability.
WORKER_CAPABILITY_HISTORY_STREAMING = 1;
@@ -883,12 +177,10 @@ enum WorkerCapability {
message WorkItem {
oneof request {
- OrchestratorRequest orchestratorRequest = 1;
+ WorkflowRequest workflowRequest = 1;
ActivityRequest activityRequest = 2;
- EntityBatchRequest entityRequest = 3; // (older) used by orchestration services implementations
- HealthPing healthPing = 4;
- EntityRequest entityRequestV2 = 5; // (newer) used by backend service implementations
}
+ reserved 3, 4, 5;
string completionToken = 10;
}
@@ -896,27 +188,11 @@ message CompleteTaskResponse {
// No payload
}
-message HealthPing {
- // No payload
-}
-
-message StreamInstanceHistoryRequest {
- string instanceId = 1;
- google.protobuf.StringValue executionId = 2;
-
- // When set to true, the service may return a more optimized response suitable for workers.
- bool forWorkItemProcessing = 3;
-}
-
-message HistoryChunk {
- repeated HistoryEvent events = 1;
-}
-
// RerunWorkflowFromEventRequest is used to rerun a workflow instance from a
// specific event ID.
message RerunWorkflowFromEventRequest {
- // sourceInstanceID is the orchestration instance ID to rerun. Can be a top
- // level instance, or sub-orchestration instance.
+ // sourceInstanceID is the workflow instance ID to rerun. Can be a top
+ // level instance, or child workflow instance.
string sourceInstanceID = 1;
// the event id to start the new workflow instance from.
@@ -935,6 +211,11 @@ message RerunWorkflowFromEventRequest {
// inputs being `StringValue` which cannot be optional, and therefore no nil
// value can be signalled or overwritten.
bool overwriteInput = 5;
+
+ // newChildWorkflowInstanceID is an optional instance ID to use when
+ // rerunning from a child workflow. Only accepted if the event ID given is
+ // targeting a child workflow creation event.
+ optional string newChildWorkflowInstanceID = 6;
}
// RerunWorkflowFromEventResponse is the response to executing
@@ -943,7 +224,7 @@ message RerunWorkflowFromEventResponse {
string newInstanceID = 1;
}
-// ListInstanceIDsRequest is used to list all orchestration instances.
+// ListInstanceIDsRequest is used to list all workflow instances.
message ListInstanceIDsRequest {
// continuationToken is the continuation token to use for pagination. This
// is the token which the next page should start from. If not given, the
@@ -965,8 +246,8 @@ message ListInstanceIDsResponse {
optional string continuationToken = 2;
}
-// GetInstanceHistoryRequest is used to get the full history of an
-// orchestration instance.
+// GetInstanceHistoryRequest is used to get the full history of a
+// workflow instance.
message GetInstanceHistoryRequest {
string instanceId = 1;
}
@@ -974,4 +255,52 @@ message GetInstanceHistoryRequest {
// GetInstanceHistoryResponse is the response to executing GetInstanceHistory.
message GetInstanceHistoryResponse {
repeated HistoryEvent events = 1;
+}
+
+service TaskHubSidecarService {
+ // Sends a hello request to the sidecar service.
+ rpc Hello(google.protobuf.Empty) returns (google.protobuf.Empty);
+
+ // Starts a new workflow instance.
+ rpc StartInstance(CreateInstanceRequest) returns (CreateInstanceResponse);
+
+ // Gets the status of an existing workflow instance.
+ rpc GetInstance(GetInstanceRequest) returns (GetInstanceResponse);
+
+ // Waits for a workflow instance to reach a running or completion state.
+ rpc WaitForInstanceStart(GetInstanceRequest) returns (GetInstanceResponse);
+
+ // Waits for a workflow instance to reach a completion state (completed, failed, terminated, etc.).
+ rpc WaitForInstanceCompletion(GetInstanceRequest) returns (GetInstanceResponse);
+
+ // Raises an event to a running workflow instance.
+ rpc RaiseEvent(RaiseEventRequest) returns (RaiseEventResponse);
+
+ // Terminates a running workflow instance.
+ rpc TerminateInstance(TerminateRequest) returns (TerminateResponse);
+
+ // Suspends a running workflow instance.
+ rpc SuspendInstance(SuspendRequest) returns (SuspendResponse);
+
+ // Resumes a suspended workflow instance.
+ rpc ResumeInstance(ResumeRequest) returns (ResumeResponse);
+
+ rpc PurgeInstances(PurgeInstancesRequest) returns (PurgeInstancesResponse);
+
+ rpc GetWorkItems(GetWorkItemsRequest) returns (stream WorkItem);
+ rpc CompleteActivityTask(ActivityResponse) returns (CompleteTaskResponse);
+
+ // Deprecated: Use CompleteWorkflowTask instead.
+ rpc CompleteOrchestratorTask(WorkflowResponse) returns (CompleteTaskResponse) {
+ option deprecated = true;
+ }
+
+ // Completes a workflow work item.
+ rpc CompleteWorkflowTask(WorkflowResponse) returns (CompleteTaskResponse);
+
+ // Rerun a Workflow from a specific event ID of a workflow instance.
+ rpc RerunWorkflowFromEvent(RerunWorkflowFromEventRequest) returns (RerunWorkflowFromEventResponse);
+
+ rpc ListInstanceIDs (ListInstanceIDsRequest) returns (ListInstanceIDsResponse);
+ rpc GetInstanceHistory (GetInstanceHistoryRequest) returns (GetInstanceHistoryResponse);
}
\ No newline at end of file
diff --git a/src/Dapr.Workflow.Grpc/runtime_state.proto b/src/Dapr.Workflow.Grpc/runtime_state.proto
index e47ee0615..8cedef156 100644
--- a/src/Dapr.Workflow.Grpc/runtime_state.proto
+++ b/src/Dapr.Workflow.Grpc/runtime_state.proto
@@ -19,7 +19,8 @@ option csharp_namespace = "Dapr.DurableTask.Protobuf";
option java_package = "io.dapr.durabletask.implementation.protobuf";
option go_package = "/api/protos";
-import "orchestrator_service.proto";
+import "orchestration.proto";
+import "history_events.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/wrappers.proto";
@@ -29,14 +30,14 @@ message RuntimeStateStalled {
optional string description = 2;
}
-// OrchestrationRuntimeState holds the current state for an orchestration.
-message OrchestrationRuntimeState {
+// WorkflowRuntimeState holds the current state for a workflow.
+message WorkflowRuntimeState {
string instanceId = 1;
repeated HistoryEvent newEvents = 2;
repeated HistoryEvent oldEvents = 3;
repeated HistoryEvent pendingTasks = 4;
repeated HistoryEvent pendingTimers = 5;
- repeated OrchestrationRuntimeStateMessage pendingMessages = 6;
+ repeated WorkflowRuntimeStateMessage pendingMessages = 6;
ExecutionStartedEvent startEvent = 7;
ExecutionCompletedEvent completedEvent = 8;
@@ -50,8 +51,14 @@ message OrchestrationRuntimeState {
optional RuntimeStateStalled stalled = 15;
}
-// OrchestrationRuntimeStateMessage holds an OrchestratorMessage and the target instance ID.
-message OrchestrationRuntimeStateMessage {
+// WorkflowRuntimeStateMessage holds a HistoryEvent payload and the target instance ID.
+message WorkflowRuntimeStateMessage {
HistoryEvent historyEvent = 1;
- string TargetInstanceID = 2;
+ string targetInstanceId = 2;
+
+ // Propagated history to deliver to the child workflow.
+ // This is a transport field used when creating child workflows with
+ // history propagation enabled. It is NOT stored as part of any
+ // workflow's history events.
+ optional PropagatedHistory propagatedHistory = 3;
}
\ No newline at end of file
diff --git a/src/Dapr.Workflow/Client/ProtoConverters.cs b/src/Dapr.Workflow/Client/ProtoConverters.cs
index 91aac5d4f..7941828c4 100644
--- a/src/Dapr.Workflow/Client/ProtoConverters.cs
+++ b/src/Dapr.Workflow/Client/ProtoConverters.cs
@@ -23,10 +23,10 @@ namespace Dapr.Workflow.Client;
internal static class ProtoConverters
{
///
- /// Converts proto to .
+ /// Converts proto to .
///
- public static WorkflowMetadata ToWorkflowMetadata(OrchestrationState state, IDaprSerializer serializer) =>
- new(state.InstanceId, state.Name, ToRuntimeStatus(state.OrchestrationStatus),
+ public static WorkflowMetadata ToWorkflowMetadata(Dapr.DurableTask.Protobuf.WorkflowState state, IDaprSerializer serializer) =>
+ new(state.InstanceId, state.Name, ToRuntimeStatus(state.WorkflowStatus),
state.CreatedTimestamp?.ToDateTime() ?? DateTime.MinValue,
state.LastUpdatedTimestamp?.ToDateTime() ?? DateTime.MinValue, serializer)
{
@@ -73,21 +73,22 @@ public static WorkflowHistoryEventType ToHistoryEventType(HistoryEvent.EventType
HistoryEvent.EventTypeOneofCase.TaskScheduled => WorkflowHistoryEventType.TaskScheduled,
HistoryEvent.EventTypeOneofCase.TaskCompleted => WorkflowHistoryEventType.TaskCompleted,
HistoryEvent.EventTypeOneofCase.TaskFailed => WorkflowHistoryEventType.TaskFailed,
- HistoryEvent.EventTypeOneofCase.SubOrchestrationInstanceCreated => WorkflowHistoryEventType.SubOrchestrationInstanceCreated,
- HistoryEvent.EventTypeOneofCase.SubOrchestrationInstanceCompleted => WorkflowHistoryEventType.SubOrchestrationInstanceCompleted,
- HistoryEvent.EventTypeOneofCase.SubOrchestrationInstanceFailed => WorkflowHistoryEventType.SubOrchestrationInstanceFailed,
+ HistoryEvent.EventTypeOneofCase.ChildWorkflowInstanceCreated => WorkflowHistoryEventType.SubOrchestrationInstanceCreated,
+ HistoryEvent.EventTypeOneofCase.ChildWorkflowInstanceCompleted => WorkflowHistoryEventType.SubOrchestrationInstanceCompleted,
+ HistoryEvent.EventTypeOneofCase.ChildWorkflowInstanceFailed => WorkflowHistoryEventType.SubOrchestrationInstanceFailed,
HistoryEvent.EventTypeOneofCase.TimerCreated => WorkflowHistoryEventType.TimerCreated,
HistoryEvent.EventTypeOneofCase.TimerFired => WorkflowHistoryEventType.TimerFired,
- HistoryEvent.EventTypeOneofCase.OrchestratorStarted => WorkflowHistoryEventType.OrchestratorStarted,
- HistoryEvent.EventTypeOneofCase.OrchestratorCompleted => WorkflowHistoryEventType.OrchestratorCompleted,
+ HistoryEvent.EventTypeOneofCase.WorkflowStarted => WorkflowHistoryEventType.OrchestratorStarted,
+ HistoryEvent.EventTypeOneofCase.WorkflowCompleted => WorkflowHistoryEventType.OrchestratorCompleted,
HistoryEvent.EventTypeOneofCase.EventSent => WorkflowHistoryEventType.EventSent,
HistoryEvent.EventTypeOneofCase.EventRaised => WorkflowHistoryEventType.EventRaised,
- HistoryEvent.EventTypeOneofCase.GenericEvent => WorkflowHistoryEventType.GenericEvent,
- HistoryEvent.EventTypeOneofCase.HistoryState => WorkflowHistoryEventType.HistoryState,
+ // HistoryEvent.EventTypeOneofCase.GenericEvent => WorkflowHistoryEventType.GenericEvent,
+ // HistoryEvent.EventTypeOneofCase.HistoryState => WorkflowHistoryEventType.HistoryState,
HistoryEvent.EventTypeOneofCase.ContinueAsNew => WorkflowHistoryEventType.ContinueAsNew,
HistoryEvent.EventTypeOneofCase.ExecutionSuspended => WorkflowHistoryEventType.ExecutionSuspended,
HistoryEvent.EventTypeOneofCase.ExecutionResumed => WorkflowHistoryEventType.ExecutionResumed,
HistoryEvent.EventTypeOneofCase.ExecutionStalled => WorkflowHistoryEventType.ExecutionStalled,
+ HistoryEvent.EventTypeOneofCase.DetachedWorkflowInstanceCreated => WorkflowHistoryEventType.SubOrchestrationInstanceCreated,
_ => WorkflowHistoryEventType.Unknown
};
}
diff --git a/src/Dapr.Workflow/Client/WorkflowGrpcClient.cs b/src/Dapr.Workflow/Client/WorkflowGrpcClient.cs
index d76522cee..7971bb531 100644
--- a/src/Dapr.Workflow/Client/WorkflowGrpcClient.cs
+++ b/src/Dapr.Workflow/Client/WorkflowGrpcClient.cs
@@ -79,7 +79,7 @@ public override async Task ScheduleNewWorkflowAsync(string workflowName,
return null;
}
- return ProtoConverters.ToWorkflowMetadata(response.OrchestrationState, serializer);
+ return ProtoConverters.ToWorkflowMetadata(response.WorkflowState, serializer);
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.NotFound)
{
diff --git a/src/Dapr.Workflow/Versioning/WorkflowVersionTracker.cs b/src/Dapr.Workflow/Versioning/WorkflowVersionTracker.cs
index bcf5360b9..be3b572be 100644
--- a/src/Dapr.Workflow/Versioning/WorkflowVersionTracker.cs
+++ b/src/Dapr.Workflow/Versioning/WorkflowVersionTracker.cs
@@ -123,11 +123,11 @@ public bool RequestPatch(string patchName, bool isReplaying)
}
///
- /// Produces the to stamp into the .
+ /// Produces the to stamp into the .
///
/// The name of the current workflow.
- /// An instance of a .
- public OrchestrationVersion BuildResponseVersion(string workflowName) => new()
+ /// An instance of a .
+ public WorkflowVersion BuildResponseVersion(string workflowName) => new()
{
Name = workflowName,
Patches = { _patchesThisTurn }
@@ -139,7 +139,7 @@ private static List ListAllVersioningPatches(IReadOnlyList
foreach (var ev in events)
{
- var version = ev.OrchestratorStarted?.Version;
+ var version = ev.WorkflowStarted?.Version;
if (version is not null)
{
result.AddRange(version.Patches);
diff --git a/src/Dapr.Workflow/Worker/Grpc/GrpcProtocolHandler.cs b/src/Dapr.Workflow/Worker/Grpc/GrpcProtocolHandler.cs
index 9123f2652..05f8a76fb 100644
--- a/src/Dapr.Workflow/Worker/Grpc/GrpcProtocolHandler.cs
+++ b/src/Dapr.Workflow/Worker/Grpc/GrpcProtocolHandler.cs
@@ -29,21 +29,16 @@ namespace Dapr.Workflow.Worker.Grpc;
internal sealed class GrpcProtocolHandler(
TaskHubSidecarService.TaskHubSidecarServiceClient grpcClient,
ILoggerFactory loggerFactory,
- int maxConcurrentWorkItems = 100,
- int maxConcurrentActivities = 100,
- string? daprApiToken = null) : IAsyncDisposable
-{
+ string? daprApiToken = null) : IAsyncDisposable {
private static readonly TimeSpan ReconnectDelay = TimeSpan.FromSeconds(5);
private static readonly TimeSpan KeepaliveInterval = TimeSpan.FromSeconds(30);
private readonly CancellationTokenSource _disposalCts = new();
- private readonly ILogger _logger = loggerFactory?.CreateLogger() ?? throw new ArgumentNullException(nameof(loggerFactory));
+ private readonly ILogger _logger = loggerFactory.CreateLogger() ?? throw new ArgumentNullException(nameof(loggerFactory));
private readonly TaskHubSidecarService.TaskHubSidecarServiceClient _grpcClient =
grpcClient ?? throw new ArgumentNullException(nameof(grpcClient));
- private readonly int _maxConcurrentWorkItems = maxConcurrentWorkItems > 0 ? maxConcurrentWorkItems : throw new ArgumentOutOfRangeException(nameof(maxConcurrentWorkItems));
- private readonly int _maxConcurrentActivities = maxConcurrentActivities > 0 ? maxConcurrentActivities : throw new ArgumentOutOfRangeException(nameof(maxConcurrentActivities));
- private readonly SemaphoreSlim _orchestrationSemaphore = new(maxConcurrentWorkItems, maxConcurrentWorkItems);
- private readonly SemaphoreSlim _activitySemaphore = new(maxConcurrentActivities, maxConcurrentActivities);
+ private readonly SemaphoreSlim _orchestrationSemaphore = new(100);
+ private readonly SemaphoreSlim _activitySemaphore = new(100);
private AsyncServerStreamingCall? _streamingCall;
private int _activeWorkItemCount;
@@ -56,7 +51,7 @@ internal sealed class GrpcProtocolHandler(
/// Handler for activity work items.
/// Cancellation token.
public async Task StartAsync(
- Func> workflowHandler,
+ Func> workflowHandler,
Func> activityHandler,
CancellationToken cancellationToken)
{
@@ -64,11 +59,7 @@ public async Task StartAsync(
var token = linkedCts.Token;
// Establish the bidirectional stream
- var request = new GetWorkItemsRequest
- {
- MaxConcurrentOrchestrationWorkItems = _maxConcurrentWorkItems,
- MaxConcurrentActivityWorkItems = _maxConcurrentActivities
- };
+ var request = new GetWorkItemsRequest();
while (!token.IsCancellationRequested)
{
@@ -157,7 +148,7 @@ private static async Task DelayOrStopAsync(TimeSpan delay, CancellationToken tok
///
private async Task ReceiveLoopAsync(
IAsyncStreamReader workItemsStream,
- Func> orchestratorHandler,
+ Func> workflowHandler,
Func> activityHandler,
CancellationToken cancellationToken)
{
@@ -173,8 +164,8 @@ private async Task ReceiveLoopAsync(
// Dispatch based on work item type
var workItemTask = workItem.RequestCase switch
{
- WorkItem.RequestOneofCase.OrchestratorRequest => Task.Run(
- () => ProcessWorkflowAsync(workItem.OrchestratorRequest, completionToken, orchestratorHandler, cancellationToken),
+ WorkItem.RequestOneofCase.WorkflowRequest => Task.Run(
+ () => ProcessWorkflowAsync(workItem.WorkflowRequest, completionToken, workflowHandler, cancellationToken),
cancellationToken),
WorkItem.RequestOneofCase.ActivityRequest => Task.Run(
() => ProcessActivityAsync(workItem.ActivityRequest, completionToken, activityHandler, cancellationToken),
@@ -187,7 +178,7 @@ private async Task ReceiveLoopAsync(
activeWorkItems.Add(workItemTask);
// Clean up completed tasks periodically
- if (activeWorkItems.Count > _maxConcurrentWorkItems * 2)
+ if (activeWorkItems.Count > 200)
{
activeWorkItems.RemoveAll(t => t.IsCompleted);
}
@@ -221,8 +212,8 @@ private async Task ReceiveLoopAsync(
///
/// Processes a workflow request work item.
///
- private async Task ProcessWorkflowAsync(OrchestratorRequest request, string completionToken,
- Func> handler, CancellationToken cancellationToken)
+ private async Task ProcessWorkflowAsync(WorkflowRequest request, string completionToken,
+ Func> handler, CancellationToken cancellationToken)
{
await _orchestrationSemaphore.WaitAsync(cancellationToken);
var activeCount = Interlocked.Increment(ref _activeWorkItemCount);
@@ -235,7 +226,7 @@ private async Task ProcessWorkflowAsync(OrchestratorRequest request, string comp
// This try/catch must NOT include the CompleteOrchestratorTaskAsync call below — a transport
// failure during delivery must not be converted into an orchestrator-level failure, as that
// would incorrectly mark a healthy workflow turn as failed.
- OrchestratorResponse result;
+ WorkflowResponse result;
try
{
result = await handler(request, completionToken);
@@ -256,7 +247,9 @@ private async Task ProcessWorkflowAsync(OrchestratorRequest request, string comp
try
{
var grpcCallOptions = CreateCallOptions(cancellationToken);
+#pragma warning disable CS0612 // Deprecated: kept for compatibility with Dapr runtimes < 1.18 where CompleteWorkflowTask is not available.
await _grpcClient.CompleteOrchestratorTaskAsync(result, grpcCallOptions);
+#pragma warning restore CS0612
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
@@ -285,7 +278,7 @@ private async Task ProcessActivityAsync(ActivityRequest request, string completi
try
{
- _logger.LogGrpcProtocolHandlerActivityProcessorStart(request.OrchestrationInstance.InstanceId, request.Name,
+ _logger.LogGrpcProtocolHandlerActivityProcessorStart(request.WorkflowInstance.InstanceId, request.Name,
request.TaskId, activeCount);
// Execute the activity and determine the result (success or application failure).
@@ -305,7 +298,7 @@ private async Task ProcessActivityAsync(ActivityRequest request, string completi
catch (Exception ex)
{
_logger.LogGrpcProtocolHandlerActivityProcessorError(ex, request.Name,
- request.OrchestrationInstance?.InstanceId);
+ request.WorkflowInstance?.InstanceId);
result = CreateActivityFailureResult(request, completionToken, ex);
}
@@ -339,7 +332,7 @@ private async Task ProcessActivityAsync(ActivityRequest request, string completi
private static ActivityResponse CreateActivityFailureResult(ActivityRequest request, string completionToken, Exception ex) =>
new()
{
- InstanceId = request.OrchestrationInstance.InstanceId,
+ InstanceId = request.WorkflowInstance.InstanceId,
TaskId = request.TaskId,
CompletionToken = completionToken,
FailureDetails = new()
@@ -353,18 +346,18 @@ private static ActivityResponse CreateActivityFailureResult(ActivityRequest requ
///
/// Creates a failure result for an orchestrator exception.
///
- private static OrchestratorResponse CreateWorkflowFailureResult(OrchestratorRequest request, string completionToken, Exception ex) =>
+ private static WorkflowResponse CreateWorkflowFailureResult(WorkflowRequest request, string completionToken, Exception ex) =>
new()
{
InstanceId = request.InstanceId,
CompletionToken = completionToken,
Actions =
{
- new OrchestratorAction
+ new WorkflowAction
{
- CompleteOrchestration = new CompleteOrchestrationAction
+ CompleteWorkflow = new CompleteWorkflowAction
{
- OrchestrationStatus = OrchestrationStatus.Failed,
+ WorkflowStatus = OrchestrationStatus.Failed,
FailureDetails = new()
{
ErrorType = ex.GetType().FullName ?? "Exception",
diff --git a/src/Dapr.Workflow/Worker/Internal/TimerOriginHelpers.cs b/src/Dapr.Workflow/Worker/Internal/TimerOriginHelpers.cs
index 4109e5332..94397701f 100644
--- a/src/Dapr.Workflow/Worker/Internal/TimerOriginHelpers.cs
+++ b/src/Dapr.Workflow/Worker/Internal/TimerOriginHelpers.cs
@@ -44,28 +44,28 @@ internal static void SetTimerOrigin(CreateTimerAction action, IMessage origin)
switch (origin)
{
case TimerOriginCreateTimer createTimer:
- action.OriginCreateTimer = createTimer;
+ action.CreateTimer = createTimer;
break;
case TimerOriginExternalEvent externalEvent:
- action.OriginExternalEvent = externalEvent;
+ action.ExternalEvent = externalEvent;
break;
case TimerOriginActivityRetry activityRetry:
- action.OriginActivityRetry = activityRetry;
+ action.ActivityRetry = activityRetry;
break;
case TimerOriginChildWorkflowRetry childWorkflowRetry:
- action.OriginChildWorkflowRetry = childWorkflowRetry;
+ action.ChildWorkflowRetry = childWorkflowRetry;
break;
}
}
///
- /// Determines whether a is an optional external event timer
+ /// Determines whether a is an optional external event timer
/// (sentinel fireAt + ExternalEvent origin).
///
- internal static bool IsOptionalExternalEventTimerAction(OrchestratorAction action)
+ internal static bool IsOptionalExternalEventTimerAction(WorkflowAction action)
{
return action.CreateTimer is { } timer
- && timer.OriginCase == CreateTimerAction.OriginOneofCase.OriginExternalEvent
+ && timer.OriginCase == CreateTimerAction.OriginOneofCase.ExternalEvent
&& timer.FireAt != null
&& timer.FireAt.Equals(ExternalEventIndefiniteFireAt);
}
@@ -76,7 +76,7 @@ internal static bool IsOptionalExternalEventTimerAction(OrchestratorAction actio
///
internal static bool IsOptionalExternalEventTimerCreatedEvent(TimerCreatedEvent timerCreated)
{
- return timerCreated.OriginCase == TimerCreatedEvent.OriginOneofCase.OriginExternalEvent
+ return timerCreated.OriginCase == TimerCreatedEvent.OriginOneofCase.ExternalEvent
&& timerCreated.FireAt != null
&& timerCreated.FireAt.Equals(ExternalEventIndefiniteFireAt);
}
diff --git a/src/Dapr.Workflow/Worker/Internal/WorkflowOrchestrationContext.cs b/src/Dapr.Workflow/Worker/Internal/WorkflowOrchestrationContext.cs
index d62d9ce25..ca3e58068 100644
--- a/src/Dapr.Workflow/Worker/Internal/WorkflowOrchestrationContext.cs
+++ b/src/Dapr.Workflow/Worker/Internal/WorkflowOrchestrationContext.cs
@@ -21,7 +21,6 @@
using Dapr.Common.Serialization;
using Dapr.DurableTask.Protobuf;
using Dapr.Workflow.Client;
-using Dapr.Workflow.Serialization;
using Dapr.Workflow.Versioning;
using Google.Protobuf;
using Microsoft.Extensions.Logging;
@@ -50,7 +49,7 @@ internal sealed class WorkflowOrchestrationContext : WorkflowContext
private readonly Dictionary> _openTasks = [];
private readonly Dictionary _taskIdToExecutionId = [];
private readonly Dictionary _executionIdToTaskId = new(StringComparer.Ordinal);
- private readonly SortedDictionary _pendingActions = [];
+ private readonly SortedDictionary _pendingActions = [];
private readonly IDaprSerializer _workflowSerializer;
private readonly ILogger _logger;
private readonly ILoggerFactory _loggerFactory;
@@ -73,7 +72,6 @@ internal sealed class WorkflowOrchestrationContext : WorkflowContext
private readonly string? _appId;
private readonly PropagatedHistory? _propagatedHistory;
- private readonly IReadOnlyList _ownHistory;
private int _sequenceNumber;
private int _guidCounter;
private object? _customStatus;
@@ -86,7 +84,7 @@ public WorkflowOrchestrationContext(string name, string instanceId, DateTime cur
IDaprSerializer workflowSerializer, ILoggerFactory loggerFactory, WorkflowVersionTracker versionTracker,
string? appId = null, string? executionId = null,
IReadOnlyList? ownHistory = null,
- IEnumerable? incomingPropagatedHistory = null)
+ IEnumerable? incomingPropagatedHistory = null)
{
_workflowSerializer = workflowSerializer;
_loggerFactory = loggerFactory;
@@ -101,10 +99,10 @@ public WorkflowOrchestrationContext(string name, string instanceId, DateTime cur
_currentUtcDateTime = currentUtcDateTime;
_appId = appId; // Necessary for setting the source app ID value on the task router
_versionTracker = versionTracker;
- _ownHistory = ownHistory ?? Array.Empty();
- var propagatedSegments = incomingPropagatedHistory?.ToList();
- _propagatedHistory = propagatedSegments is { Count: > 0 }
- ? new PropagatedHistory(propagatedSegments.Select(ConvertSegment).ToList())
+
+ var propagatedChunks = incomingPropagatedHistory?.ToList();
+ _propagatedHistory = propagatedChunks is { Count: > 0 }
+ ? new PropagatedHistory(propagatedChunks.Select(ConvertChunk).ToList())
: null;
_logger.LogWorkflowContextConstructorSetup(name, instanceId);
@@ -132,7 +130,7 @@ public override bool IsPatched(string patchName)
///
/// Gets the list of pending orchestrator actions to be sent to the Dapr sidecar.
///
- internal IReadOnlyCollection PendingActions => _pendingActions.Values;
+ internal IReadOnlyCollection PendingActions => _pendingActions.Values;
///
/// Gets the custom status set by the workflow, if any.
@@ -176,7 +174,7 @@ private async Task CallActivityInternalAsync(string name, object? input, W
return await HandleHistoryMatch(name, earlyCompletion, taskId);
}
- _pendingActions.Add(taskId, new OrchestratorAction
+ _pendingActions.Add(taskId, new WorkflowAction
{
Id = taskId,
ScheduleTask = new ScheduleTaskAction
@@ -216,7 +214,7 @@ private async Task CreateTimerInternal(
var createTimerAction = new CreateTimerAction { FireAt = fireAt };
SetTimerOrigin(createTimerAction, origin);
- _pendingActions.Add(taskId, new OrchestratorAction
+ _pendingActions.Add(taskId, new WorkflowAction
{
Id = taskId,
CreateTimer = createTimerAction
@@ -375,12 +373,12 @@ public override void SendEvent(string instanceId, string eventName, object paylo
ArgumentException.ThrowIfNullOrWhiteSpace(eventName);
var taskId = _sequenceNumber++;
- _pendingActions.Add(taskId, new OrchestratorAction
+ _pendingActions.Add(taskId, new WorkflowAction
{
Id = taskId,
SendEvent = new SendEventAction
{
- Instance = new OrchestrationInstance { InstanceId = instanceId },
+ Instance = new WorkflowInstance { InstanceId = instanceId },
Name = eventName,
Data = _workflowSerializer.Serialize(payload)
}
@@ -422,7 +420,7 @@ private async Task CallChildWorkflowInternalAsync(
var router = CreateRouter(options?.TargetAppId);
- var createSubOrchestrationAction = new CreateSubOrchestrationAction
+ var createChildWorkflowAction = new CreateChildWorkflowAction
{
Name = workflowName,
InstanceId = childInstanceId,
@@ -434,21 +432,18 @@ private async Task CallChildWorkflowInternalAsync(
var propagationScope = options?.PropagationScope ?? HistoryPropagationScope.None;
if (propagationScope != HistoryPropagationScope.None)
{
- createSubOrchestrationAction.HistoryPropagationScope = propagationScope switch
+ createChildWorkflowAction.HistoryPropagationScope = propagationScope switch
{
HistoryPropagationScope.OwnHistory => Dapr.DurableTask.Protobuf.HistoryPropagationScope.OwnHistory,
HistoryPropagationScope.Lineage => Dapr.DurableTask.Protobuf.HistoryPropagationScope.Lineage,
_ => Dapr.DurableTask.Protobuf.HistoryPropagationScope.None
};
-
- var segments = BuildPropagatedSegments(propagationScope);
- createSubOrchestrationAction.PropagatedHistory.AddRange(segments);
}
- _pendingActions.Add(taskId, new OrchestratorAction
+ _pendingActions.Add(taskId, new WorkflowAction
{
Id = taskId,
- CreateSubOrchestration = createSubOrchestrationAction,
+ CreateChildWorkflow = createChildWorkflowAction,
Router = router
});
@@ -472,12 +467,12 @@ private async Task CallChildWorkflowInternalAsync(
///
public override void ContinueAsNew(object? newInput = null, bool preserveUnprocessedEvents = true)
{
- var action = new OrchestratorAction
+ var action = new WorkflowAction
{
Id = _sequenceNumber++,
- CompleteOrchestration = new CompleteOrchestrationAction
+ CompleteWorkflow = new CompleteWorkflowAction
{
- OrchestrationStatus = OrchestrationStatus.ContinuedAsNew,
+ WorkflowStatus = OrchestrationStatus.ContinuedAsNew,
Result = _workflowSerializer.Serialize(newInput),
}
};
@@ -503,9 +498,9 @@ internal void FinalizeCarryoverEvents()
foreach (var action in _pendingActions.Values)
{
- if (action.CompleteOrchestration?.OrchestrationStatus == OrchestrationStatus.ContinuedAsNew)
+ if (action.CompleteWorkflow?.WorkflowStatus == OrchestrationStatus.ContinuedAsNew)
{
- action.CompleteOrchestration.CarryoverEvents.AddRange(_externalEventBuffer);
+ action.CompleteWorkflow.CarryoverEvents.AddRange(_externalEventBuffer);
return;
}
}
@@ -542,8 +537,8 @@ private Task HandleHistoryMatch(string name, HistoryEvent e, int taskId)
{
{ TaskCompleted: { } completed } => HandleCompletedActivityFromHistory(name, completed),
{ TaskFailed: { } failed } => HandleFailedActivityFromHistory(name, failed),
- { SubOrchestrationInstanceCompleted: { } completed } => HandleCompletedChildWorkflowFromHistory(name, completed),
- { SubOrchestrationInstanceFailed: { } failed } => HandleFailedChildWorkflowFromHistory(name, failed),
+ { ChildWorkflowInstanceCompleted: { } completed } => HandleCompletedChildWorkflowFromHistory(name, completed),
+ { ChildWorkflowInstanceFailed: { } failed } => HandleFailedChildWorkflowFromHistory(name, failed),
_ => throw new InvalidOperationException($"Unexpected history event type for task ID {taskId}")
};
}
@@ -556,7 +551,7 @@ internal void ProcessEvents(IEnumerable events, bool isReplaying)
{
switch (historyEvent)
{
- case { OrchestratorStarted: { } started }:
+ case { WorkflowStarted: { } started }:
HandleOrchestratorStarted(historyEvent, started);
break;
@@ -572,15 +567,15 @@ internal void ProcessEvents(IEnumerable events, bool isReplaying)
HandleActionCompleted(historyEvent, failed.TaskScheduledId);
break;
- case { SubOrchestrationInstanceCreated: {} created }:
+ case { ChildWorkflowInstanceCreated: {} created }:
OnSubOrchestrationCreated(historyEvent, created);
break;
- case { SubOrchestrationInstanceCompleted: { } completed }:
+ case { ChildWorkflowInstanceCompleted: { } completed }:
HandleActionCompleted(historyEvent, completed.TaskScheduledId);
break;
- case { SubOrchestrationInstanceFailed: { } failed }:
+ case { ChildWorkflowInstanceFailed: { } failed }:
HandleActionCompleted(historyEvent, failed.TaskScheduledId);
break;
@@ -603,7 +598,7 @@ internal void ProcessEvents(IEnumerable events, bool isReplaying)
}
}
- private void HandleOrchestratorStarted(HistoryEvent historyEvent, OrchestratorStartedEvent _)
+ private void HandleOrchestratorStarted(HistoryEvent historyEvent, WorkflowStartedEvent _)
{
InitializeNewTurn(historyEvent.Timestamp.ToDateTime());
}
@@ -638,15 +633,14 @@ private void HandleActionCreated(HistoryEvent historyEvent)
/// true if an optional timer was dropped; false otherwise.
private bool TryDropOptionalTimerAt(int eventId)
{
- if (_pendingActions.TryGetValue(eventId, out var pendingAction)
- && pendingAction.CreateTimer != null
- && IsOptionalExternalEventTimerAction(pendingAction))
- {
- DropOptionalExternalEventTimerAt(eventId);
- return true;
- }
+ if (!_pendingActions.TryGetValue(eventId, out var pendingAction)
+ || pendingAction.CreateTimer == null
+ || !IsOptionalExternalEventTimerAction(pendingAction))
+ return false;
+
+ DropOptionalExternalEventTimerAt(eventId);
+ return true;
- return false;
}
///
@@ -663,19 +657,19 @@ private void OnTaskScheduled(HistoryEvent historyEvent)
/// Handles a SubOrchestrationInstanceCreated history event, dropping an optional timer if needed.
///
private void OnSubOrchestrationCreated(HistoryEvent historyEvent,
- SubOrchestrationInstanceCreatedEvent created)
+ ChildWorkflowInstanceCreatedEvent created)
{
TryDropOptionalTimerAt(historyEvent.EventId);
HandleSubOrchestrationCreated(historyEvent, created);
}
-
+
private void HandleSubOrchestrationCreated(HistoryEvent historyEvent,
- SubOrchestrationInstanceCreatedEvent created)
+ ChildWorkflowInstanceCreatedEvent created)
{
// The runtime may assign an EventId that does not match our local taskId.
// Try to correlate using the child instance id (which we control).
var createdEventId = historyEvent.EventId;
-
+
// SubOrchestrationInstanceCreatedEvent should carry the child instance id.
// If it's missing/empty, we can't build a mapping.
if (!string.IsNullOrWhiteSpace(created.InstanceId) &&
@@ -685,16 +679,16 @@ private void HandleSubOrchestrationCreated(HistoryEvent historyEvent,
{
_subOrchestrationCreatedEventIdToParentTaskId[createdEventId] = parentTaskId;
}
-
+
// The "created" history event means the schedule action is no longer pending.
// Remove by parentTaskId (our action id), not by createdEventId.
_pendingActions.Remove(parentTaskId);
-
+
// Optional: prevent unbounded growth
_subOrchestrationInstanceIdToParentTaskId.Remove(created.InstanceId);
return;
}
-
+
// Fallback to old behavior if we can't correlate
_pendingActions.Remove(createdEventId);
}
@@ -735,14 +729,7 @@ private void DropOptionalExternalEventTimerAt(int atId)
}
// Shift all pending actions with id > atId down by one
- var actionsToShift = new List>();
- foreach (var kvp in _pendingActions)
- {
- if (kvp.Key > atId)
- {
- actionsToShift.Add(kvp);
- }
- }
+ var actionsToShift = _pendingActions.Where(kvp => kvp.Key > atId).ToList();
foreach (var kvp in actionsToShift)
{
@@ -757,14 +744,7 @@ private void DropOptionalExternalEventTimerAt(int atId)
}
// Shift open tasks with id > atId down by one
- var tasksToShift = new List>>();
- foreach (var kvp in _openTasks)
- {
- if (kvp.Key > atId)
- {
- tasksToShift.Add(kvp);
- }
- }
+ var tasksToShift = _openTasks.Where(kvp => kvp.Key > atId).ToList();
foreach (var kvp in tasksToShift)
{
@@ -927,7 +907,7 @@ private void RemoveTaskExecutionMapping(int taskId)
/// Handles a child workflow that completed in the workflow history.
///
private Task HandleCompletedChildWorkflowFromHistory(string workflowName,
- SubOrchestrationInstanceCompletedEvent completed)
+ ChildWorkflowInstanceCompletedEvent completed)
{
_logger.LogChildWorkflowCompletedFromHistory(workflowName, InstanceId);
return Task.FromResult(DeserializeResult(completed.Result ?? string.Empty));
@@ -937,7 +917,7 @@ private Task HandleCompletedChildWorkflowFromHistory(string wo
/// Handles a child workflow that failed in the workflow history.
///
private Task HandleFailedChildWorkflowFromHistory(string workflowName,
- SubOrchestrationInstanceFailedEvent failed)
+ ChildWorkflowInstanceFailedEvent failed)
{
_logger.LogChildWorkflowFailedFromHistory(workflowName, InstanceId);
throw new WorkflowTaskFailedException($"Child workflow '{workflowName}' failed",
@@ -1013,55 +993,38 @@ private static WorkflowTaskFailedException CreateTaskFailedException(TaskFailedE
}
///
- /// Builds the list of proto objects to attach to a
- /// based on the requested propagation scope.
+ /// Converts a proto message to a domain .
///
- private IEnumerable BuildPropagatedSegments(HistoryPropagationScope scope)
+ ///
+ /// Each entry is the deterministic byte representation
+ /// of a single ; we parse the bytes here on demand rather than re-marshalling.
+ /// Malformed event bytes are skipped (mapped to nothing) so a single bad event cannot crash the workflow.
+ ///
+ private static PropagatedHistoryEntry ConvertChunk(PropagatedHistoryChunk chunk)
{
- // Always include the current workflow's own history as the first segment
- var ownSegment = new PropagatedHistorySegment
- {
- AppId = _appId ?? string.Empty,
- InstanceId = InstanceId,
- WorkflowName = Name
- };
- ownSegment.Events.AddRange(_ownHistory);
- yield return ownSegment;
-
- // For Lineage, also include the history this workflow received from its ancestors
- if (scope == HistoryPropagationScope.Lineage && _propagatedHistory is not null)
+ var events = new List(chunk.RawEvents.Count);
+ foreach (var rawEvent in chunk.RawEvents)
{
- foreach (var entry in _propagatedHistory.Entries)
+ HistoryEvent? historyEvent;
+ try
{
- var ancestorSegment = new PropagatedHistorySegment
- {
- AppId = entry.AppId,
- InstanceId = entry.InstanceId,
- WorkflowName = entry.WorkflowName
- };
- // Re-encode the domain events back to proto events for forwarding
- // The forwarded events are already proto-sourced; they were stored as domain events
- // so we cannot round-trip them here without the original proto.
- // Instead, forward empty event lists — the metadata (appId/instanceId/workflowName)
- // is the primary useful content for filtering.
- yield return ancestorSegment;
+ historyEvent = HistoryEvent.Parser.ParseFrom(rawEvent);
+ }
+ catch (InvalidProtocolBufferException)
+ {
+ continue;
}
- }
- }
- ///
- /// Converts a proto message to a domain .
- ///
- private static PropagatedHistoryEntry ConvertSegment(PropagatedHistorySegment segment)
- {
- var events = segment.Events
- .Select(e => new PropagatedHistoryEvent(e.EventId, MapEventKind(e), MapTimestamp(e.Timestamp)))
- .ToList();
+ events.Add(new PropagatedHistoryEvent(
+ historyEvent.EventId,
+ MapEventKind(historyEvent),
+ MapTimestamp(historyEvent.Timestamp)));
+ }
return new PropagatedHistoryEntry(
- segment.AppId,
- segment.InstanceId,
- segment.WorkflowName,
+ chunk.AppId,
+ chunk.InstanceId,
+ chunk.WorkflowName,
events);
}
@@ -1076,13 +1039,13 @@ private static PropagatedHistoryEntry ConvertSegment(PropagatedHistorySegment se
HistoryEvent.EventTypeOneofCase.TaskScheduled => HistoryEventKind.TaskScheduled,
HistoryEvent.EventTypeOneofCase.TaskCompleted => HistoryEventKind.TaskCompleted,
HistoryEvent.EventTypeOneofCase.TaskFailed => HistoryEventKind.TaskFailed,
- HistoryEvent.EventTypeOneofCase.SubOrchestrationInstanceCreated => HistoryEventKind.SubOrchestrationInstanceCreated,
- HistoryEvent.EventTypeOneofCase.SubOrchestrationInstanceCompleted => HistoryEventKind.SubOrchestrationInstanceCompleted,
- HistoryEvent.EventTypeOneofCase.SubOrchestrationInstanceFailed => HistoryEventKind.SubOrchestrationInstanceFailed,
+ HistoryEvent.EventTypeOneofCase.ChildWorkflowInstanceCreated => HistoryEventKind.SubOrchestrationInstanceCreated,
+ HistoryEvent.EventTypeOneofCase.ChildWorkflowInstanceCompleted => HistoryEventKind.SubOrchestrationInstanceCompleted,
+ HistoryEvent.EventTypeOneofCase.ChildWorkflowInstanceFailed => HistoryEventKind.SubOrchestrationInstanceFailed,
HistoryEvent.EventTypeOneofCase.TimerCreated => HistoryEventKind.TimerCreated,
HistoryEvent.EventTypeOneofCase.TimerFired => HistoryEventKind.TimerFired,
- HistoryEvent.EventTypeOneofCase.OrchestratorStarted => HistoryEventKind.OrchestratorStarted,
- HistoryEvent.EventTypeOneofCase.OrchestratorCompleted => HistoryEventKind.OrchestratorCompleted,
+ HistoryEvent.EventTypeOneofCase.WorkflowStarted => HistoryEventKind.OrchestratorStarted,
+ HistoryEvent.EventTypeOneofCase.WorkflowCompleted => HistoryEventKind.OrchestratorCompleted,
HistoryEvent.EventTypeOneofCase.EventSent => HistoryEventKind.EventSent,
HistoryEvent.EventTypeOneofCase.EventRaised => HistoryEventKind.EventRaised,
HistoryEvent.EventTypeOneofCase.ContinueAsNew => HistoryEventKind.ContinueAsNew,
@@ -1094,7 +1057,7 @@ private static PropagatedHistoryEntry ConvertSegment(PropagatedHistorySegment se
///
/// Converts a proto Timestamp to a .
///
- private static DateTimeOffset MapTimestamp(Google.Protobuf.WellKnownTypes.Timestamp? timestamp) =>
+ private static DateTimeOffset MapTimestamp(Timestamp? timestamp) =>
timestamp is not null
? new DateTimeOffset(timestamp.ToDateTime(), TimeSpan.Zero)
: DateTimeOffset.MinValue;
diff --git a/src/Dapr.Workflow/Worker/WorkflowWorker.cs b/src/Dapr.Workflow/Worker/WorkflowWorker.cs
index 0d3d82158..e13c1b4ac 100644
--- a/src/Dapr.Workflow/Worker/WorkflowWorker.cs
+++ b/src/Dapr.Workflow/Worker/WorkflowWorker.cs
@@ -20,7 +20,6 @@
using Dapr.Common.Serialization;
using Dapr.DurableTask.Protobuf;
using Dapr.Workflow.Abstractions;
-using Dapr.Workflow.Serialization;
using Dapr.Workflow.Versioning;
using Dapr.Workflow.Worker.Grpc;
using Dapr.Workflow.Worker.Internal;
@@ -41,14 +40,12 @@ internal sealed class WorkflowWorker(
ILoggerFactory loggerFactory,
IDaprSerializer workflowSerializer,
IServiceProvider serviceProvider,
- WorkflowRuntimeOptions options,
IConfiguration? configuration = null) : BackgroundService
{
private readonly TaskHubSidecarService.TaskHubSidecarServiceClient _grpcClient = grpcClient ?? throw new ArgumentNullException(nameof(grpcClient));
private readonly IWorkflowsFactory _workflowsFactory = workflowsFactory ?? throw new ArgumentNullException(nameof(workflowsFactory));
private readonly IServiceProvider _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
private readonly ILogger _logger = loggerFactory.CreateLogger() ?? throw new ArgumentNullException(nameof(loggerFactory));
- private readonly WorkflowRuntimeOptions _options = options ?? throw new ArgumentNullException(nameof(options));
private readonly IDaprSerializer _serializer = workflowSerializer ?? throw new ArgumentNullException(nameof(workflowSerializer));
private readonly string? _daprApiToken = DaprDefaults.GetDefaultDaprApiToken(configuration);
@@ -64,10 +61,10 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
try
{
// Create the protocol handler
- _protocolHandler = new GrpcProtocolHandler(_grpcClient, loggerFactory, _options.MaxConcurrentWorkflows, _options.MaxConcurrentActivities, _daprApiToken);
+ _protocolHandler = new GrpcProtocolHandler(_grpcClient, loggerFactory, _daprApiToken);
// Start processing work items
- await _protocolHandler.StartAsync(HandleOrchestratorResponseAsync, HandleActivityResponseAsync, stoppingToken);
+ await _protocolHandler.StartAsync(HandleWorkflowResponseAsync, HandleActivityResponseAsync, stoppingToken);
}
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
{
@@ -80,7 +77,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
}
}
- private async Task HandleOrchestratorResponseAsync(OrchestratorRequest request, string completionToken)
+ private async Task HandleWorkflowResponseAsync(WorkflowRequest request, string completionToken)
{
_logger.LogWorkerWorkflowHandleOrchestratorRequestStart(request.InstanceId);
@@ -100,19 +97,11 @@ private async Task HandleOrchestratorResponseAsync(Orchest
if (request.RequiresHistoryStreaming)
{
- var streamRequest = new StreamInstanceHistoryRequest
- {
- InstanceId = request.InstanceId,
- ExecutionId = request.ExecutionId,
- ForWorkItemProcessing = true
- };
-
- using var call = _grpcClient.StreamInstanceHistory(streamRequest, CreateCallOptions(CancellationToken.None));
- while (await call.ResponseStream.MoveNext(CancellationToken.None).ConfigureAwait(false))
- {
- var chunk = call.ResponseStream.Current.Events;
- allPastEvents.AddRange(chunk);
- }
+ var streamRequest = new GetInstanceHistoryRequest { InstanceId = request.InstanceId };
+
+ var result = await _grpcClient.GetInstanceHistoryAsync(streamRequest, CreateCallOptions())
+ .ConfigureAwait(false);
+ allPastEvents.AddRange(result.Events);
}
// If the most recent event is `ExecutionTerminated`, acknowledge termination immediately.
@@ -121,17 +110,17 @@ private async Task HandleOrchestratorResponseAsync(Orchest
if (latestEvent?.ExecutionTerminated != null)
{
- return new OrchestratorResponse
+ return new WorkflowResponse
{
InstanceId = request.InstanceId,
CompletionToken = completionToken,
Actions =
{
- new OrchestratorAction
+ new WorkflowAction
{
- CompleteOrchestration = new CompleteOrchestrationAction
+ CompleteWorkflow = new CompleteWorkflowAction
{
- OrchestrationStatus = OrchestrationStatus.Terminated
+ WorkflowStatus = OrchestrationStatus.Terminated
}
}
}
@@ -142,7 +131,7 @@ private async Task HandleOrchestratorResponseAsync(Orchest
// This keeps the workflow paused while still committing the suspension event.
if (latestEvent?.ExecutionSuspended != null)
{
- return new OrchestratorResponse
+ return new WorkflowResponse
{
InstanceId = request.InstanceId,
CompletionToken = completionToken
@@ -178,29 +167,19 @@ private async Task HandleOrchestratorResponseAsync(Orchest
}
}
- if (string.IsNullOrEmpty(workflowName))
- {
- foreach (var e in allPastEvents.Concat(request.NewEvents).Reverse())
- {
- var state = e.HistoryState?.OrchestrationState;
- if (state is null || string.IsNullOrEmpty(state.Name))
- continue;
-
- workflowName = state.Name;
- if (string.IsNullOrEmpty(serializedInput) && !string.IsNullOrEmpty(state.Input))
- {
- serializedInput = state.Input;
- }
-
- break;
- }
- }
+ // if (string.IsNullOrEmpty(workflowName))
+ // {
+ // foreach (var e in allPastEvents.Concat(request.NewEvents).Reverse())
+ // {
+ // var state = e.
+ // }
+ // }
if (string.IsNullOrEmpty(workflowName))
{
foreach (var e in allPastEvents.Concat(request.NewEvents).Reverse())
{
- var versionName = e.OrchestratorStarted?.Version?.Name;
+ var versionName = e.WorkflowStarted?.Version?.Name;
if (!string.IsNullOrEmpty(versionName))
{
workflowName = versionName;
@@ -218,17 +197,17 @@ private async Task HandleOrchestratorResponseAsync(Orchest
else
{
_logger.LogWorkerWorkflowHandleOrchestratorRequestNotInRegistry("");
- return new OrchestratorResponse
+ return new WorkflowResponse
{
InstanceId = request.InstanceId,
CompletionToken = completionToken,
Actions =
{
- new OrchestratorAction
+ new WorkflowAction
{
- CompleteOrchestration = new CompleteOrchestrationAction
+ CompleteWorkflow = new CompleteWorkflowAction
{
- OrchestrationStatus = OrchestrationStatus.Failed,
+ WorkflowStatus = OrchestrationStatus.Failed,
FailureDetails = new()
{
IsNonRetriable = true,
@@ -246,17 +225,17 @@ private async Task HandleOrchestratorResponseAsync(Orchest
if (routerRegistry is not null && !routerRegistry.Contains(workflowName))
{
_logger.LogWorkerWorkflowHandleOrchestratorRequestNotInRegistry(workflowName);
- return new OrchestratorResponse
+ return new WorkflowResponse
{
InstanceId = request.InstanceId,
CompletionToken = completionToken,
Actions =
{
- new OrchestratorAction
+ new WorkflowAction
{
- CompleteOrchestration = new CompleteOrchestrationAction
+ CompleteWorkflow = new CompleteWorkflowAction
{
- OrchestrationStatus = OrchestrationStatus.Failed,
+ WorkflowStatus = OrchestrationStatus.Failed,
FailureDetails = new()
{
IsNonRetriable = true,
@@ -278,17 +257,17 @@ private async Task HandleOrchestratorResponseAsync(Orchest
{
_logger.LogWorkerWorkflowHandleOrchestratorRequestActivationFailed(workflowActivationException, workflowName);
- return new OrchestratorResponse
+ return new WorkflowResponse
{
InstanceId = request.InstanceId,
CompletionToken = completionToken,
Actions =
{
- new OrchestratorAction
+ new WorkflowAction
{
- CompleteOrchestration = new CompleteOrchestrationAction
+ CompleteWorkflow = new CompleteWorkflowAction
{
- OrchestrationStatus = OrchestrationStatus.Failed,
+ WorkflowStatus = OrchestrationStatus.Failed,
FailureDetails = new()
{
IsNonRetriable = true,
@@ -304,17 +283,17 @@ private async Task HandleOrchestratorResponseAsync(Orchest
_logger.LogWorkerWorkflowHandleOrchestratorRequestNotInRegistry(workflowName);
- return new OrchestratorResponse
+ return new WorkflowResponse
{
InstanceId = request.InstanceId,
CompletionToken = completionToken,
Actions =
{
- new OrchestratorAction
+ new WorkflowAction
{
- CompleteOrchestration = new CompleteOrchestrationAction
+ CompleteWorkflow = new CompleteWorkflowAction
{
- OrchestrationStatus = OrchestrationStatus.Failed,
+ WorkflowStatus = OrchestrationStatus.Failed,
FailureDetails = new()
{
IsNonRetriable = true,
@@ -333,14 +312,14 @@ private async Task HandleOrchestratorResponseAsync(Orchest
: DateTime.UtcNow;
var currentTurnStartedEvent = request.NewEvents.Reverse()
- .FirstOrDefault(e => e.OrchestratorStarted != null);
+ .FirstOrDefault(e => e.WorkflowStarted != null);
var currentTurnTimestamp = currentTurnStartedEvent?.Timestamp?.ToDateTime()
?? currentUtcDateTime;
// Initialize the context with the FULL history
- var incomingPropagatedHistory = request.PropagatedHistory.Count > 0
- ? request.PropagatedHistory
+ var incomingPropagatedHistory = request.PropagatedHistory?.Chunks.Count > 0
+ ? request.PropagatedHistory.Chunks
: null;
var context = new WorkflowOrchestrationContext(workflowName, request.InstanceId, currentUtcDateTime,
_serializer, loggerFactory, versionTracker, appId, request.ExecutionId,
@@ -388,17 +367,17 @@ private async Task HandleOrchestratorResponseAsync(Orchest
// If the history processing caused a stall (e.g. via OnOrchestratorStarted), return immediately
if (versionTracker.IsStalled)
{
- return new OrchestratorResponse
+ return new WorkflowResponse
{
InstanceId = request.InstanceId,
CompletionToken = completionToken,
Actions =
{
- new OrchestratorAction
+ new WorkflowAction
{
- CompleteOrchestration = new CompleteOrchestrationAction
+ CompleteWorkflow = new CompleteWorkflowAction
{
- OrchestrationStatus = OrchestrationStatus.Stalled,
+ WorkflowStatus = OrchestrationStatus.Stalled,
Details = versionTracker.StalledEvent?.Description ??
"Workflow stalled due to patch mismatch."
}
@@ -408,7 +387,7 @@ private async Task HandleOrchestratorResponseAsync(Orchest
}
// Get all pending actions from the context
- var response = new OrchestratorResponse
+ var response = new WorkflowResponse
{
InstanceId = request.InstanceId,
CompletionToken = completionToken
@@ -429,7 +408,7 @@ private async Task HandleOrchestratorResponseAsync(Orchest
// If the workflow issued ContinueAsNew, it already queued a completion action; just return it.
if (context.PendingActions.Any(a =>
- a.CompleteOrchestration?.OrchestrationStatus == OrchestrationStatus.ContinuedAsNew))
+ a.CompleteWorkflow?.WorkflowStatus == OrchestrationStatus.ContinuedAsNew))
{
_logger.LogWorkerWorkflowHandleOrchestratorRequestCompleted(workflowName, request.InstanceId);
return response;
@@ -454,23 +433,23 @@ private async Task HandleOrchestratorResponseAsync(Orchest
var output = await runTask.ConfigureAwait(false);
var outputJson = output != null ? _serializer.Serialize(output) : string.Empty;
- response.Actions.Add(new OrchestratorAction
+ response.Actions.Add(new WorkflowAction
{
- CompleteOrchestration = new CompleteOrchestrationAction
+ CompleteWorkflow = new CompleteWorkflowAction
{
Result = outputJson,
- OrchestrationStatus = OrchestrationStatus.Completed
+ WorkflowStatus = OrchestrationStatus.Completed
}
});
}
catch (Exception ex)
{
// Report the failure as an action so Dapr records the workflow as FAILED
- response.Actions.Add(new OrchestratorAction
+ response.Actions.Add(new WorkflowAction
{
- CompleteOrchestration = new CompleteOrchestrationAction
+ CompleteWorkflow = new CompleteWorkflowAction
{
- OrchestrationStatus = OrchestrationStatus.Failed,
+ WorkflowStatus = OrchestrationStatus.Failed,
FailureDetails = new()
{
IsNonRetriable = true,
@@ -489,17 +468,17 @@ private async Task HandleOrchestratorResponseAsync(Orchest
{
_logger.LogWorkerWorkflowHandleOrchestratorRequestFailed(ex, request.InstanceId);
- return new OrchestratorResponse
+ return new WorkflowResponse
{
InstanceId = request.InstanceId,
CompletionToken = completionToken,
Actions =
{
- new OrchestratorAction
+ new WorkflowAction
{
- CompleteOrchestration = new()
+ CompleteWorkflow = new()
{
- OrchestrationStatus = OrchestrationStatus.Failed,
+ WorkflowStatus = OrchestrationStatus.Failed,
FailureDetails = new()
{
ErrorType = ex.GetType().FullName ?? "Exception",
@@ -515,7 +494,7 @@ private async Task HandleOrchestratorResponseAsync(Orchest
private async Task HandleActivityResponseAsync(ActivityRequest request, string completionToken)
{
- _logger.LogWorkerWorkflowHandleActivityRequestStart(request.Name, request.OrchestrationInstance?.InstanceId, request.TaskId);
+ _logger.LogWorkerWorkflowHandleActivityRequestStart(request.Name, request.WorkflowInstance?.InstanceId, request.TaskId);
try
{
@@ -532,7 +511,7 @@ private async Task HandleActivityResponseAsync(ActivityRequest
return new ActivityResponse
{
- InstanceId = request.OrchestrationInstance?.InstanceId ?? string.Empty,
+ InstanceId = request.WorkflowInstance?.InstanceId ?? string.Empty,
TaskId = request.TaskId,
CompletionToken = completionToken,
FailureDetails = new()
@@ -548,7 +527,7 @@ private async Task HandleActivityResponseAsync(ActivityRequest
return new ActivityResponse
{
- InstanceId = request.OrchestrationInstance?.InstanceId ?? string.Empty,
+ InstanceId = request.WorkflowInstance?.InstanceId ?? string.Empty,
TaskId = request.TaskId,
CompletionToken = completionToken,
FailureDetails = new()
@@ -566,7 +545,7 @@ private async Task HandleActivityResponseAsync(ActivityRequest
: request.TaskId.ToString();
var context = new WorkflowActivityContextImpl(activityIdentifier,
- request.OrchestrationInstance?.InstanceId ?? string.Empty, taskExecutionKey);
+ request.WorkflowInstance?.InstanceId ?? string.Empty, taskExecutionKey);
// Restore the trace context provided by the sidecar so Activity.Current is non-null
using var traceActivity = StartActivityFromRequest(request);
@@ -590,7 +569,7 @@ private async Task HandleActivityResponseAsync(ActivityRequest
return new ActivityResponse
{
- InstanceId = request.OrchestrationInstance?.InstanceId ?? string.Empty,
+ InstanceId = request.WorkflowInstance?.InstanceId ?? string.Empty,
TaskId = request.TaskId,
Result = outputJson,
CompletionToken = completionToken
@@ -598,11 +577,11 @@ private async Task HandleActivityResponseAsync(ActivityRequest
}
catch (Exception ex)
{
- _logger.LogWorkerWorkflowHandleActivityRequestFailed(ex, request.Name, request.OrchestrationInstance?.InstanceId);
+ _logger.LogWorkerWorkflowHandleActivityRequestFailed(ex, request.Name, request.WorkflowInstance?.InstanceId);
return new ActivityResponse
{
- InstanceId = request.OrchestrationInstance?.InstanceId ?? string.Empty,
+ InstanceId = request.WorkflowInstance?.InstanceId ?? string.Empty,
TaskId = request.TaskId,
CompletionToken = completionToken,
FailureDetails = new()
@@ -663,6 +642,6 @@ public override async Task StopAsync(CancellationToken cancellationToken)
return act;
}
- private CallOptions CreateCallOptions(CancellationToken cancellationToken) =>
+ private CallOptions CreateCallOptions(CancellationToken cancellationToken = default) =>
DaprClientUtilities.ConfigureGrpcCallOptions(typeof(WorkflowWorker).Assembly, _daprApiToken, cancellationToken);
}
diff --git a/src/Dapr.Workflow/WorkflowRuntimeOptions.cs b/src/Dapr.Workflow/WorkflowRuntimeOptions.cs
index eb43d19a1..15f31f425 100644
--- a/src/Dapr.Workflow/WorkflowRuntimeOptions.cs
+++ b/src/Dapr.Workflow/WorkflowRuntimeOptions.cs
@@ -38,6 +38,7 @@ public sealed class WorkflowRuntimeOptions
/// usage.
///
/// Thrown when value is less than 1.
+ [Obsolete("This property is obsolete and no longer does anything - please use the options on the Dapr runtime instead. This property will be removed in a future SDK release.")]
public int MaxConcurrentWorkflows
{
get => _maxConcurrentWorkflows;
@@ -56,6 +57,7 @@ public int MaxConcurrentWorkflows
/// memory usage.
///
/// Thrown when value is less than 1.
+ [Obsolete("This property is obsolete and no longer does anything - please use the options on the Dapr runtime instead. This property will be removed in a future SDK release.")]
public int MaxConcurrentActivities
{
get => _maxConcurrentActivities;
diff --git a/test/Dapr.IntegrationTest.Workflow/ExternalEventDoesNotBlockConcurrencySlotTests.cs b/test/Dapr.IntegrationTest.Workflow/ExternalEventDoesNotBlockConcurrencySlotTests.cs
deleted file mode 100644
index 8e01aa5a3..000000000
--- a/test/Dapr.IntegrationTest.Workflow/ExternalEventDoesNotBlockConcurrencySlotTests.cs
+++ /dev/null
@@ -1,161 +0,0 @@
-// ------------------------------------------------------------------------
-// Copyright 2025 The Dapr Authors
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-// ------------------------------------------------------------------------
-
-using Dapr.Testcontainers.Common;
-using Dapr.Testcontainers.Harnesses;
-using Dapr.Testcontainers.Xunit.Attributes;
-using Dapr.Workflow;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-
-namespace Dapr.IntegrationTest.Workflow;
-
-///
-/// Verifies that workflows suspended on
-/// do not occupy a concurrency slot, allowing additional workflows to run while the first
-/// batch is waiting.
-///
-public sealed class ExternalEventDoesNotBlockConcurrencySlotTests
-{
- private const string WaitingStatus = "WaitingForEvent";
- private const string EventName = "ContinueSignal";
-
- ///
- /// With set to 3, schedule
- /// 3 workflows that each wait on an external event, then schedule a 4th workflow and
- /// confirm it completes before releasing the waiting ones.
- ///
- [MinimumDaprRuntimeFact("1.17")]
- public async Task FourthWorkflow_ShouldComplete_WhileFirstThreeAreWaitingOnExternalEvent()
- {
- const int concurrencyLimit = 3;
-
- var componentsDir = TestDirectoryManager.CreateTestDirectory("workflow-components");
-
- // Three workflows that will block on an external event.
- var waitingIds = Enumerable.Range(0, concurrencyLimit)
- .Select(_ => Guid.NewGuid().ToString())
- .ToArray();
-
- // One workflow that should run immediately even though the limit is 3.
- var fourthId = Guid.NewGuid().ToString();
-
- await using var environment = await DaprTestEnvironment.CreateWithPooledNetworkAsync(
- needsActorState: true,
- cancellationToken: TestContext.Current.CancellationToken);
- await environment.StartAsync(TestContext.Current.CancellationToken);
-
- var harness = new DaprHarnessBuilder(componentsDir)
- .WithEnvironment(environment)
- .BuildWorkflow();
-
- await using var testApp = await DaprHarnessBuilder.ForHarness(harness)
- .ConfigureServices(builder =>
- {
- builder.Services.AddDaprWorkflowBuilder(
- configureRuntime: opt =>
- {
- opt.MaxConcurrentWorkflows = concurrencyLimit;
- opt.RegisterWorkflow();
- opt.RegisterWorkflow();
- opt.RegisterActivity();
- },
- configureClient: (sp, clientBuilder) =>
- {
- var config = sp.GetRequiredService();
- var grpcEndpoint = config["DAPR_GRPC_ENDPOINT"];
- if (!string.IsNullOrEmpty(grpcEndpoint))
- clientBuilder.UseGrpcEndpoint(grpcEndpoint);
- });
- })
- .BuildAndStartAsync();
-
- using var scope = testApp.CreateScope();
- var daprWorkflowClient = scope.ServiceProvider.GetRequiredService();
-
- // Schedule all three waiting workflows and let them reach their suspended state.
- await Task.WhenAll(waitingIds.Select(id =>
- daprWorkflowClient.ScheduleNewWorkflowAsync(nameof(WaitForEventWorkflow), id, id)));
-
- using var waitCts = new CancellationTokenSource(TimeSpan.FromMinutes(2));
- await Task.WhenAll(waitingIds.Select(id =>
- WaitForCustomStatusAsync(daprWorkflowClient, id, WaitingStatus, waitCts.Token)));
-
- // All three are now suspended. Schedule the fourth, which should not be blocked.
- await daprWorkflowClient.ScheduleNewWorkflowAsync(nameof(EchoWorkflow), fourthId, fourthId);
-
- using var fourthCts = new CancellationTokenSource(TimeSpan.FromMinutes(2));
- var fourthResult = await daprWorkflowClient.WaitForWorkflowCompletionAsync(
- fourthId, getInputsAndOutputs: true, cancellation: fourthCts.Token);
-
- Assert.Equal(WorkflowRuntimeStatus.Completed, fourthResult.RuntimeStatus);
- Assert.Equal(fourthId, fourthResult.ReadOutputAs());
-
- // Release all three waiting workflows.
- await Task.WhenAll(waitingIds.Select(id =>
- daprWorkflowClient.RaiseEventAsync(id, EventName, "released",
- TestContext.Current.CancellationToken)));
-
- using var completionCts = new CancellationTokenSource(TimeSpan.FromMinutes(2));
- var waitingResults = await Task.WhenAll(waitingIds.Select(id =>
- daprWorkflowClient.WaitForWorkflowCompletionAsync(
- id, getInputsAndOutputs: true, cancellation: completionCts.Token)));
-
- foreach (var result in waitingResults)
- {
- Assert.Equal(WorkflowRuntimeStatus.Completed, result.RuntimeStatus);
- Assert.Equal("released", result.ReadOutputAs());
- }
- }
-
- private static async Task WaitForCustomStatusAsync(
- DaprWorkflowClient client,
- string instanceId,
- string expectedStatus,
- CancellationToken cancellationToken)
- {
- while (true)
- {
- cancellationToken.ThrowIfCancellationRequested();
- var state = await client.GetWorkflowStateAsync(
- instanceId, getInputsAndOutputs: true, cancellation: cancellationToken);
- if (state is not null &&
- string.Equals(state.ReadCustomStatusAs(), expectedStatus, StringComparison.Ordinal))
- return;
-
- await Task.Delay(TimeSpan.FromMilliseconds(200), cancellationToken);
- }
- }
-
- /// Waits indefinitely for an external event then returns its payload.
- private sealed class WaitForEventWorkflow : Workflow
- {
- public override async Task RunAsync(WorkflowContext context, string input)
- {
- context.SetCustomStatus(WaitingStatus);
- return await context.WaitForExternalEventAsync(EventName);
- }
- }
-
- private sealed class EchoActivity : WorkflowActivity
- {
- public override Task RunAsync(WorkflowActivityContext context, string input) =>
- Task.FromResult(input);
- }
-
- private sealed class EchoWorkflow : Workflow
- {
- public override async Task RunAsync(WorkflowContext context, string input) =>
- await context.CallActivityAsync(nameof(EchoActivity), input);
- }
-}
diff --git a/test/Dapr.IntegrationTest.Workflow/HistoryPropagationWorkflowTests.cs b/test/Dapr.IntegrationTest.Workflow/HistoryPropagationWorkflowTests.cs
index 82acce252..c8c1655e9 100644
--- a/test/Dapr.IntegrationTest.Workflow/HistoryPropagationWorkflowTests.cs
+++ b/test/Dapr.IntegrationTest.Workflow/HistoryPropagationWorkflowTests.cs
@@ -11,17 +11,12 @@
// limitations under the License.
// ------------------------------------------------------------------------
-using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
using Dapr.Testcontainers.Common;
-using Dapr.Testcontainers.Common.Testing;
using Dapr.Testcontainers.Harnesses;
using Dapr.Testcontainers.Xunit.Attributes;
using Dapr.Workflow;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
-using Xunit;
namespace Dapr.IntegrationTest.Workflow;
@@ -49,12 +44,34 @@ public sealed class HistoryPropagationWorkflowTests
public async Task ShouldCompleteSuccessfully_WithNoPropagationScope()
{
var instanceId = Guid.NewGuid().ToString();
- await using var testApp = await BuildTestAppAsync(
- opt =>
+ var componentsDir = TestDirectoryManager.CreateTestDirectory("workflow-components");
+
+ await using var environment = await DaprTestEnvironment.CreateWithPooledNetworkAsync(
+ needsActorState: true,
+ cancellationToken: TestContext.Current.CancellationToken);
+ await environment.StartAsync(TestContext.Current.CancellationToken);
+
+ var harness = new DaprHarnessBuilder(componentsDir)
+ .WithEnvironment(environment)
+ .BuildWorkflow();
+ await using var testApp = await DaprHarnessBuilder.ForHarness(harness)
+ .ConfigureServices(builder =>
{
- opt.RegisterWorkflow();
- opt.RegisterWorkflow();
- });
+ builder.Services.AddDaprWorkflowBuilder(
+ configureRuntime: opt =>
+ {
+ opt.RegisterWorkflow();
+ opt.RegisterWorkflow();
+ },
+ configureClient: (sp, clientBuilder) =>
+ {
+ var config = sp.GetRequiredService();
+ var grpcEndpoint = config["DAPR_GRPC_ENDPOINT"];
+ if (!string.IsNullOrWhiteSpace(grpcEndpoint))
+ clientBuilder.UseGrpcEndpoint(grpcEndpoint);
+ });
+ })
+ .BuildAndStartAsync();
using var scope = testApp.CreateScope();
var client = scope.ServiceProvider.GetRequiredService();
@@ -79,14 +96,36 @@ public async Task ShouldCompleteSuccessfully_WithNoPropagationScope()
public async Task ShouldCompleteSuccessfully_WithOwnHistoryPropagationScope()
{
var instanceId = Guid.NewGuid().ToString();
- await using var testApp = await BuildTestAppAsync(
- opt =>
+ var componentsDir = TestDirectoryManager.CreateTestDirectory("workflow-components");
+
+ await using var environment = await DaprTestEnvironment.CreateWithPooledNetworkAsync(
+ needsActorState: true,
+ cancellationToken: TestContext.Current.CancellationToken);
+ await environment.StartAsync(TestContext.Current.CancellationToken);
+
+ var harness = new DaprHarnessBuilder(componentsDir)
+ .WithEnvironment(environment)
+ .BuildWorkflow();
+ await using var testApp = await DaprHarnessBuilder.ForHarness(harness)
+ .ConfigureServices(builder =>
{
- opt.RegisterWorkflow();
- opt.RegisterWorkflow();
- opt.RegisterWorkflow();
- opt.RegisterActivity();
- });
+ builder.Services.AddDaprWorkflowBuilder(
+ configureRuntime: opt =>
+ {
+ opt.RegisterWorkflow();
+ opt.RegisterWorkflow();
+ opt.RegisterWorkflow();
+ opt.RegisterActivity();
+ },
+ configureClient: (sp, clientBuilder) =>
+ {
+ var config = sp.GetRequiredService();
+ var grpcEndpoint = config["DAPR_GRPC_ENDPOINT"];
+ if (!string.IsNullOrWhiteSpace(grpcEndpoint))
+ clientBuilder.UseGrpcEndpoint(grpcEndpoint);
+ });
+ })
+ .BuildAndStartAsync();
using var scope = testApp.CreateScope();
var client = scope.ServiceProvider.GetRequiredService();
@@ -106,13 +145,35 @@ public async Task ShouldCompleteSuccessfully_WithOwnHistoryPropagationScope()
public async Task ShouldCompleteSuccessfully_WithLineagePropagationScope()
{
var instanceId = Guid.NewGuid().ToString();
- await using var testApp = await BuildTestAppAsync(
- opt =>
+ var componentsDir = TestDirectoryManager.CreateTestDirectory("workflow-components");
+
+ await using var environment = await DaprTestEnvironment.CreateWithPooledNetworkAsync(
+ needsActorState: true,
+ cancellationToken: TestContext.Current.CancellationToken);
+ await environment.StartAsync(TestContext.Current.CancellationToken);
+
+ var harness = new DaprHarnessBuilder(componentsDir)
+ .WithEnvironment(environment)
+ .BuildWorkflow();
+ await using var testApp = await DaprHarnessBuilder.ForHarness(harness)
+ .ConfigureServices(builder =>
{
- opt.RegisterWorkflow();
- opt.RegisterWorkflow();
- opt.RegisterWorkflow();
- });
+ builder.Services.AddDaprWorkflowBuilder(
+ configureRuntime: opt =>
+ {
+ opt.RegisterWorkflow();
+ opt.RegisterWorkflow();
+ opt.RegisterWorkflow();
+ },
+ configureClient: (sp, clientBuilder) =>
+ {
+ var config = sp.GetRequiredService();
+ var grpcEndpoint = config["DAPR_GRPC_ENDPOINT"];
+ if (!string.IsNullOrWhiteSpace(grpcEndpoint))
+ clientBuilder.UseGrpcEndpoint(grpcEndpoint);
+ });
+ })
+ .BuildAndStartAsync();
using var scope = testApp.CreateScope();
var client = scope.ServiceProvider.GetRequiredService();
@@ -132,12 +193,34 @@ public async Task ShouldCompleteSuccessfully_WithLineagePropagationScope()
public async Task GetPropagatedHistory_ReturnsNull_WhenScheduledWithNoneScope()
{
var instanceId = Guid.NewGuid().ToString();
- await using var testApp = await BuildTestAppAsync(
- opt =>
+ var componentsDir = TestDirectoryManager.CreateTestDirectory("workflow-components");
+
+ await using var environment = await DaprTestEnvironment.CreateWithPooledNetworkAsync(
+ needsActorState: true,
+ cancellationToken: TestContext.Current.CancellationToken);
+ await environment.StartAsync(TestContext.Current.CancellationToken);
+
+ var harness = new DaprHarnessBuilder(componentsDir)
+ .WithEnvironment(environment)
+ .BuildWorkflow();
+ await using var testApp = await DaprHarnessBuilder.ForHarness(harness)
+ .ConfigureServices(builder =>
{
- opt.RegisterWorkflow();
- opt.RegisterWorkflow();
- });
+ builder.Services.AddDaprWorkflowBuilder(
+ configureRuntime: opt =>
+ {
+ opt.RegisterWorkflow();
+ opt.RegisterWorkflow();
+ },
+ configureClient: (sp, clientBuilder) =>
+ {
+ var config = sp.GetRequiredService();
+ var grpcEndpoint = config["DAPR_GRPC_ENDPOINT"];
+ if (!string.IsNullOrWhiteSpace(grpcEndpoint))
+ clientBuilder.UseGrpcEndpoint(grpcEndpoint);
+ });
+ })
+ .BuildAndStartAsync();
using var scope = testApp.CreateScope();
var client = scope.ServiceProvider.GetRequiredService();
@@ -160,28 +243,9 @@ public async Task GetPropagatedHistory_ReturnsNull_WhenScheduledWithNoneScope()
public async Task WithHistoryPropagation_FluentBuilder_WorksCorrectly()
{
var instanceId = Guid.NewGuid().ToString();
- await using var testApp = await BuildTestAppAsync(
- opt =>
- {
- opt.RegisterWorkflow();
- opt.RegisterWorkflow();
- });
-
- using var scope = testApp.CreateScope();
- var client = scope.ServiceProvider.GetRequiredService();
-
- await client.ScheduleNewWorkflowAsync(nameof(FluentBuilderParent), instanceId);
- var result = await client.WaitForWorkflowCompletionAsync(instanceId,
- cancellation: TestContext.Current.CancellationToken);
-
- Assert.Equal(WorkflowRuntimeStatus.Completed, result.RuntimeStatus);
- }
-
- private static async Task BuildTestAppAsync(Action configureRuntime)
- {
var componentsDir = TestDirectoryManager.CreateTestDirectory("workflow-components");
- var environment = await DaprTestEnvironment.CreateWithPooledNetworkAsync(
+ await using var environment = await DaprTestEnvironment.CreateWithPooledNetworkAsync(
needsActorState: true,
cancellationToken: TestContext.Current.CancellationToken);
await environment.StartAsync(TestContext.Current.CancellationToken);
@@ -189,12 +253,15 @@ private static async Task BuildTestAppAsync(Action
{
builder.Services.AddDaprWorkflowBuilder(
- configureRuntime: configureRuntime,
+ configureRuntime: opt =>
+ {
+ opt.RegisterWorkflow();
+ opt.RegisterWorkflow();
+ },
configureClient: (sp, clientBuilder) =>
{
var config = sp.GetRequiredService();
@@ -204,6 +271,15 @@ private static async Task BuildTestAppAsync(Action();
+
+ await client.ScheduleNewWorkflowAsync(nameof(FluentBuilderParent), instanceId);
+ var result = await client.WaitForWorkflowCompletionAsync(instanceId,
+ cancellation: TestContext.Current.CancellationToken);
+
+ Assert.Equal(WorkflowRuntimeStatus.Completed, result.RuntimeStatus);
}
private sealed record PropagationTestResult(
@@ -310,13 +386,18 @@ public override async Task RunAsync(WorkflowContext context, object? input
///
private sealed class PropagatedHistoryReceiver : Workflow