Files
contracts-flask/History/sonnet-review-comparison-verification-2026-08-27.md
“Naeel” f9745e5c6b
Deploy contracts-flask / validate (push) Canceled after 0s
Document comparison review findings
2026-08-27 11:24:32 +03:00

12 KiB
Raw Permalink Blame History

Критическая перепроверка ответа Соннета: 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.