arm-scan-gateway/docs/superpowers/plans/2026-10-03-slice-0-skeleton.md
Andrey Epifancev 58bcae2a57 Срез 0 [6.3]: прогон DoD, предложения для docs и правки .env.example
- open-questions-proposal.md: compose-фрагмент scan-emulator/scan-gateway, таблица предложений по контракту и OQ,
  чеклист для человека (файлы в ../docs не менялись)
- .env.example: SCAN_EMULATOR_POLL_SECONDS; уточнён комментарий PUBLISH_RETRIES (повторы после первой попытки)
- отмечена задача 16 плана и пункт 6.3 tasks.md

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 04:44:33 +04:00

10 KiB
Raw Blame History

Срез 0 — скелет scan-gateway — план исполнения (Срез 0)

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Branch: feat/1-slice-0-skeleton (worktree .wt/slice-0/scan-gateway). Issue: #1. Spec: openspec/changes/slice-0-skeleton/ — proposal.md, design.md, specs/*/spec.md этого change'а. Спека — единственный источник истины при рулингах; этот план сгенерирован из её tasks.md 1:1 (номера в скобках — исходные номера задач tasks.md; по завершении задачи отметить её - [x] и в tasks.md).

Global Constraints

  • Сервис без БД и фронта (DECISION-4), SMB остаётся. Раскладка шаблона, команды из api/.
  • Платформа: Python 3.11, NATS без JetStream (queue group scan-gateway, ack processing до чтения S3, LRU job_id, публикация с повторами), один бакет S3, Nexus недоступен, CI нет (make check / make check-full).
  • Горячие папки: протокол HotFolder, режимы local / smb, каталоги SCAN_INPUT_DIR/SCAN_OUTPUT_DIR (по умолчанию Input/Output), файл <job_id>_<original_name>, ответ <output>/<job_id>/<job_id>.json; таймаут min(reply_deadline, старт + SCAN_TIMEOUT_SECONDS=300), опрос 5 с, SCAN_MAX_INFLIGHT=4; повторной отправки в ИС нет (OQ-15).
  • Сырой ответ → S3 raw/{job_id}.json до нормализации, raw_s3_key в конверте; в БД не регистрируется. NullNormalizer: ошибка ИС → её код, успех → NOT_IMPLEMENTED.
  • Эмулятор: python -m src.scan_emulator, фикстуры ../fixtures/ocr/<name>*.json, подстановка file_uid, неизвестное имя → INVALID_DOCUMENT, .done после обработки.
  • Контракт: копии схем из ../docs/contracts/ байт в байт; service.name = "scan-gateway"; Nats-Msg-Id по правилам контракта.
  • Линтеры: ruff (120, E,F,I,UP,B,PL) + mypy; pytest-asyncio auto; маркеры integration/fixtures.
  • Язык комментариев/коммитов — русский, идентификаторы — английские; коммит заканчивается строкой Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>.

Задачи

Task 1: [1.1] Удалить web/, setup/.npmrc, npm-шаги setup/setup.sh, api/alembic/, `api/

  • Удалить web/, setup/.npmrc, npm-шаги setup/setup.sh, api/alembic/, api/alembic.ini, api/src/db/, api/src/models/, api/src/repositories/; убрать Dockerfile из .gitignore; проверка: git status — только удаления и .gitignore

Task 2: [1.2] src/app.py

  • src/app.py: убрать mount SPA/os.makedirs("web"), CORS, импорт raisa_fastapi_protected_api; оставить SMB-сессию (только при SCAN_HOTFOLDER_MODE=smb); src/core/config.py: убрать DBSettings; добавить ENV_FILE, SERVICE_NAME="scan-gateway", NATS_ENABLED=true, PUBLISH_RETRIES=3, DONE_CACHE_SIZE=1000, SCAN_HOTFOLDER_MODE="local", SCAN_HOTFOLDER_PATH, SCAN_INPUT_DIR="Input", SCAN_OUTPUT_DIR="Output", SCAN_POLL_SECONDS=5, SCAN_TIMEOUT_SECONDS=300, SCAN_MAX_INFLIGHT=4; requirements.txt без asyncpg, SQLAlchemy, alembic, argon2-cffi, raisa_fastapi_protected_api; проверка: чистый venv (python3.11), python run_server.py с .env.example, /healthcheck 200

Task 3: [1.3] README.md — описание сервиса (назначение, протокол горячих папок, env, запуск

  • README.md — описание сервиса (назначение, протокол горячих папок, env, запуск против compose и эмулятора), CLAUDE.md — актуализировать «Соглашения шаблона» по факту; проверка: упомянутые файлы существуют

Task 4: [2.1] api/pyproject.toml (ruff, mypy), requirements-dev.txt = `-r requirements.txt

  • api/pyproject.toml (ruff, mypy), requirements-dev.txt = -r requirements.txt + ruff, mypy, pytest, pytest-asyncio, pytest-env, httpx, jsonschema; Makefile: venv (python3.11, без raisa_*), lint, test, test-integration, check, check-full, up, emulator; проверка: make venv && make lint зелёные

