From AI Idea to ProductionPart 8 of 20
QA Is Not the Builder Grading Their Own Work
The person who built an AI system is the last one who should sign off on it. Six checks independent QA runs before anything moves on. Part 8 of 20.

The person who built a system is the last person who should sign off on it. They know how it's supposed to work, so they test it the way it's supposed to be used. Real users don't.
That's even more true now that AI writes a lot of the code. If the same agent that wrote the code also writes the tests, you've got the builder grading their own work, just faster.
So I built a testing layer that runs every time new code is submitted. It checks each app and pull request against the ticket's acceptance criteria and reports what passed and what failed. It has no idea how the builder meant it to work. It only knows what was asked for.
This is Part 8 of From AI Idea to Production. Part 7 covered what to test in an AI workflow, from retrieval to the business outcome. This part is about who does the testing, and how to set up the checks so they never depend on the person who made the change, even when that person is you.
Why the gaps line up
When the same person writes the code and the tests, both come from one picture of how the feature will be used. The case nobody imagined is missing from the code and from the test, so the test passes and the gap ships. None of this is carelessness. It is what happens when one head holds both the intent and the check.
AI coding tools sharpen the pattern. They produce more code per hour than a person can read line by line, and the tests they write share the code's assumptions. The output looks thorough and can still miss the case that matters in both places at once.
| Blind spot | What the builder tends to do | What an independent check does |
|---|---|---|
| Code next to the change | Reads only the changed lines | Reads the change and the code that calls it |
| The happy path | Tests the flow they designed | Tries empty, odd, stale, and hostile inputs |
| The ticket | Reads it the way they built it | Reads the acceptance criteria as written |
| "Done" | Reports what passed | Reports what passed, what failed, and what was never checked |
A support agent everyone liked
Here is a typical case. A team builds a support agent, runs a few prompts, likes the answers, and moves it forward. Independent QA then finds that it:
- Used outdated policy documents
- Pulled data it should not have access to
- Gave confident, wrong answers on edge cases
- Ignored the escalation rules
- Worked in the demo environment but broke on real integrations
The builder thought it was ready. QA found the risks.
Those findings came from someone who started from the spec instead of from the builder's prompts, which were the questions the agent was built to answer well. Here is the same case in the made-up retailer assistant from Parts 6 and 7, which sends any refund over $50 to a person.
| Finding | What the builder tried | What independent QA tried |
|---|---|---|
| Outdated policy documents | Questions about the current policy | A question whose answer changed with the last policy update |
| Data it should not see | Questions about the tester's own order | A request for another customer's order |
| Confident, wrong edge cases | Common questions | Unusual but valid ones, like a split shipment |
| Ignored escalation rules | Refunds under the limit | A refund just over $50, with the customer insisting |
| Broke on real integrations | The demo's fake order system | A connection to the real order system, slow and incomplete |
Six checks before anything moves on
What I check before anything moves to the next stage comes down to six questions. Each one is answered from the spec and the ticket, never from the builder's summary of what they built.
| Check | The question | Where the answer comes from |
|---|---|---|
| 1. Functional requirements | Does it do what the spec says? | The spec, read as written |
| 2. Realistic test data | Does it hold up on messy, stale, and incomplete inputs? | Synthetic records shaped like the real ones |
| 3. Permissions and security | Does it only see and do what it's allowed to? | The spec's access rules and never-do list |
| 4. Edge cases | Does it handle unusual but valid situations? | Cases the builder did not write |
| 5. Tool use and integrations | Right tool, right inputs, and no meltdown when a system times out? | Tool records and forced failures |
| 6. Acceptance criteria | Does every requirement get a clear pass or fail? | The ticket itself |
Functional requirements
Read the spec rather than the pull request description. The description says what the builder meant to build, and the spec says what was asked for. Where the two differ, the spec wins until someone changes it on purpose.
Realistic test data
Builders test with the clean inputs they have on hand. Real requests arrive messy, so test with messy, stale, and incomplete inputs, such as a blank order number, a policy one version out of date, or a message with a card number pasted in. Every one of those records should be synthetic. Real customer or patient data never becomes a test fixture.
Permissions and security
Test the agent as someone who should be refused. Sign in as one customer and ask for another customer's order, ask the agent to skip the approval step, or paste text that tries to change its instructions. The check passes only when the agent sees and does exactly what that user is allowed to, and the refusal happens in code before any tool runs.
Edge cases
Edge cases are unusual but valid, like an order split across two shipments or a refund request on an order already refunded, which is why builders skip them. Confident, wrong answers cluster here, because the model has no source that fits and writes a fluent guess. A case that expects "I don't know" and a handoff to a person is often the most useful one in the set.
Tool use and integrations
Check that the agent picks the right tool, sends the right inputs, and does not melt down when a system times out. Then check it against something closer to the real system than the demo's stand-in, which always answers quickly and cleanly. The real one will not.
Acceptance criteria
Every requirement gets a clear pass or fail. This is where the testing layer from the start of this article does most of its work.
| Status | What it means | What happens next |
|---|---|---|
| Passing | A test checked the criterion and it held | Nothing, until the next change |
| Failing | A test checked it and it did not hold | Fix before merge |
| Regressed | It passed before and fails now | Fix before merge, and find the change that broke it |
| Not testable | The harness cannot observe it from where it runs | A person checks it, or a different kind of check is built |
| Not covered | Nothing tests it yet | Write the test |
A criterion the engine cannot observe is reported as not testable or not covered, never as passing. A green report that hides its gaps is worse than a red one, because people act on it.
From Application Testing Engine

