Skip to content
September 27, 2026•9 min read•By Dzaki Amri Zaidaan

If AI Writes the Code and AI Reviews the Code, What Exactly Is the Developer Verifying?

When generation and review are both automated, the human's job shifts from line-level correctness to intent, invariants, and blast radius. This article breaks down the verification stack, shows a runnable CI gate that encodes human-owned invariants, and gives a production checklist for AI-authored pull requests.

#AI & ML#Architecture
text

If AI Writes the Code and AI Reviews the Code, What Exactly Is the Developer Verifying?

The Problem & Industry Shift

The pipeline now looks like this: an LLM drafts the feature, a second LLM drafts the tests, a third agent opens the pull request, and a review bot leaves inline comments. Every step is plausible, well-formatted, and fast. The failure mode is not that the code is obviously wrong — it is that the code is confidently wrong in ways that pass the same statistical lens that produced it.

This is not a hypothetical. Empirical studies of LLM-generated code consistently find that a meaningful fraction of generated snippets are insecure or subtly incorrect, and that developers — especially those with less experience — tend to over-trust them [1][2]. The classic mitigation, "a human reviews the diff," degrades when the diff is large, the reviewer is time-constrained, and the same model family generated both the code and the review. You get correlated errors: the generator's blind spots become the reviewer's blind spots.

The industry shift is structural, not cosmetic. Historically, the developer's core value was translation: turning intent into correct syntax and control flow. That translation is now cheap. What remains expensive — and what the developer is actually verifying — is the set of things a model cannot know from the repository alone:

  • Intent: does this code do what the business actually needs, not what the prompt literally said?
  • Invariants: which properties must hold across the whole system (idempotency, ordering, tenancy isolation, money conservation)?
  • Blast radius: what else breaks when this ships, and who is on the hook when it does?
  • Authority: who is accountable for the decision to merge?

AI review is a useful filter, not a verdict. The developer's job is to own the verdict.

Architecture & Core Mechanics

The mental model that keeps teams sane is to separate generation from verification and to make the verification layer deterministic wherever possible. Probabilistic checks (LLM review) are advisory; deterministic checks (tests, type systems, policy engines, property-based tests) are gates.

        intent (human)
             |
             v
   +-------------------+        +-------------------+
   |  Generator (LLM)  | -----> |  Tests (LLM)      |
   +-------------------+        +-------------------+
             |                            |
             v                            v
   +---------------------------------------------+
   |            Pull Request (agent)             |
   +---------------------------------------------+
             |
             v
   +-------------------+     advisory only
   |  AI Reviewer      | ------------------------+
   +-------------------+                         |
             |                                   |
             v                                   v
   +-------------------+        +-------------------+
   | Deterministic CI  |        | Human Verifier    |
   | (types, tests,    |        | (intent, invariants|
   |  policy, SCA)     |        |  blast radius)    |
   +-------------------+        +-------------------+
             |                            |
             +-------------+--------------+
                           v
                       Merge gate

The key mechanical insight: the human verifies the specification, not the implementation. If the specification is encoded as executable checks, the human's review surface shrinks from "read 800 lines" to "confirm these 12 invariants and this blast-radius assessment." This is the same shift that made infrastructure-as-code reviewable: you stopped reading shell scripts and started reading declarative diffs plus a plan output.

Three layers make this work in practice:

  1. Deterministic gates. Type checkers, linters, unit/integration tests, property-based tests, schema validators, dependency and secret scanners. These must be non-negotiable and must run on every AI-authored PR exactly as they do on human-authored ones.
  2. Policy as code. Authorization boundaries, tenancy rules, and data-residency constraints encoded in a policy engine (OPA/Rego, Cedar, or equivalent) so that "does this violate a rule?" is a machine-checkable question, not a vibe.
  3. Human-owned invariants. A small, explicit list of properties the team agrees are load-bearing. These are the things the human actually signs off on, and they should be written down and, where possible, tested.

The AI reviewer sits between generation and the deterministic gates. It is good at catching style drift, missing null checks, and obvious API misuse. It is bad at knowing that transfer() must be idempotent under retry because the payment provider retries webhooks for 72 hours. That knowledge lives in the human's head and, ideally, in a test.

Production Code Example

The following is a realistic TypeScript CI gate that encodes human-owned invariants for an AI-authored pull request. It is deliberately boring: it reads a manifest of invariants, runs deterministic checks, and refuses to merge if any invariant is unverified. The point is that the human authors the invariants; the machine enforces them.

// invariants.ts — authored and owned by humans, not generated.
// Each invariant maps to an executable check. If a check cannot be
// automated, it is marked `manual` and requires an explicit sign-off.

export type Invariant = {
  id: string;
  description: string;
  severity: "block" | "warn";
  check:
    | { kind: "command"; cmd: string }
    | { kind: "manual"; owner: string };
};

export const INVARIANTS: Invariant[] = [
  {
    id: "money-conservation",
    description:
      "Sum of ledger debits equals sum of credits after every transfer, including retries.",
    severity: "block",
    check: { kind: "command", cmd: "npm run test:ledger:invariants" },
  },
  {
    id: "tenancy-isolation",
    description:
      "No query may read or write rows outside the caller's tenant_id.",
    severity: "block",
    check: { kind: "command", cmd: "npm run test:tenancy" },
  },
  {
    id: "idempotent-webhooks",
    description:
      "Payment webhook handler is idempotent under duplicate delivery.",
    severity: "block",
    check: { kind: "command", cmd: "npm run test:webhooks:idempotency" },
  },
  {
    id: "pii-egress",
    description:
      "No PII field is written to logs or third-party analytics.",
    severity: "block",
    check: { kind: "command", cmd: "npm run test:pii:egress" },
  },
  {
    id: "rollback-plan",
    description:
      "A documented rollback path exists for this migration.",
    severity: "block",
    // Cannot be automated: a human must confirm the plan.
    check: { kind: "manual", owner: "on-call-engineer" },
  },
];
// ci-gate.ts — runs on every PR, including AI-authored ones.
// Deterministic checks are gates. The AI reviewer's output is advisory.

