225 Commits

Author SHA1 Message Date
Andrey Epifancev
0e1fbd8036 fix(consolidation): ревью Task 6 — гонки автозамены с подхватом, выбор по confirmed_at, одна выборка ГК
- T6.0: тесты на двух сессиях «автозамена ∥ подхват при правке периметра» и «автозамена ∥ создание версии»:
  автозамена ждёт, снимает незафиксированную ранее привязку, unbound_version_ids — эта версия.
- T6.2: тесты выбора самого позднего confirmed_at строятся так, чтобы «наибольший id» проигрывал;
  тест равенства confirmed_at (больший id, решение №35).
- T6.3: реальный подхват через PUT …/members — версия в INITIATED, статус не меняется.
- T6.4: lock_versions_for_entity не читает ГК ЮЛ второй раз (подзапрос заменён значением).
- T6.5: design №30 — цена: подтверждения документов ЮЛ одной ГК сериализуются на advisory даже без версий в работе.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-06 05:11:03 +04:00
Andrey Epifancev
36bc966bf6 feat(consolidation): привязка документов к версиям при подтверждении, подхват и отвязка при автозамене
- consolidation_documents: чистая fits (D-5), bind_confirmed (подхват, самый поздний confirmed_at на ключ R-1),
  unbind_entity, on_document_confirmed (привязка ко всем подходящим версиям «в работе», переход в DATA_CORRECTION
  через transition), on_documents_superseded (отвязка документов заменённых файлов).
- DocumentService.confirm вызывает хук привязки; supersede_previous снимает привязки и пишет
  FILE_REPLACE.after_value.unbound_version_ids; create и PUT …/members подхватывают по-настоящему.
- R-2: advisory-блокировка привязок ГК (lock_group_bindings) в create и lock_versions_for_entity закрывает
  гонку «создание версии ∥ подтверждение/автозамена».
- R-3: lock_document и lock_file_documents — FOR NO KEY UPDATE (совместимо с FOR KEY SHARE вставки привязки).
- Тесты: таблица fits, сценарии documents-api и primary-files, две версии одного документа, гонки на двух
  сессиях (confirm ∥ members в обе стороны, create ∥ confirm в обе стороны, цикл блокировок документа и версии).
- design.md: решения №30–№36; plan и tasks.md (3.1) отмечены.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-06 04:57:15 +04:00
Andrey Epifancev
90b7bd5c16 feat(consolidation): редактирование периметра версии с подхватом и отвязкой документов
PUT /consolidations/versions/{id}/members: замена состава ЮЛ версии, проверка принадлежности ГК
(ENTITY_NOT_IN_GROUP), минимум два ЮЛ, diff с отвязкой документов ушедших (unbind_entity) и подхватом
добавленных (bind_confirmed, заглушка до Task 6), аудит MEMBERSHIP_CHANGE. Общий пролог
«lock → видимость → редактируемость» вынесен в services/consolidation_guard.py (T4.2). Решения #25–#29.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-06 04:34:52 +04:00
Andrey Epifancev
e317fe59dd fix(consolidation): ревью Task 4 — общий lock_out, точнее тесты, решения #17–#24 в design.md
- I-1 (T4.1): lock_out — публичный в services/consolidations.py, копия в consolidation_locks удалена
- M-b (T4.3): удалён осиротевший комментарий в tests/factories.py
- M-c (T4.4): точная проверка ip_address/user_agent в аудите смены статуса
- M-d (T4.5): docstring теста Review Focus 4 ссылается на различающий тест порядка D-9
- design.md: решения без обсуждения #17 (R-8), #18 (R-9), #19–#24 (решения Task 4)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-06 04:26:31 +04:00
Andrey Epifancev
b2fa59061d feat(consolidation): политика редактируемости, блокировка с перехватом, ручные статусы Среза 4
- domain/policy: IN_WORK_STATUSES, ensure_version_editable (VERSION_NOT_EDITABLE / LOCK_REQUIRED / LOCKED_BY_OTHER)
- services/consolidation_locks: take/release, перехват с причиной, release_active_lock; FOR UPDATE версии первой (D-9),
  вставка под SAVEPOINT, нарушение uq_consolidation_lock_version_active -> 409 LOCKED_BY_OTHER с владельцем
