74 lines
5.6 KiB
Markdown
74 lines
5.6 KiB
Markdown
# 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`.
|
||
|
||
### 🟠 Средние
|
||
|
||
3. **`db/prompts.py` — `list_by_role()` запрашивает несуществующий столбец `created_by`.**
|
||
В схеме `prompts` нет `created_by` → `sqlite3.OperationalError` → `/api/prompts/list` всегда 500.
|
||
|
||
4. **`routes/prompts_bp.py` — вызов несуществующих функций:**
|
||
- `db_prompts.insert(...)` — нет (есть `save_new_version()`);
|
||
- `db_prompts.delete(...)` — нет (есть `delete_prompt()`).
|
||
`/api/prompts/save` и `/api/prompts/delete` падают с `AttributeError`.
|
||
|
||
5. **`services/llm.py`, `services/classify.py` — захардкожены `LLM_URL/LLM_KEY/LLM_MODEL` в обход `config.py`.**
|
||
`config.py` читает из `LLM_API_URL`, а compare/classify игнорируют его. Конфигурация расщеплена на 3 блока.
|
||
|
||
### 🟡 XSS (frontend)
|
||
|
||
6. **`compare.js` — `nr.name` в таблице операций не экранирован** (`escHtml()` отсутствует).
|
||
Имя услуги из договора (текст LLM) вставляется в innerHTML → XSS.
|
||
|
||
7. **`compare.js` — `sec.filename` в заголовке секции не экранирован.** Имя файла от пользователя → XSS.
|
||
|
||
### ⚪ Мелкие
|
||
|
||
8. **`llm_prompt.py`** — устаревший комментарий «Lucee API» (читает напрямую из SQLite).
|
||
9. **`db/connection.py`** — `_pg_to_sqlite`: `gen_random_uuid()` добавляет `?` в SQL, но не добавляет значение в params (мёртвый код, но при исполнении сломает запрос).
|
||
10. **`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 — так и не получено (вместо него — ревью). Если нужно — отдельным запросом.
|