import { execSync } from "node:child_process";
import { INVARIANTS, type Invariant } from "./invariants";

type Result = { id: string; ok: boolean; detail: string };

function runCommand(cmd: string): { ok: boolean; detail: string } {
  try {
    // Fail fast: any non-zero exit is a failed invariant.
    const out = execSync(cmd, { stdio: "pipe", timeout: 10 * 60 * 1000 });
    return { ok: true, detail: out.toString().slice(0, 2000) };
  } catch (err: any) {
    return {
      ok: false,
      detail: (err.stdout?.toString() ?? "") + (err.stderr?.toString() ?? ""),
    };
  }
}

function evaluate(inv: Invariant): Result {
  if (inv.check.kind === "manual") {
    // Manual invariants are never auto-passed. They require a signed
    // approval from the named owner, recorded in the PR metadata.
    const approved = process.env[`APPROVED_${inv.id.toUpperCase()}`] === "true";
    return {
      id: inv.id,
      ok: approved,
      detail: approved
        ? `signed off by ${inv.check.owner}`
        : `awaiting sign-off from ${inv.check.owner}`,
    };
  }
  const r = runCommand(inv.check.cmd);
  return { id: inv.id, ok: r.ok, detail: r.detail };
}

function main(): void {
  const results = INVARIANTS.map(evaluate);
  const blocked = results.filter(
    (r, i) => !r.ok && INVARIANTS[i].severity === "block",
  );

  for (const r of results) {
    console.log(`[${r.ok ? "PASS" : "FAIL"}] ${r.id}: ${r.detail}`);
  }

  if (blocked.length > 0) {
    console.error(
      `\nRefusing to merge: ${blocked.length} blocking invariant(s) unverified.`,
    );
    process.exit(1);
  }
  console.log("\nAll blocking invariants verified.");
}

main();

Two engineering decisions are worth calling out explicitly:

  • Manual invariants cannot be auto-passed. The rollback-plan check requires an environment variable set by a human approval step. This prevents the common failure where an agent marks its own work as verified.
  • The AI reviewer is not in this file. Its comments are surfaced to the human, but they do not gate the merge. This keeps the trust boundary explicit: probabilistic review informs, deterministic checks decide.

Performance, Cost & Trade-offs

The economics of this shift are real but easy to misread. Generation is cheap; verification is not. The trade-off table below reflects what teams typically observe when they instrument both.

DimensionAI generationAI reviewDeterministic CIHuman verification
Latencysecondssecondsminutesminutes–hours
Cost per PRlowlowcompute-boundhighest (senior time)
Catches syntax/styleyesyesyesrarely needed
Catches logic bugssometimessometimesoften (tests)sometimes
Catches intent mismatchnononoyes
Catches blast radiusnonopartial (integration)yes
Accountabilitynonenonenoneexplicit

The critical trade-off is coverage vs. correlated error. Adding more AI review does not reduce correlated error if the reviewer shares the generator's training distribution and context window. The only reliable way to break correlation is to introduce independent signals: property-based tests, fuzzing, formal checks, policy engines, and human intent review. These are more expensive per PR but they fail independently.

A second trade-off is review latency vs. review depth. AI review is fast and shallow; human review is slow and deep. The productive pattern is to let AI review handle the shallow layer (style, obvious bugs, missing tests) so that human review time is spent entirely on the deep layer (intent, invariants, blast radius). If you skip the shallow layer, humans burn out on trivia. If you skip the deep layer, you ship correlated errors.

A third consideration is security and supply chain. AI-generated code frequently pulls in dependencies and patterns that are plausible but unvetted. Deterministic SCA and secret scanning are not optional; they are the cheapest independent signal you have [3].

Finally, observability is part of verification. A change that passes all gates can still be wrong in production. Canary deploys, feature flags, and fast rollback are the last line of defense, and they are the human's responsibility to configure before merge, not after [4].

Actionable Checklist / Summary

When adopting AI-authored and AI-reviewed pull requests in production, treat the following as the minimum bar:

  • Write down your invariants. If they are not written, they cannot be verified. Start with money, tenancy, idempotency, and PII.
  • Encode invariants as executable checks wherever possible. Property-based tests and policy engines beat prose.
  • Keep AI review advisory. It informs the human; it does not gate the merge.
  • Require explicit human sign-off for invariants that cannot be automated (rollback plans, data migrations, auth changes).
  • Run identical deterministic gates on AI-authored and human-authored PRs. No exceptions, no "trust me" fast lanes.
  • Break correlated error with independent signals: fuzzing, SCA, secret scanning, policy-as-code.
  • Instrument the pipeline. Track how often AI-authored PRs are reverted, how often AI review misses issues that humans catch, and how often humans miss issues that production catches.
  • Own the blast radius. Before merge, answer: what breaks if this is wrong, and how do we roll back in under five minutes?
  • Document accountability. Every merged PR has a named human owner, regardless of who or what wrote it.

The developer is not verifying syntax. The developer is verifying that the system still means what the business thinks it means. That has always been the job; AI just made the rest of it cheap.

References