Andrey Epifancev 94ab9e90d1 openspec: архив hotfolder-direct-write (#26)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 17:23:21 +04:00

7.6 KiB
Raw Blame History

MODIFIED Requirements

Requirement: Передача файла в ИС Сканирование через горячие папки

После подтверждения сервис SHALL прочитать исходник из S3 (s3.bucket/s3.key) и записать его во входящий каталог как <job_id>.<ext>, где <ext> — расширение original_name запроса в нижнем регистре; запрос без расширения SHALL завершаться конвертом RECOGNITION_FAILED без записи в горячую папку. Файл SHALL записываться сразу во входящий каталог открытием на запись с усечением, как в старом АРМ (решение владельца 09.10.2026): временных файлов, служебного каталога и переименования нет; если <Input>/<job_id>.<ext> уже существует, он SHALL быть перезаписан содержимым объекта S3. Затем сервис SHALL опрашивать исходящий каталог <SCAN_OUTPUT_DIR>/<job_id>/ каждые SCAN_POLL_SECONDS до min(reply_deadline, старт + SCAN_TIMEOUT_SECONDS) (SCAN_TIMEOUT_SECONDS по умолчанию 1200). ИС кладёт в этот каталог все распознанные документы задания — по одному *.json на документ, набор появляется целиком и больше не меняется; пустой или отсутствующий каталог означает ожидание. Ответ SHALL считаться готовым, когда в каталоге есть хотя бы один *.json, а набор имён и размеров *.json не менялся SCAN_SETTLE_SECONDS (по умолчанию 2); готовый набор SHALL включать все *.json каталога по возрастанию имени, остальные файлы игнорируются.

Scenario: Файл положен во входящий каталог

  • WHEN получен запрос для aep_5.PDF с job_id = 3f1c…
  • THEN во входящем каталоге появляется 3f1c….pdf, байт в байт равный объекту S3; каталог Tmp/ не создаётся, временных файлов нет

Scenario: Назначение уже существует

  • WHEN запрос для aep_5.pdf с job_id = 3f1c… обрабатывается, а Input/3f1c….pdf уже лежит с другим содержимым (задание повторено ядром после потери ack в другом процессе)
  • THEN Input/3f1c….pdf перезаписан и байт в байт равен объекту S3, задание продолжается опросом Output/3f1c…/, конверт RECOGNITION_FAILED не публикуется

Scenario: Запрос без расширения

  • WHEN получен запрос с original_name = "scan" без точки
  • THEN опубликован конверт error, error.code = RECOGNITION_FAILED, сообщение о расширении; во входящем каталоге файла нет

Scenario: Ответ из одного файла

  • WHEN в Output/3f1c…/ появляется один *.json и набор не меняется SCAN_SETTLE_SECONDS
  • THEN опрос прекращается не позднее чем через SCAN_SETTLE_SECONDS + SCAN_POLL_SECONDS после первого опроса, увидевшего окончательный набор (отсчёт стабилизации — от этого опроса, а не от времени изменения файла), ответ из одного файла передаётся дальше по конвейеру

Scenario: Ответ из нескольких файлов

  • WHEN в Output/3f1c…/ лежат 3f1c….json и 3f1c…_ofr.json, посторонний файл readme.txt, и набор не меняется SCAN_SETTLE_SECONDS
  • THEN дальше по конвейеру передаются оба *.json по возрастанию имени, readme.txt игнорируется

Scenario: Набор ещё пишется

  • WHEN размер файла или число файлов в Output/3f1c…/ менялось менее SCAN_SETTLE_SECONDS назад
  • THEN ответ не считается готовым, опрос продолжается до дедлайна

Scenario: Ошибка посреди записи

  • WHEN запись Input/3f1c….pdf прервалась ошибкой ввода-вывода
  • THEN опубликован конверт RECOGNITION_FAILED; недописанный файл удалён только при SCAN_CLEANUP=true, при false остаётся на месте

Requirement: Очистка горячей папки под флагом

При SCAN_CLEANUP=true после того, как готовый набор ответа прочитан и склеен в raw/{job_id}.json (независимо от исхода нормализации), сервис SHALL удалить всё содержимое <SCAN_OUTPUT_DIR>/<job_id>/ (json-файлы ответа и любые прочие) и сам каталог <job_id>, входящий файл <Input>/<job_id>.<ext> и его вариант <Input>/<job_id>.<ext>.done, если они существуют. При SCAN_CLEANUP=false (по умолчанию) сервис MUST NOT удалять ничего, включая недописанный при ошибке записи входящий файл. При TIMEOUT, FILE_READ_ERROR, RECOGNITION_FAILED записи очистка MUST NOT выполняться (при ошибке записи и SCAN_CLEANUP=true снимается только недописанный входящий файл). Ошибка удаления SHALL быть записана в лог одной строкой на задание и MUST NOT менять конверт, metrics.warnings и /healthcheck.

Scenario: Очистка включена

  • WHEN SCAN_CLEANUP=true, ответ на 3f1c….pdf прочитан из Output/3f1c…/
  • THEN после публикации конверта в горячей папке нет Output/3f1c…/, Input/3f1c….pdf и Input/3f1c….pdf.done

Scenario: Очистка выключена

  • WHEN SCAN_CLEANUP=false, ответ прочитан
  • THEN все файлы остаются на месте, сервис ничего не удаляет и не пишет в лог о очистке (решение №27)

Scenario: Нет прав на удаление

  • WHEN SCAN_CLEANUP=true, удаление Output/3f1c…/3f1c….json завершается PermissionError
  • THEN конверт опубликован с тем же статусом, что и без очистки; в логе одна строка об ошибке удаления; /healthcheck.hotfolder = ok

Scenario: Таймаут не чистит

  • WHEN SCAN_CLEANUP=true, ответ не появился до дедлайна
  • THEN опубликован TIMEOUT, входящий файл и каталог Output/<job_id>/ (если есть) не тронуты