12 KiB
Критическая перепроверка ответа Соннета: 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 и отдельный сценарий сбоя/неполной группы.
Дополнительные вопросы Соннету
- Для B-2: укажи точный SQL-вызов и сценарий исключения, при котором расходятся
spec_eventsиspec_current; отдельно различи частичный commit и рассогласование двух таблиц. - Для B-3: должна ли смена
nameменять identity строки, илиname_hashявляется историческим ключом? Какие правила действуют при одновременной сменеnameиdate_start? - Для H-2: где именно пользователь должен видеть
UNRESOLVED, если не через summary? Укажи полный frontend/backend путь и проверяемый сценарий. - Для H-3: что возвращает
check_arithmetic()и есть ли у него побочный logging? Приведи фактический пример mismatch и ожидаемый бизнес-статус. - Для H-4: почему пустой
full_replaceоднозначно считать повреждённым ответом, если документ может содержать пустую спецификацию? Как отличить штатный результат от обрыва/невалидного JSON? - Для M-2: предоставь воспроизводимый тест или timeline с двумя потоками, который приводит к одинаковому
seqили повреждённому current state. - Для M-3: какой фактический timeout установлен на nginx/proxy и как он соотносится с heartbeat и максимальным временем обработки группы?
- Для M-4: есть ли штатный путь удаления supplement после сверки, и должен ли он удалять строки current state, если строка затронута несколькими supplements?
- Для L-2: приведи точный контракт
extract/diffprompt и доказательство, что пустой current state после ошибки действительно меняет смысл следующего LLM-вызова. - Какие findings Соннет считает подтверждёнными тестом, а какие являются только статическим риском?
- Проверь финальное событие по реальному frontend-коду: нет ли другой ветки, которая обрабатывает
completeили завершениеEventSourceбезdone? - Для каждого 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.