Открытие вынесено из блока снятия недописанного файла: при SCAN_CLEANUP=true
удаляется только файл, сбойнувший после открытия. Решения R-5…R-8 — в design.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- F-D: фейк rename бросает SMBOSError(STATUS_OBJECT_NAME_COLLISION) (и обычный FileExistsError), rmdir — STATUS_DIRECTORY_NOT_EMPTY;
тесты validate_name, сирот .part/.done, access-denied scandir, подкаталога в Output, .part при существующем назначении,
учётных данных во всех вызовах очистки; докстринг SmbHotFolder.put больше не противоречит сам себе (T12.5)
- F-E: тест ошибки очистки проверяет вызов очистки и ровно одну строку WARNING
- T9.4: докстринг протокола Normalizer; T13.4: устаревшие ссылки на «Task 14»; мёртвый noqa BLE001 в worker.py
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
- T2.1: после чтения набора каталог ответа листается заново (Local и SMB); если снимок (имена и
размеры) отличается от настоявшегося — None, набор настаивается заново.
- T2.2: SmbHotFolder — ENOTDIR из листинга Output/<job_id> (на месте каталога файл) — None,
как у LocalHotFolder.
- T2.4: тест SMB — ENOENT / SHARING_VIOLATION / ENOTDIR во время настаивания сбрасывают
накопленное состояние settle (pending_jobs() == 0).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- HotFolder.poll(job_id) -> list[AnswerFile] | None: все *.json каталога <output>/<job_id>/ по возрастанию
имени, когда набор не пуст и имена/размеры не менялись SCAN_SETTLE_SECONDS (SettleTracker: состояние по
job_id, внедряемые часы, сброс при выдаче/опустении, предел 1024 задания, потокобезопасно);
- put: временный .<name>.part в <корень>/<SCAN_TMP_DIR>/ (создаётся), rename в Input (Local и SMB);
- настройки SCAN_TMP_DIR (Tmp) и SCAN_SETTLE_SECONDS (2, >= 0); в .env.test settle = 0 (R-23);
- воркер: переходная прослойка — берётся первый файл набора (склейка всех — Task 3, R-22).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
T8.1: inbox_name заменяет `: < > " | ? *` и управляющие символы на `_`, убирает завершающие точки/пробелы;
ValueError только если имени не осталось. validate_name/put остаются строгими.
T8.2: SmbHotFolder.poll возвращает None на STATUS_SHARING_VIOLATION/STATUS_DELETE_PENDING (ИС ещё пишет ответ),
монопольное открытие сохранено.
T8.3: при ошибке mkdir, кроме EEXIST/ENOENT, put продолжает, если smbclient.path.isdir(Input); иначе исходная ошибка.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
SmbHotFolder: UNC-корень \\host\SMB_PATH (или полный UNC в SMB_PATH), put — `.<name>.part` + smbclient.replace,
poll — open_file(rb), ENOENT (в т.ч. нет каталога) -> None, healthy — listdir корня без исключений.
create_hotfolder(settings): local -> LocalHotFolder, smb -> SmbHotFolder с ленивым импортом модуля (R-6);
отсутствие импорта src.gateway.smb в режиме local проверяется в отдельном интерпретаторе.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>