Skip to content
Settings

workflows — state machine definitions

cyoda-go version 0.6.2

workflows — workflow state machine definitions: states, transitions, processors, and criteria.

POST /api/model/{entityName}/{modelVersion}/workflow/import
GET /api/model/{entityName}/{modelVersion}/workflow/export

Context path prefix is CYODA_CONTEXT_PATH (default /api). All endpoints require Authorization: Bearer <token> except when CYODA_IAM_MODE=mock.

A workflow definition is a named finite state machine attached to an entity model. Workflows are stored per model reference (entityName, modelVersion). A model may have multiple workflow definitions; the engine selects the matching one per entity using the workflow-level criterion field evaluated at entity creation time. When no criterion matches, the engine uses the default built-in workflow.

The engine executes automatically after every entity write. It sets the initial state, evaluates automated transitions (cascade), and invokes processors on each transition. Manual transitions are triggered by the client via PUT /entity/{format}/{entityId}/{transition}.

The engine enforces a per-state visit limit of 10 by default (configurable via WithMaxStateVisits) and an absolute cascade depth limit of 100 to prevent infinite loops. Static cycle detection runs at import time.

WorkflowDefinition (element of the workflows array in import):

{
"version": "1",
"name": "prize-lifecycle",
"desc": "State machine for Nobel Prize entities",
"initialState": "NEW",
"active": true,
"criterion": null,
"states": {
"NEW": {
"transitions": [
{
"name": "APPROVE",
"next": "APPROVED",
"manual": true,
"disabled": false,
"criterion": null,
"processors": [
{
"type": "EXTERNAL",
"name": "notify-approval",
"executionMode": "SYNC",
"config": {
"attachEntity": true,
"calculationNodesTags": "approval-service",
"responseTimeoutMs": 30000,
"retryPolicy": "",
"context": ""
}
}
]
},
{
"name": "AUTO_VALIDATE",
"next": "VALIDATED",
"manual": false,
"disabled": false,
"criterion": {
"type": "simple",
"jsonPath": "$.year",
"operatorType": "EQUALS",
"value": "2024"
},
"processors": []
}
]
},
"APPROVED": {
"transitions": []
},
"VALIDATED": {
"transitions": []
}
}
}

WorkflowDefinition fields:

  • version — string — schema version tag (informational; not interpreted by the engine)
  • name — string — unique within the model; the primary key for MERGE mode
  • desc — string — optional description
  • initialState — string — state assigned when the entity is first created; must exist in states
  • active — boolean — when false, the engine skips this workflow during selection
  • criterionCondition JSON or null — evaluated against the entity at creation to select this workflow; null matches all entities
  • states — object — map of state name → StateDefinition

StateDefinition:

  • transitions — array of TransitionDefinition — may be empty

TransitionDefinition fields:

  • name — string — transition name; used by the client in PUT /entity/{format}/{entityId}/{name} and in engine cascade
  • next — string — target state; must exist in states
  • manual — boolean — true means the transition requires an explicit client request; false means the engine evaluates it automatically in cascade
  • disabled — boolean — when true, the engine skips this transition entirely
  • criterionCondition JSON or null — evaluated before executing the transition; null means always matches; the same Condition DSL as search (see search topic)
  • processors — array of ProcessorDefinition — invoked sequentially on this transition

ProcessorDefinition fields:

  • type — string — processor type; see valid values below
  • name — string — logical processor name
  • executionMode — string — execution mode; see valid values below
  • configProcessorConfig

Valid type values (exhaustive for v0.6.1):

  • "EXTERNAL" — dispatches to a calculation node via gRPC using calculationNodesTags for routing

No other types are supported. Supplying any other value produces errors.VALIDATION_FAILED at workflow import time.

Valid executionMode values (exhaustive):

  • "SYNC" — the engine dispatches the processor and blocks until a response is received; the entity write transaction remains open during the wait; processor failure (including timeout and success=false in the response) returns errors.WORKFLOW_FAILED (400) and the entity remains in the source state
  • "ASYNC_SAME_TX" — same dispatch mechanics as SYNC (blocks inline, transaction stays open); failure semantics are identical to SYNC
  • "ASYNC_NEW_TX" — dispatched within a savepoint; on failure the savepoint is rolled back and the error is logged as a warning; the pipeline continues to the next processor and the transition completes; returned entity modifications are discarded

