Code that runs is not necessarily secure. Code that works is not necessarily free of hidden dangers.
Your project has been running in production for three months. Everything is fine.
Until one day, a user stumbles upon a "minor issue": they type a special character into the search box, and the page crashes. You dig through the logs and discover that the AI, when generating the code, never escaped the user's input.
You begin to worry: could this be just an isolated oversight? You review more of the code. The findings send a chill down your spine—the API written by the AI has no rate limiting, database queries are not parameterized, the file types users upload are never validated, and error messages expose the database connection string directly.
All of this code "runs." It works normally—until some boundary condition trips a hidden vulnerability.
You were not careless. You simply never established an acceptance system.
5.1 Why an Independent Acceptance Process Is Needed
In conventional AI coding practice, "acceptance" too often means the developer glances at the code, figures it looks "good enough," and waves it through.
But here is the problem: AI-generated code is almost always syntactically correct—the genuine defects hide at the level of logic and architecture. A quick glance will not reveal them. Running the code may also look fine—until a boundary condition triggers a bug.
This is precisely why an independent, structured acceptance system is required.
5.2 The Design Logic Behind the Three Lines of Defense
At the heart of the acceptance system is a design of "three lines of defense." Why three? Because errors in AI coding occur at three levels, and you need detection methods at three corresponding levels.
Line one: the functional defense—checks "does the code run?"
This is the most intuitive check. Does the code run? Is the functionality correct? You write test cases, run the tests, and review the results. If you ask the AI to implement "user registration," it writes the code, you run the tests—registration succeeds, the password is stored in the database, login succeeds. The functional defense passes.
But the functional defense has a blind spot: it only checks "whether the code runs as expected," not "whether the code runs in the correct way." Your registration feature works, yet the password is stored in plaintext—and functional tests will miss this. Because the input to the functional test is "username + password," the output is "registration successful," and the test case never examines what actually ends up in the database.
Line two: the architecture defense—checks "does the code follow the blueprint?"
This is a defense unique to AI coding. While implementing a feature, the AI can easily "help itself" to changes it should never make—for instance, to patch a bug, it directly rewrites the database table structure. Functional tests will not catch this, because the feature still behaves correctly—but the architecture has already drifted.
The method behind the architecture defense is simple: compare the blueprint file (CONTEXT.md) against the actual code. Where the blueprint specifies "passwords must be encrypted with bcrypt," the architecture defense verifies whether the actual code really calls bcrypt. Where the blueprint specifies "errors must be raised through a unified AppException," the architecture defense checks whether the actual code uses AppException.
Line three: the security defense—checks "has the code introduced security risks?"
This is the most easily overlooked defense. AI-generated code tends to be "functionally correct yet security-fragile." The reason is straightforward: the AI's training data contains a great deal of code that "works but is not secure." It learned how to write features, but it never learned how to write secure code.
Consider an example of how the three lines work together. Suppose we have the same bug—the login API returns a 500 error when the password is wrong:
- The functional defense sees: the status code is wrong, it needs fixing.
- The architecture defense sees: the error-handling logic does not live in the unified exception layer, it needs refactoring.
- The security defense sees: the error message exposes database connection information directly, it needs fixing.
The same bug, yet the three lines of defense see three different strata of the problem. Fix only the first layer, and the problems on the second and third layers remain. This is why three lines are needed—each layer covers the blind spots that the layer above it cannot reach.
Novices typically attend to only the first layer. Experienced developers check all three.
5.3 How to Design Acceptance Criteria
Acceptance criteria are not test cases. Test cases verify "whether the code runs as expected." Acceptance criteria verify "whether the code was completed according to the constraints." The two are fundamentally different.
A well-designed acceptance criterion should include three kinds of checks:
Type one: functional checks—Does the core flow work end to end? Are edge cases handled? Are error paths covered? These can be captured by test cases.
Type two: constraint checks—Are the technology-stack conventions followed? Are naming conventions respected? Are the architecture principles honored? These are the "hard constraints" set out in the blueprint, and the AI readily violates them without realizing it.
Type three: quality checks—Is the code within a reasonable line count? Is there duplicated code? Are there unsafe API calls? These are "soft constraints"—they have no crisp pass/fail line, but they require human judgment.
Here is a comparison between a "good acceptance criterion" and a "bad one":
A bad acceptance criterion (too vague to tell whether the work is done):
"The login feature is complete."
A good acceptance criterion (precise, quantifiable, verifiable):
"The login feature is complete, per these acceptance criteria:
- Passwords are compared using bcrypt
- The JWT includes user_id and expires after 2 hours
- Errors return a unified 401 AppError
- Five consecutive failures lock the account for 30 minutes"
A bad acceptance criterion is tantamount to saying nothing at all. A good one lets both the AI and the developer judge "is it done?" with precision.
5.4 The Acceptance Checklist
The following is a complete acceptance checklist. You can adjust it to fit your project:
Functional Acceptance
- All requirements have been implemented
- The main flow runs correctly
- Edge cases are handled (empty data, abnormal values, extreme conditions)
- UI interactions behave as expected (loading states, error prompts, empty states)
Code Acceptance
- Code style is consistent with the project (indentation, naming, comments)
- No obvious code-quality issues (duplicated code, overlong functions, poor naming)
- No dead code (uncalled functions, unused variables)
- Error handling is sound (does not swallow exceptions, does not leak sensitive information)
Architecture Acceptance
- No core code that should not have been touched was modified
- No unnecessary dependencies or abstractions were introduced
- No single file has bloated excessively (suggested cap: 300 lines)
- New code is consistent with the project's directory structure
- API design follows the project's conventions
Security Acceptance
- User input is validated or escaped
- Sensitive endpoints enforce access control
- No hardcoded keys or credentials
- Database queries use parameterized queries or an ORM
- No data fields that should not be exposed are returned
5.5 The Acceptance Report
Once acceptance is complete, a structured acceptance report should be produced. Here is a template:
## Acceptance Report: Milestone X
### Conclusion: PASS / NEEDS_FIX / REBUILD
### Functional Acceptance
- [x] All requirements implemented
- [x] Main flow normal
- [ ] Edge cases: empty search keyword not handled
### Code Acceptance
- [x] Code style consistent
- [x] No duplicated code
- [x] Error handling sound
### Architecture Acceptance
- [x] Core code not tampered with
- [x] No unnecessary dependencies introduced
- [x] Directory structure compliant
### Security Acceptance
- [x] Input validation complete
- [x] Access control in place
- [x] No hardcoded credentials
### Fix Suggestions (only when NEEDS_FIX)
1. Add an empty-string check in the search function
2. Return the full list when the search keyword is empty
5.6 Detecting Architecture Drift During Acceptance
Architecture drift is the most insidious and destructive problem in AI coding. While implementing a feature, the AI may "help itself" to changes in places it should leave untouched.
The Three Signals in Detail
Signal one: tampering with the foundation
If the AI has modified code in the following categories, it warrants an immediate REBUILD:
- Database connection configuration
- Authentication and authorization logic
- Global middleware
- Core utility functions
- Shared data-model definitions
Why it happens: the AI decides that implementing the current feature "requires" modifying foundational code. But usually there is no such genuine need—the AI has simply taken a shortcut.
Signal two: over-engineering
The AI has introduced unnecessary complexity:
- Abstraction layers added for simple scenarios (interfaces, factories, strategy patterns)
- Third-party libraries the project does not need
- Configuration options the current feature does not require
Why it happens: the AI leans toward "just in case" design rather than "just enough" design.
Signal three: runaway size
The AI has piled too much code into a single file:
- A component file exceeding 300 lines
- A utility file cramming in multiple unrelated functions
- An API route handling several unrelated requests
Why it happens: when "appending code," the AI does not proactively refactor. It simply adds the new functionality on top of the existing file, causing that file to balloon.
Detection Methods
How do you detect architecture drift?
1. Use git diff to review the list of changed files
Unexpected file modifications → possible tampering with the foundation
2. Check the length of newly added files
A new file over 300 lines → possible runaway size
3. Check newly added dependencies
An unexpected dependency appearing in package.json → possible over-engineering
4. Compare against the blueprint's directory structure
Code placed where the blueprint did not specify → possible architecture drift
5.7 Security Review During Acceptance
Security review is not optional. AI-generated code carries a set of common "security blind spots."
Common Security Problems
Information leakage: the API returns sensitive fields from the database (password hashes, internal IDs)
// ❌ Unsafe: returns the entire user object return Response.json(user) // ✅ Safe: returns only the needed fields return Response.json({ id: user.id, name: user.name })Missing permissions: sensitive endpoints lack permission checks
// ❌ Unsafe: anyone can delete a user DELETE /api/users/:id // ✅ Safe: only admins can delete users DELETE /api/users/:id // requires admin roleInsufficient input validation: user input flows directly into database queries or page rendering
// ❌ Unsafe: directly concatenating user input db.query(`SELECT * FROM users WHERE name = '${input}'`) // ✅ Safe: using a parameterized query db.query('SELECT * FROM users WHERE name = ?', [input])
A Method for Security Review
One effective approach is to have the AI perform a security self-check:
Please conduct a security review of the following code, checking for:
1. SQL injection risks
2. XSS risks
3. Completeness of permission checks
4. Any data being exposed that should not be
5. Hardcoded keys or credentials
5.8 Automating Acceptance
For mature projects, acceptance can be partially automated:
- Unit tests: run automatically to confirm the core logic is correct
- Lint checks: automatically inspect code style and common issues
- Type checking: TypeScript projects automatically verify types
- Security scanning: automatically scan for known vulnerabilities
But automation cannot replace human acceptance. Automated checks can surface only "problems at the code level"; they cannot surface "problems at the design level"—and the latter requires human judgment grounded in the blueprint.
Chapter Summary
The acceptance system is an indispensable stage in AI coding. The three lines of defense—functional, architectural, and security—cover the three levels at which AI coding goes wrong, each layer closing the blind spots the layer above it cannot reach. The essence of acceptance criteria is "verifiability": precise enough that both the AI and the developer can determine whether the work is done. The three signals of architecture drift (tampering with the foundation, over-engineering, and runaway size) are the hazards that demand the most vigilance during acceptance. Remember: code that runs is not necessarily correct, and code that works is not necessarily free of hidden dangers. In the next chapter, we will turn to project orchestration—how to manage the development order and quality gates when a project spans multiple features.