Delivery¶
The Delivery stage of the product process (DPPD) is where an agreed Plan becomes shipped, operated software. This page is the canonical description of how Delivery runs day to day:
- the Jira statuses a piece of work moves through, what each means, and who moves it,
- the automations that keep Jira in sync with GitHub and the deploy pipeline,
- how the Delivery backlog is organised and prioritised.
It ties together the mechanics documented elsewhere on this site: PR reviews, Branching & releases, and Epic (feature) branches.
One ticket per change
No code lands without a PPL ticket in the PPL project on percihealth.atlassian.net. The ticket key drives the branch name and PR title (see PR reviews) and links the work back to its rationale. Most tickets originate from the Delivery stage of DPPD: a Plan becomes epics and tickets that then move through the statuses below.
Source of truth: live Jira workflow
The statuses and transitions on this page were mapped directly from the live PPL Jira workflow on 19 June 2026. Jira is the source of truth; if the workflow changes, update this page in the same change (see the tip at the bottom of the Engineering index).
Work item types¶
The PPL project uses this issue hierarchy. The Frontend/Backend split is what lets an epic land the backend first and the frontend after it (see Epic branches).
| Level | Types |
|---|---|
| Epic (L1) | Epic |
| Standard (L0) | Story, Task, Bug, Spike, Frontend, Backend |
| Sub-task (L-1) | Sub-task, Design, Frontend Subtask, Backend Subtask |
Status flow¶
flowchart TD
start((Start)) -->|Create| open["Open<br/>(To Do)"]
planning["Ready for Planning<br/>(To Do)"] -->|Ready for work| open
open -->|Back to planning| planning
open -->|Start work| wip["Working on it"]
wip -->|Ready for code review| review["Ready for review"]
review -->|Changes requested| wip
review -->|Ready to test| rft["Ready for Testing"]
rft -->|Start testing| intest["in Test"]
intest -->|Test passed| merge["Ready to Merge"]
intest -->|Bugs found| wip
merge -->|"Merge to Staging ⭐"| staging["On Staging"]
staging -->|Regression passed| release["Ready for Release"]
release -->|Released to prod| done["Done"]
wip -->|No testing needed| done
blocked["Blocked"] -->|Work unblocked| wip
blocked -->|Test unblocked| intest
classDef terminal fill:#d4edda,stroke:#28a745,color:#000;
classDef todo fill:#e2e3e5,stroke:#6c757d,color:#000;
class done terminal;
class planning,open todo;
⭐ = automated transition (see Automations).
Five statuses are global, reachable from any status (the Any badge in Jira): Open, Ready for Testing, On Staging, Blocked and Closed. New tickets are created in Open. Blocked leaves again via Work unblocked (to Working on it) or Test unblocked (to in Test).
Statuses¶
Each status maps to one of Jira's three categories: To Do (blue-grey), In Progress (yellow) and Done (green).
Ownership confirmed with the team
The Moved on by column is who actually performs each forward transition, confirmed with the team on 19 Jun 2026.
| Status | Category | Meaning | Moved on by |
|---|---|---|---|
| Ready for Planning | To Do | A ticket that needs (more) planning before it can be picked up. New tickets are created in Open; Back to planning sends one here and Ready for work returns it to Open. | Product (Ready for work back to Open) |
| Open | To Do | The Delivery backlog: created and ready to be picked up. | Engineer or Designer (Start work; depends on the ticket type) |
| Working on it | In Progress | Being implemented. The engineer (or designer) creates a branch in the relevant project, e.g. PPL-121/change-auth. |
Assignee |
| Ready for review | In Progress | A PR is raised and ready for code review by another engineer. | Engineer, once review passes |
| Ready for Testing | In Progress | Review has passed; the change is handed to QA. | QA |
| in Test | In Progress | QA is actively testing the change on the feature-branch preview (see Testing). | QA: Test passed to Ready to Merge, or Bugs found back to Working on it |
| Ready to Merge | In Progress | Tested and approved. The ticket waits here for the engineer to merge the PR into develop; the merge then auto-moves it to On Staging (see Automations). |
Engineer (merges the PR) |
| On Staging | In Progress | Merged to develop. A release branch is cut and QA runs regression against it. |
QA, once regression passes |
| Ready for Release | In Progress | Regression passed on the release branch; waiting to be released to production. | Release manager (Celina) |
| Done | Done | Released to production. | n/a (terminal) |
| Blocked | In Progress | Work cannot proceed. Reachable from any status; returns via Work unblocked (to Working on it) or Test unblocked (to in Test). | Anyone |
| Closed | Done | Terminal state for cancelled / won't-do work (distinct from Done); not used much in practice. | Anyone |
CONFIRM: status casing
Two status names carry inconsistent casing in Jira: Ready for review (lower-case
"review") and in Test (lower-case "in"). They are written here exactly as
configured. Worth tidying in Jira for consistency; flag if you want that done.
Transitions¶
The workflow uses named transitions (the buttons you click in Jira). The transition name differs from the destination status, and the names are what automations hook onto, so they are listed explicitly here.
| Transition | From → To | What it means |
|---|---|---|
| Create | (start) → Open | A new ticket is created directly in Open. |
| Ready for work | Ready for Planning → Open | Planning done; ready to be picked up. |
| Back to planning | Open → Ready for Planning | The ticket needs (more) grooming before work starts. |
| Start work | Open → Working on it | Engineer (or designer) begins implementation. |
| Ready for code review | Working on it → Ready for review | PR raised, ready for review. |
| Changes requested | Ready for review → Working on it | Reviewer asked for changes; back to the author. |
| Ready to test | Ready for review → Ready for Testing | Review passed; handed to QA. |
| Start testing | Ready for Testing → in Test | QA begins testing. |
| Test passed | in Test → Ready to Merge | QA testing passed. |
| Bugs found | in Test → Working on it | QA found bugs; back to the author. |
| Merge to Staging | Ready to Merge → On Staging | The engineer merges the PR into develop; the automation performs this transition (see Automations). |
| Regression passed | On Staging → Ready for Release | Regression on the release branch passed; queued for release. |
| Released to prod | Ready for Release → Done | Change released to production. |
| No testing needed | Working on it → Done | Fast-path for non-code / admin tickets only (docs, ops). Code must not skip review / merge / release. |
| Work unblocked | Blocked → Working on it | Unblocked; resume implementation. |
| Test unblocked | Blocked → in Test | Unblocked; resume testing. |
| Open / Ready for Testing / On Staging / Blocked / Closed | (global) → that status | Global transitions, reachable from any status (the Any badge). Closed is the cancelled / won't-do terminal. |
Merging into develop is automated
A ticket sits in Ready to Merge until the engineer merges its PR into develop. That
merge triggers the automation that moves it to On Staging and stamps the fix
version (see Automations). Releases are cut from develop, see
Branching & releases.
Notable paths¶
- Fast-path (
No testing needed). For non-code / admin tickets only (docs, ops, config that does not ship as a code release) a ticket may go straight from Working on it to Done. Code changes must not use it:Donemeans released to production, so code still goes through review, merge and release even when QA adds little. - Review rework (
Changes requested). A PR that needs changes goes from Ready for review back to Working on it, so review and rework iterate without losing the ticket's place. - Test rework (
Bugs found). If QA finds bugs, in Test goes back to Working on it rather than forward. - Re-planning (
Back to planning). A ticket in Open that needs more grooming goes back to Ready for Planning; Ready for work returns it to Open.
Definition of Done¶
A code ticket is Done only when the change is released to production and:
- the PR is reviewed and approved by another engineer (see PR reviews);
- comprehensive tests ship with the change: unit / widget coverage and an automated
end-to-end test that captures the user story (the flow that proves the feature works), so it
joins the regression suite. The CI gate must be green (coverage non-regression,
analyzeclean); - it was verified in the feature-branch preview during in Test, and regression passed on staging;
- the acceptance criteria are met;
- any documented behaviour or process it changes is updated in the same PR (treat a missing doc update like a missing test);
- the ticket carries the fix version of the release it shipped in.
Non-code / admin tickets (the No testing needed fast-path) are Done when the work itself is
complete; the code-specific items above do not apply.
Automations¶
Delivery transitions are kept in sync with GitHub by Automation for Jira flows (Jira's native automation), scoped to the Perci Platform project. Documented flows:
Pull request merged to develop -> On Staging¶
| Field | Value |
|---|---|
| Trigger | Pull request merged |
| Conditions | {{pullRequest.destinationBranch}} equals develop, and {{pullRequest.sourceBranch}} contains {{issue.key}} |
| Actions | 1. Transition the work item to On Staging. 2. Edit work item fields: set Fix versions to Next. |
| Owner / actor | Luke Dixon / Automation for Jira |
| On error | E-mail the owner once when the flow starts failing after success. |
When the PR for a ticket (its source branch contains the ticket key) is merged into
develop, the ticket moves from Ready to Merge to On Staging and its fix version
is set automatically. Merging the PR is the engineer's action; the status change and the fix
version are handled for you. (The underlying On Staging transition is global, so it also
applies if the ticket was in another status when the PR merged.)
Fix version lifecycle¶
The automation always stamps the placeholder version Next, never a real version
number. The release itself is cut by a GitHub workflow that takes the release number
manually; that workflow operates only on GitHub (it creates the release branch from
develop) and does not touch Jira. Re-stamping every Next ticket to the actual
release version (for example 1.2.3) is therefore a manual step in Jira at release time.
That re-stamp is what ties each shipped change to the release it went out in. See
Branching & releases.
Manual gap, and other flows
The Next -> real-version re-stamp in Jira is manual today: the GitHub cut-release
workflow does not update Jira, so someone updates the Next tickets by hand at release
time. It is an obvious candidate for automation. This is also the only Jira automation
captured so far; other transitions (for example Released to prod -> Done on a
production deploy) are manual unless a flow is added.
Build these next (priority order)¶
The live flow proves GitHub PR events can drive Jira transitions and field edits via smart values, so these are the natural next steps. Build top-down; the first two remove the most manual toil.
| # | Automation | Removes |
|---|---|---|
| 1 | Re-stamp fix version at release cut | the manual Next -> version sweep |
| 2 | Released to prod -> Done | dragging every shipped ticket to Done |
| 3 | PR opened -> Ready for review | a manual drag per PR |
| 4 | Review approved -> Ready for Testing | a manual drag per approval |
| 5 | Changes requested -> Working on it | tickets silently stuck in review |
| 6 | Start work on first push (+ assign) | unassigned, stale-Open tickets |
Specs¶
Each is an Automation for Jira rule (same engine as the live flow). {{...}} are smart
values; always guard on the current issue status so a rule never drags a ticket backwards.
1. Re-stamp fix version at release cut
- Trigger: Branch created.
- If: branch name matches
release/(.*); capture the version from{{branch.name.match("release/(.*)")}}. - Then: for each issue in JQL
project = PPL AND fixVersion = "Next", create the version if needed and set Fix versions to the captured version. - Alternative (preferred): do it in the cut-release GitHub workflow, which already knows
X.Y.Z, by calling the Jira REST API. Pick one, not both.
2. Released to prod -> Done
- Trigger: Pull request merged.
- If:
{{pullRequest.destinationBranch}}equalsmainand{{pullRequest.sourceBranch}}matchesrelease/(.*)(also allowhotfix/*); capture the version. - Then: for each issue in JQL
project = PPL AND fixVersion = "<captured>" AND status = "Ready for Release", run the Released to prod transition to Done.
3. PR opened -> Ready for review
- Trigger: Pull request created.
- If:
{{pullRequest.sourceBranch}}contains{{issue.key}}, the PR is not a draft, and the issue status is Working on it. - Then: run the Ready for code review transition to Ready for review.
- Note: PRs may target the epic branch, not
develop, so do not condition on the destination branch here.
4. Review approved -> Ready for Testing
- Trigger: Pull request reviewed, review state = approved.
- If:
{{pullRequest.sourceBranch}}contains{{issue.key}}and the issue status is Ready for review. - Then: run the Ready to test transition to Ready for Testing.
5. Changes requested -> Working on it
- Trigger: Pull request reviewed, review state = changes requested.
- If:
{{pullRequest.sourceBranch}}contains{{issue.key}}and the issue status is Ready for review. - Then: run the Changes requested transition to Working on it.
6. Start work on first push (+ assign)
- Trigger: Commit created (or Branch created).
- If: the commit message / branch name contains
{{issue.key}}and the issue status is Open. - Then: run the Start work transition to Working on it and set Assignee to the commit author (needs linked GitHub / Jira identities).
Feasibility
The merge-based flow already in place shows the GitHub integration works. The review-state triggers (3-5) need the pull request reviewed event in Automation for Jira's GitHub integration; if it is not available, drive them from a small GitHub Action calling the Jira REST API. The release-side rules (1-2) are most reliable run from the cut-release / release-merge GitHub workflows, which already know the version.
Backlog¶
The backlog is a single ranked, groomed queue, not an archive. You finish a ticket, go to the backlog, and take the next one off the top. We run it Kanban-style: continuous pull, no sprints.
What lives where¶
- Ready for Planning (the Kanban backlog): still being groomed, off the active board.
- Open (the To Do column): groomed and ready to pull; the top is next.
- Closed: stale or won't-do; a terminal state in the Done / Released column.
A ticket is ready (belongs in Open) when it has a clear description, an Epic, a Component, a Priority, and any blockers linked. Otherwise it stays in Ready for Planning.
Order is priority¶
The ready queue is rank-ordered (drag order): the top of To Do (Open) is what to
pick next, and the Priority field drives that order. Don't scan the list and cherry-pick;
take the highest-ranked ticket you are eligible for.
Area: the Component field¶
Every ticket carries a Component marking the part of the platform it touches. Because the backend and all the frontends share one board, the Component is what makes "which ticket is for me?" obvious. The components are:
| Component | Area | Mostly picked up by |
|---|---|---|
| Member App | The perci-platform-members Flutter app |
Frontend |
| Clinical App | The perci-platform-clinicians Flutter app |
Frontend |
| Website | The brochure site (percihealth.com) |
Frontend |
| Backend | Functions, BFFs, Medplum bots, and the web-professionals portal |
Backend |
| Infrastructure | CI/CD, deploys and platform plumbing | Backend / platform |
| Admin | Cross-cutting admin and ops (e.g. documentation) | Any |
Component is used rather than the issue type because it applies to every issue type: a
Bug or Spike belongs to an area just as much as a Story does. Board quick filters
(one per component, plus Mine and Unassigned) and Epic swimlanes key off Component, so
"what is the next Backend (or Member App) ticket I can pick up?" is one click.
Admin is the catch-all
Admin covers cross-cutting and internal work that is not tied to a specific app (for
example documentation), so anyone may pick it up. The web-professionals React portal is
treated as Backend.
The pick rule¶
When you finish a ticket, take the next one in this order:
- Stay on your epic. The top-ranked ready ticket of your component(s) in the epic you are currently working. This keeps you in context.
- Otherwise move epic. If your component(s) have nothing ready in that epic, take the top-ranked ready ticket of your component(s) in the highest-ranked epic that still needs it.
- Rank breaks ties.
Epics are not formally assigned to people; this is a soft "prefer your current epic" rule, to cut context switching without locking anyone to an epic.
Frontend / backend ordering within an epic¶
Backend usually lands before the app work that depends on it (see Epic branches). So within an epic:
- Rank the Backend ticket above the app ticket (Member App / Clinical App, etc.) that needs it.
- Link the app ticket is blocked by the Backend ticket. A blocked ticket is not ready, so it will not be picked until its backend dependency is done.
Board setup¶
The PPL board is a Kanban board with the backlog enabled. Configure it under Board settings so the queue and the areas are obvious:
- Backlog on. The Kanban backlog is enabled and holds
Ready for Planning(grooming);Opensits in the To Do column as the ready-to-pull queue, rank-ordered (top is next). - Quick filters (one click to your work):
Member App:component = "Member App"Clinical App:component = "Clinical App"Website:component = WebsiteBackend:component = BackendInfrastructure:component = InfrastructureAdmin:component = AdminFrontend:component in ("Member App", "Clinical App", Website)Backend / platform:component in (Backend, Infrastructure)Mine:assignee = currentUser();Unassigned:assignee is EMPTY
- Swimlanes by Epic, so each epic is a row. With a component quick filter you see "the next Member App ticket in epic X" at a glance.
- Card layout and colours. Show Components, Priority and Epic on the card, and colour cards by Component so the areas pop.
- Columns (one per status, as on the Scrumban board): Kanban backlog =
Ready for Planning; To Do =Open; In Progress =Working on it; Blocked =Blocked; Ready for Review =Ready for review; Ready to Test =Ready for Testing; In Test / Review =in Test; Ready to Merge =Ready to Merge; On Staging =On Staging; Ready for Release =Ready for Release; Done / Released =Done+Closed.
Keeping the backlog healthy¶
- One-time clean-up. Triage the current pile: Closed for stale or irrelevant, Ready for Planning for the rest, then add Component / Epic / Priority and rank whatever survives into Open.
- Weekly refinement. Product and the leads keep the top couple of weeks of the backlog groomed and ranked; below that can stay rough.
- Stale guard. A saved filter flags stale
Opentickets for re-triage so the queue does not silt up again:project = PPL AND status = Open AND updated <= -90d ORDER BY updated ASC(optionally a Jira automation comments on or labels them).
Relation to DPPD¶
The backlog is fed by the Plan stage of DPPD: a Plan becomes an Epic plus its tickets, which enter the backlog (in Ready for Planning until groomed, then Open). Prioritising across epics is a Product / Plan decision; the backlog rank reflects it.
Proposed: leaner status model¶
Proposal, not yet applied
The statuses above are the live workflow (the source of truth). This section is a proposed improvement to discuss; nothing here is in Jira yet.
Why. Six in-progress queue states after coding (Ready for review -> Ready for Testing ->
in Test -> Ready to Merge -> On Staging -> Ready for Release) inflate cycle time, each is
a manual drag, and two names are inconsistently cased.
Proposed active states: Ready for Planning, Open, In Progress, In Review, In QA, On Staging, Ready for Release, Done (plus Blocked, Closed).
| Current | Proposed | Change |
|---|---|---|
Working on it |
In Progress | rename: matches the board column, drops the colloquial name |
Ready for review |
In Review | rename + fix casing |
Ready for Testing + in Test |
In QA | merge two queue states into one (keep both only if the QA queue is genuinely long, which is itself worth fixing) |
Ready to Merge |
(removed) | enable PR auto-merge on approved + green instead of a manual hand-off state |
On Staging |
On Staging |
keep (or rename In Regression for an action-style name) |
Net 12 -> ~9 statuses: fewer manual moves and honest cycle-time. If you keep the current
shape, at minimum fix the casing (Ready for Review, In Test) and rename
Working on it -> In Progress, then update the automations and board columns to match.
Related¶
- Product process (DPPD): how an idea becomes Delivery-ready work.
- PR reviews: branch/PR naming, review and merge rules.
- Branching & releases: the
develop->release->mainflow. - Epic (feature) branches: long-lived feature branches per epic.