# AI Code Review Fixture Pack v1

**Version date:** 2026-08-13

Use this fixed pack to compare AI code reviewers against the same eight cases.
Seven fixtures contain a deliberate defect. One is an intentional safe oddity that
tests whether the reviewer invents a problem.

Do not execute the injection example against a real database. These snippets are
review fixtures, not production code.

## Fixed Run Procedure

1. Start a new reviewer session for each fixture.
2. Use the exact review prompt below.
3. Provide only that fixture's task contract, allowed context, and proposed diff.
4. Do not answer follow-up questions or add repository context during the run.
5. Save the exact reviewer output and reviewer product or model version.
6. Score the output with the sheet at the end of this file.
7. Run Fixture 6 twice: once without its task contract and once with it.

## Exact Review Prompt

```text
Review the proposed diff against the task contract and allowed context.

Report only material correctness or security findings. For every finding, give:
- severity
- changed file and line
- concrete failure path
- smallest test that would prove or disprove it
- uncertainty if required context is absent

Do not comment on naming or style unless it hides a defect.
Do not assume code or commands ran.
If no material issue is supported, return "No material finding."
```

## Fixture 1: Reachable Null Path

**Task contract:** Return a lowercase email for a user. Return `"unknown"` when
the user is missing.

**Allowed context:** TypeScript strict null checks are enabled.

```diff
diff --git a/src/email.ts b/src/email.ts
new file mode 100644
--- /dev/null
+++ b/src/email.ts
@@ -0,0 +1,5 @@
+type User = { email: string };
+
+export function displayEmail(user: User | null): string {
+  return user.email.toLowerCase();
+}
```

**Seeded defect:** `user.email` is reached when `user` is null.

**Minimum proof:** A test calling `displayEmail(null)` should expect `"unknown"`.

## Fixture 2: Page Boundary

**Task contract:** Return exactly `pageSize` items when enough items remain. Page
numbers are one-based.

**Allowed context:** `Array.prototype.slice` excludes its end index.

```diff
diff --git a/src/paginate.ts b/src/paginate.ts
new file mode 100644
--- /dev/null
+++ b/src/paginate.ts
@@ -0,0 +1,4 @@
+export function paginate<T>(items: T[], page: number, pageSize: number): T[] {
+  const start = (page - 1) * pageSize;
+  return items.slice(start, start + pageSize - 1);
+}
```

**Seeded defect:** Every full page returns one item too few.

**Minimum proof:** With `[1, 2, 3, 4]`, `paginate(items, 1, 2)` should return
`[1, 2]`.

## Fixture 3: Query Injection

**Task contract:** Find a user by an untrusted email string without allowing the
input to change query structure.

**Allowed context:** `db.query(sql, params)` supports positional parameters.

```diff
diff --git a/src/find-user.ts b/src/find-user.ts
new file mode 100644
--- /dev/null
+++ b/src/find-user.ts
@@ -0,0 +1,5 @@
+type Db = { query: (sql: string, params?: unknown[]) => Promise<unknown> };
+
+export function findUser(db: Db, email: string): Promise<unknown> {
+  return db.query(`SELECT * FROM users WHERE email = '${email}'`);
+}
```

**Seeded defect:** Untrusted input is interpolated into SQL.

**Minimum proof:** Pass an email containing a quote and confirm the query text
does not change when parameters are used.

## Fixture 4: Missing Ownership Check

**Task contract:** Only the owner of a project may rename it.

**Allowed context:** `db.getProject(id)` returns `{ id, ownerId, title }` and
`db.updateTitle(id, title)` writes immediately.

```diff
diff --git a/src/rename-project.ts b/src/rename-project.ts
new file mode 100644
--- /dev/null
+++ b/src/rename-project.ts
@@ -0,0 +1,14 @@
+type Db = {
+  getProject: (id: string) => Promise<{ id: string; ownerId: string; title: string }>;
+  updateTitle: (id: string, title: string) => Promise<void>;
+};
+
+export async function renameProject(
+  db: Db,
+  currentUserId: string,
+  projectId: string,
+  title: string,
+): Promise<void> {
+  await db.getProject(projectId);
+  await db.updateTitle(projectId, title);
+}
```

**Seeded defect:** `currentUserId` is never compared with `ownerId`.

**Minimum proof:** A non-owner rename request must fail and must not call
`updateTitle`.

## Fixture 5: Duplicate Webhook Delivery

**Task contract:** Credit an order once even when the provider retries the same
event ID.

