chunks = this.sidecarClient.streamInstanceHistory(request);
+ while (chunks.hasNext()) {
+ for (OrchestratorService.HistoryEvent protoEvent : chunks.next().getEventsList()) {
+ historyEvents.add(HistoryEventConverter.fromProto(protoEvent));
+ }
+ }
+ return historyEvents;
+ }
+
@Override
public void createTaskHub(boolean recreateIfExists) {
this.sidecarClient.createTaskHub(CreateTaskHubRequest.newBuilder().setRecreateIfExists(recreateIfExists).build());
diff --git a/client/src/main/java/com/microsoft/durabletask/EntityQueryPageable.java b/client/src/main/java/com/microsoft/durabletask/EntityQueryPageable.java
index fcf07ab..07158bf 100644
--- a/client/src/main/java/com/microsoft/durabletask/EntityQueryPageable.java
+++ b/client/src/main/java/com/microsoft/durabletask/EntityQueryPageable.java
@@ -17,7 +17,7 @@
*
* Use {@link DurableEntityClient#getAllEntities(EntityQuery)} to obtain an instance of this class.
*
- *
Example: iterate over all entities
+ * Example: iterate over all entities
* {@code
* EntityQuery query = new EntityQuery()
* .setInstanceIdStartsWith("counter")
@@ -28,7 +28,7 @@
* }
* }
*
- * Example: iterate page by page
+ * Example: iterate page by page
* {@code
* for (EntityQueryResult page : client.getEntities().getAllEntities(query).byPage()) {
* System.out.println("Got " + page.getEntities().size() + " entities");
diff --git a/client/src/main/java/com/microsoft/durabletask/HistoryEventConverter.java b/client/src/main/java/com/microsoft/durabletask/HistoryEventConverter.java
new file mode 100644
index 0000000..53e3a8d
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/HistoryEventConverter.java
@@ -0,0 +1,246 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask;
+
+import com.microsoft.durabletask.history.*;
+import com.microsoft.durabletask.implementation.protobuf.OrchestratorService;
+
+import javax.annotation.Nullable;
+import java.time.Instant;
+
+/**
+ * Converts protobuf {@code HistoryEvent} messages into the public {@link HistoryEvent} domain model.
+ */
+final class HistoryEventConverter {
+ private HistoryEventConverter() {
+ }
+
+ /**
+ * Converts a protobuf history event into its domain representation.
+ *
+ * @param proto the protobuf history event
+ * @return the domain {@link HistoryEvent}
+ * @throws IllegalArgumentException if the event type is not set
+ * @throws UnsupportedOperationException if the event type is not recognized
+ */
+ static HistoryEvent fromProto(OrchestratorService.HistoryEvent proto) {
+ int id = proto.getEventId();
+ Instant ts = DataConverter.getInstantFromTimestamp(proto.getTimestamp());
+ switch (proto.getEventTypeCase()) {
+ case EXECUTIONSTARTED: {
+ OrchestratorService.ExecutionStartedEvent p = proto.getExecutionStarted();
+ return new ExecutionStartedEvent(id, ts,
+ p.getName(),
+ stringOrNull(p.hasVersion(), p.getVersion()),
+ stringOrNull(p.hasInput(), p.getInput()),
+ p.hasOrchestrationInstance() ? toInstance(p.getOrchestrationInstance()) : null,
+ p.hasParentInstance() ? toParentInfo(p.getParentInstance()) : null,
+ p.hasScheduledStartTimestamp()
+ ? DataConverter.getInstantFromTimestamp(p.getScheduledStartTimestamp()) : null,
+ p.hasParentTraceContext() ? toTrace(p.getParentTraceContext()) : null,
+ stringOrNull(p.hasOrchestrationSpanID(), p.getOrchestrationSpanID()),
+ p.getTagsMap());
+ }
+ case EXECUTIONCOMPLETED: {
+ OrchestratorService.ExecutionCompletedEvent p = proto.getExecutionCompleted();
+ return new ExecutionCompletedEvent(id, ts,
+ OrchestrationRuntimeStatus.fromProtobuf(p.getOrchestrationStatus()),
+ stringOrNull(p.hasResult(), p.getResult()),
+ p.hasFailureDetails() ? new FailureDetails(p.getFailureDetails()) : null);
+ }
+ case EXECUTIONTERMINATED: {
+ OrchestratorService.ExecutionTerminatedEvent p = proto.getExecutionTerminated();
+ return new ExecutionTerminatedEvent(id, ts, stringOrNull(p.hasInput(), p.getInput()), p.getRecurse());
+ }
+ case TASKSCHEDULED: {
+ OrchestratorService.TaskScheduledEvent p = proto.getTaskScheduled();
+ return new TaskScheduledEvent(id, ts,
+ p.getName(),
+ stringOrNull(p.hasVersion(), p.getVersion()),
+ stringOrNull(p.hasInput(), p.getInput()),
+ p.hasParentTraceContext() ? toTrace(p.getParentTraceContext()) : null,
+ p.getTagsMap());
+ }
+ case TASKCOMPLETED: {
+ OrchestratorService.TaskCompletedEvent p = proto.getTaskCompleted();
+ return new TaskCompletedEvent(id, ts, p.getTaskScheduledId(), stringOrNull(p.hasResult(), p.getResult()));
+ }
+ case TASKFAILED: {
+ OrchestratorService.TaskFailedEvent p = proto.getTaskFailed();
+ return new TaskFailedEvent(id, ts, p.getTaskScheduledId(),
+ p.hasFailureDetails() ? new FailureDetails(p.getFailureDetails()) : null);
+ }
+ case SUBORCHESTRATIONINSTANCECREATED: {
+ OrchestratorService.SubOrchestrationInstanceCreatedEvent p = proto.getSubOrchestrationInstanceCreated();
+ return new SubOrchestrationInstanceCreatedEvent(id, ts,
+ p.getInstanceId(),
+ p.getName(),
+ stringOrNull(p.hasVersion(), p.getVersion()),
+ stringOrNull(p.hasInput(), p.getInput()),
+ p.hasParentTraceContext() ? toTrace(p.getParentTraceContext()) : null,
+ p.getTagsMap());
+ }
+ case SUBORCHESTRATIONINSTANCECOMPLETED: {
+ OrchestratorService.SubOrchestrationInstanceCompletedEvent p =
+ proto.getSubOrchestrationInstanceCompleted();
+ return new SubOrchestrationInstanceCompletedEvent(id, ts,
+ p.getTaskScheduledId(), stringOrNull(p.hasResult(), p.getResult()));
+ }
+ case SUBORCHESTRATIONINSTANCEFAILED: {
+ OrchestratorService.SubOrchestrationInstanceFailedEvent p = proto.getSubOrchestrationInstanceFailed();
+ return new SubOrchestrationInstanceFailedEvent(id, ts, p.getTaskScheduledId(),
+ p.hasFailureDetails() ? new FailureDetails(p.getFailureDetails()) : null);
+ }
+ case TIMERCREATED: {
+ OrchestratorService.TimerCreatedEvent p = proto.getTimerCreated();
+ return new TimerCreatedEvent(id, ts, DataConverter.getInstantFromTimestamp(p.getFireAt()));
+ }
+ case TIMERFIRED: {
+ OrchestratorService.TimerFiredEvent p = proto.getTimerFired();
+ return new TimerFiredEvent(id, ts, DataConverter.getInstantFromTimestamp(p.getFireAt()), p.getTimerId());
+ }
+ case ORCHESTRATORSTARTED:
+ return new OrchestratorStartedEvent(id, ts);
+ case ORCHESTRATORCOMPLETED:
+ return new OrchestratorCompletedEvent(id, ts);
+ case EVENTSENT: {
+ OrchestratorService.EventSentEvent p = proto.getEventSent();
+ return new EventSentEvent(id, ts, p.getInstanceId(), p.getName(),
+ stringOrNull(p.hasInput(), p.getInput()));
+ }
+ case EVENTRAISED: {
+ OrchestratorService.EventRaisedEvent p = proto.getEventRaised();
+ return new EventRaisedEvent(id, ts, p.getName(), stringOrNull(p.hasInput(), p.getInput()));
+ }
+ case GENERICEVENT: {
+ OrchestratorService.GenericEvent p = proto.getGenericEvent();
+ return new GenericEvent(id, ts, stringOrNull(p.hasData(), p.getData()));
+ }
+ case HISTORYSTATE: {
+ OrchestratorService.HistoryStateEvent p = proto.getHistoryState();
+ return new HistoryStateEvent(id, ts,
+ p.hasOrchestrationState() ? toOrchestrationState(p.getOrchestrationState()) : null);
+ }
+ case CONTINUEASNEW: {
+ OrchestratorService.ContinueAsNewEvent p = proto.getContinueAsNew();
+ return new ContinueAsNewEvent(id, ts, stringOrNull(p.hasInput(), p.getInput()));
+ }
+ case EXECUTIONSUSPENDED: {
+ OrchestratorService.ExecutionSuspendedEvent p = proto.getExecutionSuspended();
+ return new ExecutionSuspendedEvent(id, ts, stringOrNull(p.hasInput(), p.getInput()));
+ }
+ case EXECUTIONRESUMED: {
+ OrchestratorService.ExecutionResumedEvent p = proto.getExecutionResumed();
+ return new ExecutionResumedEvent(id, ts, stringOrNull(p.hasInput(), p.getInput()));
+ }
+ case ENTITYOPERATIONSIGNALED: {
+ OrchestratorService.EntityOperationSignaledEvent p = proto.getEntityOperationSignaled();
+ return new EntityOperationSignaledEvent(id, ts,
+ p.getRequestId(),
+ p.getOperation(),
+ p.hasScheduledTime() ? DataConverter.getInstantFromTimestamp(p.getScheduledTime()) : null,
+ stringOrNull(p.hasInput(), p.getInput()),
+ stringOrNull(p.hasTargetInstanceId(), p.getTargetInstanceId()));
+ }
+ case ENTITYOPERATIONCALLED: {
+ OrchestratorService.EntityOperationCalledEvent p = proto.getEntityOperationCalled();
+ return new EntityOperationCalledEvent(id, ts,
+ p.getRequestId(),
+ p.getOperation(),
+ p.hasScheduledTime() ? DataConverter.getInstantFromTimestamp(p.getScheduledTime()) : null,
+ stringOrNull(p.hasInput(), p.getInput()),
+ stringOrNull(p.hasParentInstanceId(), p.getParentInstanceId()),
+ stringOrNull(p.hasParentExecutionId(), p.getParentExecutionId()),
+ stringOrNull(p.hasTargetInstanceId(), p.getTargetInstanceId()));
+ }
+ case ENTITYOPERATIONCOMPLETED: {
+ OrchestratorService.EntityOperationCompletedEvent p = proto.getEntityOperationCompleted();
+ return new EntityOperationCompletedEvent(id, ts, p.getRequestId(),
+ stringOrNull(p.hasOutput(), p.getOutput()));
+ }
+ case ENTITYOPERATIONFAILED: {
+ OrchestratorService.EntityOperationFailedEvent p = proto.getEntityOperationFailed();
+ return new EntityOperationFailedEvent(id, ts, p.getRequestId(),
+ p.hasFailureDetails() ? new FailureDetails(p.getFailureDetails()) : null);
+ }
+ case ENTITYLOCKREQUESTED: {
+ OrchestratorService.EntityLockRequestedEvent p = proto.getEntityLockRequested();
+ return new EntityLockRequestedEvent(id, ts,
+ p.getCriticalSectionId(),
+ p.getLockSetList(),
+ p.getPosition(),
+ stringOrNull(p.hasParentInstanceId(), p.getParentInstanceId()));
+ }
+ case ENTITYLOCKGRANTED: {
+ OrchestratorService.EntityLockGrantedEvent p = proto.getEntityLockGranted();
+ return new EntityLockGrantedEvent(id, ts, p.getCriticalSectionId());
+ }
+ case ENTITYUNLOCKSENT: {
+ OrchestratorService.EntityUnlockSentEvent p = proto.getEntityUnlockSent();
+ return new EntityUnlockSentEvent(id, ts,
+ p.getCriticalSectionId(),
+ stringOrNull(p.hasParentInstanceId(), p.getParentInstanceId()),
+ stringOrNull(p.hasTargetInstanceId(), p.getTargetInstanceId()));
+ }
+ case EXECUTIONREWOUND: {
+ OrchestratorService.ExecutionRewoundEvent p = proto.getExecutionRewound();
+ return new ExecutionRewoundEvent(id, ts,
+ stringOrNull(p.hasReason(), p.getReason()),
+ stringOrNull(p.hasParentExecutionId(), p.getParentExecutionId()),
+ stringOrNull(p.hasInstanceId(), p.getInstanceId()),
+ p.hasParentTraceContext() ? toTrace(p.getParentTraceContext()) : null,
+ stringOrNull(p.hasName(), p.getName()),
+ stringOrNull(p.hasVersion(), p.getVersion()),
+ stringOrNull(p.hasInput(), p.getInput()),
+ p.hasParentInstance() ? toParentInfo(p.getParentInstance()) : null,
+ p.getTagsMap());
+ }
+ case EVENTTYPE_NOT_SET:
+ throw new IllegalArgumentException("History event does not have an eventType set.");
+ default:
+ throw new UnsupportedOperationException(
+ "Deserialization of history event type " + proto.getEventTypeCase() + " is not supported.");
+ }
+ }
+
+ @Nullable
+ private static String stringOrNull(boolean present, com.google.protobuf.StringValue value) {
+ return present ? value.getValue() : null;
+ }
+
+ private static OrchestrationInstance toInstance(OrchestratorService.OrchestrationInstance p) {
+ return new OrchestrationInstance(p.getInstanceId(), stringOrNull(p.hasExecutionId(), p.getExecutionId()));
+ }
+
+ private static ParentInstanceInfo toParentInfo(OrchestratorService.ParentInstanceInfo p) {
+ return new ParentInstanceInfo(
+ p.getTaskScheduledId(),
+ stringOrNull(p.hasName(), p.getName()),
+ stringOrNull(p.hasVersion(), p.getVersion()),
+ p.hasOrchestrationInstance() ? toInstance(p.getOrchestrationInstance()) : null);
+ }
+
+ private static TraceContext toTrace(OrchestratorService.TraceContext p) {
+ return new TraceContext(p.getTraceParent(), stringOrNull(p.hasTraceState(), p.getTraceState()));
+ }
+
+ private static OrchestrationState toOrchestrationState(OrchestratorService.OrchestrationState p) {
+ return new OrchestrationState(
+ p.getInstanceId(),
+ p.getName(),
+ stringOrNull(p.hasVersion(), p.getVersion()),
+ OrchestrationRuntimeStatus.fromProtobuf(p.getOrchestrationStatus()),
+ p.hasScheduledStartTimestamp()
+ ? DataConverter.getInstantFromTimestamp(p.getScheduledStartTimestamp()) : null,
+ p.hasCreatedTimestamp() ? DataConverter.getInstantFromTimestamp(p.getCreatedTimestamp()) : null,
+ p.hasLastUpdatedTimestamp() ? DataConverter.getInstantFromTimestamp(p.getLastUpdatedTimestamp()) : null,
+ p.hasCompletedTimestamp() ? DataConverter.getInstantFromTimestamp(p.getCompletedTimestamp()) : null,
+ stringOrNull(p.hasInput(), p.getInput()),
+ stringOrNull(p.hasOutput(), p.getOutput()),
+ stringOrNull(p.hasCustomStatus(), p.getCustomStatus()),
+ p.hasFailureDetails() ? new FailureDetails(p.getFailureDetails()) : null,
+ stringOrNull(p.hasExecutionId(), p.getExecutionId()),
+ stringOrNull(p.hasParentInstanceId(), p.getParentInstanceId()),
+ p.getTagsMap());
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/ListInstanceIdsQuery.java b/client/src/main/java/com/microsoft/durabletask/ListInstanceIdsQuery.java
new file mode 100644
index 0000000..15a08a1
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/ListInstanceIdsQuery.java
@@ -0,0 +1,114 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask;
+
+import javax.annotation.Nullable;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Query for listing terminal orchestration instance IDs by completion-time window.
+ *
+ * Used with {@link DurableTaskClient#listInstanceIds(ListInstanceIdsQuery)} to enumerate the IDs of orchestration
+ * instances that reached a terminal state within a completion-time window. Unlike {@link OrchestrationStatusQuery}
+ * (which filters by creation time and returns full metadata), this query filters by completion time and
+ * returns only instance IDs, making it efficient for bulk enumeration such as archival/export.
+ */
+public final class ListInstanceIdsQuery {
+ private List runtimeStatusList = new ArrayList<>();
+ private Instant completedTimeFrom;
+ private Instant completedTimeTo;
+ private int pageSize = 100;
+ private String continuationToken;
+
+ /**
+ * Sole constructor.
+ */
+ public ListInstanceIdsQuery() {
+ }
+
+ /**
+ * Sets the terminal runtime status values to filter by. Only instances with a matching status are returned.
+ * The default empty list disables runtime-status filtering.
+ *
+ * @param runtimeStatusList the terminal runtime statuses to filter by (e.g. COMPLETED, FAILED, TERMINATED)
+ * @return this query object
+ */
+ public ListInstanceIdsQuery setRuntimeStatusList(@Nullable List runtimeStatusList) {
+ this.runtimeStatusList = runtimeStatusList != null ? new ArrayList<>(runtimeStatusList) : new ArrayList<>();
+ return this;
+ }
+
+ /**
+ * Includes instances that completed at or after the specified instant.
+ *
+ * @param completedTimeFrom the minimum completion time, or {@code null} to disable this filter
+ * @return this query object
+ */
+ public ListInstanceIdsQuery setCompletedTimeFrom(@Nullable Instant completedTimeFrom) {
+ this.completedTimeFrom = completedTimeFrom;
+ return this;
+ }
+
+ /**
+ * Includes instances that completed before the specified instant.
+ *
+ * @param completedTimeTo the maximum completion time, or {@code null} to disable this filter
+ * @return this query object
+ */
+ public ListInstanceIdsQuery setCompletedTimeTo(@Nullable Instant completedTimeTo) {
+ this.completedTimeTo = completedTimeTo;
+ return this;
+ }
+
+ /**
+ * Sets the maximum number of instance IDs to return per page. The default value is 100.
+ *
+ * A page may contain fewer IDs than the page size even when more results exist; always use
+ * {@link ListInstanceIdsResult#getContinuationToken()} to determine whether to continue paging.
+ *
+ * @param pageSize the maximum number of instance IDs to return per page
+ * @return this query object
+ */
+ public ListInstanceIdsQuery setPageSize(int pageSize) {
+ this.pageSize = pageSize;
+ return this;
+ }
+
+ /**
+ * Sets the pagination cursor used to continue listing from a previous page.
+ *
+ * This should be the {@link ListInstanceIdsResult#getContinuationToken()} value from the previous page.
+ *
+ * @param continuationToken the cursor from the previous page, or {@code null} to start from the beginning
+ * @return this query object
+ */
+ public ListInstanceIdsQuery setContinuationToken(@Nullable String continuationToken) {
+ this.continuationToken = continuationToken;
+ return this;
+ }
+
+ List getRuntimeStatusList() {
+ return this.runtimeStatusList;
+ }
+
+ @Nullable
+ Instant getCompletedTimeFrom() {
+ return this.completedTimeFrom;
+ }
+
+ @Nullable
+ Instant getCompletedTimeTo() {
+ return this.completedTimeTo;
+ }
+
+ int getPageSize() {
+ return this.pageSize;
+ }
+
+ @Nullable
+ String getContinuationToken() {
+ return this.continuationToken;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/ListInstanceIdsResult.java b/client/src/main/java/com/microsoft/durabletask/ListInstanceIdsResult.java
new file mode 100644
index 0000000..b5afba9
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/ListInstanceIdsResult.java
@@ -0,0 +1,43 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask;
+
+import javax.annotation.Nullable;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Result of a {@link DurableTaskClient#listInstanceIds(ListInstanceIdsQuery)} call.
+ *
+ * Contains a page of terminal orchestration instance IDs and a pagination cursor for fetching the next page.
+ */
+public final class ListInstanceIdsResult {
+ private final List instanceIds;
+ private final String continuationToken;
+
+ ListInstanceIdsResult(List instanceIds, @Nullable String continuationToken) {
+ this.instanceIds = Collections.unmodifiableList(instanceIds);
+ this.continuationToken = continuationToken;
+ }
+
+ /**
+ * Gets the page of terminal orchestration instance IDs that matched the query.
+ *
+ * @return an unmodifiable list of instance IDs that matched the query
+ */
+ public List getInstanceIds() {
+ return this.instanceIds;
+ }
+
+ /**
+ * Gets the pagination cursor to pass to the next
+ * {@link DurableTaskClient#listInstanceIds(ListInstanceIdsQuery)} call via
+ * {@link ListInstanceIdsQuery#setContinuationToken(String)}, or {@code null} when there are no more pages.
+ *
+ * @return the cursor for the next page, or {@code null} if no more pages are available
+ */
+ @Nullable
+ public String getContinuationToken() {
+ return this.continuationToken;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/TypedEntityMetadata.java b/client/src/main/java/com/microsoft/durabletask/TypedEntityMetadata.java
index ff059a9..f47f2de 100644
--- a/client/src/main/java/com/microsoft/durabletask/TypedEntityMetadata.java
+++ b/client/src/main/java/com/microsoft/durabletask/TypedEntityMetadata.java
@@ -14,7 +14,7 @@
* and provides a typed {@code State} property. In Java, the state is eagerly deserialized and accessible
* via {@link #getState()}.
*
- * Example:
+ * Example:
* {@code
* TypedEntityMetadata metadata = client.getEntities()
* .getEntityMetadata(entityId, Integer.class);
diff --git a/client/src/main/java/com/microsoft/durabletask/TypedEntityQueryPageable.java b/client/src/main/java/com/microsoft/durabletask/TypedEntityQueryPageable.java
index a8a7c1a..98297b5 100644
--- a/client/src/main/java/com/microsoft/durabletask/TypedEntityQueryPageable.java
+++ b/client/src/main/java/com/microsoft/durabletask/TypedEntityQueryPageable.java
@@ -14,7 +14,7 @@
*
* Use {@link DurableEntityClient#getAllEntities(EntityQuery, Class)} to obtain an instance.
*
- *
Example:
+ * Example:
* {@code
* EntityQuery query = new EntityQuery().setInstanceIdStartsWith("counter");
* for (TypedEntityMetadata entity : client.getEntities().getAllEntities(query, Integer.class)) {
diff --git a/client/src/main/java/com/microsoft/durabletask/history/ContinueAsNewEvent.java b/client/src/main/java/com/microsoft/durabletask/history/ContinueAsNewEvent.java
new file mode 100644
index 0000000..6b80aeb
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/ContinueAsNewEvent.java
@@ -0,0 +1,31 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import javax.annotation.Nullable;
+import java.time.Instant;
+
+/**
+ * History event recorded when an orchestration restarts itself via continue-as-new.
+ */
+public final class ContinueAsNewEvent extends HistoryEvent {
+ private final String input;
+
+ /**
+ * Creates a new {@code ContinueAsNewEvent}.
+ *
+ * @param eventId the event sequence ID
+ * @param timestamp the event timestamp
+ * @param input the serialized input for the new generation, or {@code null}
+ */
+ public ContinueAsNewEvent(int eventId, Instant timestamp, @Nullable String input) {
+ super(eventId, timestamp);
+ this.input = input;
+ }
+
+ /** @return the serialized input for the new generation, or {@code null} if none. */
+ @Nullable
+ public String getInput() {
+ return this.input;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/EntityLockGrantedEvent.java b/client/src/main/java/com/microsoft/durabletask/history/EntityLockGrantedEvent.java
new file mode 100644
index 0000000..d542f61
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/EntityLockGrantedEvent.java
@@ -0,0 +1,29 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import java.time.Instant;
+
+/**
+ * History event recorded when a requested entity lock is granted.
+ */
+public final class EntityLockGrantedEvent extends HistoryEvent {
+ private final String criticalSectionId;
+
+ /**
+ * Creates a new {@code EntityLockGrantedEvent}.
+ *
+ * @param eventId the event sequence ID
+ * @param timestamp the event timestamp
+ * @param criticalSectionId the ID of the critical section whose lock was granted
+ */
+ public EntityLockGrantedEvent(int eventId, Instant timestamp, String criticalSectionId) {
+ super(eventId, timestamp);
+ this.criticalSectionId = criticalSectionId;
+ }
+
+ /** @return the ID of the critical section whose lock was granted. */
+ public String getCriticalSectionId() {
+ return this.criticalSectionId;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/EntityLockRequestedEvent.java b/client/src/main/java/com/microsoft/durabletask/history/EntityLockRequestedEvent.java
new file mode 100644
index 0000000..8a062d9
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/EntityLockRequestedEvent.java
@@ -0,0 +1,65 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import javax.annotation.Nullable;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * History event recorded when an orchestration requests a lock over one or more durable entities.
+ */
+public final class EntityLockRequestedEvent extends HistoryEvent {
+ private final String criticalSectionId;
+ private final List lockSet;
+ private final int position;
+ private final String parentInstanceId;
+
+ /**
+ * Creates a new {@code EntityLockRequestedEvent}.
+ *
+ * @param eventId the event sequence ID
+ * @param timestamp the event timestamp
+ * @param criticalSectionId the ID of the critical section associated with the lock request
+ * @param lockSet the ordered set of entity instance IDs being locked, or {@code null}
+ * @param position the position of this entity within the lock set
+ * @param parentInstanceId the requesting instance ID, or {@code null}
+ */
+ public EntityLockRequestedEvent(
+ int eventId,
+ Instant timestamp,
+ String criticalSectionId,
+ @Nullable List lockSet,
+ int position,
+ @Nullable String parentInstanceId) {
+ super(eventId, timestamp);
+ this.criticalSectionId = criticalSectionId;
+ this.lockSet = lockSet != null
+ ? Collections.unmodifiableList(new ArrayList<>(lockSet)) : Collections.emptyList();
+ this.position = position;
+ this.parentInstanceId = parentInstanceId;
+ }
+
+ /** @return the ID of the critical section associated with the lock request. */
+ public String getCriticalSectionId() {
+ return this.criticalSectionId;
+ }
+
+ /** @return the ordered set of entity instance IDs being locked (never {@code null}). */
+ public List getLockSet() {
+ return this.lockSet;
+ }
+
+ /** @return the position of this entity within the lock set. */
+ public int getPosition() {
+ return this.position;
+ }
+
+ /** @return the requesting instance ID, or {@code null} if not set. */
+ @Nullable
+ public String getParentInstanceId() {
+ return this.parentInstanceId;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/EntityOperationCalledEvent.java b/client/src/main/java/com/microsoft/durabletask/history/EntityOperationCalledEvent.java
new file mode 100644
index 0000000..f85a776
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/EntityOperationCalledEvent.java
@@ -0,0 +1,92 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import javax.annotation.Nullable;
+import java.time.Instant;
+
+/**
+ * History event recorded when a two-way (call) operation is sent to a durable entity.
+ */
+public final class EntityOperationCalledEvent extends HistoryEvent {
+ private final String requestId;
+ private final String operation;
+ private final Instant scheduledTime;
+ private final String input;
+ private final String parentInstanceId;
+ private final String parentExecutionId;
+ private final String targetInstanceId;
+
+ /**
+ * Creates a new {@code EntityOperationCalledEvent}.
+ *
+ * @param eventId the event sequence ID
+ * @param timestamp the event timestamp
+ * @param requestId the unique request ID of the entity operation
+ * @param operation the name of the entity operation
+ * @param scheduledTime the scheduled delivery time, or {@code null}
+ * @param input the serialized operation input, or {@code null}
+ * @param parentInstanceId the calling instance ID, or {@code null}
+ * @param parentExecutionId the calling execution ID, or {@code null}
+ * @param targetInstanceId the target entity instance ID, or {@code null}
+ */
+ public EntityOperationCalledEvent(
+ int eventId,
+ Instant timestamp,
+ String requestId,
+ String operation,
+ @Nullable Instant scheduledTime,
+ @Nullable String input,
+ @Nullable String parentInstanceId,
+ @Nullable String parentExecutionId,
+ @Nullable String targetInstanceId) {
+ super(eventId, timestamp);
+ this.requestId = requestId;
+ this.operation = operation;
+ this.scheduledTime = scheduledTime;
+ this.input = input;
+ this.parentInstanceId = parentInstanceId;
+ this.parentExecutionId = parentExecutionId;
+ this.targetInstanceId = targetInstanceId;
+ }
+
+ /** @return the unique request ID of the entity operation. */
+ public String getRequestId() {
+ return this.requestId;
+ }
+
+ /** @return the name of the entity operation. */
+ public String getOperation() {
+ return this.operation;
+ }
+
+ /** @return the scheduled delivery time, or {@code null} if delivered immediately. */
+ @Nullable
+ public Instant getScheduledTime() {
+ return this.scheduledTime;
+ }
+
+ /** @return the serialized operation input, or {@code null} if none. */
+ @Nullable
+ public String getInput() {
+ return this.input;
+ }
+
+ /** @return the calling instance ID, or {@code null} if not set. */
+ @Nullable
+ public String getParentInstanceId() {
+ return this.parentInstanceId;
+ }
+
+ /** @return the calling execution ID, or {@code null} if not set. */
+ @Nullable
+ public String getParentExecutionId() {
+ return this.parentExecutionId;
+ }
+
+ /** @return the target entity instance ID, or {@code null} if not set. */
+ @Nullable
+ public String getTargetInstanceId() {
+ return this.targetInstanceId;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/EntityOperationCompletedEvent.java b/client/src/main/java/com/microsoft/durabletask/history/EntityOperationCompletedEvent.java
new file mode 100644
index 0000000..d8c188e
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/EntityOperationCompletedEvent.java
@@ -0,0 +1,40 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import javax.annotation.Nullable;
+import java.time.Instant;
+
+/**
+ * History event recorded when a durable entity operation completes successfully.
+ */
+public final class EntityOperationCompletedEvent extends HistoryEvent {
+ private final String requestId;
+ private final String output;
+
+ /**
+ * Creates a new {@code EntityOperationCompletedEvent}.
+ *
+ * @param eventId the event sequence ID
+ * @param timestamp the event timestamp
+ * @param requestId the unique request ID of the entity operation
+ * @param output the serialized operation output, or {@code null}
+ */
+ public EntityOperationCompletedEvent(
+ int eventId, Instant timestamp, String requestId, @Nullable String output) {
+ super(eventId, timestamp);
+ this.requestId = requestId;
+ this.output = output;
+ }
+
+ /** @return the unique request ID of the entity operation. */
+ public String getRequestId() {
+ return this.requestId;
+ }
+
+ /** @return the serialized operation output, or {@code null} if none. */
+ @Nullable
+ public String getOutput() {
+ return this.output;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/EntityOperationFailedEvent.java b/client/src/main/java/com/microsoft/durabletask/history/EntityOperationFailedEvent.java
new file mode 100644
index 0000000..a4d2cf3
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/EntityOperationFailedEvent.java
@@ -0,0 +1,42 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import com.microsoft.durabletask.FailureDetails;
+
+import javax.annotation.Nullable;
+import java.time.Instant;
+
+/**
+ * History event recorded when a durable entity operation fails.
+ */
+public final class EntityOperationFailedEvent extends HistoryEvent {
+ private final String requestId;
+ private final FailureDetails failureDetails;
+
+ /**
+ * Creates a new {@code EntityOperationFailedEvent}.
+ *
+ * @param eventId the event sequence ID
+ * @param timestamp the event timestamp
+ * @param requestId the unique request ID of the entity operation
+ * @param failureDetails the failure details, or {@code null}
+ */
+ public EntityOperationFailedEvent(
+ int eventId, Instant timestamp, String requestId, @Nullable FailureDetails failureDetails) {
+ super(eventId, timestamp);
+ this.requestId = requestId;
+ this.failureDetails = failureDetails;
+ }
+
+ /** @return the unique request ID of the entity operation. */
+ public String getRequestId() {
+ return this.requestId;
+ }
+
+ /** @return the failure details, or {@code null} if not available. */
+ @Nullable
+ public FailureDetails getFailureDetails() {
+ return this.failureDetails;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/EntityOperationSignaledEvent.java b/client/src/main/java/com/microsoft/durabletask/history/EntityOperationSignaledEvent.java
new file mode 100644
index 0000000..f299d59
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/EntityOperationSignaledEvent.java
@@ -0,0 +1,72 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import javax.annotation.Nullable;
+import java.time.Instant;
+
+/**
+ * History event recorded when a one-way (signal) operation is sent to a durable entity.
+ */
+public final class EntityOperationSignaledEvent extends HistoryEvent {
+ private final String requestId;
+ private final String operation;
+ private final Instant scheduledTime;
+ private final String input;
+ private final String targetInstanceId;
+
+ /**
+ * Creates a new {@code EntityOperationSignaledEvent}.
+ *
+ * @param eventId the event sequence ID
+ * @param timestamp the event timestamp
+ * @param requestId the unique request ID of the entity operation
+ * @param operation the name of the entity operation
+ * @param scheduledTime the scheduled delivery time, or {@code null}
+ * @param input the serialized operation input, or {@code null}
+ * @param targetInstanceId the target entity instance ID, or {@code null}
+ */
+ public EntityOperationSignaledEvent(
+ int eventId,
+ Instant timestamp,
+ String requestId,
+ String operation,
+ @Nullable Instant scheduledTime,
+ @Nullable String input,
+ @Nullable String targetInstanceId) {
+ super(eventId, timestamp);
+ this.requestId = requestId;
+ this.operation = operation;
+ this.scheduledTime = scheduledTime;
+ this.input = input;
+ this.targetInstanceId = targetInstanceId;
+ }
+
+ /** @return the unique request ID of the entity operation. */
+ public String getRequestId() {
+ return this.requestId;
+ }
+
+ /** @return the name of the entity operation. */
+ public String getOperation() {
+ return this.operation;
+ }
+
+ /** @return the scheduled delivery time, or {@code null} if delivered immediately. */
+ @Nullable
+ public Instant getScheduledTime() {
+ return this.scheduledTime;
+ }
+
+ /** @return the serialized operation input, or {@code null} if none. */
+ @Nullable
+ public String getInput() {
+ return this.input;
+ }
+
+ /** @return the target entity instance ID, or {@code null} if not set. */
+ @Nullable
+ public String getTargetInstanceId() {
+ return this.targetInstanceId;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/EntityUnlockSentEvent.java b/client/src/main/java/com/microsoft/durabletask/history/EntityUnlockSentEvent.java
new file mode 100644
index 0000000..7c12a92
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/EntityUnlockSentEvent.java
@@ -0,0 +1,53 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import javax.annotation.Nullable;
+import java.time.Instant;
+
+/**
+ * History event recorded when an entity lock is released (unlock sent).
+ */
+public final class EntityUnlockSentEvent extends HistoryEvent {
+ private final String criticalSectionId;
+ private final String parentInstanceId;
+ private final String targetInstanceId;
+
+ /**
+ * Creates a new {@code EntityUnlockSentEvent}.
+ *
+ * @param eventId the event sequence ID
+ * @param timestamp the event timestamp
+ * @param criticalSectionId the ID of the critical section being released
+ * @param parentInstanceId the releasing instance ID, or {@code null}
+ * @param targetInstanceId the target entity instance ID, or {@code null}
+ */
+ public EntityUnlockSentEvent(
+ int eventId,
+ Instant timestamp,
+ String criticalSectionId,
+ @Nullable String parentInstanceId,
+ @Nullable String targetInstanceId) {
+ super(eventId, timestamp);
+ this.criticalSectionId = criticalSectionId;
+ this.parentInstanceId = parentInstanceId;
+ this.targetInstanceId = targetInstanceId;
+ }
+
+ /** @return the ID of the critical section being released. */
+ public String getCriticalSectionId() {
+ return this.criticalSectionId;
+ }
+
+ /** @return the releasing instance ID, or {@code null} if not set. */
+ @Nullable
+ public String getParentInstanceId() {
+ return this.parentInstanceId;
+ }
+
+ /** @return the target entity instance ID, or {@code null} if not set. */
+ @Nullable
+ public String getTargetInstanceId() {
+ return this.targetInstanceId;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/EventRaisedEvent.java b/client/src/main/java/com/microsoft/durabletask/history/EventRaisedEvent.java
new file mode 100644
index 0000000..a8bde47
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/EventRaisedEvent.java
@@ -0,0 +1,39 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import javax.annotation.Nullable;
+import java.time.Instant;
+
+/**
+ * History event recorded when an external event is delivered to an orchestration instance.
+ */
+public final class EventRaisedEvent extends HistoryEvent {
+ private final String name;
+ private final String input;
+
+ /**
+ * Creates a new {@code EventRaisedEvent}.
+ *
+ * @param eventId the event sequence ID
+ * @param timestamp the event timestamp
+ * @param name the name of the event
+ * @param input the serialized event payload, or {@code null}
+ */
+ public EventRaisedEvent(int eventId, Instant timestamp, String name, @Nullable String input) {
+ super(eventId, timestamp);
+ this.name = name;
+ this.input = input;
+ }
+
+ /** @return the name of the event. */
+ public String getName() {
+ return this.name;
+ }
+
+ /** @return the serialized event payload, or {@code null} if none. */
+ @Nullable
+ public String getInput() {
+ return this.input;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/EventSentEvent.java b/client/src/main/java/com/microsoft/durabletask/history/EventSentEvent.java
new file mode 100644
index 0000000..c4accf8
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/EventSentEvent.java
@@ -0,0 +1,47 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import javax.annotation.Nullable;
+import java.time.Instant;
+
+/**
+ * History event recorded when an orchestration sends an external event to another instance.
+ */
+public final class EventSentEvent extends HistoryEvent {
+ private final String instanceId;
+ private final String name;
+ private final String input;
+
+ /**
+ * Creates a new {@code EventSentEvent}.
+ *
+ * @param eventId the event sequence ID
+ * @param timestamp the event timestamp
+ * @param instanceId the target instance ID the event was sent to
+ * @param name the name of the event
+ * @param input the serialized event payload, or {@code null}
+ */
+ public EventSentEvent(int eventId, Instant timestamp, String instanceId, String name, @Nullable String input) {
+ super(eventId, timestamp);
+ this.instanceId = instanceId;
+ this.name = name;
+ this.input = input;
+ }
+
+ /** @return the target instance ID the event was sent to. */
+ public String getInstanceId() {
+ return this.instanceId;
+ }
+
+ /** @return the name of the event. */
+ public String getName() {
+ return this.name;
+ }
+
+ /** @return the serialized event payload, or {@code null} if none. */
+ @Nullable
+ public String getInput() {
+ return this.input;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/ExecutionCompletedEvent.java b/client/src/main/java/com/microsoft/durabletask/history/ExecutionCompletedEvent.java
new file mode 100644
index 0000000..f0cdae0
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/ExecutionCompletedEvent.java
@@ -0,0 +1,56 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import com.microsoft.durabletask.FailureDetails;
+import com.microsoft.durabletask.OrchestrationRuntimeStatus;
+
+import javax.annotation.Nullable;
+import java.time.Instant;
+
+/**
+ * History event recorded when an orchestration instance reaches a terminal state.
+ */
+public final class ExecutionCompletedEvent extends HistoryEvent {
+ private final OrchestrationRuntimeStatus orchestrationStatus;
+ private final String result;
+ private final FailureDetails failureDetails;
+
+ /**
+ * Creates a new {@code ExecutionCompletedEvent}.
+ *
+ * @param eventId the event sequence ID
+ * @param timestamp the event timestamp
+ * @param orchestrationStatus the terminal runtime status
+ * @param result the serialized orchestration output, or {@code null}
+ * @param failureDetails the failure details if the orchestration failed, or {@code null}
+ */
+ public ExecutionCompletedEvent(
+ int eventId,
+ Instant timestamp,
+ OrchestrationRuntimeStatus orchestrationStatus,
+ @Nullable String result,
+ @Nullable FailureDetails failureDetails) {
+ super(eventId, timestamp);
+ this.orchestrationStatus = orchestrationStatus;
+ this.result = result;
+ this.failureDetails = failureDetails;
+ }
+
+ /** @return the terminal runtime status of the orchestration. */
+ public OrchestrationRuntimeStatus getOrchestrationStatus() {
+ return this.orchestrationStatus;
+ }
+
+ /** @return the serialized orchestration output, or {@code null} if none. */
+ @Nullable
+ public String getResult() {
+ return this.result;
+ }
+
+ /** @return the failure details if the orchestration failed, otherwise {@code null}. */
+ @Nullable
+ public FailureDetails getFailureDetails() {
+ return this.failureDetails;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/ExecutionResumedEvent.java b/client/src/main/java/com/microsoft/durabletask/history/ExecutionResumedEvent.java
new file mode 100644
index 0000000..5dbf2eb
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/ExecutionResumedEvent.java
@@ -0,0 +1,31 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import javax.annotation.Nullable;
+import java.time.Instant;
+
+/**
+ * History event recorded when a suspended orchestration instance is resumed.
+ */
+public final class ExecutionResumedEvent extends HistoryEvent {
+ private final String input;
+
+ /**
+ * Creates a new {@code ExecutionResumedEvent}.
+ *
+ * @param eventId the event sequence ID
+ * @param timestamp the event timestamp
+ * @param input the serialized resume reason, or {@code null}
+ */
+ public ExecutionResumedEvent(int eventId, Instant timestamp, @Nullable String input) {
+ super(eventId, timestamp);
+ this.input = input;
+ }
+
+ /** @return the serialized resume reason, or {@code null} if none. */
+ @Nullable
+ public String getInput() {
+ return this.input;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/ExecutionRewoundEvent.java b/client/src/main/java/com/microsoft/durabletask/history/ExecutionRewoundEvent.java
new file mode 100644
index 0000000..afe49f0
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/ExecutionRewoundEvent.java
@@ -0,0 +1,116 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import javax.annotation.Nullable;
+import java.time.Instant;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * History event recorded when an orchestration instance is rewound to a previous good state.
+ */
+public final class ExecutionRewoundEvent extends HistoryEvent {
+ private final String reason;
+ private final String parentExecutionId;
+ private final String instanceId;
+ private final TraceContext parentTraceContext;
+ private final String name;
+ private final String version;
+ private final String input;
+ private final ParentInstanceInfo parentInstance;
+ private final Map tags;
+
+ /**
+ * Creates a new {@code ExecutionRewoundEvent}.
+ *
+ * @param eventId the event sequence ID
+ * @param timestamp the event timestamp
+ * @param reason the reason for the rewind, or {@code null}
+ * @param parentExecutionId the parent execution ID (sub-orchestration rewind only), or {@code null}
+ * @param instanceId the instance ID (sub-orchestration rewind only), or {@code null}
+ * @param parentTraceContext the parent distributed-tracing context, or {@code null}
+ * @param name the orchestrator name, or {@code null}
+ * @param version the orchestrator version, or {@code null}
+ * @param input the serialized input, or {@code null}
+ * @param parentInstance the parent orchestration info, or {@code null}
+ * @param tags the orchestration tags, or {@code null}
+ */
+ public ExecutionRewoundEvent(
+ int eventId,
+ Instant timestamp,
+ @Nullable String reason,
+ @Nullable String parentExecutionId,
+ @Nullable String instanceId,
+ @Nullable TraceContext parentTraceContext,
+ @Nullable String name,
+ @Nullable String version,
+ @Nullable String input,
+ @Nullable ParentInstanceInfo parentInstance,
+ @Nullable Map tags) {
+ super(eventId, timestamp);
+ this.reason = reason;
+ this.parentExecutionId = parentExecutionId;
+ this.instanceId = instanceId;
+ this.parentTraceContext = parentTraceContext;
+ this.name = name;
+ this.version = version;
+ this.input = input;
+ this.parentInstance = parentInstance;
+ this.tags = tags != null ? Collections.unmodifiableMap(new HashMap<>(tags)) : Collections.emptyMap();
+ }
+
+ /** @return the reason for the rewind, or {@code null} if none. */
+ @Nullable
+ public String getReason() {
+ return this.reason;
+ }
+
+ /** @return the parent execution ID (sub-orchestration rewind only), or {@code null}. */
+ @Nullable
+ public String getParentExecutionId() {
+ return this.parentExecutionId;
+ }
+
+ /** @return the instance ID (sub-orchestration rewind only), or {@code null}. */
+ @Nullable
+ public String getInstanceId() {
+ return this.instanceId;
+ }
+
+ /** @return the parent distributed-tracing context, or {@code null} if not set. */
+ @Nullable
+ public TraceContext getParentTraceContext() {
+ return this.parentTraceContext;
+ }
+
+ /** @return the orchestrator name, or {@code null} if not set. */
+ @Nullable
+ public String getName() {
+ return this.name;
+ }
+
+ /** @return the orchestrator version, or {@code null} if not set. */
+ @Nullable
+ public String getVersion() {
+ return this.version;
+ }
+
+ /** @return the serialized input, or {@code null} if none. */
+ @Nullable
+ public String getInput() {
+ return this.input;
+ }
+
+ /** @return the parent orchestration info, or {@code null} if not set. */
+ @Nullable
+ public ParentInstanceInfo getParentInstance() {
+ return this.parentInstance;
+ }
+
+ /** @return the orchestration tags (never {@code null}; empty when none). */
+ public Map getTags() {
+ return this.tags;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/ExecutionStartedEvent.java b/client/src/main/java/com/microsoft/durabletask/history/ExecutionStartedEvent.java
new file mode 100644
index 0000000..a6173e3
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/ExecutionStartedEvent.java
@@ -0,0 +1,115 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import javax.annotation.Nullable;
+import java.time.Instant;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * History event recorded when an orchestration instance begins execution.
+ */
+public final class ExecutionStartedEvent extends HistoryEvent {
+ private final String name;
+ private final String version;
+ private final String input;
+ private final OrchestrationInstance orchestrationInstance;
+ private final ParentInstanceInfo parentInstance;
+ private final Instant scheduledStartTimestamp;
+ private final TraceContext parentTraceContext;
+ private final String orchestrationSpanId;
+ private final Map tags;
+
+ /**
+ * Creates a new {@code ExecutionStartedEvent}.
+ *
+ * @param eventId the event sequence ID
+ * @param timestamp the event timestamp
+ * @param name the orchestrator name
+ * @param version the orchestrator version, or {@code null}
+ * @param input the serialized orchestration input, or {@code null}
+ * @param orchestrationInstance the orchestration instance, or {@code null}
+ * @param parentInstance the parent orchestration info, or {@code null}
+ * @param scheduledStartTimestamp the scheduled start time for delayed starts, or {@code null}
+ * @param parentTraceContext the parent distributed-tracing context, or {@code null}
+ * @param orchestrationSpanId the orchestration's tracing span ID, or {@code null}
+ * @param tags the orchestration tags, or {@code null}
+ */
+ public ExecutionStartedEvent(
+ int eventId,
+ Instant timestamp,
+ String name,
+ @Nullable String version,
+ @Nullable String input,
+ @Nullable OrchestrationInstance orchestrationInstance,
+ @Nullable ParentInstanceInfo parentInstance,
+ @Nullable Instant scheduledStartTimestamp,
+ @Nullable TraceContext parentTraceContext,
+ @Nullable String orchestrationSpanId,
+ @Nullable Map tags) {
+ super(eventId, timestamp);
+ this.name = name;
+ this.version = version;
+ this.input = input;
+ this.orchestrationInstance = orchestrationInstance;
+ this.parentInstance = parentInstance;
+ this.scheduledStartTimestamp = scheduledStartTimestamp;
+ this.parentTraceContext = parentTraceContext;
+ this.orchestrationSpanId = orchestrationSpanId;
+ this.tags = tags != null ? Collections.unmodifiableMap(new HashMap<>(tags)) : Collections.emptyMap();
+ }
+
+ /** @return the name of the orchestrator. */
+ public String getName() {
+ return this.name;
+ }
+
+ /** @return the orchestrator version, or {@code null} if not set. */
+ @Nullable
+ public String getVersion() {
+ return this.version;
+ }
+
+ /** @return the serialized orchestration input, or {@code null} if none. */
+ @Nullable
+ public String getInput() {
+ return this.input;
+ }
+
+ /** @return the orchestration instance, or {@code null} if not set. */
+ @Nullable
+ public OrchestrationInstance getOrchestrationInstance() {
+ return this.orchestrationInstance;
+ }
+
+ /** @return the parent orchestration info if this is a sub-orchestration, otherwise {@code null}. */
+ @Nullable
+ public ParentInstanceInfo getParentInstance() {
+ return this.parentInstance;
+ }
+
+ /** @return the scheduled start time for delayed starts, or {@code null} if started immediately. */
+ @Nullable
+ public Instant getScheduledStartTimestamp() {
+ return this.scheduledStartTimestamp;
+ }
+
+ /** @return the distributed-tracing context of the parent, or {@code null} if not set. */
+ @Nullable
+ public TraceContext getParentTraceContext() {
+ return this.parentTraceContext;
+ }
+
+ /** @return the orchestration's tracing span ID, or {@code null} if not set. */
+ @Nullable
+ public String getOrchestrationSpanId() {
+ return this.orchestrationSpanId;
+ }
+
+ /** @return the orchestration tags (never {@code null}; empty when none). */
+ public Map getTags() {
+ return this.tags;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/ExecutionSuspendedEvent.java b/client/src/main/java/com/microsoft/durabletask/history/ExecutionSuspendedEvent.java
new file mode 100644
index 0000000..4115f9d
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/ExecutionSuspendedEvent.java
@@ -0,0 +1,31 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import javax.annotation.Nullable;
+import java.time.Instant;
+
+/**
+ * History event recorded when an orchestration instance is suspended.
+ */
+public final class ExecutionSuspendedEvent extends HistoryEvent {
+ private final String input;
+
+ /**
+ * Creates a new {@code ExecutionSuspendedEvent}.
+ *
+ * @param eventId the event sequence ID
+ * @param timestamp the event timestamp
+ * @param input the serialized suspension reason, or {@code null}
+ */
+ public ExecutionSuspendedEvent(int eventId, Instant timestamp, @Nullable String input) {
+ super(eventId, timestamp);
+ this.input = input;
+ }
+
+ /** @return the serialized suspension reason, or {@code null} if none. */
+ @Nullable
+ public String getInput() {
+ return this.input;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/ExecutionTerminatedEvent.java b/client/src/main/java/com/microsoft/durabletask/history/ExecutionTerminatedEvent.java
new file mode 100644
index 0000000..2498518
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/ExecutionTerminatedEvent.java
@@ -0,0 +1,39 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import javax.annotation.Nullable;
+import java.time.Instant;
+
+/**
+ * History event recorded when an orchestration instance is terminated.
+ */
+public final class ExecutionTerminatedEvent extends HistoryEvent {
+ private final String input;
+ private final boolean recurse;
+
+ /**
+ * Creates a new {@code ExecutionTerminatedEvent}.
+ *
+ * @param eventId the event sequence ID
+ * @param timestamp the event timestamp
+ * @param input the serialized termination input/reason, or {@code null}
+ * @param recurse whether termination recurses into sub-orchestrations
+ */
+ public ExecutionTerminatedEvent(int eventId, Instant timestamp, @Nullable String input, boolean recurse) {
+ super(eventId, timestamp);
+ this.input = input;
+ this.recurse = recurse;
+ }
+
+ /** @return the serialized termination input/reason, or {@code null} if none. */
+ @Nullable
+ public String getInput() {
+ return this.input;
+ }
+
+ /** @return whether termination recurses into sub-orchestrations. */
+ public boolean isRecurse() {
+ return this.recurse;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/GenericEvent.java b/client/src/main/java/com/microsoft/durabletask/history/GenericEvent.java
new file mode 100644
index 0000000..b23b4fa
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/GenericEvent.java
@@ -0,0 +1,31 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import javax.annotation.Nullable;
+import java.time.Instant;
+
+/**
+ * History event that carries a generic, free-form data payload.
+ */
+public final class GenericEvent extends HistoryEvent {
+ private final String data;
+
+ /**
+ * Creates a new {@code GenericEvent}.
+ *
+ * @param eventId the event sequence ID
+ * @param timestamp the event timestamp
+ * @param data the serialized event data, or {@code null}
+ */
+ public GenericEvent(int eventId, Instant timestamp, @Nullable String data) {
+ super(eventId, timestamp);
+ this.data = data;
+ }
+
+ /** @return the serialized event data, or {@code null} if none. */
+ @Nullable
+ public String getData() {
+ return this.data;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/HistoryEvent.java b/client/src/main/java/com/microsoft/durabletask/history/HistoryEvent.java
new file mode 100644
index 0000000..59511cc
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/HistoryEvent.java
@@ -0,0 +1,40 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import java.time.Instant;
+
+/**
+ * Base class for the events that make up an orchestration instance's execution history.
+ *
+ * Instances are obtained from {@link com.microsoft.durabletask.DurableTaskClient#getOrchestrationHistory(String)}. Each
+ * concrete subclass (for example {@link ExecutionStartedEvent} or {@link TaskCompletedEvent}) exposes the data specific
+ * to that event type. Use {@code instanceof} to inspect the concrete event type.
+ */
+public abstract class HistoryEvent {
+ private final int eventId;
+ private final Instant timestamp;
+
+ HistoryEvent(int eventId, Instant timestamp) {
+ this.eventId = eventId;
+ this.timestamp = timestamp;
+ }
+
+ /**
+ * Gets the sequence ID of this history event, or {@code -1} if the event is not associated with a specific action.
+ *
+ * @return the event sequence ID
+ */
+ public int getEventId() {
+ return this.eventId;
+ }
+
+ /**
+ * Gets the UTC timestamp at which this history event was recorded.
+ *
+ * @return the event timestamp
+ */
+ public Instant getTimestamp() {
+ return this.timestamp;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/HistoryStateEvent.java b/client/src/main/java/com/microsoft/durabletask/history/HistoryStateEvent.java
new file mode 100644
index 0000000..4f233c1
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/HistoryStateEvent.java
@@ -0,0 +1,34 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import javax.annotation.Nullable;
+import java.time.Instant;
+
+/**
+ * History event that carries a snapshot of the orchestration's runtime state.
+ *
+ * This is an internal checkpoint marker. The full state snapshot is surfaced via {@link #getState()}, matching the
+ * sibling .NET SDK ({@code HistoryStateEvent.State}) and Python SDK ({@code HistoryStateEvent.orchestration_state}).
+ */
+public final class HistoryStateEvent extends HistoryEvent {
+ private final OrchestrationState state;
+
+ /**
+ * Creates a new {@code HistoryStateEvent}.
+ *
+ * @param eventId the event sequence ID
+ * @param timestamp the event timestamp
+ * @param state the orchestration state snapshot, or {@code null} if not available
+ */
+ public HistoryStateEvent(int eventId, Instant timestamp, @Nullable OrchestrationState state) {
+ super(eventId, timestamp);
+ this.state = state;
+ }
+
+ /** @return the orchestration state snapshot, or {@code null} if not available. */
+ @Nullable
+ public OrchestrationState getState() {
+ return this.state;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/OrchestrationInstance.java b/client/src/main/java/com/microsoft/durabletask/history/OrchestrationInstance.java
new file mode 100644
index 0000000..c55a421
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/OrchestrationInstance.java
@@ -0,0 +1,35 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import javax.annotation.Nullable;
+
+/**
+ * Identifies an orchestration instance and, optionally, a specific execution (generation) of it.
+ */
+public final class OrchestrationInstance {
+ private final String instanceId;
+ private final String executionId;
+
+ /**
+ * Creates a new {@code OrchestrationInstance}.
+ *
+ * @param instanceId the orchestration instance ID
+ * @param executionId the execution (generation) ID, or {@code null}
+ */
+ public OrchestrationInstance(String instanceId, @Nullable String executionId) {
+ this.instanceId = instanceId;
+ this.executionId = executionId;
+ }
+
+ /** @return the unique ID of the orchestration instance. */
+ public String getInstanceId() {
+ return this.instanceId;
+ }
+
+ /** @return the execution (generation) ID, or {@code null} if not set. */
+ @Nullable
+ public String getExecutionId() {
+ return this.executionId;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/OrchestrationState.java b/client/src/main/java/com/microsoft/durabletask/history/OrchestrationState.java
new file mode 100644
index 0000000..40fcd78
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/OrchestrationState.java
@@ -0,0 +1,176 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import com.microsoft.durabletask.FailureDetails;
+import com.microsoft.durabletask.OrchestrationRuntimeStatus;
+
+import javax.annotation.Nullable;
+import java.time.Instant;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A snapshot of an orchestration instance's runtime state, as carried by a {@link HistoryStateEvent}.
+ *
+ * This mirrors the {@code OrchestrationState} surfaced by the sibling .NET and Python SDKs (the .NET
+ * {@code HistoryStateEvent.State} property and the Python {@code HistoryStateEvent.orchestration_state} value), so that
+ * archival/export consumers can capture the full state checkpoint rather than just the instance ID.
+ */
+public final class OrchestrationState {
+ private final String instanceId;
+ private final String name;
+ private final String version;
+ private final OrchestrationRuntimeStatus runtimeStatus;
+ private final Instant scheduledStartTime;
+ private final Instant createdTime;
+ private final Instant lastUpdatedTime;
+ private final Instant completedTime;
+ private final String input;
+ private final String output;
+ private final String customStatus;
+ private final FailureDetails failureDetails;
+ private final String executionId;
+ private final String parentInstanceId;
+ private final Map tags;
+
+ /**
+ * Creates a new {@code OrchestrationState}.
+ *
+ * @param instanceId the orchestration instance ID
+ * @param name the orchestration name
+ * @param version the orchestration version, or {@code null}
+ * @param runtimeStatus the runtime status of the orchestration
+ * @param scheduledStartTime the scheduled start time, or {@code null}
+ * @param createdTime the creation time, or {@code null}
+ * @param lastUpdatedTime the last-updated time, or {@code null}
+ * @param completedTime the completion time, or {@code null}
+ * @param input the serialized input, or {@code null}
+ * @param output the serialized output, or {@code null}
+ * @param customStatus the serialized custom status, or {@code null}
+ * @param failureDetails the failure details when the orchestration failed, or {@code null}
+ * @param executionId the execution ID, or {@code null}
+ * @param parentInstanceId the parent instance ID, or {@code null}
+ * @param tags the orchestration tags, or {@code null}
+ */
+ public OrchestrationState(
+ String instanceId,
+ String name,
+ @Nullable String version,
+ OrchestrationRuntimeStatus runtimeStatus,
+ @Nullable Instant scheduledStartTime,
+ @Nullable Instant createdTime,
+ @Nullable Instant lastUpdatedTime,
+ @Nullable Instant completedTime,
+ @Nullable String input,
+ @Nullable String output,
+ @Nullable String customStatus,
+ @Nullable FailureDetails failureDetails,
+ @Nullable String executionId,
+ @Nullable String parentInstanceId,
+ @Nullable Map tags) {
+ this.instanceId = instanceId;
+ this.name = name;
+ this.version = version;
+ this.runtimeStatus = runtimeStatus;
+ this.scheduledStartTime = scheduledStartTime;
+ this.createdTime = createdTime;
+ this.lastUpdatedTime = lastUpdatedTime;
+ this.completedTime = completedTime;
+ this.input = input;
+ this.output = output;
+ this.customStatus = customStatus;
+ this.failureDetails = failureDetails;
+ this.executionId = executionId;
+ this.parentInstanceId = parentInstanceId;
+ this.tags = tags == null ? null : Collections.unmodifiableMap(new HashMap<>(tags));
+ }
+
+ /** @return the orchestration instance ID. */
+ public String getInstanceId() {
+ return this.instanceId;
+ }
+
+ /** @return the orchestration name. */
+ public String getName() {
+ return this.name;
+ }
+
+ /** @return the orchestration version, or {@code null} if not set. */
+ @Nullable
+ public String getVersion() {
+ return this.version;
+ }
+
+ /** @return the runtime status of the orchestration. */
+ public OrchestrationRuntimeStatus getRuntimeStatus() {
+ return this.runtimeStatus;
+ }
+
+ /** @return the scheduled start time, or {@code null} if not set. */
+ @Nullable
+ public Instant getScheduledStartTime() {
+ return this.scheduledStartTime;
+ }
+
+ /** @return the creation time, or {@code null} if not set. */
+ @Nullable
+ public Instant getCreatedTime() {
+ return this.createdTime;
+ }
+
+ /** @return the last-updated time, or {@code null} if not set. */
+ @Nullable
+ public Instant getLastUpdatedTime() {
+ return this.lastUpdatedTime;
+ }
+
+ /** @return the completion time, or {@code null} if not set. */
+ @Nullable
+ public Instant getCompletedTime() {
+ return this.completedTime;
+ }
+
+ /** @return the serialized orchestration input, or {@code null} if not set. */
+ @Nullable
+ public String getInput() {
+ return this.input;
+ }
+
+ /** @return the serialized orchestration output, or {@code null} if not set. */
+ @Nullable
+ public String getOutput() {
+ return this.output;
+ }
+
+ /** @return the serialized custom status, or {@code null} if not set. */
+ @Nullable
+ public String getCustomStatus() {
+ return this.customStatus;
+ }
+
+ /** @return the failure details when the orchestration failed, or {@code null} otherwise. */
+ @Nullable
+ public FailureDetails getFailureDetails() {
+ return this.failureDetails;
+ }
+
+ /** @return the execution ID, or {@code null} if not set. */
+ @Nullable
+ public String getExecutionId() {
+ return this.executionId;
+ }
+
+ /** @return the parent instance ID, or {@code null} if not set. */
+ @Nullable
+ public String getParentInstanceId() {
+ return this.parentInstanceId;
+ }
+
+ /** @return an unmodifiable view of the orchestration tags, or {@code null} if not set. */
+ @Nullable
+ public Map getTags() {
+ return this.tags;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/OrchestratorCompletedEvent.java b/client/src/main/java/com/microsoft/durabletask/history/OrchestratorCompletedEvent.java
new file mode 100644
index 0000000..d99a1ef
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/OrchestratorCompletedEvent.java
@@ -0,0 +1,20 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import java.time.Instant;
+
+/**
+ * History event recorded at the end of each orchestration replay/episode. Carries no payload.
+ */
+public final class OrchestratorCompletedEvent extends HistoryEvent {
+ /**
+ * Creates a new {@code OrchestratorCompletedEvent}.
+ *
+ * @param eventId the event sequence ID
+ * @param timestamp the event timestamp
+ */
+ public OrchestratorCompletedEvent(int eventId, Instant timestamp) {
+ super(eventId, timestamp);
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/OrchestratorStartedEvent.java b/client/src/main/java/com/microsoft/durabletask/history/OrchestratorStartedEvent.java
new file mode 100644
index 0000000..c470aca
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/OrchestratorStartedEvent.java
@@ -0,0 +1,20 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import java.time.Instant;
+
+/**
+ * History event recorded at the start of each orchestration replay/episode. Carries no payload.
+ */
+public final class OrchestratorStartedEvent extends HistoryEvent {
+ /**
+ * Creates a new {@code OrchestratorStartedEvent}.
+ *
+ * @param eventId the event sequence ID
+ * @param timestamp the event timestamp
+ */
+ public OrchestratorStartedEvent(int eventId, Instant timestamp) {
+ super(eventId, timestamp);
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/ParentInstanceInfo.java b/client/src/main/java/com/microsoft/durabletask/history/ParentInstanceInfo.java
new file mode 100644
index 0000000..ef90d65
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/ParentInstanceInfo.java
@@ -0,0 +1,57 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import javax.annotation.Nullable;
+
+/**
+ * Information about the parent orchestration that created a sub-orchestration.
+ */
+public final class ParentInstanceInfo {
+ private final int taskScheduledId;
+ private final String name;
+ private final String version;
+ private final OrchestrationInstance orchestrationInstance;
+
+ /**
+ * Creates a new {@code ParentInstanceInfo}.
+ *
+ * @param taskScheduledId the task scheduled ID of the sub-orchestration in the parent's history
+ * @param name the parent orchestrator name, or {@code null}
+ * @param version the parent orchestrator version, or {@code null}
+ * @param orchestrationInstance the parent orchestration instance, or {@code null}
+ */
+ public ParentInstanceInfo(
+ int taskScheduledId,
+ @Nullable String name,
+ @Nullable String version,
+ @Nullable OrchestrationInstance orchestrationInstance) {
+ this.taskScheduledId = taskScheduledId;
+ this.name = name;
+ this.version = version;
+ this.orchestrationInstance = orchestrationInstance;
+ }
+
+ /** @return the task scheduled ID of the sub-orchestration in the parent's history. */
+ public int getTaskScheduledId() {
+ return this.taskScheduledId;
+ }
+
+ /** @return the parent orchestrator name, or {@code null} if not set. */
+ @Nullable
+ public String getName() {
+ return this.name;
+ }
+
+ /** @return the parent orchestrator version, or {@code null} if not set. */
+ @Nullable
+ public String getVersion() {
+ return this.version;
+ }
+
+ /** @return the parent orchestration instance, or {@code null} if not set. */
+ @Nullable
+ public OrchestrationInstance getOrchestrationInstance() {
+ return this.orchestrationInstance;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/SubOrchestrationInstanceCompletedEvent.java b/client/src/main/java/com/microsoft/durabletask/history/SubOrchestrationInstanceCompletedEvent.java
new file mode 100644
index 0000000..d55f0fe
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/SubOrchestrationInstanceCompletedEvent.java
@@ -0,0 +1,40 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import javax.annotation.Nullable;
+import java.time.Instant;
+
+/**
+ * History event recorded when a sub-orchestration instance completes successfully.
+ */
+public final class SubOrchestrationInstanceCompletedEvent extends HistoryEvent {
+ private final int taskScheduledId;
+ private final String result;
+
+ /**
+ * Creates a new {@code SubOrchestrationInstanceCompletedEvent}.
+ *
+ * @param eventId the event sequence ID
+ * @param timestamp the event timestamp
+ * @param taskScheduledId the event ID of the corresponding {@link SubOrchestrationInstanceCreatedEvent}
+ * @param result the serialized sub-orchestration result, or {@code null}
+ */
+ public SubOrchestrationInstanceCompletedEvent(
+ int eventId, Instant timestamp, int taskScheduledId, @Nullable String result) {
+ super(eventId, timestamp);
+ this.taskScheduledId = taskScheduledId;
+ this.result = result;
+ }
+
+ /** @return the event ID of the corresponding {@link SubOrchestrationInstanceCreatedEvent}. */
+ public int getTaskScheduledId() {
+ return this.taskScheduledId;
+ }
+
+ /** @return the serialized sub-orchestration result, or {@code null} if none. */
+ @Nullable
+ public String getResult() {
+ return this.result;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/SubOrchestrationInstanceCreatedEvent.java b/client/src/main/java/com/microsoft/durabletask/history/SubOrchestrationInstanceCreatedEvent.java
new file mode 100644
index 0000000..0eb401f
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/SubOrchestrationInstanceCreatedEvent.java
@@ -0,0 +1,84 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import javax.annotation.Nullable;
+import java.time.Instant;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * History event recorded when a sub-orchestration instance is created by an orchestration.
+ */
+public final class SubOrchestrationInstanceCreatedEvent extends HistoryEvent {
+ private final String instanceId;
+ private final String name;
+ private final String version;
+ private final String input;
+ private final TraceContext parentTraceContext;
+ private final Map tags;
+
+ /**
+ * Creates a new {@code SubOrchestrationInstanceCreatedEvent}.
+ *
+ * @param eventId the event sequence ID
+ * @param timestamp the event timestamp
+ * @param instanceId the instance ID assigned to the sub-orchestration
+ * @param name the name of the sub-orchestrator
+ * @param version the sub-orchestrator version, or {@code null}
+ * @param input the serialized sub-orchestration input, or {@code null}
+ * @param parentTraceContext the parent distributed-tracing context, or {@code null}
+ * @param tags the sub-orchestration tags, or {@code null}
+ */
+ public SubOrchestrationInstanceCreatedEvent(
+ int eventId,
+ Instant timestamp,
+ String instanceId,
+ String name,
+ @Nullable String version,
+ @Nullable String input,
+ @Nullable TraceContext parentTraceContext,
+ @Nullable Map tags) {
+ super(eventId, timestamp);
+ this.instanceId = instanceId;
+ this.name = name;
+ this.version = version;
+ this.input = input;
+ this.parentTraceContext = parentTraceContext;
+ this.tags = tags != null ? Collections.unmodifiableMap(new HashMap<>(tags)) : Collections.emptyMap();
+ }
+
+ /** @return the instance ID assigned to the sub-orchestration. */
+ public String getInstanceId() {
+ return this.instanceId;
+ }
+
+ /** @return the name of the sub-orchestrator. */
+ public String getName() {
+ return this.name;
+ }
+
+ /** @return the sub-orchestrator version, or {@code null} if not set. */
+ @Nullable
+ public String getVersion() {
+ return this.version;
+ }
+
+ /** @return the serialized sub-orchestration input, or {@code null} if none. */
+ @Nullable
+ public String getInput() {
+ return this.input;
+ }
+
+ /** @return the distributed-tracing context of the parent, or {@code null} if not set. */
+ @Nullable
+ public TraceContext getParentTraceContext() {
+ return this.parentTraceContext;
+ }
+
+ /** @return the sub-orchestration tags (never {@code null}; empty when none). */
+ public Map getTags() {
+ return this.tags;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/SubOrchestrationInstanceFailedEvent.java b/client/src/main/java/com/microsoft/durabletask/history/SubOrchestrationInstanceFailedEvent.java
new file mode 100644
index 0000000..bb3a2b9
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/SubOrchestrationInstanceFailedEvent.java
@@ -0,0 +1,42 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import com.microsoft.durabletask.FailureDetails;
+
+import javax.annotation.Nullable;
+import java.time.Instant;
+
+/**
+ * History event recorded when a sub-orchestration instance fails.
+ */
+public final class SubOrchestrationInstanceFailedEvent extends HistoryEvent {
+ private final int taskScheduledId;
+ private final FailureDetails failureDetails;
+
+ /**
+ * Creates a new {@code SubOrchestrationInstanceFailedEvent}.
+ *
+ * @param eventId the event sequence ID
+ * @param timestamp the event timestamp
+ * @param taskScheduledId the event ID of the corresponding {@link SubOrchestrationInstanceCreatedEvent}
+ * @param failureDetails the failure details, or {@code null}
+ */
+ public SubOrchestrationInstanceFailedEvent(
+ int eventId, Instant timestamp, int taskScheduledId, @Nullable FailureDetails failureDetails) {
+ super(eventId, timestamp);
+ this.taskScheduledId = taskScheduledId;
+ this.failureDetails = failureDetails;
+ }
+
+ /** @return the event ID of the corresponding {@link SubOrchestrationInstanceCreatedEvent}. */
+ public int getTaskScheduledId() {
+ return this.taskScheduledId;
+ }
+
+ /** @return the failure details, or {@code null} if not available. */
+ @Nullable
+ public FailureDetails getFailureDetails() {
+ return this.failureDetails;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/TaskCompletedEvent.java b/client/src/main/java/com/microsoft/durabletask/history/TaskCompletedEvent.java
new file mode 100644
index 0000000..c2b6ab2
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/TaskCompletedEvent.java
@@ -0,0 +1,39 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import javax.annotation.Nullable;
+import java.time.Instant;
+
+/**
+ * History event recorded when a scheduled activity task completes successfully.
+ */
+public final class TaskCompletedEvent extends HistoryEvent {
+ private final int taskScheduledId;
+ private final String result;
+
+ /**
+ * Creates a new {@code TaskCompletedEvent}.
+ *
+ * @param eventId the event sequence ID
+ * @param timestamp the event timestamp
+ * @param taskScheduledId the event ID of the corresponding {@link TaskScheduledEvent}
+ * @param result the serialized activity result, or {@code null}
+ */
+ public TaskCompletedEvent(int eventId, Instant timestamp, int taskScheduledId, @Nullable String result) {
+ super(eventId, timestamp);
+ this.taskScheduledId = taskScheduledId;
+ this.result = result;
+ }
+
+ /** @return the event ID of the corresponding {@link TaskScheduledEvent}. */
+ public int getTaskScheduledId() {
+ return this.taskScheduledId;
+ }
+
+ /** @return the serialized activity result, or {@code null} if none. */
+ @Nullable
+ public String getResult() {
+ return this.result;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/TaskFailedEvent.java b/client/src/main/java/com/microsoft/durabletask/history/TaskFailedEvent.java
new file mode 100644
index 0000000..efa206e
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/TaskFailedEvent.java
@@ -0,0 +1,42 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import com.microsoft.durabletask.FailureDetails;
+
+import javax.annotation.Nullable;
+import java.time.Instant;
+
+/**
+ * History event recorded when a scheduled activity task fails.
+ */
+public final class TaskFailedEvent extends HistoryEvent {
+ private final int taskScheduledId;
+ private final FailureDetails failureDetails;
+
+ /**
+ * Creates a new {@code TaskFailedEvent}.
+ *
+ * @param eventId the event sequence ID
+ * @param timestamp the event timestamp
+ * @param taskScheduledId the event ID of the corresponding {@link TaskScheduledEvent}
+ * @param failureDetails the failure details, or {@code null}
+ */
+ public TaskFailedEvent(
+ int eventId, Instant timestamp, int taskScheduledId, @Nullable FailureDetails failureDetails) {
+ super(eventId, timestamp);
+ this.taskScheduledId = taskScheduledId;
+ this.failureDetails = failureDetails;
+ }
+
+ /** @return the event ID of the corresponding {@link TaskScheduledEvent}. */
+ public int getTaskScheduledId() {
+ return this.taskScheduledId;
+ }
+
+ /** @return the failure details, or {@code null} if not available. */
+ @Nullable
+ public FailureDetails getFailureDetails() {
+ return this.failureDetails;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/TaskScheduledEvent.java b/client/src/main/java/com/microsoft/durabletask/history/TaskScheduledEvent.java
new file mode 100644
index 0000000..47ec0ba
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/TaskScheduledEvent.java
@@ -0,0 +1,75 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import javax.annotation.Nullable;
+import java.time.Instant;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * History event recorded when an activity task is scheduled by an orchestration.
+ */
+public final class TaskScheduledEvent extends HistoryEvent {
+ private final String name;
+ private final String version;
+ private final String input;
+ private final TraceContext parentTraceContext;
+ private final Map tags;
+
+ /**
+ * Creates a new {@code TaskScheduledEvent}.
+ *
+ * @param eventId the event sequence ID
+ * @param timestamp the event timestamp
+ * @param name the name of the scheduled activity
+ * @param version the activity version, or {@code null}
+ * @param input the serialized activity input, or {@code null}
+ * @param parentTraceContext the parent distributed-tracing context, or {@code null}
+ * @param tags the activity tags, or {@code null}
+ */
+ public TaskScheduledEvent(
+ int eventId,
+ Instant timestamp,
+ String name,
+ @Nullable String version,
+ @Nullable String input,
+ @Nullable TraceContext parentTraceContext,
+ @Nullable Map tags) {
+ super(eventId, timestamp);
+ this.name = name;
+ this.version = version;
+ this.input = input;
+ this.parentTraceContext = parentTraceContext;
+ this.tags = tags != null ? Collections.unmodifiableMap(new HashMap<>(tags)) : Collections.emptyMap();
+ }
+
+ /** @return the name of the scheduled activity. */
+ public String getName() {
+ return this.name;
+ }
+
+ /** @return the activity version, or {@code null} if not set. */
+ @Nullable
+ public String getVersion() {
+ return this.version;
+ }
+
+ /** @return the serialized activity input, or {@code null} if none. */
+ @Nullable
+ public String getInput() {
+ return this.input;
+ }
+
+ /** @return the distributed-tracing context of the parent, or {@code null} if not set. */
+ @Nullable
+ public TraceContext getParentTraceContext() {
+ return this.parentTraceContext;
+ }
+
+ /** @return the activity tags (never {@code null}; empty when none). */
+ public Map getTags() {
+ return this.tags;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/TimerCreatedEvent.java b/client/src/main/java/com/microsoft/durabletask/history/TimerCreatedEvent.java
new file mode 100644
index 0000000..4f98c0b
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/TimerCreatedEvent.java
@@ -0,0 +1,29 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import java.time.Instant;
+
+/**
+ * History event recorded when a durable timer is created by an orchestration.
+ */
+public final class TimerCreatedEvent extends HistoryEvent {
+ private final Instant fireAt;
+
+ /**
+ * Creates a new {@code TimerCreatedEvent}.
+ *
+ * @param eventId the event sequence ID
+ * @param timestamp the event timestamp
+ * @param fireAt the time at which the timer is scheduled to fire
+ */
+ public TimerCreatedEvent(int eventId, Instant timestamp, Instant fireAt) {
+ super(eventId, timestamp);
+ this.fireAt = fireAt;
+ }
+
+ /** @return the time at which the timer is scheduled to fire. */
+ public Instant getFireAt() {
+ return this.fireAt;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/TimerFiredEvent.java b/client/src/main/java/com/microsoft/durabletask/history/TimerFiredEvent.java
new file mode 100644
index 0000000..50fa923
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/TimerFiredEvent.java
@@ -0,0 +1,37 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import java.time.Instant;
+
+/**
+ * History event recorded when a durable timer fires.
+ */
+public final class TimerFiredEvent extends HistoryEvent {
+ private final Instant fireAt;
+ private final int timerId;
+
+ /**
+ * Creates a new {@code TimerFiredEvent}.
+ *
+ * @param eventId the event sequence ID
+ * @param timestamp the event timestamp
+ * @param fireAt the time at which the timer was scheduled to fire
+ * @param timerId the event ID of the corresponding {@link TimerCreatedEvent}
+ */
+ public TimerFiredEvent(int eventId, Instant timestamp, Instant fireAt, int timerId) {
+ super(eventId, timestamp);
+ this.fireAt = fireAt;
+ this.timerId = timerId;
+ }
+
+ /** @return the time at which the timer was scheduled to fire. */
+ public Instant getFireAt() {
+ return this.fireAt;
+ }
+
+ /** @return the event ID of the corresponding {@link TimerCreatedEvent}. */
+ public int getTimerId() {
+ return this.timerId;
+ }
+}
diff --git a/client/src/main/java/com/microsoft/durabletask/history/TraceContext.java b/client/src/main/java/com/microsoft/durabletask/history/TraceContext.java
new file mode 100644
index 0000000..566c7dc
--- /dev/null
+++ b/client/src/main/java/com/microsoft/durabletask/history/TraceContext.java
@@ -0,0 +1,35 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask.history;
+
+import javax.annotation.Nullable;
+
+/**
+ * Distributed-tracing context (W3C Trace Context) associated with a history event.
+ */
+public final class TraceContext {
+ private final String traceParent;
+ private final String traceState;
+
+ /**
+ * Creates a new {@code TraceContext}.
+ *
+ * @param traceParent the W3C {@code traceparent} value
+ * @param traceState the W3C {@code tracestate} value, or {@code null}
+ */
+ public TraceContext(String traceParent, @Nullable String traceState) {
+ this.traceParent = traceParent;
+ this.traceState = traceState;
+ }
+
+ /** @return the W3C {@code traceparent} value. */
+ public String getTraceParent() {
+ return this.traceParent;
+ }
+
+ /** @return the W3C {@code tracestate} value, or {@code null} if not set. */
+ @Nullable
+ public String getTraceState() {
+ return this.traceState;
+ }
+}
diff --git a/client/src/test/java/com/microsoft/durabletask/HistoryEventConverterTest.java b/client/src/test/java/com/microsoft/durabletask/HistoryEventConverterTest.java
new file mode 100644
index 0000000..1922a73
--- /dev/null
+++ b/client/src/test/java/com/microsoft/durabletask/HistoryEventConverterTest.java
@@ -0,0 +1,226 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.microsoft.durabletask;
+
+import com.google.protobuf.StringValue;
+import com.google.protobuf.Timestamp;
+import com.microsoft.durabletask.history.ExecutionCompletedEvent;
+import com.microsoft.durabletask.history.ExecutionStartedEvent;
+import com.microsoft.durabletask.history.GenericEvent;
+import com.microsoft.durabletask.history.HistoryEvent;
+import com.microsoft.durabletask.history.HistoryStateEvent;
+import com.microsoft.durabletask.history.OrchestrationState;
+import com.microsoft.durabletask.history.SubOrchestrationInstanceCompletedEvent;
+import com.microsoft.durabletask.history.TaskCompletedEvent;
+import com.microsoft.durabletask.history.TaskFailedEvent;
+import com.microsoft.durabletask.history.TimerFiredEvent;
+import com.microsoft.durabletask.implementation.protobuf.OrchestratorService;
+import org.junit.jupiter.api.Test;
+
+import java.time.Instant;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Unit tests for {@link HistoryEventConverter}, which maps protobuf history events to the public
+ * {@code com.microsoft.durabletask.history} domain model.
+ */
+public class HistoryEventConverterTest {
+
+ private static final long EPOCH_SECONDS = 1_700_000_000L;
+ private static final Instant EXPECTED_TIMESTAMP = Instant.ofEpochSecond(EPOCH_SECONDS);
+
+ private static OrchestratorService.HistoryEvent.Builder baseEvent(int eventId) {
+ return OrchestratorService.HistoryEvent.newBuilder()
+ .setEventId(eventId)
+ .setTimestamp(Timestamp.newBuilder().setSeconds(EPOCH_SECONDS).build());
+ }
+
+ @Test
+ void convertsExecutionStarted() {
+ OrchestratorService.HistoryEvent proto = baseEvent(1)
+ .setExecutionStarted(OrchestratorService.ExecutionStartedEvent.newBuilder()
+ .setName("MyOrchestration")
+ .setVersion(StringValue.of("2.0"))
+ .setInput(StringValue.of("\"hello\""))
+ .setOrchestrationInstance(OrchestratorService.OrchestrationInstance.newBuilder()
+ .setInstanceId("inst-1")
+ .setExecutionId(StringValue.of("exec-1"))))
+ .build();
+
+ HistoryEvent event = HistoryEventConverter.fromProto(proto);
+
+ ExecutionStartedEvent started = assertInstanceOf(ExecutionStartedEvent.class, event);
+ assertEquals(1, started.getEventId());
+ assertEquals(EXPECTED_TIMESTAMP, started.getTimestamp());
+ assertEquals("MyOrchestration", started.getName());
+ assertEquals("2.0", started.getVersion());
+ assertEquals("\"hello\"", started.getInput());
+ assertNotNull(started.getOrchestrationInstance());
+ assertEquals("inst-1", started.getOrchestrationInstance().getInstanceId());
+ assertEquals("exec-1", started.getOrchestrationInstance().getExecutionId());
+ }
+
+ @Test
+ void convertsExecutionCompletedWithResult() {
+ OrchestratorService.HistoryEvent proto = baseEvent(2)
+ .setExecutionCompleted(OrchestratorService.ExecutionCompletedEvent.newBuilder()
+ .setOrchestrationStatus(OrchestratorService.OrchestrationStatus.ORCHESTRATION_STATUS_COMPLETED)
+ .setResult(StringValue.of("\"done\"")))
+ .build();
+
+ HistoryEvent event = HistoryEventConverter.fromProto(proto);
+
+ ExecutionCompletedEvent completed = assertInstanceOf(ExecutionCompletedEvent.class, event);
+ assertEquals(OrchestrationRuntimeStatus.COMPLETED, completed.getOrchestrationStatus());
+ assertEquals("\"done\"", completed.getResult());
+ assertNull(completed.getFailureDetails());
+ }
+
+ @Test
+ void convertsExecutionCompletedWithFailureDetails() {
+ OrchestratorService.HistoryEvent proto = baseEvent(3)
+ .setExecutionCompleted(OrchestratorService.ExecutionCompletedEvent.newBuilder()
+ .setOrchestrationStatus(OrchestratorService.OrchestrationStatus.ORCHESTRATION_STATUS_FAILED)
+ .setFailureDetails(OrchestratorService.TaskFailureDetails.newBuilder()
+ .setErrorType("java.lang.RuntimeException")
+ .setErrorMessage("boom")))
+ .build();
+
+ HistoryEvent event = HistoryEventConverter.fromProto(proto);
+
+ ExecutionCompletedEvent completed = assertInstanceOf(ExecutionCompletedEvent.class, event);
+ assertEquals(OrchestrationRuntimeStatus.FAILED, completed.getOrchestrationStatus());
+ assertNotNull(completed.getFailureDetails());
+ assertEquals("java.lang.RuntimeException", completed.getFailureDetails().getErrorType());
+ assertEquals("boom", completed.getFailureDetails().getErrorMessage());
+ }
+
+ @Test
+ void convertsTaskCompleted() {
+ OrchestratorService.HistoryEvent proto = baseEvent(4)
+ .setTaskCompleted(OrchestratorService.TaskCompletedEvent.newBuilder()
+ .setTaskScheduledId(7)
+ .setResult(StringValue.of("42")))
+ .build();
+
+ HistoryEvent event = HistoryEventConverter.fromProto(proto);
+
+ TaskCompletedEvent completed = assertInstanceOf(TaskCompletedEvent.class, event);
+ assertEquals(7, completed.getTaskScheduledId());
+ assertEquals("42", completed.getResult());
+ }
+
+ @Test
+ void convertsTaskFailedWithFailureDetails() {
+ OrchestratorService.HistoryEvent proto = baseEvent(5)
+ .setTaskFailed(OrchestratorService.TaskFailedEvent.newBuilder()
+ .setTaskScheduledId(9)
+ .setFailureDetails(OrchestratorService.TaskFailureDetails.newBuilder()
+ .setErrorType("java.io.IOException")
+ .setErrorMessage("disk full")))
+ .build();
+
+ HistoryEvent event = HistoryEventConverter.fromProto(proto);
+
+ TaskFailedEvent failed = assertInstanceOf(TaskFailedEvent.class, event);
+ assertEquals(9, failed.getTaskScheduledId());
+ assertNotNull(failed.getFailureDetails());
+ assertEquals("java.io.IOException", failed.getFailureDetails().getErrorType());
+ }
+
+ @Test
+ void convertsSubOrchestrationCompleted() {
+ OrchestratorService.HistoryEvent proto = baseEvent(6)
+ .setSubOrchestrationInstanceCompleted(
+ OrchestratorService.SubOrchestrationInstanceCompletedEvent.newBuilder()
+ .setTaskScheduledId(3)
+ .setResult(StringValue.of("\"child-result\"")))
+ .build();
+
+ HistoryEvent event = HistoryEventConverter.fromProto(proto);
+
+ SubOrchestrationInstanceCompletedEvent completed =
+ assertInstanceOf(SubOrchestrationInstanceCompletedEvent.class, event);
+ assertEquals(3, completed.getTaskScheduledId());
+ assertEquals("\"child-result\"", completed.getResult());
+ }
+
+ @Test
+ void convertsTimerFired() {
+ Instant fireAt = Instant.ofEpochSecond(EPOCH_SECONDS + 60);
+ OrchestratorService.HistoryEvent proto = baseEvent(7)
+ .setTimerFired(OrchestratorService.TimerFiredEvent.newBuilder()
+ .setTimerId(11)
+ .setFireAt(Timestamp.newBuilder().setSeconds(fireAt.getEpochSecond()).build()))
+ .build();
+
+ HistoryEvent event = HistoryEventConverter.fromProto(proto);
+
+ TimerFiredEvent fired = assertInstanceOf(TimerFiredEvent.class, event);
+ assertEquals(11, fired.getTimerId());
+ assertEquals(fireAt, fired.getFireAt());
+ }
+
+ @Test
+ void convertsGenericEvent() {
+ OrchestratorService.HistoryEvent proto = baseEvent(8)
+ .setGenericEvent(OrchestratorService.GenericEvent.newBuilder()
+ .setData(StringValue.of("payload")))
+ .build();
+
+ HistoryEvent event = HistoryEventConverter.fromProto(proto);
+
+ GenericEvent generic = assertInstanceOf(GenericEvent.class, event);
+ assertEquals("payload", generic.getData());
+ }
+
+ @Test
+ void convertsHistoryStateExposesFullOrchestrationState() {
+ Instant created = Instant.ofEpochSecond(EPOCH_SECONDS - 100);
+ Instant lastUpdated = Instant.ofEpochSecond(EPOCH_SECONDS - 50);
+ OrchestratorService.HistoryEvent proto = baseEvent(9)
+ .setHistoryState(OrchestratorService.HistoryStateEvent.newBuilder()
+ .setOrchestrationState(OrchestratorService.OrchestrationState.newBuilder()
+ .setInstanceId("inst-9")
+ .setName("MyOrchestration")
+ .setVersion(StringValue.of("1.5"))
+ .setOrchestrationStatus(
+ OrchestratorService.OrchestrationStatus.ORCHESTRATION_STATUS_RUNNING)
+ .setCreatedTimestamp(Timestamp.newBuilder().setSeconds(created.getEpochSecond()))
+ .setLastUpdatedTimestamp(
+ Timestamp.newBuilder().setSeconds(lastUpdated.getEpochSecond()))
+ .setInput(StringValue.of("\"in\""))
+ .setCustomStatus(StringValue.of("\"working\""))
+ .putTags("env", "prod")))
+ .build();
+
+ HistoryEvent event = HistoryEventConverter.fromProto(proto);
+
+ HistoryStateEvent stateEvent = assertInstanceOf(HistoryStateEvent.class, event);
+ OrchestrationState state = stateEvent.getState();
+ assertNotNull(state);
+ assertEquals("inst-9", state.getInstanceId());
+ assertEquals("MyOrchestration", state.getName());
+ assertEquals("1.5", state.getVersion());
+ assertEquals(OrchestrationRuntimeStatus.RUNNING, state.getRuntimeStatus());
+ assertEquals(created, state.getCreatedTime());
+ assertEquals(lastUpdated, state.getLastUpdatedTime());
+ assertEquals("\"in\"", state.getInput());
+ assertEquals("\"working\"", state.getCustomStatus());
+ assertNotNull(state.getTags());
+ assertEquals("prod", state.getTags().get("env"));
+ }
+
+ @Test
+ void throwsWhenEventTypeNotSet() {
+ OrchestratorService.HistoryEvent proto = baseEvent(10).build();
+
+ assertThrows(IllegalArgumentException.class, () -> HistoryEventConverter.fromProto(proto));
+ }
+}
diff --git a/client/src/test/java/com/microsoft/durabletask/IntegrationTests.java b/client/src/test/java/com/microsoft/durabletask/IntegrationTests.java
index 59db0b7..2c41b3f 100644
--- a/client/src/test/java/com/microsoft/durabletask/IntegrationTests.java
+++ b/client/src/test/java/com/microsoft/durabletask/IntegrationTests.java
@@ -14,6 +14,9 @@
import java.util.stream.IntStream;
import java.util.stream.Stream;
+import com.microsoft.durabletask.history.ExecutionStartedEvent;
+import com.microsoft.durabletask.history.HistoryEvent;
+import com.microsoft.durabletask.history.TaskCompletedEvent;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
@@ -1202,6 +1205,73 @@ void waitForInstanceCompletionThrowsException() {
}
}
+ @Test
+ void listInstanceIdsByCompletionWindow() throws TimeoutException {
+ final String orchestratorName = "ListInstanceIdsExport";
+ final String plusOne = "PlusOne";
+
+ DurableTaskGrpcWorker worker = this.createWorkerBuilder()
+ .addOrchestrator(orchestratorName, ctx -> {
+ int value = ctx.getInput(int.class);
+ value = ctx.callActivity(plusOne, value, int.class).await();
+ ctx.complete(value);
+ })
+ .addActivity(plusOne, ctx -> ctx.getInput(int.class) + 1)
+ .buildAndStart();
+
+ DurableTaskClient client = this.createClientBuilder().build();
+ try (worker; client) {
+ Instant from = Instant.now().minus(Duration.ofMinutes(1));
+
+ String instanceId = client.scheduleNewOrchestrationInstance(orchestratorName, 0);
+ OrchestrationMetadata metadata = client.waitForInstanceCompletion(instanceId, defaultTimeout, false);
+ assertEquals(OrchestrationRuntimeStatus.COMPLETED, metadata.getRuntimeStatus());
+
+ ListInstanceIdsResult result = client.listInstanceIds(new ListInstanceIdsQuery()
+ .setCompletedTimeFrom(from)
+ .setRuntimeStatusList(Collections.singletonList(OrchestrationRuntimeStatus.COMPLETED))
+ .setPageSize(100));
+
+ assertNotNull(result);
+ assertNotNull(result.getInstanceIds());
+ assertTrue(result.getInstanceIds().contains(instanceId),
+ "Expected listInstanceIds to include the completed instance " + instanceId);
+ }
+ }
+
+ @Test
+ void getOrchestrationHistoryReturnsDomainEvents() throws TimeoutException {
+ final String orchestratorName = "StreamHistoryExport";
+ final String plusOne = "PlusOne";
+
+ DurableTaskGrpcWorker worker = this.createWorkerBuilder()
+ .addOrchestrator(orchestratorName, ctx -> {
+ int value = ctx.getInput(int.class);
+ value = ctx.callActivity(plusOne, value, int.class).await();
+ ctx.complete(value);
+ })
+ .addActivity(plusOne, ctx -> ctx.getInput(int.class) + 1)
+ .buildAndStart();
+
+ DurableTaskClient client = this.createClientBuilder().build();
+ try (worker; client) {
+ String instanceId = client.scheduleNewOrchestrationInstance(orchestratorName, 0);
+ OrchestrationMetadata metadata = client.waitForInstanceCompletion(instanceId, defaultTimeout, false);
+ assertEquals(OrchestrationRuntimeStatus.COMPLETED, metadata.getRuntimeStatus());
+
+ List history = client.getOrchestrationHistory(instanceId);
+
+ assertNotNull(history);
+ assertFalse(history.isEmpty(), "Expected a completed instance to have history events");
+ // A completed orchestration's history begins with an ExecutionStartedEvent.
+ assertTrue(history.stream().anyMatch(e -> e instanceof ExecutionStartedEvent),
+ "Expected history to contain an ExecutionStartedEvent");
+ // And it contains a successful activity completion.
+ assertTrue(history.stream().anyMatch(e -> e instanceof TaskCompletedEvent),
+ "Expected history to contain a TaskCompletedEvent");
+ }
+ }
+
@Test
void activityFanOutWithException() throws TimeoutException {
final String orchestratorName = "ActivityFanOut";
diff --git a/client/src/test/java/com/microsoft/durabletask/ListInstanceIdsQueryTest.java b/client/src/test/java/com/microsoft/durabletask/ListInstanceIdsQueryTest.java
new file mode 100644
index 0000000..bd942ba
--- /dev/null
+++ b/client/src/test/java/com/microsoft/durabletask/ListInstanceIdsQueryTest.java
@@ -0,0 +1,44 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.microsoft.durabletask;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Unit tests for {@link ListInstanceIdsQuery}.
+ */
+public class ListInstanceIdsQueryTest {
+
+ @Test
+ void getRuntimeStatusList_default_isEmptyNonNull() {
+ ListInstanceIdsQuery query = new ListInstanceIdsQuery();
+ assertNotNull(query.getRuntimeStatusList());
+ assertTrue(query.getRuntimeStatusList().isEmpty());
+ }
+
+ @Test
+ void setRuntimeStatusList_null_normalizesToEmptyList() {
+ ListInstanceIdsQuery query = new ListInstanceIdsQuery().setRuntimeStatusList(null);
+ assertNotNull(query.getRuntimeStatusList());
+ assertTrue(query.getRuntimeStatusList().isEmpty());
+ }
+
+ @Test
+ void setRuntimeStatusList_copiesInput_soExternalMutationDoesNotAffectQuery() {
+ List source = new ArrayList<>();
+ source.add(OrchestrationRuntimeStatus.COMPLETED);
+
+ ListInstanceIdsQuery query = new ListInstanceIdsQuery().setRuntimeStatusList(source);
+ source.add(OrchestrationRuntimeStatus.FAILED);
+
+ assertEquals(Arrays.asList(OrchestrationRuntimeStatus.COMPLETED), query.getRuntimeStatusList());
+ }
+}