Change a multi-step approval
On this page you read a workflow with several approval stages in a fixed order, then add a stage and check which account may answer it. It uses the same starter and the same prepared data as Build and run a vacation approval. You do not need access to a real identity system.
Detailed behavior diagram
Run the unchanged checks first, so that a later failure can only come from your change:
npm run workshop:verify -- --tools-onlynpm run test:workflow--tools-only skips the check for running containers; the Workflow Tests start
their own database. Both commands must pass before you continue.
The workflow is src/workflows/multi-role-approval.workflow.ts, and its tests
are in tests/workflows/multi-role-approval.workflow.case.ts. One instance
handles an order: these employees should receive these roles.
- Input.
RoleOrderlistsrecipientsandroleIds, both chosen from the prepared data, a validity periodassignFromtoassignTo, and areason. - Step
pair recipients with rolesmakes one request per recipient and role. It reads the employee through theStaffDirectorybinding and the role through theRoleCatalogbinding. grantRolehandles one request. The role catalog lists the approval stages a role needs, andgrantRoleopens one dialog per stage, namedapprove <stage> for <recipient>/<role>, in the catalog’s order. The first rejection ends the chain. When every stage approves, the stepcreate role assignment <recipient>/<role>hands the assignment to theRoleAssignmentbinding.assigneesFordecides who may answer a stage. If the catalog names the approvers, the dialog uses them. If it does not, a stepresolve <stage> approver for <recipient>/<role>asks the catalog for this recipient’s approver, so the question and its answer are recorded as events.
Follow one request through its stages. For each dialog, find five things: what goes in, who gets asked, where it waits, what comes back, and which events mark the transition.
The order of the stages is plain code. grantRole calls
await flow.dialog(…) inside a for loop; await suspends until the dialog
is answered, so the next stage opens only after the previous one. IdentityFlow
0.3.0 has no special API for ordered stages. The order lives in the workflow
code, so the web application and the tests cannot disagree about it.
Where several approvals are independent, the workflow uses
Promise.allSettled. It behaves as in standard JavaScript and cancels
nothing. Read the
Promise composition
before you change that part: a rejection is a business outcome you handle,
while the engine pausing the workflow is control flow and never shows up as a
settled result.
The three tests check this behavior:
- walks the ordered chain and assigns the role;
- a rejection ends the chain and never assigns the role;
- runs two recipients side by side and lets one refusal stand alone.
Practice task 16 adds a stage. It works on role request, a version of the same
chain for one recipient and one role, in
exercises/16-approval-stage/start/role-request.workflow.ts. The task is a
new house rule: a role assignment that lasts longer than 90 days needs a
security review, SECURITY_REVIEW, after the catalog’s stages and before
create role assignment. Whoever holds role:security-review answers it.
Shorter assignments stay as they are.
-
Read
exercises/16-approval-stage/README.md. -
Run the practice task. It fails first, because the long request reaches the assignment without a security review:
Terminal window npm run exercise -- 16npm run exercise -- 16 --solutionThe second command runs the reference solution, for comparison when you are done.
-
In
grantRole, at the commentTASK 16, add a dialog after the loop over the catalog’s stages. Open it only when the assignment lasts more thanSECURITY_REVIEW_ABOVE_DAYSdays, and return a refused outcome when the review rejects. The file already providescountRequestedDaysand the constants you need. Name the dialogapprove SECURITY_REVIEW for ${key}, as the task’s README says; the tests look for that name. In a string in backticks,${key}inserts the value ofkey, likefmt.Sprintfin Go;grantRolesetskeyto<recipient>/<role>. -
Run
npm run exercise -- 16again until its three tests pass:- a long assignment gets the security review after the catalog’s stages;
- a short assignment never sees it;
- a refused security review ends the chain without an assignment.
The rule belongs in the workflow rather than in the catalog’s data: the catalog does not know it, and the position of the dialog in the code is its position in the chain.
The new dialog names its assignees as a
principal:
{ providerId: 'acme', subject: 'role:security-review' }. In the practice task’s
file this is { providerId: ACME_PROVIDER_ID, subject: SECURITY_REVIEW_ROLE }.
- The provider is the identity hook that vouches for an account, here
acme, registered insrc/hooks/acme.hooks.ts. - The subject is the name the provider reports, here the role
role:security-review.
When someone answers, IdentityFlow compares the account’s principals with the
dialog’s assignees. The provider ID must match exactly, so the same subject
under another provider is a different assignee. In the practice task’s tests, the
account that holds only role:security-review may answer the review, and the
administrator is refused.
Name the assignees on every dialog. A stage’s position in the chain does not say who may answer it.
When the approvers live in another system and can change while a dialog waits, add an assignee source, as the vacation approval does; see Assignee sources. Assign a dialog to a role shows a dialog assigned to a role, as one file with its test.