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:
0264408
2026-09-15 10:14:44 +08:00
co-authored by Cursor
parent 77d1e5198b
commit f3220fd0a0
26 changed files with 1063 additions and 67 deletions
@@ -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
}
@@ -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);
}