Dentolize · Jira/Confluence QA Pipeline Walkthrough
On this pageBusiness viewTechnical viewSee also

The Four Phases

This is the core of the feature. The QA pipeline is a sequence of discrete phases you dispatch by hand — prepare, test, retest, fix — with a human deciding when to move from one to the next.


Business view

Think of it like a QA lead working with a very fast, very literal junior tester:

  1. prepare — The junior reads the change, writes up a review, and drafts a long list of test cases. You get a tidy epic in Jira with one ticket per test case, and a readable page in Confluence.
  2. You review the list. You cross out the silly ones, sharpen the vague ones, add the ones they missed. Your edited list is the plan.
  3. test — The junior runs exactly the tickets that are on the board right now, clicking through the live app, and writes the result — pass or fail, with screenshots — onto each ticket.
  4. You look at the board. You leave comments: "this fail is actually expected," "retry this on mobile width," "this one's a real bug, fix it."
  5. retest — The junior re-runs anything still open and anything you commented on, treating your comments as instructions. It never changes the code.
  6. fix — For the failures you've confirmed are real bugs, the junior edits the code on the branch, redeploys, retests, and closes the ticket with a note about which commit fixed it.

The point: the machine never decides on its own that QA is "done." A person gates every transition. The phases are separate buttons precisely so a human sits between them.

Why phases instead of one button

The workflow the old version offered a full-auto option that ran review → plan → test → fix in a single unattended run. This PR removes that. Every phase is now dispatched deliberately. That is a values choice: for a system that can edit and push code to a branch, "pause and let a human look" is a feature, not a limitation.


Technical view

Where the phases are declared

The four phases are the allowed values of the single action dispatch input:

inputs:
  action:
    description: QA phase to run
    type: choice
    required: true
    default: prepare
    options:
      - prepare
      - test
      - retest
      - fix

.github/workflows/sandbox-qa.yml:33-42. Note default: prepare (:37) — the first thing you'd ever run. The old workflow defaulted to test and offered review, plan, test, fix, full-auto plus a separate scope input; all of that is gone (see What Changed).

How a phase is executed

Every phase runs the same single shell command; the phase name is just the third argument:

- name: Run QA agent (${{ inputs.action }})
  id: qa
  env:
    GITHUB_TOKEN: ${{ github.token }}
  run: |
    set -e
    /opt/homelab/sandbox/bin/sandbox-qa dentolize "$GITHUB_REF_NAME" "${{ inputs.action }}" >> "$GITHUB_OUTPUT"

.github/workflows/sandbox-qa.yml:58-64. So the branch under review is $GITHUB_REF_NAME (the workflow is dispatched on a branch ref, not a PR event), and the phase is passed straight through to the sandbox-qa binary.

Honesty note. The per-phase behavior — the Confluence write, the Jira Story creation, the Playwright runs, the PASS→Done transitions, the Bug tickets — is entirely inside /opt/homelab/sandbox/bin/sandbox-qa and the qa-agent/atlassian-qa.py it calls. Neither file is in this repository. They are homelab infrastructure. From the workflow's perspective, a phase is an opaque command that writes KEY=VALUE lines to $GITHUB_OUTPUT. What this repo can prove a phase does is only what those outputs are used for (below); everything else in this page is the documented contract, verified against the header comment (:1-26) and the PR description, not against runnable code in /work/repo.

What the workflow does with a phase's results

After the agent step, the workflow reads these outputs (all produced by sandbox-qa, defensively — each is guarded):

OutputRead atUsed for
QA_STATE_DIR:67, :117Gate for the comment + artifact steps (only run if the agent produced a state dir).
JIRA_EPIC_URL:76Linked in the PR summary comment.
CONFLUENCE_URL:77Linked in the PR summary comment.
QA_SUMMARY:78-80A file path; its contents are appended to the comment.
QA_SLUG:120Sanitized branch slug for the artifact name (branch names contain /).

prepare vs. the rest is the only place the workflow itself branches on the phase name — to choose the summary comment's call-to-action line:

action === "prepare"
  ? "Stories are ready for the QA team's review in Jira. Delete, refine or add stories on the epic, then dispatch test."
  : "Results are on the Jira tickets (comments, screenshots, statuses). Open items = tickets not in Done."

.github/workflows/sandbox-qa.yml:98-100.

Phase-by-phase contract (from the header comment)

The authoritative human-readable spec is the file header, .github/workflows/sandbox-qa.yml:1-26:

  • prepare (:7-11) — "deep code+business review + exhaustive user stories → Confluence page [PR #N] <title> — Quality Review + Jira Epic [PR #N Quality Review] with one Story per test case." Then the gate: edit the epic natively before dispatching test.
  • test (:12-16) — "Playwright-tests the epic's CURRENT Stories on the branch sandbox; per ticket: result comment + screenshots attached + transition (PASS→Done, FAIL→In Progress + label failed); Bug tickets for confirmed non-story findings." Gate: review the board, comment on tickets.
  • retest (:17-18) — "re-tests non-Done tickets and any ticket with new human comments (treated as instructions). Never commits."
  • fix (:19-20) — "fixes confirmed FAIL tickets on the PR branch ([skip ci], self-redeploys, retests); FIXED→Done with the commit ref."

The one phase that touches code: fix

fix is the reason the job requests contents: write:

permissions:
  contents: write        # fix action pushes to the PR branch
  pull-requests: write
  issues: write

.github/workflows/sandbox-qa.yml:48-51. The [skip ci] in fix commits is what stops an infinite loop: a normal push to the PR branch would trigger sandbox.yml's deploy, but [skip ci] suppresses that, and the fix phase redeploys the sandbox itself through the sandbox CLI — because the self-hosted runner has a single slot and can't run a deploy while the QA job is holding it. This constraint is documented in the header (:22-26) and mirrors the same self-redeploy pattern used across the sandbox workflows.

Concurrency & timeout

concurrency:
  group: sandbox-qa-${{ github.ref_name }}
  cancel-in-progress: false

.github/workflows/sandbox-qa.yml:44-46 — one QA phase per branch at a time, and a running phase is never cancelled by a newer dispatch (cancel-in-progress: false). A phase can take a long time: timeout-minutes: 300 (:56). It runs on the homelab runner: runs-on: [self-hosted, homelab, dentolize] (:55).


See also