← Lab

One review, three tiers: a feature delivery workflow that scales its own overhead

18 min read ·

Every engineering organisation past a certain size ends up with the same list. A story arrives from the business. An architect has to say yes. A technical design gets written. If the design needs a new piece of infrastructure, someone has to build it. If it adds an endpoint or a Kafka message, someone has to approve the contract. Then the developers need time to read the design and ask questions, and only then does development start.

Nothing on that list is wrong. What goes wrong is the shape: five approvals in a row, each one a queue, each queue owned by someone who is also busy. A two-week feature becomes a six-week feature, and nobody can point at the fortnight that was wasted because it was spent in four different inboxes.

The workflow below keeps every check and rearranges when it happens. Three ideas carry it: the amount of process scales with the risk of the change, there is exactly one human review, and infrastructure work is requested and approved early enough that it never blocks the feature. None of it is original. Google, Uber, Amazon, Stripe and Spotify have each published the piece they discovered; the sources are at the end.

The whole workflow is also available as a slide deck: Feature delivery workflow (PDF) — the swimlane, the tiers and every block, in a format you can take to a team.


The workflow as a swimlane

Time flows downward. Each column is a team. Anything on the same row happens at the same time.

Product / BUArchitectsDev teamPlatform
IntakeProblem statementTriage & tier
DesignDesign docInfra ticket · raised + approved
ReviewArchitecture review · + API ownersContract PR · spec + lintingInfra build · after arch review
ReadyRefinement · pre-read firstInfra ready · or scheduled
BuildDevelopment · flags, CI/CD

Two things to notice before the detail. The Review row is where three of the original five approvals — the architect’s OK, the API and Kafka contract approval, and the design sign-off — collapse into one moment. And the Platform column starts during Design, not after it: the infra ticket is raised and approved while the design is still being written, so the platform team can start building the day the review closes instead of the day someone remembers to ask.

Small changes skip the Design and Review rows altogether. Which changes count as small is the job of the tier.


What a tier is

A tier is the risk class of a change, decided at triage and recorded on the ticket. It answers two questions: how far does the change reach — who else is affected if it is wrong — and how hard is it to undo. The answer decides which rows of the workflow apply and how heavy each artifact has to be. It describes the change, not the engineer: a risk level, not a permission level.

Tier 1 · LocalTier 2 · ContractTier 3 · Structural
Blast radiusYour service onlyOther teams’ consumersThe shape of the system
ReversibilityRevert the PRNeeds a deprecationMigration or rebuild
TriggerNo new interfaceNew or changed API, event, schema, integrationNew service, datastore, infra component, cross-team dependency; auth, money or PII
ArtifactThe ticket1–2 page design doc + contract PRFull design doc + ADR
ReviewersCode reviewAPI or domain ownerArchitect + every affected team + platform
Review modeIn the PRAsync comments, 1–3 daysAsync comments, 3–5 days, then one live session
Rows usedIntake → Ready → BuildAll rows, light artifactsAll rows, full artifacts

The triage itself is six yes/no questions, answered by the engineering lead and the product owner in under five minutes:

  1. Does it add or change an API endpoint, event or schema that anyone outside the team consumes? → at least tier 2
  2. Does it add a service, datastore, queue or infrastructure component? → tier 3
  3. Does it create a dependency on another team’s delivery? → tier 3
  4. Does it touch authentication, payments or personal data? → tier 3
  5. Is it a breaking change to anything existing? → tier 3
  6. Would rolling it back take more than reverting a deploy? → tier 3

None triggered means tier 1. The highest tier triggered wins.

Two rules keep this honest. Any engineer can escalate a tier without justification; nobody downgrades one without the architect. And a change that grows during implementation gets re-tiered, not waved through — the tier 1 ticket that “just needed one more endpoint” halfway through is the classic way the system gets gamed, and re-tiering is how it gets caught.

Some examples, because the boundaries are where the argument happens. Adding a discount_reason column to a table your service owns is tier 1. Adding an optional field to an existing Kafka message is tier 2. Removing a field from that same message is tier 3, even though the diff is smaller — reversibility is what matters, not lines changed. Integrating an email-delivery SDK into an existing service is tier 2, and jumps to tier 3 the moment the SDK sees personal data.


The blocks, one by one

