Skip to content

Concepts

Workflow Definition name + version Instance one run Steps do, dialog, … Event history the record starts runs records
Workflow Definition name + version Instance one run Steps do, dialog, … Event history the record starts runs records
A Workflow Definition is the code; each start creates an instance, and every step it runs is recorded in that instance’s event history.

IdentityFlow keeps two things apart: the code that describes a process, and the record of what one run of that code did. The code is a Workflow Definition. The record is the event history of an instance.

This page defines the terms the rest of the documentation uses. You don’t need to read it first: start with the tutorial, and look a term up here when a page links to it.

TermSection
Workflow Definition, version, draftWorkflow Definition
instance, instance statusInstance
step, dialog, step statusSteps
event, projectionEvents and projections
replayReplay
binding, connector, fixtureBindings and connectors
account, provider, principalAccounts, providers and principals
assignee, assignee sourceAssignees and assignee sources
access functionAccess functions
starter, Workflow Test distributionStarter and Workflow Test distribution

A Workflow Definition is the value a defineWorkflow call returns and your module exports as its default export. It has a name, a version, an optional input schema, and an async function that contains the process logic. The host deploys only the default export, so one file holds one workflow.

  • Immutable. The code behind a deployed name and version never changes. To change behavior, edit the definition in its file and raise its version, keeping the same name. If the code behind a deployed name and version changes, the host refuses to start with WF_DEFINITION_BUNDLE_CHANGED.
  • Stored. The host keeps every deployed version in its database. Running instances load their version from there, so they keep running after the old code is gone from your project.
  • Versioned. Versions follow semantic versioning. Without a version, the host assigns 0.0.0+<hash>; set one explicitly.
  • Running instances move to compatible versions. Each time an instance continues, IdentityFlow runs it with the newest deployed version that is compatible with its current one, in the sense of the semver range ^<version>. From 1.0.0 on, that is every later minor and patch version of the same major version; below 1.0.0, the range is narrower. So an instance started on 1.0.0 that waits three weeks for an approval continues on 1.2.0 if that version was deployed in the meantime. A change that running instances cannot absorb needs a new major version; Author workflows lists what a minor or patch version may change.
  • draft. A definition is a draft unless it sets draft: false. The host skips drafts at startup and logs Skipping deployment of draft workflow. Workflow Tests run a definition whether or not it is a draft.

Author workflows walks through a complete definition. Deploy a new version of a workflow shows a new version with a test.

An instance is one run of one Workflow Definition. Starting a workflow creates an instance; the instance owns its own event history.

An instance has one of five statuses:

StatusMeaning
ACTIVEThe instance is running or waiting. Waiting for a person, a timer, an external system or a child workflow shows on the step, not on the instance.
PAUSEDAn operator paused it. Timers and retries wait until an operator resumes it, or until the resume time set when pausing. A dialog can still be answered; the workflow continues after the resume.
TERMINATEDAn operator ended it. It does not run again.
COMPLETEDThe workflow function returned. The returned value is the instance result.
ERROREDThe workflow function threw an error it did not catch, usually a step that failed on its last attempt. It does not run again.

COMPLETED, ERRORED and TERMINATED are final. Errors and retries explains how an instance reaches ERRORED.

Operate running workflows opens with a figure of which commands move an instance between these statuses.

A step is a named unit of work or waiting inside an instance. There are five kinds:

StepWhat it doesStatus while it waits
flow.doRuns your code, typically a call to another system, and records the result.ACTIVE
flow.sleepWaits for a duration or until a date.SLEEPING
flow.dialogWaits for a person to answer. A dialog is this kind of step.PENDING
flow.requestStarts work in an external system and polls it for the result.REQUESTING
flow.startStarts a child workflow, a separate instance, and waits for its result.RELYING

A result checked against a schema passes through CONTINUING:

WAITS FOR A RESULT ACTIVE attempt runs SLEEPING flow.sleep PENDING flow.dialog REQUESTING flow.request RELYING flow.start CONTINUING checks result COMPLETED final ERRORED or retried sleeps returns, no schema returns, with schema waits time passes result valid fails
WAITS FOR A RESULT ACTIVE attempt runs SLEEPING flow.sleep PENDING flow.dialog REQUESTING flow.request RELYING flow.start CONTINUING checks result COMPLETED final ERRORED or retried sleeps returns, no schema returns, with schema waits time passes result valid fails
A waiting step parks in a waiting status; an arriving result, or a flow.do result with a schema, passes through CONTINUING before the step becomes COMPLETED or ERRORED.

Between two attempts of a failed step, the step is also ERRORED, with the time of its next attempt; it becomes ACTIVE again when that attempt starts. It is finally ERRORED only when no attempt is left. A step can also end as CANCELED when Promise composition stopped waiting for it; see Promise composition.

The step name identifies the step within its instance. It has to be stable: a resumed run finds its recorded work by that name.

