arm-core/docs/superpowers/plans/2026-10-05-slice-2c-osv-ingest-ledger.md
2026-10-06 00:50:17 +04:00

154 lines
33 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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<n>.<k> 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.