Section 7Production-ready — the quality gates
"The tests pass" is not "it's ready for millions of users." Between them sits a definition of done the agent must clear every time. Encode it once; enforce it with CI so nothing merges without it. Treat the list below as a DO-CONFIRM checklist: do the build, then confirm each item before you ship. If no pipeline runs your checks yet, stand that up first — Setup, part two: production infrastructure walks through it; everything below assumes CI exists.
Section 6 ended on exactly this warning: verified is not the same as ready. The salon's holiday-blocking feature has passed its behaviour tests, its demo reel looks right, a second agent has read every line — and nobody has yet asked whether the booking page survives a Saturday-morning rush, whether it can be used without a mouse, or what a customer sees when the payment provider times out. Those questions don't answer themselves, and an agent won't volunteer them. This chapter is the list of questions, written down once so you never have to keep them in your head again.
Agents change the economics of that list in both directions. On a human team, production standards live partly in culture — the senior engineer who always asks "what happens when this fails?" in review. An agent has no culture and no habits; every fresh session starts with none of your standards unless they're written down where the machinery enforces them. That's the risk. The payoff is the mirror image: an agent will clear any gate you can automate, tirelessly, on every single change — standards that human teams honour on their best days become standards your pipeline honours on every day. Read the whole chapter as a catalogue of gates worth automating, roughly in order of how often their absence burns a real product.
"Definition of done" and DO-CONFIRM. The surgeon and writer Atul Gawande, in The Checklist Manifesto, splits checklists into two kinds borrowed from aviation: READ-DO, where you read each step and perform it in order (an emergency procedure), and DO-CONFIRM, where skilled people do the work the way they always do — then stop at a defined pause point and confirm, item by item, that nothing was missed. A definition of done is a DO-CONFIRM checklist for software: the agent builds however it builds, and then, before anything merges, every item on the list gets confirmed — by a machine wherever possible, so the pause point can't be skipped in a hurry.
- Type-check is strict and green — including
noUncheckedIndexedAccess: true, which forces the agent to handle "this array slot might be empty." - Behaviour tests cover the feature's real paths, not just the happy one (the case where nothing goes wrong).
- Lint (an automated style-and-error checker) and formatting pass; no suppressed warnings snuck in.
- The non-functionals below are addressed, not skipped.
Where the definition of done lives — in CI, not in anyone's memory
"Encode it once" means two concrete moves. Every machine-checkable item — the strict type-check, the tests, the linter, the budgets below — becomes a required check in CI, so a change that fails one physically cannot merge. And every judgment item — "error states are handled gracefully," "no dark patterns" — goes into a written checklist in the repository that the reviewing agent from Section 6 is pointed at on every pull request, plus a line in AGENTS.md so the building agent sees the standards before it starts, not after it fails them. This is Section 1's evidence-over-claims rule applied to "done" itself: done is not a sentence the agent says; it's a set of checks turning green.
For the salon owner this is the difference between remembering and owning. You will never remember what noUncheckedIndexedAccess does at review time, and you don't have to: you had the agent turn it on once, in week one, and from then on every change — written by you, an agent, or five agents in parallel — clears it or doesn't merge. And the definition of done is the one part of the codebase where a non-programmer's judgment matters most, because deciding which gates a booking product needs — payments must never double-charge, the page must work on an old phone — is a business call, not a technical one.
A few items in this chapter resist full automation; those go on a written, named list rather than being dropped — unwritten manual gates aren't gates, they're intentions. How to keep such gates honest is Section 8's playbook section ("every 'test it by hand' needs its own playbook").
Enforcing readable code — what a default linter won't catch
"Lint passes" in the checklist above means style and obvious errors — it does not mean short functions, low complexity, or no duplication. Those need rules you turn on deliberately; a default ESLint or Ruff config ships without them, and an agent left alone has no reason to add them itself.
Complexity & length
ESLint's own complexity rule (cyclomatic complexity — how many independent paths run through a function), plus max-lines-per-function, max-depth (nesting), and max-params. Add eslint-plugin-sonarjs's cognitive-complexity rule (default threshold 15) — a closer proxy for "how hard is this to hold in your head" than raw cyclomatic complexity, since it also penalises deep nesting and mixed levels of abstraction.
Duplication
jscpd (a copy-paste detector) finds duplicated logic across your codebase — the most literal form of spaghetti code. Its 2026 release ships an agent skill and an MCP server specifically so an agent can check its own work for duplication mid-task, before you ever see the pull request.
Dead code
Knip (JavaScript/TypeScript) builds a full dependency graph of the project and finds unused files, exports, and dependencies. Agents leave more of this behind than humans do — replacing code is cheaper for an agent than tracing every caller first.
Module boundaries
dependency-cruiser (JS/TS) or import-linter (Python) turn Section 10's "own the interfaces" from a discipline into a rule: declare which module may import which, and CI fails on a violation — the automated version of a deep module's small public interface.
Naming
eslint-plugin-unicorn's prevent-abbreviations rule catches meaningless or abbreviated identifiers mechanically — the kind of naming that makes code hard to search and harder for a fresh agent session to understand cold. Its maintainer now rejects contributions over exactly this: too much unreadable AI-generated code showing up in submissions.
Isolation & coupling
A change that ripples through a dozen unrelated files — "shotgun surgery" — means something is too tightly coupled. dependency-cruiser can also flag circular dependencies (two modules that quietly depend on each other); tools like CodeScene go further and mine your git history for files that keep changing together, surfacing hidden coupling that imports alone don't show.
For Python, the equivalent starting point is Ruff's own complexity check (rule C901, off by default — turn it on) plus Radon for a maintainability-index score you can track over time.
If configuring each of these separately is more than you want to own, SonarQube/SonarCloud bundles complexity, duplication, and code-smell detection into a single score with a CI-blocking quality gate — and as of 2026 ships a feature set aimed specifically at AI-agent-generated code ("AI Code Assurance"). Qlty is a newer, lighter alternative built by the team behind Code Climate.
Coverage that means something
A high coverage number can be a lie — code executed but nothing actually asserted. Sanity-check it with mutation testing: a tool deliberately introduces bugs and checks your tests catch them. Tests that survive mutations are theatre. This matters more with agents, which are skilled at writing tests that run but prove nothing — a 2026 academic study of 1.2 million commits found coding agents add mocks to their own tests noticeably more often than humans do (36% of agent commits versus 26% for human ones), which is exactly how a test can pass without exercising real behaviour.
Property-based testing catches what example-based tests miss from the other direction: instead of writing one example, you state an invariant that should always hold — "decoding what you just encoded returns the original," say — and a framework generates hundreds of cases trying to break it. Anthropic used this technique with Claude to find real, previously-unknown bugs in NumPy, SciPy and Pandas that years of example-based unit tests had missed.
Mutation testing, decoded. Think of it as a fire drill for your test suite. The tool takes your working code and deliberately sabotages it in hundreds of tiny ways — flips a < to <=, deletes a line, negates a condition — and runs the tests after each sabotage. If the tests fail, good: they noticed, and that mutant is "killed." If the tests still pass with the sabotage in place, that mutant "survived" — and a real bug of the same shape would survive too. A coverage number tells you which lines the tests ran; a mutation score tells you which lines they'd actually defend.
The standard tools are Stryker Mutator for JavaScript and TypeScript and mutmut for Python; for property-based testing, fast-check (JavaScript/TypeScript) and Hypothesis (Python) are the established choices. You don't have to learn any of them — you have to name the invariant, which is a plain-language sentence about the business: "no two confirmed bookings for the same chair may ever overlap, no matter what sequence of bookings, cancellations, and reschedules got us here." Hand the agent that sentence and it writes the harness; the framework then spends hundreds of generated scenarios trying to construct a history that breaks the rule. That's a level of scrutiny no hand-written list of examples reaches — and for a booking product, that one invariant is the business.
The non-functionals agents quietly skip
Agents optimise for the happy path and the visible feature. Left unprompted, they routinely omit the things that decide whether real users have a good time. Make these explicit gates (Thoughtworks' "sensors" idea — automated checks that watch for exactly these):
Error handling & resilience
What happens when the network fails, the input is garbage, the third party is down? Demand graceful failure, not a stack trace.
Performance & load
Set budgets (e.g. Core Web Vitals) and fail the build if they regress. Proving the app holds under real traffic is load testing — Section 8 owns that.
Scalability
Keep services stateless, index the queries the feature actually runs, cache what's safe to cache, and rate-limit anything public — the design choices Section 8's load tests will lean on.
Concurrency & correctness
Two customers can click "book" on the same appointment slot in the same second. If the check-then-write isn't a single atomic step, both bookings succeed and you've double-booked a chair. Fix this at the database layer — a unique constraint or a transaction — never with an application-level "check first, then save," and write a test that deliberately fires two requests at once to prove it.
Idempotency
A different failure from the one above: the same request arrives twice — a customer's phone retries a booking or payment after a network blip they never saw fail. Without an idempotency key (a client-generated ID that lets the server recognise "I've already handled this exact request"), a retry becomes a duplicate charge or a duplicate booking.
Time zones & daylight saving
The single most common real bug in scheduling software: store and compute every appointment time in UTC, render it in the customer's local time only at the very last step, and specifically test appointments that fall across a daylight-saving transition — that's when "2:30pm" quietly stops meaning what everyone assumes.
Accessibility (a11y)
Usable with a keyboard, a screen reader, and low vision — the WCAG (Web Content Accessibility Guidelines) standard. Automatable checks catch most regressions; make them a gate.
SEO & discoverability
A booking page nobody can find earns nothing. Three gates: the content is present in the served HTML, not only after JavaScript some crawlers won't run; every page ships its metadata — title, description, Open Graph tags for link previews; and the sitemap is generated by the build and checked in CI, never maintained by hand.
Internationalisation
Hard-coded English and MM/DD/YYYY dates break the moment you cross a border. Externalise strings early. The gate: strings externalised and a second locale actually renders.
Section 1's eager agent declares victory here too: green tests measure what you asked for, and if you didn't ask for the non-functionals — error handling, accessibility, a query that holds under load — they aren't there.
The practical fix is to make the cards above part of the asking, not the remembering. Put the list in the repository — in the ticket template from Section 4 or in AGENTS.md — as questions every feature must answer before it counts as done: What happens when the network drops mid-request? What happens when two people do this at once? What happens when the same request arrives twice? What does this cost at ten thousand users? For the salon's holiday-blocking feature those questions stop being abstract instantly: a customer's phone loses signal mid-booking in a basement salon; two regulars race for the last Saturday slot; a retried tap books the same appointment twice. An agent asked those questions answers them well. An agent not asked builds the happy path — every time.
Performance budgets — a number, not a vibe
"Fast" is not a gate; a number is. For anything with a web page in it, the industry's shared numbers are Google's Core Web Vitals — three measurements taken from real visits: LCP (Largest Contentful Paint — how long until the main content is visibly there; good is 2.5 seconds or less), INP (Interaction to Next Paint — how long between tapping a button and the screen responding; good is 200 milliseconds or less), and CLS (Cumulative Layout Shift — how much the page jumps around while loading; good is 0.1 or less). Each is judged at the 75th percentile of real visits: not "fast on your laptop," but fast for at least three-quarters of actual customers — including the one on a three-year-old phone on the salon's Wi-Fi.
The enforcement tool is Lighthouse CI, the automated version of the Lighthouse audit built into Chrome: it runs the audit on every pull request and supports assertion levels — "warn" just reports, "error" fails the build when a threshold is crossed. Set the thresholds at "no worse than today," not at perfection: the job of this gate is to stop the slow creep where each change adds a little weight and no single change is to blame — regression protection for speed, exactly parallel to what the test suite does for behaviour. Agents make this gate more necessary, not less: an agent picking a library optimises for "solves the task," and without a budget watching, nothing in the loop ever pushes back on the page quietly getting heavier.
Keyboard support, done properly
"Usable with a keyboard" is a specific, testable set of claims, not a vibe. The core WCAG success criteria worth naming explicitly to an agent: every interactive element is reachable and operable by keyboard alone (2.1.1), focus can never get trapped somewhere with no way out (2.1.2), tab order follows a logical, meaningful sequence rather than DOM order that happens to look right visually (2.4.3), the focused element always has a visible indicator (2.4.7), and a sticky header or modal never fully covers the currently-focused element (2.4.11).
Power-user shortcuts are worth adding — a fast "new booking" or "search" key genuinely helps someone using the product daily — but a single-letter shortcut that fires from anywhere on the page is itself an accessibility bug: it can trigger by accident for a screen-reader or speech-input user typing normally. WCAG 2.1.4 covers exactly this: any single-character shortcut needs a way to turn it off or remap it, or must require a modifier key, or must only fire when a specific component has focus. And make shortcuts discoverable, not secret — a visible "?" shortcut that opens the list of shortcuts is a small addition that turns a power-user feature into one people actually find and use.
Who keyboard support is actually for. A screen reader is software that reads the page aloud for blind and low-vision users, and it's driven entirely from the keyboard — no mouse involved. But keyboard-only users are a much wider group: people with tremors or repetitive-strain injuries who can't use a pointer, someone with a broken trackpad, and power users who are simply faster tabbing through a form than reaching for a mouse. If the salon's booking flow can be completed with only Tab, Enter, and the arrow keys, it works for all of them at once — which is why "usable with a keyboard" is the highest-leverage accessibility gate to enforce first.
Enforce it in two layers. The automated accessibility checks from Section 6's toolbox run in CI and catch the mechanical failures — a button with no accessible name, a focus indicator styled away. The keyboard walkthrough itself stays a manual gate, which means it needs the playbook treatment from the callout above: a written procedure — start at the booking page, Tab to the calendar, pick a slot with the arrow keys, complete the booking with Enter, expected result at each step, last-verified date at the top — so that "we test keyboard support" is a repeatable check anyone can run identically, not a thing someone did once in March. Five minutes per release, and it's the five minutes that decides whether a customer with no mouse can actually give you money.
UX integrity — no dark patterns, adapts to the user
Two more things agents skip unless you ask, and both belong in review (Section 6), not just in taste.
No dark patterns. A dark pattern is an interface deliberately designed to trick someone into an action they didn't mean to take — fake urgency ("2 left!" that never changes), confirmshaming ("No thanks, I don't want to save money"), a cancel flow buried three menus deep, a pre-ticked opt-in. The canonical public catalogue is Harry Brignull's deceptive.design — the person who coined the term. If you're building a structured pattern-audit checklist of your own, give each entry the shape worth copying: a detection heuristic an agent can actually check for (a countdown timer with no server-backed deadline, a decline button with guilt-laden wording), an ethical alternative, and the specific legal risk (FTC enforcement, the EU's Unfair Commercial Practices Directive, GDPR's freely-given-consent requirement) — that turns "avoid dark patterns" from a vibe into something checkable. Dark patterns are one category worth this treatment; auth flows, forms, notifications, onboarding and error states each accumulate their own well-known anti-patterns and are worth the same structured list. This has moved from "bad practice" to real legal exposure in the EU under consumer-protection law and the Digital Services Act (Section 9); treat this as part of review, not an afterthought.
Check the other direction too — is a good, proven pattern even being used? A flow that isn't manipulative can still be badly designed. Reinventing checkout, search-and-filter, or pagination from scratch when an established pattern already exists adds risk with no benefit — untested edge cases, an interaction users have to relearn instead of one they already know. Default to a known pattern (Nielsen Norman Group's research, Google's Material Design guidelines, or a general catalogue like ui-patterns.com) and treat deviating from one as a deliberate, explained choice, not an accident of whatever the agent generated first.
Load the light version first. A heavy interactive widget — a video player, a map, a rich text editor — doesn't need its JavaScript downloaded before anyone has asked for it. Ship a static, lightweight placeholder (the facade pattern) and defer the real component until the user actually interacts with it — the pattern behind tools like lite-youtube-embed. It's a direct, measurable win for the Core Web Vitals budget in the Performance card above.
Respect what the platform already knows about the user. The browser can tell you, for free, whether someone has asked for less motion (prefers-reduced-motion — turn off or shorten animations) or less data (the Save-Data header and prefers-reduced-data media query — serve smaller images, skip the heavy widget, defer non-essential requests). This is progressive enhancement in its most literal form: ask the platform what the user wants, and adapt automatically instead of shipping one experience for everyone.
From ready to shipped — where the gates hand over
Every gate in this chapter green means one thing: the change has earned a merge. It is still invisible to every customer, and that's the correct state to be in — because "the code is good" and "real people now depend on it" are separated by one more discipline. Getting across that gap — staging, feature flags, gradual rollout, rollback, and the pipeline that makes them automatic — is Section 8. The definition of done decides whether a change deserves to reach users; the pipeline decides how it reaches them without taking the product down on the way.
Quick check: the agent reports "done — all tests green." What's still between this feature and real users?
The definition of done — a DO-CONFIRM checklist enforced in CI so nothing merges without clearing it. Green tests prove the change does what you asked; the gates prove it's ready for people you'll never meet: readable code, tests that actually defend behaviour, and the non-functionals no agent adds unprompted.
The longer answer is the chapter. Default linters pass code a maintainer would reject, so readability gets its own rules — complexity limits, duplication and dead-code detection, module boundaries. Coverage numbers can lie, so mutation testing checks whether the tests would notice sabotage and property-based testing hunts for the booking history that breaks the one invariant the business depends on. The non-functional cards — error handling, load, concurrency, idempotency, time zones, accessibility, internationalisation — become questions in the ticket template, because an agent not asked builds the happy path every time. Performance gets a number (Core Web Vitals, enforced by Lighthouse CI at "no worse than today"), keyboard support gets an automated layer plus a written manual playbook, and UX integrity — no dark patterns, proven patterns by default — belongs in review, not in taste. All of it lives in CI and the repo, not in anyone's memory. What none of it covers is the release itself — that's Section 8.