- services/consolidation_status: MANUAL_TRANSITIONS, transition (единственное место смены статуса: история + аудит,
  CANCELLED снимает блокировку), ConsolidationStatusService.change
- маршруты POST/DELETE …/lock, POST …/status; реестр constraint_conflicts
- тесты: таблица ensure_version_editable, сценарии спеки, гонка двух POST …/lock, перехват во время правки и
  аннулирования (две сессии, Review Focus 4)
- минорные T3.1 (FINISHED_STATUSES из policy в фабриках), T3.2 (удалена CONSOLIDATION_NOT_FOUND)
- план: шаги Task 4; tasks.md 2.2, 2.3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-06 04:16:23 +04:00
Andrey Epifancev
9411d9d2ae feat(consolidation): создание консолидации, список по ГК, карточка версии
POST /consolidations (версия №1 INITIATED, периметр из текущих членов ГСК, история,
CONSOLIDATION_CREATE с consolidation_version_id), GET /company-groups/{id}/consolidations
и GET /consolidations/versions/{id}. reporting_date по OQ-40; подхват документов — заглушка
bind_confirmed до Task 6, чек-лист в карточке пуст до Task 8. UNIQUE периода -> 409
CONSOLIDATION_EXISTS; RequestAudit.record принимает consolidation_version_id.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-06 03:57:52 +04:00
Andrey Epifancev
d3c37db197 feat(consolidation): репозиторий, схемы и фабрики консолидаций
Репозиторий консолидаций (get_version/list_periods/lock_versions_for_entity с блокировкой
надмножества версий по R-2, load_osv_rows, entity_files_state), схемы API (D-16),
domain/checklist.EntityFilesState (R-5), фабрики make_consolidation/make_lock/make_confirmed_osv
и очистка таблиц консолидации в CommittedJobs.cleanup().

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-06 03:46:15 +04:00
Andrey Epifancev
eaee3ec5f9 feat(model): DBML v5.16 — counterparty_match_kind, миграция 0004, шаг резолва в маппинге
Enum counterparty_match_kind (INN, NAME, ALIAS, MANUAL, PROPAGATED) и nullable-колонка osv_entry.counterparty_match_kind
(NULL <=> counterparty_id NULL). Миграция 0004_dbml_v5_16: enum, колонка, backfill тремя UPDATE по уже смапленным
строкам (ИНН без пробелов -> INN, trim(имя) -> NAME, остальные -> ALIAS), откат удаляет колонку и тип.
CounterpartyIndex.resolve возвращает Resolution(entity_id, kind) (определение в domain/osv_mapping, R-4);
маппинг пишет kind; EntryOut.match_kind. DBML_VERSION 5.16, Makefile/dbml_parity/CLAUDE/README/config.yaml.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-06 03:23:02 +04:00
Andrey Epifancev
be509f2bf5 docs(slice-4): решения без обсуждения №11–15 (pre-flight)
Рулинги контроллера R-1…R-5 из pre-flight-проверки плана: ключ привязки с признаком нарастающего,
блокировка надмножества версий, FOR NO KEY UPDATE на документах/файлах, Resolution в домене,
разнесение EntityFilesState между задачами.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-06 03:22:26 +04:00
Andrey Epifancev
52ae1b937d plan(slice-4): план исполнения — 14 задач по спеке slice-4-consolidation
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-06 03:04:17 +04:00
Andrey Epifancev
6e5ab834ec spec(slice-4): OpenSpec change консолидации — версии, периметр, блокировка, привязка документов, сводная ОСВ, экспорт
Proposal, design (D-1…D-16, решения без обсуждения), tasks и дельты спек: новые capability consolidations-api и
consolidated-osv; изменения data-model (DBML v5.16, counterparty_match_kind), documents-api (привязка при
подтверждении), primary-files (consolidation_version_id, отвязка при автозамене), osv-result-mapping (match_kind,
NEEDS_CHECK → DATA_CORRECTION), directory-policy (редактируемость версии), audit-trail (события консолидации).
Решения владельца 06.10.2026: OQ-03 пробел, OQ-19 блокировка, статусы по событиям, явная привязка, экспорт одним листом.

