Module 1: Only 3% Trust It — Why, and What to Do

Your First Verification Framework

Your First Verification Framework

Capsule overview

You've traveled the full arc of this module: the problem (3%), the extremes (accept everything / reject everything), and the principle (calibrated trust). Now you need something concrete — a framework you can use tomorrow. In this capsule you build your first verification framework: a 3-level process that tells you what to always review, what to sometimes review, and what to generally trust.

This framework isn't final. In the following modules you'll expand it with mental models (module 2), hallucination detection (module 3), and a complete professional checklist (module 4). But starting today, you already have something that works. The goal is for you to go from "I review without criteria" to "I have a process" in the next 20 minutes.


The 3-Level Framework

Level 1: ALWAYS review (Red Zone)

These are the areas where an error has critical impact. No exceptions. Even if you trust Claude Code, even if you're in a hurry, even if the code looks perfect.

🔴 ALWAYS review:

1. SECURITY
   - Are there hardcoded secrets? (API keys, passwords, tokens)
   - Are the SQL queries parameterized?
   - Do the sensitive endpoints have authentication?
   - Do the tokens expire?
   - Are the inputs sanitized?

2. BUSINESS LOGIC
   - Does the code do what the business needs?
   - Are the calculations correct? (prices, discounts, taxes)
   - Are the conditions correct? (>, <, >=, <=, ==)
   - Do the filters include/exclude the right thing?

3. DATA
   - Are the database operations correct?
   - Is there a risk of data corruption or loss?
   - Are the migrations reversible?
   - Is sensitive data protected?

Typical time: 10-30 minutes depending on complexity.

Golden rule: If you're not sure whether something falls into this level, it falls into this level.

Level 2: FREQUENTLY review (Yellow Zone)

These areas require attention but not exhaustiveness. An error here causes problems but not catastrophes. Review with a critical eye but not line by line.

🟡 FREQUENTLY review:

1. EDGE CASES
   - What happens with null/None?
   - What happens with empty lists?
   - What happens with the first/last element?
   - What happens with empty strings?
   - What happens with negative numbers or zero?

2. ERROR HANDLING
   - Are errors handled or do they crash silently?
   - Are the error messages useful (don't expose internal info)?
   - Is there try/except where there should be?
   - Are the HTTP status codes correct?

3. INPUT VALIDATION
   - Are the inputs validated before processing them?
   - Are the types correct?
   - Are the ranges reasonable? (age > 0, price >= 0)
   - Do the strings have a length limit?

4. NAMING AND CLARITY
   - Do the function names describe what they do?
   - Do the variables have descriptive names?
   - Is the code readable without comments?

Typical time: 5-15 minutes.

When to skip: If you already did an exhaustive Level 1 review and the code is low-complexity, you can do a quick Level 2 review.

Level 3: GENERALLY trust (Green Zone)

These areas are low-risk and AI is consistently good at them. A quick visual review is enough.

🟢 GENERALLY trust:

1. BOILERPLATE
   - Project setup (pip, requirements, etc.)
   - Standard imports
   - Basic framework configuration
   - File structure

2. FORMATTING AND STYLE
   - Indentation
   - Import order
   - Style conventions
   - String formatting

3. DOCUMENTATION
   - Docstrings
   - Explanatory comments
   - Basic README
   - Type hints

4. STANDARD CODE
   - Well-known patterns (singleton, factory)
   - Basic CRUD operations
   - Serialization/deserialization
   - Simple type conversions

Typical time: 1-3 minutes (visual review).

When to go deeper: If something in the Green Zone "feels off" — an import you don't recognize, an unusual configuration — promote it to Yellow Zone and review it in more detail.


The Framework in Action: Complete Example

Claude Code generates a user-management module. Let's apply the framework:

from fastapi import FastAPI, HTTPException, Depends
from fastapi.security import OAuth2PasswordBearer
from pydantic import BaseModel, EmailStr, Field
from passlib.context import CryptContext
from datetime import datetime, timedelta
from typing import Optional
import jwt
import os

app = FastAPI()

SECRET_KEY = os.getenv("SECRET_KEY", "fallback-dev-key")
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

users_db = {}

class UserCreate(BaseModel):
    email: EmailStr
    password: str = Field(..., min_length=8)
    name: str = Field(..., min_length=1, max_length=100)

class UserResponse(BaseModel):
    id: str
    email: str
    name: str
    created_at: datetime

class Token(BaseModel):
    access_token: str
    token_type: str

def hash_password(password: str) -> str:
    return pwd_context.hash(password)

def verify_password(plain_password: str, hashed_password: str) -> bool:
    return pwd_context.verify(plain_password, hashed_password)

def create_access_token(data: dict) -> str:
    to_encode = data.copy()
    expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    to_encode.update({"exp": expire})
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)

