From 58bcae2a57f100246bdcb6daac11dd2a04257a50 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Sun, 4 Oct 2026 04:44:26 +0400 Subject: [PATCH] =?UTF-8?q?=D0=A1=D1=80=D0=B5=D0=B7=200=20[6.3]:=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D0=B3=D0=BE=D0=BD=20DoD,=20=D0=BF=D1=80=D0=B5?= =?UTF-8?q?=D0=B4=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20docs=20=D0=B8=20=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8?= =?UTF-8?q?=20.env.example?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- api/.env.example | 5 +- .../plans/2026-10-03-slice-0-skeleton.md | 2 +- .../open-questions-proposal.md | 146 ++++++++++++++++++ openspec/changes/slice-0-skeleton/tasks.md | 2 +- 4 files changed, 152 insertions(+), 3 deletions(-) create mode 100644 openspec/changes/slice-0-skeleton/open-questions-proposal.md diff --git a/api/.env.example b/api/.env.example index e8dd0f1..9130ef6 100644 --- a/api/.env.example +++ b/api/.env.example @@ -22,7 +22,8 @@ OPENBAO__NATS__NATS_JWT= # Включает подписку на recognition.image.request. false — воркер не запускается (сервис поднимется без NATS). NATS_ENABLED=true -# Попытки публикации ack/результата в NATS и размер LRU обработанных job_id (защита от повторной доставки запроса) +# PUBLISH_RETRIES — число повторов публикации ack/результата в NATS после первой попытки (при 3 — до 4 попыток всего); +# DONE_CACHE_SIZE — размер LRU обработанных job_id (защита от повторной доставки запроса) PUBLISH_RETRIES=3 DONE_CACHE_SIZE=1000 @@ -55,3 +56,5 @@ SCAN_MAX_INFLIGHT=4 SCAN_FIXTURES_DIR=../../fixtures/ocr # Задержка перед выдачей ответа, секунды (имитация работы ИС) SCAN_EMULATOR_DELAY=2 +# Период опроса входящего каталога эмулятором, секунды (> 0) +SCAN_EMULATOR_POLL_SECONDS=1 diff --git a/docs/superpowers/plans/2026-10-03-slice-0-skeleton.md b/docs/superpowers/plans/2026-10-03-slice-0-skeleton.md index 668f982..c8f0ff0 100644 --- a/docs/superpowers/plans/2026-10-03-slice-0-skeleton.md +++ b/docs/superpowers/plans/2026-10-03-slice-0-skeleton.md @@ -80,5 +80,5 @@ ### 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 +- [x] Прогон DoD: `make up` → `make check-full` → ручная проверка из `docs/dev/README.md`; `openspec validate slice-0-skeleton --strict`; вывод `make check-full` — в описание PR diff --git a/openspec/changes/slice-0-skeleton/open-questions-proposal.md b/openspec/changes/slice-0-skeleton/open-questions-proposal.md new file mode 100644 index 0000000..ce9059e --- /dev/null +++ b/openspec/changes/slice-0-skeleton/open-questions-proposal.md @@ -0,0 +1,146 @@ +# Предложения по итогам Среза 0 scan-gateway: compose, контракт, открытые вопросы + +Передача для владельца `docs` и человека. Файлы в `../docs` этим change'ом **не менялись**; всё ниже — предложения. +Источник: прогон DoD (задача 6.3), `progress.md` контроллера, ревью задач 1-15. Маркеры (`T12.5`, `O-1`, `R-13`) — из ledger. + +## Фрагмент compose для scan-emulator и scan-gateway + +Вставить в `../docs/dev/docker-compose.yml` в раздел `services:` (после `minio-init`) и добавить в раздел `volumes:` том +`hotfolder:`. Профиль `services`: `docker compose --profile services up -d --wait scan-gateway scan-emulator` (в +`up -d --wait` указывать сервисы явно, как для инфраструктуры). Образ собирается из корня репозитория `scan-gateway` +(`Dockerfile`), эмулятор использует тот же образ (команда `python -m src.scan_emulator`). Эмулятору переменные +MinIO/NATS не нужны (R-11). Контекст `../../scan-gateway` верен и для workspace, и для worktree `.wt/<срез>/docs/dev`. + +```yaml + # Эмулятор ИС Сканирование: тот же образ, что у шлюза; общий том hotfolder с шлюзом. Переменные MinIO/NATS не нужны. + scan-emulator: + profiles: ["services"] + build: + context: ../../scan-gateway + image: scan-gateway:dev + command: ["python", "-m", "src.scan_emulator"] + restart: unless-stopped + environment: + SCAN_HOTFOLDER_PATH: /data/hotfolder + SCAN_FIXTURES_DIR: /fixtures/ocr + SCAN_EMULATOR_DELAY: "2" + volumes: + - hotfolder:/data/hotfolder + - ../../fixtures/ocr:/fixtures/ocr:ro + + scan-gateway: + profiles: ["services"] + build: + context: ../../scan-gateway + image: scan-gateway:dev + restart: unless-stopped + depends_on: + nats: + condition: service_healthy + minio: + condition: service_healthy + minio-init: + condition: service_completed_successfully + environment: + OPENBAO__MINIO__MINIO_URL: minio:9000 + OPENBAO__MINIO__MINIO_ADMIN_LOGIN: ${MINIO_ROOT_USER:-minioadmin} + OPENBAO__MINIO__MINIO_ADMIN_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin} + OPENBAO__MINIO__MINIO_BUCKET: ${MINIO_BUCKET:-arm-ka} + OPENBAO__MINIO__MINIO_SECURE: "false" + OPENBAO__NATS__NATS_URL: nats://nats:4222 + NATS_ENABLED: "true" + SCAN_HOTFOLDER_MODE: local + SCAN_HOTFOLDER_PATH: /data/hotfolder + volumes: + - hotfolder:/data/hotfolder + ports: + - "${BIND_ADDR:-127.0.0.1}:${SCAN_GATEWAY_PORT:-8002}:8000" + healthcheck: + # curl в python:3.11-slim нет; /healthcheck всегда отвечает 200, поэтому читаем поля nats и hotfolder + test: + - CMD + - python + - -c + - "import json,sys,urllib.request as u; d=json.load(u.urlopen('http://127.0.0.1:8000/healthcheck', timeout=3)); sys.exit(0 if d['nats']=='ok' and d['hotfolder']=='ok' else 1)" + interval: 5s + timeout: 5s + retries: 12 + start_period: 10s +``` + +И в конец файла: + +```yaml +volumes: + postgres-data: + minio-data: + hotfolder: +``` + +Проверено: YAML разбирается (`yaml.safe_load`); `docker compose -f <копия> --project-directory docs/dev --profile services +config -q` на временной копии файла с этими сервисами — код 0 (реальный проект `arm-ka` не затрагивался). Образ +`scan-gateway:dev` запущен отдельным контейнером в сети `arm-ka_default` с теми же переменными окружения: +`/healthcheck` -> `nats: ok`, `hotfolder: ok`, команда healthcheck из фрагмента возвращает 0 (контейнер удалён). + +Что искать в логах: у эмулятора при старте — `Эмулятор ИС Сканирование: корень /data/hotfolder, входящий каталог +/data/hotfolder/Input, исходящий каталог /data/hotfolder/Output, задержка 2.0 с, опрос 1.0 с`, затем +`Фикстуры: /fixtures/ocr, фикстур: 2` (при `0`/«не найден» эмулятор отвечает `INVALID_DOCUMENT` на всё). У шлюза — +`Подписка NATS активна. subject=recognition.image.request, queue=scan-gateway` и `Воркер scan-gateway запущен`. +Имена переменных сверены с `api/src/core/config.py` и `api/src/scan_emulator/__init__.py`. + +Примечания: (а) порт хоста 8002 — по spec docs, у сервиса порт по умолчанию 8000 (`SERVER__PORT`), поэтому маппинг +`8002:8000`; (б) `/healthcheck` всегда отвечает 200 (пункт 9 таблицы), поэтому healthcheck читает поля `nats`/`hotfolder`; +(в) контейнеры работают от root, права на том `hotfolder` не настраиваются; (г) `build` задан у обоих сервисов с одним +`image:` — compose соберёт образ один раз за счёт кэша. + +## Предложения по контракту, OQ и документам + +| # | Что | Откуда | Предложение | +|---|---|---|---| +| 1 | `PUBLISH_RETRIES` = число повторов после первой попытки (1+3 = 4 попытки) | R-13 (design #37), T11.1; `api/src/core/config.py` | Согласовать с tabular-parser (там может быть «3 попытки всего»); зафиксировать в `03-architecture.md` одной фразой | +| 2 | `03-architecture.md` («Схема `schema_version`»): «неизвестный MAJOR -> `SCHEMA_MISMATCH` и ack»; спека change'а и воркер — без ack | R-16 (design #41), T12.13 | Привести документ в соответствие (ack не публикуется, конверт `error` — если из тела извлекаются `job_id` и `file_id`) либо пересмотреть правило и воркер: ack на отвергнутый запрос защищает ядро от зацикливания повторов доставки, цена — лишнее сообщение и расхождение с R-16 | +| 3 | Реальные фикстуры `fixtures/ocr/aep_5.json`, `aep_8_form4.json` — ответы-ошибки ИС (`error.code = INVALID_DOCUMENT`, поля `data_ocr` пусты) | O-1, R-10 (design #32) | Для Среза 3 (OQ-13/14) нужны образцы **успешных** ответов Content AI; пока успех проверяется синтетическим ответом, `NullNormalizer` на реальных фикстурах пропускает ошибку ИС | +| 4 | §11 `05-domain-rules.md` описывает `data_ocr[]` (список), а в фикстурах `data_ocr` — объект с `label/organization/inn/units/date/table_structure` | O-1, T3.2 | Выяснить фактический формат на стенде ИС и поправить §11 или фикстуры; решение нужно до нормализатора Среза 3 | +| 5 | Шлюз читает ровно `//.json` (спека); старый код читал все файлы каталога `Output//` | T8.5 | Проверить на стенде ИС в Срезе 3: имя ответа, наличие дополнительных файлов, момент готовности | +| 6 | Временный файл `.<имя>.part` лежит в `Input` (атомарная запись `.part` + rename, R-5) — реальная ИС без маски может забрать недописанный файл | R-5, T7/T8 | Вариант: служебный подкаталог рядом (`Input/.tmp` или корень) + `rename` в `Input`; либо уточнить у владельцев ИС маску/поведение | +| 7 | Сценарий «NATS перезапущен -> подписка восстановлена за <= 20 с» не автоматизирован: общий NATS нельзя перезапускать в проверках | T11.7 | Ручная проверка на отдельном `nats-server` (поднять, подключить шлюз, убить, поднять снова) либо отдельный compose-профиль для тестов | +| 8 | Штатная остановка не публикует конверты для принятых (ack) заданий; D-2 допускает потерю при падении, при rolling deploy задание теряется до `reply_deadline` | T12.5, D-2 | Публиковать `UNKNOWN_ERROR` «сервис остановлен» в `stop()` для всех принятых заданий; Срез 3+ | +| 9 | `/healthcheck` всегда 200, даже если NATS постоянно недоступен (`nats: down`) | T12.10 | Платформе (readiness/liveness) читать поле `nats`/`hotfolder`, а не код ответа; либо отдельные `/ready` и `/live` | +| 10 | `../docs/dev/README.md` устарел относительно шлюза: «у scan-gateway Makefile пока нет», `nats = off` (шлюз показывает `disabled`), порт 8002 против 8000, шлюз требует `SCAN_HOTFOLDER_*` и не использует `OPENBAO__DATABASE__*` | Task 3, O-a | Обновить раздел «Запуск сервисов из venv» и «Интеграционные тесты»; добавить профиль `services` (фрагмент выше); `make up`/`make check-full`/`make emulator` в `scan-gateway` есть | +| 11 | Интеграционные тесты шлюза и arm-core делят общий NATS: in-process шлюз подписывается на queue group `scan-gateway` на реальном subject и может забрать запросы тестов arm-core, идущих параллельно (и наоборот) | T14.1, T11.8 | Сделать queue group и/или subject-префикс настраиваемыми для тестов (например `NATS_TEST_PREFIX`), либо отдельный NATS на тесты; пока — не гонять `make test-integration` двух сервисов одновременно | +| 12 | Пример `contracts/examples/request-image.json` имеет `reply_deadline` в прошлом (`2026-10-03T12:45:00Z`) | Task 3, O-e | Для ручного прогона подставлять будущий срок (иначе сразу `TIMEOUT`); в README контракта отметить, либо сделать пример с датой далеко в будущем | +| 13 | `DEBUG` в настройках шлюза не используется (оставлен шаблоном; `raisa_*` не импортируются) | Task 3 | Принято; убрать вместе с остатками шаблона или оставить для единообразия с arm-core | +| 14 | `ResourceWarning` aiohttp при загрузке > 1 МиБ (miniopy-async) скрыт фильтром в одном модуле тестов; при ручном прогоне со скриптом тоже виден `Unclosed client session` | T14.5 | Если сырые ответы ИС будут > 1 МиБ — пересмотреть фильтр/закрывать сессию клиента явно | +| 15 | Зависший `poll`/`put` по SMB (до 60 с) не прерывается дедлайном; нет `connection_timeout` у `smbclient` | T12.2, T8.4, design #46 | Задать `connection_timeout` при регистрации сессии и ограничить вызовы `wait_for` остатком до дедлайна; проверить на стенде | +| 16 | Расхождения и наблюдения контроллера при исполнении (`Observation` в `progress.md`) | `progress.md` | См. ниже | + +Строки `Observation` из `progress.md` (только пересказ, статусы — в ledger): + +- **O-1** — обе реальные фикстуры (`aep_5.json`, `aep_8_form4.json`) сами являются ответами-ошибками ИС + (`INVALID_DOCUMENT`, `data_ocr` — объект, не список); пути `NOT_IMPLEMENTED` для реальных фикстур недостижимы; успех + тестируется синтетическим ответом (R-10). Пункты 3 и 4 таблицы. +- **O-2** — исполнитель T11 вопреки инструкции поставил трейлер харнесса; история локальных коммитов переписана + (`filter-branch`), в remote ничего не уходило. Процессное, к docs не относится. +- **O-a..O-e** (по итогам Task 3) — `docs/dev/README.md` устарел (пункт 10); порт 8002 против 8000 (пункт 10 и + примечание к compose); `design.md` D-9 описывал compose-интеграцию эмулятора — исправлено до R-7 (`e228635`); + `check-full` без docker-сборки — закрыто задачей 15; `DEBUG` не используется (пункт 13); `reply_deadline` в примере + в прошлом (пункт 12). + +Дополнительно по итогам прогона DoD (реальные процессы шлюза и эмулятора, SIGTERM): + +- шлюз при SIGTERM корректно завершает lifespan (`Приложение закрывается`, `Соединение с NATS закрыто`), но код выхода + процесса — 143 (uvicorn повторно поднимает полученный сигнал); для оркестраторов это штатно, скрипты, ожидающие 0, + должны учитывать; +- кэш `hotfolder` в `/healthcheck` — 5 с: после возврата каталога поле остаётся `down` до 5 с. + +## Что осталось человеку + +- [ ] Просмотреть и влить PR ветки `feat/1-slice-0-skeleton` (merge — только по явной команде); вывод `make check-full` + — в описании PR (`.superpowers/sdd/2026-10-03-slice-0-skeleton/make-check-full.log`, локальный). +- [ ] После merge: `openspec archive slice-0-skeleton`, коммит и push архива в main (чеклист закрытия). +- [ ] Перенести compose-фрагмент выше в `../docs/dev/docker-compose.yml` (и обновить `README.md`, пункт 10) — + отдельным PR репозитория `docs`. +- [ ] Согласовать с владельцем tabular-parser семантику `PUBLISH_RETRIES` (пункт 1) и решить судьбу правила про + `SCHEMA_MISMATCH` + ack (пункт 2). +- [ ] Занести пункты 3-5 (образцы успешных ответов Content AI, формат `data_ocr`, имя файла ответа) в + `docs/context/07-open-questions.md`; пункты 6-8, 15 — в follow-up issue (`Follow-ups from #1`). diff --git a/openspec/changes/slice-0-skeleton/tasks.md b/openspec/changes/slice-0-skeleton/tasks.md index c87d80d..0108be1 100644 --- a/openspec/changes/slice-0-skeleton/tasks.md +++ b/openspec/changes/slice-0-skeleton/tasks.md @@ -67,5 +67,5 @@ `INVALID_DOCUMENT`; проверка: зелёный - [x] 6.2 `make check-full`: `make check` + `pytest -m integration` + `docker build`; проверка: зелёный против compose docs на ветке `feat/1-slice-0-skeleton` -- [ ] 6.3 Прогон DoD: `make up` → `make check-full` → ручная проверка из `docs/dev/README.md`; `openspec validate +- [x] 6.3 Прогон DoD: `make up` → `make check-full` → ручная проверка из `docs/dev/README.md`; `openspec validate slice-0-skeleton --strict`; вывод `make check-full` — в описание PR