- README: верхняя граница задержки settle, шаг 5 «Полного круга»; критерии ошибочного файла (корень не объект) - CLAUDE.md: по одному json на каждый распознанный документ; критерии ошибочного файла - design.md: D-5 — оговорка про нестроковый error.code отдельным предложением; №79 — источник указания, SQL уборки в порядке FK - open-questions-proposal.md п.20: замены ../docs/dev/README.md (185–187, 224–226, 252–253) и комментарий docker-compose.yml:184 - план: Task 6 — пометка про замену down -v (R-27) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
179 lines
30 KiB
Markdown
179 lines
30 KiB
Markdown
# Предложения по итогам Среза 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`.
|
||
|
||
**Фрагмент рабочий только после исправления C-1** (ветка `feat/1-slice-0-skeleton`, коммит `ad3fb34`): раньше
|
||
`python -m src.scan_emulator` импортировал пакет `src`, а тот создавал приложение шлюза и требовал `OPENBAO__*`, поэтому
|
||
контейнер эмулятора без переменных MinIO/NATS падал с `ValidationError`. Теперь импорт пакета `src` настроек шлюза не
|
||
требует; проверено на пересобранном образе: `docker run --rm -e SCAN_HOTFOLDER_PATH=/nonexistent scan-gateway:dev
|
||
python -m src.scan_emulator` завершается кодом 2 с сообщением `Эмулятор не запущен: корень горячей папки /nonexistent не
|
||
существует или не каталог` (не `ValidationError`); шлюз из того же образа стартует, `/healthcheck` отвечает 200.
|
||
|
||
Проверить после применения фрагмента:
|
||
|
||
- `docker compose --profile services up -d --wait scan-gateway scan-emulator` возвращает код 0: `scan-gateway`
|
||
зависит от `minio-init` (`service_completed_successfully`), а в списке `--wait` одноразового `minio-init` нет (по
|
||
`../docs/dev/README.md` голый `docker compose up -d --wait` возвращает 1 из-за завершившегося `minio-init`);
|
||
- в логе эмулятора при старте — строка о каталогах и строка о числе фикстур (формат `run()` в
|
||
`api/src/scan_emulator/__init__.py`: `Эмулятор ИС Сканирование: корень {}, входящий каталог {}, исходящий каталог {},
|
||
задержка {} с, опрос {} с` и `Фикстуры: {}, фикстур: {}`), то есть `Эмулятор ИС Сканирование: корень /data/hotfolder,
|
||
входящий каталог /data/hotfolder/Input, исходящий каталог /data/hotfolder/Output, задержка 2.0 с, опрос 1.0 с` и
|
||
`Фикстуры: /fixtures/ocr, фикстур: 2`;
|
||
- `scan-gateway` — `healthy`, `/healthcheck` на `127.0.0.1:8002` даёт `nats: ok`, `hotfolder: ok`.
|
||
|
||
Примечания: (а) порт хоста 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 | **Решено человеком 04.10.2026 (OQ-76, частично):** исходно — реальные фикстуры `fixtures/ocr/aep_5.json`, `aep_8_form4.json` были ответами-ошибками ИС (`error.code = INVALID_DOCUMENT`, `data_ocr` — объект с пустыми полями и `table_structure: []`). Добавлены реальные успешные образцы: `aep_3.pdf` + `aep_3.json` (успех: «Бухгалтерский баланс» 42 строки + «Отчет о финансовых результатах» 18 строк) + `aep_3_ofr.json` (второй ответ ИС по тому же файлу, `INVALID_DOCUMENT`), `aep_6.pdf` + `aep_6.json` (успех: баланс 122 строки с шапкой дат + ОФР 49 строк); `aep_5.json`, `aep_8_form4.json` — ответы-ошибки | O-1, R-10 (design #32); OQ-76 | Транспорт Среза 0 проверяется на реальных образцах (склейка, `NOT_IMPLEMENTED`, `metrics.warnings`), синтетический успешный ответ не нужен; разбор успешных ответов — Срез 3 (нормализатор, OQ-13/14) |
|
||
| 4 | **Решено человеком 04.10.2026 (OQ-77):** исходно — §11 `05-domain-rules.md` описывал `data_ocr[]` (список), а в фикстурах `data_ocr` — объект. Факт по `aep_3.json`, `aep_6.json`: `data_ocr` — **объект**, один на json-файл (`label`, `organization`, `inn`, `units`, `date` — `{form_name, confidence}` с `confidence` 0–100; `table_structure` — **список таблиц** `{label, rows[]}`; в одном json может быть несколько таблиц разных форм (в `aep_3.json` и `aep_6.json` — баланс и ОФР), у каждой свой `label`; тип документа задаёт `label`; таблицы не интерпретируются в Срезе 0 (Срез 3)); успех — `error = {"code": "", "message": ""}` (не `null`), ошибка — непустой `code` и пустой `table_structure`. §11 исправлен в `docs` | O-1, T3.2; OQ-77 | В Срезе 0 успех файла определяется как `error.code == ""` (design #57); разбор таблиц — Срез 3 |
|
||
| 5 | **Решено человеком 04.10.2026 (OQ-78):** исходно шлюз ожидал единственный файл `<output>/<job_id>/<job_id>.json`, старый код читал все файлы каталога `Output/<id>/`. Факт: ИС кладёт **все распознанные документы одновременно** в каталог `<job_id>`, по одному json на документ (`aep_3.json` + `aep_3_ofr.json`); шлюз читает **все** `*.json` каталога и склеивает в один ответ; готовность — по стабилизации набора (`SCAN_SETTLE_SECONDS`, 2 с); ошибка одного документа не отменяет остальные (`metrics.warnings`) | T8.5; OQ-78 | Реализуется доработкой PR #3 (tasks 7.x, design #54–#58); подходящее значение settle и поведение ИС на реальной шаре — проверить на стенде в Срезе 3 |
|
||
| 6 | **Решено человеком 04.10.2026 (OQ-79):** исходно временный файл `.<имя>.part` лежал в `Input` (R-5), и ИС без маски могла забрать недописанный файл. Решение: временный файл уходит в служебный каталог `SCAN_TMP_DIR` (по умолчанию `Tmp`) рядом с `Input`/`Output`, на той же шаре/ФС, затем `rename` в `Input` (атомарно) | R-5, T7/T8; OQ-79 | Реализуется доработкой PR #3 (tasks 7.x, design #59); `Tmp` должен лежать на той же шаре, что `Input`; поведение ИС на реальной шаре — проверить в Срезе 3 |
|
||
| 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 | Порт шлюза на хосте задаётся `${SCAN_GATEWAY_PORT:-8002}` (фрагмент выше), но в `../docs/dev/.env.example` и в таблице переменных `../docs/dev/README.md` его нет | T16.1 | Владельцу `docs`: добавить `SCAN_GATEWAY_PORT=8002` в `../docs/dev/.env.example`, строку в таблицу переменных `../docs/dev/README.md` (рядом с `MINIO_CONSOLE_PORT`) и 8002 — в список свободных портов хоста |
|
||
| 17 | Replay с `attempt + 1` для `job_id`, который есть в LRU процесса, получает только ack и не обрабатывается (R-17, spec «обработан этим процессом»); по `../docs/context/03-architecture.md` replay — «сброс в `queued` с `attempt + 1`». На единственном инстансе такое задание повисает до `reply_deadline` -> `timeout` | I-3, R-17 | При одобрении: LRU `_done` с ключом `(job_id, attempt)` — более высокий `attempt` готового задания обрабатывать заново; `_active` — по `job_id` (повтор во время обработки — только ack). Решает человек |
|
||
| 18 | `make up` в tabular-parser (`../tabular-parser/Makefile`, цель `up`) тоже не запускает `minio-init`: на свежем томе бакета нет | I-1 | Чужой репозиторий — только сообщить владельцу; в scan-gateway исправлено (`run --rm minio-init` после `up -d --wait`) |
|
||
| 19 | Расхождения и наблюдения контроллера при исполнении (`Observation` в `progress.md`) | `progress.md` | См. ниже |
|
||
| 20 | Сквозная проверка 7.5 (Task 6, 04.10.2026) на профиле `services`: `aep_3.pdf` → `error NOT_IMPLEMENTED` + `metrics.warnings` про `<job_id>_ofr.json`, `aep_6.pdf` → `error NOT_IMPLEMENTED` без `metrics`, `aep_5.PDF` → `error INVALID_DOCUMENT` + одна строка `warnings`; **`../docs/dev/docker-compose.yml` функциональных правок не требует** (только устаревший комментарий, п. 11): умолчания `SCAN_TMP_DIR=Tmp` и `SCAN_SETTLE_SECONDS=2` подходят (`Tmp` создаётся шлюзом в томе `hotfolder`, после прогона пуст). Устарел раздел «Проверка окружения» `../docs/dev/README.md` (сценарий 2 и абзац «не покрывает») | Task 6, R-37 (design #78) | Владельцу `docs`, в `../docs/dev/README.md`: (1) строки 220–221 заменить на `# [2026-10-04 11:23:58 UTC] 600B STANDARD 0ea16933-4cc1-4b50-bda9-a97089a918c5.json` и `dmc cat "local/arm-ka/raw/$JOB.json" # {"job_id": "<job_id>", "files": [{"name": "<job_id>.json", "response": {"file_uid": "<job_id>", "error": {"code": "INVALID_DOCUMENT", "message": "Не удалось определить тип документа."}, "data_ocr": {…}}, "parse_error": null}]}`; (2) в строке 226 лог эмулятора — `<job_id>_aep_5.PDF -> /data/hotfolder/Output/<job_id>` (каталог набора, не файл); (3) строки 227–229 («успешный путь image … не проходит — нужен успешный ответ ИС») заменить на: «Успешные ответы ИС есть (`aep_3.json` + `aep_3_ofr.json`, `aep_6.json`); шлюз склеивает все `*.json` каталога ответа и отвечает `NOT_IMPLEMENTED` (нормализация — Срез 3), ошибочные файлы набора — в `metrics.warnings`»; (4) строку 230 заменить на «`processing` виден ~10 с: задержка эмулятора 2 с, затем до `SCAN_POLL_SECONDS + SCAN_SETTLE_SECONDS` (5 + 2 с) после первого опроса, увидевшего набор»; (5) в строке 255 «у ошибок `scan-gateway` `recognition_job.metrics` пустой» заменить на «у ошибок `scan-gateway` в `recognition_job.metrics` только `warnings` — по строке `<имя файла>: <код>: <сообщение>` на ошибочный файл ответа ИС, если такие есть»; (6) после сценария 2 добавить сценарий «2а. `image` → успешный ответ ИС → `NOT_IMPLEMENTED`»: `new_job image PDF ocr/aep_3.pdf && poll 60` → `# 11:22:07 queued` / `# 11:22:08 processing` / `# 11:22:18 error NOT_IMPLEMENTED`; `dpsql -c "select status, error_code, metrics from dkb.recognition_job where id = '$JOB'"` → `# error | NOT_IMPLEMENTED | {"warnings": ["<job_id>_ofr.json: INVALID_DOCUMENT: Не удалось определить тип документа."]}`; `new_job image PDF ocr/aep_6.pdf && poll 60` → `error NOT_IMPLEMENTED`, `metrics` пусто; (7) в той же строке 255 «полная очистка — `docker compose --profile services down -v`» дополнить оговоркой, что это удаляет и общие тома `postgres-data`/`minio-data` всех worktree (R-27, design #77); (8) строки 185–187 (абзац «Рецепт прогнан дословно при уже существовавшей в БД строке `legal_entity` … отдельно не проверялась.») заменить на «Рецепт прогнан дословно на чистой БД (вставка `legal_entity` без конфликта по `inn`) и повторно при уже существующей строке (`on conflict (inn) do nothing`); оба пути проверены.» (или удалить абзац: ветка вставки на чистой БД проверена в Task 6); (9) строки 224–226: «Код `INVALID_DOCUMENT` здесь — **настоящий ответ ИС из фикстуры**: обе пары в `fixtures/ocr` (`aep_5.json`, `aep_8_form4.json`) — ответы-ошибки ИС Сканирование («Не удалось определить тип документа.», пустой `table_structure`). Эмулятор нашёл пару (в логе `scan-emulator`: `<job_id>_aep_5.PDF -> …/Output/<job_id>/<job_id>.json`; при отсутствии пары сообщение было бы «…: нет эталонного ответа в фикстурах»).» заменить на «Код `INVALID_DOCUMENT` здесь — **настоящий ответ ИС из фикстуры**: `aep_5.json` и `aep_8_form4.json` — ответы-ошибки ИС Сканирование («Не удалось определить тип документа.», пустой `table_structure`); у `aep_3` и `aep_6` есть успешные ответы (сценарий 2а). Эмулятор нашёл набор ответов (в логе `scan-emulator`: `<job_id>_aep_5.PDF -> /data/hotfolder/Output/<job_id>`; при отсутствии набора сообщение было бы «…: нет эталонного ответа в фикстурах»).» (строка 227 «Поэтому успешный путь `image` … не проходит — нужен успешный ответ ИС в `fixtures/ocr`.» заменяется по (3)); (10) строки 252–253: «успешные результаты (`done`/`needs_check`, регистрация файла `results/…` в `s3_object`) — заглушка парсера и фикстуры ИС дают только ошибки;» заменить на «успешные результаты (`done`/`needs_check`, регистрация файла `results/…` в `s3_object`) — заглушка парсера и нормализатор шлюза Среза 0 (`NOT_IMPLEMENTED`) дают только ошибки, хотя фикстуры ИС (`aep_3`, `aep_6`) успешные ответы содержат;»; (11) `../docs/dev/docker-compose.yml:184`: «# Фикстуры ответов ИС — ../../fixtures/ocr (только чтение): пара <имя>.json для входного файла UUID_<имя>.ext.» заменить на «# Фикстуры ответов ИС — ../../fixtures/ocr (только чтение): набор <имя>.json + <имя>_*.json для входного файла UUID_<имя>.ext.» (только комментарий, поведение compose не меняется) |
|
||
|
||
Строки `Observation` из `progress.md` (только пересказ, статусы — в ledger):
|
||
|
||
- **O-1** — **[закрыто 04.10.2026: образцы `aep_3.json`, `aep_6.json` (OQ-76), пункты 3 и 4 таблицы]**. Исходно: обе
|
||
реальные фикстуры (`aep_5.json`, `aep_8_form4.json`) сами являлись ответами-ошибками ИС (`INVALID_DOCUMENT`, `data_ocr`
|
||
— объект, не список), поэтому путь `NOT_IMPLEMENTED` проверялся синтетическим ответом (R-10, design №32, заменено D-9);
|
||
теперь он проверяется на реальных успешных ответах.
|
||
- **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`.
|
||
- [ ] Перенести правки раздела «Проверка окружения» `../docs/dev/README.md` по итогам сквозной проверки 7.5 (пункт 20) —
|
||
тем же PR репозитория `docs`; compose правки не требует.
|
||
- [ ] Согласовать с владельцем tabular-parser семантику `PUBLISH_RETRIES` (пункт 1) и решить судьбу правила про
|
||
`SCHEMA_MISMATCH` + ack (пункт 2).
|
||
- [x] Пункты 3-6 (OQ-76/77/78/79: образцы успешных ответов, формат `data_ocr`, состав файлов ответа, временный файл)
|
||
решены человеком 04.10.2026 и внесены в `docs/context/07-open-questions.md`; пункты 7, 8, 15 — в follow-up issue
|
||
(`Follow-ups from #1`).
|
||
- [ ] Не архивировать change `slice-0-skeleton`, пока compose-фрагмент не применён в `docs` и не проверена команда
|
||
`docker compose --profile services up -d --wait scan-gateway scan-emulator` (I-2).
|
||
- [ ] Решить I-3 (пункт 17: replay с `attempt + 1` для задания из LRU процесса).
|
||
- [ ] Определить приоритет follow-up по T12.5 (пункт 8): при каждом деплое задания в работе превращаются в
|
||
окончательный `timeout`.
|