Each block below has an owner, a place in the timeline, a rough time-box and exit criteria. The example running through all of them is a single tier 3 change: refunds issued from the support tool appear in Stripe but not in the ledger, and finance needs them reconciled within an hour.

1. Problem statement

Product / BU · Intake · half a page, one or two days

The business writes what needs to be true, not how to build it: the problem, who has it, the constraints and the metric that will say it is solved. This is Amazon’s working-backwards idea at its lightest — a one-pager, not a requirements document. An endpoint name in the story is a smell; it means someone has already designed the solution without the people who will build it.

Exit criteria: problem, affected users and success metric written down; constraints named (deadline, regulation, systems that must not change); engineering lead and product owner have both read it.

problem   : Finance cannot reconcile refunds issued from support tools;
            they appear in Stripe but not in our ledger within 24 h.
users     : finance ops (3 people), weekly close
metric    : 100 % of refunds visible in ledger < 1 h
constraint: no change to the support tool UI before Q4

2. Triage and tier

Engineering lead + product owner · Intake · five minutes per ticket

The checklist above, run once, result recorded on the ticket in a required field. Tier 1 goes straight to the refinement queue. Tier 2 and 3 get a named design owner — the engineer who will build it — and a design doc created from the template and linked.

Q1 new/changed API, event or schema?   yes  → ≥ tier 2
Q2 new service, datastore, queue, infra? yes → tier 3
Q3 dependency on another team?          no
Q4 auth / payments / PII?               yes  → tier 3
Q5 breaking change to anything?         no
result: TIER 3  (refund events + ledger sync)

3. Design doc

The engineer who will build it · Design · tier 2: 1–2 pages in a day or two; tier 3: 5–10 pages, up to a week

This is where trade-offs get written down before code exists, and it is written by the builder, not the architect. The architect is a required reviewer; that single change turns one architect into a multiplier instead of a bottleneck, and gives the team ownership of the decision they will live with.

The structure is the one Google engineers have used for years: context and scope, goals and explicit non-goals, the design, alternatives considered, cross-cutting concerns (security, privacy, observability), rollout and rollback. One section matters more than the others for this workflow — dependencies. Everything the design needs from outside the team is listed there, and two things are spawned from it: the infra ticket and the contract PR.

Exit criteria: at least one alternative rejected with a reason; contracts sketched (endpoints, topics, fields, compatibility); dependencies section filled; rollback described.

# Refund ledger sync — design doc (tier 3)
goals       : ledger sees every refund < 1 h; idempotent replay
non-goals   : changing the support tool; historical backfill
design      : consume payment.refunded → ledger-sync service → ledger DB
alternatives: (a) nightly batch from Stripe export — rejected: 24 h lag
dependencies: NEW Kafka topic payment.refunded; NEW Redis for dedupe

4. Infra ticket

Design owner raises it, platform team owns it · Design · ten minutes to raise; approved or rejected within two days

Anything the design needs that the team cannot provision through the self-service golden path becomes a separate ticket on the platform team’s board, linked to the epic with an is blocked by relation. It is raised the moment the design names the component, while the doc is still a draft.

For tier 2 and 3 the ticket has to be approved before any work starts: the platform lead confirms it is the right component and sizing, the architect confirms it fits the target architecture. This is a two-day gate on the ticket itself, not a meeting. Its purpose is to let the platform team start the moment the design review closes, with nothing left to ask for.

If the golden path already covers the need — a new topic, a standard database — no ticket is needed at all, which is the point of having golden paths.

PLAT-412  Provision Redis (dedupe cache) for ledger-sync
blocks    : LEDG-88 (refund ledger sync)
needed by : 2026-09-15 (refinement scheduled 09-12)
sizing    : ~2 M keys/day, 48 h TTL, single region
approval  : [x] platform lead 09-02   [x] architect 09-02  → execution allowed

5. Architecture review

Architect + owners of every affected API or topic · Review · three to five working days, asynchronous

The one human review. The reviewers are named at the top of the doc with a checkbox each — Stripe calls these gavel blocks. They comment asynchronously; the author resolves threads in the doc, not in direct messages; a live session is booked only for what is still open at the deadline. Approval is a signature in the doc.

The decision is recorded as an ADR — context, decision, consequences, status — so the same argument is not re-run in a year by people who were not there. Contract changes are approved explicitly by their owners, not implicitly by the architect.