Refs RSHB/arm-core#18

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-06 02:52:23 +04:00
aep
714832efd9 Merge pull request 'openspec: архив change slice-2c-osv-ingest' (#16) from chore/archive-slice-2c-osv-ingest into main
Reviewed-on: #16
2026-10-06 01:00:09 +03:00
Andrey Epifancev
09c1a0fa8e openspec: архив change slice-2c-osv-ingest (2026-10-06), спеки primary-files, osv-result-mapping, documents-api, дельты exchange/data-model/audit-trail
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-06 01:57:57 +04:00
aep
54630ee3cc Merge pull request 'Срез 2c: загрузка файлов ОСВ, цикл маппинга результата в document/osv_entry, API документов, DBML v5.15' (#15) from feat/14-slice-2c-osv-ingest into main
Reviewed-on: #15
2026-10-06 00:56:13 +03:00
Andrey Epifancev
6ccb99194d feat(model): индексы osv_entry и stored_file в v5.15 (I-1, OQ-117)
- ORM: ix_osv_entry_parent_entry_id, ix_osv_entry_recognition_job_id (models/osv.py),
  ix_stored_file_current_recognition_job_id (models/files.py) — по DBML v5.15 п. 4.
- Миграция 0003_dbml_v5_15: create_index в upgrade, drop_index в downgrade.
- tests/test_migrations.py: индексы есть после upgrade head (btree, не unique), исчезают
  после downgrade на 0002 и возвращаются при повторном upgrade.
- openspec change: design.md решение №53 (I-1 закрыт в v5.15), Migration Plan; дельта
  data-model — 0003 создаёт индексы, откат на шаг их удаляет.
- README «Ограничения Среза 2c»: убран пункт об отсутствии индексов.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-06 00:56:28 +04:00
Andrey Epifancev
b054ca6261 docs: ledger SDD Среза 2c (рулинги, находки, статусы)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-06 00:50:17 +04:00
Andrey Epifancev
75feab6015 test: гигиена тестов Среза 2c и нормализация email seed в одном месте
- test_api_documents: тавтология по counterparty_name строки 18 ОСВ_66 заменена
  значениями снимка (T9.3).
- test_mapping_golden: план счетов не пуст и есть строки с account_id (T8.6).
- e2e: код ответа DELETE проверяется после try/finally — не маскирует причину
  падения (T11.2).
- test_counterparty_resolve: единственное точное имя побеждает неоднозначность
  нормализованного (T6.1).
- test_domain_osv_mapping: шапка «ООО» без ИНН → UNKNOWN (T5.1).
- seed_lib/apply: email пользователя нормализуется один раз для upsert и ролей,
  поведение не меняется (T1.1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-06 00:39:40 +04:00
Andrey Epifancev
d14295b6d0 docs: ограничения Среза 2c, OQ-117, решения №51–52, уточнения README/CLAUDE.md
- .env.example: UPLOAD_MAX_BYTES — больший файл уходит в rejected[] с кодом
  FILE_TOO_LARGE, ответ 201/422, а не 413 (M-2).
- README «Ограничения»: размер multipart-запроса и число файлов приложением не
  ограничены — лимит ingress платформы (M-4); индексы osv_entry — OQ-117 (I-1);
  S3 — OQ-22 и OQ-111 (T12.3); автозамена частью документов — OQ-115 (T12.2);
  удаление ставит expires_at только объектам папок файла (M-3).
- README/CLAUDE.md: цикл маппинга запускается при NATS_ENABLED и подключённом S3;
  строка NATS_ENABLED в таблице настроек перечисляет цикл маппинга (T12.4).
- tasks.md 3.1: сигнатура counterparty_check(header, owner_inn,
  owner_names_normalized) (T12.5).
- design.md: №51 (R-24, I-1 — решение человека, OQ-117), №52 (R-25, fix wave
  M-1/M-3/T7.2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-06 00:38:02 +04:00
Andrey Epifancev
2ac19c6953 fix(files): DELETE ставит expires_at только объектам папок своего файла
M-3: s3_object.expires_at = now ставится только объектам с ролью uploads и ключом
uploads/{file_id}/… или с ролью results и ключом results/{file_id}/…; ссылка
задания на объект другого файла (inbox переиспользует s3_object при конфликте
(bucket, key)) не трогается и ошибкой не считается.

Фабрика make_stored_file кладёт загрузку в папку файла uploads/{file_id}/…, как
upload_key в проде.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-06 00:35:43 +04:00
Andrey Epifancev
583edf2a8d fix(mapping): категории вместо текста исключений в mapping_error, вставка строк по level
M-1 (= T7.6): metrics.mapping_error виден пользователю (JobBrief.mapping_error) —
ошибка схемы даёт «результат не соответствует схеме recognition-result: ошибок N,
первая: <loc> <type>» без input_value и ссылок pydantic; исчерпавшая попытки
ошибка БД/S3 — «маппинг не удался: ошибка БД/S3 (<ExcType>), попыток N» без
[SQL: …] и параметров. Полный текст исключения — только в логе.

T7.2: черновики osv_entry сортируются устойчиво по level перед вставкой пачками —
родитель раньше потомка на любой границе пачки, независимо от порядка строк
результата.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-06 00:33:49 +04:00
Andrey Epifancev
24d556e41e test(audit): обмен и маппинг не пишут audit_event (OQ-55)
Негативное требование спеки audit-trail: ack, результат, маппинг и таймаут вахтёра не создают строк audit_event.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-06 00:20:07 +04:00
Andrey Epifancev
f9b8a21410 docs: README/CLAUDE.md Среза 2c, tasks.md
README: разделы «Первичные документы: загрузка», «Статусы файла и документа», «Цикл маппинга», «API файлов и документов»,
«Удаление и замена», «Ограничения Среза 2c»; настройки UPLOAD_MAX_BYTES/MAPPING_POLL_SECONDS/MAPPING_MAX_ATTEMPTS; каталог
кодов ошибок файлов и документов. CLAUDE.md: модули Среза 2c, три фоновых цикла, порядок блокировок, маркер e2e.
OpenSpec: tasks.md (все [x], кроме 5.3; формулировки 1.2/2.1/3.1 по коду), дельта documents-api (job.mapping_error,
DOCUMENT_NOT_CONFIRMABLE), design.md — решения №40–50.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-06 00:11:52 +04:00
Andrey Epifancev
96525f1ace docs(e2e): предупреждение о конкуренции за recognition.result
e2e подписывается на реальный recognition.result в queue group arm-core: другой экземпляр ядра (контейнер arm-core
профиля services, локальный сервер с NATS_ENABLED=true) заберёт часть ack/результатов. README и docstring теста
требуют остановить такие экземпляры; комментарий: MATCH достигается по имени (ИНН в шапке нет).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 23:58:16 +04:00
Andrey Epifancev
f361fae79b test(e2e): сквозной прогон загрузки ОСВ через compose с реальным tabular-parser
Тест tests/e2e (маркеры integration, e2e, fixtures): полное приложение src.app.app с lifespan на временной БД,
реальные NATS и MinIO, tabular-parser из профиля services; ОСВ 66 за 2024 -> PROCESSED, документ 66/2024/Q4,
9 строк, counterparty_check = MATCH, status = RECOGNIZED; без профиля — skip. Makefile: цель e2e, check-full
включает e2e, test-integration исключает e2e. README: раздел «Сквозной прогон (e2e)».

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 23:55:23 +04:00
Andrey Epifancev
ea2423dec9 OpenSpec: решение Task 10 (design №36)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 23:47:10 +04:00
Andrey Epifancev
15021735e9 feat(files): удаление файла с ограничениями, expires_at у объекта S3, FILE_DELETE
DELETE /files/{file_id} (DOCUMENT_EDIT, policy): блокировки «задание → файл → документы», проверки после
блокировок (409 FILE_HAS_CONFIRMED_DOCUMENTS, FILE_SUPERSEDES_OTHERS), удаление osv_entry/document/recognition_job/
stored_file (разрыв FK-цикла), expires_at у объектов S3 файла и результатов, аудит FILE_DELETE с before_value.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 23:43:54 +04:00
Andrey Epifancev
e2fde90293 OpenSpec: решения Task 9 (design №33–35)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 23:39:32 +04:00
Andrey Epifancev
20983fb3d8 feat(files): API файлов, документов и строк ОСВ; подтверждение документа
GET /legal-entities/{id}/files (include_superseded, по убыванию uploaded_at),
GET /files/{id}, GET /documents/{id} (group_schema листа, warnings — R-6),
GET /documents/{id}/entries (limit ≤ 2000, по умолчанию 500),
POST /documents/{id}/confirm (DOCUMENT_EDIT; блокировка файла, затем документа;
409 FILE_SUPERSEDED / ALREADY_CONFIRMED / DOCUMENT_NOT_CONFIRMABLE; аудит
DOCUMENT_CONFIRM). Видимость — через ЮЛ-владельца, чужое — 404 как несуществующее.
Список файлов без N+1: файлы, текущие задания и документы — пакетными запросами.
JobBrief.mapping_error — причина ERROR файла по маппингу.

Тесты: сценарии documents-api, гонка подтверждения с автозаменой (Review Focus 5),
make_entries_from_result (R-2), реальная проверка уникальности документа (T2.2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 23:34:35 +04:00
Andrey Epifancev
f2324b15d3 test(mapping): автозамена нескольких файлов одним новым — N событий, version_no = max + 1
Ревью T8.1: новый файл с ОСВ 60/62/66 заменяет три прежних файла с version_no 1, 3, 2 по
возрастанию id (максимум в середине порядка блокировки) → три FILE_REPLACE, version_no = 4.
Мутации `locked[0]` / `locked[-1]` вместо max тест ловит.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 23:18:57 +04:00
Andrey Epifancev
dff448d464 OpenSpec: решение Task 8 (design №32)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 23:16:18 +04:00
Andrey Epifancev
cd4220efc6 test(mapping): golden-прогон всех снимков парсера в БД (82 результата)
`tests/test_mapping_golden.py` (integration + fixtures, рулинг R-1, tasks 3.4): все
`../fixtures/golden/expected_osv_parse/**/*.json` → файл ЮЛ в PROCESSING с заданием `done`
→ `map_file` в откатываемой транзакции с seed. Инварианты спеки на каждом документе: строк =
строк снимка без TOTAL, у строк уровня > 0 есть родитель, шесть сумм равны снимку, `account_id`
= план счетов seed для нормализованного кода, вне плана — NULL. 82 снимка, 85 документов,
13510 строк, маппинг ~4 с (бюджет 60 с); 19 кодов строк-счетов вне плана seed.

Путь теста в tasks.md 3.4 приведён к рулингу R-1 (`tests/test_mapping_golden.py`).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 23:08:25 +04:00
Andrey Epifancev
b62cec83ba feat(mapping): автозамена прежних версий файла после маппинга (FILE_REPLACE)
`supersede_previous` вызывается в `map_file` после `PROCESSED`, в той же транзакции (D-10):
файлы того же ЮЛ (не SUPERSEDED, не сам новый) с документом тех же type/счёт/год/квартал →
SUPERSEDED, `superseded_by_file_id`, `updated_at`; новому — `version_no` = max + 1;
одно системное событие `FILE_REPLACE` на заменённый файл (user_id NULL, инициатор загрузки,
перечень документов и `confirmed=true|false` в description). Документы и строки заменённых
файлов сохраняются. Автозамены одного ЮЛ сериализуются advisory-блокировкой, заменяемые
файлы блокируются FOR UPDATE в порядке id, документы перечитываются после блокировки.

Тесты: сценарий спеки «Новая ОСВ 60 за 1 кв. 2025» (CONFIRMED и RECOGNIZED), другой
счёт/год/квартал, другое ЮЛ, идемпотентность после сбоя, цепочка v1→v2→v3, одно событие на файл.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 23:06:37 +04:00
Andrey Epifancev
67e255d6c2 fix(mapping): пауза между повторами маппинга после сбоя
R-15 (design №28, находка T7.1): после повторяемой неудачи process_once возвращает False — run_mapping_loop
спит MAPPING_POLL_SECONDS перед следующей попыткой; кратковременный сбой S3/БД больше не сжигает все
MAPPING_MAX_ATTEMPTS за миллисекунды. Неповторяемая ошибка, исчерпание попыток (файл ERROR) и успешный
маппинг — по-прежнему True.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 22:54:11 +04:00
Andrey Epifancev
ecdf803bd4 OpenSpec: решения Task 7 (design №28–31)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 22:52:03 +04:00
Andrey Epifancev
e704b028ec feat(mapping): фоновый цикл маппинга результата в document/osv_entry (OQ-69, OQ-70)
services/result_mapping.py: pick_candidate (FOR UPDATE OF job SKIP LOCKED, файл PROCESSING и задание текущее),
проверка ключа results/{file_id}/{job_id}/ и бакета, чтение S3 вне транзакции, map_file одной транзакцией
(очистка документов/строк файла, документ на лист ОСВ, повтор счёта — предупреждение и NEEDS_CHECK первого листа,
строки пакетами по 1000, резолв контрагента COUNTERPARTY+BU/SUM (R-3), нарастающая по Q1 (D-9), group_schema,
metrics.mapping_warnings, PROCESSED через set_file_status_for_job). Неповторяемые ошибки — сразу ERROR с
metrics.mapping_error; прочие — откат и mapping_attempts, на MAPPING_MAX_ATTEMPTS — ERROR. run_mapping_loop —
третья задача ExchangeTasks при подключённом S3 (иначе предупреждение).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 22:44:45 +04:00
Andrey Epifancev
63343d3cba OpenSpec: решение Task 6 (design №27)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 22:29:52 +04:00
Andrey Epifancev
9658f809e7 feat(mapping): индекс и резолв контрагентов по ИНН/имени/алиасу (§3 п. 4)
CounterpartyIndex.load — два запроса по всему справочнику без policy (включая закрытые ЮЛ);
resolve — ИНН, затем точное имя, затем normalize_alias по именам и алиасам, >1 кандидата → None;
неизвестный ИНН не блокирует поиск по имени. owner_names — для counterparty_check.
Отмечены шаги Task 6 и пункт 3.1 tasks.md.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 22:27:42 +04:00
Andrey Epifancev
4e9253204c План и tasks.md: число строк golden ОСВ_66 — 9 (R-13)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 22:25:25 +04:00
Andrey Epifancev
764cd980c7 OpenSpec/план: решения Task 5 (design №25–26), число строк golden ОСВ_66 — 9
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 22:25:16 +04:00
Andrey Epifancev
f9306b8270 feat(domain): маппинг osvDocument в черновики документа и строк, нормализация кода счёта, проверки (§1.3, §9.3)
domain/osv_mapping.py — чистые функции без БД и S3 (design D-6…D-9):
normalize_account_code / account_root (ASSUMPTION(OQ-103)), build_entries
(без TOTAL, дерево по parent_row_number, сироты, account_id по плану счетов,
EntryDraft.counterparty_id = None по R-3), document_fields (DocumentDraft),
counterparty_check, document_status (OQ-05), opening_matches (±0.01, OQ-02).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 22:20:22 +04:00
Andrey Epifancev
3b4d3a45fe fix(exchange): INVALID_REQUEST публикатора переводит файл в ERROR
T4.1: публикатор, переводя задание в финальный error/INVALID_REQUEST, теперь в той же транзакции (под тем же
FOR UPDATE) ставит файлу текущего задания ERROR (spec primary-files «Жизненный цикл статуса файла»); файл
чужого задания не меняется. Тест test_invalid_request_marks_job_error_and_pass_continues[current|non_current];
CommittedJobs.make_file_job принимает original_name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 22:13:58 +04:00
Andrey Epifancev
b7ca7285cc feat(exchange): статусы stored_file по ack/ошибке/таймауту/dlq; inbox без ResultMapper (OQ-67)
- services/file_status.py: set_file_status_for_job(session, job, status, *, now, only_from=None) и пакетный
  set_file_status_for_jobs для вахтёра; переход только при current_recognition_job_id = job.id, PROCESSED и
  SUPERSEDED не перезаписываются (D-5, R-5).
- inbox: ack -> файл UPLOADED->PROCESSING; error -> ERROR; done/needs_check -> UPLOADED->PROCESSING (R-4: ack мог
  потеряться), из PROCESSING файл ждёт цикла маппинга; inbox не читает S3 и не создаёт документы.
- вахтёр: UPDATE recognition_job ... RETURNING id и UPDATE stored_file -> ERROR в той же транзакции;
  публикатор при dlq -> файл ERROR.
- удалены ResultMapper, NoopMapper, _map_result, _MapperError и параметр mapper у handle_message; app.py без mapper.
- тесты: статусы файла (ack/error/done/needs_check/без ack/timeout/dlq), чужое задание файл не трогает (Review
  Focus 1), откат статуса файла вместе с транзакцией сообщения; CommittedJobs.make_file_job/add_job/file.
- tasks.md 2.2, шаги Task 4 в плане.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 22:08:40 +04:00
Andrey Epifancev
8d2ced835c OpenSpec: решения Task 3 (design №21–24)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 21:55:21 +04:00
Andrey Epifancev
be0d1b410c feat(files): загрузка файлов организации — S3, stored_file, recognition_job в outbox, дедуп, аудит
POST /api/v1/legal-entities/{id}/files (DOCUMENT_EDIT, видимость ЮЛ → 404): тип по расширению
со сверкой сигнатуры (domain/file_types.py), лимит UPLOAD_MAX_BYTES → FILE_TOO_LARGE, дедуп по
sha256 в пределах ЮЛ (кроме SUPERSEDED) с рекомендательной блокировкой (ЮЛ, sha256) до конца
транзакции запроса, s3_object → put_object → stored_file(UPLOADED) → recognition_job(queued) →
current_recognition_job_id, аудит FILE_UPLOAD. Каждый файл — SAVEPOINT: частичный успех
(201/422), отказ S3 на файле — STORAGE_UNAVAILABLE только для него; без S3 — 503.
safe_filename/CONTENT_TYPES/read_limited вынесены в common/filenames.py (импорт справочников
их переиспользует). Схемы UploadResult/FileOut/JobBrief/DocumentBrief, PrimaryFilesService и
зависимость для Task 9–10. tasks.md 2.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 21:50:29 +04:00
Andrey Epifancev
2d69a9e867 chore: S3 get_bytes, протокол ObjectStorage, фабрики файлов/документов, настройки загрузки и маппинга
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 21:35:41 +04:00
Andrey Epifancev
ab606dc59a feat(model): DBML v5.15 — osv_data_type NU, email в нижнем регистре, миграция 0003
- OsvDataType.NU после BU, DBML_VERSION = 5.15, Makefile/dbml_parity/docstring'и на data-model-v5.15.dbml;
- миграция 0003_dbml_v5_15: ADD VALUE 'NU' AFTER 'BU' + lower(app_user.email); downgrade пересоздаёт osv_data_type
  (регистр email не возвращается);
- email хранится в нижнем регистре: users_admin.create_user и seed (_apply_users); с UNIQUE по строчному email гонка
  создания в другом регистре теперь ловится индексом (тест test_api_constraint_races обновлён);
- тесты: enum NU, откат на шаг, lower email на миграции, seed/админка, вставка NU, фиктивная v5.16.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 21:28:04 +04:00
Andrey Epifancev
d905e3aa04 OpenSpec: решения контроллера по pre-flight плана Среза 2c (design №14–20)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 21:17:05 +04:00
Andrey Epifancev
55e538ce9d chore: план исполнения Среза 2c (12 задач + финальное ревью)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 21:14:30 +04:00
Andrey Epifancev
239d5bc270 OpenSpec: change slice-2c-osv-ingest (загрузка файлов, цикл маппинга, документы — спека)
proposal, design (D-1…D-13), спеки primary-files, osv-result-mapping, documents-api (новые), recognition-job-exchange,
data-model (MODIFIED), audit-trail (ADDED), tasks (13 задач).

Issue: RSHB/arm-core#14

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 21:08:56 +04:00
aep
66eb214a66 Merge pull request 'Контракт результата v1.1: копия схемы и OsvRow.extra' (#13) from fix/11-contract-v1-1-copy into main
Reviewed-on: #13
2026-10-05 19:46:10 +03:00