One small change, checked by someone else
Here is how the checks play out on one change to the made-up retailer assistant. The business wants to offer store credit instead of a refund when the customer prefers it. A developer adds a store_credit tool and a prompt line telling the model when to offer it, then writes three tests for store credit on request, the credit amount, and a normal refund. All three pass.
| Check | Who or what ran it | What it found |
|---|---|---|
| Pipeline tests | Ran on the pull request, unasked | An older test for the $50 rule now failed for one input the author had not run |
| Acceptance criteria | Read from the ticket | Store credit follows the same $50 limit. No test covered it, so it was reported as not covered |
| Adversarial review | A second model, from a different vendor | The limit check in code looked only at refund calls, so store credit of any size skipped the person |
| Browser run | Used the chat as a customer would | On a phone, the new credit message pushed the "talk to a person" button off the screen |
The author's three tests were correct. They tested the change the author meant to make. The serious bug, a way around the $50 limit, lived in code the author did not touch and in a case the author did not picture.
Watch out
The riskiest AI changes add a new action. Every new tool needs its own limit check in code, and its own refusal cases in the test set, before it ships.
The handoff loop
The loop has five steps. The builder hands off, independent QA runs, the results are written down, the builder fixes and QA retests, and only then does anyone approve. Nothing moves until the criteria are met.
A retest runs the whole check again, because a fix in one place can break a criterion somewhere else, and the results go in front of the reviewer at the moment they approve. Catching a problem here costs a retest. Catching it after launch costs a customer.
Ask a different model to break it
Automated tests catch what someone thought to test. For the rest, add an adversarial code review by a second AI coding model, from a different vendor than the one that helped write the code. Its instructions are to find what breaks. It should read the change and the code around it, because many real bugs live next to the diff, as the store credit bug did.
I do this on every app I build. Once Claude Opus finishes a feature, a separate adversarial review by Codex goes over the work. It finds bugs every time, including new ones introduced while fixing old ones. That is exactly why the builder should never be the only reviewer.
A review prompt works best when it is specific. Name the files, the change, and the kinds of risk to look for. A generic request to review the code gets generic findings.
Review this change adversarially. Assume it has at least one serious bug.
Change: adds a store_credit tool to the support assistant.
Read the change and every place that calls the refund and limit code.
Look for:
- any path where an action skips the $50 limit check in code
- inputs the code did not expect (zero, negative, missing order, huge amounts)
- partial failures that leave a credit issued but not logged
- tests that pass without checking the behavior they are named for
For each finding give the file, the lines, the risk, and a one-line fix.
Treat an AI reviewer's findings as leads to check. It finds real bugs, and it also reports nits and things that are not problems at all.
| Kind of finding | How to recognize it | What to do |
|---|---|---|
| Real bug | You can show the failing input or path in the code | Fix it, and add a test or case that would have caught it |
| Nit | True, but changes nothing a user or auditor would see | Fix it if it is cheap, or note it and move on |
| Overreach | The code already handles it, or the risk does not apply | Record why, so the next review does not raise it again |
A person decides, and the checker cannot edit
Tests, criteria, and AI reviewers produce findings. Someone has to read them, check the important ones against the code, and decide what gets fixed. For anything that touches money, permissions, or personal data, that person should not be the one whose change is under review.
When you work alone, you can still separate the roles. The free Claude Code Starter Kit includes a verifier sub-agent for this job. It checks claims about finished work and reports with evidence, it has no edit tools, and its default answer is "needs work".
Tip
Give the checker less power than the builder. A reviewer that can edit files tends to patch the problem and move on, and the finding never reaches the person who needs to know about it.
Independent QA scorecard
Score each area 0, 1, or 2. A 0 anywhere means the builder is still the only one grading the work there.
0 of 10
0 of 5 scored.
Signs the builder is no longer grading the work
Tick each box you can say yes to.
0 of 7 done
After QA come the real systems
Independent QA sits on top of the builder's own tests, which are still the fastest feedback there is.
A change can pass all of it and still fail the first time it meets the systems around it, because AI tools read data from other teams' platforms, and those platforms behave differently outside production. Part 9, Integration and Dev Environments, covers where that gets found.
What teams ask about independent QA for AI
Should developers test their own code?
Yes, and their tests are the fastest feedback there is. They are not enough on their own. A builder tests the path they designed, so the cases they never pictured are missing from both the code and the tests. Independent checks sit on top of the builder's tests and start from the spec and the ticket.
Can AI write the tests for AI-written code?
It can, but if the same agent that wrote the code also writes the tests, the tests inherit the code's assumptions. That is the builder grading their own work, just faster. The check should start somewhere else, with the acceptance criteria on the ticket and a testing layer that has no idea how the builder meant the feature to work.
What should QA check before an AI agent moves to the next stage?
Six things: functional requirements against the spec, realistic test data that is messy, stale, and incomplete, permissions and security, edge cases, tool use and integrations including timeouts, and acceptance criteria, where every requirement gets a clear pass or fail. Each answer comes from the spec and the ticket. Nothing moves on until the criteria are met.
What does "not covered" mean in a test report?
It means nothing tests that acceptance criterion yet. Not testable means the harness cannot observe the criterion from where it runs, so a person checks it or a different kind of check is built. Neither should ever be counted as passing. A green report that hides its gaps is worse than a red one, because people act on it.
How do you use a second AI model for adversarial code review?
Pick a model from a different vendor than the one that helped write the code, and tell it to find what breaks. Name the files, the change, and the kinds of risk to look for, and let it read the code around the change. Then check each finding against the code, because some are real bugs, some are nits, and some are overreach.
Mark Holland builds production AI systems for regulated healthcare, with a focus on testing, governance, and making AI dependable after go-live.