reviewers : [x] architect  [x] owner payment.* topics  [x] platform lead
            [ ] owner ledger API  ← blocking: field naming
deadline  : 2026-09-05 (opened 09-01)
ADR-0031  : Use Kafka consumer + Redis dedupe over nightly batch

6. Contract PR

Design owner opens it, API or topic owner approves · Review · same window as the review

New or changed contracts are code and get reviewed as code. An OpenAPI change is a pull request to the API catalog; a Kafka message is a pull request to the schema registry. Linting — Spectral for OpenAPI, the registry’s compatibility check for Avro or JSON Schema — rejects the mechanical problems before a human looks: naming style, undocumented fields, breaking changes. The owner reviews only semantics.

This is the automated half of “one human review”. The people at Stripe who ran a centralised API review for years have written about it becoming a friction point at scale; pushing style and compatibility into tooling is the answer to that.

Exit criteria: spec or schema lives in the catalog or registry, not only in the doc; lint and compatibility green in CI; owner approved; consumers listed and notified.

PR #2210  schema-registry: add payment.refunded (v1)
compat    : BACKWARD  ✓  (new topic)
lint      : ✓ field names snake_case, all fields documented
consumers : ledger-sync (new), finance-reporting (informed)

7. Infra build

Platform team · the moment the architecture review closes

With the ticket already approved, the platform team starts building the day the review closes and works alongside refinement and development. They are not on the critical path, because the request and its approval happened during Design — unless the component is new to the organisation, in which case it gets its own tier 3 treatment on the platform side.

The best long-term outcome of this block is a new golden-path template, so the next team never needs a ticket.

Exit criteria: non-production instance available; production date committed; runbook and dashboards exist; if new to the org, added to the golden path or explicitly marked one-off.

8. Technical refinement

Whole dev team, facilitated by the design owner · Ready · pre-read two days before; session 60–90 minutes

Everyone who will build reads the approved design first. The session is not a presentation. Questions go into the doc as comments beforehand; the meeting opens with ten minutes of silent reading for anyone who did not, then works only the open threads. Output: tasks that each fit in a couple of days, estimates, spikes for anything nobody can estimate, acceptance criteria agreed with the product owner. Tier 1 work enters here directly from triage.

refinement LEDG-88 · 2026-09-12 · pre-read from 09-10
open threads: 2 (retry policy, replay window) → resolved in session
tasks : consumer skeleton · dedupe layer · ledger write · replay CLI
        dashboards + alerts · feature flag · load test
spike : Redis eviction under 2 M keys/day (1 day)
estimate: 11 days, 2 engineers

9. Infra ready and the Definition of Ready

Platform team confirms, engineering lead gates · the last gate before Build · a checklist, not a meeting

Ready is a state with an objective definition. The epic moves to Build only when every dependency is delivered or has a committed date the team can plan around, the design is approved, contracts are merged and refinement is done. Jira enforces it: a tier 3 ticket cannot enter Ready with an open is blocked by link.

This block exists to stop the team discovering in week three that the Redis cluster was never ordered. A committed date counts because a feature flag lets development start before production infra exists.

Definition of Ready — LEDG-88 (tier 3)
[x] design approved       [x] ADR-0031 linked
[x] contract PR merged    [x] refinement 09-12 done
[x] PLAT-412 approved; staging 09-24, prod 09-25 committed
[x] feature flag: ledger_sync_enabled (off by default)

10. Development and the Definition of Done

Dev team · Build · as estimated in refinement

Trunk-based development behind a flag, CI on every change, continuous deploy to staging. The design doc stays alive during build: when reality changes the design, the doc and the ADR are updated before the change merges. Done means shipped and observable, not merged — dashboards, alerts, runbook, contract tests against the merged spec, and the success metric from the problem statement checked once the flag is on.

Definition of Done — LEDG-88
[x] merged behind ledger_sync_enabled   [x] contract tests vs schema v1
[x] dashboard: lag p95, dedupe hits     [x] alert: lag > 30 min
[x] runbook: replay procedure           [x] ADR-0031 amended (retry policy)
[x] flag on 2026-09-29; metric: 100 % refunds in ledger, p95 lag 4 min

The tier 3 example on a calendar

The same change, laid on real dates. Working days; five weeks from 31 August.