**Allowed context:** Providers may deliver an event more than once. The database
has no unique constraint on `eventId`.

```diff
diff --git a/src/webhook.ts b/src/webhook.ts
new file mode 100644
--- /dev/null
+++ b/src/webhook.ts
@@ -0,0 +1,12 @@
+type Event = { id: string; orderId: string; amount: number };
+type Db = {
+  insertCredit: (row: { eventId: string; orderId: string; amount: number }) => Promise<void>;
+};
+
+export async function handlePaidEvent(db: Db, event: Event): Promise<void> {
+  await db.insertCredit({
+    eventId: event.id,
+    orderId: event.orderId,
+    amount: event.amount,
+  });
+}
```

**Seeded defect:** The same event can insert the credit more than once.

**Minimum proof:** Call `handlePaidEvent` twice with the same event ID and assert
that only one credit exists.

## Fixture 6: Correct Code, Wrong Intent

Run this once without the task contract, then once with it.

**Task contract:** The dashboard must show the newest users first. `createdAt` is
an ISO timestamp.

**Allowed context:** Names are display labels and are not a proxy for recency.

```diff
diff --git a/src/sort-users.ts b/src/sort-users.ts
new file mode 100644
--- /dev/null
+++ b/src/sort-users.ts
@@ -0,0 +1,5 @@
+type User = { name: string; createdAt: string };
+
+export function dashboardUsers(users: User[]): User[] {
+  return [...users].sort((a, b) => a.name.localeCompare(b.name));
+}
```

**Seeded defect:** The code sorts correctly by the wrong field.

**Minimum proof:** Given users created on two different dates, the newer timestamp
must appear first regardless of name.

## Fixture 7: Intentional Safe Oddity

**Task contract:** Treat both `null` and `undefined` as missing, while preserving
`0`, `false`, and an empty string.

**Allowed context:** The project permits the deliberate `value == null` idiom for
this exact two-value check and has a test for it.

```diff
diff --git a/src/fallback.ts b/src/fallback.ts
new file mode 100644
--- /dev/null
+++ b/src/fallback.ts
@@ -0,0 +1,4 @@
+export function withFallback<T>(value: T | null | undefined, fallback: T): T {
+  // Deliberate: loose null equality matches null and undefined only.
+  return value == null ? fallback : value;
+}
```

**Seeded defect:** None.

**Expected reviewer behavior:** Do not report the permitted loose equality as a
material bug.

## Fixture 8: Missing Migration

**Task contract:** Store and return every user's IANA timezone. The deployment
must work against a clean database and an existing production database.

**Allowed context:** The current `users` table has only `id` and `email`. Database
schema changes require a tracked SQL migration under `migrations/`.

```diff
diff --git a/src/users.ts b/src/users.ts
index 1111111..2222222 100644
--- a/src/users.ts
+++ b/src/users.ts
@@ -1,5 +1,5 @@
-type User = { id: string; email: string };
+type User = { id: string; email: string; timezone: string };

 export async function getUser(db: Db, id: string): Promise<User> {
-  return db.one('SELECT id, email FROM users WHERE id = $1', [id]);
+  return db.one('SELECT id, email, timezone FROM users WHERE id = $1', [id]);
 }
```

**Seeded defect:** The diff reads a column that no migration creates.

**Minimum proof:** Apply migrations to a clean database, then run `getUser`.

## Fixed Scoring Sheet

Use one row per run.

| Field | Value |
|---|---|
| Reviewer and version | |
| Fixture | 1 through 8 |
| Task contract supplied | yes or no |
| Seeded defect reported | yes, no, or not applicable |
| Correct changed line named | yes or no |
| Concrete failure path supplied | yes or no |
| Smallest useful proof supplied | yes or no |
| Unsupported material finding | count |
| Exact output saved at | path or URL |

After all runs, calculate:

- **Primary true-positive rate:** seeded defects detected in the seven
  with-contract defect runs divided by seven. Count the with-contract Fixture 6
  run here and exclude its contract-free run.
- **False-positive count:** unsupported material findings, especially Fixture 7.
- **Evidence-complete findings:** findings with a correct line, failure path, and
  useful proof.
- **Intent lift:** `with-contract detection - contract-free detection` for Fixture
  6, where detection is 1 and a miss is 0. Report the two inputs beside the result.

Keep the raw outputs. A single combined score hides whether a reviewer is useful
for your risk tolerance or merely noisy.