An event is an immutable record that something happened to an instance or one of its steps, for example INSTANCE_STARTED, STEP_PENDING or STEP_COMPLETED. Events are appended and never changed or deleted. They are the source of truth for an instance’s state and its audit trail.

A projection is a queryable view built from events, such as the list of dialogs waiting for one person. The IdentityFlow UI and the GraphQL API read projections. IdentityFlow updates the projections as it appends each event. The events are the record; 0.3.0 has no command that rebuilds a projection from them.

IdentityFlow does not keep your workflow function in memory while an instance waits. When the instance continues, after an answer, a timer, a retry or a restart of the host, IdentityFlow runs the workflow function again from the top. This is replay.

During replay, every step that already finished returns its recorded result from the event history. Its code does not run again. The first step without a recorded result runs for real, and execution continues from there.

A short example:

  1. The first run calls flow.do('load employee', …), which calls the directory and records { displayName: 'Alex' }. Then flow.dialog(…) waits for an answer. The run stops.
  2. Two days later someone answers. The function runs again from the top. flow.do('load employee', …) returns the recorded { displayName: 'Alex' } without calling the directory. flow.dialog(…) returns the recorded answer. The code after it runs for the first time.
RUN 1 RUN 2 · AFTER THE ANSWER load employee flow.do · runs approve waits for answer Run stops nothing in memory Event history per instance load employee returns recorded approve returns answer Code after it runs first time then stops records results replays them then runs
RUN 1 RUN 2 · AFTER THE ANSWER load employee flow.do · runs approve waits for answer Run stops nothing in memory Event history per instance load employee returns recorded approve returns answer Code after it runs first time then stops records results replays them then runs
On replay the workflow function runs from the top; finished steps return their recorded results instead of running again.

The step name is the key into the recorded results:

flow.do('load employee', () => directory.lookup(flow.params.employeeId))
  1. reuses completed result
  2. finds the recorded result
  3. not called on replay

The consequence: code outside a step runs on every replay. It must not call another system, read the clock, use random numbers or write anything. Put that work inside a flow.do step, where its result is recorded and returned on replay. Code outside steps may read flow.params, branch on step results and call further steps. Replay rules lists the rules.

A binding is a workflow’s named access to an external system, such as a directory or a payroll service. You declare it with defineBinding(name, factory); the factory creates the client. The workflow gets the client with flow.use(binding) and calls it inside a step.

A connector is the protocol implementation behind a binding. IdentityFlow 0.3.0 ships one connector, for GraphQL. For any other protocol, the binding’s factory builds your own client.

A fixture is the replacement for a binding in a Workflow Test: an object your test registers under the binding’s name, so the workflow never reaches the real system. In other tools this is a fake or test double.

Bindings and connectors shows how to write a binding. Test workflows shows fixtures.

An account is a signed-in identity from your OpenID Connect provider. An account identifies who someone is, not what they may do.

A provider is a registered identity hook with an ID, such as acme. For an account that signs in, the provider reports what that account is in your organization.

A principal is one such fact: a { providerId, subject } pair, for example { providerId: 'acme', subject: 'role:approver' }. The subject is a string the provider chooses. It can name a person (user:42), a role or a group. A principal grants nothing by itself; a dialog or an access rule has to name it.

Identity and access explains how IdentityFlow chooses the provider for a login, and Connect your identity provider shows how to write one.

An assignee is a principal that may answer a dialog. A dialog lists its assignees when it starts. An account may answer when one of its current principals matches one of the assignees. IdentityFlow asks the provider for the account’s principals again at the moment the account answers.

PRINCIPALS ASSIGNEES Account signed in Provider acme identity hook Dialog flow.dialog user:42 provider acme role:approver provider acme role:approver provider acme Account may answer checked on answer routes to reports lists matches one match is enough
PRINCIPALS ASSIGNEES Account signed in Provider acme identity hook Dialog flow.dialog user:42 provider acme role:approver provider acme role:approver provider acme Account may answer checked on answer routes to reports lists matches one match is enough
An account may answer a dialog when one principal its provider reports matches one of the dialog's assignees, provider ID and subject alike.

An assignee source is a reference to a group maintained in another system, such as “the approvers of cost center 4711”. A dialog can carry one next to its assignees. When that system sends a change event, a signed webhook call to IdentityFlow, the assignees of the open dialogs that use the source are resolved again. Assignee sources explains the details.

An access function is one of four kinds of access an account can receive for a workflow family, the versions of one workflow name in one release channel:

Access functionAllows
starterStarting the workflows.
viewerReading their instances in the Business view.
operatorThe Operations view: pausing, resuming and terminating instances, reassigning dialogs, and replaying failed assignee-source refreshes (Failed work).
auditorReading their audit records in the Audit view.

Access control shows how to grant access functions.

The starter is the example project delivered with IdentityFlow. It contains sample workflows, hooks, tests and scripts to run everything locally.

The Workflow Test distribution is the test runner delivered with each release, next to the IdentityFlow program. It runs Workflow Tests against a real PostgreSQL database. It is not published on npm.

Next: Author workflows.