An invalid executionMode value is treated as SYNC / ASYNC_SAME_TX (the engine’s default branch). It is not rejected at import time but produces undefined behaviour and must not be relied upon.

ProcessorConfig fields:

  • attachEntity — boolean — when true, the full entity payload is sent to the processor
  • calculationNodesTags — string — comma-separated tags for routing to registered calculation nodes; the engine selects a node that declares all required tags; returns errors.NO_COMPUTE_MEMBER_FOR_TAG if no node matches
  • responseTimeoutMs — int64 — timeout in milliseconds for SYNC processor response; 0 means use node default
  • retryPolicy — string — retry policy name (plugin/platform-defined); empty means no retry
  • context — string — arbitrary string forwarded to the processor as context metadata

Criteria on workflows and transitions use the same Condition DSL as search. All four condition types are supported: simple, lifecycle, group, array. Criteria are evaluated in-memory against the entity’s JSON payload and lifecycle metadata.

simple criteria match entity data fields via JSONPath. lifecycle criteria match state, creationDate, or previousTransition from entity metadata.

A null criterion on a workflow means the workflow matches any entity. A null criterion on a transition means the transition always fires (automated) or is always available (manual). When multiple automated transitions are eligible, the engine selects the first one by declaration order whose criterion matches. A null criterion matches unconditionally, so a null-criterion automated transition must be the last automated transition in declaration order; any automated transitions declared after a null-criterion transition are unreachable.

POST /api/model/{entityName}/{modelVersion}/workflow/import

  • entityName (path): string
  • modelVersion (path): int32

Request body (application/json):

{
"importMode": "MERGE",
"workflows": [
{ ...WorkflowDefinition... }
]
}
  • importMode"MERGE" (default): incoming workflows overwrite existing ones by name; existing workflows not in the import are preserved. "REPLACE": all existing workflows are discarded; only the incoming set is stored. "ACTIVATE": incoming workflows replace same-named existing ones and are set active=true; existing workflows not in the import set are set active=false.
  • workflows — array of WorkflowDefinition; all imported workflows are set active=true regardless of the active field in the body

Static validation runs before saving: definite infinite loops (cycles reachable only via automated transitions) cause 400 VALIDATION_FAILED.

Response: 200 OK, application/json:

{"success": true}

GET /api/model/{entityName}/{modelVersion}/workflow/export

Response: 200 OK, application/json:

{
"entityName": "nobel-prize",
"modelVersion": 1,
"workflows": [
{ ...WorkflowDefinition... }
]
}

Returns 404 WORKFLOW_NOT_FOUND when no workflows have been imported for the model.

Export field omission: The export response omits optional fields that were not explicitly set or are default values. Specifically, TransitionDefinition objects in the export may omit disabled (when false) and processors (when empty). States with no transitions are serialised as {} rather than {"transitions":[]}. The desc field on WorkflowDefinition is omitted when empty.

The workflow engine runs synchronously within the entity write transaction. The execution sequence for a CREATE:

  1. Load workflow definitions for the model.
  2. Evaluate each workflow’s criterion against the entity; select the first match. If none match, use the built-in default workflow.
  3. Set entity.Meta.State = workflow.initialState.
  4. If a named transition was requested (by the client), execute it: evaluate criterion, invoke processors, set entity.Meta.State = transition.next.
  5. Cascade: repeatedly scan the current state’s transitions; for each automated (manual=false) non-disabled transition, evaluate criterion; if it matches, invoke processors and advance the state. Stop when no automated transition matches or the state has no automated transitions.
  6. The engine records StateMachineEvent entries to the audit log under the entity’s transactionId.

Per-state visit limit (default 10) and total cascade depth limit (100) are enforced to prevent infinite loops.

  • errors.TRANSITION_NOT_FOUND404 — named transition does not exist in the current state’s workflow
  • errors.WORKFLOW_NOT_FOUND404 — no workflows found for the model (export endpoint)
  • errors.WORKFLOW_FAILED — workflow engine encountered an unrecoverable error during execution
  • errors.NO_COMPUTE_MEMBER_FOR_TAG — no registered calculation node matches the required calculationNodesTags
  • errors.COMPUTE_MEMBER_DISCONNECTED — a calculation node disconnected during processor dispatch
  • errors.VALIDATION_FAILED400 — static cycle detection failed during workflow import

