Срез 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>
68 lines
6.9 KiB
Markdown
68 lines
6.9 KiB
Markdown
# Tasks: Срез 0 — скелет scan-gateway
|
||
|
||
Репозиторий — scan-gateway; команды — из `api/`. Зависимости: 3.1 ждёт контракт v1.0 (`docs` 1.1–1.4); интеграционные
|
||
тесты 6.x — compose (`docs` 2.1, 5.3). Каждая задача ≤ половины дня.
|
||
|
||
## 1. Вырезать фронт и БД из шаблона (SMB остаётся)
|
||
|
||
- [ ] 1.1 Удалить `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`
|
||
- [ ] 1.2 `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`, `NATS_MAX_DELIVER=1`, `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, `python run_server.py` с `.env.example`, `/healthcheck` 200
|
||
- [ ] 1.3 `README.md` — описание сервиса (назначение, протокол горячих папок, env, запуск против compose и эмулятора),
|
||
`CLAUDE.md` — актуализировать «Соглашения шаблона» по факту; проверка: упомянутые файлы существуют
|
||
|
||
## 2. Инструменты
|
||
|
||
- [ ] 2.1 `api/pyproject.toml` (ruff, mypy), `requirements-dev.txt` = `-r requirements.txt` + ruff, mypy, pytest,
|
||
pytest-asyncio, pytest-env, httpx, jsonschema; проверка: `ruff check . && ruff format --check . && mypy src` зелёные
|
||
- [ ] 2.2 `Dockerfile` (python:3.12-slim; `CMD python run_server.py`, эмулятор — через `command` в compose), `Makefile`
|
||
(`up`, `test`, `test-integration`, `lint`, `emulator`), `.env.example`, `.env.test`; проверка: `docker build` успешен
|
||
|
||
## 3. Контракт
|
||
|
||
- [ ] 3.1 `api/contracts/` — копии схем и `examples/` из `../docs/contracts`; `src/contract/` — pydantic-модели;
|
||
`tests/unit/test_contract.py` — идентичность (skip без docs), примеры, round-trip; проверка: тесты зелёные
|
||
|
||
## 4. Горячие папки и эмулятор
|
||
|
||
- [ ] 4.1 `src/gateway/hotfolder.py`: протокол `HotFolder`, `LocalHotFolder` (put/poll/healthy, создание `Output/<uuid>/`
|
||
не требуется — читаем, если есть); `tests/unit/test_local_hotfolder.py` на `tmp_path`; проверка: put пишет
|
||
`<uuid>_<name>`, poll возвращает `None` до появления ответа и байты после
|
||
- [ ] 4.2 `src/gateway/smb.py`: `SmbHotFolder` поверх `smbclient` (UNC `\\host\path\Input`), те же методы; unit-тест с
|
||
подменённым `smbclient` (вызовы `open_file`, `listdir`); проверка: тест зелёный, в режиме `local` модуль не импортируется
|
||
- [ ] 4.3 `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 без фикстур); проверка: зелёный
|
||
|
||
## 5. Воркер и нормализатор-заглушка
|
||
|
||
- [ ] 5.1 `src/normalizer/__init__.py`: протокол `Normalizer`, `NormalizeOutcome`, `NullNormalizer` (ошибка ИС → код из
|
||
ответа; успех → `NOT_IMPLEMENTED`; не JSON → `INVALID_FORMAT`); unit-тесты на `aep_5.json`, `aep_8_form4.json`,
|
||
синтетическом ответе с `error`; проверка: зелёный
|
||
- [ ] 5.2 `src/core/clients.py` (`get_bytes`, `put_json`, `put_bytes`), `src/core/nats.py` (подключение с/без nkeys,
|
||
подписка с повтором, publish с `Nats-Msg-Id`, `ack_wait = SCAN_TIMEOUT_SECONDS + 120`, `max_deliver = 1`);
|
||
проверка: unit с фейками, интеграционный тест подключения к compose-NATS
|
||
- [ ] 5.3 `src/core/worker.py`: семафор `SCAN_MAX_INFLIGHT`, `fetch` на свободные слоты, `handle(msg)`: валидация → S3 get →
|
||
`hotfolder.put` → опрос до дедлайна → `raw/{job_id}.json` → `Normalizer` → результат/конверт → publish → ack;
|
||
таблица ошибок D-7; `/healthcheck.nats`, `.hotfolder`; проверка: unit-тесты `handle` с фейками и `LocalHotFolder` +
|
||
`process_once` эмулятора — сценарии spec (успех → NOT_IMPLEMENTED с `raw_s3_key`, INVALID_DOCUMENT, TIMEOUT с
|
||
укороченными таймаутами, RECOGNITION_FAILED при отсутствии каталога, SCHEMA_MISMATCH, FILE_READ_ERROR)
|
||
- [ ] 5.4 Тест параллелизма: пять заданий при `SCAN_MAX_INFLIGHT = 4` — четыре файла во входящем каталоге сразу, пятый после
|
||
освобождения слота (фейковые S3/NATS, `LocalHotFolder`); проверка: зелёный
|
||
|
||
## 6. Интеграция, CI, завершение
|
||
|
||
- [ ] 6.1 `tests/integration/test_roundtrip.py` (маркер `integration`): compose с `scan-emulator` — положить `aep_5.pdf` в
|
||
MinIO `uploads/…`, опубликовать `request-image.json`, дождаться конверта `NOT_IMPLEMENTED` с `raw_s3_key`, проверить
|
||
объект `raw/<job_id>.json` в MinIO и ack запроса; второй запрос с `unknown.pdf` → `INVALID_DOCUMENT`; проверка: зелёный
|
||
- [ ] 6.2 `.gitea/workflows/ci.yml` из `../docs/dev/ci/python-service.yml` (блок `env`: scan-gateway, порт 8002); проверка:
|
||
зелёный run на ветке `feat/1-slice-0-skeleton`
|
||
- [ ] 6.3 Прогон DoD: `make up` → `make test` → `make test-integration` → ручная проверка из `docs/dev/README.md`;
|
||
`openspec validate slice-0-skeleton --strict`; результаты — в описание PR
|