async def get_current_user(token: str = Depends(oauth2_scheme)):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        email = payload.get("sub")
        if email is None:
            raise HTTPException(status_code=401, detail="Invalid token")
    except jwt.ExpiredSignatureError:
        raise HTTPException(status_code=401, detail="Token expired")
    except jwt.PyJWTError:
        raise HTTPException(status_code=401, detail="Invalid token")
    
    user = users_db.get(email)
    if user is None:
        raise HTTPException(status_code=401, detail="User not found")
    return user

@app.post("/register", response_model=UserResponse)
async def register(user: UserCreate):
    if user.email in users_db:
        raise HTTPException(status_code=400, detail="Email already registered")
    
    hashed_password = hash_password(user.password)
    new_user = {
        "id": str(len(users_db) + 1),
        "email": user.email,
        "name": user.name,
        "password_hash": hashed_password,
        "created_at": datetime.utcnow()
    }
    users_db[user.email] = new_user
    return UserResponse(**new_user)

@app.post("/token", response_model=Token)
async def login(email: str, password: str):
    user = users_db.get(email)
    if not user or not verify_password(password, user["password_hash"]):
        raise HTTPException(status_code=401, detail="Invalid credentials")
    
    access_token = create_access_token(data={"sub": user["email"]})
    return Token(access_token=access_token, token_type="bearer")

@app.get("/me", response_model=UserResponse)
async def get_me(current_user: dict = Depends(get_current_user)):
    return UserResponse(**current_user)

Applying the framework

Level 1 (Red Zone) — ALWAYS review:

🔴 SECURITY:
✅ Secret — uses os.getenv(), good. BUT "fallback-dev-key" is 
   dangerous if you forget to configure it in production.
   → ACTION: Change to os.getenv("SECRET_KEY") with no fallback, 
   so it fails if it's not configured.

✅ Password hashing — uses bcrypt via passlib. Correct.

✅ Token expiration — has ACCESS_TOKEN_EXPIRE_MINUTES = 30. 
   Token includes "exp". Good.

✅ Token validation — handles ExpiredSignatureError and PyJWTError. 
   Good.

⚠️ Login endpoint — accepts email and password as query params.
   → PROBLEM: Passwords in query params show up in server logs.
   → ACTION: Change to a request body with OAuth2PasswordRequestForm.

⚠️ Rate limiting — none. An attacker can brute force.
   → ACTION: Add rate limiting to the /token endpoint.

🔴 BUSINESS LOGIC:
✅ Registration — checks for a duplicate email. Good.
✅ Login — compares the hashed password. Good.

⚠️ ID generation — uses len(users_db) + 1. If a user is deleted, 
   the IDs get reused.
   → ACTION: Use UUID instead of a counter.

Level 2 (Yellow Zone) — Review frequently:

🟡 EDGE CASES:
⚠️ What happens if the email has uppercase? "User@Email.com" and 
   "user@email.com" would be different users.
   → ACTION: Normalize the email to lowercase.

⚠️ A password with only spaces passes min_length=8.
   → ACTION: Add complexity validation (or at least strip).

🟡 ERROR HANDLING:
✅ 401 for invalid credentials — doesn't reveal whether the email exists 
   or not. Good (a security best practice).
✅ 400 for a duplicate email — correct.

