Source Code Delivery (PoC)

Turn GitLab merge-request reviews into planned, measurable Jira work — from review request, through the review/approval gates, to the hand-off to integration.

Jira key · SCDX Company-managed Scrum prplfoundationcloud Status · design locked, pre-build 2026-06-02 · rev 06-03

1 · The problem

Code review for prplOS lives entirely in GitLab. A contributor opens an MR, a reviewer asks for changes, the contributor delivers, the reviewer looks again — and that round-trip repeats, often several times, with no visibility outside the MR.

Nothing is plannable: you can't put “review this delivery” into a sprint, you can't see how many cycles an MR burned, and you can't tell whether time is lost waiting on the developer or on the reviewer. One delivery routinely spans several MRs, which makes the missing delivery-level view worse.

2 · Architecture

Jira is the system of record — the plannable, auditable surface. n8n is the glue at the two edges: it fans intake out into the structure, and it syncs GitLab MR state onto the tickets. Jira Automation handles the in-Jira assignee flips.

flowchart TD
  Dev(["Developer"]) -->|fills intake Form| RR["Review Request
(intake record)"] RR -->|n8n fan-out| Epic[["Delivery — Epic"]] Epic --> CR1["Code Review · MR a"] Epic --> CR2["Code Review · MR b"] Epic --> CR3["Code Review · MR c"] Epic --> INT["Integration
(1 per delivery)"] GL{{"GitLab MR webhooks"}} -->|n8n sync| CR1 GL -->|n8n sync| CR2 GL -->|n8n sync| CR3 CR1 -.->|all Approved → rollup| INT CR2 -.-> INT CR3 -.-> INT classDef rec fill:#eef,stroke:#88a,color:#223; classDef int fill:#e8f6ee,stroke:#5aa97a,color:#143; class RR rec; class INT int;
Form → Review Request → (n8n) Epic + per-MR Code Reviews + one Integration ticket; GitLab webhooks drive the review states.
ConcernOwner
Intake fan-out (Epic + Integration + Code Review×N, enriched from GitLab API)n8n
GitLab events → Code Review transitions; rollups → Integration ready + integratedn8n
Assignee flips on every transitionJira Automation
branch → release-track + fixVersion mappingn8n config table
Project, issue types, fields, workflows, FormJira (built via REST)

3 · Issue model

Three tiers plus a per-delivery integration item. The Form can only create the task-level Review Request; n8n reads it and builds the rest.

TypeLevelCreated byPurpose
Review Requesttaskthe Formdurable record of the ask (who / what / which MRs)
Delivery (= Epic)epicn8nthe container; groups a delivery's MRs
Code Review (new type)taskn8none per MR — the review/approval workhorse
Integration (new type)taskn8none per delivery — the integration team's item

4 · Custom fields

FieldTypeOnSet byPurpose
DeveloperuserCode Reviewn8n (GitLab author)MR author; ball on Changes Requested
RevieweruserCode Reviewtriage leadfirst gate; ball on In Review
ApproveruserCode Reviewtriage leadsecond gate; ball on In Approval
IntegratoruserIntegrationintegration teamball on Ready for Integration
GitLab Projectshort textCode Reviewn8nlookup key (path)
MR IIDnumberCode Reviewn8nlookup key
MR URLURLCode Reviewn8nlink (+ remote link)
Target Branchshort textCode Reviewn8nfrom MR target branch
MR merged atdateCode Reviewn8n (merge webhook)per-MR merge fact; rolled up to set Integration → Integrated
Release TrackselectCode Reviewn8n (derived)dev / production / … — grouping & metrics
fixVersionversions (multi)Code Reviewn8n (derived)target release(s); multi-valued for backports
Blocked reasonshort textbothn8n / humanwhy an item is flagged or blocked
Flagged (native)checkbox · customfield_10021bothn8n / humanat-risk or blocked indicator (value Impediment); rolls up to Epic
Due date (native)date · duedateCode Reviewn8n (by priority)explicit deadline; complements the SLA

Contributors must have Jira accounts, so Assignee is always a real, actionable user. Role fields (Developer/Reviewer/Approver/Integrator) are created single-user — the instance's existing multi-user Reviewers/Approvers fields are the wrong cardinality for the flip.

5 · Code Review workflow

One state = one person's court, which is what the Assignee auto-flip keys off. A change request at either gate returns the ball to the Developer and re-enters via re-review.

stateDiagram-v2
  [*] --> ToReview: n8n creates
  ToReview --> InReview: triage done (Reviewer + Approver set)
  InReview --> InApproval: reviewer approves
  InReview --> ChangesRequested: reviewer requests changes
  InApproval --> Approved: approver approves
  InApproval --> ChangesRequested: approver requests changes
  ChangesRequested --> InReview: dev delivers (always re-review)
  Approved --> ChangesRequested: reopen (integration failure)
  ToReview --> Cancelled
  InReview --> Cancelled
  ChangesRequested --> Cancelled
  InApproval --> Cancelled
  Approved --> [*]
  Cancelled --> [*]
StateCategoryAssignee (ball)
To ReviewTo Dotriage lead
In ReviewIn ProgressReviewer
Changes RequestedIn ProgressDeveloper
In ApprovalIn ProgressApprover
ApprovedDone
CancelledDone
Free metrics. Review cycles = entries into Changes Requested; time-in-status = waiting-on-dev vs -reviewer vs -approver. A validator on To Review → In Review requires Reviewer and Approver to be set.

6 · Integration workflow

One Integration ticket per delivery, born with the Epic in Waiting for Review, auto-promoted when the whole delivery is approved. Same project, separate board, so the integration team plans independently.

stateDiagram-v2
  [*] --> WaitingForReview: created with the Epic
  WaitingForReview --> ReadyForIntegration: all Code Review children Approved (n8n)
  ReadyForIntegration --> Integrating: integrator starts
  Integrating --> Integrated: n8n — all delivery MRs merged in GitLab
  Integrating --> WaitingForReview: failure → reopen Code Review item(s)
  Integrated --> [*]
n8n drives the transitions. Waiting → Ready for Integration fires when every Code Review child is Approved; Integrating → Integrated fires automatically once all of the delivery's MRs are merged in GitLab (the merge webhook). Only Ready → Integrating is a human move (the integrator picking it up).
Failure path. A merge conflict or failed build/test that needs code changes reopens the affected Code Review item to Changes Requested (dev → re-review → re-approve) and drops the Integration ticket back to Waiting for Review. Rework stays on the gated, traceable path.

7 · Release tracks

Per Code Review item, multi-valued so a dev MR and its production backport coexist. n8n owns a small branch → track + version table.

TrackBranchfixVersion
developmentlatest-24.105.0.0
productionmainline-23.05 / 4.0.y4.0.x

8 · Intake form

Lightweight — per-MR detail comes from the GitLab API, not the form.

FieldMaps to
Delivery title *Request summary + Epic name
Context / description *why / what
MR URLs * (one per line)one Code Review per line
Related feature / epicoptional link
Priority / review-byoptional
Suggested reviewersoptional hint

9 · The two engines

Fan-out — n8n, on Review Request submit

Sync — n8n, GitLab → Jira

Assignee flips — Jira Automation (UI; no public REST)

RuleOn transition toAction
R1In ReviewAssignee ← Reviewer
R2Changes RequestedAssignee ← Developer
R3In ApprovalAssignee ← Approver
R4Ready for IntegrationAssignee ← Integrator
R5To Review (created)Assignee ← triage lead

Deadlines, SLA & escalation — n8n cron

No JSM = no native SLA engine, so n8n replicates it. Reaction time = age of the current status (one state = one role's court), checked against a priority × waiting-state matrix (tunable n8n config):

PriorityIn Review / Changes Req. / In ApprovalReady for Integration
Supermegacritical (S-1)4h4h
Blocker (S0)4h4h
Critical (S1)1d1d
High (S2)2d3d
Low (S3)4d5d
On breach the cron sets Flagged = Impediment + a comment, pings the Assignee on Slack/email, escalates to the triage lead, and rolls the flag up to the Epic — the early “won't land this sprint” signal. Blocked (a real external dependency) is the same Flag set by a human with a Blocked reason — not a workflow status, so the gate state and whose-court semantics stay intact.
The cron is also the reconciler. Webhooks are best-effort, so the same scheduled flow re-checks GitLab MR state for in-flight deliveries and self-heals — stamping a missed MR merged at, advancing a stuck rollup, or fixing a missed gate. No terminal state (Approved-rollup → Ready, merge-rollup → Integrated) relies on a single webhook firing.

10 · Open items & risks

11 · Build plan

  1. Phase 1 — Foundation (REST): project SCDX (gh-scrum-template) + single-user role fields + GitLab/Blocked-reason fields + versions. Reversible.
  2. Phase 2 — Process: create statuses Changes Requested / In Approval, both workflows + schemes + the To-Review→In-Review validator. ⚠ status/scheme/automation REST support uncertain — probe, fall back to UI.
  3. Phase 3 — Form (Forms REST, experimental): the intake form.
  4. Phase 4 — Glue: Assignee-flip rules R1–R5 (REST probe, else UI) + the tested n8n→Jira REST contract (fan-out incl. title writeback, sync, SLA cron).

12 · Decision log

#Decision
13-tier model: Review Request → Delivery (Epic) → Code Review (custom); + Integration (custom)
2Roles: Assignee (auto) + Developer + Reviewer + Approver, all Jira users; both Reviewer & Approver must approve
3Gates sequential: Reviewer → Approver; either change request returns ball to Developer
4 / 4a / 4b / 4cIntegration = separate ticket · per-delivery · upfront Waiting → auto-ready · same project, separate board
5CR workflow as §5; single Changes Requested state; always re-review
6Integration failure → reopen Code Review item(s)
7Assignee flips via Jira Automation
8Versions layered: fixVersion + Release Track + Target Branch
9Reviewer / Approver assigned at Jira triage
10MR capture: multi-line, one URL per line (gate-event mapping deferred)
11Lookup key: GitLab Project + MR IID
12Contributors must have Jira accounts
13Triage ball: configured triage lead
14Project key SCDX, name “Source Code Delivery (PoC)”, company-managed Scrum
15MR↔ticket link: per-MR key as MR title suffix (SCDX-…) + immutable MR IID/Project/URL fallback
16Blocked / at-risk: reuse native Flagged (Impediment) + Blocked reason field — no Blocked status
17Deadlines/SLA: n8n cron, reaction-time = status age vs priority×state matrix; sets Flag + Due date, Slack/email + escalation
18Integration → Integrated by n8n when all Code Review children merged — per-MR MR merged at stamped on the merge webhook, rolled up; cron reconciles