Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
33 KiB
SDD ledger — plan: docs/superpowers/plans/2026-10-05-slice-2c-osv-ingest.md
Spec: openspec/changes/slice-2c-osv-ingest/ (доступна). Merge-base ветки: 55e538c (HEAD плана). Docs worktree: ../docs (ветка feat/16-slice-2c-osv-ingest).
Маркеры: T. minor задачи, C/I/M финальное ревью, R-n рулинг (design #m), O-n наблюдение.
Pre-flight: таблица конфликтов (пары задач по общим файлам/интерфейсам)
| Пара | Что производит -> потребляет | Найдено |
|---|---|---|
| T1 -> T2..T12 | T1: enums.NU, DBML_VERSION 5.15, Makefile DBML, HEAD миграций, email lower | чисто |
| T2 -> T3,T4,T6,T7,T9,T10 | T2: ObjectStorage/ObjectNotFoundError, FakeStorage.get_bytes, make_stored_file/make_document/make_result_object, настройки | tasks.md 1.2 ждёт ещё make_entries_from_result; в плане T2 его нет, а T9 использует -> R-2 |
| T3 -> T9,T10 | T3: PrimaryFilesService, schemas/files.py (FileOut/JobBrief/DocumentBrief), dependencies/files.py, files router, common/filenames | T9 расширяет schemas/dependencies (OK); T10 добавляет delete в тот же сервис (OK) |
| T3 -> T2 | T3 переносит safe_filename/CONTENT_TYPES в common/filenames.py; T2 переносит ObjectStorage из directory_import.py | оба правят directory_import.py; T2 раньше T3 - порядок задаёт отсутствие конфликта |
| T4 -> T7 | T4: set_file_status_for_job, PROCESSING по ack; T7 pick_candidate требует file.status=PROCESSING | ack-less результат (ack потерян, core NATS без JetStream) оставит файл UPLOADED навсегда -> R-4; helper не различает UPLOADED/ERROR при ack -> R-5 |
| T4 -> T7 | T4 удаляет ResultMapper/handle_message(mapper); T7 меняет ExchangeTasks.start/app.py | оба правят recognition_exchange.py и app.py: последовательно, T4 раньше (OK) |
| T5 -> T6 | normalize_alias из domain/aliases.py; T6 использует его же | чисто |
| T5 -> T7 | T5: EntryDraft, build_entries(doc, chart) без resolve; T7 нужен counterparty_id | EntryDraft без counterparty_id, design D-6 ожидал resolve -> R-3 |
| T6 -> T7 | CounterpartyIndex.load/resolve/owner_names | чисто |
| T7 -> T8 | map_file; T8 вызывает supersede_previous после PROCESSED в той же транзакции | T7 говорит "завершается PROCESSED", T8 добавляет вызов: OK. uploader_id берётся из file.uploaded_by |
| T7/T8 -> tasks.md 3.4 | golden-прогон 82 снимков (spec osv-result-mapping, сценарий) | в плане нет задачи -> R-1 |
| T8 -> T9 | заменённые файлы скрыты include_superseded (проверка в T9) | чисто |
| T9 -> T10 | repositories/files.py get_file; T10 добавляет repo-функции; DELETE маршрут в read.py | чисто |
| T9 -> T7 | DocumentOut.warnings / group_schema: источник job.metrics.mapping_warnings и job.group_schema (T7) | неоднозначно привязка предупреждений к документу -> R-6 |
| T10 -> T11 | e2e использует DELETE для очистки | чисто |
| T11 -> T12 | Makefile check-full включает e2e; README | T12 тоже правит README: последовательно (OK) |
| T12 -> T1 | docs-коммиты в ../docs: T1 (entities-v5.15, guide, шаблоны) и T12 (§1.3.. qa-guide) | разные файлы, кроме tasks.md docs-change (1.2 в T1, 2.1 в T12) - OK |
Самосогласованность каждой задачи:
| Task | Своё содержимое | Находка |
|---|---|---|
| 1 | тесты <-> код (NU, 0003, email) согласованы; docs-часть - файлы в другом worktree + workspace CLAUDE.md вне git | R-7 |
| 2 | Files перечислены; тесты test_clients/test_factories | чисто |
| 3 | интерфейс vs тесты: 413 -> RejectedFile; 422 если accepted пуст; fixtures-маркер на реальном xls | чисто |
| 4 | helper WHERE NOT IN (SUPERSEDED, PROCESSED); ack "только из UPLOADED" в тексте, но helper этого не обеспечивает | R-5 |
| 5 | DocumentDraft упомянут без определения | определяет исполнитель (dataclass в osv_mapping.py) - не рулинг |
| 6 | чисто | чисто |
| 7 | map_file в тексте "завершается PROCESSED"; metrics jsonb мутировать через новый dict/flag_modified | заметка исполнителю |
| 8 | чисто (тест include_superseded - в T9) | чисто |
| 9 | make_entries_from_result используется, но не создан | R-2 |
| 10 | порядок: current_recognition_job_id=NULL + flush до удаления job | чисто |
| 11 | skip без профиля; e2e Makefile | чисто |
| 12 | openspec validate --strict в обоих репо |
чисто |
Rulings
R-1 (design #14): Ruling: golden-прогон (tasks 3.4, 82 снимка) не вынесен в задачу плана - выполняется как второй коммит Task 8 (после появления полного map_file с автозаменой): api/tests/test_mapping_golden.py, маркеры integration+fixtures, инварианты спеки, <= 60 с - спека требует сценарий "Все golden-снимки"; цена ошибки: тест переезжает в другой коммит.
R-2 (design #15): Ruling: make_entries_from_result(session, file, job, result_json) в tests/factories.py создаётся в Task 9 (зависит от build_entries из Task 5), а не в Task 2 - иначе Task 2 тянет домен, которого ещё нет; цена: ничего.
R-3 (design #16): Ruling: EntryDraft получает поле counterparty_id: uuid.UUID | None = None (Task 5); build_entries остаётся чистой функцией без resolve (как в плане); Task 7 проставляет его через dataclasses.replace(draft, counterparty_id=index.resolve(...)) только для group_type=COUNTERPARTY и data_type in {BU, SUM} - сохраняет сигнатуру плана, закрывает требование спеки "Резолв контрагента"; цена: сигнатуру можно переделать на callable (D-6).
R-4 (design #17): Ruling: inbox при результате done/needs_check переводит файл UPLOADED -> PROCESSING (только текущее задание, только из UPLOADED) - core NATS без JetStream, ack может потеряться; без этого файл с готовым результатом никогда не попадёт в pick_candidate (требует PROCESSING); не противоречит спеке ("файл остаётся PROCESSING" выполняется); цена: убрать одну ветку.
R-5 (design #18): Ruling: set_file_status_for_job(..., only_from: Collection[FileStatus] | None = None): ack -> PROCESSING только only_from={UPLOADED} (ack после ERROR/timeout не воскрешает файл); R-4 то же; ERROR/SUPERSEDED/PROCESSED - как в плане; цена: параметр.
R-6 (design #19): Ruling: DocumentOut.warnings = job.metrics.mapping_warnings файла целиком (файл-уровневые, не по листам; у большинства файлов один документ), group_schema - элемент job.group_schema с locator документа; цена: фильтрация по листу позже.
R-7 (design #20): Ruling: Task 1 правит workspace /Users/andrey.epifancev/projects/awp-ca/CLAUDE.md (вне git, по плану) - только замена упоминаний версии DBML v5.14 -> v5.15; цена: откат правки текста.
Progress
Task 1: dispatched (sonnet, BASE d905e3a, agent a609035fa8271560d)
Task 1: implementer DONE (arm-core ab606dc, docs 5545ee5); решение исполнителя: test_api_constraint_races email-race тест инвертирован (UNIQUE теперь ловит гонку) — следствие спеки; review dispatched (sonnet)
Task 1: minor T1.1 (deferred): seed_lib/apply.py ~232 дубль нормализации email в цикле ролей (один map)
Task 1: complete (commits d905e3a..ab606dc + docs 5545ee5, review clean)
Task 2: dispatched (sonnet, BASE ab606dc, agent aabc48c2714385737)
Task 2: implementer DONE 2d69a9e; review dispatched
Task 2: minor T2.1 (deferred -> Task 12): tasks.md 1.2 текст упоминает make_entries_from_result (R-2 -> Task 9): поправить формулировку в Task 12
Task 2: minor T2.2 (deferred -> Task 9): test_make_document_defaults_and_uniqueness не проверяет уникальность — переименовать (Task 9 правит factories/test_factories)
Task 2: minor T2.3 (deferred): TABULAR_FILE_TYPES в factories дублирует DEFAULT_FILE_TYPES; T2.4 (rejected: D-12 vs бриф — response.read() эквивалентно); T2.5 (note): make_stored_file job.max_attempts = DB default 1 — тесты Task 3+ передают явно
Task 2: complete (commits ab606dc..2d69a9e, review clean)
Task 3: dispatched (opus, BASE 2d69a9e, agent aadbd466c7da63383)
Task 3: implementer DONE be0d1b4; review dispatched (opus)
R-8 (design #21): Ruling: 503 STORAGE_UNAVAILABLE через DomainError, 413 строкой только внутри read_limited — Global Constraints/спека главнее брифа — цена: формат тела 503.
R-9 (design #22): Ruling: сбой S3 на файле → rejected STORAGE_UNAVAILABLE по файлу (все отклонены → 422) — цена: вернуть 503.
R-10 (design #23): Ruling: SAVEPOINT на файл внутри транзакции запроса (спека: "отдельная транзакция") — бриф плана; цена: сессия на файл.
R-11 (design #24): Ruling: advisory lock (ЮЛ,sha256), requested_at, size_bytes, .jpeg, operation_id — подтверждены тестами; цена: убрать блокировку.
Task 3: minor T3.1 (deferred): lock_content docstring неточен про ROLLBACK TO SAVEPOINT; T3.2 (deferred): теоретический deadlock advisory lock при встречном порядке файлов — комментарий; T3.3 (deferred): except Exception вокруг put_object маскирует баги под STORAGE_UNAVAILABLE; T3.4 (deferred): responses={422: UploadResult} затирает HTTPValidationError в OpenAPI; T3.5 (deferred): src.domain.file_types зависит от FastAPI через common/filenames (разделить); T3.6 (deferred): нет тестов на filename="" и два одинаковых файла в одном запросе; T3.7 (fix в Task 12): tasks.md 2.1 upload_many -> upload_one; T3.8 (rejected: по брифу): xls сигнатура 4 байта OLE2
Task 3: complete (commits 2d69a9e..be0d1b4, review clean)
Task 4: dispatched (opus, BASE 8d2ced8, agent a4fa10feda2880cdd)
Task 4: implementer DONE b7ca728 (343 integration passed); review dispatched (opus). Lock order job->file обязателен для Task 7 (carry in dispatch)
Task 4: review: Important T4.1 (INVALID_REQUEST публикатора не ставит файлу ERROR) -> fix round 1 (resume implementer)
Task 4: minor T4.2 (deferred): нет индекса stored_file.current_recognition_job_id, пакетный UPDATE вахтёра — seq scan (взять RETURNING id,file_id + id IN); T4.3 (deferred): повторный ERROR из ERROR сдвигает updated_at; T4.4 (deferred): dlq не проверяет rowcount UPDATE задания; T4.5 (deferred): тест отката не проверяет откат s3_object; T4.6 (deferred): docstring recognition_exchange.py:42 ссылается на ещё не существующий result_mapping.py (появится в Task 7); T4.7 (carry): порядок блокировок job -> file обязателен для Task 7 и Task 10
Task 4: T4.1 fixed 3b4d3a4; fix round 1/5 (1 addressed, 0 open; commits b7ca728..3b4d3a4); контроллер: полный pytest -m integration после Task 4 — 344 passed (регрессия обмена Среза 0 зелёная)
Task 4: complete (commits 8d2ced8..3b4d3a4, review clean)
Task 5: dispatched (opus, BASE 3b4d3a4, agent abeead383539fda93)
O-1: план/бриф Task 5/7/11 говорят «20 строк» у golden ОСВ_66 за 2024; фактически в снимке 10 строк (9 без TOTAL); критерий — «строк результата − 1» (спека). Task 7/11 использовать это правило, не число 20.
Task 5: implementer DONE f9306b8 (41 unit + 2 fixtures passed); review dispatched (opus)
R-12 (design #25): Ruling: корень листа = первая строка level 0 без родителя; шапка с пустым нормализованным именем без ИНН -> UNKNOWN; ИНН по цифрам; ±0.01 включительно — решения исполнителя Task 5 приняты; цена: MISMATCH вместо UNKNOWN.
R-13 (design #26): Ruling: «20 строк» в плане/tasks.md исправлено на 9 — в снимке 10 строк минус TOTAL; цена: нет.
Task 5: minor T5.1 (deferred): нет теста «ООО» -> UNKNOWN; T5.2 (deferred): ИНН вида «ИНН/КПП …» склеится в 19 цифр -> MISMATCH (в данных нет); T5.3 (deferred): дубль row_number схлопывает id (контракт гарантирует); T5.4 (note): третий сегмент двузначный не найдёт план — по букве спеки
Carry: Task 7 — ветка «нет корня» в D-9 (пустые листы root=None); Task 8 golden — account_id сверять с планом счетов seed (19 нормализованных кодов golden нет в seed -> NULL легально), а не «заполнено у всех»
Task 5: complete (commits 3b4d3a4..f9306b8, review clean)
Task 6: dispatched (sonnet, BASE 4e92532, agent abc225b5d10445e26)
Task 6: implementer DONE 9658f80 (10 integration passed); review dispatched (sonnet)
R-14 (design #27): Ruling: ИНН точно после normalize_inn; пустая нормализация не в индексе; единственное точное совпадение имени побеждает неоднозначность нормализованного (по шагам) — цена: приоритет шагов.
Task 6: minor T6.1 (deferred): нет теста приоритета «точное имя побеждает дубль нормализованного» (добавить в Task 12 или финале); T6.2 (deferred): ИНН вида «3528052718.0»/с префиксом не матчится (fallback на имя); T6.3 (deferred): load кэшировать по циклу, если справочник вырастет; T6.4 (rejected): Self вместо кавычек — косметика; T6.5 (deferred): комментарий про переименование «Альфа»→«Альфаомега» в тесте
Task 6: complete (commits 4e92532..9658f80, review clean); tasks.md 3.1 отмечен
Task 7: dispatched (opus, BASE 63343d3, agent ab3ea8f9a676619bd)
Task 7: implementer DONE e704b02 (29 mapping tests; 387 integration|fixtures; perf ОСВ_60 0.294 с; golden 1034 строки vs 1045 в плане); review dispatched (opus)
R-15 (design #28): Ruling: после повторяемой неудачи маппинга process_once возвращает False -> пауза MAPPING_POLL_SECONDS — иначе 3 попытки за мс; цена: задержка в metrics.
R-16 (design #29): Ruling: статус документа по предупреждениям его листа; metrics.mapping_warnings — общий список файла — цена: передавать весь список.
R-17 (design #30): Ruling: «нарастающая по данным Q1» -> предупреждение -> NEEDS_CHECK (литерально D-7/D-9) — цена: исключить предупреждение из правила статуса. ДЛЯ ЧЕЛОВЕКА: любой Q2-Q4 документ, нарастающий определённый по данным, остаётся NEEDS_CHECK.
R-18 (design #31): Ruling: job_id/file_id в результате != заданию -> fatal; S3-ошибки кроме NotFound повторяемые; BALANCE/INCOME/OTHER игнорируются; сироты -> NEEDS_CHECK; ключ с ./../пустыми сегментами отвергается — цена: убрать проверки.
Task 7: review: Important T7.1 (повторы без паузы) -> fix round 1 (resume implementer; R-15)
Task 7: minor T7.2 (deferred): вставка пачками по 1000 с self-FK опирается на порядок parent раньше child (tabular-parser гарантирует, контракт нет) — сортировать по level или задокументировать; T7.3 (deferred): при снижении MAPPING_MAX_ATTEMPTS файлы с attempts>=max зависают в PROCESSING; T7.4 (deferred): два инстанса могут параллельно мапить одного кандидата (D-1; второй пропускает на шаге 3, S3 читается дважды, попытка при ошибке считается дважды); T7.5 (deferred): при старте без S3 цикл не запускается до рестарта; T7.6 (deferred, security-ish): текст pydantic ValidationError с input_value попадает в metrics.mapping_error (обрезать до типа и пути); T7.7 (carry -> Task 10): DELETE /files обязан держать порядок job -> file; T7.8 (carry -> Task 9): DocumentOut.warnings (R-6) может показывать предупреждения чужого листа — уточнить; T7.9 (deferred): golden-тест сверяет 4 из 6 сумм (closing* нет); T7.10 (deferred): цепочечное сравнение yug.account_id == chart_6603 is not None нечитаемо; T7.11 (deferred): решения #3/#4/#8 без тестов; T7.12 (deferred): тесты validate_result_key под integration (не в make check); T7.13 (deferred): при documents: [] сообщение «схема», а не «нет документов ОСВ» (исход ERROR верен)
Task 7: T7.1 fixed 67e255d; fix round 1/5 (1 addressed, 0 open; commits e704b02..67e255d)
O-2: ревьюер запускал pytest через uv run и оставил нетрекаемый api/uv.lock — удалён контроллером; ревьюерам: использовать api/.venv/bin/pytest
Task 7: complete (commits 63343d3..67e255d, review clean)
Task 8: dispatched (opus, BASE 67e255d, agent a545c94b9bd55b984); включает R-1 golden вторым коммитом
Task 8: implementer DONE b62cec8 + cd4220e (golden: 82 снимка/85 документов/13510 строк, 4.07 с; 398 integration|fixtures passed); review dispatched (opus)
R-19 (design #32): Ruling: автозамена под advisory-lock по ЮЛ + FOR UPDATE старых файлов по id, документы перечитываются после блокировки; документ без года/квартала не участвует — цена: убрать блокировку.
Task 8: review: Important T8.1 (нет теста: один новый файл заменяет несколько разных старых; version_no=max+1) -> fix round 1 (resume implementer)
Task 8: minor T8.2 (deferred): advisory-lock без теста гонки; T8.3 (deferred): при смене ключа документа между выборкой и блокировкой description пустой «документы: ;»; T8.4 (deferred -> вопрос человеку/OQ): старый файл с документами 60 и 51, замещённый файлом только с 60, целиком SUPERSEDED — документ 51 пропадает из списка; в description про 51 нет; T8.5 (deferred): golden — level сверять с row["level"]; T8.6 (deferred): golden — assert непустого плана счетов (иначе при пустом seed тест ничего не проверит); T8.7 (deferred): golden — повтор счёта в двух листах даст ложное падение при новых фикстурах
O-3: 19 нормализованных кодов строк-счетов golden нет в seed плана счетов (01.01, 66.01, 76.01, 60.АВ, 76.ОТ…) -> account_id NULL; проверить полноту seed субсчетов до Среза ВГО
Task 8: T8.1 fixed f2324b1; fix round 1/5 (1 addressed, 0 open; commits cd4220e..f2324b1)
Task 8: complete (commits 67e255d..f2324b1, review clean); tasks.md 3.3, 3.4 отмечены
Task 9: dispatched (opus, BASE f2324b1, agent a6be160d81a7aee15)
Task 9: implementer DONE 20983fb (427 integration|fixtures passed; 11 решений); review dispatched (opus)
R-20 (design #33): Ruling: JobBrief.mapping_error; 409 DOCUMENT_NOT_CONFIRMABLE; FILE_SUPERSEDED раньше ALREADY_CONFIRMED; исчезнувший документ -> 404 — цена: убрать поле/код. -> дописать в documents-api spec delta (Task 12).
R-21 (design #34): Ruling: confirm: блокировки файл -> документ, перечитывание под FOR UPDATE; чтение файлов в PrimaryFilesService, документов в DocumentService; DocumentOut по текущему заданию; пачечная загрузка вместо relationship — цена: порядок блокировок.
R-22 (design #35): Ruling: test_openapi_routes отвергает лишние маршруты под /legal-entities,/files,/documents (Task 10 обязан добавить DELETE) — цена: ослабить тест.
Task 9: minor T9.1 (deferred): make_entries_from_result импортирует приватные _entry_values/_group_schema маппера — сделать публичными; T9.2 (deferred): make_entries_from_result не проставляет counterparty_id; T9.3 (deferred): тавтологичная проверка yug["counterparty_name"] is None or isinstance(...) в golden-тесте test_api_documents; T9.4 (deferred): FileListQuery уже PageParams — лишний второй PageParams в read.py; T9.5 (deferred): дубль констант VIEW/NOT_FOUND в read.py и documents.py; T9.6 (deferred): file_out живёт в services/documents.py, импортируется из primary_files; T9.7 (deferred, follow-up для 2d): EntryOut без is_modified (нужен панели правки); T9.8 (carry -> Task 12): README каталог кодов ошибок (Task 3 + Task 9) и spec delta documents-api (mapping_error, DOCUMENT_NOT_CONFIRMABLE)
Task 9: complete (commits f2324b1..20983fb, review clean); tasks.md 4.1 отмечен
Task 10: dispatched (sonnet, BASE e2fde90, agent aeaa61c38463579b6)
Task 10: implementer DONE (41 integration passed; 14 тестов удаления); review dispatched (sonnet); concern: FK document из financial_statement_meta/consolidation_document/ex_snapshot_item не чистятся — Срезы 3/4 (OQ про удаление после консолидаций, Task 12)
R-23 (design #36): Ruling: delete: блокировки задания -> файл -> документы; 409 после блокировок; expires_at безусловно; FK на document из будущих таблиц не чистятся (Срез 3: financial_statement_meta) — цена: IntegrityError 500 в Срезе 3 без очистки. -> OQ в Task 12 (удаление после консолидаций, ретеншн).
Task 10: minor T10.1 (deferred): before_value.documents отсортированы по id, а карточка GET — по (type, account, locator, id); T10.2 (deferred): test_delete_error_file проверяет user_id is not None, не равенство инициатору; T10.3 (deferred): test_mapping_loop_ignores_deleted_file — не гонка, ветка job is None в _map_candidate не проверена (реальных гоночных тестов 2); T10.4 (deferred): _wait_for_lock_waiter считает любой Lock-ожидатель в БД; T10.5 (deferred, теоретич.): job, вставленный между lock_jobs и lock_file, не получит expires_at (путей создания второго job нет); T10.6 (note): DELETE живёт в endpoints/files/read.py по брифу
Task 10: complete (commits e2fde90..1502173, review clean); tasks.md 4.2 отмечен
Task 11: dispatched (sonnet, BASE ea2423d, agent a55fd95eed08797d7)
Task 11: implementer DONE f361fae; e2e: с профилем services PASSED (4.12 с, ОСВ 66 -> PROCESSED, 66/2024/Q4, cumulative, MATCH, RECOGNIZED, 9 строк, DELETE 204), без профиля 1 skipped; make check 900 passed; make test-integration 437 passed; review dispatched (sonnet)
O-4: compose-проект arm-ka запущен из уже удалённого .wt/slice-1/docs/dev/docker-compose.yml; парсер поднят с --no-deps -p arm-ka; make up из другого worktree может пересоздать инфраструктуру
O-5: DELETE лишь ставит expires_at — каждый e2e-прогон оставляет ~2 объекта в реальном MinIO (ретеншн OQ-22)
O-6: образ tabular-parser:dev пересобран из main соседнего репозитория (~35 с); контейнер tabular-parser после прогона остановлен
Task 11: review: Important T11.1 (README: конкуренция за recognition.result queue group с контейнером arm-core профиля services/локальным make run) -> fix round 1 (resume implementer)
Task 11: minor T11.2 (deferred): assert deleted.status_code == 204 в finally может замаскировать причину падения; T11.3 (deferred): проверка порядка sorted(x)==x тавтологична, суммы не сверяются с golden; T11.4 (deferred): MATCH достигается по имени, не по ИНН (формулировка брифа вводит в заблуждение) — комментарий; T11.5 (deferred): _EngineStub не вызывает dispose; T11.6 (deferred): make e2e не проверяет инфраструктуру (check-infra)
Task 11: T11.1 fixed 96525f1; fix round 1/5 (1 addressed, 0 open; commits f361fae..96525f1)
Task 11: complete (commits ea2423d..96525f1, review clean); tasks.md 5.1 отмечен
Task 12: dispatched (sonnet, BASE 96525f1, agent ad5f8d93d4465dcf2)
Task 12: implementer DONE f9b8a21 (arm-core) + docs 45ee08c; OQ-110..116; design №40-50; unit 900, integration 437, e2e 1 passed c реальным парсером (4.3 с), fixtures 13+1skip; docker-build, web-check EXIT 0; openspec validate --strict оба репо ok. review dispatched (sonnet). Контроллер запустил make check-full (e2e skip: парсер остановлен) -> check-full-controller.log
Контроллер: make check-full (HEAD f9b8a21, парсер остановлен) EXIT 0: unit 900 passed; integration 437 passed; e2e 1 skipped (профиль services не поднят); dbml-check пуст; docker-build и web-check ok — лог check-full-controller.log
Task 12: review: Important T12.1 (негативное требование OQ-55 «обмен/маппинг не пишут audit_event» не покрыто тестом, трассировка ссылалась на несуществующие проверки) -> fix round 1 (resume implementer)
Task 12: minor T12.2 (-> final fix wave): README.md:147 «(OQ — см. …)» без номера (OQ-115); T12.3: README «Ограничения» S3 — ссылки OQ-22 и OQ-111; T12.4: README.md:462/CLAUDE.md «запускаются при NATS_ENABLED» — цикл маппинга ещё требует storage; строка NATS_ENABLED в таблице настроек README:636 не перечисляет цикл; T12.5: tasks.md 3.1 сигнатура counterparty_check(header, owner_inn, owner_names_normalized); T12.6: docs 09-delivery-plan «Итог make check-full» — сверить с итоговым прогоном (контроллер: EXIT 0 при e2e skip; отдельно e2e 1 passed с парсером); T12.7: qa-guide U16: до ERROR файл UPLOADED (без ack); T12.8: неточные ссылки трассировки в task-12-report.md (test_full_cycle_over_real_nats в inbox; test_dlq_moves_current_file_to_error; mapping_error проверяет test_file_card_equals_list_item)
Task 12: T12.1 fixed 24d556e (2 теста на OQ-55); fix round 1/5 (1 addressed; commits f9b8a21..24d556e); T12 minors T12.2-T12.8 -> final fix wave
Final review: dispatched (opus, agent aa604d1a3615c92a7) on 55e538c..24d556e + docs 5545ee5/45ee08c; re-review Task 12 (haiku) параллельно
Task 12: re-review: T12.1 addressed (watchdog-тест слабее: мутация ловится падением expire_deadlines, не assert аудита); Task 12: complete (commits 96525f1..24d556e + docs 45ee08c, review clean)
FINAL REVIEW (opus): вердикт Needs fixes; критических нет. Находки:
I-1: osv_entry без индексов на parent_entry_id и recognition_job_id (и stored_file.current_recognition_job_id = T4.2): DELETE /files/{id} на таблице ~200 тыс. строк — 8,6 с на документ из 1000 строк (измерение в psql на временной таблице), при ~3 млн строк ~2 мин -> HTTP-таймаут с удержанием блокировок. Требует DBML v5.16 + миграция 0004 — решение человека (источник истины — DBML).
M-1 (= T7.6 расширенная): mapping_error содержит str(ValidationError) (input_value) и текст SQLAlchemy/asyncpg ([SQL: ...], параметры) до 2000 символов — виден пользователю с DOCUMENT_VIEW.
M-2: api/.env.example: «больше — ответ 413» неверно (rejected[FILE_TOO_LARGE], 201/422).
M-3: DELETE ставит expires_at объекту результата по ссылке job.result_s3_object_id без проверки префикса; inbox переиспользует s3_object при конфликте (bucket,key) -> чужой upload может получить expires_at.
M-4 (наблюдение): суммарный размер multipart и число файлов не ограничены (спул Starlette) — лимит ingress платформы; упомянуть в README/OQ.
R-24 (design #51): Ruling: I-1 НЕ чинится в этой ветке — нужен DBML v5.16 + миграция 0004 (индексы osv_entry(parent_entry_id), osv_entry(recognition_job_id), stored_file(current_recognition_job_id)); заведено как OQ + follow-up issue с блокером «до прода / до подсреза 2d (UI удаления)»; цена: DELETE деградирует на большой таблице. РЕШЕНИЕ ЧЕЛОВЕКА.
Fix wave (ONE fixer, fresh opus): M-1, M-2, M-3, M-4(README), T7.2 (сортировка entries по level перед пачками), T12.2-T12.8, гигиена тестов T9.3, T8.6, T11.2, T2.2, T6.1, T5.1, T1.1; OQ для I-1 в docs.
Final fix wave: dispatched (opus fresh, BASE 24d556e, agent ac9672d78b1c518c4)
Final fix wave: DONE (583edf2, 2ac19c6, d14295b, 75feab6 + docs 8c73fe9); make check 901 passed; integration|fixtures 448 passed, 1 skipped; openspec validate ok оба репо; scoped re-review dispatched
Контроллер на HEAD 75feab6: e2e с реальным tabular-parser 1 passed (4.01 с); -m fixtures 14 passed (с e2e); парсер остановлен, инфраструктура Up
Re-review fix wave (sonnet): all findings addressed, new breakage none (130 passed focused).
FINAL STATUS findings: M-1 fixed 583edf2; T7.2 fixed 583edf2; M-3 fixed 2ac19c6; M-2, M-4, T12.2-T12.8 fixed d14295b (+docs 8c73fe9); T9.3/T8.6/T11.2/T6.1/T5.1/T1.1 fixed 75feab6; T2.2 confirmed already ok; I-1 deferred (R-24, OQ-117, DBML v5.16 + миграция 0004, решение человека).
FINAL make check-full на HEAD 75feab6 (контроллер): EXIT 0 — unit 901 passed; integration 444 passed; e2e 1 skipped (профиль не поднят); docker-build и web-check ok; e2e с реальным tabular-parser 1 passed (4.01 с); -m fixtures 14 passed.