From f95a23adc76c62c014a1e5cd9867ef91a6908bcf Mon Sep 17 00:00:00 2001 From: Andy Staples Date: Fri, 6 Mar 2026 13:51:54 -0700 Subject: [PATCH 1/6] Initial implementation --- durabletask/client.py | 20 ++ durabletask/internal/PROTO_SOURCE_COMMIT_HASH | 3 +- .../internal/orchestrator_service_pb2.py | 308 +++++++++--------- .../internal/orchestrator_service_pb2.pyi | 50 ++- durabletask/testing/README.md | 3 +- durabletask/testing/in_memory_backend.py | 152 ++++++++- durabletask/worker.py | 97 ++++++ tests/durabletask/test_rewind_e2e.py | 300 +++++++++++++++++ 8 files changed, 775 insertions(+), 158 deletions(-) create mode 100644 tests/durabletask/test_rewind_e2e.py diff --git a/durabletask/client.py b/durabletask/client.py index e00ba99..c0c9b9d 100644 --- a/durabletask/client.py +++ b/durabletask/client.py @@ -299,6 +299,26 @@ def resume_orchestration(self, instance_id: str): self._logger.info(f"Resuming instance '{instance_id}'.") self._stub.ResumeInstance(req) + def rewind_orchestration(self, instance_id: str, *, + reason: Optional[str] = None): + """Rewinds a failed orchestration instance to its last known good state. + + Rewind removes failed task and sub-orchestration results from the + orchestration history and replays the orchestration from the last + successful checkpoint. Activities that previously succeeded are + not re-executed; only failed work is retried. + + Args: + instance_id: The ID of the orchestration instance to rewind. + reason: An optional reason string describing why the orchestration is being rewound. + """ + req = pb.RewindInstanceRequest( + instanceId=instance_id, + reason=helpers.get_string_value(reason)) + + self._logger.info(f"Rewinding instance '{instance_id}'.") + self._stub.RewindInstance(req) + def restart_orchestration(self, instance_id: str, *, restart_with_new_instance_id: bool = False) -> str: """Restarts an existing orchestration instance. diff --git a/durabletask/internal/PROTO_SOURCE_COMMIT_HASH b/durabletask/internal/PROTO_SOURCE_COMMIT_HASH index f617f8e..1288660 100644 --- a/durabletask/internal/PROTO_SOURCE_COMMIT_HASH +++ b/durabletask/internal/PROTO_SOURCE_COMMIT_HASH @@ -1,3 +1,4 @@ 443b333f4f65a438dc9eb4f090560d232afec4b7 fd9369c6a03d6af4e95285e432b7c4e943c06970 -026329c53fe6363985655857b9ca848ec7238bd2 \ No newline at end of file +026329c53fe6363985655857b9ca848ec7238bd2 +57930bf659bb4c90cfeae44eaf465e000a67ecf1 // DO NOT MERGE - FEATURE BRANCH \ No newline at end of file diff --git a/durabletask/internal/orchestrator_service_pb2.py b/durabletask/internal/orchestrator_service_pb2.py index 29ba897..f6c0790 100644 --- a/durabletask/internal/orchestrator_service_pb2.py +++ b/durabletask/internal/orchestrator_service_pb2.py @@ -19,7 +19,7 @@ from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n/durabletask/internal/orchestrator_service.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1egoogle/protobuf/wrappers.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1cgoogle/protobuf/struct.proto\"^\n\x15OrchestrationInstance\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x31\n\x0b\x65xecutionId\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\xed\x01\n\x0f\x41\x63tivityRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12-\n\x07version\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x05input\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x35\n\x15orchestrationInstance\x18\x04 \x01(\x0b\x32\x16.OrchestrationInstance\x12\x0e\n\x06taskId\x18\x05 \x01(\x05\x12)\n\x12parentTraceContext\x18\x06 \x01(\x0b\x32\r.TraceContext\"\xaa\x01\n\x10\x41\x63tivityResponse\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x0e\n\x06taskId\x18\x02 \x01(\x05\x12,\n\x06result\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x0e\x66\x61ilureDetails\x18\x04 \x01(\x0b\x32\x13.TaskFailureDetails\x12\x17\n\x0f\x63ompletionToken\x18\x05 \x01(\t\"\xb6\x02\n\x12TaskFailureDetails\x12\x11\n\terrorType\x18\x01 \x01(\t\x12\x14\n\x0c\x65rrorMessage\x18\x02 \x01(\t\x12\x30\n\nstackTrace\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12)\n\x0cinnerFailure\x18\x04 \x01(\x0b\x32\x13.TaskFailureDetails\x12\x16\n\x0eisNonRetriable\x18\x05 \x01(\x08\x12\x37\n\nproperties\x18\x06 \x03(\x0b\x32#.TaskFailureDetails.PropertiesEntry\x1aI\n\x0fPropertiesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12%\n\x05value\x18\x02 \x01(\x0b\x32\x16.google.protobuf.Value:\x02\x38\x01\"\xbf\x01\n\x12ParentInstanceInfo\x12\x17\n\x0ftaskScheduledId\x18\x01 \x01(\x05\x12*\n\x04name\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12-\n\x07version\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x35\n\x15orchestrationInstance\x18\x04 \x01(\x0b\x32\x16.OrchestrationInstance\"i\n\x0cTraceContext\x12\x13\n\x0btraceParent\x18\x01 \x01(\t\x12\x12\n\x06spanID\x18\x02 \x01(\tB\x02\x18\x01\x12\x30\n\ntraceState\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\xe5\x03\n\x15\x45xecutionStartedEvent\x12\x0c\n\x04name\x18\x01 \x01(\t\x12-\n\x07version\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x05input\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x35\n\x15orchestrationInstance\x18\x04 \x01(\x0b\x32\x16.OrchestrationInstance\x12+\n\x0eparentInstance\x18\x05 \x01(\x0b\x32\x13.ParentInstanceInfo\x12;\n\x17scheduledStartTimestamp\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12)\n\x12parentTraceContext\x18\x07 \x01(\x0b\x32\r.TraceContext\x12\x39\n\x13orchestrationSpanID\x18\x08 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12.\n\x04tags\x18\t \x03(\x0b\x32 .ExecutionStartedEvent.TagsEntry\x1a+\n\tTagsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xa7\x01\n\x17\x45xecutionCompletedEvent\x12\x31\n\x13orchestrationStatus\x18\x01 \x01(\x0e\x32\x14.OrchestrationStatus\x12,\n\x06result\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x0e\x66\x61ilureDetails\x18\x03 \x01(\x0b\x32\x13.TaskFailureDetails\"X\n\x18\x45xecutionTerminatedEvent\x12+\n\x05input\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x0f\n\x07recurse\x18\x02 \x01(\x08\"\x83\x02\n\x12TaskScheduledEvent\x12\x0c\n\x04name\x18\x01 \x01(\t\x12-\n\x07version\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x05input\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12)\n\x12parentTraceContext\x18\x04 \x01(\x0b\x32\r.TraceContext\x12+\n\x04tags\x18\x05 \x03(\x0b\x32\x1d.TaskScheduledEvent.TagsEntry\x1a+\n\tTagsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"[\n\x12TaskCompletedEvent\x12\x17\n\x0ftaskScheduledId\x18\x01 \x01(\x05\x12,\n\x06result\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"W\n\x0fTaskFailedEvent\x12\x17\n\x0ftaskScheduledId\x18\x01 \x01(\x05\x12+\n\x0e\x66\x61ilureDetails\x18\x02 \x01(\x0b\x32\x13.TaskFailureDetails\"\xbb\x02\n$SubOrchestrationInstanceCreatedEvent\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12-\n\x07version\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x05input\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12)\n\x12parentTraceContext\x18\x05 \x01(\x0b\x32\r.TraceContext\x12=\n\x04tags\x18\x06 \x03(\x0b\x32/.SubOrchestrationInstanceCreatedEvent.TagsEntry\x1a+\n\tTagsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"o\n&SubOrchestrationInstanceCompletedEvent\x12\x17\n\x0ftaskScheduledId\x18\x01 \x01(\x05\x12,\n\x06result\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"k\n#SubOrchestrationInstanceFailedEvent\x12\x17\n\x0ftaskScheduledId\x18\x01 \x01(\x05\x12+\n\x0e\x66\x61ilureDetails\x18\x02 \x01(\x0b\x32\x13.TaskFailureDetails\"?\n\x11TimerCreatedEvent\x12*\n\x06\x66ireAt\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"N\n\x0fTimerFiredEvent\x12*\n\x06\x66ireAt\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x0f\n\x07timerId\x18\x02 \x01(\x05\"\x1a\n\x18OrchestratorStartedEvent\"\x1c\n\x1aOrchestratorCompletedEvent\"_\n\x0e\x45ventSentEvent\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12+\n\x05input\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"M\n\x10\x45ventRaisedEvent\x12\x0c\n\x04name\x18\x01 \x01(\t\x12+\n\x05input\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\":\n\x0cGenericEvent\x12*\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"D\n\x11HistoryStateEvent\x12/\n\x12orchestrationState\x18\x01 \x01(\x0b\x32\x13.OrchestrationState\"A\n\x12\x43ontinueAsNewEvent\x12+\n\x05input\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"F\n\x17\x45xecutionSuspendedEvent\x12+\n\x05input\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"D\n\x15\x45xecutionResumedEvent\x12+\n\x05input\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\xdc\x01\n\x1c\x45ntityOperationSignaledEvent\x12\x11\n\trequestId\x18\x01 \x01(\t\x12\x11\n\toperation\x18\x02 \x01(\t\x12\x31\n\rscheduledTime\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12+\n\x05input\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x36\n\x10targetInstanceId\x18\x05 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\xcb\x02\n\x1a\x45ntityOperationCalledEvent\x12\x11\n\trequestId\x18\x01 \x01(\t\x12\x11\n\toperation\x18\x02 \x01(\t\x12\x31\n\rscheduledTime\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12+\n\x05input\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x36\n\x10parentInstanceId\x18\x05 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x37\n\x11parentExecutionId\x18\x06 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x36\n\x10targetInstanceId\x18\x07 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\x90\x01\n\x18\x45ntityLockRequestedEvent\x12\x19\n\x11\x63riticalSectionId\x18\x01 \x01(\t\x12\x0f\n\x07lockSet\x18\x02 \x03(\t\x12\x10\n\x08position\x18\x03 \x01(\x05\x12\x36\n\x10parentInstanceId\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"`\n\x1d\x45ntityOperationCompletedEvent\x12\x11\n\trequestId\x18\x01 \x01(\t\x12,\n\x06output\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\\\n\x1a\x45ntityOperationFailedEvent\x12\x11\n\trequestId\x18\x01 \x01(\t\x12+\n\x0e\x66\x61ilureDetails\x18\x02 \x01(\x0b\x32\x13.TaskFailureDetails\"\xa2\x01\n\x15\x45ntityUnlockSentEvent\x12\x19\n\x11\x63riticalSectionId\x18\x01 \x01(\t\x12\x36\n\x10parentInstanceId\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x36\n\x10targetInstanceId\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"3\n\x16\x45ntityLockGrantedEvent\x12\x19\n\x11\x63riticalSectionId\x18\x01 \x01(\t\"\xed\x03\n\x15\x45xecutionRewoundEvent\x12,\n\x06reason\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x37\n\x11parentExecutionId\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x30\n\ninstanceId\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12)\n\x12parentTraceContext\x18\x04 \x01(\x0b\x32\r.TraceContext\x12*\n\x04name\x18\x05 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12-\n\x07version\x18\x06 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x05input\x18\x07 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x0eparentInstance\x18\x08 \x01(\x0b\x32\x13.ParentInstanceInfo\x12.\n\x04tags\x18\t \x03(\x0b\x32 .ExecutionRewoundEvent.TagsEntry\x1a+\n\tTagsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xe0\x0c\n\x0cHistoryEvent\x12\x0f\n\x07\x65ventId\x18\x01 \x01(\x05\x12-\n\ttimestamp\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x32\n\x10\x65xecutionStarted\x18\x03 \x01(\x0b\x32\x16.ExecutionStartedEventH\x00\x12\x36\n\x12\x65xecutionCompleted\x18\x04 \x01(\x0b\x32\x18.ExecutionCompletedEventH\x00\x12\x38\n\x13\x65xecutionTerminated\x18\x05 \x01(\x0b\x32\x19.ExecutionTerminatedEventH\x00\x12,\n\rtaskScheduled\x18\x06 \x01(\x0b\x32\x13.TaskScheduledEventH\x00\x12,\n\rtaskCompleted\x18\x07 \x01(\x0b\x32\x13.TaskCompletedEventH\x00\x12&\n\ntaskFailed\x18\x08 \x01(\x0b\x32\x10.TaskFailedEventH\x00\x12P\n\x1fsubOrchestrationInstanceCreated\x18\t \x01(\x0b\x32%.SubOrchestrationInstanceCreatedEventH\x00\x12T\n!subOrchestrationInstanceCompleted\x18\n \x01(\x0b\x32\'.SubOrchestrationInstanceCompletedEventH\x00\x12N\n\x1esubOrchestrationInstanceFailed\x18\x0b \x01(\x0b\x32$.SubOrchestrationInstanceFailedEventH\x00\x12*\n\x0ctimerCreated\x18\x0c \x01(\x0b\x32\x12.TimerCreatedEventH\x00\x12&\n\ntimerFired\x18\r \x01(\x0b\x32\x10.TimerFiredEventH\x00\x12\x38\n\x13orchestratorStarted\x18\x0e \x01(\x0b\x32\x19.OrchestratorStartedEventH\x00\x12<\n\x15orchestratorCompleted\x18\x0f \x01(\x0b\x32\x1b.OrchestratorCompletedEventH\x00\x12$\n\teventSent\x18\x10 \x01(\x0b\x32\x0f.EventSentEventH\x00\x12(\n\x0b\x65ventRaised\x18\x11 \x01(\x0b\x32\x11.EventRaisedEventH\x00\x12%\n\x0cgenericEvent\x18\x12 \x01(\x0b\x32\r.GenericEventH\x00\x12*\n\x0chistoryState\x18\x13 \x01(\x0b\x32\x12.HistoryStateEventH\x00\x12,\n\rcontinueAsNew\x18\x14 \x01(\x0b\x32\x13.ContinueAsNewEventH\x00\x12\x36\n\x12\x65xecutionSuspended\x18\x15 \x01(\x0b\x32\x18.ExecutionSuspendedEventH\x00\x12\x32\n\x10\x65xecutionResumed\x18\x16 \x01(\x0b\x32\x16.ExecutionResumedEventH\x00\x12@\n\x17\x65ntityOperationSignaled\x18\x17 \x01(\x0b\x32\x1d.EntityOperationSignaledEventH\x00\x12<\n\x15\x65ntityOperationCalled\x18\x18 \x01(\x0b\x32\x1b.EntityOperationCalledEventH\x00\x12\x42\n\x18\x65ntityOperationCompleted\x18\x19 \x01(\x0b\x32\x1e.EntityOperationCompletedEventH\x00\x12<\n\x15\x65ntityOperationFailed\x18\x1a \x01(\x0b\x32\x1b.EntityOperationFailedEventH\x00\x12\x38\n\x13\x65ntityLockRequested\x18\x1b \x01(\x0b\x32\x19.EntityLockRequestedEventH\x00\x12\x34\n\x11\x65ntityLockGranted\x18\x1c \x01(\x0b\x32\x17.EntityLockGrantedEventH\x00\x12\x32\n\x10\x65ntityUnlockSent\x18\x1d \x01(\x0b\x32\x16.EntityUnlockSentEventH\x00\x12\x32\n\x10\x65xecutionRewound\x18\x1e \x01(\x0b\x32\x16.ExecutionRewoundEventH\x00\x42\x0b\n\teventType\"\x83\x02\n\x12ScheduleTaskAction\x12\x0c\n\x04name\x18\x01 \x01(\t\x12-\n\x07version\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x05input\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x04tags\x18\x04 \x03(\x0b\x32\x1d.ScheduleTaskAction.TagsEntry\x12)\n\x12parentTraceContext\x18\x05 \x01(\x0b\x32\r.TraceContext\x1a+\n\tTagsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xab\x02\n\x1c\x43reateSubOrchestrationAction\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12-\n\x07version\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x05input\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12)\n\x12parentTraceContext\x18\x05 \x01(\x0b\x32\r.TraceContext\x12\x35\n\x04tags\x18\x06 \x03(\x0b\x32\'.CreateSubOrchestrationAction.TagsEntry\x1a+\n\tTagsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"?\n\x11\x43reateTimerAction\x12*\n\x06\x66ireAt\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"u\n\x0fSendEventAction\x12(\n\x08instance\x18\x01 \x01(\x0b\x32\x16.OrchestrationInstance\x12\x0c\n\x04name\x18\x02 \x01(\t\x12*\n\x04\x64\x61ta\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\x97\x03\n\x1b\x43ompleteOrchestrationAction\x12\x31\n\x13orchestrationStatus\x18\x01 \x01(\x0e\x32\x14.OrchestrationStatus\x12,\n\x06result\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12-\n\x07\x64\x65tails\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x30\n\nnewVersion\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12&\n\x0f\x63\x61rryoverEvents\x18\x05 \x03(\x0b\x32\r.HistoryEvent\x12+\n\x0e\x66\x61ilureDetails\x18\x06 \x01(\x0b\x32\x13.TaskFailureDetails\x12\x34\n\x04tags\x18\x07 \x03(\x0b\x32&.CompleteOrchestrationAction.TagsEntry\x1a+\n\tTagsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"q\n\x1cTerminateOrchestrationAction\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12,\n\x06reason\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x0f\n\x07recurse\x18\x03 \x01(\x08\"\x9c\x02\n\x17SendEntityMessageAction\x12@\n\x17\x65ntityOperationSignaled\x18\x01 \x01(\x0b\x32\x1d.EntityOperationSignaledEventH\x00\x12<\n\x15\x65ntityOperationCalled\x18\x02 \x01(\x0b\x32\x1b.EntityOperationCalledEventH\x00\x12\x38\n\x13\x65ntityLockRequested\x18\x03 \x01(\x0b\x32\x19.EntityLockRequestedEventH\x00\x12\x32\n\x10\x65ntityUnlockSent\x18\x04 \x01(\x0b\x32\x16.EntityUnlockSentEventH\x00\x42\x13\n\x11\x45ntityMessageType\"\xb1\x03\n\x12OrchestratorAction\x12\n\n\x02id\x18\x01 \x01(\x05\x12+\n\x0cscheduleTask\x18\x02 \x01(\x0b\x32\x13.ScheduleTaskActionH\x00\x12?\n\x16\x63reateSubOrchestration\x18\x03 \x01(\x0b\x32\x1d.CreateSubOrchestrationActionH\x00\x12)\n\x0b\x63reateTimer\x18\x04 \x01(\x0b\x32\x12.CreateTimerActionH\x00\x12%\n\tsendEvent\x18\x05 \x01(\x0b\x32\x10.SendEventActionH\x00\x12=\n\x15\x63ompleteOrchestration\x18\x06 \x01(\x0b\x32\x1c.CompleteOrchestrationActionH\x00\x12?\n\x16terminateOrchestration\x18\x07 \x01(\x0b\x32\x1d.TerminateOrchestrationActionH\x00\x12\x35\n\x11sendEntityMessage\x18\x08 \x01(\x0b\x32\x18.SendEntityMessageActionH\x00\x42\x18\n\x16orchestratorActionType\"|\n\x19OrchestrationTraceContext\x12,\n\x06spanID\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x31\n\rspanStartTime\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xc0\x03\n\x13OrchestratorRequest\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x31\n\x0b\x65xecutionId\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12!\n\npastEvents\x18\x03 \x03(\x0b\x32\r.HistoryEvent\x12 \n\tnewEvents\x18\x04 \x03(\x0b\x32\r.HistoryEvent\x12\x37\n\x10\x65ntityParameters\x18\x05 \x01(\x0b\x32\x1d.OrchestratorEntityParameters\x12 \n\x18requiresHistoryStreaming\x18\x06 \x01(\x08\x12\x38\n\nproperties\x18\x07 \x03(\x0b\x32$.OrchestratorRequest.PropertiesEntry\x12=\n\x19orchestrationTraceContext\x18\x08 \x01(\x0b\x32\x1a.OrchestrationTraceContext\x1aI\n\x0fPropertiesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12%\n\x05value\x18\x02 \x01(\x0b\x32\x16.google.protobuf.Value:\x02\x38\x01\"\xf2\x02\n\x14OrchestratorResponse\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12$\n\x07\x61\x63tions\x18\x02 \x03(\x0b\x32\x13.OrchestratorAction\x12\x32\n\x0c\x63ustomStatus\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x17\n\x0f\x63ompletionToken\x18\x04 \x01(\t\x12\x37\n\x12numEventsProcessed\x18\x05 \x01(\x0b\x32\x1b.google.protobuf.Int32Value\x12=\n\x19orchestrationTraceContext\x18\x06 \x01(\x0b\x32\x1a.OrchestrationTraceContext\x12\x17\n\x0frequiresHistory\x18\x07 \x01(\x08\x12\x11\n\tisPartial\x18\x08 \x01(\x08\x12/\n\nchunkIndex\x18\t \x01(\x0b\x32\x1b.google.protobuf.Int32Value\"\xff\x03\n\x15\x43reateInstanceRequest\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12-\n\x07version\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x05input\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12;\n\x17scheduledStartTimestamp\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12?\n\x1aorchestrationIdReusePolicy\x18\x06 \x01(\x0b\x32\x1b.OrchestrationIdReusePolicy\x12\x31\n\x0b\x65xecutionId\x18\x07 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12.\n\x04tags\x18\x08 \x03(\x0b\x32 .CreateInstanceRequest.TagsEntry\x12)\n\x12parentTraceContext\x18\t \x01(\x0b\x32\r.TraceContext\x12/\n\x0brequestTime\x18\n \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x1a+\n\tTagsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"S\n\x1aOrchestrationIdReusePolicy\x12/\n\x11replaceableStatus\x18\x01 \x03(\x0e\x32\x14.OrchestrationStatusJ\x04\x08\x02\x10\x03\",\n\x16\x43reateInstanceResponse\x12\x12\n\ninstanceId\x18\x01 \x01(\t\"E\n\x12GetInstanceRequest\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x1b\n\x13getInputsAndOutputs\x18\x02 \x01(\x08\"V\n\x13GetInstanceResponse\x12\x0e\n\x06\x65xists\x18\x01 \x01(\x08\x12/\n\x12orchestrationState\x18\x02 \x01(\x0b\x32\x13.OrchestrationState\"Y\n\x15RewindInstanceRequest\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12,\n\x06reason\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\x18\n\x16RewindInstanceResponse\"\xfe\x05\n\x12OrchestrationState\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12-\n\x07version\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x31\n\x13orchestrationStatus\x18\x04 \x01(\x0e\x32\x14.OrchestrationStatus\x12;\n\x17scheduledStartTimestamp\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x34\n\x10\x63reatedTimestamp\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x38\n\x14lastUpdatedTimestamp\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12+\n\x05input\x18\x08 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12,\n\x06output\x18\t \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x32\n\x0c\x63ustomStatus\x18\n \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x0e\x66\x61ilureDetails\x18\x0b \x01(\x0b\x32\x13.TaskFailureDetails\x12\x31\n\x0b\x65xecutionId\x18\x0c \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x36\n\x12\x63ompletedTimestamp\x18\r \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x36\n\x10parentInstanceId\x18\x0e \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x04tags\x18\x0f \x03(\x0b\x32\x1d.OrchestrationState.TagsEntry\x1a+\n\tTagsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"b\n\x11RaiseEventRequest\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12+\n\x05input\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\x14\n\x12RaiseEventResponse\"g\n\x10TerminateRequest\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12,\n\x06output\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x11\n\trecursive\x18\x03 \x01(\x08\"\x13\n\x11TerminateResponse\"R\n\x0eSuspendRequest\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12,\n\x06reason\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\x11\n\x0fSuspendResponse\"Q\n\rResumeRequest\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12,\n\x06reason\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\x10\n\x0eResumeResponse\"6\n\x15QueryInstancesRequest\x12\x1d\n\x05query\x18\x01 \x01(\x0b\x32\x0e.InstanceQuery\"\x82\x03\n\rInstanceQuery\x12+\n\rruntimeStatus\x18\x01 \x03(\x0e\x32\x14.OrchestrationStatus\x12\x33\n\x0f\x63reatedTimeFrom\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x31\n\rcreatedTimeTo\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x32\n\x0ctaskHubNames\x18\x04 \x03(\x0b\x32\x1c.google.protobuf.StringValue\x12\x18\n\x10maxInstanceCount\x18\x05 \x01(\x05\x12\x37\n\x11\x63ontinuationToken\x18\x06 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x36\n\x10instanceIdPrefix\x18\x07 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x1d\n\x15\x66\x65tchInputsAndOutputs\x18\x08 \x01(\x08\"\x82\x01\n\x16QueryInstancesResponse\x12/\n\x12orchestrationState\x18\x01 \x03(\x0b\x32\x13.OrchestrationState\x12\x37\n\x11\x63ontinuationToken\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\xfa\x01\n\x16ListInstanceIdsRequest\x12+\n\rruntimeStatus\x18\x01 \x03(\x0e\x32\x14.OrchestrationStatus\x12\x35\n\x11\x63ompletedTimeFrom\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x33\n\x0f\x63ompletedTimeTo\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x10\n\x08pageSize\x18\x04 \x01(\x05\x12\x35\n\x0flastInstanceKey\x18\x05 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"e\n\x17ListInstanceIdsResponse\x12\x13\n\x0binstanceIds\x18\x01 \x03(\t\x12\x35\n\x0flastInstanceKey\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\xc2\x01\n\x15PurgeInstancesRequest\x12\x14\n\ninstanceId\x18\x01 \x01(\tH\x00\x12\x33\n\x13purgeInstanceFilter\x18\x02 \x01(\x0b\x32\x14.PurgeInstanceFilterH\x00\x12\'\n\rinstanceBatch\x18\x04 \x01(\x0b\x32\x0e.InstanceBatchH\x00\x12\x11\n\trecursive\x18\x03 \x01(\x08\x12\x17\n\x0fisOrchestration\x18\x05 \x01(\x08\x42\t\n\x07request\"\xaa\x01\n\x13PurgeInstanceFilter\x12\x33\n\x0f\x63reatedTimeFrom\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x31\n\rcreatedTimeTo\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12+\n\rruntimeStatus\x18\x03 \x03(\x0e\x32\x14.OrchestrationStatus\"f\n\x16PurgeInstancesResponse\x12\x1c\n\x14\x64\x65letedInstanceCount\x18\x01 \x01(\x05\x12.\n\nisComplete\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.BoolValue\"N\n\x16RestartInstanceRequest\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12 \n\x18restartWithNewInstanceId\x18\x02 \x01(\x08\"-\n\x17RestartInstanceResponse\x12\x12\n\ninstanceId\x18\x01 \x01(\t\"0\n\x14\x43reateTaskHubRequest\x12\x18\n\x10recreateIfExists\x18\x01 \x01(\x08\"\x17\n\x15\x43reateTaskHubResponse\"\x16\n\x14\x44\x65leteTaskHubRequest\"\x17\n\x15\x44\x65leteTaskHubResponse\"\x86\x02\n\x13SignalEntityRequest\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12+\n\x05input\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x11\n\trequestId\x18\x04 \x01(\t\x12\x31\n\rscheduledTime\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12)\n\x12parentTraceContext\x18\x06 \x01(\x0b\x32\r.TraceContext\x12/\n\x0brequestTime\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\x16\n\x14SignalEntityResponse\"<\n\x10GetEntityRequest\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x14\n\x0cincludeState\x18\x02 \x01(\x08\"D\n\x11GetEntityResponse\x12\x0e\n\x06\x65xists\x18\x01 \x01(\x08\x12\x1f\n\x06\x65ntity\x18\x02 \x01(\x0b\x32\x0f.EntityMetadata\"\xcb\x02\n\x0b\x45ntityQuery\x12:\n\x14instanceIdStartsWith\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x34\n\x10lastModifiedFrom\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x32\n\x0elastModifiedTo\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x14\n\x0cincludeState\x18\x04 \x01(\x08\x12\x18\n\x10includeTransient\x18\x05 \x01(\x08\x12-\n\x08pageSize\x18\x06 \x01(\x0b\x32\x1b.google.protobuf.Int32Value\x12\x37\n\x11\x63ontinuationToken\x18\x07 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"3\n\x14QueryEntitiesRequest\x12\x1b\n\x05query\x18\x01 \x01(\x0b\x32\x0c.EntityQuery\"s\n\x15QueryEntitiesResponse\x12!\n\x08\x65ntities\x18\x01 \x03(\x0b\x32\x0f.EntityMetadata\x12\x37\n\x11\x63ontinuationToken\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\xdb\x01\n\x0e\x45ntityMetadata\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x34\n\x10lastModifiedTime\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x18\n\x10\x62\x61\x63klogQueueSize\x18\x03 \x01(\x05\x12.\n\x08lockedBy\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x35\n\x0fserializedState\x18\x05 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\x8f\x01\n\x19\x43leanEntityStorageRequest\x12\x37\n\x11\x63ontinuationToken\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x1b\n\x13removeEmptyEntities\x18\x02 \x01(\x08\x12\x1c\n\x14releaseOrphanedLocks\x18\x03 \x01(\x08\"\x92\x01\n\x1a\x43leanEntityStorageResponse\x12\x37\n\x11\x63ontinuationToken\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x1c\n\x14\x65mptyEntitiesRemoved\x18\x02 \x01(\x05\x12\x1d\n\x15orphanedLocksReleased\x18\x03 \x01(\x05\"]\n\x1cOrchestratorEntityParameters\x12=\n\x1a\x65ntityMessageReorderWindow\x18\x01 \x01(\x0b\x32\x19.google.protobuf.Duration\"\x86\x02\n\x12\x45ntityBatchRequest\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x31\n\x0b\x65ntityState\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12%\n\noperations\x18\x03 \x03(\x0b\x32\x11.OperationRequest\x12\x37\n\nproperties\x18\x04 \x03(\x0b\x32#.EntityBatchRequest.PropertiesEntry\x1aI\n\x0fPropertiesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12%\n\x05value\x18\x02 \x01(\x0b\x32\x16.google.protobuf.Value:\x02\x38\x01\"\x91\x02\n\x11\x45ntityBatchResult\x12!\n\x07results\x18\x01 \x03(\x0b\x32\x10.OperationResult\x12!\n\x07\x61\x63tions\x18\x02 \x03(\x0b\x32\x10.OperationAction\x12\x31\n\x0b\x65ntityState\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x0e\x66\x61ilureDetails\x18\x04 \x01(\x0b\x32\x13.TaskFailureDetails\x12\x17\n\x0f\x63ompletionToken\x18\x05 \x01(\t\x12&\n\x0eoperationInfos\x18\x06 \x03(\x0b\x32\x0e.OperationInfo\x12\x15\n\rrequiresState\x18\x07 \x01(\x08\"\x95\x01\n\rEntityRequest\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x13\n\x0b\x65xecutionId\x18\x02 \x01(\t\x12\x31\n\x0b\x65ntityState\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12(\n\x11operationRequests\x18\x04 \x03(\x0b\x32\r.HistoryEvent\"\x8a\x01\n\x10OperationRequest\x12\x11\n\toperation\x18\x01 \x01(\t\x12\x11\n\trequestId\x18\x02 \x01(\t\x12+\n\x05input\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12#\n\x0ctraceContext\x18\x04 \x01(\x0b\x32\r.TraceContext\"w\n\x0fOperationResult\x12*\n\x07success\x18\x01 \x01(\x0b\x32\x17.OperationResultSuccessH\x00\x12*\n\x07\x66\x61ilure\x18\x02 \x01(\x0b\x32\x17.OperationResultFailureH\x00\x42\x0c\n\nresultType\"W\n\rOperationInfo\x12\x11\n\trequestId\x18\x01 \x01(\t\x12\x33\n\x13responseDestination\x18\x02 \x01(\x0b\x32\x16.OrchestrationInstance\"\xa8\x01\n\x16OperationResultSuccess\x12,\n\x06result\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x30\n\x0cstartTimeUtc\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\nendTimeUtc\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xa7\x01\n\x16OperationResultFailure\x12+\n\x0e\x66\x61ilureDetails\x18\x01 \x01(\x0b\x32\x13.TaskFailureDetails\x12\x30\n\x0cstartTimeUtc\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\nendTimeUtc\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\x9c\x01\n\x0fOperationAction\x12\n\n\x02id\x18\x01 \x01(\x05\x12\'\n\nsendSignal\x18\x02 \x01(\x0b\x32\x11.SendSignalActionH\x00\x12=\n\x15startNewOrchestration\x18\x03 \x01(\x0b\x32\x1c.StartNewOrchestrationActionH\x00\x42\x15\n\x13operationActionType\"\xf0\x01\n\x10SendSignalAction\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12+\n\x05input\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x31\n\rscheduledTime\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12/\n\x0brequestTime\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12)\n\x12parentTraceContext\x18\x06 \x01(\x0b\x32\r.TraceContext\"\xaa\x02\n\x1bStartNewOrchestrationAction\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12-\n\x07version\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x05input\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x31\n\rscheduledTime\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12/\n\x0brequestTime\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12)\n\x12parentTraceContext\x18\x07 \x01(\x0b\x32\r.TraceContext\"5\n\x1a\x41\x62\x61ndonActivityTaskRequest\x12\x17\n\x0f\x63ompletionToken\x18\x01 \x01(\t\"\x1d\n\x1b\x41\x62\x61ndonActivityTaskResponse\":\n\x1f\x41\x62\x61ndonOrchestrationTaskRequest\x12\x17\n\x0f\x63ompletionToken\x18\x01 \x01(\t\"\"\n AbandonOrchestrationTaskResponse\"3\n\x18\x41\x62\x61ndonEntityTaskRequest\x12\x17\n\x0f\x63ompletionToken\x18\x01 \x01(\t\"\x1b\n\x19\x41\x62\x61ndonEntityTaskResponse\"\x83\x01\n,SkipGracefulOrchestrationTerminationsRequest\x12%\n\rinstanceBatch\x18\x01 \x01(\x0b\x32\x0e.InstanceBatch\x12,\n\x06reason\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"P\n-SkipGracefulOrchestrationTerminationsResponse\x12\x1f\n\x17unterminatedInstanceIds\x18\x01 \x03(\t\"\xb9\x01\n\x13GetWorkItemsRequest\x12+\n#maxConcurrentOrchestrationWorkItems\x18\x01 \x01(\x05\x12&\n\x1emaxConcurrentActivityWorkItems\x18\x02 \x01(\x05\x12$\n\x1cmaxConcurrentEntityWorkItems\x18\x03 \x01(\x05\x12\'\n\x0c\x63\x61pabilities\x18\n \x03(\x0e\x32\x11.WorkerCapability\"\x8c\x02\n\x08WorkItem\x12\x33\n\x13orchestratorRequest\x18\x01 \x01(\x0b\x32\x14.OrchestratorRequestH\x00\x12+\n\x0f\x61\x63tivityRequest\x18\x02 \x01(\x0b\x32\x10.ActivityRequestH\x00\x12,\n\rentityRequest\x18\x03 \x01(\x0b\x32\x13.EntityBatchRequestH\x00\x12!\n\nhealthPing\x18\x04 \x01(\x0b\x32\x0b.HealthPingH\x00\x12)\n\x0f\x65ntityRequestV2\x18\x05 \x01(\x0b\x32\x0e.EntityRequestH\x00\x12\x17\n\x0f\x63ompletionToken\x18\n \x01(\tB\t\n\x07request\"\x16\n\x14\x43ompleteTaskResponse\"\x0c\n\nHealthPing\"\x84\x01\n\x1cStreamInstanceHistoryRequest\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x31\n\x0b\x65xecutionId\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x1d\n\x15\x66orWorkItemProcessing\x18\x03 \x01(\x08\"-\n\x0cHistoryChunk\x12\x1d\n\x06\x65vents\x18\x01 \x03(\x0b\x32\r.HistoryEvent\"$\n\rInstanceBatch\x12\x13\n\x0binstanceIds\x18\x01 \x03(\t*\xb5\x02\n\x13OrchestrationStatus\x12 \n\x1cORCHESTRATION_STATUS_RUNNING\x10\x00\x12\"\n\x1eORCHESTRATION_STATUS_COMPLETED\x10\x01\x12)\n%ORCHESTRATION_STATUS_CONTINUED_AS_NEW\x10\x02\x12\x1f\n\x1bORCHESTRATION_STATUS_FAILED\x10\x03\x12!\n\x1dORCHESTRATION_STATUS_CANCELED\x10\x04\x12#\n\x1fORCHESTRATION_STATUS_TERMINATED\x10\x05\x12 \n\x1cORCHESTRATION_STATUS_PENDING\x10\x06\x12\"\n\x1eORCHESTRATION_STATUS_SUSPENDED\x10\x07*\xab\x01\n\x10WorkerCapability\x12!\n\x1dWORKER_CAPABILITY_UNSPECIFIED\x10\x00\x12\'\n#WORKER_CAPABILITY_HISTORY_STREAMING\x10\x01\x12%\n!WORKER_CAPABILITY_SCHEDULED_TASKS\x10\x02\x12$\n WORKER_CAPABILITY_LARGE_PAYLOADS\x10\x03\x32\xf0\x0f\n\x15TaskHubSidecarService\x12\x37\n\x05Hello\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12@\n\rStartInstance\x12\x16.CreateInstanceRequest\x1a\x17.CreateInstanceResponse\x12\x38\n\x0bGetInstance\x12\x13.GetInstanceRequest\x1a\x14.GetInstanceResponse\x12\x41\n\x0eRewindInstance\x12\x16.RewindInstanceRequest\x1a\x17.RewindInstanceResponse\x12\x44\n\x0fRestartInstance\x12\x17.RestartInstanceRequest\x1a\x18.RestartInstanceResponse\x12\x41\n\x14WaitForInstanceStart\x12\x13.GetInstanceRequest\x1a\x14.GetInstanceResponse\x12\x46\n\x19WaitForInstanceCompletion\x12\x13.GetInstanceRequest\x1a\x14.GetInstanceResponse\x12\x35\n\nRaiseEvent\x12\x12.RaiseEventRequest\x1a\x13.RaiseEventResponse\x12:\n\x11TerminateInstance\x12\x11.TerminateRequest\x1a\x12.TerminateResponse\x12\x34\n\x0fSuspendInstance\x12\x0f.SuspendRequest\x1a\x10.SuspendResponse\x12\x31\n\x0eResumeInstance\x12\x0e.ResumeRequest\x1a\x0f.ResumeResponse\x12\x41\n\x0eQueryInstances\x12\x16.QueryInstancesRequest\x1a\x17.QueryInstancesResponse\x12\x44\n\x0fListInstanceIds\x12\x17.ListInstanceIdsRequest\x1a\x18.ListInstanceIdsResponse\x12\x41\n\x0ePurgeInstances\x12\x16.PurgeInstancesRequest\x1a\x17.PurgeInstancesResponse\x12\x31\n\x0cGetWorkItems\x12\x14.GetWorkItemsRequest\x1a\t.WorkItem0\x01\x12@\n\x14\x43ompleteActivityTask\x12\x11.ActivityResponse\x1a\x15.CompleteTaskResponse\x12H\n\x18\x43ompleteOrchestratorTask\x12\x15.OrchestratorResponse\x1a\x15.CompleteTaskResponse\x12?\n\x12\x43ompleteEntityTask\x12\x12.EntityBatchResult\x1a\x15.CompleteTaskResponse\x12G\n\x15StreamInstanceHistory\x12\x1d.StreamInstanceHistoryRequest\x1a\r.HistoryChunk0\x01\x12>\n\rCreateTaskHub\x12\x15.CreateTaskHubRequest\x1a\x16.CreateTaskHubResponse\x12>\n\rDeleteTaskHub\x12\x15.DeleteTaskHubRequest\x1a\x16.DeleteTaskHubResponse\x12;\n\x0cSignalEntity\x12\x14.SignalEntityRequest\x1a\x15.SignalEntityResponse\x12\x32\n\tGetEntity\x12\x11.GetEntityRequest\x1a\x12.GetEntityResponse\x12>\n\rQueryEntities\x12\x15.QueryEntitiesRequest\x1a\x16.QueryEntitiesResponse\x12M\n\x12\x43leanEntityStorage\x12\x1a.CleanEntityStorageRequest\x1a\x1b.CleanEntityStorageResponse\x12X\n\x1b\x41\x62\x61ndonTaskActivityWorkItem\x12\x1b.AbandonActivityTaskRequest\x1a\x1c.AbandonActivityTaskResponse\x12\x66\n\x1f\x41\x62\x61ndonTaskOrchestratorWorkItem\x12 .AbandonOrchestrationTaskRequest\x1a!.AbandonOrchestrationTaskResponse\x12R\n\x19\x41\x62\x61ndonTaskEntityWorkItem\x12\x19.AbandonEntityTaskRequest\x1a\x1a.AbandonEntityTaskResponse\x12\x86\x01\n%SkipGracefulOrchestrationTerminations\x12-.SkipGracefulOrchestrationTerminationsRequest\x1a..SkipGracefulOrchestrationTerminationsResponseBf\n1com.microsoft.durabletask.implementation.protobufZ\x10/internal/protos\xaa\x02\x1eMicrosoft.DurableTask.Protobufb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n/durabletask/internal/orchestrator_service.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1egoogle/protobuf/wrappers.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1cgoogle/protobuf/struct.proto\"^\n\x15OrchestrationInstance\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x31\n\x0b\x65xecutionId\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\xed\x01\n\x0f\x41\x63tivityRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12-\n\x07version\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x05input\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x35\n\x15orchestrationInstance\x18\x04 \x01(\x0b\x32\x16.OrchestrationInstance\x12\x0e\n\x06taskId\x18\x05 \x01(\x05\x12)\n\x12parentTraceContext\x18\x06 \x01(\x0b\x32\r.TraceContext\"\xaa\x01\n\x10\x41\x63tivityResponse\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x0e\n\x06taskId\x18\x02 \x01(\x05\x12,\n\x06result\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x0e\x66\x61ilureDetails\x18\x04 \x01(\x0b\x32\x13.TaskFailureDetails\x12\x17\n\x0f\x63ompletionToken\x18\x05 \x01(\t\"\xb6\x02\n\x12TaskFailureDetails\x12\x11\n\terrorType\x18\x01 \x01(\t\x12\x14\n\x0c\x65rrorMessage\x18\x02 \x01(\t\x12\x30\n\nstackTrace\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12)\n\x0cinnerFailure\x18\x04 \x01(\x0b\x32\x13.TaskFailureDetails\x12\x16\n\x0eisNonRetriable\x18\x05 \x01(\x08\x12\x37\n\nproperties\x18\x06 \x03(\x0b\x32#.TaskFailureDetails.PropertiesEntry\x1aI\n\x0fPropertiesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12%\n\x05value\x18\x02 \x01(\x0b\x32\x16.google.protobuf.Value:\x02\x38\x01\"\xbf\x01\n\x12ParentInstanceInfo\x12\x17\n\x0ftaskScheduledId\x18\x01 \x01(\x05\x12*\n\x04name\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12-\n\x07version\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x35\n\x15orchestrationInstance\x18\x04 \x01(\x0b\x32\x16.OrchestrationInstance\"i\n\x0cTraceContext\x12\x13\n\x0btraceParent\x18\x01 \x01(\t\x12\x12\n\x06spanID\x18\x02 \x01(\tB\x02\x18\x01\x12\x30\n\ntraceState\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\xe5\x03\n\x15\x45xecutionStartedEvent\x12\x0c\n\x04name\x18\x01 \x01(\t\x12-\n\x07version\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x05input\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x35\n\x15orchestrationInstance\x18\x04 \x01(\x0b\x32\x16.OrchestrationInstance\x12+\n\x0eparentInstance\x18\x05 \x01(\x0b\x32\x13.ParentInstanceInfo\x12;\n\x17scheduledStartTimestamp\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12)\n\x12parentTraceContext\x18\x07 \x01(\x0b\x32\r.TraceContext\x12\x39\n\x13orchestrationSpanID\x18\x08 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12.\n\x04tags\x18\t \x03(\x0b\x32 .ExecutionStartedEvent.TagsEntry\x1a+\n\tTagsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xa7\x01\n\x17\x45xecutionCompletedEvent\x12\x31\n\x13orchestrationStatus\x18\x01 \x01(\x0e\x32\x14.OrchestrationStatus\x12,\n\x06result\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x0e\x66\x61ilureDetails\x18\x03 \x01(\x0b\x32\x13.TaskFailureDetails\"X\n\x18\x45xecutionTerminatedEvent\x12+\n\x05input\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x0f\n\x07recurse\x18\x02 \x01(\x08\"\x83\x02\n\x12TaskScheduledEvent\x12\x0c\n\x04name\x18\x01 \x01(\t\x12-\n\x07version\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x05input\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12)\n\x12parentTraceContext\x18\x04 \x01(\x0b\x32\r.TraceContext\x12+\n\x04tags\x18\x05 \x03(\x0b\x32\x1d.TaskScheduledEvent.TagsEntry\x1a+\n\tTagsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"[\n\x12TaskCompletedEvent\x12\x17\n\x0ftaskScheduledId\x18\x01 \x01(\x05\x12,\n\x06result\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"W\n\x0fTaskFailedEvent\x12\x17\n\x0ftaskScheduledId\x18\x01 \x01(\x05\x12+\n\x0e\x66\x61ilureDetails\x18\x02 \x01(\x0b\x32\x13.TaskFailureDetails\"\xbb\x02\n$SubOrchestrationInstanceCreatedEvent\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12-\n\x07version\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x05input\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12)\n\x12parentTraceContext\x18\x05 \x01(\x0b\x32\r.TraceContext\x12=\n\x04tags\x18\x06 \x03(\x0b\x32/.SubOrchestrationInstanceCreatedEvent.TagsEntry\x1a+\n\tTagsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"o\n&SubOrchestrationInstanceCompletedEvent\x12\x17\n\x0ftaskScheduledId\x18\x01 \x01(\x05\x12,\n\x06result\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"k\n#SubOrchestrationInstanceFailedEvent\x12\x17\n\x0ftaskScheduledId\x18\x01 \x01(\x05\x12+\n\x0e\x66\x61ilureDetails\x18\x02 \x01(\x0b\x32\x13.TaskFailureDetails\"?\n\x11TimerCreatedEvent\x12*\n\x06\x66ireAt\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"N\n\x0fTimerFiredEvent\x12*\n\x06\x66ireAt\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x0f\n\x07timerId\x18\x02 \x01(\x05\"\x1a\n\x18OrchestratorStartedEvent\"\x1c\n\x1aOrchestratorCompletedEvent\"_\n\x0e\x45ventSentEvent\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12+\n\x05input\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"M\n\x10\x45ventRaisedEvent\x12\x0c\n\x04name\x18\x01 \x01(\t\x12+\n\x05input\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\":\n\x0cGenericEvent\x12*\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"D\n\x11HistoryStateEvent\x12/\n\x12orchestrationState\x18\x01 \x01(\x0b\x32\x13.OrchestrationState\"A\n\x12\x43ontinueAsNewEvent\x12+\n\x05input\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"F\n\x17\x45xecutionSuspendedEvent\x12+\n\x05input\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"D\n\x15\x45xecutionResumedEvent\x12+\n\x05input\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\xdc\x01\n\x1c\x45ntityOperationSignaledEvent\x12\x11\n\trequestId\x18\x01 \x01(\t\x12\x11\n\toperation\x18\x02 \x01(\t\x12\x31\n\rscheduledTime\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12+\n\x05input\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x36\n\x10targetInstanceId\x18\x05 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\xcb\x02\n\x1a\x45ntityOperationCalledEvent\x12\x11\n\trequestId\x18\x01 \x01(\t\x12\x11\n\toperation\x18\x02 \x01(\t\x12\x31\n\rscheduledTime\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12+\n\x05input\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x36\n\x10parentInstanceId\x18\x05 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x37\n\x11parentExecutionId\x18\x06 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x36\n\x10targetInstanceId\x18\x07 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\x90\x01\n\x18\x45ntityLockRequestedEvent\x12\x19\n\x11\x63riticalSectionId\x18\x01 \x01(\t\x12\x0f\n\x07lockSet\x18\x02 \x03(\t\x12\x10\n\x08position\x18\x03 \x01(\x05\x12\x36\n\x10parentInstanceId\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"`\n\x1d\x45ntityOperationCompletedEvent\x12\x11\n\trequestId\x18\x01 \x01(\t\x12,\n\x06output\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\\\n\x1a\x45ntityOperationFailedEvent\x12\x11\n\trequestId\x18\x01 \x01(\t\x12+\n\x0e\x66\x61ilureDetails\x18\x02 \x01(\x0b\x32\x13.TaskFailureDetails\"\xa2\x01\n\x15\x45ntityUnlockSentEvent\x12\x19\n\x11\x63riticalSectionId\x18\x01 \x01(\t\x12\x36\n\x10parentInstanceId\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x36\n\x10targetInstanceId\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"3\n\x16\x45ntityLockGrantedEvent\x12\x19\n\x11\x63riticalSectionId\x18\x01 \x01(\t\"\xed\x03\n\x15\x45xecutionRewoundEvent\x12,\n\x06reason\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x37\n\x11parentExecutionId\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x30\n\ninstanceId\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12)\n\x12parentTraceContext\x18\x04 \x01(\x0b\x32\r.TraceContext\x12*\n\x04name\x18\x05 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12-\n\x07version\x18\x06 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x05input\x18\x07 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x0eparentInstance\x18\x08 \x01(\x0b\x32\x13.ParentInstanceInfo\x12.\n\x04tags\x18\t \x03(\x0b\x32 .ExecutionRewoundEvent.TagsEntry\x1a+\n\tTagsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xe0\x0c\n\x0cHistoryEvent\x12\x0f\n\x07\x65ventId\x18\x01 \x01(\x05\x12-\n\ttimestamp\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x32\n\x10\x65xecutionStarted\x18\x03 \x01(\x0b\x32\x16.ExecutionStartedEventH\x00\x12\x36\n\x12\x65xecutionCompleted\x18\x04 \x01(\x0b\x32\x18.ExecutionCompletedEventH\x00\x12\x38\n\x13\x65xecutionTerminated\x18\x05 \x01(\x0b\x32\x19.ExecutionTerminatedEventH\x00\x12,\n\rtaskScheduled\x18\x06 \x01(\x0b\x32\x13.TaskScheduledEventH\x00\x12,\n\rtaskCompleted\x18\x07 \x01(\x0b\x32\x13.TaskCompletedEventH\x00\x12&\n\ntaskFailed\x18\x08 \x01(\x0b\x32\x10.TaskFailedEventH\x00\x12P\n\x1fsubOrchestrationInstanceCreated\x18\t \x01(\x0b\x32%.SubOrchestrationInstanceCreatedEventH\x00\x12T\n!subOrchestrationInstanceCompleted\x18\n \x01(\x0b\x32\'.SubOrchestrationInstanceCompletedEventH\x00\x12N\n\x1esubOrchestrationInstanceFailed\x18\x0b \x01(\x0b\x32$.SubOrchestrationInstanceFailedEventH\x00\x12*\n\x0ctimerCreated\x18\x0c \x01(\x0b\x32\x12.TimerCreatedEventH\x00\x12&\n\ntimerFired\x18\r \x01(\x0b\x32\x10.TimerFiredEventH\x00\x12\x38\n\x13orchestratorStarted\x18\x0e \x01(\x0b\x32\x19.OrchestratorStartedEventH\x00\x12<\n\x15orchestratorCompleted\x18\x0f \x01(\x0b\x32\x1b.OrchestratorCompletedEventH\x00\x12$\n\teventSent\x18\x10 \x01(\x0b\x32\x0f.EventSentEventH\x00\x12(\n\x0b\x65ventRaised\x18\x11 \x01(\x0b\x32\x11.EventRaisedEventH\x00\x12%\n\x0cgenericEvent\x18\x12 \x01(\x0b\x32\r.GenericEventH\x00\x12*\n\x0chistoryState\x18\x13 \x01(\x0b\x32\x12.HistoryStateEventH\x00\x12,\n\rcontinueAsNew\x18\x14 \x01(\x0b\x32\x13.ContinueAsNewEventH\x00\x12\x36\n\x12\x65xecutionSuspended\x18\x15 \x01(\x0b\x32\x18.ExecutionSuspendedEventH\x00\x12\x32\n\x10\x65xecutionResumed\x18\x16 \x01(\x0b\x32\x16.ExecutionResumedEventH\x00\x12@\n\x17\x65ntityOperationSignaled\x18\x17 \x01(\x0b\x32\x1d.EntityOperationSignaledEventH\x00\x12<\n\x15\x65ntityOperationCalled\x18\x18 \x01(\x0b\x32\x1b.EntityOperationCalledEventH\x00\x12\x42\n\x18\x65ntityOperationCompleted\x18\x19 \x01(\x0b\x32\x1e.EntityOperationCompletedEventH\x00\x12<\n\x15\x65ntityOperationFailed\x18\x1a \x01(\x0b\x32\x1b.EntityOperationFailedEventH\x00\x12\x38\n\x13\x65ntityLockRequested\x18\x1b \x01(\x0b\x32\x19.EntityLockRequestedEventH\x00\x12\x34\n\x11\x65ntityLockGranted\x18\x1c \x01(\x0b\x32\x17.EntityLockGrantedEventH\x00\x12\x32\n\x10\x65ntityUnlockSent\x18\x1d \x01(\x0b\x32\x16.EntityUnlockSentEventH\x00\x12\x32\n\x10\x65xecutionRewound\x18\x1e \x01(\x0b\x32\x16.ExecutionRewoundEventH\x00\x42\x0b\n\teventType\"\x83\x02\n\x12ScheduleTaskAction\x12\x0c\n\x04name\x18\x01 \x01(\t\x12-\n\x07version\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x05input\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x04tags\x18\x04 \x03(\x0b\x32\x1d.ScheduleTaskAction.TagsEntry\x12)\n\x12parentTraceContext\x18\x05 \x01(\x0b\x32\r.TraceContext\x1a+\n\tTagsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xab\x02\n\x1c\x43reateSubOrchestrationAction\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12-\n\x07version\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x05input\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12)\n\x12parentTraceContext\x18\x05 \x01(\x0b\x32\r.TraceContext\x12\x35\n\x04tags\x18\x06 \x03(\x0b\x32\'.CreateSubOrchestrationAction.TagsEntry\x1a+\n\tTagsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"?\n\x11\x43reateTimerAction\x12*\n\x06\x66ireAt\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"u\n\x0fSendEventAction\x12(\n\x08instance\x18\x01 \x01(\x0b\x32\x16.OrchestrationInstance\x12\x0c\n\x04name\x18\x02 \x01(\t\x12*\n\x04\x64\x61ta\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\x97\x03\n\x1b\x43ompleteOrchestrationAction\x12\x31\n\x13orchestrationStatus\x18\x01 \x01(\x0e\x32\x14.OrchestrationStatus\x12,\n\x06result\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12-\n\x07\x64\x65tails\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x30\n\nnewVersion\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12&\n\x0f\x63\x61rryoverEvents\x18\x05 \x03(\x0b\x32\r.HistoryEvent\x12+\n\x0e\x66\x61ilureDetails\x18\x06 \x01(\x0b\x32\x13.TaskFailureDetails\x12\x34\n\x04tags\x18\x07 \x03(\x0b\x32&.CompleteOrchestrationAction.TagsEntry\x1a+\n\tTagsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"q\n\x1cTerminateOrchestrationAction\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12,\n\x06reason\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x0f\n\x07recurse\x18\x03 \x01(\x08\"\x9c\x02\n\x17SendEntityMessageAction\x12@\n\x17\x65ntityOperationSignaled\x18\x01 \x01(\x0b\x32\x1d.EntityOperationSignaledEventH\x00\x12<\n\x15\x65ntityOperationCalled\x18\x02 \x01(\x0b\x32\x1b.EntityOperationCalledEventH\x00\x12\x38\n\x13\x65ntityLockRequested\x18\x03 \x01(\x0b\x32\x19.EntityLockRequestedEventH\x00\x12\x32\n\x10\x65ntityUnlockSent\x18\x04 \x01(\x0b\x32\x16.EntityUnlockSentEventH\x00\x42\x13\n\x11\x45ntityMessageType\">\n\x19RewindOrchestrationAction\x12!\n\nnewHistory\x18\x01 \x03(\x0b\x32\r.HistoryEvent\"\xec\x03\n\x12OrchestratorAction\x12\n\n\x02id\x18\x01 \x01(\x05\x12+\n\x0cscheduleTask\x18\x02 \x01(\x0b\x32\x13.ScheduleTaskActionH\x00\x12?\n\x16\x63reateSubOrchestration\x18\x03 \x01(\x0b\x32\x1d.CreateSubOrchestrationActionH\x00\x12)\n\x0b\x63reateTimer\x18\x04 \x01(\x0b\x32\x12.CreateTimerActionH\x00\x12%\n\tsendEvent\x18\x05 \x01(\x0b\x32\x10.SendEventActionH\x00\x12=\n\x15\x63ompleteOrchestration\x18\x06 \x01(\x0b\x32\x1c.CompleteOrchestrationActionH\x00\x12?\n\x16terminateOrchestration\x18\x07 \x01(\x0b\x32\x1d.TerminateOrchestrationActionH\x00\x12\x35\n\x11sendEntityMessage\x18\x08 \x01(\x0b\x32\x18.SendEntityMessageActionH\x00\x12\x39\n\x13rewindOrchestration\x18\t \x01(\x0b\x32\x1a.RewindOrchestrationActionH\x00\x42\x18\n\x16orchestratorActionType\"|\n\x19OrchestrationTraceContext\x12,\n\x06spanID\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x31\n\rspanStartTime\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xc0\x03\n\x13OrchestratorRequest\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x31\n\x0b\x65xecutionId\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12!\n\npastEvents\x18\x03 \x03(\x0b\x32\r.HistoryEvent\x12 \n\tnewEvents\x18\x04 \x03(\x0b\x32\r.HistoryEvent\x12\x37\n\x10\x65ntityParameters\x18\x05 \x01(\x0b\x32\x1d.OrchestratorEntityParameters\x12 \n\x18requiresHistoryStreaming\x18\x06 \x01(\x08\x12\x38\n\nproperties\x18\x07 \x03(\x0b\x32$.OrchestratorRequest.PropertiesEntry\x12=\n\x19orchestrationTraceContext\x18\x08 \x01(\x0b\x32\x1a.OrchestrationTraceContext\x1aI\n\x0fPropertiesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12%\n\x05value\x18\x02 \x01(\x0b\x32\x16.google.protobuf.Value:\x02\x38\x01\"\xf2\x02\n\x14OrchestratorResponse\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12$\n\x07\x61\x63tions\x18\x02 \x03(\x0b\x32\x13.OrchestratorAction\x12\x32\n\x0c\x63ustomStatus\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x17\n\x0f\x63ompletionToken\x18\x04 \x01(\t\x12\x37\n\x12numEventsProcessed\x18\x05 \x01(\x0b\x32\x1b.google.protobuf.Int32Value\x12=\n\x19orchestrationTraceContext\x18\x06 \x01(\x0b\x32\x1a.OrchestrationTraceContext\x12\x17\n\x0frequiresHistory\x18\x07 \x01(\x08\x12\x11\n\tisPartial\x18\x08 \x01(\x08\x12/\n\nchunkIndex\x18\t \x01(\x0b\x32\x1b.google.protobuf.Int32Value\"\xff\x03\n\x15\x43reateInstanceRequest\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12-\n\x07version\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x05input\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12;\n\x17scheduledStartTimestamp\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12?\n\x1aorchestrationIdReusePolicy\x18\x06 \x01(\x0b\x32\x1b.OrchestrationIdReusePolicy\x12\x31\n\x0b\x65xecutionId\x18\x07 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12.\n\x04tags\x18\x08 \x03(\x0b\x32 .CreateInstanceRequest.TagsEntry\x12)\n\x12parentTraceContext\x18\t \x01(\x0b\x32\r.TraceContext\x12/\n\x0brequestTime\x18\n \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x1a+\n\tTagsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"S\n\x1aOrchestrationIdReusePolicy\x12/\n\x11replaceableStatus\x18\x01 \x03(\x0e\x32\x14.OrchestrationStatusJ\x04\x08\x02\x10\x03\",\n\x16\x43reateInstanceResponse\x12\x12\n\ninstanceId\x18\x01 \x01(\t\"E\n\x12GetInstanceRequest\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x1b\n\x13getInputsAndOutputs\x18\x02 \x01(\x08\"V\n\x13GetInstanceResponse\x12\x0e\n\x06\x65xists\x18\x01 \x01(\x08\x12/\n\x12orchestrationState\x18\x02 \x01(\x0b\x32\x13.OrchestrationState\"Y\n\x15RewindInstanceRequest\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12,\n\x06reason\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\x18\n\x16RewindInstanceResponse\"\xfe\x05\n\x12OrchestrationState\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12-\n\x07version\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x31\n\x13orchestrationStatus\x18\x04 \x01(\x0e\x32\x14.OrchestrationStatus\x12;\n\x17scheduledStartTimestamp\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x34\n\x10\x63reatedTimestamp\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x38\n\x14lastUpdatedTimestamp\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12+\n\x05input\x18\x08 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12,\n\x06output\x18\t \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x32\n\x0c\x63ustomStatus\x18\n \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x0e\x66\x61ilureDetails\x18\x0b \x01(\x0b\x32\x13.TaskFailureDetails\x12\x31\n\x0b\x65xecutionId\x18\x0c \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x36\n\x12\x63ompletedTimestamp\x18\r \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x36\n\x10parentInstanceId\x18\x0e \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x04tags\x18\x0f \x03(\x0b\x32\x1d.OrchestrationState.TagsEntry\x1a+\n\tTagsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"b\n\x11RaiseEventRequest\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12+\n\x05input\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\x14\n\x12RaiseEventResponse\"g\n\x10TerminateRequest\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12,\n\x06output\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x11\n\trecursive\x18\x03 \x01(\x08\"\x13\n\x11TerminateResponse\"R\n\x0eSuspendRequest\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12,\n\x06reason\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\x11\n\x0fSuspendResponse\"Q\n\rResumeRequest\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12,\n\x06reason\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\x10\n\x0eResumeResponse\"6\n\x15QueryInstancesRequest\x12\x1d\n\x05query\x18\x01 \x01(\x0b\x32\x0e.InstanceQuery\"\x82\x03\n\rInstanceQuery\x12+\n\rruntimeStatus\x18\x01 \x03(\x0e\x32\x14.OrchestrationStatus\x12\x33\n\x0f\x63reatedTimeFrom\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x31\n\rcreatedTimeTo\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x32\n\x0ctaskHubNames\x18\x04 \x03(\x0b\x32\x1c.google.protobuf.StringValue\x12\x18\n\x10maxInstanceCount\x18\x05 \x01(\x05\x12\x37\n\x11\x63ontinuationToken\x18\x06 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x36\n\x10instanceIdPrefix\x18\x07 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x1d\n\x15\x66\x65tchInputsAndOutputs\x18\x08 \x01(\x08\"\x82\x01\n\x16QueryInstancesResponse\x12/\n\x12orchestrationState\x18\x01 \x03(\x0b\x32\x13.OrchestrationState\x12\x37\n\x11\x63ontinuationToken\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\xfa\x01\n\x16ListInstanceIdsRequest\x12+\n\rruntimeStatus\x18\x01 \x03(\x0e\x32\x14.OrchestrationStatus\x12\x35\n\x11\x63ompletedTimeFrom\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x33\n\x0f\x63ompletedTimeTo\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x10\n\x08pageSize\x18\x04 \x01(\x05\x12\x35\n\x0flastInstanceKey\x18\x05 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"e\n\x17ListInstanceIdsResponse\x12\x13\n\x0binstanceIds\x18\x01 \x03(\t\x12\x35\n\x0flastInstanceKey\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\xc2\x01\n\x15PurgeInstancesRequest\x12\x14\n\ninstanceId\x18\x01 \x01(\tH\x00\x12\x33\n\x13purgeInstanceFilter\x18\x02 \x01(\x0b\x32\x14.PurgeInstanceFilterH\x00\x12\'\n\rinstanceBatch\x18\x04 \x01(\x0b\x32\x0e.InstanceBatchH\x00\x12\x11\n\trecursive\x18\x03 \x01(\x08\x12\x17\n\x0fisOrchestration\x18\x05 \x01(\x08\x42\t\n\x07request\"\xaa\x01\n\x13PurgeInstanceFilter\x12\x33\n\x0f\x63reatedTimeFrom\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x31\n\rcreatedTimeTo\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12+\n\rruntimeStatus\x18\x03 \x03(\x0e\x32\x14.OrchestrationStatus\"f\n\x16PurgeInstancesResponse\x12\x1c\n\x14\x64\x65letedInstanceCount\x18\x01 \x01(\x05\x12.\n\nisComplete\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.BoolValue\"N\n\x16RestartInstanceRequest\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12 \n\x18restartWithNewInstanceId\x18\x02 \x01(\x08\"-\n\x17RestartInstanceResponse\x12\x12\n\ninstanceId\x18\x01 \x01(\t\"0\n\x14\x43reateTaskHubRequest\x12\x18\n\x10recreateIfExists\x18\x01 \x01(\x08\"\x17\n\x15\x43reateTaskHubResponse\"\x16\n\x14\x44\x65leteTaskHubRequest\"\x17\n\x15\x44\x65leteTaskHubResponse\"\x86\x02\n\x13SignalEntityRequest\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12+\n\x05input\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x11\n\trequestId\x18\x04 \x01(\t\x12\x31\n\rscheduledTime\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12)\n\x12parentTraceContext\x18\x06 \x01(\x0b\x32\r.TraceContext\x12/\n\x0brequestTime\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\x16\n\x14SignalEntityResponse\"<\n\x10GetEntityRequest\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x14\n\x0cincludeState\x18\x02 \x01(\x08\"D\n\x11GetEntityResponse\x12\x0e\n\x06\x65xists\x18\x01 \x01(\x08\x12\x1f\n\x06\x65ntity\x18\x02 \x01(\x0b\x32\x0f.EntityMetadata\"\xcb\x02\n\x0b\x45ntityQuery\x12:\n\x14instanceIdStartsWith\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x34\n\x10lastModifiedFrom\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x32\n\x0elastModifiedTo\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x14\n\x0cincludeState\x18\x04 \x01(\x08\x12\x18\n\x10includeTransient\x18\x05 \x01(\x08\x12-\n\x08pageSize\x18\x06 \x01(\x0b\x32\x1b.google.protobuf.Int32Value\x12\x37\n\x11\x63ontinuationToken\x18\x07 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"3\n\x14QueryEntitiesRequest\x12\x1b\n\x05query\x18\x01 \x01(\x0b\x32\x0c.EntityQuery\"s\n\x15QueryEntitiesResponse\x12!\n\x08\x65ntities\x18\x01 \x03(\x0b\x32\x0f.EntityMetadata\x12\x37\n\x11\x63ontinuationToken\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\xdb\x01\n\x0e\x45ntityMetadata\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x34\n\x10lastModifiedTime\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x18\n\x10\x62\x61\x63klogQueueSize\x18\x03 \x01(\x05\x12.\n\x08lockedBy\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x35\n\x0fserializedState\x18\x05 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\x8f\x01\n\x19\x43leanEntityStorageRequest\x12\x37\n\x11\x63ontinuationToken\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x1b\n\x13removeEmptyEntities\x18\x02 \x01(\x08\x12\x1c\n\x14releaseOrphanedLocks\x18\x03 \x01(\x08\"\x92\x01\n\x1a\x43leanEntityStorageResponse\x12\x37\n\x11\x63ontinuationToken\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x1c\n\x14\x65mptyEntitiesRemoved\x18\x02 \x01(\x05\x12\x1d\n\x15orphanedLocksReleased\x18\x03 \x01(\x05\"]\n\x1cOrchestratorEntityParameters\x12=\n\x1a\x65ntityMessageReorderWindow\x18\x01 \x01(\x0b\x32\x19.google.protobuf.Duration\"\x86\x02\n\x12\x45ntityBatchRequest\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x31\n\x0b\x65ntityState\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12%\n\noperations\x18\x03 \x03(\x0b\x32\x11.OperationRequest\x12\x37\n\nproperties\x18\x04 \x03(\x0b\x32#.EntityBatchRequest.PropertiesEntry\x1aI\n\x0fPropertiesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12%\n\x05value\x18\x02 \x01(\x0b\x32\x16.google.protobuf.Value:\x02\x38\x01\"\x91\x02\n\x11\x45ntityBatchResult\x12!\n\x07results\x18\x01 \x03(\x0b\x32\x10.OperationResult\x12!\n\x07\x61\x63tions\x18\x02 \x03(\x0b\x32\x10.OperationAction\x12\x31\n\x0b\x65ntityState\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x0e\x66\x61ilureDetails\x18\x04 \x01(\x0b\x32\x13.TaskFailureDetails\x12\x17\n\x0f\x63ompletionToken\x18\x05 \x01(\t\x12&\n\x0eoperationInfos\x18\x06 \x03(\x0b\x32\x0e.OperationInfo\x12\x15\n\rrequiresState\x18\x07 \x01(\x08\"\x95\x01\n\rEntityRequest\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x13\n\x0b\x65xecutionId\x18\x02 \x01(\t\x12\x31\n\x0b\x65ntityState\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12(\n\x11operationRequests\x18\x04 \x03(\x0b\x32\r.HistoryEvent\"\x8a\x01\n\x10OperationRequest\x12\x11\n\toperation\x18\x01 \x01(\t\x12\x11\n\trequestId\x18\x02 \x01(\t\x12+\n\x05input\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12#\n\x0ctraceContext\x18\x04 \x01(\x0b\x32\r.TraceContext\"w\n\x0fOperationResult\x12*\n\x07success\x18\x01 \x01(\x0b\x32\x17.OperationResultSuccessH\x00\x12*\n\x07\x66\x61ilure\x18\x02 \x01(\x0b\x32\x17.OperationResultFailureH\x00\x42\x0c\n\nresultType\"W\n\rOperationInfo\x12\x11\n\trequestId\x18\x01 \x01(\t\x12\x33\n\x13responseDestination\x18\x02 \x01(\x0b\x32\x16.OrchestrationInstance\"\xa8\x01\n\x16OperationResultSuccess\x12,\n\x06result\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x30\n\x0cstartTimeUtc\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\nendTimeUtc\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xa7\x01\n\x16OperationResultFailure\x12+\n\x0e\x66\x61ilureDetails\x18\x01 \x01(\x0b\x32\x13.TaskFailureDetails\x12\x30\n\x0cstartTimeUtc\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\nendTimeUtc\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\x9c\x01\n\x0fOperationAction\x12\n\n\x02id\x18\x01 \x01(\x05\x12\'\n\nsendSignal\x18\x02 \x01(\x0b\x32\x11.SendSignalActionH\x00\x12=\n\x15startNewOrchestration\x18\x03 \x01(\x0b\x32\x1c.StartNewOrchestrationActionH\x00\x42\x15\n\x13operationActionType\"\xf0\x01\n\x10SendSignalAction\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12+\n\x05input\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x31\n\rscheduledTime\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12/\n\x0brequestTime\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12)\n\x12parentTraceContext\x18\x06 \x01(\x0b\x32\r.TraceContext\"\xaa\x02\n\x1bStartNewOrchestrationAction\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12-\n\x07version\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x05input\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x31\n\rscheduledTime\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12/\n\x0brequestTime\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12)\n\x12parentTraceContext\x18\x07 \x01(\x0b\x32\r.TraceContext\"5\n\x1a\x41\x62\x61ndonActivityTaskRequest\x12\x17\n\x0f\x63ompletionToken\x18\x01 \x01(\t\"\x1d\n\x1b\x41\x62\x61ndonActivityTaskResponse\":\n\x1f\x41\x62\x61ndonOrchestrationTaskRequest\x12\x17\n\x0f\x63ompletionToken\x18\x01 \x01(\t\"\"\n AbandonOrchestrationTaskResponse\"3\n\x18\x41\x62\x61ndonEntityTaskRequest\x12\x17\n\x0f\x63ompletionToken\x18\x01 \x01(\t\"\x1b\n\x19\x41\x62\x61ndonEntityTaskResponse\"\x83\x01\n,SkipGracefulOrchestrationTerminationsRequest\x12%\n\rinstanceBatch\x18\x01 \x01(\x0b\x32\x0e.InstanceBatch\x12,\n\x06reason\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"P\n-SkipGracefulOrchestrationTerminationsResponse\x12\x1f\n\x17unterminatedInstanceIds\x18\x01 \x03(\t\"\xe4\x01\n\x13GetWorkItemsRequest\x12+\n#maxConcurrentOrchestrationWorkItems\x18\x01 \x01(\x05\x12&\n\x1emaxConcurrentActivityWorkItems\x18\x02 \x01(\x05\x12$\n\x1cmaxConcurrentEntityWorkItems\x18\x03 \x01(\x05\x12\'\n\x0c\x63\x61pabilities\x18\n \x03(\x0e\x32\x11.WorkerCapability\x12)\n\x0fworkItemFilters\x18\x0b \x01(\x0b\x32\x10.WorkItemFilters\"\x85\x01\n\x0fWorkItemFilters\x12,\n\x0eorchestrations\x18\x01 \x03(\x0b\x32\x14.OrchestrationFilter\x12#\n\nactivities\x18\x02 \x03(\x0b\x32\x0f.ActivityFilter\x12\x1f\n\x08\x65ntities\x18\x03 \x03(\x0b\x32\r.EntityFilter\"5\n\x13OrchestrationFilter\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x10\n\x08versions\x18\x02 \x03(\t\"0\n\x0e\x41\x63tivityFilter\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x10\n\x08versions\x18\x02 \x03(\t\"\x1c\n\x0c\x45ntityFilter\x12\x0c\n\x04name\x18\x01 \x01(\t\"\x8c\x02\n\x08WorkItem\x12\x33\n\x13orchestratorRequest\x18\x01 \x01(\x0b\x32\x14.OrchestratorRequestH\x00\x12+\n\x0f\x61\x63tivityRequest\x18\x02 \x01(\x0b\x32\x10.ActivityRequestH\x00\x12,\n\rentityRequest\x18\x03 \x01(\x0b\x32\x13.EntityBatchRequestH\x00\x12!\n\nhealthPing\x18\x04 \x01(\x0b\x32\x0b.HealthPingH\x00\x12)\n\x0f\x65ntityRequestV2\x18\x05 \x01(\x0b\x32\x0e.EntityRequestH\x00\x12\x17\n\x0f\x63ompletionToken\x18\n \x01(\tB\t\n\x07request\"\x16\n\x14\x43ompleteTaskResponse\"\x0c\n\nHealthPing\"\x84\x01\n\x1cStreamInstanceHistoryRequest\x12\x12\n\ninstanceId\x18\x01 \x01(\t\x12\x31\n\x0b\x65xecutionId\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x1d\n\x15\x66orWorkItemProcessing\x18\x03 \x01(\x08\"-\n\x0cHistoryChunk\x12\x1d\n\x06\x65vents\x18\x01 \x03(\x0b\x32\r.HistoryEvent\"$\n\rInstanceBatch\x12\x13\n\x0binstanceIds\x18\x01 \x03(\t*\xb5\x02\n\x13OrchestrationStatus\x12 \n\x1cORCHESTRATION_STATUS_RUNNING\x10\x00\x12\"\n\x1eORCHESTRATION_STATUS_COMPLETED\x10\x01\x12)\n%ORCHESTRATION_STATUS_CONTINUED_AS_NEW\x10\x02\x12\x1f\n\x1bORCHESTRATION_STATUS_FAILED\x10\x03\x12!\n\x1dORCHESTRATION_STATUS_CANCELED\x10\x04\x12#\n\x1fORCHESTRATION_STATUS_TERMINATED\x10\x05\x12 \n\x1cORCHESTRATION_STATUS_PENDING\x10\x06\x12\"\n\x1eORCHESTRATION_STATUS_SUSPENDED\x10\x07*\xab\x01\n\x10WorkerCapability\x12!\n\x1dWORKER_CAPABILITY_UNSPECIFIED\x10\x00\x12\'\n#WORKER_CAPABILITY_HISTORY_STREAMING\x10\x01\x12%\n!WORKER_CAPABILITY_SCHEDULED_TASKS\x10\x02\x12$\n WORKER_CAPABILITY_LARGE_PAYLOADS\x10\x03\x32\xf0\x0f\n\x15TaskHubSidecarService\x12\x37\n\x05Hello\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12@\n\rStartInstance\x12\x16.CreateInstanceRequest\x1a\x17.CreateInstanceResponse\x12\x38\n\x0bGetInstance\x12\x13.GetInstanceRequest\x1a\x14.GetInstanceResponse\x12\x41\n\x0eRewindInstance\x12\x16.RewindInstanceRequest\x1a\x17.RewindInstanceResponse\x12\x44\n\x0fRestartInstance\x12\x17.RestartInstanceRequest\x1a\x18.RestartInstanceResponse\x12\x41\n\x14WaitForInstanceStart\x12\x13.GetInstanceRequest\x1a\x14.GetInstanceResponse\x12\x46\n\x19WaitForInstanceCompletion\x12\x13.GetInstanceRequest\x1a\x14.GetInstanceResponse\x12\x35\n\nRaiseEvent\x12\x12.RaiseEventRequest\x1a\x13.RaiseEventResponse\x12:\n\x11TerminateInstance\x12\x11.TerminateRequest\x1a\x12.TerminateResponse\x12\x34\n\x0fSuspendInstance\x12\x0f.SuspendRequest\x1a\x10.SuspendResponse\x12\x31\n\x0eResumeInstance\x12\x0e.ResumeRequest\x1a\x0f.ResumeResponse\x12\x41\n\x0eQueryInstances\x12\x16.QueryInstancesRequest\x1a\x17.QueryInstancesResponse\x12\x44\n\x0fListInstanceIds\x12\x17.ListInstanceIdsRequest\x1a\x18.ListInstanceIdsResponse\x12\x41\n\x0ePurgeInstances\x12\x16.PurgeInstancesRequest\x1a\x17.PurgeInstancesResponse\x12\x31\n\x0cGetWorkItems\x12\x14.GetWorkItemsRequest\x1a\t.WorkItem0\x01\x12@\n\x14\x43ompleteActivityTask\x12\x11.ActivityResponse\x1a\x15.CompleteTaskResponse\x12H\n\x18\x43ompleteOrchestratorTask\x12\x15.OrchestratorResponse\x1a\x15.CompleteTaskResponse\x12?\n\x12\x43ompleteEntityTask\x12\x12.EntityBatchResult\x1a\x15.CompleteTaskResponse\x12G\n\x15StreamInstanceHistory\x12\x1d.StreamInstanceHistoryRequest\x1a\r.HistoryChunk0\x01\x12>\n\rCreateTaskHub\x12\x15.CreateTaskHubRequest\x1a\x16.CreateTaskHubResponse\x12>\n\rDeleteTaskHub\x12\x15.DeleteTaskHubRequest\x1a\x16.DeleteTaskHubResponse\x12;\n\x0cSignalEntity\x12\x14.SignalEntityRequest\x1a\x15.SignalEntityResponse\x12\x32\n\tGetEntity\x12\x11.GetEntityRequest\x1a\x12.GetEntityResponse\x12>\n\rQueryEntities\x12\x15.QueryEntitiesRequest\x1a\x16.QueryEntitiesResponse\x12M\n\x12\x43leanEntityStorage\x12\x1a.CleanEntityStorageRequest\x1a\x1b.CleanEntityStorageResponse\x12X\n\x1b\x41\x62\x61ndonTaskActivityWorkItem\x12\x1b.AbandonActivityTaskRequest\x1a\x1c.AbandonActivityTaskResponse\x12\x66\n\x1f\x41\x62\x61ndonTaskOrchestratorWorkItem\x12 .AbandonOrchestrationTaskRequest\x1a!.AbandonOrchestrationTaskResponse\x12R\n\x19\x41\x62\x61ndonTaskEntityWorkItem\x12\x19.AbandonEntityTaskRequest\x1a\x1a.AbandonEntityTaskResponse\x12\x86\x01\n%SkipGracefulOrchestrationTerminations\x12-.SkipGracefulOrchestrationTerminationsRequest\x1a..SkipGracefulOrchestrationTerminationsResponseBf\n1com.microsoft.durabletask.implementation.protobufZ\x10/internal/protos\xaa\x02\x1eMicrosoft.DurableTask.Protobufb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -53,10 +53,10 @@ _globals['_ORCHESTRATIONSTATE_TAGSENTRY']._serialized_options = b'8\001' _globals['_ENTITYBATCHREQUEST_PROPERTIESENTRY']._loaded_options = None _globals['_ENTITYBATCHREQUEST_PROPERTIESENTRY']._serialized_options = b'8\001' - _globals['_ORCHESTRATIONSTATUS']._serialized_start=18595 - _globals['_ORCHESTRATIONSTATUS']._serialized_end=18904 - _globals['_WORKERCAPABILITY']._serialized_start=18907 - _globals['_WORKERCAPABILITY']._serialized_end=19078 + _globals['_ORCHESTRATIONSTATUS']._serialized_start=19032 + _globals['_ORCHESTRATIONSTATUS']._serialized_end=19341 + _globals['_WORKERCAPABILITY']._serialized_start=19344 + _globals['_WORKERCAPABILITY']._serialized_end=19515 _globals['_ORCHESTRATIONINSTANCE']._serialized_start=207 _globals['_ORCHESTRATIONINSTANCE']._serialized_end=301 _globals['_ACTIVITYREQUEST']._serialized_start=304 @@ -157,156 +157,166 @@ _globals['_TERMINATEORCHESTRATIONACTION']._serialized_end=8296 _globals['_SENDENTITYMESSAGEACTION']._serialized_start=8299 _globals['_SENDENTITYMESSAGEACTION']._serialized_end=8583 - _globals['_ORCHESTRATORACTION']._serialized_start=8586 - _globals['_ORCHESTRATORACTION']._serialized_end=9019 - _globals['_ORCHESTRATIONTRACECONTEXT']._serialized_start=9021 - _globals['_ORCHESTRATIONTRACECONTEXT']._serialized_end=9145 - _globals['_ORCHESTRATORREQUEST']._serialized_start=9148 - _globals['_ORCHESTRATORREQUEST']._serialized_end=9596 + _globals['_REWINDORCHESTRATIONACTION']._serialized_start=8585 + _globals['_REWINDORCHESTRATIONACTION']._serialized_end=8647 + _globals['_ORCHESTRATORACTION']._serialized_start=8650 + _globals['_ORCHESTRATORACTION']._serialized_end=9142 + _globals['_ORCHESTRATIONTRACECONTEXT']._serialized_start=9144 + _globals['_ORCHESTRATIONTRACECONTEXT']._serialized_end=9268 + _globals['_ORCHESTRATORREQUEST']._serialized_start=9271 + _globals['_ORCHESTRATORREQUEST']._serialized_end=9719 _globals['_ORCHESTRATORREQUEST_PROPERTIESENTRY']._serialized_start=954 _globals['_ORCHESTRATORREQUEST_PROPERTIESENTRY']._serialized_end=1027 - _globals['_ORCHESTRATORRESPONSE']._serialized_start=9599 - _globals['_ORCHESTRATORRESPONSE']._serialized_end=9969 - _globals['_CREATEINSTANCEREQUEST']._serialized_start=9972 - _globals['_CREATEINSTANCEREQUEST']._serialized_end=10483 + _globals['_ORCHESTRATORRESPONSE']._serialized_start=9722 + _globals['_ORCHESTRATORRESPONSE']._serialized_end=10092 + _globals['_CREATEINSTANCEREQUEST']._serialized_start=10095 + _globals['_CREATEINSTANCEREQUEST']._serialized_end=10606 _globals['_CREATEINSTANCEREQUEST_TAGSENTRY']._serialized_start=1773 _globals['_CREATEINSTANCEREQUEST_TAGSENTRY']._serialized_end=1816 - _globals['_ORCHESTRATIONIDREUSEPOLICY']._serialized_start=10485 - _globals['_ORCHESTRATIONIDREUSEPOLICY']._serialized_end=10568 - _globals['_CREATEINSTANCERESPONSE']._serialized_start=10570 - _globals['_CREATEINSTANCERESPONSE']._serialized_end=10614 - _globals['_GETINSTANCEREQUEST']._serialized_start=10616 - _globals['_GETINSTANCEREQUEST']._serialized_end=10685 - _globals['_GETINSTANCERESPONSE']._serialized_start=10687 - _globals['_GETINSTANCERESPONSE']._serialized_end=10773 - _globals['_REWINDINSTANCEREQUEST']._serialized_start=10775 - _globals['_REWINDINSTANCEREQUEST']._serialized_end=10864 - _globals['_REWINDINSTANCERESPONSE']._serialized_start=10866 - _globals['_REWINDINSTANCERESPONSE']._serialized_end=10890 - _globals['_ORCHESTRATIONSTATE']._serialized_start=10893 - _globals['_ORCHESTRATIONSTATE']._serialized_end=11659 + _globals['_ORCHESTRATIONIDREUSEPOLICY']._serialized_start=10608 + _globals['_ORCHESTRATIONIDREUSEPOLICY']._serialized_end=10691 + _globals['_CREATEINSTANCERESPONSE']._serialized_start=10693 + _globals['_CREATEINSTANCERESPONSE']._serialized_end=10737 + _globals['_GETINSTANCEREQUEST']._serialized_start=10739 + _globals['_GETINSTANCEREQUEST']._serialized_end=10808 + _globals['_GETINSTANCERESPONSE']._serialized_start=10810 + _globals['_GETINSTANCERESPONSE']._serialized_end=10896 + _globals['_REWINDINSTANCEREQUEST']._serialized_start=10898 + _globals['_REWINDINSTANCEREQUEST']._serialized_end=10987 + _globals['_REWINDINSTANCERESPONSE']._serialized_start=10989 + _globals['_REWINDINSTANCERESPONSE']._serialized_end=11013 + _globals['_ORCHESTRATIONSTATE']._serialized_start=11016 + _globals['_ORCHESTRATIONSTATE']._serialized_end=11782 _globals['_ORCHESTRATIONSTATE_TAGSENTRY']._serialized_start=1773 _globals['_ORCHESTRATIONSTATE_TAGSENTRY']._serialized_end=1816 - _globals['_RAISEEVENTREQUEST']._serialized_start=11661 - _globals['_RAISEEVENTREQUEST']._serialized_end=11759 - _globals['_RAISEEVENTRESPONSE']._serialized_start=11761 - _globals['_RAISEEVENTRESPONSE']._serialized_end=11781 - _globals['_TERMINATEREQUEST']._serialized_start=11783 - _globals['_TERMINATEREQUEST']._serialized_end=11886 - _globals['_TERMINATERESPONSE']._serialized_start=11888 - _globals['_TERMINATERESPONSE']._serialized_end=11907 - _globals['_SUSPENDREQUEST']._serialized_start=11909 - _globals['_SUSPENDREQUEST']._serialized_end=11991 - _globals['_SUSPENDRESPONSE']._serialized_start=11993 - _globals['_SUSPENDRESPONSE']._serialized_end=12010 - _globals['_RESUMEREQUEST']._serialized_start=12012 - _globals['_RESUMEREQUEST']._serialized_end=12093 - _globals['_RESUMERESPONSE']._serialized_start=12095 - _globals['_RESUMERESPONSE']._serialized_end=12111 - _globals['_QUERYINSTANCESREQUEST']._serialized_start=12113 - _globals['_QUERYINSTANCESREQUEST']._serialized_end=12167 - _globals['_INSTANCEQUERY']._serialized_start=12170 - _globals['_INSTANCEQUERY']._serialized_end=12556 - _globals['_QUERYINSTANCESRESPONSE']._serialized_start=12559 - _globals['_QUERYINSTANCESRESPONSE']._serialized_end=12689 - _globals['_LISTINSTANCEIDSREQUEST']._serialized_start=12692 - _globals['_LISTINSTANCEIDSREQUEST']._serialized_end=12942 - _globals['_LISTINSTANCEIDSRESPONSE']._serialized_start=12944 - _globals['_LISTINSTANCEIDSRESPONSE']._serialized_end=13045 - _globals['_PURGEINSTANCESREQUEST']._serialized_start=13048 - _globals['_PURGEINSTANCESREQUEST']._serialized_end=13242 - _globals['_PURGEINSTANCEFILTER']._serialized_start=13245 - _globals['_PURGEINSTANCEFILTER']._serialized_end=13415 - _globals['_PURGEINSTANCESRESPONSE']._serialized_start=13417 - _globals['_PURGEINSTANCESRESPONSE']._serialized_end=13519 - _globals['_RESTARTINSTANCEREQUEST']._serialized_start=13521 - _globals['_RESTARTINSTANCEREQUEST']._serialized_end=13599 - _globals['_RESTARTINSTANCERESPONSE']._serialized_start=13601 - _globals['_RESTARTINSTANCERESPONSE']._serialized_end=13646 - _globals['_CREATETASKHUBREQUEST']._serialized_start=13648 - _globals['_CREATETASKHUBREQUEST']._serialized_end=13696 - _globals['_CREATETASKHUBRESPONSE']._serialized_start=13698 - _globals['_CREATETASKHUBRESPONSE']._serialized_end=13721 - _globals['_DELETETASKHUBREQUEST']._serialized_start=13723 - _globals['_DELETETASKHUBREQUEST']._serialized_end=13745 - _globals['_DELETETASKHUBRESPONSE']._serialized_start=13747 - _globals['_DELETETASKHUBRESPONSE']._serialized_end=13770 - _globals['_SIGNALENTITYREQUEST']._serialized_start=13773 - _globals['_SIGNALENTITYREQUEST']._serialized_end=14035 - _globals['_SIGNALENTITYRESPONSE']._serialized_start=14037 - _globals['_SIGNALENTITYRESPONSE']._serialized_end=14059 - _globals['_GETENTITYREQUEST']._serialized_start=14061 - _globals['_GETENTITYREQUEST']._serialized_end=14121 - _globals['_GETENTITYRESPONSE']._serialized_start=14123 - _globals['_GETENTITYRESPONSE']._serialized_end=14191 - _globals['_ENTITYQUERY']._serialized_start=14194 - _globals['_ENTITYQUERY']._serialized_end=14525 - _globals['_QUERYENTITIESREQUEST']._serialized_start=14527 - _globals['_QUERYENTITIESREQUEST']._serialized_end=14578 - _globals['_QUERYENTITIESRESPONSE']._serialized_start=14580 - _globals['_QUERYENTITIESRESPONSE']._serialized_end=14695 - _globals['_ENTITYMETADATA']._serialized_start=14698 - _globals['_ENTITYMETADATA']._serialized_end=14917 - _globals['_CLEANENTITYSTORAGEREQUEST']._serialized_start=14920 - _globals['_CLEANENTITYSTORAGEREQUEST']._serialized_end=15063 - _globals['_CLEANENTITYSTORAGERESPONSE']._serialized_start=15066 - _globals['_CLEANENTITYSTORAGERESPONSE']._serialized_end=15212 - _globals['_ORCHESTRATORENTITYPARAMETERS']._serialized_start=15214 - _globals['_ORCHESTRATORENTITYPARAMETERS']._serialized_end=15307 - _globals['_ENTITYBATCHREQUEST']._serialized_start=15310 - _globals['_ENTITYBATCHREQUEST']._serialized_end=15572 + _globals['_RAISEEVENTREQUEST']._serialized_start=11784 + _globals['_RAISEEVENTREQUEST']._serialized_end=11882 + _globals['_RAISEEVENTRESPONSE']._serialized_start=11884 + _globals['_RAISEEVENTRESPONSE']._serialized_end=11904 + _globals['_TERMINATEREQUEST']._serialized_start=11906 + _globals['_TERMINATEREQUEST']._serialized_end=12009 + _globals['_TERMINATERESPONSE']._serialized_start=12011 + _globals['_TERMINATERESPONSE']._serialized_end=12030 + _globals['_SUSPENDREQUEST']._serialized_start=12032 + _globals['_SUSPENDREQUEST']._serialized_end=12114 + _globals['_SUSPENDRESPONSE']._serialized_start=12116 + _globals['_SUSPENDRESPONSE']._serialized_end=12133 + _globals['_RESUMEREQUEST']._serialized_start=12135 + _globals['_RESUMEREQUEST']._serialized_end=12216 + _globals['_RESUMERESPONSE']._serialized_start=12218 + _globals['_RESUMERESPONSE']._serialized_end=12234 + _globals['_QUERYINSTANCESREQUEST']._serialized_start=12236 + _globals['_QUERYINSTANCESREQUEST']._serialized_end=12290 + _globals['_INSTANCEQUERY']._serialized_start=12293 + _globals['_INSTANCEQUERY']._serialized_end=12679 + _globals['_QUERYINSTANCESRESPONSE']._serialized_start=12682 + _globals['_QUERYINSTANCESRESPONSE']._serialized_end=12812 + _globals['_LISTINSTANCEIDSREQUEST']._serialized_start=12815 + _globals['_LISTINSTANCEIDSREQUEST']._serialized_end=13065 + _globals['_LISTINSTANCEIDSRESPONSE']._serialized_start=13067 + _globals['_LISTINSTANCEIDSRESPONSE']._serialized_end=13168 + _globals['_PURGEINSTANCESREQUEST']._serialized_start=13171 + _globals['_PURGEINSTANCESREQUEST']._serialized_end=13365 + _globals['_PURGEINSTANCEFILTER']._serialized_start=13368 + _globals['_PURGEINSTANCEFILTER']._serialized_end=13538 + _globals['_PURGEINSTANCESRESPONSE']._serialized_start=13540 + _globals['_PURGEINSTANCESRESPONSE']._serialized_end=13642 + _globals['_RESTARTINSTANCEREQUEST']._serialized_start=13644 + _globals['_RESTARTINSTANCEREQUEST']._serialized_end=13722 + _globals['_RESTARTINSTANCERESPONSE']._serialized_start=13724 + _globals['_RESTARTINSTANCERESPONSE']._serialized_end=13769 + _globals['_CREATETASKHUBREQUEST']._serialized_start=13771 + _globals['_CREATETASKHUBREQUEST']._serialized_end=13819 + _globals['_CREATETASKHUBRESPONSE']._serialized_start=13821 + _globals['_CREATETASKHUBRESPONSE']._serialized_end=13844 + _globals['_DELETETASKHUBREQUEST']._serialized_start=13846 + _globals['_DELETETASKHUBREQUEST']._serialized_end=13868 + _globals['_DELETETASKHUBRESPONSE']._serialized_start=13870 + _globals['_DELETETASKHUBRESPONSE']._serialized_end=13893 + _globals['_SIGNALENTITYREQUEST']._serialized_start=13896 + _globals['_SIGNALENTITYREQUEST']._serialized_end=14158 + _globals['_SIGNALENTITYRESPONSE']._serialized_start=14160 + _globals['_SIGNALENTITYRESPONSE']._serialized_end=14182 + _globals['_GETENTITYREQUEST']._serialized_start=14184 + _globals['_GETENTITYREQUEST']._serialized_end=14244 + _globals['_GETENTITYRESPONSE']._serialized_start=14246 + _globals['_GETENTITYRESPONSE']._serialized_end=14314 + _globals['_ENTITYQUERY']._serialized_start=14317 + _globals['_ENTITYQUERY']._serialized_end=14648 + _globals['_QUERYENTITIESREQUEST']._serialized_start=14650 + _globals['_QUERYENTITIESREQUEST']._serialized_end=14701 + _globals['_QUERYENTITIESRESPONSE']._serialized_start=14703 + _globals['_QUERYENTITIESRESPONSE']._serialized_end=14818 + _globals['_ENTITYMETADATA']._serialized_start=14821 + _globals['_ENTITYMETADATA']._serialized_end=15040 + _globals['_CLEANENTITYSTORAGEREQUEST']._serialized_start=15043 + _globals['_CLEANENTITYSTORAGEREQUEST']._serialized_end=15186 + _globals['_CLEANENTITYSTORAGERESPONSE']._serialized_start=15189 + _globals['_CLEANENTITYSTORAGERESPONSE']._serialized_end=15335 + _globals['_ORCHESTRATORENTITYPARAMETERS']._serialized_start=15337 + _globals['_ORCHESTRATORENTITYPARAMETERS']._serialized_end=15430 + _globals['_ENTITYBATCHREQUEST']._serialized_start=15433 + _globals['_ENTITYBATCHREQUEST']._serialized_end=15695 _globals['_ENTITYBATCHREQUEST_PROPERTIESENTRY']._serialized_start=954 _globals['_ENTITYBATCHREQUEST_PROPERTIESENTRY']._serialized_end=1027 - _globals['_ENTITYBATCHRESULT']._serialized_start=15575 - _globals['_ENTITYBATCHRESULT']._serialized_end=15848 - _globals['_ENTITYREQUEST']._serialized_start=15851 - _globals['_ENTITYREQUEST']._serialized_end=16000 - _globals['_OPERATIONREQUEST']._serialized_start=16003 - _globals['_OPERATIONREQUEST']._serialized_end=16141 - _globals['_OPERATIONRESULT']._serialized_start=16143 - _globals['_OPERATIONRESULT']._serialized_end=16262 - _globals['_OPERATIONINFO']._serialized_start=16264 - _globals['_OPERATIONINFO']._serialized_end=16351 - _globals['_OPERATIONRESULTSUCCESS']._serialized_start=16354 - _globals['_OPERATIONRESULTSUCCESS']._serialized_end=16522 - _globals['_OPERATIONRESULTFAILURE']._serialized_start=16525 - _globals['_OPERATIONRESULTFAILURE']._serialized_end=16692 - _globals['_OPERATIONACTION']._serialized_start=16695 - _globals['_OPERATIONACTION']._serialized_end=16851 - _globals['_SENDSIGNALACTION']._serialized_start=16854 - _globals['_SENDSIGNALACTION']._serialized_end=17094 - _globals['_STARTNEWORCHESTRATIONACTION']._serialized_start=17097 - _globals['_STARTNEWORCHESTRATIONACTION']._serialized_end=17395 - _globals['_ABANDONACTIVITYTASKREQUEST']._serialized_start=17397 - _globals['_ABANDONACTIVITYTASKREQUEST']._serialized_end=17450 - _globals['_ABANDONACTIVITYTASKRESPONSE']._serialized_start=17452 - _globals['_ABANDONACTIVITYTASKRESPONSE']._serialized_end=17481 - _globals['_ABANDONORCHESTRATIONTASKREQUEST']._serialized_start=17483 - _globals['_ABANDONORCHESTRATIONTASKREQUEST']._serialized_end=17541 - _globals['_ABANDONORCHESTRATIONTASKRESPONSE']._serialized_start=17543 - _globals['_ABANDONORCHESTRATIONTASKRESPONSE']._serialized_end=17577 - _globals['_ABANDONENTITYTASKREQUEST']._serialized_start=17579 - _globals['_ABANDONENTITYTASKREQUEST']._serialized_end=17630 - _globals['_ABANDONENTITYTASKRESPONSE']._serialized_start=17632 - _globals['_ABANDONENTITYTASKRESPONSE']._serialized_end=17659 - _globals['_SKIPGRACEFULORCHESTRATIONTERMINATIONSREQUEST']._serialized_start=17662 - _globals['_SKIPGRACEFULORCHESTRATIONTERMINATIONSREQUEST']._serialized_end=17793 - _globals['_SKIPGRACEFULORCHESTRATIONTERMINATIONSRESPONSE']._serialized_start=17795 - _globals['_SKIPGRACEFULORCHESTRATIONTERMINATIONSRESPONSE']._serialized_end=17875 - _globals['_GETWORKITEMSREQUEST']._serialized_start=17878 - _globals['_GETWORKITEMSREQUEST']._serialized_end=18063 - _globals['_WORKITEM']._serialized_start=18066 - _globals['_WORKITEM']._serialized_end=18334 - _globals['_COMPLETETASKRESPONSE']._serialized_start=18336 - _globals['_COMPLETETASKRESPONSE']._serialized_end=18358 - _globals['_HEALTHPING']._serialized_start=18360 - _globals['_HEALTHPING']._serialized_end=18372 - _globals['_STREAMINSTANCEHISTORYREQUEST']._serialized_start=18375 - _globals['_STREAMINSTANCEHISTORYREQUEST']._serialized_end=18507 - _globals['_HISTORYCHUNK']._serialized_start=18509 - _globals['_HISTORYCHUNK']._serialized_end=18554 - _globals['_INSTANCEBATCH']._serialized_start=18556 - _globals['_INSTANCEBATCH']._serialized_end=18592 - _globals['_TASKHUBSIDECARSERVICE']._serialized_start=19081 - _globals['_TASKHUBSIDECARSERVICE']._serialized_end=21113 + _globals['_ENTITYBATCHRESULT']._serialized_start=15698 + _globals['_ENTITYBATCHRESULT']._serialized_end=15971 + _globals['_ENTITYREQUEST']._serialized_start=15974 + _globals['_ENTITYREQUEST']._serialized_end=16123 + _globals['_OPERATIONREQUEST']._serialized_start=16126 + _globals['_OPERATIONREQUEST']._serialized_end=16264 + _globals['_OPERATIONRESULT']._serialized_start=16266 + _globals['_OPERATIONRESULT']._serialized_end=16385 + _globals['_OPERATIONINFO']._serialized_start=16387 + _globals['_OPERATIONINFO']._serialized_end=16474 + _globals['_OPERATIONRESULTSUCCESS']._serialized_start=16477 + _globals['_OPERATIONRESULTSUCCESS']._serialized_end=16645 + _globals['_OPERATIONRESULTFAILURE']._serialized_start=16648 + _globals['_OPERATIONRESULTFAILURE']._serialized_end=16815 + _globals['_OPERATIONACTION']._serialized_start=16818 + _globals['_OPERATIONACTION']._serialized_end=16974 + _globals['_SENDSIGNALACTION']._serialized_start=16977 + _globals['_SENDSIGNALACTION']._serialized_end=17217 + _globals['_STARTNEWORCHESTRATIONACTION']._serialized_start=17220 + _globals['_STARTNEWORCHESTRATIONACTION']._serialized_end=17518 + _globals['_ABANDONACTIVITYTASKREQUEST']._serialized_start=17520 + _globals['_ABANDONACTIVITYTASKREQUEST']._serialized_end=17573 + _globals['_ABANDONACTIVITYTASKRESPONSE']._serialized_start=17575 + _globals['_ABANDONACTIVITYTASKRESPONSE']._serialized_end=17604 + _globals['_ABANDONORCHESTRATIONTASKREQUEST']._serialized_start=17606 + _globals['_ABANDONORCHESTRATIONTASKREQUEST']._serialized_end=17664 + _globals['_ABANDONORCHESTRATIONTASKRESPONSE']._serialized_start=17666 + _globals['_ABANDONORCHESTRATIONTASKRESPONSE']._serialized_end=17700 + _globals['_ABANDONENTITYTASKREQUEST']._serialized_start=17702 + _globals['_ABANDONENTITYTASKREQUEST']._serialized_end=17753 + _globals['_ABANDONENTITYTASKRESPONSE']._serialized_start=17755 + _globals['_ABANDONENTITYTASKRESPONSE']._serialized_end=17782 + _globals['_SKIPGRACEFULORCHESTRATIONTERMINATIONSREQUEST']._serialized_start=17785 + _globals['_SKIPGRACEFULORCHESTRATIONTERMINATIONSREQUEST']._serialized_end=17916 + _globals['_SKIPGRACEFULORCHESTRATIONTERMINATIONSRESPONSE']._serialized_start=17918 + _globals['_SKIPGRACEFULORCHESTRATIONTERMINATIONSRESPONSE']._serialized_end=17998 + _globals['_GETWORKITEMSREQUEST']._serialized_start=18001 + _globals['_GETWORKITEMSREQUEST']._serialized_end=18229 + _globals['_WORKITEMFILTERS']._serialized_start=18232 + _globals['_WORKITEMFILTERS']._serialized_end=18365 + _globals['_ORCHESTRATIONFILTER']._serialized_start=18367 + _globals['_ORCHESTRATIONFILTER']._serialized_end=18420 + _globals['_ACTIVITYFILTER']._serialized_start=18422 + _globals['_ACTIVITYFILTER']._serialized_end=18470 + _globals['_ENTITYFILTER']._serialized_start=18472 + _globals['_ENTITYFILTER']._serialized_end=18500 + _globals['_WORKITEM']._serialized_start=18503 + _globals['_WORKITEM']._serialized_end=18771 + _globals['_COMPLETETASKRESPONSE']._serialized_start=18773 + _globals['_COMPLETETASKRESPONSE']._serialized_end=18795 + _globals['_HEALTHPING']._serialized_start=18797 + _globals['_HEALTHPING']._serialized_end=18809 + _globals['_STREAMINSTANCEHISTORYREQUEST']._serialized_start=18812 + _globals['_STREAMINSTANCEHISTORYREQUEST']._serialized_end=18944 + _globals['_HISTORYCHUNK']._serialized_start=18946 + _globals['_HISTORYCHUNK']._serialized_end=18991 + _globals['_INSTANCEBATCH']._serialized_start=18993 + _globals['_INSTANCEBATCH']._serialized_end=19029 + _globals['_TASKHUBSIDECARSERVICE']._serialized_start=19518 + _globals['_TASKHUBSIDECARSERVICE']._serialized_end=21550 # @@protoc_insertion_point(module_scope) diff --git a/durabletask/internal/orchestrator_service_pb2.pyi b/durabletask/internal/orchestrator_service_pb2.pyi index a5a21a5..d80b762 100644 --- a/durabletask/internal/orchestrator_service_pb2.pyi +++ b/durabletask/internal/orchestrator_service_pb2.pyi @@ -593,8 +593,14 @@ class SendEntityMessageAction(_message.Message): entityUnlockSent: EntityUnlockSentEvent def __init__(self, entityOperationSignaled: _Optional[_Union[EntityOperationSignaledEvent, _Mapping]] = ..., entityOperationCalled: _Optional[_Union[EntityOperationCalledEvent, _Mapping]] = ..., entityLockRequested: _Optional[_Union[EntityLockRequestedEvent, _Mapping]] = ..., entityUnlockSent: _Optional[_Union[EntityUnlockSentEvent, _Mapping]] = ...) -> None: ... +class RewindOrchestrationAction(_message.Message): + __slots__ = ("newHistory",) + NEWHISTORY_FIELD_NUMBER: _ClassVar[int] + newHistory: _containers.RepeatedCompositeFieldContainer[HistoryEvent] + def __init__(self, newHistory: _Optional[_Iterable[_Union[HistoryEvent, _Mapping]]] = ...) -> None: ... + class OrchestratorAction(_message.Message): - __slots__ = ("id", "scheduleTask", "createSubOrchestration", "createTimer", "sendEvent", "completeOrchestration", "terminateOrchestration", "sendEntityMessage") + __slots__ = ("id", "scheduleTask", "createSubOrchestration", "createTimer", "sendEvent", "completeOrchestration", "terminateOrchestration", "sendEntityMessage", "rewindOrchestration") ID_FIELD_NUMBER: _ClassVar[int] SCHEDULETASK_FIELD_NUMBER: _ClassVar[int] CREATESUBORCHESTRATION_FIELD_NUMBER: _ClassVar[int] @@ -603,6 +609,7 @@ class OrchestratorAction(_message.Message): COMPLETEORCHESTRATION_FIELD_NUMBER: _ClassVar[int] TERMINATEORCHESTRATION_FIELD_NUMBER: _ClassVar[int] SENDENTITYMESSAGE_FIELD_NUMBER: _ClassVar[int] + REWINDORCHESTRATION_FIELD_NUMBER: _ClassVar[int] id: int scheduleTask: ScheduleTaskAction createSubOrchestration: CreateSubOrchestrationAction @@ -611,7 +618,8 @@ class OrchestratorAction(_message.Message): completeOrchestration: CompleteOrchestrationAction terminateOrchestration: TerminateOrchestrationAction sendEntityMessage: SendEntityMessageAction - def __init__(self, id: _Optional[int] = ..., scheduleTask: _Optional[_Union[ScheduleTaskAction, _Mapping]] = ..., createSubOrchestration: _Optional[_Union[CreateSubOrchestrationAction, _Mapping]] = ..., createTimer: _Optional[_Union[CreateTimerAction, _Mapping]] = ..., sendEvent: _Optional[_Union[SendEventAction, _Mapping]] = ..., completeOrchestration: _Optional[_Union[CompleteOrchestrationAction, _Mapping]] = ..., terminateOrchestration: _Optional[_Union[TerminateOrchestrationAction, _Mapping]] = ..., sendEntityMessage: _Optional[_Union[SendEntityMessageAction, _Mapping]] = ...) -> None: ... + rewindOrchestration: RewindOrchestrationAction + def __init__(self, id: _Optional[int] = ..., scheduleTask: _Optional[_Union[ScheduleTaskAction, _Mapping]] = ..., createSubOrchestration: _Optional[_Union[CreateSubOrchestrationAction, _Mapping]] = ..., createTimer: _Optional[_Union[CreateTimerAction, _Mapping]] = ..., sendEvent: _Optional[_Union[SendEventAction, _Mapping]] = ..., completeOrchestration: _Optional[_Union[CompleteOrchestrationAction, _Mapping]] = ..., terminateOrchestration: _Optional[_Union[TerminateOrchestrationAction, _Mapping]] = ..., sendEntityMessage: _Optional[_Union[SendEntityMessageAction, _Mapping]] = ..., rewindOrchestration: _Optional[_Union[RewindOrchestrationAction, _Mapping]] = ...) -> None: ... class OrchestrationTraceContext(_message.Message): __slots__ = ("spanID", "spanStartTime") @@ -1250,16 +1258,50 @@ class SkipGracefulOrchestrationTerminationsResponse(_message.Message): def __init__(self, unterminatedInstanceIds: _Optional[_Iterable[str]] = ...) -> None: ... class GetWorkItemsRequest(_message.Message): - __slots__ = ("maxConcurrentOrchestrationWorkItems", "maxConcurrentActivityWorkItems", "maxConcurrentEntityWorkItems", "capabilities") + __slots__ = ("maxConcurrentOrchestrationWorkItems", "maxConcurrentActivityWorkItems", "maxConcurrentEntityWorkItems", "capabilities", "workItemFilters") MAXCONCURRENTORCHESTRATIONWORKITEMS_FIELD_NUMBER: _ClassVar[int] MAXCONCURRENTACTIVITYWORKITEMS_FIELD_NUMBER: _ClassVar[int] MAXCONCURRENTENTITYWORKITEMS_FIELD_NUMBER: _ClassVar[int] CAPABILITIES_FIELD_NUMBER: _ClassVar[int] + WORKITEMFILTERS_FIELD_NUMBER: _ClassVar[int] maxConcurrentOrchestrationWorkItems: int maxConcurrentActivityWorkItems: int maxConcurrentEntityWorkItems: int capabilities: _containers.RepeatedScalarFieldContainer[WorkerCapability] - def __init__(self, maxConcurrentOrchestrationWorkItems: _Optional[int] = ..., maxConcurrentActivityWorkItems: _Optional[int] = ..., maxConcurrentEntityWorkItems: _Optional[int] = ..., capabilities: _Optional[_Iterable[_Union[WorkerCapability, str]]] = ...) -> None: ... + workItemFilters: WorkItemFilters + def __init__(self, maxConcurrentOrchestrationWorkItems: _Optional[int] = ..., maxConcurrentActivityWorkItems: _Optional[int] = ..., maxConcurrentEntityWorkItems: _Optional[int] = ..., capabilities: _Optional[_Iterable[_Union[WorkerCapability, str]]] = ..., workItemFilters: _Optional[_Union[WorkItemFilters, _Mapping]] = ...) -> None: ... + +class WorkItemFilters(_message.Message): + __slots__ = ("orchestrations", "activities", "entities") + ORCHESTRATIONS_FIELD_NUMBER: _ClassVar[int] + ACTIVITIES_FIELD_NUMBER: _ClassVar[int] + ENTITIES_FIELD_NUMBER: _ClassVar[int] + orchestrations: _containers.RepeatedCompositeFieldContainer[OrchestrationFilter] + activities: _containers.RepeatedCompositeFieldContainer[ActivityFilter] + entities: _containers.RepeatedCompositeFieldContainer[EntityFilter] + def __init__(self, orchestrations: _Optional[_Iterable[_Union[OrchestrationFilter, _Mapping]]] = ..., activities: _Optional[_Iterable[_Union[ActivityFilter, _Mapping]]] = ..., entities: _Optional[_Iterable[_Union[EntityFilter, _Mapping]]] = ...) -> None: ... + +class OrchestrationFilter(_message.Message): + __slots__ = ("name", "versions") + NAME_FIELD_NUMBER: _ClassVar[int] + VERSIONS_FIELD_NUMBER: _ClassVar[int] + name: str + versions: _containers.RepeatedScalarFieldContainer[str] + def __init__(self, name: _Optional[str] = ..., versions: _Optional[_Iterable[str]] = ...) -> None: ... + +class ActivityFilter(_message.Message): + __slots__ = ("name", "versions") + NAME_FIELD_NUMBER: _ClassVar[int] + VERSIONS_FIELD_NUMBER: _ClassVar[int] + name: str + versions: _containers.RepeatedScalarFieldContainer[str] + def __init__(self, name: _Optional[str] = ..., versions: _Optional[_Iterable[str]] = ...) -> None: ... + +class EntityFilter(_message.Message): + __slots__ = ("name",) + NAME_FIELD_NUMBER: _ClassVar[int] + name: str + def __init__(self, name: _Optional[str] = ...) -> None: ... class WorkItem(_message.Message): __slots__ = ("orchestratorRequest", "activityRequest", "entityRequest", "healthPing", "entityRequestV2", "completionToken") diff --git a/durabletask/testing/README.md b/durabletask/testing/README.md index 0a6e985..06ed30a 100644 --- a/durabletask/testing/README.md +++ b/durabletask/testing/README.md @@ -255,8 +255,7 @@ The in-memory backend is designed for testing and has some limitations compared 1. **No persistence**: All state is lost when the backend is stopped 2. **No distributed execution**: Runs in a single process 3. **No history streaming**: StreamInstanceHistory is not implemented -4. **No rewind**: RewindInstance is not implemented -5. **No recursive termination**: Recursive termination is not supported +4. **No recursive termination**: Recursive termination is not supported ### Best Practices diff --git a/durabletask/testing/in_memory_backend.py b/durabletask/testing/in_memory_backend.py index 590688a..24ebef5 100644 --- a/durabletask/testing/in_memory_backend.py +++ b/durabletask/testing/in_memory_backend.py @@ -981,8 +981,33 @@ def DeleteTaskHub(self, request: pb.DeleteTaskHubRequest, context): return pb.DeleteTaskHubResponse() def RewindInstance(self, request: pb.RewindInstanceRequest, context): - """Rewinds an orchestration instance (not implemented).""" - context.abort(grpc.StatusCode.UNIMPLEMENTED, "RewindInstance not implemented") + """Rewinds a failed orchestration instance. + + The backend validates the instance is in a failed state, appends + an ``ExecutionRewoundEvent`` to the pending events, resets the + instance status to RUNNING, and re-enqueues the orchestration + so the worker can replay it and produce a + ``RewindOrchestrationAction`` with the corrected history. + """ + with self._lock: + instance = self._instances.get(request.instanceId) + if not instance: + context.abort( + grpc.StatusCode.NOT_FOUND, + f"Orchestration instance '{request.instanceId}' not found") + return pb.RewindInstanceResponse() + + if instance.status != pb.ORCHESTRATION_STATUS_FAILED: + context.abort( + grpc.StatusCode.FAILED_PRECONDITION, + f"Orchestration instance '{request.instanceId}' is not in a failed state") + return pb.RewindInstanceResponse() + + reason = request.reason.value if request.HasField("reason") else None + self._prepare_rewind(instance, reason) + + self._logger.info(f"Rewound instance '{request.instanceId}'") + return pb.RewindInstanceResponse() def AbandonTaskActivityWorkItem(self, request: pb.AbandonActivityTaskRequest, context): """Abandons an activity work item.""" @@ -1196,6 +1221,8 @@ def _process_action(self, instance: OrchestrationInstance, action: pb.Orchestrat self._process_send_event_action(action.sendEvent) elif action.HasField("sendEntityMessage"): self._process_send_entity_message_action(instance, action) + elif action.HasField("rewindOrchestration"): + self._process_rewind_orchestration_action(instance, action.rewindOrchestration) def _process_complete_orchestration_action(self, instance: OrchestrationInstance, complete_action: pb.CompleteOrchestrationAction): @@ -1205,6 +1232,14 @@ def _process_complete_orchestration_action(self, instance: OrchestrationInstance instance.output = complete_action.result.value if complete_action.result else None instance.failure_details = complete_action.failureDetails if complete_action.failureDetails else None + # Append orchestratorCompleted to history when the orchestration + # reaches a terminal state. This positional marker allows the + # SDK to distinguish a post-rewind replay from a new rewind + # request by comparing the position of the last + # orchestratorCompleted against the last executionRewound. + if status != pb.ORCHESTRATION_STATUS_CONTINUED_AS_NEW: + instance.history.append(helpers.new_orchestrator_completed_event()) + if status == pb.ORCHESTRATION_STATUS_CONTINUED_AS_NEW: # Handle continue-as-new new_input = complete_action.result.value if complete_action.result else None @@ -1558,6 +1593,119 @@ def _signal_entity_internal(self, entity_id: str, operation: str, ) self._queue_entity_operation(entity_id, event) + def _prepare_rewind(self, instance: OrchestrationInstance, + reason: Optional[str] = None): + """Prepares an orchestration instance for rewind. + + Appends an ``ExecutionRewoundEvent`` to the pending events, resets + the instance status to RUNNING, and re-enqueues it so the worker + can replay it. The actual history rewriting is done by the SDK + worker when it processes the rewind event. + + Args: + instance: The orchestration instance to rewind. + reason: Optional reason string for the rewind. + + Note: + Must be called while holding ``self._lock``. + """ + # Reset instance state so it can be re-processed. + instance.status = pb.ORCHESTRATION_STATUS_RUNNING + instance.output = None + instance.failure_details = None + instance.last_updated_at = datetime.now(timezone.utc) + + # Clear any stale dispatched events. + instance.dispatched_events.clear() + + # Add the ExecutionRewound event as a new pending event. + rewind_event = pb.HistoryEvent( + eventId=-1, + timestamp=timestamp_pb2.Timestamp(), + executionRewound=pb.ExecutionRewoundEvent( + reason=wrappers_pb2.StringValue(value=reason) if reason else None, + ), + ) + instance.pending_events.append(rewind_event) + + # Refresh the completion token and enqueue. + instance.completion_token = self._next_completion_token + self._next_completion_token += 1 + self._orchestration_in_flight.discard(instance.instance_id) + self._enqueue_orchestration(instance.instance_id) + + def _process_rewind_orchestration_action( + self, instance: OrchestrationInstance, + rewind_action: pb.RewindOrchestrationAction): + """Processes a RewindOrchestrationAction returned by the SDK. + + The action contains a ``newHistory`` field with the rewritten + history computed by the SDK (failed tasks and sub-orchestration + failures removed). The backend replaces the instance's history + with this new history, recursively rewinds any failed + sub-orchestrations, and re-enqueues the orchestration. + """ + new_history = list(rewind_action.newHistory) + + # Replace history with the rewritten version. + instance.history = new_history + instance.status = pb.ORCHESTRATION_STATUS_RUNNING + instance.output = None + instance.failure_details = None + instance.last_updated_at = datetime.now(timezone.utc) + + # Identify sub-orchestrations that were created but did not + # complete successfully — they need to be recursively rewound. + completed_sub_orch_task_ids: set[int] = set() + created_sub_orchs: dict[int, str] = {} + for event in new_history: + if event.HasField("subOrchestrationInstanceCreated"): + created_sub_orchs[event.eventId] = ( + event.subOrchestrationInstanceCreated.instanceId) + elif event.HasField("subOrchestrationInstanceCompleted"): + completed_sub_orch_task_ids.add( + event.subOrchestrationInstanceCompleted.taskScheduledId) + + # Extract the rewind reason from the last ExecutionRewound event. + reason: Optional[str] = None + for event in reversed(new_history): + if event.HasField("executionRewound"): + if event.executionRewound.HasField("reason"): + reason = event.executionRewound.reason.value + break + + # Recursively rewind failed sub-orchestrations. + for task_id, sub_instance_id in created_sub_orchs.items(): + if task_id not in completed_sub_orch_task_ids: + sub_instance = self._instances.get(sub_instance_id) + if (sub_instance + and sub_instance.status == pb.ORCHESTRATION_STATUS_FAILED): + self._prepare_rewind(sub_instance, reason) + self._watch_sub_orchestration( + instance.instance_id, sub_instance_id, task_id) + + # Re-enqueue so the orchestration replays with the clean history. + # The executionRewound event is added to pending_events so the + # worker can see it in new_events; the worker uses the presence + # of executionRewound in old_events (history) to distinguish + # this normal post-rewind replay from the initial rewind + # short-circuit. Note: we do NOT add orchestratorStarted here + # because the work-item dispatch loop already inserts one when + # the instance has non-empty history. + rewind_event = None + for event in new_history: + if event.HasField("executionRewound"): + rewind_event = event + break + instance.pending_events.clear() + instance.dispatched_events.clear() + if rewind_event is not None: + instance.pending_events.append(rewind_event) + instance.completion_token = self._next_completion_token + self._next_completion_token += 1 + self._orchestration_in_flight.discard(instance.instance_id) + self._enqueue_orchestration(instance.instance_id) + def _enqueue_entity(self, entity_id: str): """Enqueues an entity for processing.""" if entity_id not in self._entity_queue_set: diff --git a/durabletask/worker.py b/durabletask/worker.py index 442165d..3b8af41 100644 --- a/durabletask/worker.py +++ b/durabletask/worker.py @@ -1314,6 +1314,40 @@ def execute( "The new history event list must have at least one event in it." ) + # Check for rewind BEFORE replay. A rewind is indicated by an + # executionRewound event in new_events. We need to distinguish + # two cases: + # 1. A *new* rewind request that the worker must short-circuit: + # either there is no executionRewound in old_events yet + # (first rewind), or the last orchestratorCompleted comes + # AFTER the last executionRewound in old_events — meaning + # the orchestration ran and completed (with failure) after + # a prior rewind. + # 2. A *post-rewind replay* where the backend has already + # processed the RewindOrchestrationAction, cleaned the + # history, and re-dispatched the orchestration. In this + # case the last executionRewound in old_events comes AFTER + # the last orchestratorCompleted. + has_rewind_in_new = any( + e.HasField("executionRewound") for e in new_events + ) + if has_rewind_in_new: + last_rewind_pos = -1 + last_completed_pos = -1 + for i, e in enumerate(old_events): + if e.HasField("executionRewound"): + last_rewind_pos = i + elif e.HasField("orchestratorCompleted"): + last_completed_pos = i + + if last_rewind_pos < 0 or last_completed_pos > last_rewind_pos: + # Either first rewind (no executionRewound in history) + # or the orchestration completed after a prior rewind + # (orchestratorCompleted comes after executionRewound), + # so this is a new rewind that needs short-circuiting. + return self._build_rewind_result( + instance_id, orchestration_name, old_events, new_events) + ctx = _RuntimeOrchestrationContext(instance_id, self._registry) try: # Rebuild local state by replaying old history into the orchestrator function @@ -1374,6 +1408,66 @@ def execute( actions=actions, encoded_custom_status=ctx._encoded_custom_status ) + def _build_rewind_result( + self, + instance_id: str, + orchestration_name: str, + old_events: Sequence[pb.HistoryEvent], + new_events: Sequence[pb.HistoryEvent], + ) -> ExecutionResults: + """Build an ``ExecutionResults`` containing a ``RewindOrchestrationAction``. + + When the worker detects an ``executionRewound`` event in the new + events (that does not yet appear in the committed history) it + rewrites the history by removing failed task results + (``taskFailed``) and failed sub-orchestration results + (``subOrchestrationInstanceFailed``). The ``executionRewound`` + event is kept so the backend knows why the rewind happened and + so it remains in the history for audit purposes. + + For failed activities, the corresponding ``taskScheduled`` event + is also removed so that the SDK will re-generate a + ``scheduleTask`` action during the next replay, causing the + backend to re-dispatch the activity. + + For failed sub-orchestrations, the ``subOrchestrationInstanceCreated`` + event is kept so the backend can identify which sub-orchestration + instances to recursively rewind. + """ + self._logger.info( + f"{instance_id}: Orchestration {orchestration_name} is being rewound" + ) + + # Combine old + new events into a single timeline, then remove + # failed results so the orchestration can replay successfully. + all_events = list(old_events) + list(new_events) + + # First pass: collect the task-scheduled IDs that correspond to + # failed activities so we can remove the matching taskScheduled + # events in the second pass. + failed_task_ids: set[int] = set() + for event in all_events: + if event.HasField("taskFailed"): + failed_task_ids.add(event.taskFailed.taskScheduledId) + + # Second pass: build the clean history. + clean_history: list[pb.HistoryEvent] = [] + for event in all_events: + if event.HasField("taskFailed"): + continue + if event.HasField("taskScheduled") and event.eventId in failed_task_ids: + continue + if event.HasField("subOrchestrationInstanceFailed"): + continue + clean_history.append(event) + + rewind_action = pb.RewindOrchestrationAction(newHistory=clean_history) + action = pb.OrchestratorAction( + id=-1, + rewindOrchestration=rewind_action, + ) + return ExecutionResults(actions=[action], encoded_custom_status=None) + def process_event( self, ctx: _RuntimeOrchestrationContext, event: pb.HistoryEvent ) -> None: @@ -1796,6 +1890,9 @@ def process_event( elif event.HasField("orchestratorCompleted"): # Added in Functions only (for some reason) and does not affect orchestrator flow pass + elif event.HasField("executionRewound"): + # Informational event added when an orchestration is rewound. No action needed. + pass elif event.HasField("eventSent"): # Check if this eventSent corresponds to an entity operation call after being translated to the old # entity protocol by the Durable WebJobs extension. If so, treat this message similarly to diff --git a/tests/durabletask/test_rewind_e2e.py b/tests/durabletask/test_rewind_e2e.py new file mode 100644 index 0000000..e86a5bf --- /dev/null +++ b/tests/durabletask/test_rewind_e2e.py @@ -0,0 +1,300 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +import json +import threading +import time + +import pytest + +from durabletask import client, task, worker +from durabletask.testing import create_test_backend + +HOST = "localhost:50055" + + +@pytest.fixture(autouse=True) +def backend(): + """Create an in-memory backend for testing.""" + b = create_test_backend(port=50055) + yield b + b.stop() + b.reset() + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +# These counters live at module level so that orchestrators and +# activities can mutate them via ``nonlocal``. +_activity_call_count = 0 +_should_fail = True + + +def _reset_counters(): + global _activity_call_count, _should_fail + _activity_call_count = 0 + _should_fail = True + + +# --------------------------------------------------------------------------- +# Tests +# --------------------------------------------------------------------------- + + +def test_rewind_failed_activity(): + """Rewind a failed orchestration whose single activity failed. + + After rewind the activity succeeds and the orchestration completes. + """ + _reset_counters() + + def failing_activity(_: task.ActivityContext, input: str) -> str: + global _activity_call_count, _should_fail + _activity_call_count += 1 + if _should_fail: + raise RuntimeError("Simulated failure") + return f"Hello, {input}!" + + def orchestrator(ctx: task.OrchestrationContext, input: str): + result = yield ctx.call_activity(failing_activity, input=input) + return result + + with worker.TaskHubGrpcWorker(host_address=HOST) as w: + w.add_orchestrator(orchestrator) + w.add_activity(failing_activity) + w.start() + + c = client.TaskHubGrpcClient(host_address=HOST) + instance_id = c.schedule_new_orchestration(orchestrator, input="World") + state = c.wait_for_orchestration_completion(instance_id, timeout=30) + + # The orchestration should have failed. + assert state is not None + assert state.runtime_status == client.OrchestrationStatus.FAILED + + # Fix the activity so it now succeeds, then rewind. + global _should_fail + _should_fail = False + c.rewind_orchestration(instance_id, reason="retry after fix") + + state = c.wait_for_orchestration_completion(instance_id, timeout=30) + + assert state is not None + assert state.runtime_status == client.OrchestrationStatus.COMPLETED + assert state.serialized_output == json.dumps("Hello, World!") + assert state.failure_details is None + # Activity was called twice (once failed, once succeeded after rewind). + assert _activity_call_count == 2 + + +def test_rewind_preserves_successful_results(): + """When an orchestration has a mix of successful and failed activities, + rewind should re-execute only the failed activity while the successful + result is replayed from history.""" + _reset_counters() + + call_tracker: dict[str, int] = {"first": 0, "second": 0} + + def first_activity(_: task.ActivityContext, input: str) -> str: + call_tracker["first"] += 1 + return f"first:{input}" + + def second_activity(_: task.ActivityContext, input: str) -> str: + call_tracker["second"] += 1 + if call_tracker["second"] == 1: + raise RuntimeError("Temporary failure") + return f"second:{input}" + + def orchestrator(ctx: task.OrchestrationContext, input: str): + r1 = yield ctx.call_activity(first_activity, input=input) + r2 = yield ctx.call_activity(second_activity, input=input) + return [r1, r2] + + with worker.TaskHubGrpcWorker(host_address=HOST) as w: + w.add_orchestrator(orchestrator) + w.add_activity(first_activity) + w.add_activity(second_activity) + w.start() + + c = client.TaskHubGrpcClient(host_address=HOST) + instance_id = c.schedule_new_orchestration(orchestrator, input="test") + state = c.wait_for_orchestration_completion(instance_id, timeout=30) + + # The orchestration should have failed (second_activity fails). + assert state is not None + assert state.runtime_status == client.OrchestrationStatus.FAILED + + # Rewind – second_activity will now succeed on retry. + c.rewind_orchestration(instance_id, reason="retry") + state = c.wait_for_orchestration_completion(instance_id, timeout=30) + + assert state is not None + assert state.runtime_status == client.OrchestrationStatus.COMPLETED + assert state.serialized_output == json.dumps(["first:test", "second:test"]) + assert state.failure_details is None + # first_activity should NOT be re-executed – its result is replayed. + assert call_tracker["first"] == 1 + # second_activity was called twice (once failed, once succeeded). + assert call_tracker["second"] == 2 + + +def test_rewind_not_found(): + """Rewinding a non-existent instance should raise an RPC error.""" + with worker.TaskHubGrpcWorker(host_address=HOST) as w: + w.start() + c = client.TaskHubGrpcClient(host_address=HOST) + with pytest.raises(Exception): + c.rewind_orchestration("nonexistent-id") + + +def test_rewind_non_failed_instance(): + """Rewinding a completed (non-failed) instance should raise an error.""" + def orchestrator(ctx: task.OrchestrationContext, _): + return "done" + + with worker.TaskHubGrpcWorker(host_address=HOST) as w: + w.add_orchestrator(orchestrator) + w.start() + + c = client.TaskHubGrpcClient(host_address=HOST) + instance_id = c.schedule_new_orchestration(orchestrator) + state = c.wait_for_orchestration_completion(instance_id, timeout=30) + assert state is not None + assert state.runtime_status == client.OrchestrationStatus.COMPLETED + + with pytest.raises(Exception): + c.rewind_orchestration(instance_id) + + +def test_rewind_with_sub_orchestration(): + """Rewind should recursively rewind failed sub-orchestrations.""" + sub_call_count = 0 + + def child_activity(_: task.ActivityContext, input: str) -> str: + nonlocal sub_call_count + sub_call_count += 1 + if sub_call_count == 1: + raise RuntimeError("Child failure") + return f"child:{input}" + + def child_orchestrator(ctx: task.OrchestrationContext, input: str): + result = yield ctx.call_activity(child_activity, input=input) + return result + + def parent_orchestrator(ctx: task.OrchestrationContext, input: str): + result = yield ctx.call_sub_orchestrator( + child_orchestrator, input=input) + return f"parent:{result}" + + with worker.TaskHubGrpcWorker(host_address=HOST) as w: + w.add_orchestrator(parent_orchestrator) + w.add_orchestrator(child_orchestrator) + w.add_activity(child_activity) + w.start() + + c = client.TaskHubGrpcClient(host_address=HOST) + instance_id = c.schedule_new_orchestration( + parent_orchestrator, input="data") + state = c.wait_for_orchestration_completion(instance_id, timeout=30) + + # Parent should fail because child failed. + assert state is not None + assert state.runtime_status == client.OrchestrationStatus.FAILED + + # Rewind – child_activity will succeed on retry. + c.rewind_orchestration(instance_id, reason="sub-orch fix") + state = c.wait_for_orchestration_completion(instance_id, timeout=30) + + assert state is not None + assert state.runtime_status == client.OrchestrationStatus.COMPLETED + assert state.serialized_output == json.dumps("parent:child:data") + assert sub_call_count == 2 + + +def test_rewind_without_reason(): + """Rewind should work when no reason is provided.""" + _reset_counters() + call_count = 0 + + def flaky_activity(_: task.ActivityContext, _1) -> str: + nonlocal call_count + call_count += 1 + if call_count == 1: + raise RuntimeError("Boom") + return "ok" + + def orchestrator(ctx: task.OrchestrationContext, _): + result = yield ctx.call_activity(flaky_activity) + return result + + with worker.TaskHubGrpcWorker(host_address=HOST) as w: + w.add_orchestrator(orchestrator) + w.add_activity(flaky_activity) + w.start() + + c = client.TaskHubGrpcClient(host_address=HOST) + instance_id = c.schedule_new_orchestration(orchestrator) + state = c.wait_for_orchestration_completion(instance_id, timeout=30) + assert state is not None + assert state.runtime_status == client.OrchestrationStatus.FAILED + + # Rewind without a reason + c.rewind_orchestration(instance_id) + state = c.wait_for_orchestration_completion(instance_id, timeout=30) + + assert state is not None + assert state.runtime_status == client.OrchestrationStatus.COMPLETED + assert state.serialized_output == json.dumps("ok") + + +def test_rewind_twice(): + """Rewind the same orchestration twice after it fails a second time. + + The first rewind cleans up the initial failure. The activity then + fails again. A second rewind should clean up the new failure and + the orchestration should eventually complete. + """ + call_count = 0 + + def flaky_activity(_: task.ActivityContext, input: str) -> str: + nonlocal call_count + call_count += 1 + # Fail on the 1st and 2nd calls; succeed on the 3rd. + if call_count <= 2: + raise RuntimeError(f"Failure #{call_count}") + return f"Hello, {input}!" + + def orchestrator(ctx: task.OrchestrationContext, input: str): + result = yield ctx.call_activity(flaky_activity, input=input) + return result + + with worker.TaskHubGrpcWorker(host_address=HOST) as w: + w.add_orchestrator(orchestrator) + w.add_activity(flaky_activity) + w.start() + + c = client.TaskHubGrpcClient(host_address=HOST) + instance_id = c.schedule_new_orchestration(orchestrator, input="World") + state = c.wait_for_orchestration_completion(instance_id, timeout=30) + + # First failure. + assert state is not None + assert state.runtime_status == client.OrchestrationStatus.FAILED + + # First rewind — activity will fail again (call_count == 2). + c.rewind_orchestration(instance_id, reason="first rewind") + state = c.wait_for_orchestration_completion(instance_id, timeout=30) + + assert state is not None + assert state.runtime_status == client.OrchestrationStatus.FAILED + + # Second rewind — activity will succeed (call_count == 3). + c.rewind_orchestration(instance_id, reason="second rewind") + state = c.wait_for_orchestration_completion(instance_id, timeout=30) + + assert state is not None + assert state.runtime_status == client.OrchestrationStatus.COMPLETED + assert state.serialized_output == json.dumps("Hello, World!") + assert call_count == 3 From 9ae8aa0795b34bb85f5ab9fcf87f506546b442b6 Mon Sep 17 00:00:00 2001 From: Andy Staples Date: Fri, 6 Mar 2026 14:40:54 -0700 Subject: [PATCH 2/6] Fix in-memory-backend history ordering, rewind check --- durabletask/internal/helpers.py | 15 ++++++ durabletask/testing/in_memory_backend.py | 34 +++++++++----- durabletask/worker.py | 60 +++++++++++++----------- 3 files changed, 70 insertions(+), 39 deletions(-) diff --git a/durabletask/internal/helpers.py b/durabletask/internal/helpers.py index 4720046..3e95d00 100644 --- a/durabletask/internal/helpers.py +++ b/durabletask/internal/helpers.py @@ -181,6 +181,21 @@ def new_terminated_event(*, encoded_output: Optional[str] = None) -> pb.HistoryE ) +def new_execution_completed_event( + status: 'pb.OrchestrationStatus', + encoded_result: Optional[str] = None, + failure_details: Optional['pb.TaskFailureDetails'] = None) -> pb.HistoryEvent: + return pb.HistoryEvent( + eventId=-1, + timestamp=timestamp_pb2.Timestamp(), + executionCompleted=pb.ExecutionCompletedEvent( + orchestrationStatus=status, + result=get_string_value(encoded_result), + failureDetails=failure_details, + ) + ) + + def get_string_value(val: Optional[str]) -> Optional[wrappers_pb2.StringValue]: if val is None: return None diff --git a/durabletask/testing/in_memory_backend.py b/durabletask/testing/in_memory_backend.py index 24ebef5..556df37 100644 --- a/durabletask/testing/in_memory_backend.py +++ b/durabletask/testing/in_memory_backend.py @@ -241,14 +241,16 @@ def StartInstance(self, request: pb.CreateInstanceRequest, context): ) self._next_completion_token += 1 - # Add initial events to start the orchestration - orchestrator_started = helpers.new_orchestrator_started_event(start_time) + # Add initial events to start the orchestration. + # orchestratorStarted bookends each replay batch and is + # always the very first event, followed by executionStarted. execution_started = helpers.new_execution_started_event( request.name, instance_id, request.input.value if request.input else None, dict(request.tags) if request.tags else None, version=version, ) + orchestrator_started = helpers.new_orchestrator_started_event(start_time) instance.pending_events.append(orchestrator_started) instance.pending_events.append(execution_started) @@ -612,6 +614,22 @@ def CompleteOrchestratorTask(self, request: pb.OrchestratorResponse, context): instance.completion_token = self._next_completion_token self._next_completion_token += 1 + # Bookend the replay with orchestratorCompleted. + # Skip for continue-as-new (status is PENDING after reset). + if instance.status != pb.ORCHESTRATION_STATUS_PENDING: + instance.history.append(helpers.new_orchestrator_completed_event()) + + # executionCompleted is the very last event when the + # orchestration reaches a terminal state. + if self._is_terminal_status(instance.status): + instance.history.append( + helpers.new_execution_completed_event( + instance.status, + instance.output, + instance.failure_details, + ) + ) + # Remove from in-flight before notifying or re-enqueuing self._orchestration_in_flight.discard(request.instanceId) @@ -1092,9 +1110,9 @@ def _create_instance_internal(self, instance_id: str, name: str, ) self._next_completion_token += 1 - orchestrator_started = helpers.new_orchestrator_started_event(now) execution_started = helpers.new_execution_started_event( name, instance_id, encoded_input, version=version) + orchestrator_started = helpers.new_orchestrator_started_event(now) instance.pending_events.append(orchestrator_started) instance.pending_events.append(execution_started) @@ -1232,14 +1250,6 @@ def _process_complete_orchestration_action(self, instance: OrchestrationInstance instance.output = complete_action.result.value if complete_action.result else None instance.failure_details = complete_action.failureDetails if complete_action.failureDetails else None - # Append orchestratorCompleted to history when the orchestration - # reaches a terminal state. This positional marker allows the - # SDK to distinguish a post-rewind replay from a new rewind - # request by comparing the position of the last - # orchestratorCompleted against the last executionRewound. - if status != pb.ORCHESTRATION_STATUS_CONTINUED_AS_NEW: - instance.history.append(helpers.new_orchestrator_completed_event()) - if status == pb.ORCHESTRATION_STATUS_CONTINUED_AS_NEW: # Handle continue-as-new new_input = complete_action.result.value if complete_action.result else None @@ -1265,11 +1275,11 @@ def _process_complete_orchestration_action(self, instance: OrchestrationInstance # Build the new pending events in the correct order: # OrchestratorStarted, ExecutionStarted, carryover, new arrivals now = datetime.now(timezone.utc) - orchestrator_started = helpers.new_orchestrator_started_event(now) execution_started = helpers.new_execution_started_event( instance.name, instance.instance_id, new_input, version=new_version, ) + orchestrator_started = helpers.new_orchestrator_started_event(now) instance.pending_events.append(orchestrator_started) instance.pending_events.append(execution_started) instance.pending_events.extend(carryover_events) diff --git a/durabletask/worker.py b/durabletask/worker.py index 3b8af41..f9a3a3a 100644 --- a/durabletask/worker.py +++ b/durabletask/worker.py @@ -1315,39 +1315,40 @@ def execute( ) # Check for rewind BEFORE replay. A rewind is indicated by an - # executionRewound event in new_events. We need to distinguish - # two cases: - # 1. A *new* rewind request that the worker must short-circuit: - # either there is no executionRewound in old_events yet - # (first rewind), or the last orchestratorCompleted comes - # AFTER the last executionRewound in old_events — meaning - # the orchestration ran and completed (with failure) after - # a prior rewind. - # 2. A *post-rewind replay* where the backend has already - # processed the RewindOrchestrationAction, cleaned the - # history, and re-dispatched the orchestration. In this - # case the last executionRewound in old_events comes AFTER - # the last orchestratorCompleted. + # executionRewound event in new_events. We look for an + # executionCompleted event anywhere in the history (old or new + # events) to decide whether to rewind or replay: + # 1. executionCompleted IS present → the orchestration reached a + # terminal state (e.g. failed). This is a *new* rewind that + # the worker must short-circuit by building clean history. + # 2. executionCompleted is NOT present → the backend already + # processed the RewindOrchestrationAction which removed + # executionCompleted from history. This is a normal + # post-rewind replay. has_rewind_in_new = any( e.HasField("executionRewound") for e in new_events ) if has_rewind_in_new: - last_rewind_pos = -1 - last_completed_pos = -1 - for i, e in enumerate(old_events): - if e.HasField("executionRewound"): - last_rewind_pos = i - elif e.HasField("orchestratorCompleted"): - last_completed_pos = i - - if last_rewind_pos < 0 or last_completed_pos > last_rewind_pos: - # Either first rewind (no executionRewound in history) - # or the orchestration completed after a prior rewind - # (orchestratorCompleted comes after executionRewound), - # so this is a new rewind that needs short-circuiting. + from itertools import chain + has_execution_completed = any( + e.HasField("executionCompleted") + for e in chain(old_events, new_events) + ) + if has_execution_completed: + # The orchestration completed (with failure) and needs + # rewinding — short-circuit to build clean history. return self._build_rewind_result( instance_id, orchestration_name, old_events, new_events) + # During replay, remove executionCompleted events from the + # committed history so the orchestrator function replays + # cleanly. orchestratorCompleted events are kept as they + # bookend each replay batch. + old_events = [ + e for e in old_events + if not e.HasField("executionCompleted") + ] + ctx = _RuntimeOrchestrationContext(instance_id, self._registry) try: # Rebuild local state by replaying old history into the orchestrator function @@ -1459,6 +1460,8 @@ def _build_rewind_result( continue if event.HasField("subOrchestrationInstanceFailed"): continue + if event.HasField("executionCompleted"): + continue clean_history.append(event) rewind_action = pb.RewindOrchestrationAction(newHistory=clean_history) @@ -1888,7 +1891,10 @@ def process_event( entity_task.fail(str(failure), failure) ctx.resume() elif event.HasField("orchestratorCompleted"): - # Added in Functions only (for some reason) and does not affect orchestrator flow + # Bookend event for each replay batch — no action needed. + pass + elif event.HasField("executionCompleted"): + # Terminal marker event — no action needed during replay. pass elif event.HasField("executionRewound"): # Informational event added when an orchestration is rewound. No action needed. From 5dfc57fb2261709c16861387bfea92938a9c7da0 Mon Sep 17 00:00:00 2001 From: Andy Staples Date: Fri, 6 Mar 2026 14:52:49 -0700 Subject: [PATCH 3/6] Match other rewind implementations better --- durabletask/worker.py | 35 +- tests/durabletask/test_build_rewind_result.py | 710 ++++++++++++++++++ 2 files changed, 743 insertions(+), 2 deletions(-) create mode 100644 tests/durabletask/test_build_rewind_result.py diff --git a/durabletask/worker.py b/durabletask/worker.py index f9a3a3a..3a63c8d 100644 --- a/durabletask/worker.py +++ b/durabletask/worker.py @@ -1443,13 +1443,27 @@ def _build_rewind_result( # failed results so the orchestration can replay successfully. all_events = list(old_events) + list(new_events) + # Extract the executionRewound event from new_events so we can + # read its parentExecutionId (set when this is a sub-orchestration + # being rewound by its parent). + rewind_event: pb.ExecutionRewoundEvent | None = None + for e in new_events: + if e.HasField("executionRewound"): + rewind_event = e.executionRewound + break + + # Generate a new execution ID for the rewound execution. + new_execution_id = uuid.uuid4().hex + # First pass: collect the task-scheduled IDs that correspond to - # failed activities so we can remove the matching taskScheduled - # events in the second pass. + # failed activities / sub-orchestrations so we can remove the + # matching taskScheduled events in the second pass. failed_task_ids: set[int] = set() for event in all_events: if event.HasField("taskFailed"): failed_task_ids.add(event.taskFailed.taskScheduledId) + elif event.HasField("subOrchestrationInstanceFailed"): + failed_task_ids.add(event.subOrchestrationInstanceFailed.taskScheduledId) # Second pass: build the clean history. clean_history: list[pb.HistoryEvent] = [] @@ -1462,6 +1476,23 @@ def _build_rewind_result( continue if event.HasField("executionCompleted"): continue + + # Modify the executionStarted event: assign a fresh + # execution ID and, for sub-orchestrations, update the + # parent's execution ID so it matches the parent's new run. + if event.HasField("executionStarted"): + event_copy = pb.HistoryEvent() + event_copy.CopyFrom(event) + event_copy.executionStarted.orchestrationInstance.executionId.CopyFrom( + ph.get_string_value(new_execution_id)) + if (rewind_event is not None + and rewind_event.HasField("parentExecutionId") + and rewind_event.parentExecutionId.value): + event_copy.executionStarted.parentInstance.orchestrationInstance.executionId.CopyFrom( + rewind_event.parentExecutionId) + clean_history.append(event_copy) + continue + clean_history.append(event) rewind_action = pb.RewindOrchestrationAction(newHistory=clean_history) diff --git a/tests/durabletask/test_build_rewind_result.py b/tests/durabletask/test_build_rewind_result.py new file mode 100644 index 0000000..bcee4e7 --- /dev/null +++ b/tests/durabletask/test_build_rewind_result.py @@ -0,0 +1,710 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +"""Unit tests for _OrchestrationExecutor._build_rewind_result. + +These tests directly invoke the rewind history-rewriting logic and +verify that the clean history produced by the worker matches the +expected semantics: + +* The ``executionStarted`` event gets a new execution ID. +* When a ``parentExecutionId`` is present on the rewind event, + the ``parentInstance.orchestrationInstance.executionId`` on the + ``executionStarted`` copy is updated accordingly. +* ``taskFailed`` events and their corresponding ``taskScheduled`` + events are removed. +* ``subOrchestrationInstanceFailed`` events and their corresponding + ``taskScheduled`` events are removed. +* ``subOrchestrationInstanceCreated`` events for failed sub-orchestrations + are kept so the backend can recursively rewind them. +* ``executionCompleted`` events are removed. +* Successful activity/sub-orchestration results are preserved. +""" + +import json +import logging + +import durabletask.internal.helpers as helpers +import durabletask.internal.orchestrator_service_pb2 as pb +from durabletask import task, worker +from google.protobuf import wrappers_pb2 + +logging.basicConfig( + format='%(asctime)s.%(msecs)03d %(name)s %(levelname)s: %(message)s', + datefmt='%Y-%m-%d %H:%M:%S', + level=logging.DEBUG) +TEST_LOGGER = logging.getLogger("tests") + +TEST_INSTANCE_ID = "rewind-test-instance" +ORIGINAL_EXECUTION_ID = "original-exec-id" + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +def _make_execution_started( + name: str, + instance_id: str = TEST_INSTANCE_ID, + execution_id: str = ORIGINAL_EXECUTION_ID, + parent_instance: pb.ParentInstanceInfo | None = None, +) -> pb.HistoryEvent: + """Create an executionStarted event with an explicit execution ID.""" + event = pb.HistoryEvent( + eventId=-1, + executionStarted=pb.ExecutionStartedEvent( + name=name, + orchestrationInstance=pb.OrchestrationInstance( + instanceId=instance_id, + executionId=wrappers_pb2.StringValue(value=execution_id), + ), + ), + ) + if parent_instance is not None: + event.executionStarted.parentInstance.CopyFrom(parent_instance) + return event + + +def _make_execution_rewound( + reason: str = "test rewind", + parent_execution_id: str | None = None, +) -> pb.HistoryEvent: + """Create an executionRewound history event.""" + rewound = pb.ExecutionRewoundEvent( + reason=wrappers_pb2.StringValue(value=reason), + ) + if parent_execution_id is not None: + rewound.parentExecutionId.CopyFrom( + wrappers_pb2.StringValue(value=parent_execution_id)) + return pb.HistoryEvent( + eventId=-1, + executionRewound=rewound, + ) + + +def _dummy_orchestrator(ctx: task.OrchestrationContext, _): + return None + + +def _make_executor() -> worker._OrchestrationExecutor: + """Create a minimal _OrchestrationExecutor (no registered functions needed).""" + registry = worker._Registry() + registry.add_orchestrator(_dummy_orchestrator) + return worker._OrchestrationExecutor(registry, TEST_LOGGER) + + +def _get_clean_history(result: worker.ExecutionResults) -> list[pb.HistoryEvent]: + """Extract the clean history from a RewindOrchestrationAction result.""" + assert len(result.actions) == 1 + action = result.actions[0] + assert action.HasField("rewindOrchestration") + return list(action.rewindOrchestration.newHistory) + + +# --------------------------------------------------------------------------- +# Tests: execution ID changes +# --------------------------------------------------------------------------- + + +def test_rewind_assigns_new_execution_id(): + """The executionStarted event in the clean history must have a new, + different execution ID.""" + executor = _make_executor() + + old_events = [ + helpers.new_orchestrator_started_event(), + _make_execution_started("my_orch"), + helpers.new_task_scheduled_event(1, "my_activity"), + helpers.new_task_failed_event(1, RuntimeError("boom")), + helpers.new_orchestrator_completed_event(), + helpers.new_execution_completed_event( + pb.ORCHESTRATION_STATUS_FAILED, + failure_details=helpers.new_failure_details(RuntimeError("boom"))), + ] + new_events = [ + helpers.new_orchestrator_started_event(), + _make_execution_rewound("retry"), + ] + + result = executor._build_rewind_result( + TEST_INSTANCE_ID, "my_orch", old_events, new_events) + clean = _get_clean_history(result) + + # Find the executionStarted event + started_events = [e for e in clean if e.HasField("executionStarted")] + assert len(started_events) == 1 + + new_exec_id = started_events[0].executionStarted.orchestrationInstance.executionId.value + assert new_exec_id != ORIGINAL_EXECUTION_ID + assert len(new_exec_id) > 0 # must be a non-empty string + + +def test_rewind_preserves_execution_started_fields(): + """The executionStarted copy should preserve the original fields + (name, instance ID) while changing only the execution ID.""" + executor = _make_executor() + + old_events = [ + helpers.new_orchestrator_started_event(), + _make_execution_started("preserve_me"), + helpers.new_task_scheduled_event(1, "act"), + helpers.new_task_failed_event(1, RuntimeError("fail")), + helpers.new_orchestrator_completed_event(), + helpers.new_execution_completed_event( + pb.ORCHESTRATION_STATUS_FAILED, + failure_details=helpers.new_failure_details(RuntimeError("fail"))), + ] + new_events = [ + helpers.new_orchestrator_started_event(), + _make_execution_rewound("retry"), + ] + + result = executor._build_rewind_result( + TEST_INSTANCE_ID, "preserve_me", old_events, new_events) + clean = _get_clean_history(result) + + started = [e for e in clean if e.HasField("executionStarted")][0] + assert started.executionStarted.name == "preserve_me" + assert started.executionStarted.orchestrationInstance.instanceId == TEST_INSTANCE_ID + + +def test_rewind_updates_parent_execution_id(): + """When the rewind event carries a parentExecutionId, the + executionStarted copy must update + parentInstance.orchestrationInstance.executionId to match.""" + executor = _make_executor() + + parent_exec_id = "parent-old-exec-id" + parent_new_exec_id = "parent-new-exec-id" + + parent_info = pb.ParentInstanceInfo( + taskScheduledId=5, + name=wrappers_pb2.StringValue(value="parent_orch"), + orchestrationInstance=pb.OrchestrationInstance( + instanceId="parent-instance", + executionId=wrappers_pb2.StringValue(value=parent_exec_id), + ), + ) + + old_events = [ + helpers.new_orchestrator_started_event(), + _make_execution_started("child_orch", parent_instance=parent_info), + helpers.new_task_scheduled_event(1, "child_act"), + helpers.new_task_failed_event(1, RuntimeError("child fail")), + helpers.new_orchestrator_completed_event(), + helpers.new_execution_completed_event( + pb.ORCHESTRATION_STATUS_FAILED, + failure_details=helpers.new_failure_details(RuntimeError("child fail"))), + ] + new_events = [ + helpers.new_orchestrator_started_event(), + _make_execution_rewound("parent rewind", parent_execution_id=parent_new_exec_id), + ] + + result = executor._build_rewind_result( + TEST_INSTANCE_ID, "child_orch", old_events, new_events) + clean = _get_clean_history(result) + + started = [e for e in clean if e.HasField("executionStarted")][0] + # Parent execution ID should be updated to the new one. + actual_parent_exec_id = ( + started.executionStarted.parentInstance + .orchestrationInstance.executionId.value + ) + assert actual_parent_exec_id == parent_new_exec_id + + +def test_rewind_no_parent_execution_id_leaves_parent_unchanged(): + """When the rewind event has no parentExecutionId, the executionStarted + copy should leave the parentInstance untouched (if any).""" + executor = _make_executor() + + parent_exec_id = "parent-exec-id-unchanged" + parent_info = pb.ParentInstanceInfo( + taskScheduledId=5, + name=wrappers_pb2.StringValue(value="parent_orch"), + orchestrationInstance=pb.OrchestrationInstance( + instanceId="parent-instance", + executionId=wrappers_pb2.StringValue(value=parent_exec_id), + ), + ) + + old_events = [ + helpers.new_orchestrator_started_event(), + _make_execution_started("child_orch", parent_instance=parent_info), + helpers.new_task_scheduled_event(1, "child_act"), + helpers.new_task_failed_event(1, RuntimeError("fail")), + helpers.new_orchestrator_completed_event(), + helpers.new_execution_completed_event( + pb.ORCHESTRATION_STATUS_FAILED, + failure_details=helpers.new_failure_details(RuntimeError("fail"))), + ] + new_events = [ + helpers.new_orchestrator_started_event(), + # No parentExecutionId on the rewind event. + _make_execution_rewound("top-level rewind"), + ] + + result = executor._build_rewind_result( + TEST_INSTANCE_ID, "child_orch", old_events, new_events) + clean = _get_clean_history(result) + + started = [e for e in clean if e.HasField("executionStarted")][0] + actual_parent_exec_id = ( + started.executionStarted.parentInstance + .orchestrationInstance.executionId.value + ) + # Should remain unchanged. + assert actual_parent_exec_id == parent_exec_id + + +# --------------------------------------------------------------------------- +# Tests: failed activity cleanup +# --------------------------------------------------------------------------- + + +def test_rewind_removes_failed_activity_events(): + """taskFailed and its corresponding taskScheduled should be removed.""" + executor = _make_executor() + + old_events = [ + helpers.new_orchestrator_started_event(), + _make_execution_started("orch"), + helpers.new_task_scheduled_event(1, "my_act"), + helpers.new_task_failed_event(1, RuntimeError("boom")), + helpers.new_orchestrator_completed_event(), + helpers.new_execution_completed_event( + pb.ORCHESTRATION_STATUS_FAILED, + failure_details=helpers.new_failure_details(RuntimeError("boom"))), + ] + new_events = [ + helpers.new_orchestrator_started_event(), + _make_execution_rewound("retry"), + ] + + result = executor._build_rewind_result( + TEST_INSTANCE_ID, "orch", old_events, new_events) + clean = _get_clean_history(result) + + # No taskFailed or taskScheduled for the failed activity. + assert not any(e.HasField("taskFailed") for e in clean) + assert not any( + e.HasField("taskScheduled") and e.eventId == 1 for e in clean + ) + + +def test_rewind_preserves_successful_activity(): + """Successful taskScheduled + taskCompleted should remain in clean history.""" + executor = _make_executor() + + old_events = [ + helpers.new_orchestrator_started_event(), + _make_execution_started("orch"), + # Activity 1 succeeds + helpers.new_task_scheduled_event(1, "good_act"), + helpers.new_task_completed_event(1, json.dumps("ok")), + helpers.new_orchestrator_completed_event(), + # Activity 2 fails + helpers.new_orchestrator_started_event(), + helpers.new_task_scheduled_event(2, "bad_act"), + helpers.new_task_failed_event(2, RuntimeError("fail")), + helpers.new_orchestrator_completed_event(), + helpers.new_execution_completed_event( + pb.ORCHESTRATION_STATUS_FAILED, + failure_details=helpers.new_failure_details(RuntimeError("fail"))), + ] + new_events = [ + helpers.new_orchestrator_started_event(), + _make_execution_rewound("retry"), + ] + + result = executor._build_rewind_result( + TEST_INSTANCE_ID, "orch", old_events, new_events) + clean = _get_clean_history(result) + + # Activity 1's taskScheduled and taskCompleted should still be present. + assert any( + e.HasField("taskScheduled") and e.eventId == 1 for e in clean + ) + assert any( + e.HasField("taskCompleted") and e.taskCompleted.taskScheduledId == 1 + for e in clean + ) + # Activity 2's taskScheduled and taskFailed should be removed. + assert not any( + e.HasField("taskScheduled") and e.eventId == 2 for e in clean + ) + assert not any(e.HasField("taskFailed") for e in clean) + + +# --------------------------------------------------------------------------- +# Tests: failed sub-orchestration cleanup +# --------------------------------------------------------------------------- + + +def test_rewind_removes_failed_sub_orch_events(): + """subOrchestrationInstanceFailed and its corresponding taskScheduled + should be removed, but subOrchestrationInstanceCreated is kept.""" + executor = _make_executor() + + old_events = [ + helpers.new_orchestrator_started_event(), + _make_execution_started("parent_orch"), + helpers.new_sub_orchestration_created_event(1, "child_orch", "child-id"), + helpers.new_orchestrator_completed_event(), + helpers.new_orchestrator_started_event(), + helpers.new_sub_orchestration_failed_event(1, RuntimeError("child exploded")), + helpers.new_orchestrator_completed_event(), + helpers.new_execution_completed_event( + pb.ORCHESTRATION_STATUS_FAILED, + failure_details=helpers.new_failure_details(RuntimeError("child exploded"))), + ] + new_events = [ + helpers.new_orchestrator_started_event(), + _make_execution_rewound("rewind child"), + ] + + result = executor._build_rewind_result( + TEST_INSTANCE_ID, "parent_orch", old_events, new_events) + clean = _get_clean_history(result) + + # subOrchestrationInstanceFailed is removed. + assert not any( + e.HasField("subOrchestrationInstanceFailed") for e in clean + ) + # The corresponding taskScheduled (eventId == 1) used by + # subOrchestrationInstanceFailed should also be removed, since + # _build_rewind_result collects the taskScheduledId from + # subOrchestrationInstanceFailed too. + # Note: subOrchestrationInstanceCreated uses a separate event with + # its own eventId and is NOT removed. + assert not any( + e.HasField("taskScheduled") and e.eventId == 1 for e in clean + ) + # The subOrchestrationInstanceCreated event should be preserved + # so the backend can identify which sub-orchestration to rewind. + assert any( + e.HasField("subOrchestrationInstanceCreated") for e in clean + ) + + +def test_rewind_preserves_successful_sub_orchestration(): + """Successful sub-orchestration events should be preserved.""" + executor = _make_executor() + + old_events = [ + helpers.new_orchestrator_started_event(), + _make_execution_started("parent_orch"), + # Sub-orch 1 succeeds + helpers.new_sub_orchestration_created_event(1, "child_ok", "child-ok-id"), + helpers.new_orchestrator_completed_event(), + helpers.new_orchestrator_started_event(), + helpers.new_sub_orchestration_completed_event(1, json.dumps("child result")), + # Sub-orch 2 fails + helpers.new_sub_orchestration_created_event(2, "child_fail", "child-fail-id"), + helpers.new_orchestrator_completed_event(), + helpers.new_orchestrator_started_event(), + helpers.new_sub_orchestration_failed_event(2, RuntimeError("fail")), + helpers.new_orchestrator_completed_event(), + helpers.new_execution_completed_event( + pb.ORCHESTRATION_STATUS_FAILED, + failure_details=helpers.new_failure_details(RuntimeError("fail"))), + ] + new_events = [ + helpers.new_orchestrator_started_event(), + _make_execution_rewound("retry"), + ] + + result = executor._build_rewind_result( + TEST_INSTANCE_ID, "parent_orch", old_events, new_events) + clean = _get_clean_history(result) + + # Sub-orch 1's created + completed should be present. + assert any( + e.HasField("subOrchestrationInstanceCreated") + and e.subOrchestrationInstanceCreated.instanceId == "child-ok-id" + for e in clean + ) + assert any( + e.HasField("subOrchestrationInstanceCompleted") + and e.subOrchestrationInstanceCompleted.taskScheduledId == 1 + for e in clean + ) + # Sub-orch 2's failed event should be removed. + assert not any( + e.HasField("subOrchestrationInstanceFailed") for e in clean + ) + # Sub-orch 2's created event should be kept (for backend recursive rewind). + assert any( + e.HasField("subOrchestrationInstanceCreated") + and e.subOrchestrationInstanceCreated.instanceId == "child-fail-id" + for e in clean + ) + + +# --------------------------------------------------------------------------- +# Tests: executionCompleted removal +# --------------------------------------------------------------------------- + + +def test_rewind_removes_execution_completed(): + """executionCompleted events should be stripped from the clean history.""" + executor = _make_executor() + + old_events = [ + helpers.new_orchestrator_started_event(), + _make_execution_started("orch"), + helpers.new_task_scheduled_event(1, "act"), + helpers.new_task_failed_event(1, RuntimeError("fail")), + helpers.new_orchestrator_completed_event(), + helpers.new_execution_completed_event( + pb.ORCHESTRATION_STATUS_FAILED, + failure_details=helpers.new_failure_details(RuntimeError("fail"))), + ] + new_events = [ + helpers.new_orchestrator_started_event(), + _make_execution_rewound("retry"), + ] + + result = executor._build_rewind_result( + TEST_INSTANCE_ID, "orch", old_events, new_events) + clean = _get_clean_history(result) + + assert not any(e.HasField("executionCompleted") for e in clean) + + +# --------------------------------------------------------------------------- +# Tests: orchestratorStarted/Completed preservation +# --------------------------------------------------------------------------- + + +def test_rewind_keeps_orchestrator_started_and_completed(): + """orchestratorStarted and orchestratorCompleted bookend events + should be preserved in clean history.""" + executor = _make_executor() + + old_events = [ + helpers.new_orchestrator_started_event(), + _make_execution_started("orch"), + helpers.new_task_scheduled_event(1, "act"), + helpers.new_orchestrator_completed_event(), + helpers.new_orchestrator_started_event(), + helpers.new_task_failed_event(1, RuntimeError("fail")), + helpers.new_orchestrator_completed_event(), + helpers.new_execution_completed_event( + pb.ORCHESTRATION_STATUS_FAILED, + failure_details=helpers.new_failure_details(RuntimeError("fail"))), + ] + new_events = [ + helpers.new_orchestrator_started_event(), + _make_execution_rewound("retry"), + ] + + result = executor._build_rewind_result( + TEST_INSTANCE_ID, "orch", old_events, new_events) + clean = _get_clean_history(result) + + orch_started_count = sum( + 1 for e in clean if e.HasField("orchestratorStarted") + ) + orch_completed_count = sum( + 1 for e in clean if e.HasField("orchestratorCompleted") + ) + # old_events has 2 orchestratorStarted + 2 orchestratorCompleted, + # new_events adds 1 orchestratorStarted. All should be kept. + assert orch_started_count >= 2 + assert orch_completed_count >= 2 + + +# --------------------------------------------------------------------------- +# Tests: executionRewound event preserved +# --------------------------------------------------------------------------- + + +def test_rewind_keeps_execution_rewound_event(): + """The executionRewound event itself should remain in the clean + history so it is visible in the audit trail.""" + executor = _make_executor() + + old_events = [ + helpers.new_orchestrator_started_event(), + _make_execution_started("orch"), + helpers.new_task_scheduled_event(1, "act"), + helpers.new_task_failed_event(1, RuntimeError("fail")), + helpers.new_orchestrator_completed_event(), + helpers.new_execution_completed_event( + pb.ORCHESTRATION_STATUS_FAILED, + failure_details=helpers.new_failure_details(RuntimeError("fail"))), + ] + new_events = [ + helpers.new_orchestrator_started_event(), + _make_execution_rewound("rewind reason"), + ] + + result = executor._build_rewind_result( + TEST_INSTANCE_ID, "orch", old_events, new_events) + clean = _get_clean_history(result) + + rewound_events = [e for e in clean if e.HasField("executionRewound")] + assert len(rewound_events) == 1 + assert rewound_events[0].executionRewound.reason.value == "rewind reason" + + +# --------------------------------------------------------------------------- +# Tests: mixed scenario +# --------------------------------------------------------------------------- + + +def test_rewind_mixed_activities_and_sub_orchestrations(): + """A complex scenario with successful activities, failed activities, + successful sub-orchestrations, and failed sub-orchestrations. + Verifies that only failed items are cleaned and execution ID is updated.""" + executor = _make_executor() + + old_events = [ + helpers.new_orchestrator_started_event(), + _make_execution_started("complex_orch"), + # Activity 1 succeeds (eventId=1) + helpers.new_task_scheduled_event(1, "good_activity"), + helpers.new_task_completed_event(1, json.dumps("good")), + # Sub-orch A succeeds (eventId=2) + helpers.new_sub_orchestration_created_event(2, "child_ok", "child-ok-id"), + helpers.new_orchestrator_completed_event(), + helpers.new_orchestrator_started_event(), + helpers.new_sub_orchestration_completed_event(2, json.dumps("child ok result")), + # Activity 3 fails (eventId=3) + helpers.new_task_scheduled_event(3, "bad_activity"), + helpers.new_task_failed_event(3, RuntimeError("act fail")), + helpers.new_orchestrator_completed_event(), + # Sub-orch B fails (eventId=4) + helpers.new_orchestrator_started_event(), + helpers.new_sub_orchestration_created_event(4, "child_fail", "child-fail-id"), + helpers.new_orchestrator_completed_event(), + helpers.new_orchestrator_started_event(), + helpers.new_sub_orchestration_failed_event(4, RuntimeError("sub orch fail")), + helpers.new_orchestrator_completed_event(), + helpers.new_execution_completed_event( + pb.ORCHESTRATION_STATUS_FAILED, + failure_details=helpers.new_failure_details(RuntimeError("overall fail"))), + ] + new_events = [ + helpers.new_orchestrator_started_event(), + _make_execution_rewound("fix everything"), + ] + + result = executor._build_rewind_result( + TEST_INSTANCE_ID, "complex_orch", old_events, new_events) + clean = _get_clean_history(result) + + # --- Execution ID changed --- + started = [e for e in clean if e.HasField("executionStarted")] + assert len(started) == 1 + assert (started[0].executionStarted.orchestrationInstance + .executionId.value != ORIGINAL_EXECUTION_ID) + + # --- Successful activity 1 preserved --- + assert any( + e.HasField("taskScheduled") and e.eventId == 1 for e in clean + ) + assert any( + e.HasField("taskCompleted") and e.taskCompleted.taskScheduledId == 1 + for e in clean + ) + + # --- Failed activity 3 removed --- + assert not any( + e.HasField("taskScheduled") and e.eventId == 3 for e in clean + ) + assert not any(e.HasField("taskFailed") for e in clean) + + # --- Successful sub-orch A preserved --- + assert any( + e.HasField("subOrchestrationInstanceCreated") + and e.subOrchestrationInstanceCreated.instanceId == "child-ok-id" + for e in clean + ) + assert any( + e.HasField("subOrchestrationInstanceCompleted") + and e.subOrchestrationInstanceCompleted.taskScheduledId == 2 + for e in clean + ) + + # --- Failed sub-orch B: failed event removed, created kept --- + assert not any( + e.HasField("subOrchestrationInstanceFailed") for e in clean + ) + assert any( + e.HasField("subOrchestrationInstanceCreated") + and e.subOrchestrationInstanceCreated.instanceId == "child-fail-id" + for e in clean + ) + + # --- executionCompleted removed --- + assert not any(e.HasField("executionCompleted") for e in clean) + + # --- executionRewound preserved --- + assert any(e.HasField("executionRewound") for e in clean) + + +def test_rewind_does_not_mutate_original_events(): + """Verify that the original history events are not modified in place.""" + executor = _make_executor() + + es_event = _make_execution_started("orch") + original_exec_id = ( + es_event.executionStarted.orchestrationInstance.executionId.value + ) + + old_events = [ + helpers.new_orchestrator_started_event(), + es_event, + helpers.new_task_scheduled_event(1, "act"), + helpers.new_task_failed_event(1, RuntimeError("fail")), + helpers.new_orchestrator_completed_event(), + helpers.new_execution_completed_event( + pb.ORCHESTRATION_STATUS_FAILED, + failure_details=helpers.new_failure_details(RuntimeError("fail"))), + ] + new_events = [ + helpers.new_orchestrator_started_event(), + _make_execution_rewound("retry"), + ] + + executor._build_rewind_result( + TEST_INSTANCE_ID, "orch", old_events, new_events) + + # The original executionStarted event should NOT be mutated. + assert ( + es_event.executionStarted.orchestrationInstance.executionId.value + == original_exec_id + ) + + +def test_rewind_result_action_structure(): + """The result should contain exactly one OrchestratorAction with id=-1 + and a rewindOrchestration field.""" + executor = _make_executor() + + old_events = [ + helpers.new_orchestrator_started_event(), + _make_execution_started("orch"), + helpers.new_task_scheduled_event(1, "act"), + helpers.new_task_failed_event(1, RuntimeError("fail")), + helpers.new_orchestrator_completed_event(), + helpers.new_execution_completed_event( + pb.ORCHESTRATION_STATUS_FAILED, + failure_details=helpers.new_failure_details(RuntimeError("fail"))), + ] + new_events = [ + helpers.new_orchestrator_started_event(), + _make_execution_rewound("retry"), + ] + + result = executor._build_rewind_result( + TEST_INSTANCE_ID, "orch", old_events, new_events) + + assert len(result.actions) == 1 + action = result.actions[0] + assert action.id == -1 + assert action.HasField("rewindOrchestration") + assert result.encoded_custom_status is None From 36000600367e821704879a796e9e769cbc8ec68a Mon Sep 17 00:00:00 2001 From: Andy Staples Date: Fri, 6 Mar 2026 15:00:36 -0700 Subject: [PATCH 4/6] Lint + pylance --- durabletask/worker.py | 12 ++-- tests/durabletask/test_build_rewind_result.py | 58 ++++++++----------- 2 files changed, 30 insertions(+), 40 deletions(-) diff --git a/durabletask/worker.py b/durabletask/worker.py index 3a63c8d..2641949 100644 --- a/durabletask/worker.py +++ b/durabletask/worker.py @@ -1484,12 +1484,12 @@ def _build_rewind_result( event_copy = pb.HistoryEvent() event_copy.CopyFrom(event) event_copy.executionStarted.orchestrationInstance.executionId.CopyFrom( - ph.get_string_value(new_execution_id)) - if (rewind_event is not None - and rewind_event.HasField("parentExecutionId") - and rewind_event.parentExecutionId.value): - event_copy.executionStarted.parentInstance.orchestrationInstance.executionId.CopyFrom( - rewind_event.parentExecutionId) + ph.get_string_value_or_empty(new_execution_id)) + if rewind_event is not None: + if rewind_event.HasField("parentExecutionId"): + if rewind_event.parentExecutionId.value: + event_copy.executionStarted.parentInstance.orchestrationInstance.executionId.CopyFrom( + rewind_event.parentExecutionId) clean_history.append(event_copy) continue diff --git a/tests/durabletask/test_build_rewind_result.py b/tests/durabletask/test_build_rewind_result.py index bcee4e7..68c7428 100644 --- a/tests/durabletask/test_build_rewind_result.py +++ b/tests/durabletask/test_build_rewind_result.py @@ -420,26 +420,22 @@ def test_rewind_preserves_successful_sub_orchestration(): clean = _get_clean_history(result) # Sub-orch 1's created + completed should be present. - assert any( - e.HasField("subOrchestrationInstanceCreated") - and e.subOrchestrationInstanceCreated.instanceId == "child-ok-id" - for e in clean - ) - assert any( - e.HasField("subOrchestrationInstanceCompleted") - and e.subOrchestrationInstanceCompleted.taskScheduledId == 1 - for e in clean - ) + created_ids = [ + e.subOrchestrationInstanceCreated.instanceId + for e in clean if e.HasField("subOrchestrationInstanceCreated") + ] + assert "child-ok-id" in created_ids + completed_sub_ids = [ + e.subOrchestrationInstanceCompleted.taskScheduledId + for e in clean if e.HasField("subOrchestrationInstanceCompleted") + ] + assert 1 in completed_sub_ids # Sub-orch 2's failed event should be removed. assert not any( e.HasField("subOrchestrationInstanceFailed") for e in clean ) # Sub-orch 2's created event should be kept (for backend recursive rewind). - assert any( - e.HasField("subOrchestrationInstanceCreated") - and e.subOrchestrationInstanceCreated.instanceId == "child-fail-id" - for e in clean - ) + assert "child-fail-id" in created_ids # --------------------------------------------------------------------------- @@ -618,26 +614,22 @@ def test_rewind_mixed_activities_and_sub_orchestrations(): assert not any(e.HasField("taskFailed") for e in clean) # --- Successful sub-orch A preserved --- - assert any( - e.HasField("subOrchestrationInstanceCreated") - and e.subOrchestrationInstanceCreated.instanceId == "child-ok-id" - for e in clean - ) - assert any( - e.HasField("subOrchestrationInstanceCompleted") - and e.subOrchestrationInstanceCompleted.taskScheduledId == 2 - for e in clean - ) + created_ids = [ + e.subOrchestrationInstanceCreated.instanceId + for e in clean if e.HasField("subOrchestrationInstanceCreated") + ] + assert "child-ok-id" in created_ids + completed_sub_ids = [ + e.subOrchestrationInstanceCompleted.taskScheduledId + for e in clean if e.HasField("subOrchestrationInstanceCompleted") + ] + assert 2 in completed_sub_ids # --- Failed sub-orch B: failed event removed, created kept --- assert not any( e.HasField("subOrchestrationInstanceFailed") for e in clean ) - assert any( - e.HasField("subOrchestrationInstanceCreated") - and e.subOrchestrationInstanceCreated.instanceId == "child-fail-id" - for e in clean - ) + assert "child-fail-id" in created_ids # --- executionCompleted removed --- assert not any(e.HasField("executionCompleted") for e in clean) @@ -674,10 +666,8 @@ def test_rewind_does_not_mutate_original_events(): TEST_INSTANCE_ID, "orch", old_events, new_events) # The original executionStarted event should NOT be mutated. - assert ( - es_event.executionStarted.orchestrationInstance.executionId.value - == original_exec_id - ) + actual = es_event.executionStarted.orchestrationInstance.executionId.value + assert actual == original_exec_id def test_rewind_result_action_structure(): From 7eb6a8e425b505eaefd7d832ad6aa2b52e6aeeaf Mon Sep 17 00:00:00 2001 From: Andy Staples Date: Fri, 6 Mar 2026 15:13:52 -0700 Subject: [PATCH 5/6] Lint --- durabletask/testing/in_memory_backend.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/durabletask/testing/in_memory_backend.py b/durabletask/testing/in_memory_backend.py index 556df37..f9b0691 100644 --- a/durabletask/testing/in_memory_backend.py +++ b/durabletask/testing/in_memory_backend.py @@ -1688,8 +1688,7 @@ def _process_rewind_orchestration_action( for task_id, sub_instance_id in created_sub_orchs.items(): if task_id not in completed_sub_orch_task_ids: sub_instance = self._instances.get(sub_instance_id) - if (sub_instance - and sub_instance.status == pb.ORCHESTRATION_STATUS_FAILED): + if (sub_instance and sub_instance.status == pb.ORCHESTRATION_STATUS_FAILED): self._prepare_rewind(sub_instance, reason) self._watch_sub_orchestration( instance.instance_id, sub_instance_id, task_id) From 91057ab0e3b55b4e9ceca534d8b66fdd46dd2923 Mon Sep 17 00:00:00 2001 From: Andy Staples Date: Mon, 9 Mar 2026 14:42:50 -0600 Subject: [PATCH 6/6] PR Feedback --- durabletask/worker.py | 53 +++++++++------------------- tests/durabletask/test_rewind_e2e.py | 4 +-- 2 files changed, 17 insertions(+), 40 deletions(-) diff --git a/durabletask/worker.py b/durabletask/worker.py index 2641949..f69bbfe 100644 --- a/durabletask/worker.py +++ b/durabletask/worker.py @@ -1328,26 +1328,11 @@ def execute( has_rewind_in_new = any( e.HasField("executionRewound") for e in new_events ) - if has_rewind_in_new: - from itertools import chain - has_execution_completed = any( - e.HasField("executionCompleted") - for e in chain(old_events, new_events) - ) - if has_execution_completed: - # The orchestration completed (with failure) and needs - # rewinding — short-circuit to build clean history. - return self._build_rewind_result( - instance_id, orchestration_name, old_events, new_events) - - # During replay, remove executionCompleted events from the - # committed history so the orchestrator function replays - # cleanly. orchestratorCompleted events are kept as they - # bookend each replay batch. - old_events = [ - e for e in old_events - if not e.HasField("executionCompleted") - ] + if has_rewind_in_new and any(e.HasField("executionCompleted") for e in old_events): + # The orchestration completed (with failure) and needs + # rewinding — short-circuit to build clean history. + return self._build_rewind_result( + instance_id, orchestration_name, old_events, new_events) ctx = _RuntimeOrchestrationContext(instance_id, self._registry) try: @@ -1439,19 +1424,14 @@ def _build_rewind_result( f"{instance_id}: Orchestration {orchestration_name} is being rewound" ) - # Combine old + new events into a single timeline, then remove - # failed results so the orchestration can replay successfully. - all_events = list(old_events) + list(new_events) + if len(new_events) != 2 or not new_events[1].HasField("executionRewound"): + raise ValueError( + "When rewinding an orchestration, the new events list must contain exactly two events: orchestratorStarted and the executionRewound event." + ) - # Extract the executionRewound event from new_events so we can - # read its parentExecutionId (set when this is a sub-orchestration - # being rewound by its parent). - rewind_event: pb.ExecutionRewoundEvent | None = None - for e in new_events: - if e.HasField("executionRewound"): - rewind_event = e.executionRewound - break + rewind_event: pb.ExecutionRewoundEvent = new_events[1].executionRewound + all_events = list(old_events) + list(new_events) # Generate a new execution ID for the rewound execution. new_execution_id = uuid.uuid4().hex @@ -1485,11 +1465,10 @@ def _build_rewind_result( event_copy.CopyFrom(event) event_copy.executionStarted.orchestrationInstance.executionId.CopyFrom( ph.get_string_value_or_empty(new_execution_id)) - if rewind_event is not None: - if rewind_event.HasField("parentExecutionId"): - if rewind_event.parentExecutionId.value: - event_copy.executionStarted.parentInstance.orchestrationInstance.executionId.CopyFrom( - rewind_event.parentExecutionId) + if rewind_event.HasField("parentExecutionId"): + if rewind_event.parentExecutionId.value: + event_copy.executionStarted.parentInstance.orchestrationInstance.executionId.CopyFrom( + rewind_event.parentExecutionId) clean_history.append(event_copy) continue @@ -1925,7 +1904,7 @@ def process_event( # Bookend event for each replay batch — no action needed. pass elif event.HasField("executionCompleted"): - # Terminal marker event — no action needed during replay. + # Terminal marker event — in practice, this never appears during replay. pass elif event.HasField("executionRewound"): # Informational event added when an orchestration is rewound. No action needed. diff --git a/tests/durabletask/test_rewind_e2e.py b/tests/durabletask/test_rewind_e2e.py index e86a5bf..0cea97c 100644 --- a/tests/durabletask/test_rewind_e2e.py +++ b/tests/durabletask/test_rewind_e2e.py @@ -2,8 +2,6 @@ # Licensed under the MIT License. import json -import threading -import time import pytest @@ -51,7 +49,7 @@ def test_rewind_failed_activity(): _reset_counters() def failing_activity(_: task.ActivityContext, input: str) -> str: - global _activity_call_count, _should_fail + global _activity_call_count _activity_call_count += 1 if _should_fail: raise RuntimeError("Simulated failure")