Skip to main content
info

This page is pulled from internal/workflows/README.md in the core repository, where it is maintained alongside the engine code.

Workflow Engine Architecture

This document explains how the workflow engine operates within the Openlane platform, including the flow of data through hooks, events, and the engine itself.

Overview​

The workflows capability is built on simple composable primitives that combine into complex behaviors:

PrimitivePurposeExample
TriggerWhen to evaluatefield modified, edge added
ConditionWhether to proceedobject.status == "DRAFT"
When (per action)Action-level gateassignments.approved >= 3
ActionWhat to doAPPROVAL, NOTIFY, FIELD_UPDATE
OperatorsCompositionfield_modified AND is_tuesday

The engine evaluates triggers → checks conditions → executes actions (respecting their When expressions, e.g. blocking on approvals until criteria met).

Any meaningful sequential vs concurrent distinction around execution emerges from how you structure the definition, not from engine constraints:

  • One approval action with multiple targets + quorum = concurrent
  • Multiple approval actions = sequential with dependencies
  • When expressions = conditional execution based on state

There is an action index as a part of the executor, but it is just tracking progression through the action list, with approvals creating natural "gates" that block until their criteria are satisfied. The engine doesn't impose ordering beyond that - the definition author controls the flow through how they structure actions and their conditions.

High level Architecture​

You can simplify all of the moving parts related to workflows into 2 buckets:

  • Bucket 1: pre-commit interception, where we fork into a "proposed" change
  • Bucket 2: post-commit events, where we're responding after a transaction

Pre-Commit Interception​

For workflows where we intercept the change before its applied (approvalTiming = PRE_COMMIT):

  • Eligible fields are staged into proposals for approval.
  • Ineligible fields and edge changes are applied immediately.
  • The response reflects direct changes, while approval-gated fields remain unchanged.
User Request ──► Hook intercepts ──► Creates WorkflowProposal ──► Returns entity with direct changes
│
▼
Proposal submitted ──► WorkflowInstance created ──► Assignments created
│
▼
Users approve/reject
│
▼
Quorum satisfied?
│
┌───────────────────────────┴───────────┐
│ │
▼ ▼
YES: Apply NO: Fail
proposal changes workflow
to entity

Post-Commit Events​

For workflows that react to changes:

User Request ──► Mutation commits ──► gala event emitted ──► handleWorkflowMutation
│
▼
Find matching definitions
│
▼
Trigger workflows
(NOTIFY, WEBHOOK, etc.)

Approval Submission Modes​

Workflow definitions with approval actions have an approvalSubmissionMode that controls when the approval process begins. When omitted, the system defaults to AUTO_SUBMIT.

ModeProposal Initial StateBehavior
AUTO_SUBMITSUBMITTEDApproval assignments are created immediately when the mutation is intercepted. Approvers are notified right away. This is the standard flow.
MANUAL_SUBMITDRAFT (planned)Intended: proposal is created in DRAFT state and assignments are created only after explicit submit. Current behavior is coerced to AUTO_SUBMIT.

Current Implementation Note: AUTO_SUBMIT is the default and currently the only effective mode. MANUAL_SUBMIT is accepted in definitions but currently coerced to AUTO_SUBMIT, so proposals are submitted immediately and assignments are created. The GraphQL mutation to submit draft proposals is not yet exposed (WorkflowProposal is an internal entity with entgql.Skip(entgql.SkipAll)). Completing MANUAL_SUBMIT support will enable true draft staging before requesting approval.

Approval Timing​

approvalTiming controls whether approvals block changes or run after commit.

TimingBehavior
PRE_COMMITHook intercepts updates, stages eligible fields into proposals; ineligible fields and edge changes apply immediately.
POST_COMMITMutation commits normally; approval actions are converted to review actions and run after commit (no mutation blocking).

Definition Prefiltering​

WorkflowDefinition persists derived trigger fields (trigger_operations, trigger_fields) for coarse SQL prefiltering. These are computed from definition_json by HookWorkflowDefinitionPrefilter and used by FindMatchingDefinitions to reduce the candidate set before reading the full JSON and evaluating triggers.

