Skip to content

Change a multi-step approval

Catalog stages per role catalog Security review only above 90 days Assignment role granted > 90 days approved
Catalog stages per role catalog Security review only above 90 days Assignment role granted > 90 days approved
Add a security review after the catalog stages, only for assignments longer than 90 days, then test both paths.

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
CATALOG STAGES, IN ORDER first stage flow.dialog next stage flow.dialog SECURITY_REVIEW role:security-review Assign role create role assignment Chain ends no role assignment approves ≤ 90 days > 90 days approves rejects rejects
CATALOG STAGES, IN ORDER first stage flow.dialog next stage flow.dialog SECURITY_REVIEW role:security-review Assign role create role assignment Chain ends no role assignment approves ≤ 90 days > 90 days approves rejects rejects
Each stage's dialog opens only after the previous one approves, the security review comes after the catalog's stages, and any rejection ends the chain.

Run the unchanged checks first, so that a later failure can only come from your change:

Terminal window
npm run workshop:verify -- --tools-only
npm 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. RoleOrder lists recipients and roleIds, both chosen from the prepared data, a validity period assignFrom to assignTo, and a reason.
  • Step pair recipients with roles makes one request per recipient and role. It reads the employee through the StaffDirectory binding and the role through the RoleCatalog binding.
  • grantRole handles one request. The role catalog lists the approval stages a role needs, and grantRole opens one dialog per stage, named approve <stage> for <recipient>/<role>, in the catalog’s order. The first rejection ends the chain. When every stage approves, the step create role assignment <recipient>/<role> hands the assignment to the RoleAssignment binding.
  • assigneesFor decides who may answer a stage. If the catalog names the approvers, the dialog uses them. If it does not, a step resolve <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.

  1. Read exercises/16-approval-stage/README.md.

  2. Run the practice task. It fails first, because the long request reaches the assignment without a security review:

    Terminal window
    npm run exercise -- 16
    npm run exercise -- 16 --solution

    The second command runs the reference solution, for comparison when you are done.

  3. In grantRole, at the comment TASK 16, add a dialog after the loop over the catalog’s stages. Open it only when the assignment lasts more than SECURITY_REVIEW_ABOVE_DAYS days, and return a refused outcome when the review rejects. The file already provides countRequestedDays and the constants you need. Name the dialog approve 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 of key, like fmt.Sprintf in Go; grantRole sets key to <recipient>/<role>.

  4. Run npm run exercise -- 16 again 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 in src/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.

Next: Run the starter’s tests in CI.