One review, three tiers: a feature delivery workflow that scales its own overhead
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 / BU | Architects | Dev team | Platform | |
|---|---|---|---|---|
| Intake | Problem statement | Triage & tier | ||
| Design | Design doc | Infra ticket · raised + approved | ||
| Review | Architecture review · + API owners | Contract PR · spec + linting | Infra build · after arch review | |
| Ready | Refinement · pre-read first | Infra ready · or scheduled | ||
| Build | Development · 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 · Local | Tier 2 · Contract | Tier 3 · Structural | |
|---|---|---|---|
| Blast radius | Your service only | Other teams’ consumers | The shape of the system |
| Reversibility | Revert the PR | Needs a deprecation | Migration or rebuild |
| Trigger | No new interface | New or changed API, event, schema, integration | New service, datastore, infra component, cross-team dependency; auth, money or PII |
| Artifact | The ticket | 1–2 page design doc + contract PR | Full design doc + ADR |
| Reviewers | Code review | API or domain owner | Architect + every affected team + platform |
| Review mode | In the PR | Async comments, 1–3 days | Async comments, 3–5 days, then one live session |
| Rows used | Intake → Ready → Build | All rows, light artifacts | All rows, full artifacts |
The triage itself is six yes/no questions, answered by the engineering lead and the product owner in under five minutes:
- Does it add or change an API endpoint, event or schema that anyone outside the team consumes? → at least tier 2
- Does it add a service, datastore, queue or infrastructure component? → tier 3
- Does it create a dependency on another team’s delivery? → tier 3
- Does it touch authentication, payments or personal data? → tier 3
- Is it a breaking change to anything existing? → tier 3
- 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 Q42. 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 dedupe4. 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 allowed5. 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 batch6. 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 engineers9. 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 minThe tier 3 example on a calendar
The same change, laid on real dates. Working days; five weeks from 31 August.
| Lane | Wk 1 · 31 Aug | Wk 2 · 7 Sep | Wk 3 · 14 Sep | Wk 4 · 21 Sep | Wk 5 · 28 Sep |
|---|---|---|---|---|---|
| Product / BU | Problem statement (2 d) | Flag on (29 Sep) | |||
| Architects | Arch review, async (5 d) | ||||
| Dev team | Triage → tier 3 · Design doc (5 d) | Contract PR (5 d) | Refinement (2 d) · Development starts | Development (behind flag) | Development → done |
| Platform | Infra ticket → approved (2 d) | Infra build starts 14 Sep | Staging 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.
| Rule | Behaviour |
|---|---|
| Custom field | Tier (1 / 2 / 3), required to leave Intake. Set at triage by the engineering lead and product owner. |
| Tier 1 shortcut | Intake → Ready allowed directly. Design and In review are hidden for tier 1 tickets. |
| Tier 2 gate | In review → Ready requires a linked pull request (the contract PR) in state Merged. |
| Tier 3 gate | In review → Ready requires a design-doc link with status Approved, an ADR link, and no open is blocked by issue on any board. |
| Platform link | Infra requests are separate tickets on the platform board, linked blocks → epic, so the dependency is visible on both boards. |
| Infra approval | A 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-tiering | Changing 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.
- Lead time per stage, by tier. Tier 3 sitting two weeks in review means more reviewers or a smaller template, not more process.
- Re-tiered mid-flight. High means the triage checklist is unclear. Near zero may mean nobody dares escalate.
- Change failure rate by tier. If tier 1 changes cause as many incidents as tier 3, the checklist is missing a risk category.
- Review turnaround. From “in review” to every reviewer box ticked; target within the time-box on 90 % of docs.
- Infra on the critical path. Share of tier 3 epics that waited in Ready for a platform ticket. Should trend to zero as golden paths grow.
- Tier mix. The distribution itself, for the reason above.
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
- Design Docs at Google — Malte Ubl
- Engineering Planning with RFCs, Design Documents and ADRs — The Pragmatic Engineer
- Companies Using RFCs or Design Docs and Examples of These — Gergely Orosz
- Amazon 2015 Letter to Shareholders — one-way and two-way doors
- APIs as infrastructure: future-proofing Stripe with versioning — Stripe
- How Stripe Builds APIs — Postman blog
- Documenting Architecture Decisions — Michael Nygard
- How We Use Golden Paths to Solve Fragmentation in Our Software Ecosystem — Spotify Engineering
- RFD 1: Requests for Discussion — Oxide Computer
- Technical guardrails for LLM-assisted code: the full catalog — Lab34