There is a quiet crisis happening in engineering teams right now, and most Tech Leads have not named it yet.

Your team ships twice as much code. Pull request volume is up 300%. But production incidents are not going down — in some teams, they are going up. Reviewers are exhausted, flagging style issues in AI-generated diffs while missing the architectural decision buried three layers deep.

The problem is not the code quality. The problem is when we are reviewing.

The Diff Is Already a Lagging Indicator

Traditional code review was designed for a world where writing code was slow. A developer spent hours crafting logic. By the time they opened a PR, the code represented their best current thinking. Reviewing the diff made sense — the diff was the thinking.

AI changes this equation entirely. A developer can describe a feature to Claude Code and have 500 lines of implementation in four minutes. The diff arrives almost immediately after the intent is formed. The “thinking” happened in the prompt, not the code.

When you review that diff, you are auditing the output of a process you were not part of. You are checking that the generated code is internally consistent, that it follows conventions, that the tests cover the happy path. What you are not checking is whether the intent itself was sound.

This is upstream blindness. And it is the real source of the quality problems teams are experiencing.

What Upstream Review Looks Like

Upstream code review shifts the review artifact from the diff to the intent.

There are three layers to review upstream:

1. The problem specification (what are we actually solving?)

Before any AI prompt is written, the intent should be reviewable. This is not a heavy process — it can be a two-paragraph design note in a shared doc, or a structured comment in the ticket:

Problem: Users with >1000 orders hit a timeout on the orders listing page.
Root cause hypothesis: N+1 query on the order items join.
Proposed solution: Paginate at service layer, add eager load for items.
Constraints: Must remain backwards compatible with the mobile API v2.
Acceptance criteria:
  - Orders listing loads <500ms for accounts with 10k orders
  - No changes to GET /api/v2/orders response shape

This is reviewable in two minutes. A senior engineer can spot the wrong hypothesis, the missed constraint, the acceptance criteria that misses the real user need — before any code is generated.

2. The architecture decision (how does it fit the system?)

For any change that touches a service boundary, a data model, or a caching strategy, the architectural decision should be captured and reviewed independently of the implementation.

I use a lightweight Architecture Decision Record format. Not the full ADR with status and consequences — just the decision and its reasoning, three to five sentences:

## Decision: Paginate orders at the service layer, not the repository layer

We will add cursor-based pagination to `OrderService.listByCustomer()` rather 
than in the repository. This keeps the repository interface clean and allows 
the service to apply business rules (e.g., filtering cancelled orders) before 
pagination counts are calculated.

Alternative considered: Pagination in repository → rejected because it forces 
business logic into the data layer to handle the cancellation filter.

An experienced Tech Lead reviewing this can catch a mistake in thirty seconds. The same mistake buried in 300 lines of generated code takes fifteen minutes to find.

3. The test contract (what behavior are we guaranteeing?)

Test cases written before code generation serve two purposes: they define the contract the AI must satisfy, and they are reviewable independently of the implementation.

I now routinely ask developers to write test signatures before they prompt for the implementation:

describe('OrderService.listByCustomer', () => {
  it('returns paginated results with cursor for accounts with >100 orders', ...)
  it('respects cursor across page boundaries without duplicates', ...)
  it('filters cancelled orders before applying pagination limit', ...)
  it('returns empty page with no next cursor when results exhausted', ...)
  it('throws AuthorizationError when customer ID does not match caller', ...)
})

Review the test contract. Agree on it. Then let the AI generate both the implementation and the test bodies. The contract was the real intellectual work — the AI fills in the rest.

The RFC-First Workflow in Practice

Combining these three upstream artifacts into a lightweight “RFC-first” workflow transforms how reviews happen.

The flow looks like this:

  1. Developer opens a lightweight RFC — problem spec, architectural decision, test contract. This takes fifteen to thirty minutes.
  2. Async review happens on the RFC — one to two reviewers, synchronous if the decision is complex.
  3. RFC is approved — developer (or the AI) generates the implementation against the agreed contract.
  4. PR is opened — reviewers verify the implementation matches the RFC. They are not re-litigating design. They are checking execution.

The PR review becomes fast and mechanical. Because the hard questions were already answered.

I ran this workflow on my team for three months. PR review cycles dropped from an average of 2.1 days to 0.6 days. Production incidents from architectural mistakes dropped to near zero. The RFC review itself averages eleven minutes.

What Still Belongs in the Diff Review

Upstream review does not eliminate diff review. It changes what you look for.

In a diff review under this model, you are checking:

  • Execution fidelity — does the implementation match what the RFC said?
  • AI hallucinations — did the model introduce something not in the spec? (This is more common than people think — AI confidently adds “helpful” behavior.)
  • Edge cases the test contract missed — error handling, concurrency, resource cleanup.
  • Code health — naming, complexity, dead code.

You are not reconsidering the architecture. You are not debating the algorithm. Those decisions are upstream and already made.

The Intent Validation Toolchain

As teams mature in upstream review, they start automating intent validation.

The most practical early step is a PR template that links to the upstream RFC:

## PR Checklist
- [ ] Links to approved RFC / design note
- [ ] All RFC acceptance criteria have corresponding tests
- [ ] No features added beyond RFC scope (flag as RFC deviation if yes)

More advanced teams run automated checks: does the diff touch a file outside the RFC scope? Does test coverage drop below the RFC-specified threshold? Does the API surface change in a way inconsistent with the RFC?

These checks are lightweight to implement and catch the most common AI overshoot problem — the model does slightly more than you asked, and slightly more is sometimes wrong.

The Culture Shift This Requires

Upstream review requires one cultural change that is harder than the workflow: senior engineers must spend time earlier in the cycle.

In traditional review culture, the senior engineer’s value is in the final check. In upstream review culture, their value is in the early clarification. This means being available for RFC reviews, not just PR reviews. It means writing comments on a two-paragraph spec, not a three-hundred-line diff.

For some seniors, this feels like less leverage. In practice it is dramatically more leverage — catching a wrong hypothesis before the AI generates the implementation saves far more time than catching it in a PR.

The Shift Is Already Happening

Upstream code review is not a new idea. Good engineering teams have always tried to align on design before implementation. What is new is the urgency.

When implementation takes four minutes instead of four days, the gap between “agreed intent” and “running code” collapses. The review model must collapse with it.

Teams that adapt to this — that move their review energy upstream — will ship faster, with fewer production surprises, with less reviewer exhaustion. Teams that keep reviewing diffs the way they always have will find that more code means more noise, not more signal.

The diff was always a lagging indicator. Now the lag is just four minutes long.

Export for reading

Comments