Files
contracts-flask/History/2026-08-26-sonnet-code-review.md
2026-08-26 16:29:41 +03:00

5.6 KiB
Raw Permalink Blame History

Sonnet: код-ревью contracts-flask (2026-08-26)

Контекст

  • Написан промпт для Sonnet: contracts-flask/docs/prompt-sonnet-architecture.md (задача — прочитать файлы текущего сервиса сверки и дать описание/архитектуру).
  • Sonnet прочитал все указанные файлы и вместо описания выдал код-ревью с 10 находками (2 критические, 3 средних, 2 XSS, 3 мелких). Архитектурное описание — отдельным документом (не выдано).

Находки Sonnet

🔴 Критические

  1. process.py + spec_events.py — UPDATE/DELETE никогда не применяются (сломана частичная сверка). LLM возвращает target_id: "r1" (индекс строки), а apply_ops() читает op.get("target_hash", ""). Поля target_hash в ответе LLM нет → все UPDATE/DELETE из mode=partial молча уходят в UNRESOLVED. Трассировка: _build_spec_text() показывает строки [id: r1] → LLM возвращает target_id: "r1"process.py передаёт ops без трансляции r1→hash → spec_events.py th = op.get("target_hash","") = "" → UNRESOLVED. Работает только mode=full_replace (все ADD) и первый документ (extract, только ADD).

  2. Dockerfile — образ не запустится: upload/ не скопирован. COPY site /app/site — только site/, upload/ отсутствует. При старте routes/__init__.py делает from upload.backend.upload_refs import ..., app.py добавляет /app в sys.path. В образе /app/upload/ нет → ModuleNotFoundError.

🟠 Средние

  1. db/prompts.pylist_by_role() запрашивает несуществующий столбец created_by. В схеме prompts нет created_bysqlite3.OperationalError/api/prompts/list всегда 500.

  2. routes/prompts_bp.py — вызов несуществующих функций:

    • db_prompts.insert(...) — нет (есть save_new_version());
    • db_prompts.delete(...) — нет (есть delete_prompt()). /api/prompts/save и /api/prompts/delete падают с AttributeError.
  3. services/llm.py, services/classify.py — захардкожены LLM_URL/LLM_KEY/LLM_MODEL в обход config.py. config.py читает из LLM_API_URL, а compare/classify игнорируют его. Конфигурация расщеплена на 3 блока.

🟡 XSS (frontend)

  1. compare.jsnr.name в таблице операций не экранирован (escHtml() отсутствует). Имя услуги из договора (текст LLM) вставляется в innerHTML → XSS.

  2. compare.jssec.filename в заголовке секции не экранирован. Имя файла от пользователя → XSS.

Мелкие

  1. llm_prompt.py — устаревший комментарий «Lucee API» (читает напрямую из SQLite).
  2. db/connection.py_pg_to_sqlite: gen_random_uuid() добавляет ? в SQL, но не добавляет значение в params (мёртвый код, но при исполнении сломает запрос).
  3. services/classify.py — комментарий «re-classify after file changes» вводит в заблуждение: reset_classify_status() сбрасывает только processingpending, уже классифицированные не трогает.

Итоговая таблица

# Файл Проблема Критичность
1 process.py, spec_events.py UPDATE/DELETE → UNRESOLVED, частичная сверка сломана 🔴 критический
2 Dockerfile upload/ не скопирован → образ не стартует 🔴 критический
3 db/prompts.py created_by отсутствует → 500 на /api/prompts/list 🟠 средний
4 prompts_bp.py insert()/delete() — не те имена функций → 500 🟠 средний
5 llm.py, classify.py захардкожены LLM_URL/KEY/MODEL в обход config.py 🟠 средний
6 compare.js nr.name не экранирован → XSS 🟡 XSS
7 compare.js sec.filename не экранирован → XSS 🟡 XSS
8 llm_prompt.py устаревший комментарий мелкое
9 connection.py gen_random_uuid() мёртвый код с ошибкой мелкое
10 classify.py комментарий re-classify вводит в заблуждение мелкое

Статус

  • Все 10 находок исправлены (v2.0.15, commit e3f5581, запушено).
  • #1 (критично) — сам: process.py трансляция target_idtarget_hash.
  • #2–#10 (механика) — Флаш-субагент: Dockerfile, prompts CRUD, XSS, конфиг LLM, комментарии.
  • Верифицировано: py_compile, node -c, import app — OK.
  • Архитектурное описание от Sonnet — так и не получено (вместо него — ревью). Если нужно — отдельным запросом.