feat: persist process history events, listeners, and ACTION executions
Give hosts an append-only timeline, post-commit OrdoEventListener hooks, and durable ACTION results without blocking the approval flow. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
package com.jetlumen.ordo.api;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
|
||||
/** Persisted record of an ACTION step invocation. */
|
||||
public record ActionExecution(String id, String instanceId, String stepId, String actionKey,
|
||||
ActionExecutionStatus status, String errorMessage, Instant startedAt,
|
||||
Instant finishedAt) {
|
||||
public ActionExecution {
|
||||
requireText(id, "id");
|
||||
requireText(instanceId, "instanceId");
|
||||
requireText(stepId, "stepId");
|
||||
requireText(actionKey, "actionKey");
|
||||
Objects.requireNonNull(status, "status must not be null");
|
||||
Objects.requireNonNull(startedAt, "startedAt must not be null");
|
||||
errorMessage = errorMessage == null || errorMessage.isBlank() ? null : errorMessage.strip();
|
||||
}
|
||||
|
||||
private static void requireText(String value, String name) {
|
||||
if (value == null || value.isBlank()) {
|
||||
throw new IllegalArgumentException(name + " must not be blank");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.jetlumen.ordo.api;
|
||||
|
||||
/** Lifecycle of a persisted ACTION-step execution. */
|
||||
public enum ActionExecutionStatus {
|
||||
PENDING,
|
||||
SUCCESS,
|
||||
FAILED
|
||||
}
|
||||
@@ -47,4 +47,10 @@ public interface OrdoEngine {
|
||||
|
||||
/** Paginated listing of all registered process definitions. */
|
||||
Page<ProcessDefinition> listDefinitions(PageRequest pageRequest);
|
||||
|
||||
/** Instance timeline, oldest-first; see {@link com.jetlumen.ordo.api.repository.ProcessHistoryRepository#query}. */
|
||||
Page<ProcessEvent> queryHistory(String instanceId, PageRequest pageRequest);
|
||||
|
||||
/** ACTION executions for an instance, oldest-first. */
|
||||
Page<ActionExecution> queryActionExecutions(String instanceId, PageRequest pageRequest);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.jetlumen.ordo.api;
|
||||
|
||||
/**
|
||||
* Host hook invoked after a process mutation has been committed. Implementations must not throw
|
||||
* in a way that affects the engine: the runtime isolates listener failures.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface OrdoEventListener {
|
||||
void onEvent(ProcessEvent event);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.jetlumen.ordo.api;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
|
||||
/** Immutable audit record of something that happened to a process instance. */
|
||||
public record ProcessEvent(String id, String instanceId, String taskId, String stepId, ProcessEventType type,
|
||||
String actor, String detail, Instant occurredAt) {
|
||||
public ProcessEvent {
|
||||
requireText(id, "id");
|
||||
requireText(instanceId, "instanceId");
|
||||
Objects.requireNonNull(type, "type must not be null");
|
||||
Objects.requireNonNull(occurredAt, "occurredAt must not be null");
|
||||
taskId = blankToNull(taskId);
|
||||
stepId = blankToNull(stepId);
|
||||
actor = blankToNull(actor);
|
||||
detail = blankToNull(detail);
|
||||
}
|
||||
|
||||
private static String blankToNull(String value) {
|
||||
return value == null || value.isBlank() ? null : value.strip();
|
||||
}
|
||||
|
||||
private static void requireText(String value, String name) {
|
||||
if (value == null || value.isBlank()) {
|
||||
throw new IllegalArgumentException(name + " must not be blank");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.jetlumen.ordo.api;
|
||||
|
||||
/** Kinds of append-only process history events. */
|
||||
public enum ProcessEventType {
|
||||
INSTANCE_STARTED,
|
||||
TASK_CREATED,
|
||||
TASK_APPROVED,
|
||||
TASK_REJECTED,
|
||||
TASK_SKIPPED,
|
||||
INSTANCE_APPROVED,
|
||||
INSTANCE_REJECTED,
|
||||
INSTANCE_WITHDRAWN,
|
||||
ACTION_SUCCEEDED,
|
||||
ACTION_FAILED
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.jetlumen.ordo.api.repository;
|
||||
|
||||
import com.jetlumen.ordo.api.ActionExecution;
|
||||
import com.jetlumen.ordo.api.ActionExecutionStatus;
|
||||
import com.jetlumen.ordo.api.query.Page;
|
||||
import com.jetlumen.ordo.api.query.PageRequest;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
/** Storage port for ACTION-step execution records. */
|
||||
public interface ActionExecutionRepository {
|
||||
void insert(ActionExecution execution);
|
||||
|
||||
/**
|
||||
* Completes a pending execution.
|
||||
*
|
||||
* @return true if the row was still pending and was updated
|
||||
*/
|
||||
boolean complete(String executionId, ActionExecutionStatus status, String errorMessage, Instant finishedAt);
|
||||
|
||||
/** Executions for one instance, oldest-first ({@code started_at}, then {@code id}). */
|
||||
Page<ActionExecution> query(String instanceId, PageRequest pageRequest);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.jetlumen.ordo.api.repository;
|
||||
|
||||
import com.jetlumen.ordo.api.ProcessEvent;
|
||||
import com.jetlumen.ordo.api.query.Page;
|
||||
import com.jetlumen.ordo.api.query.PageRequest;
|
||||
|
||||
/** Storage port for append-only process history. */
|
||||
public interface ProcessHistoryRepository {
|
||||
void append(ProcessEvent event);
|
||||
|
||||
/** Timeline for one instance, oldest-first ({@code occurred_at}, then {@code id}). */
|
||||
Page<ProcessEvent> query(String instanceId, PageRequest pageRequest);
|
||||
}
|
||||
Reference in New Issue
Block a user