Skip to main content

kind: WorkflowTemplate

What it is

A WorkflowTemplate defines the state machine an issue, run, or other tracked item moves through inside a workspace. It’s the equivalent of “status options on a board”: you declare an ordered list of stages, each tagged with a type (open, started, completed, or cancelled), and the workspace UI uses that as the column layout for kanban boards plus the legal transition graph for status changes. WorkflowTemplates are workspace-scoped and idempotent on metadata.slug within a workspace. The workflow_templates DB table has no slug column, so on export the manifest synthesises a kebab-case slug from metadata.name to keep round-trips stable. Built-in templates seeded by the server (sequential, parallel, dev-test-loop, pipeline) are owned by the server and explicitly excluded from export — re-applying an exported workspace will never overwrite them and never produce drift against them.

YAML schema

Field reference

Stage types

Every stage carries one of four type tags. The tag is what the orchestrator and UI key on for behaviour — not the stage name, which is purely human-facing. Transitions are unrestricted by default: from any stage to any other stage. The state machine is structural (“which columns exist?”), not behavioural (“which arrows are legal?”).

Examples

Minimal example

The minimum legal template: one open stage and one completed stage. No started and no cancelled are fine.

Realistic example with all common fields

The ready row above intentionally illustrates a rejected shape — Validate refuses two open stages. Pick one of backlog/ready as the entry state and tag the other started.

Multi-template bundle

Multiple templates may be declared in one file; each is applied independently. There are no cross-template references.

CLI reference

The crewship apply path is the only one that resolves metadata.slug → server ID for you. The flat crewship workflow get/delete accept a slug for convenience and do the lookup themselves.

REST endpoint mapping

The template_json column stores the entire stages array as a JSON string (the column is TEXT, not JSON). The handler does not re-parse user input — it passes the marshalled string straight through to the DB, so any future stage-shape extension is forward-compatible. All paths are workspace-scoped via the JWT/workspace context. RBAC: OWNER, ADMIN, and MANAGER can create / update / delete; every authenticated role can read.

Validation rules

  • metadata.name is required (server rejects empty name with HTTP 400).
  • metadata.slug is required and must be unique within the workspace.
  • spec.stages must be a non-empty array.
  • Each spec.stages[].name must be non-empty and unique within the template.
  • Each spec.stages[].position must be unique within the template.
  • Each spec.stages[].type must be one of open, started, completed, cancelled.
  • Exactly one stage must have type=open.
  • At least one stage must have type=completed.
  • spec.color and spec.stages[].color, if set, must match ^#[0-9A-Fa-f]{6}$. Three-digit shorthand is not accepted.
Failed validation is reported per-document with the offending template slug in the error message so multi-doc bundles surface every issue in one pass.

Apply behavior

ApplyUpsert (default)

  1. List the workspace’s templates via GET /api/v1/workflow-templates.
  2. Match by metadata.name:
    • No match → Action=Create, POST /api/v1/workflow-templates with {name, description, template_json, icon, color}.
    • Match with differing fields → Action=Update, PATCH /api/v1/workflow-templates/{id} carrying the same body.
    • Match with identical fields → Action=Unchanged, no HTTP call issued.
Stages are compared after sorting by position, so reordering the stages array in the YAML file without changing positions is a no-op (Unchanged), not a drift.

ApplyStrict

Fails with a slug already exists error if any declared template already exists by name. Useful for new-workspace bootstrapping where overwriting a same-named template would be a bug.

ApplyReplace

For each declared template, emits Action=Delete followed by Action=Create. Use this only when you want a fresh row (e.g. you’ve renamed a stage and want the underlying record reset rather than mutated in place). Apply also deletes any templates in the workspace that the manifest no longer declares — but never the built-in templates, which are protected server-side.

Round-trip via export

crewship export workspace lists every non-builtin template and emits one kind: WorkflowTemplate document per row. The export decodes template_json back into the structured stages array so the output is directly re-applyable:
The synthesised slug strips non-alphanumeric runs to single dashes (Engineering Standardengineering-standard). If two templates in the same workspace share a name (rejected by the DB’s UNIQUE(workspace_id, name) index — so this should never happen in practice), their slugs would collide and crewship apply would reject the bundle. Built-in templates (sequential, parallel, dev-test-loop, pipeline, plus any others the server seeds in the future) are filtered out. The intent is that re-applying an exported workspace bundle never produces drift against server-owned rows.

See also

  • kind: Project — projects don’t reference workflow templates directly today, but a future default_workflow_slug field is planned.
  • kind: TriageRuleactions.set_status references a stage name; if you wire one up, make sure the stage exists in whichever template the matched issue lives under.
  • kind: SavedView — views can filter by stage type (type=open/type=started) to render “active work” boards.
  • SPEC-2 section 8 — authoritative contract for this kind.