Task 5: [2.2] Dockerfile (`python

  • Dockerfile (python:3.11-slim; CMD python run_server.py, эмулятор — через command в compose), .env.example, .env.test; проверка: docker build успешен без Nexus

Task 6: [3.1] api/contracts/ — копии схем и examples/ из ../docs/contracts

  • api/contracts/ — копии схем и examples/ из ../docs/contracts; src/contract/ — pydantic-модели (конверт со статусами processing | done | needs_check | error); tests/unit/test_contract.py — идентичность (skip без docs), примеры, round-trip; проверка: тесты зелёные

Task 7: [4.1] src/gateway/hotfolder.py

  • src/gateway/hotfolder.py: протокол HotFolder, LocalHotFolder (put/poll/healthy); tests/unit/test_local_hotfolder.py на tmp_path; проверка: put пишет <uuid>_<name>, poll возвращает None до появления ответа и байты после

Task 8: [4.2] src/gateway/smb.py

  • src/gateway/smb.py: SmbHotFolder поверх smbclient (UNC \\host\path\Input), те же методы; unit-тест с подменённым smbclient (вызовы open_file, listdir); проверка: тест зелёный, в режиме local модуль не импортируется

Task 9: [4.3] src/scan_emulator/__init__.py + __main__.py

  • src/scan_emulator/__init__.py + __main__.py: функция process_once(root, fixtures, delay) и цикл; поиск фикстуры по имени/префиксу, подстановка file_uid, ошибка INVALID_DOCUMENT, переименование в .done; tests/unit/test_emulator.py — сценарии spec «Точное имя», «Префикс», «Нет пары» на tmp_path с копиями ../fixtures/ocr/*.json (маркер fixtures, skip без фикстур); проверка: зелёный

Task 10: [5.1] src/normalizer/__init__.py

  • src/normalizer/__init__.py: протокол Normalizer, NormalizeOutcome, NullNormalizer (ошибка ИС → код из ответа; успех → NOT_IMPLEMENTED; не JSON → INVALID_FORMAT); unit-тесты на aep_5.json, aep_8_form4.json, синтетическом ответе с error; проверка: зелёный

Task 11: [5.2] src/core/clients.py (get_bytes, put_json, put_bytes), src/core/nats.py

  • src/core/clients.py (get_bytes, put_json, put_bytes), src/core/nats.py (подключение с/без nkeys, автопереподключение, subscribe_requests(queue="scan-gateway", cb), publish с Nats-Msg-Id и повторами); проверка: unit с фейками, интеграционный тест подключения к compose-NATS без JetStream

Task 12: [5.3] src/core/worker.py

  • src/core/worker.py: on_request(msg): валидация → LRU job_id → ack processing → очередь; семафор SCAN_MAX_INFLIGHT; handle(job): S3 get → hotfolder.put → опрос до дедлайна → raw/{job_id}.json → Normalizer → результат/конверт → publish; таблица ошибок D-7; /healthcheck.nats, .hotfolder; проверка: unit-тесты handle с фейками и LocalHotFolder + process_once эмулятора — сценарии spec (ack раньше файла, повторный запрос → один файл, успех → NOT_IMPLEMENTED с raw_s3_key, INVALID_DOCUMENT, TIMEOUT с укороченными таймаутами, RECOGNITION_FAILED при отсутствии каталога, SCHEMA_MISMATCH без ack, FILE_READ_ERROR)

Task 13: [5.4] Тест параллелизма

  • Тест параллелизма: пять заданий при SCAN_MAX_INFLIGHT = 4 — пять ack сразу, четыре файла во входящем каталоге, пятый после освобождения слота (фейковые S3/NATS, LocalHotFolder); проверка: зелёный

Task 14: [6.1] tests/integration/test_roundtrip.py (маркер integration)

  • tests/integration/test_roundtrip.py (маркер integration): compose с scan-emulator — положить aep_5.pdf в MinIO uploads/…, опубликовать request-image.json, собрать recognition.result: processing, затем NOT_IMPLEMENTED с raw_s3_key; проверить объект raw/<job_id>.json в MinIO; второй запрос с unknown.pdf → INVALID_DOCUMENT; проверка: зелёный

Task 15: [6.2] make check-full

  • make check-full: make check + pytest -m integration + docker build; проверка: зелёный против compose docs на ветке feat/1-slice-0-skeleton

Task 16: [6.3] Прогон DoD

  • Прогон DoD: make up → make check-full → ручная проверка из docs/dev/README.md; openspec validate slice-0-skeleton --strict; вывод make check-full — в описание PR