LaneWk 1 · 31 AugWk 2 · 7 SepWk 3 · 14 SepWk 4 · 21 SepWk 5 · 28 Sep
Product / BUProblem statement (2 d)Flag on (29 Sep)
ArchitectsArch review, async (5 d)
Dev teamTriage → tier 3 · Design doc (5 d)Contract PR (5 d)Refinement (2 d) · Development startsDevelopment (behind flag)Development → done
PlatformInfra ticket → approved (2 d)Infra build starts 14 SepStaging 24 Sep · Prod 25 Sep

Twenty-five working days from problem statement to flag on. The detail worth staring at is the Platform lane: the ticket was raised and approved in week 1, so on 14 September — the day the review closed — the platform team started with nothing further to ask. Staging Redis exists on 24 September while development is already two weeks in behind the flag, and production is scheduled before the flag goes on. In the sequential version of this workflow, Redis gets requested after refinement, around 16 September, and the same feature ships in the second week of October.

The same workflow at tier 1 and tier 2

Most work is not tier 3, and the workflow should feel light for it.

Tier 1 — add discount_reason to the internal admin form. Problem statement: finance needs to know why a discount was given. Triage: no new interface, own table, no new personal data. Refinement: one task, acceptance criteria agreed, into the sprint. Development: migration, form field, audit log entry. Three days; no design doc, no review, no contract PR, no infra.

Tier 2 — expose GET /orders/{id}/invoices. Triage: new endpoint on an existing service, consumed by the portal team. Design doc: one page with the OpenAPI snippet, the pagination choice and the rollout. Contract PR to the API catalog: Spectral green, owner approves the next day. No architect, no ADR, no infra ticket. Refinement and two tasks with contract tests against the merged spec. Seven days.

If the distribution across a quarter lands far from roughly 60–70 % tier 1, 20–30 % tier 2, 5–10 % tier 3, either the triage criteria are too strict or the architecture genuinely makes everything structural. Both are worth knowing; only one is a process problem.


Making the tool enforce it

A workflow that lives in people’s heads decays in a quarter. This one is a state machine in Jira with exit criteria per transition.

RuleBehaviour
Custom fieldTier (1 / 2 / 3), required to leave Intake. Set at triage by the engineering lead and product owner.
Tier 1 shortcutIntake → Ready allowed directly. Design and In review are hidden for tier 1 tickets.
Tier 2 gateIn review → Ready requires a linked pull request (the contract PR) in state Merged.
Tier 3 gateIn review → Ready requires a design-doc link with status Approved, an ADR link, and no open is blocked by issue on any board.
Platform linkInfra requests are separate tickets on the platform board, linked blocks → epic, so the dependency is visible on both boards.
Infra approvalA platform ticket linked to a tier 2–3 epic cannot leave To do until the platform lead and the architect are both recorded as approvers.
Re-tieringChanging Tier after Design logs a comment and notifies the architect; downgrades need their approval.

Knowing whether it works

Measured per tier and per stage, reviewed monthly. The purpose is to find where work actually waits, and it is almost always a review queue.

Three honest caveats. The most common failure is an architect who has been burned before insisting everything is tier 2 or 3; incident history by tier is the data that settles that, and the metrics exist partly to have it. The second is teams under-tiering to skip the doc; the escalation rule and the re-tier metric catch it. The third is treating the tier as a rank — senior engineers write design docs for tier 3 work like everyone else, because the doc is for the team that will maintain the thing, not for the author.


Where it comes from

None of the above is invented here. Google’s design-doc practice supplies the document structure and the rule that a doc is worth writing only when the solution is ambiguous. Uber’s RFC process, as documented by Gergely Orosz, is the direct ancestor of the tiers: a flat RFC process broke past two thousand engineers, and the fix was lightweight templates for team-scoped changes, heavyweight ones for company-wide impact, and formal review reserved for the most critical tier. Amazon’s one-way and two-way doors are the reversibility test. Stripe’s API review is the contract PR, and its later critics are the reason the mechanical part is automated. Michael Nygard’s 2011 article is the ADR. Spotify’s golden paths are why the platform lane can sometimes be empty. Oxide’s RFD process is the most complete public example of the whole thing running in the open.

What is ours is the assembly — and the numbers, which are a working hypothesis until the metrics say otherwise.

Download the workflow deck (PDF)

Further reading