feat: adversarial prompt rewrite + README + example

- Rewrite all review prompts with XML-structured adversarial stance
  (role, operating_stance, attack_surface, finding_bar, calibration)
- Rename skill from codex-review to adversarial-review
- Add verbatim output rule for reviewer findings
- Improve resume prompt with adversarial re-review focus
- Add README with installation, usage, architecture, roadmap
- Add synthetic example of review output
- Inspired by openai/codex-plugin-cc (Apache-2.0) prompt structure

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-03 18:29:15 +03:00
co-authored by Claude Opus 4.6
parent 7c130253b1
commit b86a1778e6
3 changed files with 407 additions and 74 deletions
+154
View File
@@ -0,0 +1,154 @@
# Adversarial Review
Claude Code skill for adversarial AI code and plan review.
One AI writes the code. Another tears it apart. Iterate until approved.
## What is this
Most AI code review tools validate your changes — "looks good, maybe add tests."
Adversarial review does the opposite: the reviewer **defaults to skepticism**
and tries to break confidence in the change. It looks for what will fail
in production, not what might be nice to improve.
This is a [Claude Code skill](https://docs.anthropic.com/en/docs/claude-code)
— a single `SKILL.md` file that teaches Claude how to run adversarial reviews
through an external AI model (currently OpenAI Codex).
## Key features
**Two stages** — works on both planning and implementation:
- **Plan review** — review the plan BEFORE writing code. Catch architecture
mistakes, missing steps, and risks early
- **Code review** — review the implementation. Bugs, security, data loss
- **Code-vs-plan** — verify the implementation matches the plan
**Lightweight** — one file, no server, no broker, no dependencies beyond
the reviewer CLI. Compare with [codex-plugin-cc](https://github.com/openai/codex-plugin-cc):
~15 JS modules, App Server, JSON-RPC broker, lifecycle hooks.
This skill is a text instruction that any AI agent can interpret.
**Iterative** — Claude doesn't just show the review and stop.
It actively fixes issues based on reviewer feedback and resubmits
for re-review. Up to 5 rounds until approved.
**Universal foundation** — the skill relies on basic agent capabilities:
run a command, read a file, edit a file. The reviewer is a swappable
component: today Codex, tomorrow Gemini CLI, next week a local model.
Switching backends = changing one launch command; prompts and workflow stay the same.
## How it works
```
┌─────────┐ ┌──────────┐ ┌─────────┐
│ Claude │────>│ Reviewer │────>│ Claude │
│ (code) │ │ (Codex) │ │ (fix) │
└─────────┘ └──────────┘ └─────────┘
^ │
│ ┌──────────┐ │
└─────────│ Reviewer │<───────────┘
│(re-review)│
└──────────┘
VERDICT: APPROVED
```
### Three modes
| Mode | What it reviews | When to use |
|------|----------------|-------------|
| `plan` | Implementation plan | Before writing code |
| `code` | Git diff (unstaged, staged, or branch) | After writing code |
| `code-vs-plan` | Code changes against the plan | Verify implementation matches plan |
Mode is auto-detected from context, or you can force it with an argument.
## Installation
### Requirements
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code)
- [OpenAI Codex CLI](https://github.com/openai/codex): `npm install -g @openai/codex`
- OpenAI API key (`OPENAI_API_KEY` environment variable)
### Setup
```bash
# Clone the repository
git clone https://github.com/<your-username>/adversarial-review.git
# Symlink into Claude Code skills directory
ln -s "$(pwd)/adversarial-review" ~/.agents/skills/adversarial-review
```
After symlinking, the skill is available as `/adversarial-review` in Claude Code.
## Usage
```
# Auto-detect what to review
/adversarial-review
# Review a plan
/adversarial-review plan
# Review code changes
/adversarial-review code
# Review a specific file
/adversarial-review path/to/plan.md
# Use higher reasoning effort
/adversarial-review xhigh
# Use a different model
/adversarial-review model:gpt-5.3-codex
```
## Prompt architecture
The skill uses XML-structured prompts inspired by adversarial review methodology:
- **`<role>`** — adversarial reviewer, defaults to skepticism
- **`<operating_stance>`** — break confidence, not validate
- **`<attack_surface>`** — concrete checklist: auth, data integrity,
race conditions, rollback safety, schema drift, error handling, observability
- **`<finding_bar>`** — every finding must answer 4 questions:
what can go wrong, why this code is vulnerable, impact, recommendation
- **`<scope_exclusions>`** — no style, naming, or speculative comments
- **`<calibration>`** — one strong finding > five weak ones
## Example output
See [examples/review-output.md](examples/review-output.md) for a sample
adversarial review output.
## Roadmap
- [ ] Gemini as alternative reviewer backend
- [ ] Local model support (Ollama, llama.cpp)
- [ ] CI integration (GitHub Actions)
- [ ] Multi-reviewer mode (parallel review by multiple models)
## Inspiration
The adversarial prompt structure was developed after studying
[openai/codex-plugin-cc](https://github.com/openai/codex-plugin-cc) (Apache-2.0)
— the official OpenAI plugin for code review with Codex in Claude Code.
What we borrowed as ideas:
- XML-structured prompts (`<role>`, `<operating_stance>`, `<attack_surface>`, etc.)
- Adversarial stance: "break confidence, not validate"
- Attack surface checklist approach
- Finding bar: 4 questions each finding must answer
- Calibration rules: prefer strong findings over weak ones
What we did differently:
- **Iterative loop** — Claude actively fixes issues and resubmits (vs "stop and ask user")
- **Plan review** — reviews plans before code, not just code
- **Single file** — one SKILL.md vs 15+ JS modules with App Server
- **Verbatim output** — reviewer findings shown as-is, not rephrased
## License
Apache-2.0 — see [LICENSE](LICENSE).
+177 -74
View File
@@ -1,23 +1,23 @@
---
name: codex-review
description: Ревью плана или кода через OpenAI Codex. Автодетект режима, итеративные правки до одобрения.
name: adversarial-review
description: Adversarial AI code/plan review. Codex ревьюит, Claude правит, итеративный цикл до одобрения. Автодетект режима plan/code/code-vs-plan.
user_invocable: true
---
# Итеративное ревью через Codex
# Adversarial Code Review
Отправляет текущую работу в OpenAI Codex на adversarial-ревью. Автоматически определяет, что ревьюить: **план** или **код**. Claude правит по замечаниям Codex и переотправляет до одобрения. Максимум 5 раундов.
Отправляет текущую работу на adversarial-ревью через внешнюю AI-модель (по умолчанию — OpenAI Codex). Автоматически определяет, что ревьюить: **план** или **код**. Claude правит по замечаниям ревьюера и переотправляет до одобрения. Максимум 5 раундов.
---
## Когда вызывать
- `/codex-review` — автодетект что ревьюить
- `/codex-review plan` — принудительно ревью плана
- `/codex-review code` — принудительно ревью кода
- `/codex-review <путь-к-файлу>` — ревью конкретного файла (аргумент содержит `/` или `.`)
- Переопределение reasoning: `/codex-review xhigh` или `/codex-review low` (одно из: `none`, `low`, `medium`, `high`, `xhigh`)
- Переопределение модели: `/codex-review model:gpt-5.3-codex` (аргумент с префиксом `model:`)
- `/adversarial-review` — автодетект что ревьюить
- `/adversarial-review plan` — принудительно ревью плана
- `/adversarial-review code` — принудительно ревью кода
- `/adversarial-review <путь-к-файлу>` — ревью конкретного файла (аргумент содержит `/` или `.`)
- Переопределение reasoning: `/adversarial-review xhigh` или `/adversarial-review low` (одно из: `none`, `low`, `medium`, `high`, `xhigh`)
- Переопределение модели: `/adversarial-review model:gpt-5.3-codex` (аргумент с префиксом `model:`)
## Инструкции
@@ -93,11 +93,11 @@ git rev-parse --verify main 2>/dev/null && echo main || echo master
Branch берётся ТОЛЬКО когда нет локальных изменений — иначе контекст раздувается.
Для branch в промпте указывай команду `git diff ${BASE_BRANCH}...HEAD` (полный diff).
Codex имеет доступ к репо и сам прочитает полный diff и файлы.
Ревьюер имеет доступ к репо и сам прочитает полный diff и файлы.
В промпт (шаг 4) передай список файлов и какие git diff команды запускать.
**Много файлов (> 50):** если объединённый список превышает 50 путей,
передай в промпт только git-команды без списка файлов — Codex разберётся сам.
передай в промпт только git-команды без списка файлов — ревьюер разберётся сам.
Если все источники пусты — нет изменений для ревью, сообщи пользователю.
@@ -105,64 +105,182 @@ Codex имеет доступ к репо и сам прочитает полн
### Шаг 4: Сформировать промпт и запустить первый раунд
Сформируй промпт в зависимости от режима:
Сформируй промпт в зависимости от режима. Все промпты используют adversarial stance.
**Промпт для ревью плана:**
```
Review the implementation plan in <plan-path>. Focus on:
1. Correctness — will this plan achieve the stated goals?
2. Risks — what could go wrong? Edge cases? Data loss?
3. Missing steps — is anything forgotten?
4. Alternatives — is there a simpler or better approach?
5. Security — any security concerns?
<role>
You are a senior adversarial reviewer of implementation plans.
Your job is to break confidence in the plan, not to validate it.
</role>
<operating_stance>
Default to skepticism. Assume the plan has gaps until the evidence says otherwise.
Do not give credit for good intent or likely follow-up work.
If something only works on the happy path, treat that as a real weakness.
</operating_stance>
<task>
Review the implementation plan in <plan-path>.
</task>
<attack_surface>
Check each area. Skip if not applicable:
- Feasibility — will this approach actually work given the codebase and constraints?
- Missing steps — what is forgotten or assumed but not stated?
- Risk areas — what could go wrong during implementation? Data loss? Downtime?
- Sequencing — are steps in the right order? Are there hidden dependencies?
- Alternatives — is there a simpler or more robust approach?
- Rollback — can this be safely reverted if it fails halfway?
- Security — auth, data exposure, injection, unsafe operations
</attack_surface>
<finding_bar>
Each finding MUST answer:
1. What can go wrong? (concrete scenario, not hypothetical)
2. Why is this plan vulnerable? (cite specific section)
3. Impact — what breaks and how badly?
4. Recommendation — specific change to the plan
</finding_bar>
<scope_exclusions>
DO NOT comment on: formatting, wording style, speculative issues without concrete trigger scenario.
</scope_exclusions>
<calibration>
Prefer one strong finding over several weak ones.
If the plan is solid, say so clearly — false positives erode trust.
</calibration>
<output_format>
## Summary
One paragraph: what this plan does and your overall assessment.
## Findings
For each finding:
### [severity: critical|high|medium] Finding title
- **Section:** which part of the plan
- **What can go wrong:** ...
- **Why vulnerable:** ...
- **Impact:** ...
- **Recommendation:** ...
If no findings: "No actionable findings."
## Verdict
VERDICT: APPROVED — no findings, or all findings are low severity
VERDICT: REVISE — one or more high/critical findings
</output_format>
```
**Промпт для ревью кода (<= 50 файлов):**
```
<role>
You are a senior adversarial code reviewer.
Your job is to break confidence in the change, not to validate it.
</role>
<operating_stance>
Default to skepticism. Assume the change can fail in subtle, high-cost,
or user-visible ways until the evidence says otherwise.
Do not give credit for good intent, partial fixes, or likely follow-up work.
If something only works on the happy path, treat that as a real weakness.
</operating_stance>
<task>
Review the code changes in this repo. Changed files:
<список файлов из --name-only>
Changes include: <unstaged changes / staged changes / unstaged + staged changes / branch changes vs ${BASE_BRANCH}>.
Run <git diff commands> to see the full diffs. Focus on:
1. Bugs — logic errors, off-by-one, null handling, race conditions
2. Edge cases — what inputs or states could break this?
3. Style — does the code follow existing project conventions?
4. Security — injection, credential exposure, unsafe operations
5. Tests — is the change adequately tested?
Run <git diff commands> to see the full diffs.
</task>
<attack_surface>
Check each area. Skip if not applicable to this change:
- Auth & permissions: bypasses, privilege escalation, missing checks
- Data integrity: loss, corruption, partial writes, constraint violations
- Race conditions: TOCTOU, concurrent access, deadlocks
- Rollback safety: can this change be safely reverted?
- Schema drift: migrations, backward compatibility, data format changes
- Error handling: swallowed errors, missing retries, cascading failures
- Observability: will operators know when this breaks?
</attack_surface>
<finding_bar>
Each finding MUST answer:
1. What can go wrong? (concrete scenario, not hypothetical)
2. Why is this code vulnerable? (cite specific file and lines)
3. Impact — what breaks and how badly? (data loss > downtime > degraded UX)
4. Recommendation — specific fix with code reference
</finding_bar>
<scope_exclusions>
DO NOT comment on: code style, formatting, naming conventions,
speculative issues without concrete trigger scenario,
"nice to have" improvements unrelated to correctness or safety.
</scope_exclusions>
<calibration>
Prefer one strong finding over several weak ones.
Severity: critical (data loss/security) > high (bug in prod) > medium (edge case).
If the change is solid, say so clearly — false positives erode trust.
</calibration>
<output_format>
## Summary
One paragraph: what this change does and your overall assessment.
## Findings
For each finding:
### [severity: critical|high|medium] Finding title
- **File:** path/to/file.ext lines N-M
- **What can go wrong:** ...
- **Why vulnerable:** ...
- **Impact:** ...
- **Recommendation:** ...
If no findings: "No actionable findings."
## Verdict
VERDICT: APPROVED — no findings, or all findings are low severity
VERDICT: REVISE — one or more high/critical findings
</output_format>
```
**Промпт для ревью кода (> 50 файлов):**
Тот же промпт, что выше, но секция `<task>` без списка файлов:
```
<task>
Review the code changes in this repo.
Changes include: <unstaged changes / staged changes / ...>.
Run <git diff commands> to see changed files and full diffs. Focus on:
1. Bugs — ...
(те же 5 пунктов)
Run <git diff commands> to see changed files and full diffs.
</task>
```
**Промпт для ревью кода против плана:**
Тот же промпт для ревью кода, но секция `<task>` дополняется:
```
<task>
Review the code changes in this repo against the implementation plan in <plan-path>.
Changed files:
<список файлов или пусто если > 50>
Changes include: <тип>.
Run <git diff commands> to see the full diffs. Focus on:
1. Completeness — does the implementation cover all plan steps?
2. Deviations — where does the code differ from the plan? Are deviations justified?
3. Bugs — logic errors, edge cases, null handling
4. Security — injection, credential exposure, unsafe operations
5. Missing — what from the plan is not yet implemented?
Run <git diff commands> to see the full diffs.
</task>
```
**Все промпты заканчиваются:**
И в `<attack_surface>` добавляются пункты:
```
Be specific and actionable. Reference file paths and line numbers where possible.
If the work is solid and ready, end your review with exactly: VERDICT: APPROVED
If changes are needed, end with exactly: VERDICT: REVISE
- Completeness: does the implementation cover all plan steps?
- Deviations: where does the code differ from the plan? Are deviations justified?
- Missing: what from the plan is not yet implemented?
```
**Запуск Codex — шаблон команды:**
@@ -170,7 +288,7 @@ If changes are needed, end with exactly: VERDICT: REVISE
Флаги:
- `-m gpt-5.4` — модель (переопределяется аргументом `model:...`)
- `-c model_reasoning_effort=high` — глубина рассуждения (переопределяется аргументом `xhigh`, `low` и т.д.)
- `-s read-only`Codex только читает, не пишет
- `-s read-only`ревьюер только читает, не пишет
- `-o /tmp/codex-review-${REVIEW_ID}.md` — файл для записи ответа
```bash
@@ -188,42 +306,22 @@ timeout 600 codex exec \
- Команда **синхронная**: когда она вернулась, файл `-o` уже готов. **НЕ** используй poll-loop (`while/sleep`).
- Если exit code = 124 (таймаут) — сообщи пользователю и предложи повторить.
**Пример для режима code** (unstaged изменения в двух файлах):
```bash
timeout 600 codex exec \
-m gpt-5.4 \
-c model_reasoning_effort=high \
-s read-only \
-o /tmp/codex-review-${REVIEW_ID}.md \
"Review the code changes in this repo. Changed files:
agents/.agents/skills/codex-review/SKILL.md
TODO.md
Changes include: unstaged changes.
Run git diff to see the full diffs. Focus on:
1. Bugs — logic errors, off-by-one, null handling, race conditions
...
If changes are needed, end with exactly: VERDICT: REVISE"
```
**После запуска:** найди в выводе строку `session id: <uuid>` и сохрани значение как `CODEX_SESSION_ID` — оно нужно для `resume` в последующих раундах.
**Примечания:**
- Модель по умолчанию: `gpt-5.4` с `model_reasoning_effort=high`. Пользователь может переопределить через аргументы.
- Всегда `-s read-only`Codex не должен писать файлы.
- Всегда `-s read-only`ревьюер не должен писать файлы.
- `-o` для захвата вывода в файл. **НЕ** запускай в background — команда сама вернёт управление.
### Шаг 5: Прочитать ревью и проверить вердикт
1. Прочитать `/tmp/codex-review-${REVIEW_ID}.md`
2. Показать пользователю:
2. Показать пользователю **дословно** (verbatim) — не перефразировать findings ревьюера:
```
## Codex Review — Раунд N (режим: <plan|code|code-vs-plan>, модель: gpt-5.4)
## Adversarial Review — Раунд N (режим: <plan|code|code-vs-plan>, модель: gpt-5.4)
[Отзыв Codex]
[Отзыв ревьюера — дословно]
```
3. Проверить вердикт:
@@ -234,7 +332,7 @@ If changes are needed, end with exactly: VERDICT: REVISE"
### Шаг 6: Внести правки
По замечаниям Codex:
По замечаниям ревьюера:
**Для ревью плана:** исправить план — адресовать каждое замечание. Обновить файл плана (или temp-файл). Показать пользователю:
@@ -254,7 +352,7 @@ If changes are needed, end with exactly: VERDICT: REVISE"
### Шаг 7: Переотправить в Codex (Раунды 2-5)
**Resume — основной путь.** Экономит токены и сохраняет контекст сессии Codex. Свежий `codex exec` без resume — **аварийный fallback**, расходует значительно больше токенов. Использовать только при ошибке resume.
**Resume — основной путь.** Экономит токены и сохраняет контекст сессии. Свежий `codex exec` без resume — **аварийный fallback**, расходует значительно больше токенов. Использовать только при ошибке resume.
1. Запусти resume с подавлением stderr (`2>/dev/null`):
@@ -265,7 +363,11 @@ timeout 600 codex exec resume ${CODEX_SESSION_ID} \
Here's what I changed:
[Список правок]
Please re-review. End with VERDICT: APPROVED or VERDICT: REVISE" 2>/dev/null
Re-review with the same adversarial stance. Focus on:
1. Whether my fixes actually resolve the reported issues
2. Any NEW issues introduced by the fixes
End with VERDICT: APPROVED or VERDICT: REVISE" 2>/dev/null
```
Используй `timeout: 620000` в параметрах Bash tool.
@@ -274,7 +376,7 @@ Please re-review. End with VERDICT: APPROVED or VERDICT: REVISE" 2>/dev/null
2. Проверь результат по exit code:
- **exit 0** — успех. stdout содержит чистое ревью. Показать пользователю напрямую (Write в файл и Read **не нужны**). Проверить VERDICT: последняя непустая строка stdout = `VERDICT: APPROVED` или `VERDICT: REVISE`. Если вердикт отсутствует → вывод мог быть обрезан, перейти к Fallback. Далее применить обработку вердикта из Шага 5 (APPROVED → Шаг 8, REVISE → Шаг 6).
- **exit 124** — таймаут. Сообщи пользователю: "Codex не ответил за 10 минут" и предложи повторить.
- **exit 124** — таймаут. Сообщи пользователю: "Ревьюер не ответил за 10 минут" и предложи повторить.
- **другой exit code** — сообщи пользователю: "Resume не удался (exit code N)". Перейди к Fallback. Диагностика без stderr недоступна — не пытайся парсить stdout как ошибку.
**Fallback** — если `resume` не сработал (сессия истекла, session ID не захвачен, ошибка):
@@ -288,19 +390,19 @@ Please re-review. End with VERDICT: APPROVED or VERDICT: REVISE" 2>/dev/null
**Одобрено:**
```
## Codex Review — Итог (режим: <режим>, модель: gpt-5.4)
## Adversarial Review — Итог (режим: <режим>, модель: gpt-5.4)
**Статус:** ✅ Одобрено после N раунд(ов)
[Итоговый отзыв]
---
**Проверено и одобрено Codex. Ожидает вашего решения.**
**Проверено и одобрено ревьюером. Ожидает вашего решения.**
```
**Достигнут максимум раундов:**
```
## Codex Review — Итог (режим: <режим>, модель: gpt-5.4)
## Adversarial Review — Итог (режим: <режим>, модель: gpt-5.4)
**Статус:** ⚠️ Достигнут максимум (5 раундов) — не полностью одобрено
@@ -308,7 +410,7 @@ Please re-review. End with VERDICT: APPROVED or VERDICT: REVISE" 2>/dev/null
[Нерешённые вопросы]
---
**У Codex остались замечания. Просмотрите их и решите, как действовать дальше.**
**У ревьюера остались замечания. Просмотрите их и решите, как действовать дальше.**
```
### Шаг 9: Очистка
@@ -327,13 +429,14 @@ rm -f /tmp/claude-plan-${REVIEW_ID}.md /tmp/codex-review-${REVIEW_ID}.md
## Правила
- Claude **активно правит** по замечаниям Codex — это НЕ просто передача сообщений
- Claude **активно правит** по замечаниям ревьюера — это НЕ просто передача сообщений
- Findings ревьюера показываются **дословно** (verbatim) — не перефразировать, не сокращать
- Автодетект режима ревью по контексту; аргументы пользователя имеют приоритет
- При явном аргументе `plan` или в Claude Code Plan Mode: пропускать git-проверки и определение base branch
- Resume — основной путь для повторных раундов. Свежий exec — аварийный fallback (дорогой по токенам)
- Cleanup — best-effort: в Plan Mode пропускать, при отказе продолжать без ошибки
- Предпочитать существующие файлы, не создавать лишние копии
- Всегда read-only sandbox — Codex никогда не пишет файлы
- Всегда read-only sandbox — ревьюер никогда не пишет файлы
- Максимум 5 раундов для защиты от бесконечных циклов
- Показывать пользователю отзывы и правки каждого раунда
- Если Codex CLI не установлен или упал — сообщить пользователю: `npm install -g @openai/codex`
+76
View File
@@ -0,0 +1,76 @@
# Example: Adversarial Code Review Output
This is a synthetic example showing what a typical adversarial review looks like.
---
## Adversarial Review — Round 1 (mode: code, model: gpt-5.4)
## Summary
This change adds a user deletion endpoint to the API. The endpoint accepts
a user ID, deletes the user record, and returns 204. The implementation
handles the happy path correctly but has two significant issues around
data integrity and authorization.
## Findings
### [severity: critical] User deletion does not cascade to dependent records
- **File:** src/api/users.py lines 45-52
- **What can go wrong:** Deleting a user leaves orphaned records in `orders`,
`sessions`, and `audit_log` tables. Foreign key constraints are set to
`NO ACTION`, so the delete succeeds but dependent data becomes unreferenceable.
Queries joining on `user_id` will silently drop rows.
- **Why vulnerable:** The `delete_user()` function calls
`db.users.delete(user_id)` directly without handling related tables.
No cascade is configured at the database level.
- **Impact:** Data integrity violation. Orphaned orders cannot be attributed
to any user. Financial reconciliation reports will undercount.
- **Recommendation:** Either add `ON DELETE CASCADE` to foreign keys in a
migration, or delete dependent records explicitly in a transaction before
deleting the user.
### [severity: high] Missing authorization check on delete endpoint
- **File:** src/api/users.py lines 38-40
- **What can go wrong:** Any authenticated user can delete any other user
by sending `DELETE /api/users/{id}`. The endpoint checks authentication
(`@require_auth`) but not authorization — there is no admin role check
or ownership validation.
- **Why vulnerable:** The route decorator only has `@require_auth`, not
`@require_role('admin')`. The `user_id` parameter comes from the URL
path and is never compared to the authenticated user's ID.
- **Impact:** Privilege escalation. Any logged-in user can delete
arbitrary accounts including admins.
- **Recommendation:** Add `@require_role('admin')` decorator, or if
self-deletion is intended, verify `request.user.id == user_id`.
## Verdict
VERDICT: REVISE
---
### Fixes (Round 1)
- Added `ON DELETE CASCADE` migration for `orders.user_id` and `sessions.user_id`
- Added `@require_role('admin')` to the delete endpoint
- Added test for unauthorized deletion attempt (returns 403)
---
## Adversarial Review — Round 2 (mode: code, model: gpt-5.4)
## Summary
Both critical issues from round 1 are resolved. The cascade migration is correct
and the authorization check is in place. No new issues found.
## Findings
No actionable findings.
## Verdict
VERDICT: APPROVED