# Критическая перепроверка ответа Соннета: pipeline сверки Дата: 2026-08-27 Основание: ответ Соннета из `History/sonnet-review-comparison-answer-2026-08-27.md` ## Подтверждено по исходникам ### B-1: `complete` против `done` Подтверждено. - `site/services/process.py` завершает `run_pipeline()` событием `{"type": "complete"}`. - `site/static/compare.js` завершает успешный SSE только в ветке `d.type === 'done'`. - При нормальном закрытии генератора после неизвестного события клиент получает `EventSource.onerror`, поэтому успешная сверка может отображаться как ошибка соединения. Это не предположение Соннета, а прямое несоответствие backend/frontend event contract. ### B-2: отдельные commits в `apply_ops()` Основная часть finding подтверждена. - `site/db/spec_events.py::apply_ops()` вызывает `execute()` для каждого события и изменения current state. - `site/db/connection.py::execute()` делает `conn.commit()` после каждого SQL-вызова. - В `apply_ops()` нет единой транзакции и rollback. - Поэтому исключение после уже выполненных операций оставляет предыдущие commits в БД. - `process.py` после исключения формирует нулевую summary и не показывает уже применённые операции как applied. Требует отдельной проверки формулировка о том, что history и current state обязательно расходятся при каждом сценарии: это зависит от того, на каком именно SQL-вызове возникает исключение. Но частичный commit и недостоверная summary подтверждены. ### B-3: stale `name_hash` Подтверждено. `site/db/spec_events.py::_update_spec_current()` обновляет `name`, `price`, `qty`, `sum`, `date_start`, но не пересчитывает и не обновляет `name_hash`. При последующем ADD с новым именем `_hash()` создаёт другой ключ, поэтому сценарий с дублированием реален. Нужно отдельно проверить бизнес-правило для изменения `date_start`: изменение даты может означать новый период и не во всех случаях должно менять identity строки. Автоматически объединять `name` и `date_start` в одно исправление нельзя без подтверждения модели идентичности. ### H-1: `seq` для неизвестного action Подтверждено. В ветке `else` `apply_ops()` вызывает `_log_unresolved(...)`, но не делает `seq += 1`. Следующая операция получает тот же `seq`. ### H-2: `unresolved` недоступен в summary Подтверждено по текущим участкам. `apply_ops()` возвращает только `added`, `updated`, `deleted`. При этом frontend использует `d.total_unresolved`, а `run_pipeline()` не формирует это поле в финальном событии. Требует проверки полный путь отображения `UNRESOLVED`: факт отсутствия счётчика в summary подтверждён, но следует проверить, нет ли другого endpoint/UI, который показывает события напрямую. ### H-3: результат `check_arithmetic()` игнорируется Подтверждено для `run_pipeline()`. `process.py` вызывает `check_arithmetic(ops)` и игнорирует возвращаемое значение. Требуется дополнительно проверить, логирует ли сама функция несоответствия и является ли её контракт предупреждением или валидатором, прежде чем выбирать severity и формат исправления. ### H-4: пустой `full_replace` Подтверждено по порядку операций. В `process.py` `clear_current(contract_id)` вызывается после получения `mode` и до `apply_ops()`, без проверки непустого `ops`. Пустой список при `mode == 'full_replace'` очищает current state. Нужно уточнить у Соннета, какие именно ответы считать повреждёнными: пустой `ops` может быть штатным ответом для пустой спецификации, хотя для существующего current state это опасный случай. ### M-1: финальное событие после ошибок Подтверждено. После цикла `run_pipeline()` безусловно выдаёт финальное событие, даже если каждый supplement завершился `extract_error`. ## Подтверждено частично или требует дополнительных доказательств ### M-2: concurrency Риск правдоподобен, но формулировку о конкретных коллизиях `seq` нужно доказать тестом. `WAL` сериализует записи, но `get_next_seq()` и последующие операции разделены во времени. Нужен воспроизводимый конкурентный тест с двумя pipeline на одном `contract_id`. ### M-3: SSE timeout Отсутствие клиентского timeout видно, но само по себе не доказывает пользовательский дефект: сервер отправляет heartbeat каждые 15 секунд. Нужно проверить proxy timeout, лимит длительности LLM и поведение при остановленном backend. ### M-4: `last_event_id` Подтверждена причина риска: `spec_current` вставляется без `last_event_id`, а UPDATE также его не заполняет; cleanup в `supplements.delete_by_document()` фильтрует по этому полю. Нужен тест удаления supplement после сверки, чтобы окончательно подтвердить наблюдаемое поведение. ### L-1: сортировка Подтверждено, что `list_by_contract()` не выбирает `doc_date` и `created_at` как поля результата, поэтому ключ сортировки в `process.py` фактически использует значения по умолчанию. При этом SQL уже сортирует по `s.created_at`, поэтому это скорее misleading code, а не доказанная поломка порядка. ### L-2: пустой current state Технически возможно, но вывод о неправильном prompt зависит от семантики группы и контракта `build_prompt()`. Нужна точная проверка prompt и отдельный сценарий сбоя/неполной группы. ## Дополнительные вопросы Соннету 1. Для B-2: укажи точный SQL-вызов и сценарий исключения, при котором расходятся `spec_events` и `spec_current`; отдельно различи частичный commit и рассогласование двух таблиц. 2. Для B-3: должна ли смена `name` менять identity строки, или `name_hash` является историческим ключом? Какие правила действуют при одновременной смене `name` и `date_start`? 3. Для H-2: где именно пользователь должен видеть `UNRESOLVED`, если не через summary? Укажи полный frontend/backend путь и проверяемый сценарий. 4. Для H-3: что возвращает `check_arithmetic()` и есть ли у него побочный logging? Приведи фактический пример mismatch и ожидаемый бизнес-статус. 5. Для H-4: почему пустой `full_replace` однозначно считать повреждённым ответом, если документ может содержать пустую спецификацию? Как отличить штатный результат от обрыва/невалидного JSON? 6. Для M-2: предоставь воспроизводимый тест или timeline с двумя потоками, который приводит к одинаковому `seq` или повреждённому current state. 7. Для M-3: какой фактический timeout установлен на nginx/proxy и как он соотносится с heartbeat и максимальным временем обработки группы? 8. Для M-4: есть ли штатный путь удаления supplement после сверки, и должен ли он удалять строки current state, если строка затронута несколькими supplements? 9. Для L-2: приведи точный контракт `extract`/`diff` prompt и доказательство, что пустой current state после ошибки действительно меняет смысл следующего LLM-вызова. 10. Какие findings Соннет считает подтверждёнными тестом, а какие являются только статическим риском? 11. Проверь финальное событие по реальному frontend-коду: нет ли другой ветки, которая обрабатывает `complete` или завершение `EventSource` без `done`? 12. Для каждого BLOCKER укажи минимальный regression test, который сначала падает на текущей версии и проходит после исправления. ## Предварительный вывод Без дополнительных ответов Соннета уже достаточно доказательств для регистрации B-1, B-2, B-3, H-1, H-2, H-3, H-4 и M-1 как реальных проблем текущего кода. M-2, M-3, M-4 и L-2 требуют воспроизводимых тестов или более точной трассировки. L-1 подтверждён как избыточная/вводящая в заблуждение сортировка, но не как текущая поломка порядка документов. Изменения production-кода по этим findings не выполнялись. ## Обязательное ограничение для дальнейшего общения с Соннетом Соннет не должен писать код, patch или diff и не должен изменять файлы. Нужно запрашивать только критический анализ, проверяемые доказательства, воспроизводимые сценарии, тестовые идеи в виде описания и дополнительные вопросы. Реализацию выполняем отдельно после собственной проверки findings.