Skip to content

Assigning a workflow to an (organisation, product)

Every (organisation, product) combination on the platform must have an active workflow before cases can be opened against it. Without one, clinical-api refuses case creation with 422 missing_workflow_definition (see PR #154). This runbook walks through how to provision, edit, and reassign workflows from the admin UI.

Mental model

  • A WorkflowDefinition belongs to exactly one (orgId, productId) slot and carries a version number that's unique within that slot (@@unique([orgId, productId, version])).
  • Workflows move through statuses: draftactivearchived. Only active rows satisfy the case-creation gate.
  • Reassignment (changing the (org, product)) is allowed on draft rows only. The orchestrator renumbers version to the next free value under the new slot so the unique constraint doesn't bite the operator.
  • A product-wide default workflow has orgId IS NULL for a specific product. The runtime resolver falls back to the default when no org-specific active workflow exists for the (org, product) pair the caller is hitting. Only cross-tenant admins can author or promote a draft to default (isDefault: true on create/update); a 409 fires if another active default already exists for the same product.

Where to find the status

Open Organisations → click an org → Products tab. The new Workflow column shows one of three things for each assigned product:

Cell shows Meaning Action
<workflow name> + green active badge The slot has a published workflow; case creation will succeed. Click the name to open the workflow detail page.
<workflow name> + yellow draft badge A draft exists but hasn't been published; cases for this slot will be refused. Click in to publish (or delete + start over).
Create workflow link No workflow row yet; cases for this slot will be refused. Click to deep-link into the new-workflow form with this (org, product) pre-selected.
<workflow name> + blue default badge A product-wide default workflow (orgId IS NULL) applies — the org inherits it. No action needed; case creation will succeed via the resolver fallback.

Provisioning a new workflow

  1. From Org → Products, click Create workflow in the Workflow column. The new-workflow page opens with the Organisation + Product dropdowns already filled in.
  2. Set Name, Default mode (client_driven for operator- authored step graphs, active for server-driven), and the Workflow timeout (seconds). Click Create draft.
  3. The editor opens. Build the step graph using the palette on the left.
  4. Save draft as you go. Validation runs after each save and surfaces any errors that would block publish.
  5. Publish when ready. Status transitions to active; the workflow now satisfies the case-creation gate.

The first publish unblocks case creation for the slot. Subsequent versions follow the same flow (clone or "new version" on the detail page → edit → publish), with the platform auto-archiving the previous active row.

Authoring a product-wide default

On the new-workflow form, tick Default for all organisations. The Organisation dropdown is replaced with a hint — the workflow will apply to every org that has the chosen product, until that org authors its own override. Only cross-tenant admins see / can use the checkbox; tenant clients sending is_default: true are refused with 403. Promoting an existing draft to default is also possible from the workflow-edit Assignment card; the orchestrator refuses with 409 if another active default already exists for the product (archive the incumbent first).

Editing an existing draft

On the workflow-edit page (/workflows/<id>/edit):

  • The header carries the workflow name, status badge, version, and Save/Publish buttons (unchanged).
  • The Assignment card sits below the header. It's editable while the workflow is draft and read-only afterwards.

The Assignment card lets you reassign a draft workflow to a different (org, product) without re-creating it. Common reasons:

  • Operator typo on creation. Picked the wrong org/product on the new-workflow form; move it before publish.
  • Lift a working draft to a sibling org/product. When a draft authored against one slot becomes the template you want to use elsewhere — you can also use the clone action on the detail page if you want to keep a copy under the original slot.

How the version renumber works

If you change the (org, product), the orchestrator queries the target slot for its current highest version and stamps the moved workflow with version = max + 1. The unique constraint (orgId, productId, version) therefore never trips when reassigning. Same-slot saves (same org, same product) keep the existing version unchanged.

Published / archived workflows can't be reassigned

The Assignment card switches to read-only when status is active or archived. Re-binding a live workflow would change which slot its instances belong to and break the audit chain. If you need to move a published workflow:

  1. Archive it (so case creation falls back to the missing_workflow_definition error or to a different active version).
  2. Clone it to the target slot via the detail page's clone action.
  3. Publish the clone.

Failure modes you'll hit

Symptom What it means Fix
POST /v1/clinical-api/cases422 missing_workflow_definition No active workflow for this (org, product). Create + publish one.
PATCH /workflow-definitions/:id409 Only draft definitions can be edited Trying to reassign a published / archived workflow. Archive + clone instead.
PATCH …403 Tenant clients can only assign workflows to their own org A tenant-scoped client (oauth2 client_credentials) tried to move a workflow to a different org via the API. Use a cross-tenant admin to perform the move.
Two drafts in the same slot, both with version: 1 (shouldn't be possible — unique constraint enforces it) If you see this, file a bug.

Backend / API notes

The reassignment surface lives on the existing PATCH endpoint — no new routes:

PATCH /v1/orchestrator/workflow-definitions/{id}
Content-Type: application/json
Authorization: Bearer <admin-token>

{
  "orgId":     "<new-org-id>",
  "productId": "<new-product-id>"
}

Other PATCH fields (name, description, definition, defaultMode, workflowTimeoutSeconds) continue to work alongside reassignment.

clinical-api enforces the gate via the orchestrator's internal endpoint:

GET /workflow-definitions/active?org_id=<id>&product_id=<id>
Authorization: Bearer <SERVICE_AUTH_TOKEN>

Returns { hasActive: boolean, definitionId?: string }. Used by CasesService.create() before persisting a case. The check walks the resolver fallback (org-specific → product-wide default) so a product-wide default satisfies the gate for every org with that product. See docs/audiences/tech/services/orchestrator.md for the full contract, plus the GET /workflow-definitions/resolve route external consumers use to fetch the active workflow without knowing its id.