{"slug":"code-review-patterns","title":"Code Review, QA Validation, and Pre-Release Gates","tags":["code-review","qa","testing","linting","security","pre-release"],"agent_summary":"Code review checklist, automated QA gates, pre-release validation — TypeScript/lint checks, security review patterns, accessibility gates, performance budgets, and the structured fix attestation format for bug commits.","trigger_phrases":["code review","QA validation","pre-release gate","code review checklist","lint","type check","accessibility audit","fix attestation"],"runnable":false,"markdown":"\n## Overview\n\nCode review and QA gates exist at multiple levels: automated checks (lint, types, tests), manual review (logic, security, patterns), and visual verification (UI, accessibility). Nothing ships without passing all gates.\n\n## Automated Gate Checklist\n\nRun before every PR merge:\n\n```bash\n# TypeScript\nnpx tsc --noEmit\n\n# Lint\nnpm run lint\n\n# Tests\nnpm test\n\n# Build\nnpm run build\n```\n\nAll four must pass. Any failure blocks the merge.\n\n## Code Review Checklist\n\n### Logic and Correctness\n```\n[ ] Function does what its name says\n[ ] Edge cases handled: null, empty array, zero, negative numbers\n[ ] Async errors caught (await without try/catch = unhandled rejection)\n[ ] No off-by-one errors in loops\n[ ] Correct comparison: === not ==\n[ ] Race conditions considered for async operations\n```\n\n### Security\n```\n[ ] No secrets or API keys in code or comments\n[ ] User input validated before use (Zod at API boundary)\n[ ] SQL queries parameterized (no string interpolation)\n[ ] File paths validated against traversal (path.resolve, path.normalize)\n[ ] Auth check before every protected operation\n[ ] Rate limiting on public endpoints\n[ ] RLS enabled on new Supabase tables\n```\n\n### TypeScript Quality\n```\n[ ] No `any` types (use `unknown` + type narrowing instead)\n[ ] No non-null assertions (!) without a comment explaining why it's safe\n[ ] Error types are typed, not `catch (e)` with implicit `any`\n[ ] Interfaces preferred over type aliases for objects\n[ ] Exported types documented with JSDoc if complex\n```\n\n### Performance\n```\n[ ] No N+1 queries (batch or join instead)\n[ ] Large lists virtualized\n[ ] Images have explicit dimensions\n[ ] No blocking operations in React render path\n[ ] No unbounded array operations (.collect() equivalent)\n```\n\n### Maintainability\n```\n[ ] Function under 50 lines (extract if longer)\n[ ] No magic numbers (use named constants)\n[ ] Naming is clear without comments\n[ ] Duplication extracted to shared utility\n[ ] Complex logic has a comment explaining WHY (not what)\n```\n\n## Security Review Patterns\n\n### Auth Bypass Check\n\n```typescript\n// WRONG — missing auth check\nexport async function DELETE(request: NextRequest, { params }) {\n  const { id } = await params;\n  await db.from(\"projects\").delete().eq(\"id\", id);  // anyone can delete anything\n  return new Response(null, { status: 204 });\n}\n\n// RIGHT — ownership verification\nexport async function DELETE(request: NextRequest, { params }) {\n  const { userId } = await auth();\n  if (!userId) return NextResponse.json({ error: \"Unauthorized\" }, { status: 401 });\n\n  const { id } = await params;\n  const project = await db.from(\"projects\").select().eq(\"id\", id).single();\n  if (!project || project.user_id !== userId) {\n    return NextResponse.json({ error: \"Not found\" }, { status: 404 });  // don't reveal existence\n  }\n\n  await db.from(\"projects\").delete().eq(\"id\", id);\n  return new Response(null, { status: 204 });\n}\n```\n\n### Input Validation Check\n\n```typescript\n// WRONG — unvalidated input\nexport async function POST(request: NextRequest) {\n  const { name, userId } = await request.json();\n  await db.from(\"projects\").insert({ name, userId });  // no validation\n}\n\n// RIGHT — validated at boundary\nconst Schema = z.object({ name: z.string().min(1).max(100) });\n\nexport async function POST(request: NextRequest) {\n  const { userId } = await auth();\n  if (!userId) return errorResponse(\"Unauthorized\", 401);\n\n  const parsed = Schema.safeParse(await request.json());\n  if (!parsed.success) return errorResponse(\"Invalid\", 422, parsed.error.flatten());\n\n  await db.from(\"projects\").insert({ name: parsed.data.name, user_id: userId });\n}\n```\n\n## Fix Attestation Format\n\nEvery bug fix commit must include:\n\n```\nfix: [short description]\n\nFIX ATTESTATION:\n- Broken: [observable symptom]\n- Root cause: [actual bug]\n- Changed: [files modified and what changed]\n- Verified: [how confirmed it's fixed]\n- Regression risk: [side effects or \"none\"]\n- Timestamp: 2026-05-12T10:00:00Z\n```\n\nExample:\n\n```\nfix: API returns 500 when query contains special characters\n\nFIX ATTESTATION:\n- Broken: GET /api/search?q=C%2B%2B returns 500 Internal Server Error\n- Root cause: Missing encodeURIComponent on user input before vector DB query\n- Changed: src/app/api/search/route.ts — added input sanitization at line 23\n- Verified: Added test case for special chars in test/api-search.test.ts, passes\n- Regression risk: None — sanitization is additive, doesn't change valid queries\n- Timestamp: 2026-05-12T10:30:00Z\n```\n\n## QA Validation Gates\n\n### Visual Verification (UI changes)\n\n```bash\n# Screenshot before and after\nnpx playwright screenshot --full-page http://localhost:3000 before.png\n# [make changes]\nnpx playwright screenshot --full-page http://localhost:3000 after.png\n```\n\nRequired for: any UI change, new page, style updates. Screenshots attached to PR.\n\n### API Verification\n\n```bash\n# Health check\ncurl -s http://localhost:3000/api/health | jq .\n\n# Auth check (should return 401)\ncurl -s http://localhost:3000/api/projects | jq .error\n\n# Authenticated request\ncurl -s -H \"Authorization: Bearer $TOKEN\" http://localhost:3000/api/projects | jq .\n```\n\n### Accessibility Gate\n\n```bash\n# Run axe accessibility check\nnpx @axe-core/cli http://localhost:3000\n\n# Or in Playwright test\nimport { checkA11y } from \"axe-playwright\";\nawait checkA11y(page, undefined, { detailedReport: true });\n```\n\nMust pass: no critical or serious violations.\n\n## Delivery Report Format\n\nEvery delivery includes:\n\n```\nDELIVERED: [what was built]\nPROOF: [screenshot paths or test output]\nVALIDATION: lint: PASS | types: PASS | tests: PASS | build: PASS\nVERIFICATION: CODE | VISUAL | FULL\n```\n\nNo VALIDATION block = rejected by Carlos. No PROOF = no VISUAL or FULL verification claim.\n","html":"<h2>Overview</h2>\n<p>Code review and QA gates exist at multiple levels: automated checks (lint, types, tests), manual review (logic, security, patterns), and visual verification (UI, accessibility). Nothing ships without passing all gates.</p>\n<h2>Automated Gate Checklist</h2>\n<p>Run before every PR merge:</p>\n<pre><code class=\"language-bash\"># TypeScript\nnpx tsc --noEmit\n\n# Lint\nnpm run lint\n\n# Tests\nnpm test\n\n# Build\nnpm run build\n</code></pre>\n<p>All four must pass. Any failure blocks the merge.</p>\n<h2>Code Review Checklist</h2>\n<h3>Logic and Correctness</h3>\n<pre><code>[ ] Function does what its name says\n[ ] Edge cases handled: null, empty array, zero, negative numbers\n[ ] Async errors caught (await without try/catch = unhandled rejection)\n[ ] No off-by-one errors in loops\n[ ] Correct comparison: === not ==\n[ ] Race conditions considered for async operations\n</code></pre>\n<h3>Security</h3>\n<pre><code>[ ] No secrets or API keys in code or comments\n[ ] User input validated before use (Zod at API boundary)\n[ ] SQL queries parameterized (no string interpolation)\n[ ] File paths validated against traversal (path.resolve, path.normalize)\n[ ] Auth check before every protected operation\n[ ] Rate limiting on public endpoints\n[ ] RLS enabled on new Supabase tables\n</code></pre>\n<h3>TypeScript Quality</h3>\n<pre><code>[ ] No `any` types (use `unknown` + type narrowing instead)\n[ ] No non-null assertions (!) without a comment explaining why it's safe\n[ ] Error types are typed, not `catch (e)` with implicit `any`\n[ ] Interfaces preferred over type aliases for objects\n[ ] Exported types documented with JSDoc if complex\n</code></pre>\n<h3>Performance</h3>\n<pre><code>[ ] No N+1 queries (batch or join instead)\n[ ] Large lists virtualized\n[ ] Images have explicit dimensions\n[ ] No blocking operations in React render path\n[ ] No unbounded array operations (.collect() equivalent)\n</code></pre>\n<h3>Maintainability</h3>\n<pre><code>[ ] Function under 50 lines (extract if longer)\n[ ] No magic numbers (use named constants)\n[ ] Naming is clear without comments\n[ ] Duplication extracted to shared utility\n[ ] Complex logic has a comment explaining WHY (not what)\n</code></pre>\n<h2>Security Review Patterns</h2>\n<h3>Auth Bypass Check</h3>\n<pre><code class=\"language-typescript\">// WRONG — missing auth check\nexport async function DELETE(request: NextRequest, { params }) {\n  const { id } = await params;\n  await db.from(\"projects\").delete().eq(\"id\", id);  // anyone can delete anything\n  return new Response(null, { status: 204 });\n}\n\n// RIGHT — ownership verification\nexport async function DELETE(request: NextRequest, { params }) {\n  const { userId } = await auth();\n  if (!userId) return NextResponse.json({ error: \"Unauthorized\" }, { status: 401 });\n\n  const { id } = await params;\n  const project = await db.from(\"projects\").select().eq(\"id\", id).single();\n  if (!project || project.user_id !== userId) {\n    return NextResponse.json({ error: \"Not found\" }, { status: 404 });  // don't reveal existence\n  }\n\n  await db.from(\"projects\").delete().eq(\"id\", id);\n  return new Response(null, { status: 204 });\n}\n</code></pre>\n<h3>Input Validation Check</h3>\n<pre><code class=\"language-typescript\">// WRONG — unvalidated input\nexport async function POST(request: NextRequest) {\n  const { name, userId } = await request.json();\n  await db.from(\"projects\").insert({ name, userId });  // no validation\n}\n\n// RIGHT — validated at boundary\nconst Schema = z.object({ name: z.string().min(1).max(100) });\n\nexport async function POST(request: NextRequest) {\n  const { userId } = await auth();\n  if (!userId) return errorResponse(\"Unauthorized\", 401);\n\n  const parsed = Schema.safeParse(await request.json());\n  if (!parsed.success) return errorResponse(\"Invalid\", 422, parsed.error.flatten());\n\n  await db.from(\"projects\").insert({ name: parsed.data.name, user_id: userId });\n}\n</code></pre>\n<h2>Fix Attestation Format</h2>\n<p>Every bug fix commit must include:</p>\n<pre><code>fix: [short description]\n\nFIX ATTESTATION:\n- Broken: [observable symptom]\n- Root cause: [actual bug]\n- Changed: [files modified and what changed]\n- Verified: [how confirmed it's fixed]\n- Regression risk: [side effects or \"none\"]\n- Timestamp: 2026-05-12T10:00:00Z\n</code></pre>\n<p>Example:</p>\n<pre><code>fix: API returns 500 when query contains special characters\n\nFIX ATTESTATION:\n- Broken: GET /api/search?q=C%2B%2B returns 500 Internal Server Error\n- Root cause: Missing encodeURIComponent on user input before vector DB query\n- Changed: src/app/api/search/route.ts — added input sanitization at line 23\n- Verified: Added test case for special chars in test/api-search.test.ts, passes\n- Regression risk: None — sanitization is additive, doesn't change valid queries\n- Timestamp: 2026-05-12T10:30:00Z\n</code></pre>\n<h2>QA Validation Gates</h2>\n<h3>Visual Verification (UI changes)</h3>\n<pre><code class=\"language-bash\"># Screenshot before and after\nnpx playwright screenshot --full-page http://localhost:3000 before.png\n# [make changes]\nnpx playwright screenshot --full-page http://localhost:3000 after.png\n</code></pre>\n<p>Required for: any UI change, new page, style updates. Screenshots attached to PR.</p>\n<h3>API Verification</h3>\n<pre><code class=\"language-bash\"># Health check\ncurl -s http://localhost:3000/api/health | jq .\n\n# Auth check (should return 401)\ncurl -s http://localhost:3000/api/projects | jq .error\n\n# Authenticated request\ncurl -s -H \"Authorization: Bearer $TOKEN\" http://localhost:3000/api/projects | jq .\n</code></pre>\n<h3>Accessibility Gate</h3>\n<pre><code class=\"language-bash\"># Run axe accessibility check\nnpx @axe-core/cli http://localhost:3000\n\n# Or in Playwright test\nimport { checkA11y } from \"axe-playwright\";\nawait checkA11y(page, undefined, { detailedReport: true });\n</code></pre>\n<p>Must pass: no critical or serious violations.</p>\n<h2>Delivery Report Format</h2>\n<p>Every delivery includes:</p>\n<pre><code>DELIVERED: [what was built]\nPROOF: [screenshot paths or test output]\nVALIDATION: lint: PASS | types: PASS | tests: PASS | build: PASS\nVERIFICATION: CODE | VISUAL | FULL\n</code></pre>\n<p>No VALIDATION block = rejected by Carlos. No PROOF = no VISUAL or FULL verification claim.</p>\n"}