Skip to main content

Part 1 — Runtime Components

Table of Contents


Architecture

The system uses a two-layer gateway architecture where the client never communicates directly with the workflow engine. All requests pass through FormManager, which either handles them directly or proxies them to WorkflowManager.

┌──────────┐       ┌──────────────┐       ┌───────────────────┐
│ Client │ ───▸ │ FormManager │ ───▸ │ WorkflowManager │
│ (UI) │ │ (FM Gateway) │ │ (WFM Engine) │
└──────────┘ └──────────────┘ └───────────────────┘

This separation ensures that the UI layer is decoupled from the core workflow engine. FM acts as a consistent entry point, handling concerns like correlation headers, request timeouts, and client-specific response wrapping before forwarding workflow operations to WFM.


Service Roles

FormManager (FM)

FormManager is the gateway and proxy layer that sits between the Studio UI and the WorkflowManager. It has two distinct responsibilities:

  1. Direct endpoints: FM owns user management (CRUD operations on workflow users), model management (upsert/fetch workflow definitions), health checks, UI settings delivery, and cache eviction. These endpoints are handled entirely by FM without contacting WFM.

  2. Proxy endpoints: For all workflow-related operations (context loading, task actions, file operations, notifications, queries, etc.), FM acts as a transparent proxy. It forwards the client request to WFM, injects correlation headers for distributed tracing, enforces request timeouts, and wraps the response in the standard FM response envelope before returning it to the client.

WorkflowManager (WFM)

WorkflowManager is the core workflow engine service. It is responsible for:

  • Process lifecycle: Creating, executing, and completing workflow process instances.
  • Task management: Assigning tasks to users/swimlanes, locking/unlocking tasks, completing tasks and advancing the process.
  • Context state: Maintaining the dataInstance (form data), processInstance (process metadata, tasks, notes, files), and activity logs for each process.
  • Flow function execution: Running server-side flow functions (business logic) defined in the workflow model — including task functions, file operations, switch evaluations, and named functions.
  • Notifications: Generating and storing notification records when process state changes (created, updated, completed, finished).
  • Batch jobs: Processing scheduled and queued batch operations.

Auto-Injected Fields

The FM client automatically enriches every outgoing request before it reaches the server:

FieldSourceInjected IntoDescription
organizationIdCurrent sessionRequest bodyThe organization scope for multi-tenant isolation.
appIdCurrent sessionRequest bodyThe application identifier scoping all data and models.
userSelected user in UIRequest headerThe userId of the user performing the action.

These fields do not need to be manually provided by the caller — the FM client handles injection transparently.


Common Request Headers

HeaderTypeRequiredDescription
userstringYes (most endpoints)The userId of the authenticated user performing the action. Auto-injected by the FM client from the currently selected user in the UI.
lpLogClientIdstringNoA client-generated identifier used for log filtering and distributed tracing. Enables per-session log streaming in the Azure service logs endpoint.
additionalheadersstringNoURL-encoded JSON string containing additional context headers. These are forwarded to workflow flow functions during execution, allowing the caller to pass custom metadata into the flow runtime.
studiotokenstringConditionalAuthentication token required specifically for the Azure service log streaming endpoint (/azure/serviceLog/:service).
if-none-matchstringNoStandard HTTP ETag header used by GET /model/getModel/:id for conditional caching. If the value matches the current model's ETag, a 304 Not Modified is returned.

Response Envelope

All responses received by the FM client are wrapped in a standard envelope:

interface IFormManagerBaseResponse<T> {
status: "success" | "customerror" | "permissionError" | "error";
data: T;
error?: { message: string; title?: string };
}
FieldTypeDescription
statusstringDetermines how the client interprets the response. "success" indicates a normal result. "customerror" indicates a domain/business rule violation. "permissionError" indicates an authorization failure. "error" indicates an unexpected server failure.
dataTThe actual response payload. When status is not "success", this may contain a stack trace string for debugging.
errorobjectPresent when status is not "success". Contains a human-readable message and an optional title for display in the UI.

Error Structure

When the server returns an error, the response body conforms to the ICustomError structure:

interface ICustomError {
statusCode: number;
type: "business" | "technical";
category: "warning" | "error";
message: string;
code: string;
summary?: string;
parameters?: Record<string, string>;
detail?: string;
stack?: string;
}
FieldDescription
statusCodeHTTP status code (e.g., 400, 404, 500).
type"business" means a domain rule was violated (e.g., duplicate user, missing required field). "technical" means an infrastructure or system failure.
category"warning" for non-blocking issues, "error" for blocking failures.
messageDetailed error message describing what went wrong.
codeMachine-readable error code (e.g., "wm3120", "fm3027"). Prefixed with wm for WFM errors and fm for FM errors.
summaryShort user-facing title for the error, suitable for UI display.
parametersAdditional key-value context about the error (e.g., the offending field name).
detailExtended error details, often including process/instance identifiers for debugging.
stackStack trace, included only in development/debug environments.