5.6 KiB
Sonnet: код-ревью contracts-flask (2026-08-26)
Контекст
- Написан промпт для Sonnet:
contracts-flask/docs/prompt-sonnet-architecture.md(задача — прочитать файлы текущего сервиса сверки и дать описание/архитектуру). - Sonnet прочитал все указанные файлы и вместо описания выдал код-ревью с 10 находками (2 критические, 3 средних, 2 XSS, 3 мелких). Архитектурное описание — отдельным документом (не выдано).
Находки Sonnet
🔴 Критические
-
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.pyth = op.get("target_hash","")= "" → UNRESOLVED. Работает толькоmode=full_replace(все ADD) и первый документ (extract, только ADD). -
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.
🟠 Средние
-
db/prompts.py—list_by_role()запрашивает несуществующий столбецcreated_by. В схемеpromptsнетcreated_by→sqlite3.OperationalError→/api/prompts/listвсегда 500. -
routes/prompts_bp.py— вызов несуществующих функций:db_prompts.insert(...)— нет (естьsave_new_version());db_prompts.delete(...)— нет (естьdelete_prompt())./api/prompts/saveи/api/prompts/deleteпадают сAttributeError.
-
services/llm.py,services/classify.py— захардкоженыLLM_URL/LLM_KEY/LLM_MODELв обходconfig.py.config.pyчитает изLLM_API_URL, а compare/classify игнорируют его. Конфигурация расщеплена на 3 блока.
🟡 XSS (frontend)
-
compare.js—nr.nameв таблице операций не экранирован (escHtml()отсутствует). Имя услуги из договора (текст LLM) вставляется в innerHTML → XSS. -
compare.js—sec.filenameв заголовке секции не экранирован. Имя файла от пользователя → XSS.
⚪ Мелкие
llm_prompt.py— устаревший комментарий «Lucee API» (читает напрямую из SQLite).db/connection.py—_pg_to_sqlite:gen_random_uuid()добавляет?в SQL, но не добавляет значение в params (мёртвый код, но при исполнении сломает запрос).services/classify.py— комментарий «re-classify after file changes» вводит в заблуждение:reset_classify_status()сбрасывает толькоprocessing→pending, уже классифицированные не трогает.
Итоговая таблица
| # | Файл | Проблема | Критичность |
|---|---|---|---|
| 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_id→target_hash. - #2–#10 (механика) — Флаш-субагент: Dockerfile, prompts CRUD, XSS, конфиг LLM, комментарии.
- Верифицировано:
py_compile,node -c,import app— OK. - Архитектурное описание от Sonnet — так и не получено (вместо него — ревью). Если нужно — отдельным запросом.