Key properties:

  • Intentionally lossy: trigger_fields is a union across triggers and does not preserve per-trigger pairing.
  • Selectors and trigger expressions are not represented.
  • If any trigger omits fields/edges, trigger_fields is cleared (nil) to avoid false negatives.
  • The JSON definition remains the source of truth; prefiltering only excludes impossible matches.

Safe exclusion criteria:

  • eventType not in trigger_operations.
  • trigger_fields is non-empty and has no overlap with changed fields/edges.

Approval Invalidation​

When a WorkflowProposal's changes are modified after approvals have been given:

  1. HookWorkflowProposalInvalidateAssignments detects the change
  2. All APPROVED assignments are reset to PENDING
  3. Invalidation metadata is recorded (who, when, hash diff)
  4. Affected users are notified
  5. Re-approval is required

This implements GitHub-style "dismiss stale reviews" behavior.

Workflow Metadata​

The workflowMetadata query exposes eligible fields and eligible edges per workflow object type for UI composition and trigger authoring. Eligible fields and edges are derived from the entityops schema registry.

Adding Workflow Support to a Schema​

  1. Add the WorkflowApprovalMixin to your schema:
func (Policy) Mixin() []ent.Mixin {
return []ent.Mixin{
// ... other mixins
WorkflowApprovalMixin{},
}
}
  1. Register eligible fields (entx annotation) and eligible edges (workflow edge annotations/mixins) in the workflow metadata
  2. Add edges from WorkflowObjectRef schema if they don't already exist
  3. Run code generation / task regenerate, merge the output / changes
  4. Create workflow definitions targeting your schema type and test it out

Workflow Definition Structure​

There are a few examples included in this package, and you can find the types in our models if you want to look at all the various fields, but here's a representative example of policy approvals:

{
"name": "Policy Approval Workflow",
"schemaType": "Policy",
"workflowKind": "APPROVAL",
"approvalSubmissionMode": "AUTO_SUBMIT",
"triggers": [
{
"operation": "UPDATE",
"fields": ["description", "details"],
"expression": "object.status == 'PUBLISHED'"
}
],
"conditions": [
{
"expression": "object.category != 'INTERNAL'"
}
],
"actions": [
{
"key": "manager_approval",
"type": "APPROVAL",
"params": {
"targets": [{"type": "RESOLVER", "resolver_key": "object_owner"}],
"required_count": 1
}
},
{
"key": "notify_team",
"type": "NOTIFICATION",
"when": "assignments.approved >= 1",
"params": {
"targets": [{"type": "GROUP", "id": "policy-team-group-id"}],
"title": "Policy {{object.name}} approved",
"body": "The policy has been approved and changes applied."
}
}
]
}

Events and Event Topics​

These are the “statuses” stored in workflow_events.event_type and payload.event_type. We only persist business-facing snapshots plus emit failure tracking. Runtime flow uses gala topics (WorkflowTriggered, ActionStarted, AssignmentCompleted, etc.), but not all topics are persisted.

Legacy event types like ACTION_STARTED, ACTION_FAILED, ACTION_SKIPPED, ASSIGNMENT_RESOLVED, INSTANCE_PAUSED, and INSTANCE_RESUMED are no longer persisted; their outcomes are represented via ACTION_COMPLETED details or are purely runtime concerns.


Detailed Architecture Diagrams​

The following mermaid diagrams illustrate the various flows and permutations supported by the workflow system.

Pre-Commit Approval Flow (Detailed)​

This diagram shows what happens when a user updates a field that requires approval.

Multi-Approver Quorum Flow​

When a workflow requires multiple approvers with a quorum threshold.

Multiple Concurrent Workflow Instances​

When a single mutation triggers multiple workflow definitions.

Domain-Based Approval Isolation​

Different field sets create separate approval domains, allowing concurrent workflows on the same object.

Post-Commit Notification Flow​

For workflows that react to changes (no approval required).

Parallel Approval Actions​

When a workflow has multiple independent approval actions that execute concurrently.

Approval Invalidation Flow​

When a proposal is edited after approvals have been given.

Concurrent Approval Race Condition Handling​

How the engine handles concurrent approval submissions.

Event Emission and Recovery​

How the system handles event emission failures and recovery.

Field Eligibility and Proposal Routing​

How the system determines which fields can be workflow-controlled.