🟡 VALIDATION:
✅ EmailStr validates the email format — correct.
✅ min_length on password and name — correct.

Level 3 (Green Zone) — Generally trust:

🟢 BOILERPLATE:
✅ Imports — they all exist and are correct.
✅ FastAPI setup — standard.
✅ Pydantic models — well structured.
✅ Type hints — consistent.

Result

Total review time: ~15 minutes

Found:
- 1 critical issue (password in query params)
- 2 high issues (fallback secret, no rate limiting)
- 2 medium issues (ID generation, email normalization)
- 1 low issue (password of only spaces)

Decision: EDIT (don't regenerate)
- 85% of the code is fine
- The issues are specific and fixable
- The structure and approach are correct

Quick Framework Checklist

For daily use, this is your pocket checklist:

Before accepting Claude Code's code:

🔴 RED ZONE (always):
□ Are there hardcoded secrets?
□ Are the SQL queries safe?
□ Do the sensitive endpoints have auth?
□ Is the business logic correct?
□ Is the data protected?

🟡 YELLOW ZONE (frequently):
□ Does it handle null/empty/edge cases?
□ Are the errors handled correctly?
□ Are the inputs validated?
□ Are the names descriptive?

🟢 GREEN ZONE (quick glance):
□ Do the imports look correct?
□ Is the structure standard?
□ Is the formatting consistent?

If everything is OK → Accept
If there are issues in Green/Yellow → Edit
If there are issues in Red → Don't accept without fixing

Connection to the Project

How it connects to the capstone project (Module 8)

In module 8 you'll receive a FastAPI codebase with 15-20 planted problems. Your job will be to do a complete professional code review using this framework (and the expanded versions from modules 2-7). The problems will be distributed across the 3 zones:

  • Red Zone: Hallucinations, security holes, incorrect business logic
  • Yellow Zone: Unhandled edge cases, incomplete error handling
  • Green Zone: Some minor issues that shouldn't consume your time

The framework helps you prioritize: attack the Red Zone first, the Yellow Zone next, the Green Zone last (or never).


Troubleshooting

Problem 1: "The framework feels too simple"

Cause: It's simple on purpose. It's your FIRST framework — a starting point. Solution: In the following modules you'll expand it with mental models (module 2), hallucination detection techniques (module 3), a professional checklist with 15+ items (module 4), and error patterns (module 5). This framework is the skeleton; the following modules add the muscle.

Problem 2: "I don't know which zone something falls into"

Cause: Some types of code are on the border between zones. Solution: Rule: when in doubt, promote to the higher zone. It's better to review something in Yellow Zone that was actually Green Zone, than to trust something in Red Zone by treating it as Yellow.

Problem 3: "It takes too long to review the Red Zone"

Cause: You're reviewing more than necessary or the code is genuinely complex. Solution: For the Red Zone, focus on the 5 specific checks in the quick checklist. You don't need to understand every line — you need to verify that those 5 points are covered. If the code is genuinely complex (auth with roles, granular permissions, multi-tenancy), then yes, it takes time — and it's time well invested.


Exercises

Exercise 1: Apply the framework (Easy)

Classify each item into Red / Yellow / Green Zone:

  1. An import of datetime
  2. A function that calculates the tax rate based on the user's state
  3. A SQL query that searches for users by name
  4. A standard Dockerfile for Python
  5. A function that encrypts credit card data
See solution
  1. Import of datetime → Green Zone. Standard Python import, zero risk. Visual review.
  2. Tax rate calculation → Red Zone. Business logic with financial impact. Review that the rates are correct, that the conditions include all states, that the calculations are precise.
  3. SQL query by name → Red/Yellow Zone. Red if it uses string concatenation (SQL injection). Yellow if it uses parameterized queries (verify the query logic).
  4. Standard Dockerfile → Green Zone. Standard pattern. Review base versions quickly.
  5. Encrypt card data → Red Zone (Critical). Security + compliance (PCI-DSS). Review the algorithm, key management, compliance. It possibly shouldn't be generated with AI without a security expert.

Exercise 2: Review with the framework (Medium)

Apply the 3-level framework to this code generated by Claude Code:

from fastapi import FastAPI, Query
from typing import List, Optional
import sqlite3

app = FastAPI()

def get_db():
    conn = sqlite3.connect("tasks.db")
    return conn

@app.get("/tasks")
async def search_tasks(
    query: Optional[str] = None,
    status: Optional[str] = None,
    limit: int = Query(default=10, ge=1, le=100)
):
    conn = get_db()
    cursor = conn.cursor()
    
    sql = "SELECT * FROM tasks WHERE 1=1"
    
    if query:
        sql += f" AND title LIKE '%{query}%'"
    if status:
        sql += f" AND status = '{status}'"
    
    sql += f" LIMIT {limit}"
    
    cursor.execute(sql)
    tasks = cursor.fetchall()
    conn.close()
    
    return {"tasks": tasks, "count": len(tasks)}

Document: (1) what you found in each zone, (2) what actions to take.

See solution

🔴 Red Zone — CRITICAL:

  1. SQL Injection. The line sql += f" AND title LIKE '%{query}%'" concatenates user input directly into the SQL. An attacker can inject arbitrary SQL.

    Example attack: query = "'; DROP TABLE tasks; --"
    

    Action: Use parameterized queries:

    sql += " AND title LIKE ?"
    params.append(f"%{query}%")
    cursor.execute(sql, params)
  2. Same problem with status: sql += f" AND status = '{status}'" — the same vulnerability.

🟡 Yellow Zone:

  1. The connection isn't closed in case of an error. If cursor.execute() fails, conn.close() never runs. A connection memory leak. Action: Use a context manager (with) or try/finally.

  2. It doesn't validate status. It accepts any string as status. Should it be an Enum? Action: Define valid status values.

  3. It returns raw tuples. sqlite3's fetchall() returns tuples, not dicts. The response has no column names. Action: Use row_factory = sqlite3.Row or map to a Pydantic model.

🟢 Green Zone:

  1. ✅ Correct imports
  2. ✅ Query parameter with validation (ge=1, le=100) — good
  3. ✅ General endpoint structure — OK

Decision: REJECT and regenerate (or edit significantly). The SQL injection is a deal-breaker. The structure needs fundamental changes (parameterized queries, connection management). It's faster to regenerate with a prompt that specifies "use parameterized queries and connection pooling."

Exercise 3: The module exercise — Analyze 3 snippets (Hard)

This is the integrative exercise for module 1. Analyze these 3 AI-generated snippets and for each one:

  1. Apply the 3-level framework
  2. Assign a % of trust
  3. Decide: accept, edit, or reject
  4. Justify your decision

Snippet A — Formatting utility:

from datetime import datetime
from typing import Optional

def format_timestamp(
    dt: Optional[datetime] = None,
    fmt: str = "%Y-%m-%d %H:%M:%S"
) -> str:
    if dt is None:
        dt = datetime.utcnow()
    return dt.strftime(fmt)

def time_ago(dt: datetime) -> str:
    now = datetime.utcnow()
    diff = now - dt
    
    if diff.days > 365:
        years = diff.days // 365
        return f"{years} year{'s' if years > 1 else ''} ago"
    elif diff.days > 30:
        months = diff.days // 30
        return f"{months} month{'s' if months > 1 else ''} ago"
    elif diff.days > 0:
        return f"{diff.days} day{'s' if diff.days > 1 else ''} ago"
    elif diff.seconds > 3600:
        hours = diff.seconds // 3600
        return f"{hours} hour{'s' if hours > 1 else ''} ago"
    elif diff.seconds > 60:
        minutes = diff.seconds // 60
        return f"{minutes} minute{'s' if minutes > 1 else ''} ago"
    else:
        return "just now"

Snippet B — Money transfer endpoint:

@app.post("/transfer")
async def transfer_money(
    from_account: str,
    to_account: str,
    amount: float
):
    sender = get_account(from_account)
    receiver = get_account(to_account)
    
    sender.balance -= amount
    receiver.balance += amount
    
    save_account(sender)
    save_account(receiver)
    
    return {"status": "success", "amount": amount}

Snippet C — Logging configuration:

import logging
import sys

def setup_logging(level: str = "INFO") -> logging.Logger:
    logger = logging.getLogger("app")
    logger.setLevel(getattr(logging, level.upper()))
    
    handler = logging.StreamHandler(sys.stdout)
    handler.setLevel(getattr(logging, level.upper()))
    
    formatter = logging.Formatter(
        "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
    )
    handler.setFormatter(formatter)
    logger.addHandler(handler)
    
    return logger
See solution

Snippet A — Formatting utility:

🔴 Red Zone: No security or business logic → N/A 🟡 Yellow Zone:

  • Edge case: time_ago with a future date → gives negative results. Handle it?
  • Edge case: diff.days exactly 365 → doesn't enter the years branch. Minor. 🟢 Green Zone: Correct imports, clear names, clean structure.

Trust: 80%. Low-risk utility. AI is good at this. Decision: Accept with a mental note that time_ago doesn't handle future dates. Review time: 3 minutes.


Snippet B — Money transfer:

🔴 Red Zone — MULTIPLE PROBLEMS:

  1. Doesn't validate amount — accepts negatives (reverse transfer), zero, or astronomical amounts
  2. Doesn't check for sufficient balance — the sender can go negative
  3. It's not atomic — if save_account(sender) works but save_account(receiver) fails, the money disappears
  4. Has no authentication — anyone can transfer from any account
  5. Uses float for money — floating point math causes precision errors (0.1 + 0.2 ≠ 0.3)
  6. No logging/auditing — financial transfers with no record

Trust: 5%. Financial business logic with multiple critical failures. Decision: REJECT. Regenerate with a detailed prompt that specifies validations, atomicity, auth, and Decimal for amounts. Review time: 10 minutes (to identify all the problems).


Snippet C — Logging configuration:

🔴 Red Zone: N/A (no security or business logic) 🟡 Yellow Zone:

  • getattr(logging, level.upper()) — if level isn't valid, it raises AttributeError. Could be handled with try/except.
  • It calls logger.addHandler without checking if it already exists — if you call setup_logging() multiple times, it adds duplicate handlers. 🟢 Green Zone: Correct imports, standard logging pattern, reasonable format.

Trust: 75%. Standard configuration with low risk. Decision: Edit. Add a duplicate-handlers check and handling for an invalid level. Review time: 4 minutes.


Meta-observation: The 3 snippets demonstrate calibration perfectly:

  • Snippet A (utility): high trust, accept quickly
  • Snippet B (financial): almost no trust, reject
  • Snippet C (config): medium-high trust, accept with minor edits

If you had applied the same level of review to all 3, you would have spent too much time on A and C, or too little on B.


Summary

In this capsule you learned:

  • The 3-level framework (Red / Yellow / Green) gives you a clear verification process
  • Red Zone (always review): security, business logic, data
  • Yellow Zone (frequently): edge cases, error handling, validation, naming
  • Green Zone (generally trust): boilerplate, formatting, documentation, standard patterns
  • The framework applies in minutes, not hours — it prioritizes where to invest time
  • When in doubt, promote to the higher zone
  • This framework is the starting point — modules 2-7 expand it

Next module: Mental Models for AI Code — the thinking frameworks that raise your verification judgment.


Additional resources

  1. OWASP Top 10 - The 10 most common vulnerabilities — your Red Zone checklist for security
  2. Python Security Best Practices - Security practices specific to Python
  3. FastAPI Security Tutorial - Official FastAPI security documentation
  4. SQLAlchemy — Preventing SQL Injection - How to use parameterized queries correctly
  5. Google — Code Review Developer Guide - Google's code review framework
  6. Clean Code — Robert C. Martin - Naming and clarity principles that apply to the Yellow Zone

Debugging & Code Review with Claude Code — Module 1, Capsule 05 Claude Code Agentic Development Path — Guide #6 of 11