Andrey Epifancev 561eaa5c71 docs: соглашения шаблона в CLAUDE.md и OpenSpec change slice-0-skeleton
Срез 0 (issue #1): раздел «Соглашения шаблона» (что оставлено от шаблона команды, что
вырезается, что добавляется); change openspec/changes/slice-0-skeleton — proposal, design
с таблицей «Decisions made without discussion», спеки и tasks; в openspec/config.yaml
строки с двоеточием взяты в кавычки (YAML разбирал их как словари).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 02:43:41 +04:00

7.4 KiB
Raw Blame History

Spec Delta: image-recognition-gateway

Purpose

Адаптер внешней ИС Сканирование (Content AI) для PDF/PNG/JPG: принимает задание из NATS, передаёт файл через горячие папки, ждёт ответ не дольше таймаута, сохраняет сырой ответ в S3 и отвечает ядру по контракту v1.0. Без БД и фронта.

ADDED Requirements

Requirement: Сервис не зависит от БД и фронта

Репозиторий MUST NOT содержать web/, alembic/, src/db/, src/models/, src/repositories/; зависимости SQLAlchemy, asyncpg, alembic, raisa_fastapi_protected_api MUST NOT присутствовать; smbprotocol SHALL остаться. Для старта SHALL хватать OPENBAO__MINIO__*, OPENBAO__NATS__NATS_URL, SCAN_HOTFOLDER_MODE=local, SCAN_HOTFOLDER_PATH.

Scenario: Старт в режиме локальных папок

  • WHEN сервис запущен с SCAN_HOTFOLDER_MODE=local и существующим каталогом SCAN_HOTFOLDER_PATH
  • THEN GET /healthcheck → 200 с полями uptime, version, nats, hotfolder = ok; OPENBAO__SMB__* не требуются

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

Для запроса recognition.image.request, проходящего схему, сервис SHALL прочитать исходник из S3 (s3.bucket/s3.key) и записать его во входящий каталог как <job_id>_<original_name>; затем SHALL опрашивать исходящий каталог <SCAN_OUTPUT_DIR>/<job_id>/<job_id>.json каждые SCAN_POLL_SECONDS до min(reply_deadline, старт + SCAN_TIMEOUT_SECONDS).

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

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

Scenario: Ответ получен

  • WHEN в Output/3f1c…/3f1c….json появляется ответ ИС до истечения таймаута
  • THEN опрос прекращается не позднее чем через SCAN_POLL_SECONDS, ответ передаётся дальше по конвейеру

Requirement: Сырой ответ сохраняется в S3 до нормализации

Полученный ответ ИС SHALL быть записан как есть в raw/{job_id}.json того же бакета до вызова нормализатора; ключ SHALL передаваться в конверте результата полем raw_s3_key; в БД объект не регистрируется (ядро его не создаёт).

Scenario: Нормализатор упал

  • WHEN ответ ИС получен, а нормализатор бросил исключение
  • THEN объект raw/{job_id}.json существует в S3, конверт содержит raw_s3_key и error.code = UNKNOWN_ERROR

Requirement: Результат по контракту и подтверждение

Сервис SHALL публиковать в recognition.result конверт по recognition-result-message.schema.json с service.name = "scan-gateway", Nats-Msg-Id = result:<job_id>:<attempt>; при status ≠ error — файл результата в results/{file_id}/{job_id}/result.json и result_s3; запрос SHALL подтверждаться только после публикации конверта. В Срезе 0 нормализатор SHALL возвращать: при error ≠ null в ответе ИС — status = error с кодом из ответа; иначе — error.code = NOT_IMPLEMENTED.

Scenario: Успешный ответ ИС в Срезе 0

  • WHEN ответ ИС содержит data_ocr без error
  • THEN конверт status = error, error.code = NOT_IMPLEMENTED, raw_s3_key = raw/<job_id>.json, запрос подтверждён

Scenario: Ошибка ИС

  • WHEN ответ ИС содержит error.code = INVALID_DOCUMENT
  • THEN конверт status = error, error.code = INVALID_DOCUMENT, error.message из ответа, raw_s3_key заполнен

Requirement: Таймаут ожидания ответа

Если ответ не появился до дедлайна, сервис SHALL опубликовать конверт status = error, error.code = TIMEOUT и подтвердить запрос; повторная отправка файла в ИС MUST NOT выполняться (consumer max_deliver = 1).

Scenario: ИС молчит

  • WHEN запрос с reply_deadline через 10 с, ответ не появляется
  • THEN через ≤ 15 с опубликован конверт TIMEOUT, входящий файл остаётся в каталоге, запрос подтверждён

Requirement: Ошибки транспорта превращаются в результат

Сервис SHALL отвечать конвертом status = error с кодом по таблице: запрос не по схеме или MAJOR ≠ 1 → SCHEMA_MISMATCH; объект S3 не читается → FILE_READ_ERROR; запись во входящий каталог не удалась → RECOGNITION_FAILED; ответ ИС не разбирается как JSON → INVALID_FORMAT; иное исключение → UNKNOWN_ERROR. Во всех случаях конверт SHALL быть опубликован, запрос подтверждён, процесс MUST NOT завершаться.

Scenario: Горячая папка недоступна

  • WHEN каталог SCAN_HOTFOLDER_PATH не существует в момент обработки
  • THEN конверт RECOGNITION_FAILED с сообщением о недоступности горячей папки; /healthcheck.hotfolder = down

Requirement: Параллельная обработка ограничена

Сервис SHALL обрабатывать не более SCAN_MAX_INFLIGHT (по умолчанию 4) заданий одновременно; ack_wait consumer'а SHALL превышать SCAN_TIMEOUT_SECONDS не менее чем на 120 с.

Scenario: Пять заданий подряд

  • WHEN в очереди пять запросов при SCAN_MAX_INFLIGHT = 4
  • THEN четыре файла появляются во входящем каталоге сразу, пятый — после завершения любого из четырёх

Requirement: Копия контракта идентична источнику

api/contracts/*.schema.json SHALL быть идентичны ../docs/contracts/; pydantic-модели SHALL валидировать примеры docs.

Scenario: CI сервиса

  • WHEN выполняются тесты контракта при доступном ../docs
  • THEN идентичность подтверждена, примеры валидны