Import a workflow:

curl -s -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"importMode": "MERGE",
"workflows": [
{
"version": "1",
"name": "prize-lifecycle",
"initialState": "NEW",
"active": true,
"states": {
"NEW": {
"transitions": [
{
"name": "APPROVE",
"next": "APPROVED",
"manual": true,
"processors": []
}
]
},
"APPROVED": {
"transitions": []
}
}
}
]
}' \
"http://localhost:8080/api/model/nobel-prize/1/workflow/import"

Export workflows:

curl -s -H "Authorization: Bearer $TOKEN" \
"http://localhost:8080/api/model/nobel-prize/1/workflow/export"

Trigger a manual transition:

curl -s -X PUT \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"category":"physics","year":"2024"}' \
"http://localhost:8080/api/entity/JSON/74807f00-ed0d-11ee-a357-ae468cd3ed16/APPROVE"

Replace all workflows:

curl -s -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"importMode": "REPLACE",
"workflows": [
{
"version": "1",
"name": "simple-wf",
"initialState": "OPEN",
"active": true,
"states": {
"OPEN": { "transitions": [] }
}
}
]
}' \
"http://localhost:8080/api/model/nobel-prize/1/workflow/import"
  • models
  • crud
  • grpc
  • search
  • errors.TRANSITION_NOT_FOUND
  • errors.WORKFLOW_NOT_FOUND
  • errors.WORKFLOW_FAILED
  • errors.NO_COMPUTE_MEMBER_FOR_TAG
  • errors.COMPUTE_MEMBER_DISCONNECTED
  • cyoda help models — A model is a named, versioned schema registered per tenant. Every entity in the system is an instance of exactly one model. Models are identified by (entityName, modelVersion). The model ID is a deterministic UUID v5 derived from that key: UUID.newSHA1(NameSpaceURL, "{entityName}.{modelVersion}").
  • cyoda help crud — Entities are instances of models. Each entity has a UUID, a model reference (entityName, modelVersion), and a lifecycle state managed by the workflow engine. Creating an entity requires the referenced model to be in LOCKED state. All write operations run within a Cyoda transaction and return a transactionId alongside the affected entity IDs.
  • cyoda help grpc — cyoda-go exposes one gRPC service: CloudEventsService (package org.cyoda.cloud.api.grpc). All gRPC methods use the CloudEvents Protobuf envelope (io.cloudevents.v1.CloudEvent) as both request and response types. The event type string in the CloudEvent envelope selects the operation; the JSON payload in text_data (or binary_data) carries the operation-specific body.
  • cyoda help search — Search operates against a specific entity model (entityName, modelVersion). Two modes are supported:
  • cyoda help errors TRANSITION_NOT_FOUND — Entity workflow state machines define explicit transitions between states. This error fires when a transition is triggered that does not exist in the model’s workflow definition for the entity’s current state. Also occurs when the transition name is misspelled or when the entity is in a terminal state that allows no further transitions.
  • cyoda help errors WORKFLOW_NOT_FOUND — Entity models reference a workflow by name to govern state transitions. This error is returned when the named workflow cannot be found in the tenant’s workflow registry, during entity type registration or when a model references a workflow that was deleted.
  • cyoda help errors WORKFLOW_FAILED — During an entity create or transition operation the associated workflow processors (pre-processors, post-processors) or guard conditions ran but one of them signalled failure. The failure message from the processor is included in the error detail.
  • cyoda help errors NO_COMPUTE_MEMBER_FOR_TAG — Workflow processors are dispatched to nodes that advertise matching compute tags. When no node with the required tag is alive in the cluster within the configured wait timeout (CYODA_DISPATCH_WAIT_TIMEOUT), the operation is rejected with this error.
  • cyoda help errors COMPUTE_MEMBER_DISCONNECTED — The compute member responsible for executing a processor or workflow step disconnected before completing the operation. The task may or may not have been executed.