## Context
Запись входящего файла сейчас — двухфазная: `Tmp/.<имя>.part`, затем rename (SMB) / жёсткая ссылка (local) в `Input`
без замены (решения №17, №26, №32, №35 Среза 3a). Старый АРМ писал файл прямо в `Input` и в контуре работал. Владелец
09.10.2026 выбрал повторить старый АРМ и перезаписывать существующий файл.
## Goals / Non-Goals
**Goals:** прямая запись в `Input` в обоих режимах; меньше требований к правам на шаре; срок ожидания ответа 20 мин.
**Non-Goals:** см. proposal.
## Decisions
### D-1. Прямая запись с усечением (решение владельца 09.10.2026)
`SmbHotFolder.put`: `smbclient.open_file(\<имя>, mode="wb", **credentials)` и `write`. `LocalHotFolder.put`:
`open(/<имя>, "wb")` (`Path.write_bytes`). Существующий файл перезаписывается. Повторная запись в рамках одного
процесса по-прежнему исключена дедупликацией по `job_id` в воркере (требование «Подтверждение запроса до обработки»);
перезапись возможна только при повторе задания ядром в другом процессе.
### D-2. Каталог `Input` создаётся, как раньше
Перед записью `Input` создаётся без родителей, «уже есть» и отказ в правах при существующем каталоге — не ошибка
(`_ensure_dir` в `smb`, `mkdir(exist_ok=True)` в `local`); корень не создаётся. Старый АРМ каталог не создавал, но
терпимое создание поведения в контуре не меняет, а dev-режим и тесты на нём держатся.
### D-3. `Tmp` остаётся только у эмулятора
`SCAN_TMP_DIR` удаляется из настроек шлюза (`Settings`, `extra="ignore"` — старое значение в `.env` не ломает старт).
Эмулятор читает свою переменную `SCAN_TMP_DIR` (`EmulatorSettings`) и собирает набор ответа через
`LocalHotFolder.tmp_dir`, поэтому параметр `tmp_dir` у `LocalHotFolder` остаётся, но `put` его не использует.
### D-4. Срок ожидания 20 мин
`SCAN_TIMEOUT_SECONDS` по умолчанию 1200. Эффективный дедлайн — `min(reply_deadline, старт + SCAN_TIMEOUT_SECONDS)`,
поэтому arm-core поднимает срок ответа OCR до 20 мин отдельным PR (RSHB/arm-core#87).
### D-5. Ошибка записи
При исключении во время открытия/записи `put` снимает недописанный `/<имя>` только при включённой очистке
(ошибка снятия подавляется, исходное исключение поднимается) — R-3.
## Risks / Trade-offs
- ИС может увидеть недописанный файл (OQ-79) → смягчение: монопольное открытие `smbclient` на время записи; так же
работал старый АРМ. Откат — вернуть двухфазную запись из истории ветки.
- Ошибка посреди записи оставляет обрезанный файл в `Input` → удаляется только при `SCAN_CLEANUP=true` (как `.part`
раньше, D-5); иначе остаётся, конверт `RECOGNITION_FAILED`.
## Decisions made without discussion
| # | Дата | Решение | Обоснование | Цена ошибки |
|---|---|---|---|---|
| R-1 | 2026-10-09 | Режим `local` тоже пишет прямо в `Input` (не только `smb`) | Одинаковая семантика режимов, один набор тестов; эмулятор пишет/читает локально мгновенно | Эмулятор может прочитать недописанный файл — на локальной ФС практически невозможно; откат — вернуть `.part` в `local` |
| R-2 | 2026-10-09 | `Input` по-прежнему создаётся перед записью (D-2), хотя старый АРМ этого не делал | Отказ в правах на `mkdir` при существующем каталоге уже не ошибка; dev и тесты на этом держатся | Лишний вызов `mkdir` на шаре на каждое задание |
| R-3 | 2026-10-09 | Ошибка записи: недописанный файл в `Input` удаляется только при `SCAN_CLEANUP=true` (D-5) | Требование «при `SCAN_CLEANUP=false` ничего не удалять» (OQ-144) сохраняется | Обрезанный файл остаётся в `Input`, ИС может его распознать с ошибкой; ядро получит `RECOGNITION_FAILED` от шлюза раньше |
| R-4 | 2026-10-09 | `SCAN_TMP_DIR` удаляется из шлюза, у эмулятора остаётся (D-3) | Шлюзу каталог больше не нужен; эмулятору нужен для атомарной сборки ответа | Нет: `extra="ignore"` — старые `.env` стартуют |
| R-5 | 2026-10-09 | Правка вызова `SmbHotFolder` в фабрике (`gateway/__init__.py`) перенесена из задачи 2 плана в задачу 1 | Без неё mypy красный между задачами (лишний позиционный аргумент) | Нет |
| R-6 | 2026-10-09 | Сверх плана: `tests/unit/conftest.py` не передаёт `SCAN_TMP_DIR`; `test_worker.py::test_existing_input_waits_for_answer` ждёт перезаписи объектом S3; в `test_roundtrip.py` эмулятору передаётся литерал `"Tmp"` | Прямые следствия D-1 и D-3 | Нет |
| R-7 | 2026-10-09 | Финальное ревью I-1: ошибка **открытия** входящего файла ничего не удаляет (открытие вынесено из блока снятия недописанного файла); снимается только файл, сбойнувший после открытия | Иначе при `SCAN_CLEANUP=true` удалялся бы целый файл прошлой доставки, который, возможно, читает ИС | Нет |
| R-8 | 2026-10-09 | Финальное ревью I-1(б): если при повторе задания в другом процессе файл занят ИС (`STATUS_SHARING_VIOLATION` на открытии), шлюз публикует `RECOGNITION_FAILED`, а не считает файл доставленным — как старый АРМ (`wb` без особых случаев) | Решение владельца «как в старом АРМ»; повтор в другом процессе — только после потери ack (редко) | Ядро может получить `RECOGNITION_FAILED` раньше успешного ответа первого экземпляра (OCR `max_attempts` = 1) — аналитику придётся перезагрузить файл; вынесено вопросом владельцу |